19- Arduino, Funciones de Tiempo


Los timers de Arduino.

Comencemos con c贸mo el microcontrolador generalmente sabe cu谩nto tiempo pasa. 隆No tiene reloj! Para el funcionamiento del microcontrolador, es vital un llamado generador de reloj, o un oscilador de cristal, o un cristal de cuarzo. Es un oscilador, tambi茅n es un reloj. Clock en ingl茅s es reloj. S铆, pero no todo es tan simple. El cristal de cuarzo se encuentra junto al MC en la placa (tambi茅n muchos MC tienen un generador de reloj incorporado), las placas Arduino suelen tener un oscilador de 16 MHz, tambi茅n hay modelos de 8 MHz. El generador de reloj hace algo muy simple: acciona el microcontrolador a su propia frecuencia de reloj, es decir, un cristal de 16 MHz acciona el MC 16 millones de veces por segundo. El microcontrolador, a su vez, conociendo la frecuencia del cuarzo, puede estimar el tiempo entre ciclos (16 MHz = 0.0625 microsegundos), y as铆 navegar en el tiempo. Todos los microcontroladores poseen temporizadores internos. Estos son dispositivos ubicados f铆sicamente dentro del MC que se dedican a contar los pulsos del generador de reloj. 

Y ya podemos usar esto, para esto Arduino tiene funciones de tiempo listas para usar. Hay tres contadores en el ATmega328 de Arduino, y el temporizador n煤mero 0 es responsable de contar el tiempo. Cualquier otro contador puede hacer esto, pero trabajando en el IDE de Arduino obtienes inmediatamente esta configuraci贸n, porque al crear un boceto en el IDE de Arduino, trabajamos autom谩ticamente con la biblioteca Arduino.h, donde se implementan todas las funciones de tiempo necesarias


.

Retrasos o delays.

La funci贸n de tiempo m谩s simple desde el punto de vista de uso es la demora, tenemos dos de ellas:

  • delay (time) – 芦suspende禄 la ejecuci贸n del c贸digo por milisegundos. Durante delay() el c贸digo no se ejecuta,  excepto las interrupciones. Se recomienda usarlo solo en los casos m谩s extremos o en aquellos casos en los que la demora no afecta la velocidad del dispositivo. El parametro time toma el tipo de datos unsigned long y puede pausar la ejecuci贸n desde 1 ms a ~ 50 d铆as (4,294,967,295 milisegundos) con una resoluci贸n de 1 milisegundo. Se ejecuta en el temporizador del sistema Timer 0, por lo que no funciona dentro de una interrupci贸n y cuando las interrupciones est谩n deshabilitadas.
  • delayMicroseconds (time) – An谩logo a delay(), pausa la ejecuci贸n del c贸digo durante time en microsegundos. time toma el tipo de datos unsigned int y puede pausar la ejecuci贸n de 4 a 16383 渭s con una resoluci贸n de 4 渭s. Importante: delayMicroseconds no funciona en un temporizador, como otras funciones de tiempo en Arduino, sino en la cuenta del reloj del procesador. De esto se deduce que delayMicroseconds puede funcionar en interrupci贸n y con interrupciones desactivadas.

Los retrasos son muy f谩ciles de usar:

void setup() {}
void loop() {
  // codigo
  delay(500);  // espera medio segundo
}

De esta manera podemos hacer algo de c贸digo dos veces por segundo por ejemplo. La funci贸n delayMicroseconds () a veces no funciona correctamente con variables, debe intentar usar constantes (const o simplemente un n煤mero). Para crear retrasos de microsegundos con un per铆odo variable y trabajar correctamente en ciclos, es mejor usar la siguiente construcci贸n:

// funci贸n de retardo 渭s casera
void myDelayMicroseconds(uint32_t us) {
  uint32_t tmr = micros();
  while (micros() - tmr < us);
}

Pero, 驴qu茅 pasa si necesitamos realizar una acci贸n dos veces por segundo y las otras tres? Y el tercero, 10 veces por segundo, por ejemplo. Inmediatamente nos acostumbramos a la idea de que es mejor no usar retrasos en el c贸digo real. Excepto esto delayMicroseconds (), a veces es necesario generar alg煤n tipo de protocolos de comunicaci贸n. Una herramienta normal para la gesti贸n del tiempo de su c贸digo son las funciones que cuentan el tiempo desde el inicio del MC.


