← Índice del curso

Sistemas Embebidos · Tema 14

Superloop y polling

Es la arquitectura del taller y de la mayoría de los productos que hay en el mundo: un bucle que da vueltas, preguntando una y otra vez si hay algo para hacer. Es simple, fácil de depurar y suficiente para más casos de los que se piensa. Para usarla bien hay que saber exactamente cuánto cuesta cada vuelta y dónde se rompe.

01 · Punto de partida

La arquitectura más usada

En el tema 4 presentamos el superloop como la más simple de las cuatro arquitecturas. Ahora que tenemos el vocabulario del tiempo real, podemos ser precisos sobre cuándo funciona y cuándo no. Una consideración previa: los proyectos de firmware que se resuelven bien con un superloop son muchísimos, y resolverlos con algo más complejo es un error de diseño. Un termostato, un control remoto, un sensor que mide y duerme, un control de iluminación: todos viven felices en un bucle.

Pero el superloop tiene una propiedad matemática incómoda: todo lo que el programa hace comparte la misma vuelta. Si una tarea tarda más, todas las demás esperan; si se agrega una función nueva, todas las respuestas se vuelven un poco más lentas. Lo que hace buena a esta arquitectura es su simplicidad, y lo que la limita es esa dependencia de todo con todo.

Retomamos el esqueleto del tema 12 para mostrar sus límites, y los temas que siguen (interrupciones, temporizadores, planificación) para superarlos.

02 · La estructura

Anatomía del superloop

Un superloop consiste en una inicialización que se ejecuta una vez y un bucle infinito que recorre las tareas del producto, cada una como una función que hace un trabajo y vuelve:

int main(void) {
    hardware_init();                 // una sola vez

    while (1) {                      // para siempre
        tarea_boton();               //  0,1 ms
        tarea_sensor();              //  2 ms
        tarea_pantalla();            // 30 ms
        tarea_comunicacion();        //  8 ms
    }
}

Llamamos tiempo de vuelta (T) a lo que tarda una pasada completa, y como cada tarea se ejecuta una vez por vuelta:

T = suma de los tiempos de todas las tareas de la vuelta = 0,1 + 2 + 30 + 8 ≈ 40 ms

De ahí salen consecuencias que se ven enseguida. Todas las tareas se ejecutan a la misma frecuencia, 1/T = 25 veces por segundo, aunque no la necesiten: el sensor quizá bastaría con leerlo cada 100 ms, y el botón debería consultarse mil veces por segundo. La tarea más larga domina la vuelta: la pantalla, con 30 ms, hace que los 10 ms restantes sean casi irrelevantes. Y el orden importa: una tarea que depende del resultado de otra debe ir después.

La gran ventaja es el determinismo del orden. No hay concurrencia: dos tareas nunca se ejecutan «a la vez», así que no existen condiciones de carrera entre ellas (salvo con interrupciones, el tema siguiente). Depurar un superloop es depurar un programa lineal: se pone un punto de interrupción y se sigue la ejecución paso a paso. Esa simplicidad es un argumento de peso, sobre todo en equipos chicos.

03 · El mecanismo

Polling: preguntar una y otra vez

El polling (sondeo) es la técnica con la que un superloop se entera de lo que ocurre afuera: en cada vuelta consulta el estado de lo que le interesa. Hay tres formas habituales:

  • Consultar un pin. «¿Está apretado el botón?» (boton_presionado(), tema 12).
  • Consultar una bandera de un periférico. «¿Llegó un byte por la UART?» (el bit RXNE del registro de estado). «¿Terminó la conversión del ADC?»
  • Consultar el tiempo. «¿Pasaron ya 100 ms desde la última vez?» (se verá más abajo).
