Skip to content

Expert · Embedded C, system calls and careers · topic 57 of 60 · 25 min

Embedded patterns: interrupts, timing and constraints

Explanation

Firmware is usually a "super loop" that checks inputs and updates outputs forever, helped by interrupts: hardware events that pause the loop and run a short interrupt service routine (ISR). ISRs must be quick and share data with the main loop through volatile variables and careful design. Debounce buttons, avoid blocking delays, avoid malloc in many projects, and follow rules like MISRA C in safety-critical products.

The super loop

c
int main(void) {
    hardware_init();
    for (;;) {
        read_sensors();
        update_state_machine();
        update_outputs();
    }
}
Simple and predictable; good for small devices.

Interrupts

An interrupt is a hardware signal (a byte arrived on the UART, a timer expired, a button was pressed) that makes the CPU pause the current code, run a special function called an interrupt service routine (ISR), and then resume exactly where it was. Interrupts let the device react within microseconds without constantly polling.

c
static volatile uint32_t ticks;            /* shared with the ISR: volatile */
static volatile uint8_t  button_pressed;

void SysTick_Handler(void) {               /* called by hardware every 1 ms (name from the vendor) */
    ticks++;
}

void EXTI0_IRQHandler(void) {              /* button interrupt */
    button_pressed = 1;                    /* just record it; do the work in the main loop */
    /* clear the interrupt flag here, as the reference manual requires */
}

int main(void) {
    for (;;) {
        if (button_pressed) {
            button_pressed = 0;
            start_wash_cycle();            /* slow work happens outside the ISR */
        }
    }
}
Careful: Keep ISRs short: no printf, no malloc, no long loops or delays inside them. While an ISR runs, other interrupts (and the main loop) wait, so a slow ISR causes missed data and jitter. Variables shared between an ISR and the main loop must be volatile, and multi-byte updates (for example a 32-bit counter on an 8-bit AVR) must be read with interrupts briefly disabled, or the main loop can see half-updated values.

Timing without blocking

c
uint32_t last = 0;
for (;;) {
    if (ticks - last >= 500) {     /* every 500 ms, without stopping everything else */
        last = ticks;
        toggle_led();
    }
    poll_uart();                   /* the loop keeps serving other jobs */
}
Using unsigned subtraction makes the comparison correct even when ticks wraps around.

Debouncing a button

A mechanical button "bounces": for a few milliseconds after pressing, its contact opens and closes many times, which the MCU sees as several presses. Debouncing means accepting a new state only after it has been stable for, say, 20 ms (in software) or adding a small RC filter (in hardware).

Constraints that shape embedded C

  • Little RAM: avoid big local arrays (small stacks) and often avoid malloc entirely, because fragmentation over months of uptime can cause failure. Allocate statically at start-up.
  • Real-time deadlines: an airbag controller or a motor driver must respond within microseconds or milliseconds; code paths must have predictable timing.
  • Reliability: devices run for years without restarts; watchdog timers reset the MCU if the main loop ever hangs.
  • Safety and standards: automotive code follows MISRA C and processes like ISO 26262; AUTOSAR provides a standard software architecture for car ECUs.
  • RTOSes such as FreeRTOS and Zephyr add tasks, queues and timers when a super loop becomes too complex.
Think of it like this: An interrupt is like a doorbell while you are cooking: you pause, take the parcel (record the event quickly), and go back to the stove. If you stopped to unpack and assemble the parcel at the door, the dal would burn and the next visitor would ring unanswered.
Tip: Learn to read a datasheet and a reference manual early; finding the right register, bit and timing table is a daily embedded skill that interviewers test with practical questions.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Embedded patterns: interrupts, timing and constraints · 1/6

Embedded patterns

In this lesson you will learn how firmware is structured.

Prefer a video by a teacher? Find free videos on “embedded c interrupts isr volatile super loop debounce tutorial” on YouTube ↗

In real life: A washing machine controller

A washing machine's controller runs a state machine (fill, wash, rinse, spin) in its main loop. A timer interrupt keeps time, a door-sensor interrupt stops the drum immediately if the door opens, and the motor speed is updated every 10 ms. No malloc is used; all buffers are static, and a watchdog resets the board if the loop ever stops.

c
typedef enum { IDLE, FILL, WASH, RINSE, SPIN, DONE } State;
static State state = IDLE;

void update_state_machine(void) {
    switch (state) {
        case FILL:  if (water_level() >= TARGET_LEVEL) { valve_close(); state = WASH; } break;
        case WASH:  if (elapsed_ms() > WASH_MS) state = RINSE; break;
        /* … */
        default: break;
    }
}

Practice (0/3 done)

Task 1 · Question

What should an interrupt service routine usually do?

Task 2 · Code · Javascript

The same software debounce algorithm in JavaScript, which runs here: write debounce(samples, stableCount) where samples is an array of raw button readings (0 or 1) taken every millisecond. The debounced state starts at samples[0] and changes only after the raw reading has differed from the current state for stableCount consecutive samples. Return the number of debounced 0→1 presses.

Task 3 · Practice

Design (on paper or in C on a PC) the firmware for a cold-storage alarm: a timer tick every 1 ms, a temperature sensor read every 1 s, an alarm buzzer that beeps if the temperature is above 8 °C for more than 5 minutes, and a button to silence it. Show the state machine, which parts run in ISRs and which in the main loop, and which variables are volatile.

Quick quiz

Question 1

1.Why do many embedded projects avoid malloc after start-up?

Question 2

2.What does a watchdog timer do?

Question 3

3.What is button bounce?

Question 4

4.Which is a real-time operating system often used on microcontrollers?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why do many embedded projects avoid malloc after start-up?

Answer: Fragmentation and unpredictable failures over long uptimes

Static allocation is predictable.

What does a watchdog timer do?

Answer: Resets the MCU if the software stops "kicking" it (for example after a hang)

A safety net for hangs.

What is button bounce?

Answer: Rapid open/close transitions for a few milliseconds after pressing

Debounce in software or hardware.

Which is a real-time operating system often used on microcontrollers?

Answer: FreeRTOS

Zephyr is another.

Which language is a natural next step after C for embedded and systems work?Related

Answer: C++ or Rust

Both are common in embedded and systems roles.

From Projects and careers with C

Does volatile make a variable safe to share between threads?Related

Answer: No: it only prevents the compiler from optimising away accesses

Use atomics or locks for threads.

From Embedded C basics

How do you stop a timer started with const id = setInterval(tick, 1000)?Related

Answer: clearInterval(id)

Pass the id that setInterval returned to clearInterval.

From Timers: running code later

In debounce, what does clearTimeout(timer) do on each call?Related

Answer: Cancels the previously scheduled call

Each new call cancels the waiting one and restarts the wait.

From Memoization, debounce and throttle

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Embedded C, system calls and careers” before its exam: C on Linux: files and processes with system calls, C interview questions, Projects and careers with C.
  2. Module 12 exam: Embedded C, system calls and careers · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. 60 topics left in the course before the final exam.

← Previous: Embedded C basics