A logic analyser wired to a microcontroller board, and a laptop showing a dense clock, two short interrupt pulses and a transfer window
Table of contents

Polling vs Interrupts vs DMA: Choosing How Data Moves

Polling vs interrupts vs DMA explained: latency, CPU load, power and determinism, with worked numbers for UART and ADC data and each approach's failure mode.

The ADC samples at 100 kHz, an interrupt fires on every conversion, and the firmware that worked fine at 1 kHz now drops samples and the user interface freezes. Nothing is broken; the processor is spending most of its time entering and leaving interrupt handlers. The choice of polling vs interrupts vs DMA decides how much of the CPU is left for actual work, how quickly the system responds and how long it can sleep. This article compares the three with worked numbers, explains the failure mode each one has, and summarizes what research on interrupt overload found about systems that handle too many events one at a time.

Key Takeaways

  • Interrupt entry on Cortex-M3 and M4 takes 12 cycles with zero-wait-state memory (15–16 on M0+ and M0), and tail-chaining between handlers about 6. The handler’s own work usually costs more.
  • Per-event overhead scales with the event rate. At 60 cycles per interrupt and 48 MHz, a 115,200-baud UART costs about 1.4% of the CPU; an ADC at 1 MS/s would need 125%, which is impossible.
  • DMA moves data without the CPU and interrupts once per buffer: 100 kS/s into 512-sample buffers is about 195 interrupts per second.
  • Polling adds latency up to the polling period and keeps the CPU awake, which is costly in battery devices.
  • Interrupt-driven systems can livelock under heavy load. Research on network kernels and embedded systems shows the fix is to bound interrupt work or switch to polling when the rate is high.

The problem: per-event costs that grow with the data rate

Every way of moving data has a fixed cost per event and a cost per byte. Low-rate, irregular events (a button, a limit switch, a byte arriving occasionally) favor interrupts: nothing happens until the event, and the response is fast. High-rate, regular streams (an ADC, a microphone, an accelerometer FIFO) favor DMA: the per-event cost of an interrupt multiplied by tens of thousands of events per second consumes the processor. Polling sits apart: it’s the simplest and most predictable, and it costs CPU time whether or not anything happened.

Most firmware problems with data movement come from using the method that fit the first prototype at a rate it was never meant for.

How each approach behaves

Polling: simple, predictable and always awake

Polling means the firmware checks a status flag in a loop or at a fixed interval. It has no hidden concurrency: the code that reads the data runs exactly where you put it. That makes it easy to reason about and test.

The costs are latency and power. The worst-case response time is the polling period plus the work in the loop. If a flag is checked every millisecond, an event can wait up to a millisecond. And the processor can’t sleep while it polls, which in a battery device usually rules polling out except for short, bounded waits.

Interrupts: fast response, overhead per event

An interrupt lets the hardware call a handler when the event occurs. Joseph Yiu’s Arm guide to interrupt latency gives the entry latency for Cortex-M processors with zero-wait-state memory: 12 cycles on the Cortex-M3 and M4, 15 on the M0+ and 16 on the M0. When one handler finishes and another is pending, tail-chaining skips part of the register save and restore and takes about 6 cycles on the M3 and M4. Wait states, bus contention, higher-priority handlers and the chip’s own interrupt routing add to those figures.

The handler’s work then comes on top. An illustrative budget of 60 cycles per interrupt (entry, a short handler that reads a register and stores a byte, and exit) gives:

  • A UART at 115,200 baud with 10 bits per character delivers 11,520 characters per second. At 60 cycles each on a 48 MHz core, that’s about 1.4% of the CPU. Interrupts are a good fit.
  • An ADC at 100 kS/s with one interrupt per sample costs 6 million cycles per second, 12.5% of the CPU, before any processing.
  • The same ADC at 1 MS/s would need 125% of the CPU. It can’t be done with an interrupt per sample.

When interrupts overload the system

Mogul and Ramakrishnan’s 1997 paper in ACM Transactions on Computer Systems described receive livelock: under a high enough rate of network packets, an interrupt-driven kernel can spend all its time in interrupt handling and make no useful progress, because the higher-priority interrupts starve the code that would have consumed the data. Their remedy combined interrupts at low load with polling at high load, and limited the work done in interrupt context.

Regehr and Duongsaa brought the same concern to embedded systems at LCTES 2005. They pointed out that thread schedulers can’t protect a system from overload that happens below them, in interrupt handlers, and presented software and hardware “interrupt schedulers” that delay or drop excess interrupt requests to bound the amount and granularity of work done in interrupt context. The design lesson for firmware is to make sure the worst-case interrupt rate is bounded, either by hardware (FIFOs, DMA) or by software that disables or rate-limits a source when it fires too often.

DMA: moving blocks without the CPU

A DMA controller copies data between a peripheral and memory on its own, triggered by the peripheral, and interrupts the CPU only when a block is done (or half done, in circular double-buffer mode). The CPU is free, or asleep, while the data moves.

In the ADC example, a circular buffer of 512 samples with an interrupt at each half means one interrupt per 256 samples; at 100 kS/s that’s about 390 interrupts per second instead of 100,000, and at full-buffer granularity about 195. The per-event overhead becomes negligible, and the samples stay evenly spaced because the hardware, not the software, triggers each conversion.

