01_interrupciones - jospicant/Attiny GitHub Wiki

Interrupciones Externas

El Attiny85 tiene dos fuentes posibles de interrupción externa:

  • INT0: interrupción que se produce en el pin7 (PB2),puede producirse por flanco o por nivel.
  • PCINT0-PCINT5: interrupción por cambio de estado en los pines 1,2,3,5,6 y 7 (PB0 a PB5 )

PinOut

El Attiny85 maneja varios vectores de interrupción y en este caso nos interesan las interrupciones producidas por acción en un pin externo:

Vector_Interrupción

El programa debe habilitar la posibilidad de que se produzcan interrupciones y cuando se produzca la interrupción se llamará a la rutina de interrupción afectada.

Si se habilitan las interrupciones externas, estas se producirán aunque sus pines estén configurados como salidas.

Las interrupciones PCINT0-5 son ASÍNCRONAS por lo que no necesitan del reloj para producirse, esto nos permite despertar al micro desde otros modos de "sleep" distintos al modo idle.

Las Interrupciones de INT0 se pueden producir de forma:

  • SÍNCRONA; Por detección del flanco de subida/bajada,y para ello debe existir reloj (no podremos despertar al micro por flanco usando esta, si no tenemos un reloj funcionando).

  • ASÍNCRONA; Por detección de nivel bajo, en este caso no necesitamos un reloj y sí podríamos despertar un micro usando esta interrupción si el modo en el que duerme tiene el reloj parado. Eso sí, deb e mantenerse a nivel bajo el suficiente tiempo, si no, podría despertar al micro pero no ejecutarse la rutina de interrupción. (ese considerando el caso de que se use la interrupción para despertar un micro).

Los registros implicados para configurar correctamente las interrupciones externas son:

  • SREG: Registro de estado, configurando el bit I podremos habilitar o deshabilitar todas las interrupciones posibles, por lo cual, para que se pueda producir una interrupción, este bit I debe ponerse a 1.
    Este bit se pone a 0 por hardware después de producirse una interrupción y se puede habilitar con la instrucción RETI para que se puedan producir otras interrupciones una vez atendida la interrupción producida (última instrucción de la rutina de interrupción).
    Con las funciones sei() y cli() (<avr/interrupts.h>) se puede configurar dicho bit de interrupción.

SREG

  • GIMSK: con este registro habilitamos que interrupción externa usaremos INT0 o PCINTx

GIMSK

  • PCMSK: Con dicho registro seleccionamos de forma individual que pin de PCINT[5:0] queremos configurar como interrupción por cambio de nivel.

PCMSK

  • MCUCR: Configurando los bits 1:0 Interrupt Sense Control ISC0[1:0] elegiremos el modo en el cual trabaja la interrupión INT0, por flanco, nivel bajo o cualquier cambio.

MCUCR

ICS01-00

  • GIFR: Registro que nos mostrará los FLAGS generales de las interrupciones externas INT0-PCINTx.

En el bit INTF0 nos mostrará si se ha producido una interrupción INT0. Si I=1 (SREG), INT0=1 (GIMSK) y se produce una interrupción en el pin INT0, la MCU saltará al vector de interrupción INT0 y el Flag se pondrá a 0 al finalizar la rutina de interrupción. Nota: Este flag siempre está a 0 si INTO se configura por nivel.

En el bit PCIF nos mostrará si se ha producido una interrupción por PCINTx. Si I=1 PCIE=1 y se produce interrupción PCINTx, la MCU saltará a la rutina de interrupción PCINT y cuando termine esta, el flag PCIF se pondrá a 0.

GIFR

  • Podemos ver un ejemplo donde usamos la interrupción INT0 (pin7) por nivel bajo para despertar el micro:
/*******************************************************************************************************************************************************************************
 * *****************************************************************************************************************************************************************************
 *
 * Pondremos a dormir el Attiny, en modo  SLEEP_MODE_PWR_DOWN. Este modo es el que consume menos ya que deshabilita casi todos los relojes
 * a excepción del Watchdog y solo puede despertar por INT0,Pin Change int (PCIE), Condición de inicio por USI (por puerto Serie) o por WatchDog.
 *
 * Despertaremos al Attiny produciendo una interrupción INT0 por nivel bajo con un pulsador en el PIN7 ( PB2 ) del Attiny
 *  *
 * Al encender el micro por primera vez, realizamos ciertas operaciones y me pongo a dormir ( mediante el flag  "interrupcion_ok" controlamos si ha producido una interrupción )
 * Despertaremos por la INT0 a nivel bajo ( asíncrono, no necesitamos del reloj ppal, por lo q podremos pararlo y no nos impidirá despertar con esta interrupción )
 * Una vez despertamos haremos lo que necesitemos y volveremos a dormir esperando otra int INT0.
 *
 * Se controla bien cuando están las interrupciones activas para evitar que se produzcan interrupciones incontroladas, solo las queremos para despertar el micro
 * por el pin7
 *
 *
 *                                                             CONSUMO: 0.2 uA durmiendo (Alimentando con Pila CR2032 )
 *                                                             
 *                                                             FUSE LOW BYTE:       0x62
 *                                                             FUSE HIGH BYTE :     0xDF
 *                                                             EXTENDED FUSE BYTE:  0xFF
 *                                                             LOCK BIT BYTE:       0xFF
 *
 *
 ******************************************************************************************************************************************************************************
 ******************************************************************************************************************************************************************************/

#include <Arduino.h>
#include <avr/sleep.h>


#define led 0   // led en PB0

