Event-Driven State Machine

BME554L - Fall 2026

Author
Affiliation

Dr. Mark Palmeri, M.D., Ph.D.

Duke University

Published

August 25, 2026

Introduction

  • Nested conditional logic main loops are hard to read and maintain.
  • State machines are a common way to implement complex logic.
  • States have transitions that are triggered by events or conditions.
    • States can have entry / exit routines that are executed when the state is entered / exited.
    • The “run” status of a state is commonly referred to as the “state machine tick” and recurrently loops.
  • State diagrams are used to visualize state machines.
  • State structures are used to capture variables associated with describing the state.

In-Class Demo

  • You (and your daily routine)
    • What are your “states”?
    • What are your “events”?
  • ICD

stateDiagram-v2
    direction LR
    [*] --> Init

    Init : Do Stuff
    RhythmDetect : Detect Rhythm
    Sleep : Save Power
    Pace : Pace
    Error : Put device into safe mode

    state OTA {
        direction LR
        [*] --> ota_init
        ota_init --> ota_run
        ota_run --> ota_exit
        ota_init : Download Update
        ota_run : Over The Air Update
        ota_exit : Post Successful OTA
    }

    Init --> RhythmDetect : Successful Init
    Init --> Error : Init Fault Error Code
    RhythmDetect --> Pace : Arrhythmia Detected
    Pace --> RhythmDetect : Pacing Timer Expired
    RhythmDetect --> Sleep : NSR
    Sleep --> RhythmDetect : Sleep Timer Expired
    Sleep --> OTA : OTA Button Pressed
    OTA --> RhythmDetect : OTA Success Kernel Event Posted
    Error --> [*]

Implementation

State Diagram

  • Generating the state diagram is the first step in implementing a state machine.
  • Usually start with “pencil and paper” to sketch out the states and transitions.
  • Consider the following:
    • What are the states?
    • What are the transitions?
    • What are the events that trigger transitions?
    • What are the entry / exit routines for each state?
  • UML (Universal Modeling Language) is a common way to represent state diagrams.

Mermaid Example

stateDiagram-v2
    direction LR
    [*] --> Init

    state Init {
        init_run: Do Stuff
    }

    state RhythmDetect {
        rhythm_detect_run: Detect Rhythm
    }

    state Sleep {
        sleep_run: Save Power
    }

    state Pace {
        pace_run: Pace
    }

    state OTA {
        ota_init: Download Update
        ota_init --> ota_run
        ota_run: Over The Air Update
        ota_run --> ota_exit
        ota_exit: Post Successful OTA
    }

    state Error {
        error_run: Put device into safe mode
    }

    Init --> RhythmDetect : Successful Init
    Init --> Error : Init Fault Error Code
    RhythmDetect --> Pace : Arrhythmia Detected
    Pace --> RhythmDetect : Pacing Timer Expired
    RhythmDetect --> Sleep : NSR
    Sleep --> RhythmDetect : Sleep Timer Expired
    Sleep --> OTA : OTA Button Pressed
    OTA --> RhythmDetect : OTA Success Kernel Event Posted
    Error --> [*]

Rendered Diagram

stateDiagram-v2
    direction LR
    [*] --> Init

    state Init {
        init_run: Do Stuff
    }

    state RhythmDetect {
        rhythm_detect_run: Detect Rhythm
    }

    state Sleep {
        sleep_run: Save Power
    }

    state Pace {
        pace_run: Pace
    }

    state OTA {
        ota_init: Download Update
        ota_init --> ota_run
        ota_run: Over The Air Update
        ota_run --> ota_exit
        ota_exit: Post Successful OTA
    }

    state Error {
        error_run: Put device into safe mode
    }

    Init --> RhythmDetect : Successful Init
    Init --> Error : Init Fault Error Code
    RhythmDetect --> Pace : Arrhythmia Detected
    Pace --> RhythmDetect : Pacing Timer Expired
    RhythmDetect --> Sleep : NSR
    Sleep --> RhythmDetect : Sleep Timer Expired
    Sleep --> OTA : OTA Button Pressed
    OTA --> RhythmDetect : OTA Success Kernel Event Posted
    Error --> [*]

Kernel Events

  • What is a “kernel”?
    • The part of the RTOS that interacts with hardware.
  • Kernel events can be used to indicate that “something” has happened, which may dictate the function of the state machine.
    • Button press
    • Sensor crossing a threshold
    • Timer expiring
  • When an event occurs, it triggers an Interupt Service Routine (ISR), which calls a callback function.

Code: Switch-Case

  • The simplest implementation of a state machine is a switch-case statement.
  • The switch statement is used to select the current state.
  • The case statements are used to implement the logic for each state.
    • Cases can be nested to implement sub-states.
    • Enumerations can be used to give states verbose names instead of numbers.
  • The break statement is used to exit the switch statement.
  • The default statement is used to handle unexpected states.

Pseudo-Code

enum device_states { init, run, sleep };

int device_state = init; // initialize state

/* structure to bookkeep state variables */
struct device_state_vars {
    int var1;
    int var2;
};

while (1) {
    switch (device_state) {
        case init:
            /* do stuff to initialize device */
            device_state = run; // change the state
            break;  // exit the switch statement
        case run:
            /* run device */
            if (condition) {
                device_state = sleep;
            }
            break;
        case sleep:
            /* sleep device */
            if (condition) {
                device_state = run;
            }
            break;
        default:
            /* handle unexpected state */
            break;
    }
}

The switch-case approach loses some of its elegance when there are many states and many transitions and states have entry / exit routines.

Resources