STMicroelectronics’ application note AN4031 on its STM32F2, F4 and F7 DMA controllers is a representative description of these modes, including circular mode and a double-buffer mode in which the controller switches between two memory buffers automatically. DMA has its own costs: setup complexity, the need to process each half of the buffer before it’s overwritten, cache coherence on cores with data caches, and bus bandwidth shared with the CPU. A full buffer that isn’t processed in time is silently overwritten, so firmware should detect and count overruns.

Hybrids are normal

Real firmware mixes the three. A UART receives with DMA into a ring buffer and uses an idle-line interrupt to signal the end of a message. A sensor’s data-ready line wakes the processor by interrupt, and the handler starts a DMA transfer of the sensor’s FIFO. A bootloader polls a flag in a tight loop because nothing else is running. The question is never which one is best in general, but which one fits each event’s rate, timing requirement and power budget.

Polling vs interrupts vs DMA compared

Property Polling Interrupts DMA
Response latency Up to the polling period Tens of cycles plus handler Per buffer (block latency)
CPU cost per event Continuous, even when idle Fixed overhead per event Near zero per item; one interrupt per block
Behavior at high event rates Predictable but wasteful Can overload or livelock Scales well
Power CPU can’t sleep while polling CPU sleeps between events CPU sleeps during transfers
Determinism High (no concurrency) Depends on priorities and nesting High for sample timing (hardware-triggered)
Complexity Lowest Moderate (shared data, priorities) Highest (buffers, overruns, caches)
Best fit Short bounded waits, simple loops Irregular, low- to moderate-rate events Regular, high-rate streams

What to actually do

Calculate the interrupt load before choosing

Multiply the worst-case event rate by the cycles per interrupt (entry, handler and exit) and divide by the clock. If the result is more than a few percent, consider DMA or batching.

Use DMA for continuous sensor streams

For ADCs, audio, accelerometer FIFOs and high-rate serial data, trigger the peripheral from a timer, move data with DMA into a double buffer and process each half while the other fills.

Keep handlers short and bounded

Do the minimum in interrupt context (read, store, flag) and defer processing to the main loop or a task. Measure the worst-case handler time.

Protect against interrupt storms

A noisy input line or a misconfigured peripheral can fire continuously. Rate-limit or disable sources that exceed their expected rate, and count overruns everywhere data can be lost.

Poll only where it’s cheap and bounded

Polling is fine for a short wait on a flag that will be set within microseconds, or in a loop where nothing else needs to run. Avoid it where the device should be asleep.

What not to do

Don’t add an interrupt per sample to a stream that will grow in rate. And don’t assume DMA buffers are processed in time; measure the processing time against the half-buffer period.

How to put it to work

If you have an hour

List every interrupt source in your firmware with its worst-case rate, estimate the cycles per interrupt and add up the CPU load. The largest entries are candidates for DMA.

If you have a sprint

Move the highest-rate source to timer-triggered DMA with a double buffer, add an overrun counter and measure CPU load and sleep time before and after with a GPIO toggle on entry and exit of each handler.

If you want it to last the product’s lifetime

Keep the interrupt-load table with the firmware design and update it whenever a sample rate, baud rate or peripheral changes. Add overrun counters to field diagnostics so lost data shows up in logs rather than as unexplained behavior.

The question to ask yourself: “At the highest event rate this product will ever see, what fraction of the CPU goes to moving data, and what happens when it reaches 100%?”

Frequently asked questions

What is the difference between polling and interrupts?

Polling means the firmware repeatedly checks whether an event has happened; interrupts let the hardware call a handler when it happens. Polling is simple and predictable but uses CPU time constantly and adds latency up to the polling period. Interrupts respond quickly and let the CPU sleep, but cost overhead per event.

When should I use DMA instead of interrupts?

When data arrives at a high, regular rate, such as ADC samples, audio or high-speed serial data. DMA moves the data without the CPU and interrupts once per buffer, so the per-event overhead that would overload an interrupt-per-sample design disappears.

What is interrupt latency on a Cortex-M?

According to Arm’s guide, entry latency with zero-wait-state memory is 12 cycles on the Cortex-M3 and M4, 15 on the M0+ and 16 on the M0, with tail-chaining between handlers taking about 6 cycles on the M3 and M4. Wait states, bus contention and higher-priority interrupts add to it.

What is interrupt livelock?

It’s a state in which a system spends so much time handling interrupts that it makes no useful progress, because the code that would process the data never runs. It was described for network kernels under heavy packet load, and the usual remedies are switching to polling at high load and bounding interrupt work.

Is polling ever better than interrupts?

Yes, for short, bounded waits (such as waiting a few microseconds for a peripheral to become ready), for very simple loops with nothing else to do, and for avoiding the overhead and concurrency of interrupts at very high event rates where a dedicated loop polls a hardware FIFO.

Related reading: measuring microcontroller power consumption, RTOS vs bare-metal firmware and from sensor input to embedded inference.

Sources

  1. Mogul, J. C., & 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
  2. Regehr, J., & 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/
  3. 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
  4. Arm Ltd. Armv7-M Architecture Reference Manual (DDI 0403), exception model. https://developer.arm.com/documentation/ddi0403/latest/
  5. STMicroelectronics (2016). “Using the STM32F2, STM32F4 and STM32F7 Series DMA Controller.” Application Note AN4031, Rev. 3. https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf
Written byAlphaCrew Studio

We create technical content and animation, build custom apps and platforms, and set up AI-assisted workflows for engineering and technology teams.