Devices are slow compared with processors. A UART needs hundreds of clock cycles to send one byte, a sensor may produce a reading once a second, and a network packet can arrive at any moment. A system has three ways to deal with that. With polling, the processor checks the device again and again. With interrupts, the device signals the processor when it needs attention. With DMA, a separate engine moves the data and the processor only hears about the result. This article explains all three, shows what polling costs on a small SoC, describes step by step what happens when an interrupt arrives, and shows how to choose.
13 min read | Beginner
What You'll Learn
- Explain polling, interrupts and DMA and what each costs the processor
- Calculate how much of a program's time goes to polling from its instruction counts
- Describe step by step what a RISC-V processor does when an interrupt arrives
- Choose between the three methods for a given device and data rate
The Problem: Devices Are Slow
Take a UART that sends each byte as 10 bits, with each bit lasting 16 clock cycles, so one byte takes 160 cycles. In that time the processor could execute well over a hundred instructions. The UART has an 8-byte FIFO (first in, first out buffer) that holds bytes waiting to be sent, so the processor can hand over a few bytes at once, but once the FIFO is full, it has to wait for room.
The question every system must answer is: what does the processor do while a device is not ready, and how does it learn that the device is ready again? There are three answers, shown side by side below:
Polling
Polling means reading the device's status register in a loop until the device reports it is ready, then doing the transfer. A simple UART driver does exactly this before every byte:
while (UART_STATUS & 1) /* bit 0: FIFO full */
;
UART_TXDATA = (u32)c;
The compiler turns the loop into three instructions: a load of STATUS, an AND that keeps bit 0, and a branch back while the bit is 1. Memory-Mapped I/O and Registers explains the registers and why STATUS must be declared volatile.
Polling is the simplest method, needs no extra hardware, and reacts fastest once the device is ready, because the processor is already looking. Its cost is that the processor can do nothing else while it waits.
Measuring the Cost
In one test, a small program sent 256 bytes over the UART by polling, generating each byte and updating a checksum as it went. It was run at two UART speeds, set by the DIV register (clock cycles per bit): DIV = 16, and four times faster at DIV = 4. At the slow speed the program read the status register about 13 times per byte. At the fast speed it read it about twice per byte, and the whole run took about a quarter of the time.
Each status read is one pass through the three-instruction loop, so the polling instructions are easy to count. Taking them away from the total leaves the real work, and that came out exactly the same at both speeds. The program did identical work; the only difference is how long it spent asking the UART whether it was ready. At the slow speed, 72 percent of all instructions executed were polling. At the fast speed it was 28 percent.
And it is not only instructions: every status read is a bus access that costs 6 cycles, which is why the core spent over 40 percent of the slow run waiting on the bus. In practice the whole run was spent sending 256 bytes, and nothing else could happen in the meantime.
Quick Check
Q: If the UART ran at DIV = 32, what would happen to the number of status reads and to the amount of real work?
Show Answer
The real work stays the same; it does not depend on the UART speed. Each byte would take 320 cycles instead of 160, so the processor would wait about twice as long per byte and make roughly twice as many status reads.
Interrupts
An interrupt turns the question around: instead of the processor asking the device, the device tells the processor. The device has an output wire, its interrupt request line, which it drives high when it needs attention, for example when its FIFO has room or new data has arrived. A timer is a simple example: its irq output is high while its cycle counter MTIME is greater than or equal to the compare value CMP, so software can ask to be woken at a chosen time.
What Happens When an Interrupt Arrives
The processor checks the interrupt line between instructions. In a RISC-V processor running in machine mode, the most basic privilege level, this is what happens when an enabled interrupt is pending:
- The processor finishes the current instruction and does not start the next one.
- It saves the address of the instruction it would have run next in a special register,
mepc(machine exception PC), records the reason inmcause, and disables further interrupts. - It jumps to the address held in
mtvec(machine trap vector), where software has placed the interrupt handler, also called the interrupt service routine. - The handler saves the general-purpose registers it will use on the stack, serves the device (for example, writes the next bytes into the UART FIFO, which makes the device lower its request), and restores the registers.
- The handler ends with the instruction
mret, which jumps back to the address inmepcand enables interrupts again. The interrupted program continues as if nothing had happened.
The special registers are CSRs, control and status registers, which hold the processor's own settings rather than program data.
The benefit is that the processor runs other work, or sleeps, between interrupts. The price is the overhead of each interrupt: emptying the pipeline, the jump, saving and restoring registers, and the return. That typically costs tens of cycles on a small core before the handler does any useful work. The time from the device's request until the handler starts is the interrupt latency, and real-time systems specify a maximum for it.
For the UART, an interrupt each time the FIFO becomes empty would let the handler write 8 bytes per interrupt: 256 bytes would need 256 / 8 = 32 interrupts, and between them the processor would be free.
Where Does This Matter?
In a battery-powered device, polling also costs energy, because the processor cannot sleep while it loops. Interrupt-driven designs let the processor sleep until a device wakes it, which is how microcontrollers in sensors and remote controls run for years on a coin cell. When a system has many interrupt sources, an interrupt controller collects them and tells the processor which one to serve first, as Interrupt Controllers explains.
Enabling, Prioritizing and Sleeping
A processor does not take every interrupt that arrives. Each source has an enable bit, and there is a global enable that switches all of them on or off. In a RISC-V processor the mie register (machine interrupt enable) holds one bit per source: bit 3 for software interrupts, bit 7 for the timer, bit 11 for external interrupts from devices. The matching bits of mip (machine interrupt pending) show which sources are requesting, and the MIE bit of mstatus is the global switch. An interrupt is taken only when its pending bit, its enable bit and the global enable are all 1. That is how software can set up a handler first and only then let interrupts in, and how it can briefly block them while it changes data that a handler also uses.
When several sources request at once, the processor needs a rule for which one to serve first: a priority. A fixed order is common, with an interrupt controller in front of the processor deciding among many devices. Some systems also allow nesting: a high-priority interrupt may interrupt the handler of a lower-priority one, which keeps the latency of urgent events short at the cost of more complex software.
Interrupts also let a processor sleep. RISC-V has the instruction wfi (wait for interrupt): the processor stops executing and can stop its clock until an enabled interrupt becomes pending. An idle loop built on wfi instead of polling uses a small fraction of the power. A typical low-power program sets the timer's compare register to the next deadline, executes wfi, does its work when the timer wakes it, and goes back to sleep.
DMA: Let Another Block Move the Data
For large amounts of data, even one interrupt per few bytes is too much. DMA, direct memory access, hands the whole transfer to a separate block, the DMA engine. It is a bus manager like the processor: it can read and write memory and device registers by itself.
The processor programs it through its registers: the source address (for example a buffer in SRAM), the destination (the UART's TXDATA register), the number of bytes, and a start bit. The DMA engine then moves the data, checking the device's readiness signals itself, and raises one interrupt when it has finished. For the 256-byte stream that would be one setup, one interrupt and, between them, a processor free for other work.
DMA has costs too. The engine is extra hardware. It shares the interconnect with the processor, so the two sometimes compete for the same bus or memory and one has to wait. And software must make sure the processor does not change a buffer while the DMA engine is still reading it. DMA Engines goes further into how such an engine works.
Choosing Between Them
| Method | Processor cost | Good for |
|---|---|---|
| Polling | busy the whole time it waits | very short waits, boot code, simple systems with nothing else to do |
| Interrupts | a handler per event, tens of cycles of overhead each | events that are rare or unpredictable: key presses, timers, incoming packets |
| DMA | setup plus one interrupt per transfer | large or continuous data: audio, network, storage, displays, cameras |
Real systems combine them. A network interface uses DMA for the packet data and an interrupt to say that packets have arrived; boot code polls because it has nothing else to do yet; a driver may poll for a few microseconds before falling back to an interrupt, because for very short waits the interrupt overhead costs more than it saves.
Quick Check
Q: A sensor produces one 4-byte reading every second. Which method fits best, and why?
Show Answer
Interrupts. Events are rare, so polling would waste almost all processor time, or keep it from sleeping, and DMA would be overkill for one small reading. One interrupt per reading costs a short handler once a second.
Common Mistakes
Mistake #1: Polling in a system that should sleep
A polling loop keeps the processor running at full power. For rare events, use an interrupt and let the processor sleep in between.
Mistake #2: Interrupting for every byte of a fast stream
At high data rates, the overhead of entering and leaving the handler dominates. Use a FIFO that interrupts only when it is nearly empty or full, or use DMA.
Mistake #3: Forgetting to clear the request
If the handler does not make the device lower its interrupt line, by serving it or by clearing its status bit, the processor takes the same interrupt again as soon as the handler returns, and the program never continues.
Practice Exercise
Challenge: The Price of Polling
Assume a program sends 256 bytes by polling in 41,283 cycles, on a UART that needs 160 cycles per byte, and does 15 instructions of real work per byte. (a) What share of the run is the minimum time the UART needs to send 256 bytes? (b) Suppose an interrupt-driven version needs 32 interrupts, each costing 60 cycles of overhead plus 8 bytes x 15 instructions of real work, at about one cycle per instruction (an assumption for this estimate). How many processor cycles would the whole transfer take, and what share of the UART's sending time would the processor be free?
Hint
Multiply the bytes by the cycles per byte, then compare with the whole run.
Solution
(a) 256 x 160 = 40,960 cycles, 99 percent of the run: the program is limited by the UART, and the processor spends nearly all that time waiting. (b) 32 x (60 + 8 x 15) = 32 x 180 = 5,760 cycles. The UART still needs about 40,960 cycles to send everything, so the processor is busy for 5,760 / 40,960 = 14 percent of that time and free for the other 86 percent.
Summary
- Polling keeps the processor busy; interrupts free it between events; DMA frees it for whole transfers.
- In one test of sending bytes by polling on a slow UART, 72 percent of the instructions were polling, and the real work stayed the same at both speeds.
- On an interrupt, a RISC-V processor saves the PC in mepc, jumps to the handler at mtvec, and returns with mret.
- Each interrupt costs overhead; DMA moves whole buffers with one setup and one interrupt.
- Real systems combine all three, choosing by event rate, data size and power.