Implementation of a Table-Driven Key State Machine in C

1. Description and Code Implementation

Implement a table-driven key state machine that can be coded according to the steps of state enumeration definition, event enumeration definition, callback function types, state table structure, state table definition, and state machine processing function.Table-driven design: centralizes the mapping of <span>"state - event - next state - callback"</span> in a <span>structure</span>, avoiding a large number of <span>if-else</span> or <span>switch-case</span> hardcoding, making it easier to maintain and extend. Below is the complete simulation in C:

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
// ====================== Simulation Configuration (Replace Hardware Dependency) ======================#define KEY_PRESS_VALUE   1
#define KEY_IDLE_VALUE    !KEY_PRESS_VALUE
#define KEY_FILTER_MS     20
#define KEY_SHORT_PRESS_MS  300
#define KEY_LONG_PRESS_MS   1000
//1. Define enumeration types (states, events)
// State and event enumeration (consistent with bare metal)
typedef enum {
    KEY_IDLE = 0,
    KEY_DOWN} key_state_t;
typedef enum {
    EVT_NONE = 0,
    EVT_PRESS,
    EVT_SHORT_PRESS,
    EVT_LONG_PRESS,
    EVT_RELEASE} key_event_t;
// Key FSM structure (consistent with bare metal)
typedef struct {
    key_state_t state;
    uint32_t last_time;
    uint32_t press_start_time;
    uint32_t press_end_time;
    void (*key_idle_func)(void);
    void (*key_down_func)(void);
    void (*key_short_press_func)(void);
    void (*key_long_press_func)(void);
    void (*key_click_func)(void);
    uint8_t (*get_func)(void);
    uint32_t (*get_time_func)(void);
} key_fsm_t;
// State transition table item structure (consistent with bare metal)
typedef struct {
    key_state_t cur_state;
    key_event_t event;
    key_state_t next_state;
    void (*action)(key_fsm_t *fsm);
} key_state_item_t;
// ====================== Simulation Tools (Core) ======================// Simulate GPIO state of KEY0/KEY1 (0=released, 1=pressed)
static uint8_t sim_key0_state = KEY_IDLE_VALUE;
static uint8_t sim_key1_state = KEY_IDLE_VALUE;
// Simulate system time (milliseconds)
static uint32_t sim_sys_time_ms = 0;
// Simulate KEY0 read function
static uint8_t sim_key0_get_func(void) {
    return sim_key0_state;
}
// Simulate KEY1 read function
static uint8_t sim_key1_get_func(void) {
    return sim_key1_state;
}
// Simulate system time retrieval function
static uint32_t sim_sys_get_time_ms(void) {
    return sim_sys_time_ms;
}
// Simulation tool: set key state (0=released, 1=pressed)
void sim_set_key(uint8_t key_idx, uint8_t press) {
    if (key_idx == 0) {
        sim_key0_state = press ? KEY_PRESS_VALUE : KEY_IDLE_VALUE;
    } else if (key_idx == 1) {
        sim_key1_state = press ? KEY_PRESS_VALUE : KEY_IDLE_VALUE;
    }}
// Simulation tool: advance system time (increase ms)
void sim_advance_time(uint32_t ms) {
    sim_sys_time_ms += ms;
}
// Simulation tool: reset simulation environment
void sim_reset(void) {
    sim_key0_state = KEY_IDLE_VALUE;
    sim_key1_state = KEY_IDLE_VALUE;
    sim_sys_time_ms = 0;
}
// ====================== Simulation Callback Functions (Log Printing) ======================static void sim_key0_idle(void) { printf("[SIM] KEY0 idle\r\n"); }
static void sim_key0_down(void) { printf("[SIM] KEY0 down\r\n"); }
static void sim_key0_short(void) { printf("[SIM] KEY0 short press\r\n"); }
static void sim_key0_long(void) { printf("[SIM] KEY0 long press\r\n"); }
static void sim_key0_click(void) { printf("[SIM] KEY0 click\r\n"); }
static void sim_key1_idle(void) { printf("[SIM] KEY1 idle\r\n"); }
static void sim_key1_down(void) { printf("[SIM] KEY1 down\r\n"); }
static void sim_key1_short(void) { printf("[SIM] KEY1 short press\r\n"); }
static void sim_key1_long(void) { printf("[SIM] KEY1 long press\r\n"); }
static void sim_key1_click(void) { printf("[SIM] KEY1 click\r\n"); }
// ====================== State Action Functions (Consistent with Bare Metal) ======================static void key_idle(key_fsm_t *fsm) {
    if (fsm->key_idle_func) fsm->key_idle_func();}
