zephyr-temporizacion-timers

Temporización en Zephyr: timers y retardos

  • 4 min

La temporización en Zephyr es la forma de controlar cuándo ocurren las cosas dentro del RTOS.

En un sistema embebido, controlar cuándo ocurren las cosas es tan importante como qué cosas ocurren.

Venimos de ver los Hilos (Threads). Un error muy común al empezar es usar bucles while gigantes para contar tiempo o usar retardos bloqueantes que congelan el procesador. En un RTOS como Zephyr, eso es un pecado capital.

Hoy vamos a ver cómo manejar el tiempo de forma eficiente, permitiendo que el procesador se vaya a dormir (ahorrando batería) mientras espera.

Espera activa y pasiva

Lo primero es distinguir entre dos formas de esperar.

Espera activa (k_busy_wait)

El procesador se queda en un bucle “mirando el reloj”. Consume el 100% de la CPU y de la batería. Nadie más (salvo interrupciones de hardware) puede ejecutarse.

/* Espera 50 microsegundos quemando ciclos de CPU */
k_busy_wait(50);
Copied!

¿Cuándo usarla? Solo para retardos muy cortos (microsegundos) donde el coste de cambiar de contexto (dormir y despertar el hilo) sería mayor que la propia espera. Por ejemplo, al comunicarse con un sensor bit-banging.

Espera pasiva (k_msleep)

El hilo le dice al Kernel: “Despiértame dentro de 1 segundo”. El Kernel marca el hilo como “Suspendido”, saca al hilo de la CPU y pone a ejecutar a otro (o pone la CPU en modo bajo consumo).

/* Duerme 1000 milisegundos liberando la CPU */
k_msleep(1000);
Copied!

Esta es la función que usaréis el 99% de las veces. Recordad siempre usar k_msleep (milisegundos) o k_usleep (microsegundos) en lugar de bucles for vacíos.

Timers de software (k_timer)

Hasta ahora, si queríamos hacer algo periódico, poníamos un k_msleep dentro de un bucle while. Pero, ¿y si queremos ejecutar una función cada 100ms sin ocupar un hilo entero para ello?

Para esto existen los Timers.

Un k_timer es un objeto del Kernel que, cuando su cuenta llega a cero, ejecuta una función de “callback” (llamada de retorno).

Anatomía de un timer

Para usar un timer necesitamos:

Definirlo: Crear la estructura.

Inicializarlo: Decirle qué función ejecutar cuando expire.

Arrancarlo: Decirle cada cuánto tiempo debe saltar.

Ejemplo: un LED controlado por un timer

Vamos a hacer parpadear un LED usando un Timer, sin usar ningún bucle while en el main.

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* Configuración del LED (igual que en el Blinky) */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

/* 1. Definimos la función de expiración (lo que hace el timer) */
void my_timer_handler(struct k_timer *dummy)
{
    /* ¡CUIDADO! Esto se ejecuta en contexto de INTERRUPCIÓN (ISR) */
    gpio_pin_toggle_dt(&led);
}

/* 2. Definimos el Timer estáticamente */
/* K_TIMER_DEFINE(nombre, funcion_expiracion, funcion_stop) */
K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);

int main(void)
{
    if (!gpio_is_ready_dt(&led)) {
        return 0;
    }
    gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);

    /* 3. Arrancamos el Timer */
    /* k_timer_start(timer, duracion_inicial, periodo) */
    /* Empieza en 1 segundo, y repite cada 0.5 segundos */
    k_timer_start(&my_timer, K_SECONDS(1), K_MSEC(500));

    /* El main ya no tiene nada que hacer, puede terminar o dormir */
    return 0;
}
Copied!

El contexto de interrupción (ISR)

Aquí viene el concepto más difícil de entender sobre los Timers en Zephyr.

La función de expiración de k_timer se ejecuta en el controlador de la interrupción del reloj del sistema, no en un hilo normal.

Límites del contexto ISR:

  1. No puedes dormir: Prohibido usar k_sleep, k_mutex_lock o cualquier función que pueda bloquear. Si lo haces, el sistema crasheará con un “Kernel Panic”.
  2. Sé rápido: Entra, haz lo tuyo y sal. No hagas cálculos complejos ni printks largos.

Si necesitas hacer algo complejo cuando salta el timer (ej. leer un sensor I2C lento o enviar por red), el Timer no debe hacerlo directamente. El Timer debe despertar a un Hilo (usando un semáforo o una cola) para que el Hilo haga el trabajo pesado.

Midiendo el tiempo (k_uptime_get)

A veces no queremos esperar, solo queremos saber cuánto tiempo ha pasado. Zephyr mantiene un contador interno del sistema desde el arranque.

  • k_uptime_get(): Devuelve milisegundos (int64_t) desde el boot.
  • k_cycle_get_32(): Devuelve ciclos de reloj (para máxima precisión).

Ejemplo de uso para medir cuánto tarda una función:

int64_t tiempo_inicio = k_uptime_get();

funcion_lenta_de_calculo();

int64_t duracion = k_uptime_get() - tiempo_inicio;
printk("La funcion tardó %lld ms\n", duracion);
Copied!