Funciones de conteo de tiempo.

Estas funciones devuelven el tiempo transcurrido desde el inicio del microcontrolador, llamado uptime (Engl. Uptime). Tenemos dos de esas funciones:

  • millis ()– Devuelve el n煤mero de milisegundos desde el inicio. Es unsigned long, de 1 a 4 294 967 295 milisegundos (~ 50 d铆as), tiene una resoluci贸n de 1 milisegundo, despu茅s del desbordamiento se restablece a 0.  Se ejecuta en el temporizador del sistema Temporizador 0.
  • micros ()– Devuelve el n煤mero de microsegundos desde el inicio. Es un unsigned long, de 4 a 4,294,967,295 microsegundos (~ 70 minutos), tiene una resoluci贸n de 4 microsegundos, se restablece a 0 despu茅s del desbordamiento.  Se ejecuta en el temporizador del sistema Temporizador 0.

Puede preguntar, 驴c贸mo nos ayudar谩 el tiempo desde el inicio del MC a organizar acciones en el tiempo? Es muy simple, el esquema es as铆:

  • Tom贸 medidas
  • Recordamos la hora actual desde el inicio del MC (en una variable separada)
  • Buscamos la diferencia entre la hora actual y la memorizada
  • Tan pronto como la diferencia sea mayor que el tiempo requerido del 芦Temporizador禄, realizamos la acci贸n
  • 芦Restablecemos禄 el temporizador
    • Aqu铆 hay dos opciones, igualar la variable del temporizador con el milis () actual o aumentarla por el tama帽o del per铆odo

La implementaci贸n de dicho temporizador en millis () se ve as铆:

// variable de almacenamiento de tiempo (unsigned long)
uint32_t myTimer1;
void setup() {}
void loop() {
  if (millis() - myTimer1 >= 500) {  // buscando la diferencia (500 ms)    
    myTimer1 = millis();              // reiniciar el temporizador
    // realizar una acci贸n
  }
}

La segunda opci贸n para restablecer el temporizador se escribir谩 as铆:

// variable de almacenamiento de tiempo (unsigned long)
uint32_t myTimer1;
int period = 500;
void setup() {}
void loop() {
  if (millis() - myTimer1 >= period) {   // buscando la diferencia (500 ms)  
    myTimer1 += period;              // reiniciar el temporizador
    // realizar una acci贸n
  }
}