static void key_down(key_fsm_t *fsm) {
    if (fsm->key_down_func) fsm->key_down_func();}
static void key_short_press(key_fsm_t *fsm) {
    if (fsm->key_short_press_func) fsm->key_short_press_func();}
static void key_long_press(key_fsm_t *fsm) {
    if (fsm->key_long_press_func) fsm->key_long_press_func();}
static void key_click(key_fsm_t *fsm) {
    if (fsm->key_click_func) fsm->key_click_func();}
// ====================== State Transition Table (Consistent with Bare Metal) ======================static const key_state_item_t key_table[] = {
    {KEY_IDLE,  EVT_PRESS,       KEY_DOWN,  key_down},
    {KEY_DOWN,  EVT_RELEASE,     KEY_IDLE,  key_click},
    {KEY_IDLE,  EVT_SHORT_PRESS, KEY_IDLE,  key_short_press},
    {KEY_IDLE,  EVT_LONG_PRESS,  KEY_IDLE,  key_long_press},
};
#define KEY_TABLE_SIZE (sizeof(key_table) / sizeof(key_table[0]))
// ====================== State Machine Core Function (Identical to Bare Metal) ======================void key_fsm_proc(key_fsm_t *fsm) {
    if (fsm == NULL || fsm->get_func == NULL || fsm->get_time_func == NULL)
        return;
    uint8_t key_input = fsm->get_func();
    uint32_t now = fsm->get_time_func();
    key_event_t event = EVT_NONE;
    switch (fsm->state) {
        case KEY_IDLE:
            if (fsm->press_end_time > fsm->press_start_time) {
                if (fsm->press_end_time - fsm->press_start_time >= KEY_LONG_PRESS_MS) {
                    event = EVT_LONG_PRESS;
                    fsm->press_start_time = now;
                    fsm->press_end_time = now;
                } else if (fsm->press_end_time - fsm->press_start_time >= KEY_FILTER_MS) {
                    event = EVT_SHORT_PRESS;
                    fsm->press_start_time = now;
                    fsm->press_end_time = now;
                }
            } else if (key_input == KEY_PRESS_VALUE) {
                if (now - fsm->last_time > KEY_FILTER_MS) {
                    event = EVT_PRESS;
                    fsm->press_start_time = now;
                }
            } else {
                fsm->last_time = now;
            }
            break;
        case KEY_DOWN:
            if (key_input == KEY_PRESS_VALUE) {
                fsm->last_time = now;
                fsm->press_end_time = now;
            } else if (now - fsm->last_time > KEY_FILTER_MS) {
                event = EVT_RELEASE;
            }
            break;
        default:
            break;
    }
    for (size_t i = 0; i < KEY_TABLE_SIZE; ++i) {
        if (key_table[i].cur_state == fsm->state && key_table[i].event == event) {
            if (key_table[i].action)
                key_table[i].action(fsm);
            fsm->state = key_table[i].next_state;
            break;
        }
    }}
// ====================== Test Cases ======================// Helper function: print test title
void print_test_title(const char *title) {
    printf("\n==================================================\n");
    printf("Simulation Test: %s\n", title);
    printf("==================================================\n");}
