Fundamentals 32 min read

Mastering Event‑Driven Design and State Machines for Embedded Firmware

This article explains how event‑driven programming and state‑machine concepts can be applied to microcontroller firmware, compares a simple flag‑based approach with a message‑queue solution, and presents a reusable GF1.0 framework that combines ISR, message buffering, and a main state machine for robust embedded systems.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Mastering Event‑Driven Design and State Machines for Embedded Firmware

Event‑Driven Thinking

Event‑driven programming treats external stimuli as events that wake a dormant object, similar to how a student sneaks a nap during a loosely supervised study session and relies on a classmate to alert them when the teacher arrives.

Event‑Driven vs. Interrupts in Microcontrollers

In microcontroller software, an interrupt service routine (ISR) detects hardware events and sets a flag. The main loop polls these flags, determines which events occurred, and executes the corresponding handlers. This flag‑based design (shown in List 9 ) separates the driver layer from the application layer but has two major drawbacks:

When multiple different events fire close together, the order of occurrence is lost.

If the same event fires repeatedly, later occurrences may be overwritten, causing missed events.

Message‑Queue Solution

To preserve event order and avoid loss, events are transformed into messages stored in a FIFO circular buffer. Each message contains a header ( u8MsgID, optional u8UsrID) and a payload defined by a union MSG_ARG that can hold integers, floats, or pointers.

#define FLG_UART 0x01
#define FLG_TMR  0x02
#define FLG_EXI  0x04
#define FLG_KEY  0x08

volatile INT8U g_u8EvntFlgGrp = 0; /* event flag group */

The buffer structure ( MB) holds the array of MSG objects and bookkeeping fields ( u8MBLock, u8MsgSum, u8MQHead, u8MQTail). The API consists of nine functions, such as mq_init(), mq_msg_post_fifo(), and mq_msg_req_fifo(), which are called from ISRs (producer) and the main task (consumer).

Advantages of the Message‑Based Mechanism

Because each ISR posts a complete message, the system can:

Record the exact chronological order of different events.

Handle multiple occurrences of the same event without loss, as each occurrence becomes a distinct message.

Separate time‑critical ISR work (only message creation) from longer processing in the main loop.

GF1.0 Framework – ISR + Message Buffer + Main State Machine

The GF1.0 architecture (illustrated in the original figures) consists of:

Multiple ISRs that generate messages.

A circular message queue that stores these messages.

A single application‑level state machine that consumes messages and drives the system logic.

The state‑machine engine ( sme_kernel()) repeatedly checks the queue, retrieves a message, looks up the current state in a compressed FSM table, invokes the appropriate action function, and updates the state.

void sme_kernel(void)
{
    while (1) {
        if (!mq_is_empty()) {
            if (mq_msg_req_fifo(&stMsgTmp) == MREQ_NOERR) {
                u8CurStat = get_cur_state();
                stNodeTmp = g_arFsmDrvTbl[u8CurStat];
                if (stNodeTmp.u8StatChk == u8CurStat) {
                    u8CurStat = stNodeTmp.fpAction(&stMsgTmp);
                    set_cur_state(u8CurStat);
                } else {
                    state_crash(u8CurStat);
                }
            }
        } else {
            idle_task();
        }
    }
}

Practical Example – Key and Timeout Handling

A simple application monitors two events: a key press (event KEY) and a 10‑second timeout (event TOUT). The key‑press detection is itself a three‑state sub‑state‑machine (WAIT_DOWN → SHAKE → WAIT_UP) driven by a periodic timer ISR ( TICK). When the debounce timer expires, the ISR posts a KEY message; the timeout ISR increments a software counter and posts a TOUT message after 500 ticks.

Serial Communication Case Study

For UART communication, two design patterns are discussed:

ISR + Message : each received byte becomes a message – simple but inefficient for high baud rates.

ISR + Buffer + Message : the ISR stores bytes in a receive buffer, and once a full frame is detected, it posts a single message containing the frame. This approach respects layering (driver handles low‑level I/O) and avoids buffer overflow.

The receive ISR is modeled as a state machine that parses the frame header, length, payload, and checksum, locking the buffer while the main task validates the data.

Conclusion

The GF1.0 framework demonstrates how event‑driven design, message queues, and state machines together form a clean, reusable structure for bare‑metal firmware. By isolating hardware‑specific ISR work from high‑level application logic, developers can build reliable, maintainable embedded systems that scale from simple key handling to complex protocol stacks.

Event‑driven architecture diagram
Event‑driven architecture diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

state machineMessage Queueembedded systemsfirmwareMicrocontroller
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.