// Polling de una bandera de periférico: ¿llegó un byte por la UART?
if (USART1_SR & (1u << 5)) {          // RXNE: el registro de datos tiene un byte
    uint8_t b = USART1_DR;             // leerlo borra la bandera
    procesar_byte(b);
}
Ventajas y costos del polling.
A favorEn contra
Muy simple de escribir y de entenderLa latencia es, en el peor caso, una vuelta completa
Orden de ejecución determinista y fácil de depurarUn evento más corto que una vuelta puede pasar inadvertido
Sin datos compartidos con rutinas de interrupciónGasta energía: el procesador nunca descansa, aunque no haya nada para hacer
Sin memoria ni código extra (no hay sistema operativo)Cada tarea nueva alarga la vuelta de todas las demás

Los costos, que son los que importan, se entienden mejor con números. El resto del tema los pone en cifras.

04 · El número que define todo

La latencia es una vuelta completa

Imaginá que el botón se presiona justo después de que el bucle lo consultó. No se volverá a mirar hasta la próxima vuelta, T ms más tarde. Con el botón presionado «a destiempo» la espera es, en promedio, de T/2 y, en el peor caso, de T. Si además hay que agregar el tiempo de atender el evento (A):

latencia en el peor caso = T + A

Con T = 40 ms y A = 0,5 ms, la respuesta puede tardar hasta 40,5 ms. Es una cifra que no depende de qué tan rápido sea el código que atiende al botón, sino de todo lo demás que hay en el bucle. Y también es el jitter del tema 13: la respuesta puede tardar entre 0,5 y 40,5 ms según el instante exacto de la pulsación.

Los pulsos que no se ven

Hay una consecuencia más grave que la demora: los eventos más cortos que una vuelta pueden perderse del todo. Si el botón se mantiene apretado solo W = 20 ms y el bucle lo mira cada T = 40 ms, la consulta cae dentro del pulso con probabilidad W/T = 50 %. La mitad de las pulsaciones se pierden, sin ningún aviso y sin que el programa tenga un error. Es la versión práctica del teorema del muestreo: para ver un pulso hay que mirar al menos cada vez que dura.

El caso extremo es la comunicación serie. A 115.200 baudios llega un byte nuevo cada 86,8 µs, y el registro de recepción de esta UART guarda uno solo. Con una vuelta de 40 ms, llegan unos 460 bytes por vuelta y el programa lee uno: se pierde el 99,8 %. Por eso el polling de una UART rápida solo funciona si el bucle es más veloz que un carácter (menos de 87 µs), y en la práctica nadie lo hace así: se usan interrupciones o DMA con un buffer (tema 15).

Qué puede detectar un superloop con una vuelta de T ms.
EventoDuración o ritmo¿Alcanza T = 40 ms?
Pulsación deliberada de un botónUnos 100 ms o másSí: se ve siempre, con una latencia de hasta 40 ms
Golpe breve a un botón o a un sensor de contacto10 a 30 msNo siempre: se pierde parte de los eventos
Pulsos de un caudalímetro o encoderDesde cientos de µsNo: hay que contarlos por interrupción o con un temporizador de hardware
Byte por UART a 9600 baudios (1,04 ms por byte)Un byte cada 1,04 msNo: se pierden casi todos sin un buffer por interrupción
Cambio de temperaturaSegundosSobra

05 · Lo que hace crecer la vuelta

El enemigo: las esperas bloqueantes

Una función bloqueante es una que no vuelve hasta que algo ocurrió: una espera de tiempo, una bandera de un periférico, la respuesta de otro chip. Mientras espera, el bucle no avanza y todo lo demás queda congelado. En el taller usamos una, y fue deliberado: esperar_ms(500) detiene el programa medio segundo.