// Helper function: initialize FSM instance
void fsm_init(key_fsm_t *fsm, uint8_t key_idx) {
    fsm->state = KEY_IDLE;
    fsm->last_time = 0;
    fsm->press_start_time = 0;
    fsm->press_end_time = 0;
    if (key_idx == 0) {
        fsm->key_idle_func = sim_key0_idle;
        fsm->key_down_func = sim_key0_down;
        fsm->key_short_press_func = sim_key0_short;
        fsm->key_long_press_func = sim_key0_long;
        fsm->key_click_func = sim_key0_click;
        fsm->get_func = sim_key0_get_func;
    } 
    else if (key_idx == 1) {
        fsm->key_idle_func = sim_key1_idle;
        fsm->key_down_func = sim_key1_down;
        fsm->key_short_press_func = sim_key1_short;
        fsm->key_long_press_func = sim_key1_long;
        fsm->key_click_func = sim_key1_click;
        fsm->get_func = sim_key1_get_func;
    }
    fsm->get_time_func = sim_sys_get_time_ms;
}
// Test Case 1: KEY0 Short Press
void test_key0_short_press(void) {
    print_test_title("KEY0 Short Press Test");
    key_fsm_t key0_fsm;
    fsm_init(&amp;key0_fsm, 0);
    sim_reset();
    // 1. Initial state: KEY_IDLE
    printf("Initial state: %s\n", key0_fsm.state == KEY_IDLE ? "KEY_IDLE (Correct)" : "Error");
    // 2. Simulate pressing KEY0 (no debounce)
    sim_set_key(0, 1);
    sim_advance_time(10);
    key_fsm_proc(&amp;key0_fsm);
    printf("Pressed for 10ms (no debounce): state=%s (Expected KEY_IDLE)\n",
           key0_fsm.state == KEY_IDLE ? "KEY_IDLE (Correct)" : "Error");
    // 3. Debounce complete (total 25ms)
    sim_advance_time(15);
    key_fsm_proc(&amp;key0_fsm);
    printf("Pressed for 25ms (debounce complete): state=%s (Expected KEY_DOWN)\n",
           key0_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 4. Hold pressed for 200ms (not reaching long press threshold)
    sim_advance_time(200);
    key_fsm_proc(&amp;key0_fsm);
    printf("Holding pressed for 200ms: state=%s (Expected KEY_DOWN)\n",
           key0_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 5. Release KEY0 (no debounce)
    sim_set_key(0, 0);
    sim_advance_time(10);
    key_fsm_proc(&amp;key0_fsm);
    printf("Released for 10ms (no debounce): state=%s (Expected KEY_DOWN)\n",
           key0_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 6. Release debounce complete (total 25ms)
    sim_advance_time(15);
    key_fsm_proc(&amp;key0_fsm);
    printf("Released for 25ms (debounce complete): state=%s (Expected KEY_IDLE)\n",
           key0_fsm.state == KEY_IDLE ? "KEY_IDLE (Correct)" : "Error");
    // 7. Trigger short press event
    key_fsm_proc(&amp;key0_fsm);
}
// Test Case 2: KEY0 Long Press
void test_key0_long_press(void) {
    print_test_title("KEY0 Long Press Test");
    key_fsm_t key0_fsm;
    fsm_init(&amp;key0_fsm, 0);
    sim_reset();
    // 1. Press and debounce
    sim_set_key(0, 1);
    sim_advance_time(25);
    key_fsm_proc(&amp;key0_fsm);
    printf("Pressed for 25ms (debounce complete): state=%s (Expected KEY_DOWN)\n",
           key0_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 2. Hold pressed for 1200ms (exceeds long press threshold)
    sim_advance_time(1200);
    key_fsm_proc(&amp;key0_fsm);
    printf("Holding pressed for 1200ms: state=%s (Expected KEY_DOWN)\n",
           key0_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 3. Release and debounce
    sim_set_key(0, 0);
    sim_advance_time(25);
    key_fsm_proc(&amp;key0_fsm);
    printf("Released for 25ms (debounce complete): state=%s (Expected KEY_IDLE)\n",
           key0_fsm.state == KEY_IDLE ? "KEY_IDLE (Correct)" : "Error");
}
// Test Case 3: KEY1 Bounce Test
void test_key1_bounce(void) {
    print_test_title("KEY1 Bounce Test");
    key_fsm_t key1_fsm;
    fsm_init(&amp;key1_fsm, 1);
    sim_reset();
    // Simulate bounce: press for 5ms → release for 5ms, repeat 5 times
    for (int i = 0; i < 5; i++) {
        sim_set_key(1, 1);
        sim_advance_time(5);
        key_fsm_proc(&amp;key1_fsm);
        sim_set_key(1, 0);
        sim_advance_time(5);
        key_fsm_proc(&amp;key1_fsm);
    }
    printf("After bounce: state=%s (Expected KEY_IDLE)\n",
           key1_fsm.state == KEY_IDLE ? "KEY_IDLE (Correct)" : "Error");
    // Stable press and debounce
    sim_set_key(1, 1);
    sim_advance_time(25);
    key_fsm_proc(&amp;key1_fsm);
    printf("Stable press for 25ms: state=%s (Expected KEY_DOWN)\n",
           key1_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
}
// Test Case 4: Concurrent Key Press (KEY0 Short Press + KEY1 Long Press)
void test_double_key_concurrent(void) {
    print_test_title("Double Key Concurrent Test");
    key_fsm_t key0_fsm, key1_fsm;
    fsm_init(&amp;key0_fsm, 0);
    fsm_init(&amp;key1_fsm, 1);
    sim_reset();
    // 1. Press KEY0 (debounce complete)
    sim_set_key(0, 1);
    sim_advance_time(25);
    key_fsm_proc(&amp;key0_fsm);
    key_fsm_proc(&amp;key1_fsm);
    printf("KEY0 pressed for 25ms: state=%s (Expected KEY_DOWN)\n",
           key0_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 2. After 300ms, press KEY1 (debounce complete)
    sim_advance_time(300);
    sim_set_key(1, 1);
    sim_advance_time(25);
    key_fsm_proc(&amp;key0_fsm);
    key_fsm_proc(&amp;key1_fsm);
    printf("KEY1 pressed for 25ms: state=%s (Expected KEY_DOWN)\n",
           key1_fsm.state == KEY_DOWN ? "KEY_DOWN (Correct)" : "Error");
    // 3. KEY1 held down for 800ms (total 1100ms, triggers long press)
    sim_advance_time(800);
    key_fsm_proc(&amp;key0_fsm);
    key_fsm_proc(&amp;key1_fsm);
    // 4. Release KEY0 (debounce complete)
    sim_set_key(0, 0);
    sim_advance_time(25);
    key_fsm_proc(&amp;key0_fsm);
    key_fsm_proc(&amp;key1_fsm);
    // 5. Release KEY1 (debounce complete)
    sim_set_key(1, 0);
    sim_advance_time(25);
    key_fsm_proc(&amp;key0_fsm);
    key_fsm_proc(&amp;key1_fsm);
}
// ====================== Test Main Function ======================int main(void) {
    //test part
    printf("Table-driven Key State Machine Simulation Test Suite\n");
    printf("========================\n");
    test_key0_short_press();      // KEY0 Short Press
    test_key0_long_press();       // KEY0 Long Press
    test_key1_bounce();           // KEY1 Bounce
    test_double_key_concurrent(); // Double Key Concurrent
    printf("\n========================\n");
    printf("All simulation tests completed!\n");
    //    /*
    key_fsm_t key0_fsm, key1_fsm;
    fsm_init(&amp;key0_fsm, 0);
    fsm_init(&amp;key1_fsm, 1);
    while(1)
    {
        key_fsm_proc(&amp;key0_fsm);
        key_fsm_proc(&amp;key1_fsm);
        delay(1000);
    }
    */
    return 0;

2. Code Explanation

  • Table-Driven Design: The mapping of state – event – next state – callback is centralized in <span>key_table</span>, avoiding a large number of <span>if-else</span> or <span>switch-case</span> hardcoding, making it easier to maintain and extend.
  • Callback Mechanism: Triggers corresponding business logic (e.g., <span>on_key_down</span> handles the press action), achieving decoupling of logic and state machine.
  • Extensibility: If new states or events need to be added, simply add entries in the enumeration and state table without modifying the core logic of the state machine.

3. Testing

Implementation of a Table-Driven Key State Machine in CImplementation of a Table-Driven Key State Machine in C

Leave a Comment