Esp32&Interrupciones programadas

Dentro de la gama de interrupciones con Arduino están las interrupciones programadas ,también llamadas interrupciones por software y las interrupciones externas,o interrupciones por hardware.

En una interrupción programada, un evento o interrupción se disparará despues de un tiempo programado.

Antes de seguir hay que aclarar un concepto importante ,que si no se entiende .nos dará problemas de comprensión.

Una interrupción no corta la ejecución de un codigo instantaneamente, o sea que hasta que no termine la secuencia no actuará la interrupción, por eso podemos definir restricciones lógicas al proceso de la interrupción. Para poner en práctica esta afirmación habra que esperar a ver el ejemplo de dos servos ,uno de ellos ejecuta un barrido en un tiempo T, y su codigo esta situado en el void loop() en primer lugar, a continuación aparece el codigo de la interrupción que se dispara en un intervalo de tiempo t, pues bien una restricción lógica seria que “T ” no puede ser mayor que ” t”

He leido también por algun post que la función millis() se ve interrumpida durante la interrupción. Pero como veis en el ejemplo de los servos eso no es correcto. Basta con imprimir por el monitor serie dicha función y no pierde la cuenta.

Temporizadores

Son los encargados de controlar los intervalos de tiempo que requerimos para disparar la interrupción.

El ESP32 tiene dos grupos de temporizadores, y cada grupo dos temporizadores de hardware de propósito general. Todos los temporizadores se basan en contadores de 64 bits y preescaladores de 16 bits .

El preescalador se usa para dividir la frecuencia de la señal base (generalmente 80 MHz), que luego se usa para incrementar / disminuir el contador del temporizador . Dado que el preescalador tiene 16 bits, puede dividir la frecuencia de la señal de reloj por un factor de 2 a 65536 , lo que brinda mucha libertad de configuración.

Los contadores del temporizador se pueden configurar para contar hacia arriba o hacia abajo y admitir la recarga automática y la recarga del software . También pueden generar alarmas cuando alcanzan un valor específico, definido por el software. El valor del contador puede ser leído por el programa de software.

VARIABLES

Comenzamos nuestro código declarando algunas variables globales. El primero será un contador que será utilizado por la rutina del servicio de interrupción (ISR) para señalar al “void loop() ” que se ha producido una interrupción.

El contador también es útil porque si por alguna razón el “manejo de una interrupción ·en el “void loop()” toma más tiempo de lo esperado ,se seguirán generando interrupciones cada intervalo de tiempo programado, porque el contador se incrementará en consecuencia.

Como es habitual, dado que esta variable de contador se compartirá entre el “void loop()”y el ISR, debe declararse con la palabra clave volatile, lo que evita que se elimine debido a las optimizaciones del compilador. De forma mas sencilla podemos decir que esta variable sigue en activo a lo largo del tiempo de funcionamiento de la placa.

volatile int contador;// hemos llamado contador a la variable gobal

Tendremos un contador adicional para rastrear el numero de interrupciones que se han producido desde el inicio del programa. Este solo será utilizado por el “void loop() ” y, por lo tanto, no es necesario declararla como volátil.

int interrupcion_num;

Configurador del temporizador:

hw_timer_t * timer = NULL;

portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

Función ISR: “onTimer”

Nuestra función será tan simple como incrementar el contador de interrupciones que le indicará al lazo principal que ocurrió una interrupción. Esto se hará dentro de una sección crítica, declarada con las macros portENTER_CRITICAL_ISR y portEXIT_CRITICAL_ISR, las cuales reciben como parámetros de entrada la dirección de la variable global portMUX_TYPE que declaramos antes.

Actualización: La rutina de manejo de interrupciones debe tener el atributo IRAM_ATTR, para que el compilador coloque el código en IRAM. Además, las rutinas de manejo de interrupciones solo deben llamar a funciones también ubicadas en IRAM. Hay que evitar utilizar demoras dentro de esta función como delay()

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  contador++;
  portEXIT_CRITICAL_ISR(&timerMux);
 
}

FUNCIÓN SETUP

Inicializar el temporizador:

timer = timerBegin(0, 80, true);

Como entrada, esta función recibe el número del temporizador que queremos usar (de 0 a 3, ya que tenemos 4 temporizadores de hardware), el valor del preescaler y una bandera que indica si el contador debe contar hacia arriba (true) o hacia abajo ( false).

Para este ejemplo usaremos el primer temporizador y pasaremos true al último parámetro, por lo que el contador cuenta hacia arriba.

Con respecto al preescalador, la frecuencia de la señal base utilizada por los contadores ESP32 es de 80 MHz . Este valor es igual a 80 000 000 Hz, lo que significa que la señal haría que el contador del temporizador aumentara 80 000 000 veces por segundo.

