Módulo 2.1

Zephyr RTOS: Más allá de los básicos

Secuencia de arranque, timers, work queues y FIFOs

Santi Scagliusi, PhD

Comenzar arrow_downward
power_settings_new
Boot Sequence
Secuencia de arranque
timer
Kernel Timers
Tareas periódicas
work
Work Queues
Colas de trabajo
queue
FIFOs
Datos de tamaño variable

Secuencia de Inicialización

Zephyr inicializa el sistema en fases bien definidas antes de llamar a main().

1
PRE_KERNEL_1
Clock control driver, serial driver
2
PRE_KERNEL_2
System timer driver (RTC1)
3
POST_KERNEL
Logging, BLE stack, system workqueue
4
APPLICATION
AT Monitor, static threads de usuario
5
main()
Llamado desde el main thread

info Concepto clave

Cada fase tiene un nivel de prioridad. Los drivers y subsistemas se registran con macros SYS_INIT() indicando en qué fase deben inicializarse.

SYS_INIT(my_driver_init,
POST_KERNEL,
CONFIG_MY_DRIVER_INIT_PRIORITY);
warning
PRE_KERNEL: El scheduler aún no está activo. No se pueden usar APIs que requieran el kernel (sleep, semáforos, etc.).
check_circle
POST_KERNEL y superior: El kernel está activo. Se pueden usar todas las APIs de Zephyr.

Thread Context vs ISR Context

Entender la diferencia es fundamental para escribir código correcto.

account_tree

Thread Context

  • schedule Activación: Por el scheduler
  • timer Duración: Puede ser larga
  • check Bloqueo: Puede bloquearse (sleep, wait)
  • check APIs: Todas las APIs del kernel disponibles

lightbulb Código de aplicación normal se ejecuta en contexto de thread.

bolt

ISR Context

  • electric_bolt Activación: Por evento hardware
  • speed Duración: Debe ser muy corta
  • block Bloqueo: NO puede bloquearse
  • warning APIs: Solo APIs non-blocking
warning NUNCA llamar a funciones bloqueantes desde ISR.

APIs por Contexto

Referencia rápida de qué APIs se pueden usar en cada contexto.

Aspecto
account_tree Thread Context
bolt ISR Context
k_sleep() Permitido Prohibido
k_sem_take() con timeout Permitido Prohibido
k_sem_give() Permitido Permitido
k_mutex_lock() Permitido Prohibido
k_work_submit() Permitido Permitido
tips_and_updates
Regla general: Si una API puede bloquear (esperar), no se puede usar en ISR. Usa k_work_submit() para diferir trabajo pesado a contexto de thread.

Ciclo de Vida del Thread

Un thread transita entre diferentes estados durante su ejecución.

Componentes de un Thread

  • memory
    Control Block (k_thread)
    Metadatos del thread
  • layers
    Stack
    Memoria para variables locales
  • play_arrow
    Entry Point
    Función de entrada
  • priority_high
    Prioridad
    Determina orden de ejecución
  • settings
    Opciones
    Configuración adicional

Estados del Thread

play_circle
Running
Ejecutándose en CPU
arrow_downward
k_yield()
arrow_downward
k_sleep()
pending
Ready
En ready queue
hourglass_empty
Unready
Esperando/Suspendido
info
Transiciones: k_sleep() pasa a Unready, k_yield() pasa a Ready, timeout completado despierta al thread.

Scheduler en Profundidad

¿Cómo decide Zephyr qué thread ejecutar a continuación?

Tipos de Threads por Prioridad

< -N
Meta-IRQ
Puede interrumpir threads cooperativos
< 0
Cooperative
No puede ser interrumpido por el scheduler
>= 0
Preemptible
Puede ser interrumpido por threads de mayor prioridad

lightbulb Menor número = Mayor prioridad

Context Switching

Al cambiar de thread, el scheduler guarda y restaura el estado completo:

  • save Registros del procesador
  • save Stack pointer
  • save Program counter

Puntos de Reschedule

  • check k_yield() - cede voluntariamente
  • check k_sleep() - espera tiempo
  • check Espera completa (semaforo, etc.)
  • check Time slice expira

Kernel Timers

Ejecutar código periódicamente o tras un retardo.

Concepto

Los kernel timers ejecutan un callback cuando expiran. El callback se ejecuta en contexto de ISR (System Timer interrupt).