Fuentes habituales de bloqueo en un superloop.
OperaciónDuración típicaAlternativa
delay() / esperar_ms()Lo que se pidaComparar contra una marca de tiempo (más abajo)
Esperar la bandera «registro de transmisión vacío» para enviar un byte86,8 µs por byte a 115200 baudios; un mensaje de 80 caracteres tarda casi 7 msEnviar un byte por vuelta, o usar interrupciones o DMA
Leer un sensor por I²C esperando la respuestaCientos de µs a varios msIniciar la lectura y recoger el resultado en una vuelta posterior
Escribir o borrar flashMilisegundos a decenas de ms por páginaHacerlo en un momento controlado; con un RTOS, en una tarea aparte
printf y otras funciones de formatoVariable; ms si salen por serieUn buffer circular que se vacía de a poco
Operaciones con una tarjeta SD o una redDecenas a cientos de msMáquinas de estados o tareas separadas

El efecto dominó es el mismo en todas: la duración del bloqueo se suma entera al tiempo de vuelta, y por lo tanto a la latencia de todo lo demás. Un delay(100) «inofensivo» agregado para espaciar una lectura convierte un bucle de 5 ms en uno de 105 ms: el botón responde 20 veces más lento. Es el error de diseño más común en programas de aficionados, y también en no pocos productos.

06 · La técnica central

Tareas no bloqueantes y tareas en trozos

La solución a los bloqueos tiene dos ideas complementarias: no esperar activamente, y no hacer todo de una vez.

Esperar sin bloquear: marcas de tiempo

En lugar de detener el programa durante 100 ms, la tarea guarda cuándo tocó por última vez y, en cada vuelta, pregunta si ya pasó su período. Si no, vuelve de inmediato:

static uint32_t t_sensor, t_pantalla;           // cuándo les tocó la última vez

while (1) {
    uint32_t ahora = tiempo_ms();               // contador de milisegundos (tema 17)

    if (ahora - t_sensor >= 10) {               // ¿pasaron 10 ms?
        t_sensor += 10;                         // próxima marca: sin acumular error
        leer_sensor();
    }
    if (ahora - t_pantalla >= 100) {            // ¿pasaron 100 ms?
        t_pantalla += 100;
        refrescar_pantalla();
    }
    atender_boton();                            // esta se consulta en cada vuelta
}

Dos detalles de esta forma de escribir son importantes. Primero, t_sensor += 10 en lugar de t_sensor = ahora: así el período no se desliza por la demora con que se atiende (no acumula deriva). Segundo, la comparación se hace restando, ahora - t_sensor >= 10, no comparando ahora >= t_sensor + 10. Con enteros sin signo de 32 bits, la resta es correcta aunque el contador dé la vuelta (cada 49,7 días, tema 13); la comparación directa falla justo en ese momento. Por ejemplo, con t_sensor = 0xFFFFFFF0 y ahora = 5, la resta da 21 (módulo 2³²), que es lo correcto; la suma t_sensor + 10 = 0xFFFFFFFA es mayor que 5 y la tarea no se dispararía.

Con este esquema cada tarea tiene su propio ritmo (10 ms, 100 ms) y el bucle recorre las marcas sin detenerse nunca. La vuelta se vuelve muy corta, de decenas de microsegundos, siempre que ninguna tarea sea larga.

Hacer las cosas de a poco: tareas en trozos

Cuando una tarea es inevitablemente larga (dibujar una pantalla de 30 ms), se la divide en pasos y se ejecuta un paso por vuelta. La tarea recuerda en qué paso iba con una variable estática, es decir, es una máquina de estados (tema 24):

// La pantalla se dibuja una línea por vuelta (3 ms cada una) en lugar de 30 ms de golpe
static uint8_t linea;

void pantalla_paso(void) {
    pantalla_dibujar_linea(linea);              // trabajo acotado: una sola línea
    if (++linea == LINEAS) linea = 0;           // al terminar, vuelve a empezar
}

// El envío por la UART, un carácter por vuelta y sin esperar
static const char *tx_ptr;

void uart_enviar_texto(const char *s) { tx_ptr = s; }       // encola el texto (si no hay otro en curso)

void uart_tx_paso(void) {
    if (tx_ptr && *tx_ptr && (USART1_SR & (1u << 7))) {  // ¿hay algo para enviar y el registro está libre?
        USART1_DR = *tx_ptr++;                  // enviar UN carácter y seguir
    }
}

