Esperar veinte ticks parece fácil hasta que el número vuelve a ser pequeño
Un dispositivo embedded adquiere datos, inicia una inferencia de IA y espera el resultado dentro de un intervalo. El firmware lee un contador creciente: ¿basta guardar el vencimiento y compararlo con el valor actual? Cerca del límite del contador, esa comparación puede declarar un timeout justo al empezar la espera. El problema no es la velocidad del modelo, sino cómo representamos el tiempo con un número finito de bits.
Estudiamos un contador sin signo de 16 bits, con valores de 0 a 65535. Partimos de 65530 y esperamos 20 incrementos. El vencimiento representado será 14, menor que el valor inicial. Deducimos por qué la diferencia modular recupera el tiempo transcurrido bajo condiciones precisas y por qué no puede recuperar una vuelta completa no observada. Todos los números proceden de un programa Python ejecutado: no compilamos firmware, medimos una placa ni ejecutamos un kernel en el dispositivo.
Primero los ticks, después los segundos
Un tick es un incremento discreto del contador. Suponer 1000 ticks por segundo permite convertir cuentas en segundos, pero no convierte el programa en una medida temporal. El contador de 16 bits vuelve al valor inicial tras 65536 incrementos:65,536 s a la frecuencia supuesta. Para 32 bits, el mismo cálculo da unos 49,71 días. Más bits aplazan el problema; no corrigen una relación de orden que no sabe atravesar el retorno a cero.
M es el número de estados disponibles, no el mayor valor almacenable; el mayor es M−1. El símbolo mod indica el resto al dividir por M, elegido entre 0 y M−1. Comparar si el valor actual es mayor o igual que 14 ya da verdadero cuando vale 65530, aunque no ha transcurrido ningún tick. Ese contraste por sí solo no representa la espera, aunque funcionaría en un ejemplo alejado del límite.
Comparar un intervalo, no dos etiquetas de una esfera
El contador se parece a una esfera que, después de la última marca, vuelve a la primera. La analogía explica por qué una etiqueta menor no significa necesariamente antes, pero no indica cuántas vueltas hubo. Para recuperar la distancia recorrida desde el inicio, restamos y devolvemos el resultado al mismo conjunto de estados. Es una diferencia modular, no una resta ordinaria entre enteros ilimitados.
Tras 11 ticks, el contador indica 5. La resta ordinaria 5−65530 es negativa; reducida módulo 65536 se convierte en 11. Tras 20 ticks, el contador indica 14 y la diferencia modular es 20, alcanzando la duración requerida. El criterio también interpreta correctamente los valores inmediatamente anteriores y posteriores al vencimiento. Pero antes de llamarlo universal debemos demostrar qué información contiene realmente delta.
La demostración también identifica el límite
Llamamos E al número real de incrementos desde el inicio. Es una variable del modelo de referencia, no información contenida automáticamente en el contador. Si los incrementos ocurren sin saltos ni reinicios, now es el resto de start+E. Al sustituirlo en la diferencia modular, start se cancela y queda E módulo M. Para E entre 0 y M−1, ese resto coincide con E.
Tenemos una condición suficiente precisa: el intervalo realmente transcurrido desde el inicio debe ser menor que una vuelta completa. El umbral duration debe ser representable y usar la misma unidad de cuenta. En esas condiciones, comparar delta con duration equivale a comparar los ticks reales con el umbral. No hace falta que el valor actual sea numéricamente mayor que el inicial: el límite esencial afecta al intervalo representado por ambas lecturas.
| E real (ticks) | now | delta | Vencimiento correcto | Prueba ingenua |
|---|---|---|---|---|
| 0 | 65530 | 0 | 0 | 1 |
| 5 | 65535 | 5 | 0 | 1 |
| 11 | 5 | 11 | 0 | 0 |
| 19 | 13 | 19 | 0 | 0 |
| 20 | 14 | 20 | 1 | 1 |
| 26 | 20 | 26 | 1 | 1 |
En la tabla,1 significa verdadero y 0 falso; la prueba ingenua compara now con el vencimiento 14. Las primeras dos filas muestran el fallo importante: el programa podría detener la inferencia sin conceder la espera necesaria. Las filas posteriores explican por qué probar solo después de volver a cero podría hacer parecer correcto el criterio. Un algoritmo temporal necesita casos anteriores a la transición, no solo la lectura final.