timer Definicion: K_TIMER_DEFINE()
play_arrow Iniciar: k_timer_start()
stop Detener: k_timer_stop()
warning El callback se ejecuta en ISR. Debe ser rápido y no puede bloquear.
kernel_timer.c
// Definir timer con callbacks
K_TIMER_DEFINE(my_timer, timer_handler, NULL);
void timer_handler(struct k_timer *timer) {
// ISR context - debe ser rápido!
gpio_pin_toggle_dt(&led);
}
int main(void) {
// Iniciar: delay inicial 500ms,
// periodo 500ms
k_timer_start(&my_timer,
K_MSEC(500),
K_MSEC(500));
while (1) {
k_sleep(K_FOREVER);
}
}
One-shot
period = K_NO_WAIT
Periódico
period > 0

Work Queues

Diferir trabajo de ISR a contexto de thread.

Concepto

Los work queues permiten encolar trabajo que se ejecutara en un thread dedicado, no en ISR.

ISR
arrow_forward
W1
W2
W3
arrow_forward
Thread

Dos tipos de Work Queue

public
System Workqueue
Compartido, prioridad -1 (cooperativo)
k_work_submit()
lock
User Workqueue
Privado, más determinista
k_work_queue_init()
work_queue.c
struct k_work my_work;
void work_handler(struct k_work *work) {
// Thread context - puede bloquear
LOG_INF("Processing work item");
k_sleep(K_MSEC(100)); // OK!
}
void isr_handler(void) {
// Desde ISR: encolar trabajo
k_work_submit(&my_work);
}
int main(void) {
// Inicializar work item
k_work_init(&my_work, work_handler);
// ...
}
tips_and_updates
Ventaja: El handler del work item se ejecuta en contexto de thread, pudiendo usar todas las APIs del kernel.

FIFOs para Datos de Tamaño Variable

A diferencia de message queues, los FIFOs manejan datos de tamaño variable.

Concepto

Los FIFOs almacenan punteros a datos alojados en el heap. Requieren que el primer miembro de la estructura sea void *fifo_reserved.

void *fifo_reserved
uint8_t data[256]
uint16_t len
warning
Responsabilidad del desarrollador: Usar k_malloc()/k_free() para gestionar la memoria.

Configuración necesaria

prj.conf
CONFIG_HEAP_MEM_POOL_SIZE=1024
fifo_example.c
K_FIFO_DEFINE(my_fifo);
struct data_item_t {
void *fifo_reserved; // DEBE ser primero!
uint8_t data[256];
uint16_t len;
};
// Productor
struct data_item_t *item;
item = k_malloc(sizeof(*item));
// Llenar item...
k_fifo_put(&my_fifo, item);
// Consumidor
struct data_item_t *received;
received = k_fifo_get(&my_fifo, K_FOREVER);
// Usar received...
k_free(received); // CRITICO!
error
Memory leak: Si no llamas a k_free(), la memoria se pierde para siempre.

Tabla Resumen de Primitivas

¿Cuándo usar cada mecanismo de Zephyr?

Primitiva Características Uso típico
Thread preemptible Stack propio, puede ser interrumpido Código de aplicación general
Thread cooperativo No interrumpido por scheduler Secciones críticas, drivers
System workqueue Stack compartido, prioridad -1 Diferir trabajo de ISR
User workqueue Stack privado, más determinista Tareas ligeras de aplicación
Kernel timer Callback en ISR Tareas periódicas cortas
FIFO Datos de tamaño variable, usa heap Comunicación con memoria dinámica

Conceptos Clave

Los tres pilares para trabajar con Zephyr avanzado.

bolt

ISR Context

Callback de timer y handlers de interrupcion. No bloquear.

account_tree

Thread Context

Work handlers y código normal. Todas las APIs disponibles.

queue

FIFOs con Heap

Siempre liberar memoria. Configurar CONFIG_HEAP_MEM_POOL_SIZE.

tips_and_updates

"Usa work queues para mover trabajo pesado fuera de ISR a contexto de thread."

Regla de oro: ISRs cortas, trabajo largo en threads o work queues.

arrow_forward Siguiente módulo

2.2 Debugging y Troubleshooting

Herramientas y técnicas para depurar aplicaciones Zephyr.