La tarea de la UART no espera nunca: pregunta si el registro está libre (TXE) y, si no lo está, vuelve y lo intenta en la próxima vuelta. Eso transforma una espera de 7 ms en ocho microsegundos por carácter distribuidos a lo largo del bucle. Lo mismo vale para una antirrebote del botón, que conviene basar en tiempo y no en la cantidad de vueltas (que depende de T):

bool boton_estable(void) {                     // se llama en cada vuelta
    static uint32_t t_cambio;
    static bool estable, previo;

    bool lectura = boton_presionado();
    if (lectura != previo) { previo = lectura; t_cambio = tiempo_ms(); }   // cambió: reiniciar la cuenta
    if (lectura != estable && (tiempo_ms() - t_cambio) >= 20) {      // estable durante 20 ms
        estable = lectura;
    }
    return estable;
}

El costo de partir las tareas es de complejidad: la lógica secuencial («primero A, después B, después C») se convierte en una máquina de estados con variables que sobreviven entre vueltas. Ese es el precio de un superloop que responde rápido, y es también una de las razones por las que, al crecer, conviene pasar a un planificador o a un RTOS.

07 · Hasta dónde llega

Los límites del superloop

Con tareas no bloqueantes y divididas, un superloop puede lograr vueltas de decenas de microsegundos y atender sin problema decenas de funciones. Pero hay cuatro límites estructurales que no se corrigen con disciplina:

  • No hay prioridades. Una alarma crítica y una actualización de pantalla esperan su turno en la misma cola. La respuesta a lo urgente nunca es mejor que una vuelta completa.
  • La latencia crece con el programa. Cada tarea o cada trozo agregado alarga la vuelta de todas. Un producto que cumplía su plazo con cinco funciones puede dejar de cumplirlo con ocho, sin que se haya tocado el código del evento crítico.
  • Hay piso para la latencia: la tarea indivisible más larga. Si alguna operación no se puede partir (borrar una página de flash de 20 ms, una transacción larga con un chip), ninguna respuesta puede tardar menos que eso. Un requisito de 5 ms es inalcanzable con una operación indivisible de 20 ms.
  • El procesador nunca descansa. El bucle da vueltas aunque no haya nada que hacer. En un dispositivo a pila, esa es la diferencia entre días y años de autonomía (tema 2); el reposo exige despertarse con una interrupción (temas 34 y 35).
Cuándo alcanza el superloop y cuándo conviene pasar al siguiente escalón.
SituaciónConclusión
El peor caso de la vuelta (T + A) es unas diez veces menor que el plazo más cortoEl superloop alcanza con margen
Hay eventos más cortos que una vuelta (pulsos, bytes seriales rápidos)Interrupciones para capturarlos (tema 15)
Una operación indivisible supera el plazo de algún eventoInterrupciones o un RTOS que pueda desalojarla (temas 15 y 20)
Varias tareas con ritmos muy distintos y plazos en el rango de milisegundosPlanificador por tiempo (tema 19)
Un dispositivo a pila que debe dormir casi todo el tiempoArquitectura por eventos con reposo (temas 26 y 34)

Es útil recordar que pasar de arquitectura no es un fracaso. Un producto pequeño que nace como superloop y crece hasta necesitar interrupciones, o incluso un RTOS, hizo lo correcto: empezó con lo simple y subió solo cuando lo midió necesario. Lo que sí es un error es quedarse en el superloop cuando el análisis del peor caso ya mostró que no alcanza.

08 · Analogía con programación

Medir el superloop, en Python

Este programa calcula, para cinco maneras de escribir el mismo bucle, el tiempo de vuelta, la latencia en el peor caso y qué fracción de las pulsaciones breves de 20 ms se detecta. Las pulsaciones llegan en instantes distribuidos al azar respecto del bucle (con un generador reproducible) y se cuentan 2000.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