Aunque podríamos hacer los cálculos con este valor para configurar el número de contador para generar la interrupción, aprovecharemos el preescalador para simplificarlo. Así, si dividimos este valor entre 80 (utilizando 80 como valor del preescaler), obtendremos una señal con una frecuencia de 1 MHz que incrementará el contador del temporizador 1 000 000 tics por segundo. Cada tic tendra una duración de 1 microseg.

Función de manejo del temporizador:

timerAttachInterrupt(timer, &onTimer, true);

Esta función recibe como entrada un puntero al temporizador inicializado, el cual almacenamos en nuestra variable global, la dirección de la función que manejará la interrupción y una bandera que indica si la interrupción a generar es de tipo flanco (true) o nivel (false) .

Valor del contador

timerAlarmWrite(timer, 1000000, true);

A continuación, usaremos la función timerAlarmWrite para especificar el valor del contador en el que se generará la interrupción del temporizador. Entonces, esta función recibe como primera entrada el puntero al temporizador, como segunda el valor del contador en el que se debe generar la interrupción y como tercera una bandera que indica si el temporizador debe recargarse automáticamente al generar la interrupción.

Entonces, como primer argumento pasamos nuestra variable global de temporizador nuevamente, y como tercer argumento pasaremos verdadero, por lo que el contador se recargará y, por lo tanto, la interrupción se generará periódicamente.

Con respecto al segundo argumento, recuerde que configuramos el preescalador para que esto signifique el número de microsegundos después de los cuales debería ocurrir la interrupción. Entonces, para este ejemplo, asumimos que queremos generar una interrupción cada segundo, y así pasamos el valor de 1 000 000 microsegundos, que es igual a 1 segundo.

Importante: Tenga en cuenta que este valor se especifica en microsegundos solo si especificamos el valor 80 para el preescalador. Podemos usar diferentes valores de preescalador y en ese caso necesitamos hacer los cálculos para saber cuándo el contador alcanzará un cierto valor.

Habilitación del contador:

timerAlarmEnable(timer);

.Terminamos nuestra función de configuración habilitando el temporizador con una llamada a la función timerAlarmEnable, pasando como entrada nuestra variable de temporizador

veamos como queda la función setup.

Con estas instrucciones basta para decirle al lazo principal que en cada segundo nuestra variable va a sumar 1




void setup() {
    
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 1000000, true);
  timerAlarmEnable(timer);
 
}


VOID LOOP()


void loop() {
  
     
   
 
  if (contador>0) {
 portENTER_CRITICAL(&timerMux);
    contador--;
    
    portEXIT_CRITICAL(&timerMux);
    
 interrupcion_num++; //sigue el codigo para el manejo de la interrupción
}
}

Entonces, comprobaremos si la variable contador es mayor que cero y si lo es, lo primero que haremos será decrementar este contador, señalando que la interrupción ha sido reconocida y será manejada.

Dado que esta variable se comparte con el ISR, lo haremos dentro de una sección crítica, que especificamos mediante una macro portENTER_CRITICAL y una portEXIT_CRITICAL. Ambas llamadas reciben como argumento la dirección de nuestra variable global portMUX_TYPE.

El manejo real de las interrupciones consistirá simplemente en incrementar el contador con el número total de interrupciones ocurridas desde el inicio del programa.

EJEMPLOS DE UTILIDAD ACLARATORIA

EJEMPLO 1: Entender cuando se produce la interrupción con dos servos.

Con las mismas variables enunciadas en la parte didactica vamos a mover dos servos que barren un ciclo completo 0-180 y 180-0, de forma que uno de ellos estara continuamente funcionando interrumpiendose a los 15 segundos donde actuará el otro servo.

Ya vemos que he introducido un intervalo de tiempo t=15 segundos, mientras que podemos calcular el tiempo de ejecución del barrido del servo_04 T=2*6*180= 2160 mseg. Igualmente el tiempo ti del servo_05 que interrumpe sera

ti=2*180*5=1800 mseg. Vemos que no violamos la restricción de que T>t, .

Con el monitor serie se puede ver perfectamente la secuencia:

Serial.print(“contador= “); Serial.println(contador);

Esta claro que el contador marca 0 aprox cada 2160 msg (tiempo T) lo hace 6 veces (6*2160=12960 msg),antes de los 2160 mseg siguientes mas el tiempo de imprimir por el puerto serie (2172 msg),habran pasado 15 segundos y se activará la interrupción ejecutando tres acciones: primera decrementar la cuenta de count, con lo que count nunca pasara de 0, segunda incrementar el numero de interrupciones totales y tercero: poner en marcha el servo_15 que actuará durante 1800 msg. A continuación comenzara la misma secuencia de forma que se ira monitorizando el numero de interrupciones y marcando 0 como cuenta del contador. En resumen que se produce una interrupción de aprox 2 segundos ( tiempo de la secuencia ” if contador>0″ ) cada 15 segundos.

