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;
}
}
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.