驴Cu谩les son las ventajas y desventajas? La primera forma (temporizador = milis () 馃槈 芦Sale禄 si aparecen retrasos y otras causas de bloqueo en el c贸digo, durante la ejecuci贸n de las cuales millis () logra aumentar durante m谩s de un per铆odo de tiempo y, a largo plazo, 隆el per铆odo 芦desaparecer谩禄! Pero al mismo tiempo, si bloquea la ejecuci贸n del c贸digo por un tiempo superior a un per铆odo, el temporizador corregir谩 esta diferencia, ya que la reseteamos con los milis actuales.

La segunda forma ( temporizador + = per铆odo;) cumple r铆gidamente el per铆odo, es decir, no 鈥渄esaparece鈥 con el tiempo si hay un peque帽o retraso en el c贸digo. La desventaja aqu铆 es que si el temporizador salta un per铆odo, se 鈥渄isparar谩鈥 varias veces durante la siguiente verificaci贸n.

D茅jame recordarte que uint32_t este es el segundo nombre del tipo de datos entero largo sin signo, unsigned long, es m谩s corto de escribir. 驴Por qu茅 una variable tiene que ser de este tipo? Porque la funci贸n millis () devuelve exactamente este tipo de datos, es decir si hacemos nuestra variable como int se desbordar谩 en 32,7 segundos. Pero el milis tambi茅n est谩 limitado a 4.294.967.295, y en caso de desbordamiento tambi茅n se restablecer谩 a 0. Lo har谩 en 4 294 967 295/1000/60/60/24 = 49,7 d铆as. 驴Significa esto que nuestro temporizador se 芦romper谩禄 despu茅s de 50 d铆as? No, esta construcci贸n sobrevive tranquilamente al desbordamiento poni茅ndose a 0 y sigue.

Verificaci贸n de desbordamiento (overflow)

Veamos un ejemplo de c贸digo para verificar si ha habido desbordamiento en el contador:

// millis se almacena en la variable timer0_millis en los archivos del kernel
// extern para modificaci贸n directa
extern volatile unsigned long timer0_millis;
void setup() {
  Serial.begin(9600);
  // 5 segundos antes del desbordamiento de milis
  timer0_millis = UINT32_MAX - 5000;
}
uint32_t timer;
void loop() {
 // nuestro temporizador predeterminado con un segundo per铆odo
  if (millis() - timer >= 1000) {
    timer = millis();
    // muestra milisegundos
    Serial.println(millis() / 1000L);
     // mira como super贸 el desbordamiento
    // y sigue funcionando
  }
}

驴Por qu茅 este dise帽o funciona y no se rompe? Porque estamos usando un tipo de datos sin signo que, cuando se desborda, comienza a contar desde cero. Por lo tanto, cuando los milis se vuelven cero y crecen, y le restamos un n煤mero enorme, obtenemos no un valor negativo, sino un valor completamente correcto, que es el tiempo desde el reinicio anterior del temporizador. Por lo tanto, la estructura no solo contin煤a funcionando despu茅s de ~ 50 d铆as, sino que tambi茅n pasa el momento de 芦desbordamiento禄 sin perder el per铆odo.

Volvamos al tema de la multitarea: queremos realizar una acci贸n dos veces por segundo, la segunda – tres y la tercera – 10. Necesitamos 3 variables de temporizador y 3 construcciones con la condici贸n:

// variable de almacenamiento de tiempo (unsigned long)
uint32_t myTimer1, myTimer2, myTimer3;
void setup() {}
void loop() {
  if (millis() - myTimer1 >= 500) {   // temporizador de 500 ms (2 veces por segundo)
    myTimer1 = millis();              //reinicia
    // realizar la acci贸n 1
    // 2 veces por segundo
  }
  if (millis() - myTimer2 >= 333) {   // temporizador de 333 ms (3 veces por segundo)
    myTimer2 = millis();              // reinicia
    // realizar la acci贸n 2
    // 3 veces por segundo
  }
  if (millis() - myTimer3 >= 100) {   // temporizador de 100 ms (10 veces por segundo)  
    myTimer3 = millis();              //reinicia
   // realizar la acci贸n 3
    // 10 veces por segundo
  }
}

Y as铆 es como podemos, por ejemplo, sondear el sensor 10 veces por segundo, filtrar los valores y mostrar las lecturas en la pantalla dos veces por segundo. Y parpadea una luz tres veces por segundo. Por qu茅 no?

C贸digo optimizado para temporizaciones con millis()

Recientemente, me hice una pregunta: 驴es posible hacer un temporizador con milis(), que omita correctamente el desbordamiento sin eliminar el per铆odo?

#define PERIOD 500
uint32_t timer = 0;
void loop() {
  if (millis() - timer >= PERIOD) {
    do {
      timer += PERIOD;
      if (timer < PERIOD) break;  // desbordamiento de uint32_t  
    } while (timer < millis() - PERIOD); // protecci贸n contra saltarse un paso
  }
}

Este temporizador tiene la mec谩nica de un temporizador cl谩sico con el almacenamiento en una variable de temporizador, y su per铆odo es siempre un m煤ltiplo de PERIOD y no se pierde. Esta construcci贸n se puede simplificar para aun m谩s:

#define PERIOD 500
uint32_t timer = 0;
void loop() {
  if (millis() - timer >= PERIOD) {
    timer += PERIOD;
  }
}

En este caso, el algoritmo resulta ser m谩s corto, se conserva la multiplicidad de los per铆odos (el per铆odo no desaparece si hay retrasos en el c贸digo), pero se pierde la protecci贸n contra perdidas.


Deja un comentario