Hemos introducido la función millis()/1000 para monitorizar el tiempo transcurrido.

SI queremos ser exhaustivos en la medida del tiempo podemos introducir al principio del void loop() “Serial.print (millis())”, para medir los tiempos de cada secuencia.

Os invito a comprobar lo que ocurre si programamos un intervalo de 5 segundos en vez de 15 segundos. Aunque no se viola la restricción comprobarás que se producirá una interrupción a los 4 segundos y la siguiente a los 6 segundos, asi se va repitiendo indefinidamente. Sin embargo a partir de un intervalo programado de 6 segundos, las interrupciones se produciran segun lo previsto, no se si podriamos establecer un intervalo minimo de 3T, o sea el triple del tiempo empleado en la secuencia del proceso que se quiere interrumpir.

Otro comentario,:si introducimos un retraso en la secuencia del manejo de la interrupción mayor que el intervalo, equivaldra a sumar el tiempo de la secuencia y el tiempo del intervalo programado, algo que no tiene mucho sentido si lo que queremos es que la interrupción sea lo mas corta posible, pero siempre en función del tiempo programado. Esta claro que a intervalos mayores,nos podremos permitir el lujo de aumentar el tiem,po empleado en la secuencia del manejo de la interrupción.

#include <Servo.h>

 Servo servo_15;  //declaro objeto servo que conectaré en gpio 15
Servo servo_04;  / declaro objeto servo que conectaré a gpio 04
int pos15;
int pos04;

volatile int contador;
int interrupcion_num;
 hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
 
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  contador++;
   
  portEXIT_CRITICAL_ISR(&timerMux);
 
}
 
void setup() {
 Serial.begin(115200);

 servo_15.attach(15);// conexion del objeto definido a su gpio
   servo_04.attach(04);
 pos15=0;
 pos04=0;
servo_15.write(pos15);
 servo_04.write(pos04);
 
  
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 15000000, true);// INTERVALO DE 15 SEGUNDOS
  timerAlarmEnable(timer);
 
}
 
void loop() {

Serial.println(millis());

   Serial.print("contador= ");

   Serial.println(contador);
   for (pos04 = 0; pos04 <= 180; pos04 += 1) { 

    // in steps of 1 degree
    servo_04.write(pos04);              
   delay(6);                       
  }
  for (pos04 = 180; pos04 >= 0; pos04 -= 1) { 
   servo_04.write(pos04);              
  delay(6);                       
  }
   
 
  if (contador>0) {
 portENTER_CRITICAL(&timerMux);
    contador--;
    
    portEXIT_CRITICAL(&timerMux);
    
 
    interrupcion_num++;
    Serial.print("tiempo transcurrido= ");
    Serial.println(millis()/1000);
    Serial.print("Numero de interrupción:");
    Serial.println(interrupcion_num);
      for (pos15 = 0; pos15 <= 180; pos15 += 1) { 
   
    servo_15.write(pos15);              
   delay(5);                       
  }
  for (pos15 = 180; pos15 >= 0; pos15 -= 1) { 
   servo_15.write(pos15);              
  delay(5);                       
  }
  
  }
}

Ejemplo 2

Igual que el anterior utilizando como isr una variable booleana


#include <Servo.h>

 Servo servo_15;  // create servo object to control a servo
Servo servo_04;

int pos15;
int pos04;

volatile bool control=false;
int interrupcion_num;
 hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
 
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  control=true;
   
  portEXIT_CRITICAL_ISR(&timerMux);
 
}
 
void setup() {
 Serial.begin(115200);

 servo_15.attach(15);
   servo_04.attach(04);
 pos15=0;
 pos04=0;
servo_15.write(pos15);
 servo_04.write(pos04);
 
  
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 6000000, true);
  timerAlarmEnable(timer);
 
}
 
void loop() {
 Serial.println( millis());
   
   for (pos04 = 0; pos04 <= 180; pos04 += 1) { 
    // in steps of 1 degree
    servo_04.write(pos04);              
   delay(6);                       
  }
  for (pos04 = 180; pos04 >= 0; pos04 -= 1) { 
   servo_04.write(pos04);              
  delay(6);                       
  }
   
 
  if (control==true) {
   
    
 portENTER_CRITICAL(&timerMux);
    control=false;
    
    portEXIT_CRITICAL(&timerMux);
    
 
    interrupcion_num++;

    Serial.print("tiempo transcurrido= ");
    Serial.println(millis()/1000);
    Serial.print("Numero de interrupción:");
    Serial.println(interrupcion_num);
      for (pos15 = 0; pos15 <= 180; pos15 += 1) { 
   
    servo_15.write(pos15);              
   delay(5);                       
  }
  for (pos15 = 180; pos15 >= 0; pos15 -= 1) { 
   servo_15.write(pos15);              
  delay(5);                       
  }
  
  }
}

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Translate »