SENSOR_MS, SONDEO_MS, ATENCION_MS = 2.0, 0.1, 0.5
PULSO_MS = 20.0           # duración de la pulsación breve
EVENTOS = 2000
SEMILLA = 2026


def azar():
    x = SEMILLA
    while True:
        x = (x * 1664525 + 1013904223) & 0xFFFFFFFF
        yield x / 4294967296


def evaluar(pantalla_ms, uart_ms, retardo_ms):
    vuelta = SENSOR_MS + pantalla_ms + uart_ms + retardo_ms + SONDEO_MS
    g = azar()
    detectados = 0
    for _ in range(EVENTOS):
        espera = next(g) * vuelta          # hasta la próxima consulta del botón
        if espera < PULSO_MS:               # el botón sigue apretado cuando se lo mira
            detectados += 1
    return vuelta, vuelta + ATENCION_MS, 100 * detectados / EVENTOS


CASOS = [
    ("Todo bloqueante",                                30,   8,   0),
    ("Pantalla en 3 trozos de 10 ms",                  10,   8,   0),
    ("Pantalla en 3 trozos + UART sin esperar",        10,   0.2, 0),
    ("Todo en trozos pequeños (3 ms) y UART sin esperar", 3, 0.2, 0),
    ("Todo bloqueante + delay(50)",                    30,   8,  50),
]
for nombre, pantalla, uart, retardo in CASOS:
    vuelta, lat, pct = evaluar(pantalla, uart, retardo)
    print(f"{nombre:50s} vuelta {vuelta:5.1f} ms  latencia máx {lat:5.1f} ms  pulsos de 20 ms detectados {pct:5.1f} %")

Resultado: con todo bloqueante la vuelta es de 40,1 ms, la latencia en el peor caso de 40,6 ms y se detecta apenas el 50,3 % de las pulsaciones de 20 ms. Partir la pantalla en tres trozos baja la vuelta a 20,1 ms y sube la detección al 99,5 %. Con la UART también sin espera, la vuelta es de 12,3 ms y se ve el 100 %. Con trozos de 3 ms, la vuelta cae a 5,3 ms. Y un delay(50) en el programa bloqueante lleva la vuelta a 90,1 ms y la detección a un 23,3 %. Probá cambiar PULSO_MS a 5 con el último caso bueno: la detección baja a cerca del 95 %, porque 5,3 ms de vuelta siguen siendo más largos que el pulso.

El mensaje del ejercicio es que la lógica del producto no cambió en ninguno de los cinco casos: solo cambió cómo se escribieron las tareas. La arquitectura superloop admite una enorme mejora de comportamiento sin salir de ella, a base de cuidado. Y también muestra su piso: ni siquiera con trozos de 3 ms se capturan pulsos de 5 ms con seguridad.

09 · Exploración interactiva

El bucle y el botón

El laboratorio calcula el tiempo de vuelta de un superloop con un sensor (2 ms), una pantalla, una comunicación serie y, opcionalmente, un retardo bloqueante, y simula 2000 pulsaciones al azar. Elegí la duración de la pulsación, cómo se escribe la pantalla y la UART, el retardo y la latencia máxima exigida. Verás qué fracción de pulsos se detecta, cuánto tarda la respuesta en el peor caso y cada cuánto se refresca la pantalla completa.

Un superloop con cuatro tareas

Tiempo de vuelta—
Frecuencia de consulta del botón—
Latencia en el peor caso—
Pulsaciones detectadas—
Refresco completo de la pantalla cada—
Resultado—

Cambiá cualquier opción para ver cómo se modifica la vuelta.