Dos lecturas no reconstruyen una vuelta completa oculta
Dejemos pasar exactamente 65536 ticks sin información adicional. El contador vuelve a 65530, su valor inicial, y delta es 0. La espera real de 20 ticks ha vencido hace mucho, pero la comparación modular no lo reconoce. La demostración no falla: se incumple su hipótesis E menor que M. Las observaciones son idénticas a las de una espera nula; ningún algoritmo que solo reciba esas observaciones puede distinguir ambos casos con certeza.
La solución exige más información o restricciones: un contador de épocas, un intervalo máximo garantizado entre observaciones, un temporizador más ancho o un servicio del sistema operativo que conserve el estado necesario. Prometer vagamente lecturas periódicas no basta: la frecuencia debe relacionarse con el máximo retraso posible, incluidas suspensión y bloqueos. Si el dispositivo reinicia o el contador se pone a cero por otra causa, ya no estamos en el modelo de incremento modular simple.
Por qué también se habla de media vuelta
Otro problema es decidir cuál de dos marcas temporales arbitrarias va primero sin conocer previamente el sentido del tiempo entre ellas. En una esfera de 8 estados,0 y 4 son opuestos: la distancia modular es 4 en ambos sentidos. Asignar un orden local exige un horizonte inferior a media vuelta. No hay que confundirlo con la demostración anterior: recuperar un intervalo no negativo conocido como transcurrido permite E menor que una vuelta completa; ordenar marcas arbitrarias plantea otra pregunta.
Por ello no usamos una conversión a entero con signo como atajo universal. Además de las hipótesis de medio intervalo, esa técnica depende de las reglas del lenguaje y la representación. El código calcula explícitamente el resto módulo M con enteros Python, haciendo visible el modelo aritmético. Eso no demuestra equivalencia con cualquier expresión C aparentemente similar compilada para cualquier procesador.
El tipo de la variable no siempre es el tipo de la resta
En C, los operandos pequeños pueden sufrir promociones enteras antes de restarse. Si int representa todos los valores de un tipo sin signo de 16 bits, los operandos pueden promoverse a int. La resta 5−65530 se calcula entonces como entero negativo representable, no directamente módulo 65536. Asignar el resultado a un tipo sin signo de la anchura prevista introduce después la conversión modular. Son dos pasos distintos.
La distinción aparece en el borrador público N1570del 12 deabril de 2011, apartados 6.2.5,6.3.1.1 y 6.3.1.3. Lo citamos como borrador C11, no como descripción certificada de todos los compiladores actuales. Comparar antes de convertir puede usar un valor distinto del previsto por el modelo. No compilamos ningún listado C; implementar exige comprobar tipos efectivos, promociones, constantes y conversiones en el toolchain correspondiente, sin deducir corrección del nombre de una variable.
Qué hace realmente el kernel: más que una resta
Leímos xTaskCheckForTimeOut en tasks.c de FreeRTOS-Kernel V11.2.0, una versión explícita y no una referencia vaga a la rama actual. El método lee la cuenta dentro de una sección crítica, considera el estado de desbordamiento y actualiza el tiempo restante cuando la espera continúa. También maneja opciones como bloqueo abortado y espera indefinida. El resumen corresponde al método inspeccionado, no a la ejecución o validación del kernel completo.
El caso de la vuelta oculta no demuestra por tanto un fallo de FreeRTOS: nuestro modelo recibe solo dos lecturas, mientras el método del kernel dispone de estado adicional. Hay que comparar interfaces con la misma información disponible. Copiar una línea de resta de un algoritmo que también usa épocas, secciones críticas y actualizaciones de estado puede eliminar precisamente las condiciones que hacían correcto al original.
La lectura atómica es un problema distinto de la aritmética. Un contador ancho leído en varias operaciones puede mezclar partes de instantes diferentes; un reinicio puede cambiar el significado de la época. Suponemos lecturas coherentes y ningún reinicio. Python no demuestra lecturas atómicas en un microcontrolador ni la eficacia de una sección crítica en una arquitectura concreta. Harían falta plataforma, compilador, configuración y pruebas específicas.
El programa comprueba una propiedad, no un rendimiento
Para comprobar la deducción más allá de unos pocos ejemplos, reducimos el contador a 8 bits y probamos las 256 posiciones iniciales con los 256 intervalos de 0 a 255. Son 65536 casos, y en todos la diferencia modular coincide con el intervalo real. No es un porcentaje de fiabilidad ni un muestreo estadístico: es una comprobación exhaustiva del modelo reducido y de la propiedad concreta. La demostración explica por qué la misma identidad vale para cualquier M bajo las hipótesis declaradas.
Probar cada pareja cuesta M²casos: con b bits son 4 elevado a b. Por eso la prueba exhaustiva usa 8 bits y no afirma explorar todas las historias de un sistema de 16 o 32 bits. Una sola diferencia requiere un número constante de operaciones aritméticas si el contador cabe en una palabra máquina. Eso no es una medida en microsegundos: implementación, compilador y plataforma determinan el coste real. Los enteros ilimitados de Python no modelan el consumo energético de una MCU.
El fragmento adjunto separa E, el tiempo real conocido por la simulación, de now, la lectura disponible para el algoritmo. Mantener ambas variables es esencial para detectar alias temporal. Generar el resultado esperado con la misma diferencia modular repetiría la implementación y ocultaría el límite de una vuelta completa. Los datos guardados incluyen expresamente E igual a M, donde el método carece de la información necesaria.
De las cuentas a una deadline real de inferencia
Veinte ticks no garantizan automáticamente terminar en 20 milisegundos. Incluso suponiendo 1000 ticks por segundo, la lectura inicial puede ocurrir entre incrementos y la tarea puede ejecutarse después de un retraso de planificación. El modelo cuenta incrementos, no reconstruye la fase inicial. Una deadline física exige definir cuándo empieza el presupuesto, qué fuente temporal lo mide y cuándo se hace efectiva la acción de timeout.
En una cadena sensor–preprocesamiento–inferencia–actuador, medir solo la espera de la red neuronal excluye otras contribuciones. Comprobar el vencimiento al terminar la inferencia puede detectar el retraso sin impedirlo. La aritmética correcta responde a una pregunta necesaria pero más limitada: ¿cuántos incrementos han pasado bajo las condiciones del modelo? No demuestra latencia máxima, percentiles, consumo ni cumplimiento de tiempo real de toda la cadena.
Pasar del modelo a la medida exigiría identificar plataforma, configuración del tick, versiones del kernel y compilador, protocolo de adquisición y condiciones de operación. No presentamos esos datos como medidos: el artículo no contiene pruebas de hardware. El interés de EL-AI por aplicaciones embedded es una dirección editorial y aplicada por explorar, no evidencia de una placa o instalación empresarial existente con estas prestaciones.
La respuesta: una lectura cero no reinicia el tiempo transcurrido
Un timeout debe interpretarse como intervalo. En el ejemplo, partir de 65530 y llegar a 14 significa 20 ticks transcurridos si sabemos que el intervalo real es inferior a 65536 ticks. La diferencia modular conserva esa información al atravesar cero. Si pudo pasar una vuelta completa no observada, dos lecturas no bastan: hace falta estado adicional o una cota temporal garantizada. Reconocerlo evita confundir una fórmula útil con una promesa universal de fiabilidad.
Fuentes, versiones y código
M=65536
start=65530
duration=20
for elapsed in [0,5,11,19,20,26,65536]:
now=(start+elapsed)%M
delta=(now-start)%M
print(elapsed,now,delta,delta>=duration)
for start8 in range(256):
for elapsed8 in range(256):
now8=(start8+elapsed8)%256
assert (now8-start8)%256==elapsed8
Código, datos e instrucciones · JSON. Cálculos didácticos ejecutados con Python 3.14.0; figuras con Matplotlib 3.11.2. Análisis asistido por IA, sin afirmar revisión por pares ni humana. Portada original ImageGen ilustrativa: no documenta personas, sedes ni instalaciones de EL-AI. Fuentes consultadas el 21 de octubre de 2026.

