Loading header...

Timers and Non-Blocking State Machines

Delay loops are easy, but they stop firmware from doing useful work. Timers and state machines let firmware react to events while still performing actions at the right time.

Learning Objectives

By the end of this lesson, you should be able to:

  • explain why blocking delays limit responsiveness;
  • implement elapsed-time checks with a tick counter;
  • write a simple non-blocking state machine;
  • handle timer wraparound safely.

Blocking Delay Problem

led_on();
delay_ms(500);
led_off();
delay_ms(500);

During each delay, the CPU may be unable to poll inputs, process serial data, update control loops, or enter low-power sleep correctly.

System Tick

A periodic timer interrupt can increment a counter:

static volatile uint32_t system_ms;

void SysTick_Handler(void)
{
    system_ms++;
}

uint32_t millis(void)
{
    uint32_t snapshot;
    disable_interrupts();
    snapshot = system_ms;
    enable_interrupts();
    return snapshot;
}

The interrupt gives a time base; the application decides what to do with it.

Wraparound-Safe Elapsed Time

static bool elapsed(uint32_t now, uint32_t start, uint32_t interval)
{
    return (uint32_t)(now - start) >= interval;
}

Unsigned subtraction handles wraparound correctly as long as intervals are less than half the counter range.

Non-Blocking LED State Machine

typedef enum {
    LED_STATE_OFF = 0,
    LED_STATE_ON
} led_state_t;

typedef struct {
    led_state_t state;
    uint32_t last_change_ms;
} led_blinker_t;

void led_blinker_update(led_blinker_t *b, uint32_t now)
{
    switch (b->state) {
    case LED_STATE_OFF:
        if (elapsed(now, b->last_change_ms, 500u)) {
            led_on();
            b->state = LED_STATE_ON;
            b->last_change_ms = now;
        }
        break;
    case LED_STATE_ON:
        if (elapsed(now, b->last_change_ms, 500u)) {
            led_off();
            b->state = LED_STATE_OFF;
            b->last_change_ms = now;
        }
        break;
    }
}
stateDiagram-v2 [*] --> OFF OFF --> ON: 500 ms elapsed ON --> OFF: 500 ms elapsed

Main Loop Pattern

int main(void)
{
    led_blinker_t blinker = {LED_STATE_OFF, 0u};

    while (1) {
        uint32_t now = millis();
        led_blinker_update(&blinker, now);
        uart_service(now);
        button_service(now);
    }
}

Each service function runs quickly and returns.

Common Mistakes

  • Resetting the start time every loop iteration.
  • Using signed arithmetic for wrapping counters.
  • Doing too much work inside the timer ISR.
  • Mixing blocking delays into an otherwise non-blocking design.
  • Assuming timer tick accuracy is better than the clock source.

Summary

Timers provide time measurement. State machines provide behavior. Together they replace blocking delays with firmware that remains responsive, testable, and easier to extend.

Further Reading