Recorrido guiado

  1. Con los valores iniciales (pulsación de 20 ms, todo bloqueante): vuelta de 40,1 ms y apenas el 50 % de las pulsaciones detectadas. La latencia máxima (40,6 ms) cumple los 100 ms exigidos, pero el requisito de latencia no basta: se pierden la mitad de los eventos.
  2. Pasá la pantalla a 3 trozos de 10 ms: la vuelta baja a 20,1 ms y se detecta el 99,5 %. Casi todo, pero no todo: un pulso de 20 ms contra una vuelta de 20,1 ms deja un resquicio.
  3. Sumá la UART sin esperar: la vuelta es de 12,3 ms y se detecta el 100 %. Fijate en el costo: la pantalla ahora se refresca completa cada 37 ms (tres vueltas de 12,3), un poco antes que los 40 ms originales.
  4. Elegí 10 trozos de 3 ms con la UART sin esperar: vuelta de 5,3 ms, latencia de 5,8 ms. Pero la pantalla completa se refresca cada 53 ms; partir más acelera la respuesta y retrasa el refresco.
  5. Con todo bloqueante, agregá el delay(50): la vuelta salta a 90,1 ms y la detección cae al 23 %. Un solo renglón «inocente» destruye el comportamiento.
  6. Probá una pulsación de 5 ms con los trozos pequeños y la UART sin esperar: detecta el 95 %. Para capturar eventos tan breves de forma confiable, el superloop llegó a su piso y hace falta una interrupción.
  7. Poné la latencia exigida en 20 ms y una pulsación de 100 ms con todo bloqueante: se ven todas las pulsaciones, pero la respuesta tarda 40,6 ms. Aquí el problema es el opuesto: detecta bien y responde tarde.
Desafío: el costo de partir en trozos

Partir la pantalla en más trozos baja el tiempo de vuelta (mejor respuesta del botón) pero alarga el tiempo que tarda en completarse una actualización de la pantalla: con 10 trozos de 3 ms y una vuelta de 5,3 ms, la imagen tarda 53 ms en refrescarse entera, algo más que con la versión bloqueante (40 ms). Si el requisito de la pantalla fuese un refresco completo en menos de 40 ms, no se podrían cumplir las dos cosas a la vez con este procesador. Esa tensión entre responder rápido a lo urgente y completar rápido lo largo es la que resuelve un planificador con prioridades (temas 19 y 20).

10 · Método de trabajo

Método para dimensionar un superloop

  1. Listá las tareas y, para cada una, el tiempo de ejecución en el peor caso (medido con un pin o con el contador de ciclos, tema 13) y la frecuencia con que debe ejecutarse.
  2. Identificá las operaciones bloqueantes y las indivisibles. Las primeras se eliminan o se parten; las segundas fijan un piso para la latencia.
  3. Calculá el tiempo de vuelta en el peor caso: la suma de lo que puede tardar cada tarea más el sondeo. Esa es la latencia máxima para cualquier evento detectado por polling.
  4. Compará con cada plazo. La regla práctica: el peor caso de la vuelta debería ser menos de la décima parte del plazo más corto, y menos de la mitad del evento más breve.
  5. Reemplazá las esperas por marcas de tiempo y escribí las tareas largas como máquinas de estados de pasos acotados.
  6. Medí la vuelta real con un pin que cambia de nivel en cada vuelta: la frecuencia de esa señal es 1/T y el ancho de sus pulsos muestra el jitter.
  7. Revisá el resultado cada vez que se agrega una función. Una tarea nueva cambia la latencia de todas las demás; el análisis se repite.
  8. Definí el criterio de salida: si algún plazo no se cumple con margen, o hay eventos más cortos que la vuelta, es el momento de pasar a interrupciones (tema 15).

El pin que alterna en cada vuelta es un instrumento barato y poderoso: con un osciloscopio o un analizador lógico se ve de un vistazo si el bucle da 200 vueltas por segundo o 25, y qué variaciones tiene según lo que hace el programa. Es el equivalente a un heartbeat en un servidor, y suele revelar bloqueos que nadie sospechaba.

11 · Comprensión

Confusiones frecuentes

«Si el código es rápido, no hay problema de latencia»

La latencia de un evento no depende de qué tan rápido se atiende, sino de cuándo se lo consulta, y eso lo decide el tiempo de vuelta. Un código de atención de 1 µs en un bucle de 40 ms responde en 40 ms.

