These are the exact fragments the model serves — also available as an
ordered study guide.
Getting off the main loop
A microcontroller runs one program, top to bottom, forever. The trouble is that the world it controls does not wait its turn. A button gets pressed halfway through some slow calculation; a sensor needs reading at an exact rhythm the main loop cannot keep. A program that only ever does the next line of its loop is always a little late, and sometimes it misses the moment entirely.
There are two ways off that treadmill, and this concept is about both. The first is the interrupt: a piece of hardware that can reach in, stop the CPU mid-stride, run a short handler, and then let the loop carry on as if nothing happened. The second is the hardware timer: a counter that ticks at a known rate on its own, so the program can keep accurate time without sitting there counting.
Between them they turn firmware from something that constantly asks "has anything happened yet?" into something that gets told the instant it does, and that can act on a schedule tighter than its own main loop. Reading a pin by checking it over and over — polling — is where every beginner starts and where this concept begins too, because seeing what polling cannot do is the fastest way to see why interrupts and timers exist.
Polling — asking, over and over
Polling is the obvious way to watch for something: read the condition in a loop, and when it changes, act. Reading a GPIO input pin over and over to see whether a button has been pressed is polling, and it works — as long as the loop comes back around often enough to catch the change. It is like getting up to check the mailbox every few seconds all day: most trips find nothing, they keep you from doing anything else, and a letter that arrives and is picked up again between two trips is one you never see, and the whole method lives or dies on how often you look.
The first cost is waste. Most of the time the answer is "no, nothing yet," and every one of those checks is CPU work spent learning nothing. While the loop is busy polling one thing, it is not doing anything else, and if some other part of the loop runs long, the checks come further apart.
The second cost is worse, because it is a bug you cannot see coming. Between two reads the program is blind. If the event you are watching for is briefer than the gap between checks — a fast pulse from a sensor, a short blip on a line — it can arrive and vanish while the loop is off doing something else, and the check that lands afterward sees nothing wrong. Polling does not just cost cycles; past a certain speed it stops being reliable at all.
The interrupt — being told, not asking
On the same stream of events, polling checks at fixed times and misses a brief event that falls between two checks, while the interrupt catches every event the instant it arrives.
An interrupt turns the whole arrangement around. Instead of the program asking again and again whether something has happened, the hardware tells it the instant it does. When the triggering event occurs, a signal reaches the CPU, and the CPU stops what it is doing between one instruction and the next. It is like a doorbell instead of pacing to the door to look: you can get on with your work and be pulled away only at the moment someone actually arrives — you do not have to keep walking to the door to check, because the door itself calls you.
What happens next is precise. The CPU saves just enough of its state to find its way back, then jumps to a fixed piece of code set up in advance to handle this event: the interrupt service routine, or ISR. The ISR runs, does its job, and returns. The CPU restores the state it saved and resumes the interrupted program at the exact instruction it was about to run, which never learns it was paused.
The payoff is that no event has to wait for the loop to come back around, and none slips through a gap between checks. A brief pulse that polling would sample right past raises its interrupt the moment it appears, and the ISR catches it. The cost of watching drops to nothing — the CPU spends no cycles asking — and the response is nearly immediate instead of "whenever the loop next looks."
What can raise an interrupt
Two sources raise an interrupt on the CPU: an edge on an input pin, and a hardware timer reaching its set count.
An interrupt is only useful if something can raise one, and on a microcontroller the sources fall into two broad kinds. The first is external: a change on an input pin. A GPIO pin can be configured to interrupt not on a level but on an edge — the moment it goes from LOW to HIGH (a rising edge) or from HIGH to LOW (a falling edge). That edge is the "something happened," and it is what a button, a limit switch, or a sensor's data-ready line uses to get the CPU's attention.
The second kind is internal, generated by the chip's own peripherals, and the most important of these is the hardware timer. A timer counts on its own and can be set to raise an interrupt when it reaches a chosen value, so the "event" is simply the passage of a known amount of time. Nothing outside the chip has to move; the interrupt fires on schedule.
Those two — an edge on a pin and a timer reaching its count — cover most of what embedded code reacts to. One says "the outside world just did something"; the other says "the moment you were waiting for has arrived." A single program usually has several of both configured at once, each pointed at its own handler, so the CPU can be pulled aside by whichever fires first.
What makes a good ISR
On an interrupt the CPU leaves the main loop, runs a short ISR that just sets a flag and returns, and the main loop later reads the flag and does the heavy work.
An interrupt service routine runs at the worst possible time — in the middle of whatever the main program was doing — so the rule that governs it is short. A good ISR does the smallest amount of work that cannot wait, and then gets out. It is like signing for a delivery at the door and setting the box down to open later, rather than unpacking it on the doorstep while everything else waits: take the thing off the doorstep, put it down, and go back to what you were doing; do not unwrap it there.
In practice "the smallest amount" usually means: read whatever the hardware needs read, clear the condition that raised the interrupt so it does not fire again immediately, set a flag or drop a value into a buffer, and return. The flag is the whole trick. The ISR does not act on the event; it just records that the event happened. The main loop, on its own schedule, notices the flag, clears it, and does the real work — the slow calculation, the screen update, the reply.
The reason to be this strict is that while an ISR runs, the CPU is not doing anything else, and other interrupts may be held off waiting for it to finish. A long ISR makes the whole system sluggish and unresponsive, which is the exact problem interrupts were supposed to solve. Keep the handler tiny and the responsiveness stays; let it grow and you have quietly rebuilt a program that misses things.
Hardware timers — a counter that never sleeps
A fixed-rate clock ticks a counter upward; when the count reaches the set value the timer fires an interrupt and resets to zero, repeating at a precise interval.
A hardware timer is, at its heart, a counter wired to a clock. Every tick of a clock running at a known, fixed rate adds one to the count, and it does this in dedicated hardware, entirely on its own — the CPU can be running any code, or none, and the count keeps climbing. Because the tick rate is known, the count is time: a certain number of ticks is a certain number of seconds, exactly. It is like a metronome ticking steadily in the corner: it keeps perfect time on its own whether or not you are paying attention to it, keeping the beat whether or not anyone is listening.
That single counter does two jobs. Set it to raise an interrupt when it reaches a chosen value and then reset, and it becomes a metronome that fires on a precise, repeating interval — every millisecond, every sample, forever, without the main loop's help. Or leave it running freely and read the count at two different moments; the difference is exactly how much time passed between them, which is how firmware measures how long something took.
The number you compare against is what sets the rhythm. A microcontroller's clock is usually fixed — a classic 8-bit part like the one on an Arduino Uno runs at 16 MHz, and many 32-bit parts run far faster, into the tens or hundreds of megahertz as of the mid-2020s — so choosing the compare value chooses the interval directly. Pick it for a few thousand ticks and the timer fires a few thousand times a second, on the beat, no matter what the rest of the program is up to.
Why a timer beats counting instructions
The tempting way to wait a fixed amount of time without a timer is a delay loop: spin the CPU through a do-nothing loop a set number of times and trust that the counting takes about as long as you want. It is easy to write and it is wrong in two expensive ways.
The first is that it burns the CPU to do nothing. For the entire delay the processor is stuck spinning, unavailable for any other work — the opposite of what you want on a chip that has real jobs to do. The second is that its timing is not trustworthy. How long a loop actually takes depends on the clock speed, on how the compiler chose to translate it, and on any interrupt that fires in the middle and steals time. Change the clock, change compilers, or add an interrupt somewhere else and a delay that was tuned to a millisecond quietly becomes something else. The number drifts, and nothing warns you.
A hardware timer has neither problem. It keeps time in dedicated hardware at a known rate, so its interval is accurate and stays accurate no matter what the CPU is doing or how the code is compiled — and it leaves the CPU free the whole while, because the counting is not the processor's job. When timing has to be right, you do not count instructions and hope; you let a counter that only counts do the counting.
Where this earns its keep
Two everyday jobs show why interrupts and timers are not optional. The first is a button. Configure a pin to interrupt on its falling edge and a press calls the CPU the instant it happens, no polling required — except that a real switch does not close cleanly. Its metal contacts bounce for a short interval, roughly a few milliseconds up to a couple of tens of milliseconds, and each bounce looks like another edge, so one press can fire the interrupt several times. The fix uses both tools together: on the first edge, note the time and then ignore further edges until a timer says the settling window has passed. That is debouncing, and it is an interrupt caught cleanly by a timer.
The second job is doing something at an exact rhythm. Reading a sensor or updating a control output has to happen at a steady rate or the math built on it goes wrong, and the main loop is far too irregular to be that rate. A timer set to fire, say, a few thousand times a second gives the program a heartbeat: every tick, sample the input; between ticks, do everything else. The rhythm comes from hardware, not from hoping the loop takes the same time each pass.
For a build like a small self-driving car, both show up immediately. A bumper switch or a wheel-encoder pulse is an edge interrupt — miss it and the car does not notice the wall or loses count of how far it has gone. The steering and motor control run off a timer's steady beat so the loop that keeps the car on course samples and corrects at a fixed rate. Get these right and the car reacts the moment the world changes and keeps time while it thinks; get them wrong and it is always a little late, which on something moving is the whole game.