//#define adc_disable() bitClear(ADCSRA,ADEN)   
//#define adc_enable()  bitSet(ADCSRA,ADEN)
#define adc_disable() (ADCSRA &=~(1 <<ADEN))   // ADEN = 7   1 << 7  = >  1000_0000 invertido = > 0111_1111  esto AND con lo que había lo q hace es poner a 0 el bit ADEN  ADEN=0
#define adc_enable() (ADCSRA |= (1 <<ADEN))    //                         1000_0000 or con ADCSRA =>  ADEN =1

#define Calib_ms 49                            // 49 ciclos for = 1ms ( a una frecuencia de 1 MHz )

volatile bool interrupcion_ok = false;         // Uso tipo volatile para que la interrupción no le afecte


//************************************************************************************************************************************************************************
//************************************************************************************************************************************************************************
void setup() {

  cli();  // Si Deshabilito interrupciones, delay() ya no funcionará y si lo uso no se comportará correctamente pq usa las interrupciones de los timers --> uso una función DelMs(donde calibro aprox los ms )
  pinMode(led,OUTPUT);
  //pinMode(PB1,INPUT_PULLUP);
  pinMode(PB2,INPUT_PULLUP);              //pulsador para despertar por INT0
  //pinMode(PB3,INPUT_PULLUP);
  //pinMode(PB4,INPUT_PULLUP);
  pinMode(PB5,INPUT_PULLUP);             //Pulsador Reset


  bitSet(GIMSK,INT0);                             // GIMSK (  INT0=1  PCIE = 0 ) Configuro INterrupción INT0 ( puerto PB2 = pin7 )
  bitClear(GIMSK,PCIE);
  bitClear(MCUCR,ISC00);                          // MCUCR ( ICSC00 = 0  ISC01 = 0  Activo por nivel bajo la interrupción INT0 )
  bitClear(MCUCR,ISC01);

 }

//*****************************************************************************************************************************************************************************
//*****************************************************************************************************************************************************************************
void loop() {

   static unsigned int cuenta_int=0;

  //********************* Aquí pondré todo lo q tiene q hacer antes de ir a dormir **************************
  //******** evitaré interrupciones pq quiero q haga todo lo que quiera sin q se active la rutina de int ****

  if( interrupcion_ok == false){                         //Primera vez q se alimenta o después de un Reset
    cuenta_int=0;
    interrupcion_ok=false;
    for(int i=0; i<3; i++){ Parpadea_led(250); }
  }
  else{                                                 //Aquí solo entra si viene de una interrupción INT0 ( controlado por flag  interrupcion_ok = false o true )
                                                        //Aquí haré todo lo que quiera hacer tras haber despertado por INT0
    cuenta_int++;                                        //actualizo cuenta y flag de interrupción
    interrupcion_ok=false;

    Parpadea_led(1000);                                  //Hago todo lo q quiera tras ocurrir la interrución q me despertó
    for (int i=0;i<cuenta_int; i++){
      Parpadea_led(200);
    }

   //********************************* Ahora ya lo duermo *************************************************
   //******************************************************************************************************
  }

    A_Dormir();                               // solo despierta por int  INT0, aquí habilitaré int para poder despertar por INT0
                                              // al volver de la interrupción, las interrupciones se activan solas --> sei() por lo q si quiero seguir controlando
                                              // q no se produzcan interr, debo desactivarlas.
    cli();    //importante, si no las desactivo puedo producir una INT0 en cualquier parte del código y prefiero tenerlo controlado y que solo se produzca para despertar
              // el Attiny

}

//************************************************************************************************************
//************************************************************************************************************

//Uso esta función para producir unos retrasos artificiales, sin interrupciones, solo por tiempo usado
//en los bucles for

void DelMs(unsigned int milisg){                         // Nota: las variables de los bucles for se definen como "volatile"
  for (volatile int i=0; i<milisg; i++){                 //Pq si se produjese una interrupción a mitad del for, al volver de la interrupción sigue por donde se quedó ( no pierde la cuenta del for )
    for(volatile int j=0; j<Calib_ms; j++){             //este bucle se calibra para que dure aprox 1 ms
      //x bucles/msg   200msg  == 200 veces * x         // unos 49-50 bucles tardan 1 ms ( a 1MHz ) se puede ajsutar con la variable "Calib_ms"
    }
  }
}

//*************************************************************************************************************
void A_Dormir(){

  bitSet(PRR,PRUSI);                              //Reloj del USI off
  adc_disable();                                  //Antes de dormir desactivo ADC
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);            //y configuro Modo en el que voy a dormir
  sei();                //Habilito int antes de ponerme a dormir pq si no, no podré despertarlo ( salvo reset o apagarlo )
  sleep_enable();       //solo habilitaré int aquí para poder despertar por INT0
  sleep_cpu();
}

//*************************************************************************************************************
void Parpadea_led(int tiempo){
  digitalWrite(led,HIGH); DelMs(tiempo); digitalWrite(led,LOW); DelMs(tiempo);
}


//**************************************************************************************************************
ISR(INT0_vect)
{
  cli();                 //Aseguro int desactivadas para q no se produzca ninguna otra mientras estoy en esta int
  interrupcion_ok=true;  //actualizo Flag
  //cuenta_int++;        // aquí no funciona bien ( aunque sea volatile )
  //cuando sale de la interrupción, se activa automáticamente la interrupción  --> sei()  Ojo con esto.
}
  • podemos ver el código en INT0
⚠️ **GitHub.com Fallback** ⚠️