«Un delay corto no hace daño»

Un delay suma su duración entera a la vuelta, y por lo tanto a la latencia de todo. Los delays de 10 o 20 ms «para que se estabilice» se acumulan: tres de ellos convierten un bucle de microsegundos en uno de decenas de ms.

«El polling es una mala práctica»

Es una técnica adecuada cuando el evento es lento frente a la vuelta, o cuando la simplicidad y la ausencia de concurrencia valen más que la latencia. Muchos productos exitosos son superloops. Es mala práctica usarlo donde el análisis mostró que no alcanza.

«Mientras el bucle no se cuelgue, no hay problema»

Un bucle puede dar vueltas perfectamente y perder eventos, responder tarde o atrasar tareas. Colgarse es un fallo evidente; perder pulsos es silencioso. Por eso se mide la vuelta y se verifica la detección con eventos reales.

«Contar vueltas sirve para medir tiempo»

La duración de una vuelta cambia con lo que hace el programa (una vuelta con comunicación tarda más que una sin ella). Un antirrebote de «5 lecturas iguales» dura 0,5 ms con una vuelta de 100 µs y 200 ms con una de 40 ms. El tiempo se mide con un temporizador, no con vueltas.

«Con un RTOS todos estos problemas desaparecen»

Muchos desaparecen, pero otros aparecen (sincronización, pilas, prioridades, tema 22). Un superloop bien escrito es más simple, más pequeño y suficiente para una gran cantidad de productos; el RTOS se justifica cuando el análisis lo exige.

12 · Práctica guiada

Predecí antes de comprobar

1. Tiempo de vuelta y latencia

Un superloop ejecuta cuatro tareas que tardan como máximo 0,3 ms, 1,5 ms, 12 ms y 4 ms, y además tiene un delay(20). ¿Cuál es el tiempo de vuelta, la frecuencia de consulta y la latencia en el peor caso de un evento detectado por polling, si atenderlo lleva 0,5 ms?

Ver solución

T = 0,3 + 1,5 + 12 + 4 + 20 = 37,8 ms. Frecuencia de consulta = 1000 / 37,8 ≈ 26 veces por segundo. Latencia en el peor caso = 37,8 + 0,5 = 38,3 ms. Casi todo el tiempo (el 53 %) es el delay: quitarlo lleva la vuelta a 17,8 ms.

2. Pulsos perdidos

Una señal dura 15 ms y el bucle tiene una vuelta de 40 ms. ¿Qué fracción de los pulsos se detecta? ¿Cuál debería ser la vuelta máxima para detectar todos?

Ver solución

Se detecta W/T = 15 / 40 = 37,5 %. Para detectarlos todos, la vuelta debe ser no mayor que la duración del pulso: T ≤ 15 ms (y en la práctica menos, para dejar margen). Si no se logra, hay que capturar el pulso por interrupción.

3. Una UART por polling

Llegan bytes a 9600 baudios (10 bits por byte) y el programa lee un byte por vuelta. Con una vuelta de 5 ms, ¿cuántos bytes llegan por vuelta y qué porcentaje se pierde? ¿Qué vuelta máxima haría falta?

Ver solución

Un byte tarda 10 / 9600 ≈ 1,04 ms. En 5 ms llegan 5 / 1,04 ≈ 4,8 bytes y el programa lee uno: se pierde alrededor del 79 %. Para no perder nada, la vuelta debe ser menor que 1,04 ms, y sin margen de jitter. Es la razón por la que la recepción serie se hace por interrupción con un buffer.

4. Comparación a prueba de vuelta del contador

Una tarea de 10 ms usa if (ahora >= ultimo + 10). Cuando ultimo = 0xFFFFFFF8, ¿qué pasa? ¿Y con if (ahora - ultimo >= 10)?

Ver solución

