Contenido
Polling vs interrupciones vs DMA: cómo mover los datos
Polling vs interrupciones vs DMA: latencia, carga de CPU, consumo y determinismo, con números calculados para UART y ADC y la forma en que falla cada enfoque.
El ADC muestrea a 100 kHz, una interrupción se dispara en cada conversión, y el firmware que funcionaba bien a 1 kHz ahora pierde muestras y la interfaz de usuario se congela. No hay nada descompuesto; el procesador se pasa casi todo el tiempo entrando y saliendo de rutinas de interrupción. La elección entre polling vs interrupciones vs DMA decide cuánto CPU queda para el trabajo real, qué tan rápido responde el sistema y cuánto tiempo puede dormir. Aquí comparamos los tres con números calculados, explicamos la forma en que falla cada uno y resumimos lo que encontró la investigación sobre sobrecarga de interrupciones en sistemas que atienden demasiados eventos uno por uno.
Puntos clave
- La entrada a una interrupción en Cortex-M3 y M4 toma 12 ciclos con memoria sin estados de espera (15 a 16 en M0+ y M0), y el encadenamiento entre rutinas (tail-chaining) unos 6. El trabajo propio de la rutina suele costar más.
- La sobrecarga por evento crece con la tasa de eventos. Con 60 ciclos por interrupción a 48 MHz, una UART a 115,200 baudios cuesta cerca de 1.4 % del CPU; un ADC a 1 MS/s necesitaría 125 %, que es imposible.
- El DMA mueve datos sin el CPU e interrumpe una vez por buffer: 100 kS/s en buffers de 512 muestras son unas 195 interrupciones por segundo.
- El polling agrega una latencia de hasta un periodo de consulta y mantiene despierto al CPU, lo que sale caro en dispositivos con batería.
- Los sistemas manejados por interrupciones pueden quedar en livelock con carga alta. La investigación en kernels de red y en sistemas embebidos muestra que la solución es acotar el trabajo en interrupciones o pasar a polling cuando la tasa es alta.
El problema: costos por evento que crecen con la tasa de datos
Toda forma de mover datos tiene un costo fijo por evento y un costo por byte. Los eventos de baja tasa e irregulares (un botón, un final de carrera, un byte que llega de vez en cuando) favorecen las interrupciones: no pasa nada hasta que llega el evento, y la respuesta es rápida. Los flujos regulares de alta tasa (un ADC, un micrófono, la FIFO de un acelerómetro) favorecen el DMA: el costo por evento de una interrupción multiplicado por decenas de miles de eventos por segundo se come al procesador. El polling va aparte: es lo más sencillo y predecible, y cuesta tiempo de CPU haya pasado algo o no.
Casi todos los problemas de firmware con el movimiento de datos vienen de usar el método que le quedaba al primer prototipo a una tasa para la que nunca se pensó.
Cómo se comporta cada enfoque
Polling: sencillo, predecible y siempre despierto
Hacer polling significa que el firmware revisa una bandera de estado en un ciclo o a un intervalo fijo. No hay concurrencia escondida: el código que lee el dato corre exactamente donde lo pusiste. Eso lo vuelve fácil de razonar y de probar.
Los costos son la latencia y el consumo. El peor tiempo de respuesta es el periodo de consulta más el trabajo del ciclo. Si una bandera se revisa cada milisegundo, un evento puede esperar hasta un milisegundo. Y el procesador no puede dormir mientras consulta, lo que en un dispositivo con batería casi siempre descarta el polling salvo para esperas cortas y acotadas.
Interrupciones: respuesta rápida, sobrecarga por evento
Una interrupción deja que el hardware llame a una rutina cuando ocurre el evento. La guía de Joseph Yiu para Arm sobre latencia de interrupciones da la latencia de entrada de los procesadores Cortex-M con memoria sin estados de espera: 12 ciclos en el Cortex-M3 y el M4, 15 en el M0+ y 16 en el M0. Cuando una rutina termina y hay otra pendiente, el tail-chaining se salta parte del guardado y restauración de registros y toma unos 6 ciclos en el M3 y el M4. Los estados de espera, la contención del bus, las rutinas de mayor prioridad y el propio cableado de interrupciones del chip se suman a esas cifras.
Encima va el trabajo de la rutina. Un presupuesto ilustrativo de 60 ciclos por interrupción (entrada, una rutina corta que lee un registro y guarda un byte, y salida) da:
- Una UART a 115,200 baudios con 10 bits por carácter entrega 11,520 caracteres por segundo. A 60 ciclos cada uno en un núcleo de 48 MHz, es cerca de 1.4 % del CPU. Las interrupciones le quedan bien.
- Un ADC a 100 kS/s con una interrupción por muestra cuesta 6 millones de ciclos por segundo, 12.5 % del CPU, antes de cualquier procesamiento.
- El mismo ADC a 1 MS/s necesitaría 125 % del CPU. No se puede con una interrupción por muestra.
Cuando las interrupciones saturan al sistema
El artículo de Mogul y Ramakrishnan de 1997 en ACM Transactions on Computer Systems describió el livelock de recepción: con una tasa de paquetes de red lo bastante alta, un kernel manejado por interrupciones puede pasarse todo el tiempo atendiendo interrupciones sin avanzar en nada útil, porque las interrupciones de mayor prioridad matan de hambre al código que consumiría los datos. Su remedio combinó interrupciones con carga baja y polling con carga alta, y limitó el trabajo que se hace en contexto de interrupción.
Regehr y Duongsaa llevaron la misma preocupación a los sistemas embebidos en LCTES 2005. Señalaron que los planificadores de hilos no pueden proteger a un sistema de una sobrecarga que ocurre por debajo de ellos, en las rutinas de interrupción, y presentaron “planificadores de interrupciones” en software y hardware que retrasan o descartan las solicitudes de interrupción excedentes para acotar la cantidad y la granularidad del trabajo en contexto de interrupción. La lección de diseño para el firmware es asegurarse de que la peor tasa de interrupciones esté acotada, ya sea por hardware (FIFO, DMA) o por software que deshabilite o limite una fuente cuando se dispara demasiado.
DMA: mover bloques sin el CPU
Un controlador DMA copia datos entre un periférico y la memoria por su cuenta, disparado por el periférico, e interrumpe al CPU solo cuando termina un bloque (o la mitad, en modo circular con doble buffer). El CPU queda libre, o dormido, mientras se mueven los datos.
En el ejemplo del ADC, un buffer circular de 512 muestras con una interrupción en cada mitad da una interrupción cada 256 muestras; a 100 kS/s son unas 390 interrupciones por segundo en lugar de 100,000, y con granularidad de buffer completo unas 195. La sobrecarga por evento se vuelve despreciable, y las muestras siguen equiespaciadas porque es el hardware, no el software, el que dispara cada conversión.
La nota de aplicación AN4031 de STMicroelectronics sobre los controladores DMA de sus STM32F2, F4 y F7 describe bien estos modos, incluido el modo circular y un modo de doble buffer en el que el controlador cambia solo entre dos buffers de memoria. El DMA tiene sus propios costos: la complejidad de configurarlo, la necesidad de procesar cada mitad del buffer antes de que se sobrescriba, la coherencia de caché en núcleos con caché de datos y el ancho de banda del bus que comparte con el CPU. Un buffer lleno que no se procesa a tiempo se sobrescribe sin avisar, así que el firmware debe detectar y contar los desbordamientos.
Lo normal es combinarlos
El firmware real mezcla los tres. Una UART recibe por DMA a un buffer circular y usa una interrupción de línea inactiva para avisar que terminó un mensaje. La línea de dato listo de un sensor despierta al procesador con una interrupción, y la rutina arranca una transferencia DMA de la FIFO del sensor. Un bootloader consulta una bandera en un ciclo cerrado porque no hay nada más corriendo. La pregunta nunca es cuál es mejor en general, sino cuál le queda a cada evento según su tasa, su requisito de tiempo y su presupuesto de energía.
Polling vs interrupciones vs DMA, comparados
| Propiedad | Polling | Interrupciones | DMA |
|---|---|---|---|
| Latencia de respuesta | Hasta el periodo de consulta | Decenas de ciclos más la rutina | Por buffer (latencia de bloque) |
| Costo de CPU por evento | Continuo, aunque no pase nada | Sobrecarga fija por evento | Casi nulo por dato; una interrupción por bloque |
| Comportamiento a tasas altas | Predecible pero derrochador | Puede saturarse o entrar en livelock | Escala bien |
| Consumo | El CPU no puede dormir mientras consulta | El CPU duerme entre eventos | El CPU duerme durante las transferencias |
| Determinismo | Alto (sin concurrencia) | Depende de prioridades y anidamiento | Alto en el tiempo de muestreo (disparado por hardware) |
| Complejidad | La más baja | Moderada (datos compartidos, prioridades) | La más alta (buffers, desbordamientos, cachés) |
| Le queda mejor a | Esperas cortas y acotadas, ciclos simples | Eventos irregulares de tasa baja a moderada | Flujos regulares de tasa alta |
Qué hacer
Calcula la carga de interrupciones antes de elegir
Multiplica la peor tasa de eventos por los ciclos por interrupción (entrada, rutina y salida) y divide entre el reloj. Si el resultado pasa de unos cuantos por ciento, considera DMA o agrupar eventos.
Usa DMA para flujos continuos de sensores
Para ADC, audio, FIFO de acelerómetros y datos seriales de alta velocidad, dispara el periférico con un temporizador, mueve los datos por DMA a un doble buffer y procesa cada mitad mientras se llena la otra.
Mantén las rutinas cortas y acotadas
Haz lo mínimo en contexto de interrupción (leer, guardar, levantar una bandera) y deja el procesamiento para el ciclo principal o una tarea. Mide el peor tiempo de cada rutina.
Protégete contra tormentas de interrupciones
Una línea de entrada con ruido o un periférico mal configurado pueden dispararse sin parar. Limita o deshabilita las fuentes que se pasen de su tasa esperada, y cuenta los desbordamientos en cualquier lugar donde se puedan perder datos.
Haz polling solo donde sea barato y acotado
El polling está bien para una espera corta sobre una bandera que se va a levantar en microsegundos, o en un ciclo donde no hay nada más que correr. Evítalo donde el dispositivo debería estar dormido.
Qué no hacer
No pongas una interrupción por muestra en un flujo cuya tasa va a crecer. Y no supongas que los buffers de DMA se procesan a tiempo; mide el tiempo de procesamiento contra el periodo de medio buffer.
Cómo ponerlo en práctica
Si tienes una hora
Enlista cada fuente de interrupción de tu firmware con su peor tasa, estima los ciclos por interrupción y suma la carga de CPU. Las entradas más grandes son candidatas para DMA.
Si tienes un sprint
Pasa la fuente de mayor tasa a DMA disparado por temporizador con doble buffer, agrega un contador de desbordamientos y mide la carga de CPU y el tiempo de reposo antes y después con un cambio de estado de GPIO a la entrada y salida de cada rutina.
Si quieres que dure toda la vida del producto
Guarda la tabla de carga de interrupciones junto con el diseño del firmware y actualízala cada vez que cambie una frecuencia de muestreo, una velocidad de comunicación o un periférico. Agrega contadores de desbordamiento al diagnóstico de campo para que los datos perdidos aparezcan en los registros y no como un comportamiento inexplicable.
La pregunta que debes hacerte: “Con la tasa de eventos más alta que va a ver este producto, ¿qué fracción del CPU se va en mover datos, y qué pasa cuando llega a 100 %?”
Preguntas frecuentes
¿Cuál es la diferencia entre polling e interrupciones?
El polling significa que el firmware revisa una y otra vez si ocurrió un evento; las interrupciones dejan que el hardware llame a una rutina cuando ocurre. El polling es sencillo y predecible pero usa CPU todo el tiempo y agrega una latencia de hasta un periodo de consulta. Las interrupciones responden rápido y dejan dormir al CPU, pero cuestan una sobrecarga por evento.
¿Cuándo conviene usar DMA en lugar de interrupciones?
Cuando los datos llegan a una tasa alta y regular, como muestras de ADC, audio o datos seriales de alta velocidad. El DMA mueve los datos sin el CPU e interrumpe una vez por buffer, así que desaparece la sobrecarga por evento que saturaría un diseño con una interrupción por muestra.
¿Cuál es la latencia de interrupción en un Cortex-M?
Según la guía de Arm, la latencia de entrada con memoria sin estados de espera es de 12 ciclos en el Cortex-M3 y el M4, 15 en el M0+ y 16 en el M0, y el tail-chaining entre rutinas toma unos 6 ciclos en el M3 y el M4. Los estados de espera, la contención del bus y las interrupciones de mayor prioridad se suman.
¿Qué es el livelock por interrupciones?
Es un estado en el que un sistema pasa tanto tiempo atendiendo interrupciones que no avanza en nada útil, porque el código que procesaría los datos nunca corre. Se describió en kernels de red con mucha carga de paquetes, y los remedios habituales son pasar a polling con carga alta y acotar el trabajo en interrupciones.
¿El polling es mejor que las interrupciones en algún caso?
Sí: en esperas cortas y acotadas (como esperar unos microsegundos a que un periférico esté listo), en ciclos muy simples sin nada más que hacer, y para evitar la sobrecarga y la concurrencia de las interrupciones a tasas muy altas, cuando un ciclo dedicado vacía una FIFO de hardware.
Lecturas relacionadas: cómo medir el consumo de un microcontrolador, RTOS vs bare metal y del sensor a la inferencia embebida.
Fuentes
- Mogul, J. C., y Ramakrishnan, K. K. (1997). “Eliminating Receive Livelock in an Interrupt-Driven Kernel.” ACM Transactions on Computer Systems, 15(3), 217–252. https://doi.org/10.1145/263326.263335
- Regehr, J., y Duongsaa, U. (2005). “Preventing Interrupt Overload.” Proceedings of the ACM SIGPLAN/SIGBED Conference on Languages, Compilers, and Tools for Embedded Systems (LCTES 2005). https://users.cs.utah.edu/~regehr/papers/lctes05/
- Yiu, J. (2016). “A Beginner’s Guide on Interrupt Latency – and Interrupt Latency of the Arm Cortex-M Processors.” Arm Community. https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/beginner-guide-on-interrupt-latency-and-interrupt-latency-of-the-arm-cortex-m-processors
- Arm Ltd. Armv7-M Architecture Reference Manual (DDI 0403), modelo de excepciones. https://developer.arm.com/documentation/ddi0403/latest/
- STMicroelectronics (2016). “Using the STM32F2, STM32F4 and STM32F7 Series DMA Controller.” Nota de aplicación AN4031, rev. 3. https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf



