The Powerful Impact of QP/C State Machines in Embedded Systems

In embedded system development, the behavior of devices often changes with internal state transitions. The State Pattern can manage these state-driven behavior changes, making the code clearer and more maintainable. Below, we will demonstrate this using QP/C.

<strong>Pattern Definition</strong>
The State Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes, making it appear as if it has changed its class. State encapsulation involves encapsulating the behavior of each state in separate classes, state transitions allow the object to switch behaviors by changing the current state reference, and eliminating conditional checks replaces complex conditional statements with polymorphism. This is the core idea of the State Pattern.

<strong>Application of State Pattern in Embedded Systems</strong>
Device State Machine Management
In embedded devices, devices often need to transition between different states, such as initialization, running, sleeping, error, etc.

// Device State Definition
typedef enum {
    DEVICE_STATE_INIT,
    DEVICE_STATE_READY,
    DEVICE_STATE_RUNNING,
    DEVICE_STATE_SLEEP,
    DEVICE_STATE_ERROR,
    DEVICE_STATE_CALIBRATION
} device_state_t;

// Device Event Definition
typedef enum {
    EVENT_POWER_ON,
    EVENT_INIT_COMPLETE,
    EVENT_START_OPERATION,
    EVENT_STOP_OPERATION,
    EVENT_ENTER_SLEEP,
    EVENT_WAKEUP,
    EVENT_ERROR_DETECTED,
    EVENT_ERROR_RESOLVED,
    EVENT_CALIBRATION_REQUEST
} device_event_t;

<strong>Communication Protocol Handling</strong>
Communication protocols often involve complex state transitions, such as connection establishment, data exchange, error recovery, etc.

// Communication Protocol State
typedef enum {
    PROTOCOL_STATE_DISCONNECTED,
    PROTOCOL_STATE_CONNECTING,
    PROTOCOL_STATE_HANDSHAKE,
    PROTOCOL_STATE_CONNECTED,
    PROTOCOL_STATE_TRANSFERRING,
    PROTOCOL_STATE_ERROR,
    PROTOCOL_STATE_RECONNECTING
} protocol_state_t;

<strong>User Interface State Management</strong>
The user interface of embedded devices needs to handle various interaction states.

// User Interface State
typedef enum {
    UI_STATE_BOOT,
    UI_STATE_MAIN_MENU,
    UI_STATE_SETTINGS,
    UI_STATE_OPERATION,
    UI_STATE_CONFIRMATION,
    UI_STATE_HELP,
    UI_STATE_SHUTDOWN
} ui_state_t;

<strong>QP/C Framework: An Industrial-Grade Solution for Embedded State Machines</strong>
QP/C (Quantum Platform in C) is a state machine-based active object framework designed specifically for embedded systems. It provides a complete implementation of Hierarchical State Machines (HSM), exemplifying the State Pattern in the embedded field.

<strong>Core Architecture of QP/C Framework</strong>
// Active Object Base Class
typedef struct QHsm {
    QStateHandler state;     // Current state handler function
    QStateHandler temp;      // Temporary state storage
} QHsm;

// Event Base Class
typedef struct QEvt {
    QSignal sig;            // Event signal
} QEvt;

// State Handler Function Type Definition
typedef QState (*QStateHandler)(void * const me, QEvt const * const e);

// State Machine Return Values
#define Q_RET_HANDLED       ((QState)0)
#define Q_RET_IGNORED       ((QState)1)
#define Q_RET_TRAN          ((QState)2)
#define Q_RET_SUPER         ((QState)3)
#define Q_RET_EXIT          ((QState)4)
#define Q_RET_ENTRY         ((QState)5)

// State Machine Macros
#define Q_HANDLED()         (Q_RET_HANDLED)
#define Q_IGNORED()         (Q_RET_IGNORED)
#define Q_TRAN(target_)     (((QHsm *)me)->temp = (QStateHandler)(target_), Q_RET_TRAN)
#define Q_SUPER(super_)     (((QHsm *)me)->temp = (QStateHandler)(super_), Q_RET_SUPER)

<strong>Complete State Machine Implementation Example</strong>
Below, we will demonstrate the use of the QP/C framework with a complete LED blinking controller.
#include "qpc.h"    // QP/C framework header file

// Event Signal Definition
enum BlinkySignals {
    TIMEOUT_SIG = Q_USER_SIG,   // Timeout signal
    BUTTON_PRESS_SIG,           // Button press signal
    MAX_SIGNALS                 // Maximum signal count
};

// Blinky Active Object Structure
typedef struct {
    QHsm super;         // Inherit QHsm
    uint32_t blink_count;   // Blink count
    uint32_t blink_interval;// Blink interval (ms)
    QTimeEvt timeEvt;   // Time event
} Blinky;

// State Handler Function Declarations
QState Blinky_initial(Blinky * const me, QEvt const * const e);
QState Blinky_off(Blinky * const me, QEvt const * const e);
QState Blinky_on(Blinky * const me, QEvt const * const e);
QState Blinky_fast(Blinky * const me, QEvt const * const e);
QState Blinky_error(Blinky * const me, QEvt const * const e);

// Initial State Handler
QState Blinky_initial(Blinky * const me, QEvt const * const e) {
    (void)e; // Unused parameter
    
    // Initialize object properties
    me->blink_count = 0;
    me->blink_interval = 1000; // Default 1 second interval
    
    // Subscribe to system events
    QActive_subscribe((QActive *)me, BUTTON_PRESS_SIG);
    
    // Transition to off state
    return Q_TRAN(&Blinky_off);
}

// Off State Handler
QState Blinky_off(Blinky * const me, QEvt const * const e) {
    switch (e->sig) {
        case Q_ENTRY_SIG: {
            // Enter state: Turn off LED
            BSP_ledOff();
            // Start timeout timer, trigger after 1 second
            QTimeEvt_armX(&me->timeEvt, me->blink_interval, 0U);
            return Q_HANDLED();
        }
        
        case Q_EXIT_SIG: {
            // Exit state: Stop timer
            QTimeEvt_disarm(&me->timeEvt);
            return Q_HANDLED();
        }
        
        case TIMEOUT_SIG: {
            // Timeout event: Transition to on state
            me->blink_count++;
            return Q_TRAN(&Blinky_on);
        }
        
        case BUTTON_PRESS_SIG: {
            // Button pressed: Switch to fast blink mode
            return Q_TRAN(&Blinky_fast);
        }
    }
    return Q_SUPER(&QHsm_top); // Pass to parent state
}

// On State Handler
QState Blinky_on(Blinky * const me, QEvt const * const e) {
    switch (e->sig) {
        case Q_ENTRY_SIG: {
            // Enter state: Turn on LED
            BSP_ledOn();
            // Start timeout timer
            QTimeEvt_armX(&me->timeEvt, me->blink_interval, 0U);
            
            // Check blink count to prevent overuse
            if (me->blink_count > 10000) {
                return Q_TRAN(&Blinky_error);
            }
            return Q_HANDLED();
        }
        
        case Q_EXIT_SIG: {
            QTimeEvt_disarm(&me->timeEvt);
            return Q_HANDLED();
        }
        
        case TIMEOUT_SIG: {
            // Timeout event: Transition back to off state
            return Q_TRAN(&Blinky_off);
        }
        
        case BUTTON_PRESS_SIG: {
            // Button pressed: Switch to fast blink mode
            return Q_TRAN(&Blinky_fast);
        }
    }
    return Q_SUPER(&QHsm_top);
}

// Fast Blink State
QState Blinky_fast(Blinky * const me, QEvt const * const e) {
    switch (e->sig) {
        case Q_ENTRY_SIG: {
            BSP_ledOn();
            // Fast blink: 100ms interval
            QTimeEvt_armX(&me->timeEvt, 100, 0U);
            return Q_HANDLED();
        }
        
        case TIMEOUT_SIG: {
            // Toggle LED state to implement blinking
            BSP_ledToggle();
            QTimeEvt_armX(&me->timeEvt, 100, 0U);
            return Q_HANDLED();
        }
        
        case BUTTON_PRESS_SIG: {
            // Press button again: Return to normal mode
            return Q_TRAN(&Blinky_off);
        }
    }
    return Q_SUPER(&QHsm_top);
}

// Error State
QState Blinky_error(Blinky * const me, QEvt const * const e) {
    switch (e->sig) {
        case Q_ENTRY_SIG: {
            // Error state: LED stays on
            BSP_ledOn();
            // Log error
            LOG_ERROR("Blink count exceeded limit: %lu", me->blink_count);
            return Q_HANDLED();
        }
        
        case BUTTON_PRESS_SIG: {
            // Long press for 3 seconds to reset
            // Long press detection can be implemented here
            me->blink_count = 0;
            return Q_TRAN(&Blinky_off);
        }
    }
    return Q_SUPER(&QHsm_top);
}

// State Machine Initialization
void Blinky_ctor(Blinky * const me) {
    QHsm_ctor(&me->super, Q_STATE_CAST(&Blinky_initial));
    QTimeEvt_ctorX(&me->timeEvt, &me->super, TIMEOUT_SIG, 0U);
}

<strong>Complex State Machine: Communication Protocol Handling</strong>
Below is a demonstration of a more complex communication protocol state machine implementation:
// Communication Protocol Active Object
typedef struct {
    QHsm super;
    uint8_t retry_count;
    uint32_t connection_timeout;
    QTimeEvt ack_timer;
    QTimeEvt connection_timer;
} ProtocolHandler;

// Protocol State Handler Functions
QState Protocol_initial(ProtocolHandler * const me, QEvt const * const e);
QState Protocol_disconnected(ProtocolHandler * const me, QEvt const * const e);
QState Protocol_connecting(ProtocolHandler * const me, QEvt const * const e);
QState Protocol_handshake(ProtocolHandler * const me, QEvt const * const e);
QState Protocol_connected(ProtocolHandler * const me, QEvt const * const e);

QState Protocol_disconnected(ProtocolHandler * const me, QEvt const * const e) {
    switch (e->sig) {
        case Q_ENTRY_SIG: {
            me->retry_count = 0;
            LOG_INFO("Protocol: Disconnected");
            return Q_HANDLED();
        }
        
        case CONNECT_REQUEST_SIG: {
            LOG_INFO("Protocol: Starting connection");
            return Q_TRAN(&Protocol_connecting);
        }
    }
    return Q_SUPER(&QHsm_top);
}

QState Protocol_connecting(ProtocolHandler * const me, QEvt const * const e) {
    switch (e->sig) {
        case Q_ENTRY_SIG: {
            // Start connection timeout timer
            QTimeEvt_armX(&me->connection_timer, 5000, 0U);
            
            // Send connection request
            if (send_connect_request() != SUCCESS) {
                return Q_TRAN(&Protocol_disconnected);
            }
            return Q_HANDLED();
        }
        
        case Q_EXIT_SIG: {
            QTimeEvt_disarm(&me->connection_timer);
            return Q_HANDLED();
        }
        
        case CONNECT_RESPONSE_SIG: {
            // Received connection response
            if (is_valid_connect_response(e)) {
                return Q_TRAN(&Protocol_handshake);
            } else {
                return Q_TRAN(&Protocol_disconnected);
            }
        }
        
        case CONNECTION_TIMEOUT_SIG: {
            // Connection timeout
            me->retry_count++;
            if (me->retry_count < MAX_RETRIES) {
                LOG_WARN("Protocol: Connection timeout, retrying...");
                return Q_TRAN(&Protocol_connecting);
            } else {
                LOG_ERROR("Protocol: Max retries exceeded");
                return Q_TRAN(&Protocol_disconnected);
            }
        }
    }
    return Q_SUPER(&QHsm_top);
}

<strong>Advantages of State Pattern in Embedded Systems</strong>
<strong>Improved Code Clarity</strong>
The traditional switch-case approach versus the state pattern approach
// State Pattern: Each state handled independently
QState StateA_handler(Device *me, QEvt const *e) {
    switch (e->sig) {
        case EVENT_X_SIG:
            return Q_TRAN(&StateB);
        case EVENT_Y_SIG:
            return Q_HANDLED();
    }
    return Q_SUPER(&ParentState);
}

<strong>Easy to Extend and Maintain</strong>
When adding a new state, simply add a new state handler function without affecting existing logic:
// New Power Save State
QState Device_powerSave(Device *me, QEvt const *e) {
    switch (e->sig) {
        case Q_ENTRY_SIG:
            enter_power_save_mode();
            return Q_HANDLED();
            
        case WAKEUP_SIG:
            return Q_TRAN(&Device_active);
            
        case LOW_BATTERY_SIG:
            return Q_TRAN(&Device_shutdown);
    }
    return Q_SUPER(&QHsm_top);
}

<strong>Better Testability</strong>
State machines can be tested independently:
// Unit Test Example
void test_protocol_handshake(void) {
    ProtocolHandler proto;
    Protocol_ctor(&proto);
    
    // Initialize state machine
    QHsm_init(&proto.super, 0);
    
    // Send connection request
    QEvt connect_evt = { CONNECT_REQUEST_SIG };
    QHsm_dispatch(&proto.super, &connect_evt);
    
    // Verify state transition
    TEST_ASSERT_EQUAL(STATE_CONNECTING, get_current_state(&proto));
    
    // Send connection response
    QEvt response_evt = { CONNECT_RESPONSE_SIG };
    QHsm_dispatch(&proto.super, &response_evt);
    
    TEST_ASSERT_EQUAL(STATE_HANDSHAKE, get_current_state(&proto));
}

<strong>Conclusion</strong>
The State Pattern, through professional frameworks like QP/C, demonstrates powerful capabilities in embedded systems. It not only addresses the challenges of complex state management but also brings a clear architecture where each state is independently encapsulated with defined responsibilities. The State Pattern can significantly enhance code quality and system reliability without sacrificing performance.

Leave a Comment