Con la suma, ultimo + 10 vale 0x100000002, que en 32 bits queda en 2. Como ahora (cerca de 0xFFFFFFF8) es mucho mayor que 2, la condición se cumple de inmediato y la tarea se dispara antes de que pasen los 10 ms. Con la resta, ahora - ultimo da siempre la cantidad real de milisegundos transcurridos en aritmética módulo 2³², incluso al cruzar la vuelta del contador. Es el único modo seguro de comparar tiempos con un contador que da la vuelta.

5. Dividir una tarea larga

El resto de las tareas del bucle suman 2,3 ms. Se quiere una vuelta de 10 ms o menos, y una pantalla que tarda 30 ms en total. ¿En cuántos trozos hay que partirla y cada cuánto se refresca completa?

Ver solución

Cada trozo puede durar como máximo 10 − 2,3 = 7,7 ms. 30 / 7,7 ≈ 3,9, así que hacen falta 4 trozos de 7,5 ms. La vuelta queda en 2,3 + 7,5 = 9,8 ms, y la pantalla se refresca completa cada 4 × 9,8 = 39,2 ms. Partir más (por ejemplo, en 10 trozos) bajaría la vuelta pero alargaría el refresco.

6. Cuando el superloop no alcanza

Un producto debe responder a una señal de seguridad en menos de 5 ms, y su firmware borra una página de flash que bloquea el procesador durante 20 ms. ¿Se puede cumplir con un superloop puro?

Ver solución

No. La operación de borrado es indivisible y dura más que el plazo: mientras ocurre, ningún código del bucle puede mirar la señal. La latencia mínima posible es de 20 ms. Las salidas son: atender la señal por interrupción (si el chip permite que sigan activas durante el borrado y la rutina no está en la flash que se borra), reservar el borrado para momentos en los que la señal no puede ocurrir, o cambiar de arquitectura hacia un esquema que pueda desalojar la tarea larga.

13 · Síntesis

Ideas para recordar

  • El superloop es un bucle infinito que recorre las tareas en orden; cada tarea corre una vez por vuelta, así que todas comparten la misma frecuencia 1/T.
  • El polling consulta pines, banderas de periféricos o el tiempo en cada vuelta; es simple y determinista, sin concurrencia, pero la latencia en el peor caso es la vuelta completa (T + atención).
  • Un evento más corto que la vuelta puede perderse sin aviso: la fracción detectada es aproximadamente W/T; las comunicaciones serie rápidas no se pueden atender por polling sin un buffer.
  • Las esperas bloqueantes (delays, esperar banderas, escrituras de flash, printf) suman su duración entera a la vuelta; son la causa más común de latencia inexplicable.
  • Se esquivan con marcas de tiempo (ahora - ultimo >= periodo, correcto aunque el contador dé la vuelta, y ultimo += periodo para no acumular deriva) y partiendo las tareas largas en pasos acotados con máquinas de estados.
  • Partir más reduce la latencia de lo urgente pero alarga el tiempo en completarse lo largo; la duración de una operación indivisible fija un piso para la latencia.
  • Los límites estructurales son: sin prioridades, la latencia crece con cada tarea agregada, el piso de las operaciones indivisibles y el procesador siempre activo.
  • Se mide la vuelta con un pin que alterna en cada pasada; si el peor caso no es unas diez veces menor que el plazo, o hay eventos más cortos que la vuelta, es hora de usar interrupciones.

¿Listo para el tema 15?

Antes de avanzar, calculá el tiempo de vuelta y la latencia de un bucle con tus propias tareas; explicá por qué un pulso de 5 ms puede perderse en un bucle de 20 ms; y escribí la condición de una tarea periódica de 50 ms que funcione aunque el contador de milisegundos dé la vuelta. Si podés justificar las tres respuestas, entendés a fondo el superloop y su techo.

En el próximo tema superamos ese techo con las interrupciones: cómo el hardware avisa al procesador sin que el programa pregunte, qué son los vectores, las prioridades y la latencia de entrada, y cómo escribir una rutina de servicio.