🌱 Embedded C: Lập trình Máy trạng thái - State Machine

🌱 Embedded C: Lập trình Máy trạng thái - State Machine

    Khi thiết kế chương trình nhúng, đặc biệt là trong phát triển sản phẩm, việc quản lý các trạng thái là rất quan trọng. Khi mà khối lượng code rất lớn, Dev không thể ném hết vào một vòng while(1), hoặc một vài hàm interrupt. Một ví dụ sản phẩm nhúng đó là chiếc điện thoại, khi bật nguồn sẽ cần chạy một đoạn code khác, khi khoá màn hình, hay chạy app sẽ chạy những đoạn code khác nhau. Nếu không quản lý hiệu quả, khi code / maintain / debug, Dev rất dễ lạc vào mê cũng code.

    Vì vậy, mô hình State Machine (Máy trạng thái) sinh ra để giúp quản lý các trạng thái và chuyển đổi giữa chúng một cách có tổ chức, dễ bảo trì và tránh lỗi logic. Bài viết này sẽ hướng dẫn chi tiết cách thiết kế và triển khai State Machine trong lập trình nhúng với C.



2. Ví dụ thực tế: ATM System

2.1. Phân tích yêu cầu

    Một hệ thống ATM đơn giản sẽ có các trạng thái sau:

  • IDLE: Chờ khách hàng
  • CARD_INSERTED: Đã nhận thẻ, đang xử lý
  • PIN_ENTERED: Đã nhập PIN đúng
  • OPTION_SELECTED: Đã chọn giao dịch
  • AMOUNT_ENTERED: Đã nhập số tiền

    Nếu phát triển phần mềm mà không có design, thì bất kỳ thời điểm nào cũng phải check xem sản phẩm đang ở trạng thái nào! Hơn nữa, đối với mỗi trạng thái, thì sản phẩm chỉ có một số behaviour nhất định.

    Vì vậy, cách tốt hơn hết là quản lý chương trình phần mềm cho từng trạng thái, và tại từng thời điểm, product sẽ chỉ ở duy nhất một trạng thái, và sẽ có phương thức để chuyển trạng thái khi cần! Đó cũng chính là ý tưởng của Máy trạng thái (State Machine), một phương án triển khai phần mềm phổ biến nhất trong Embedded System.

2.2. State Transition Diagram

    Đối với ví dụ trên, chúng ta có thể vẽ ra bảng chuyển trạng thái của cây ATM như sau:

State Transition Diagram
State Transition Diagram

1. Finite State Machine (FSM) là gì?

Finite State Machine (FSM) - Máy trạng thái hữu hạn là một mô hình toán học được sử dụng để mô tả hành vi của một hệ thống có thể tồn tại ở một trong số hữu hạn các trạng thái tại một thời điểm.

Các thành phần chính của FSM:

  • States (Trạng thái): Tập hợp hữu hạn các trạng thái mà hệ thống có thể ở
  • Events/Inputs (Sự kiện): Các tín hiệu đầu vào kích hoạt chuyển đổi trạng thái
  • Transitions (Chuyển đổi): Quy tắc chuyển từ trạng thái này sang trạng thái khác
  • Actions (Hành động): Các thao tác thực hiện khi chuyển đổi trạng thái
  • Initial State (Trạng thái ban đầu): Trạng thái khởi tạo của hệ thống

    State Machine mang lại rất nhiều lợi ích khi phát triển phần mềm nhúng:

  • Code được tổ chức theo từng trạng thái rõ ràng, dễ đọc và debug. Cũng như việc thực hiện testing cũng đơn giản hơn.
  • Tránh các trạng thái không hợp lệ, giảm bug do điều kiện phức tạp.
  • Thêm trạng thái mới không ảnh hưởng đến code cũ, giúp dev có thể mở rộng phần mềm dễ dàng hơn.
  • State Machine sử dụng ít tài nguyên, không ảnh hưởng đến tốc độ project.

3. Các phương pháp implement FSM trong C

3.1. Method 1: Switch-Case (Đơn giản nhất)

// Định nghĩa các trạng thái
typedef enum {
    STATE_IDLE,
    STATE_CARD_INSERTED,
    STATE_PIN_ENTERED,
    STATE_OPTION_SELECTED,
    STATE_AMOUNT_ENTERED
} ATM_State_t;

// Định nghĩa các sự kiện
typedef enum {
    EVENT_CARD_INSERT,
    EVENT_PIN_ENTER,
    EVENT_OPTION_SELECT,
    EVENT_AMOUNT_ENTER,
    EVENT_DISPENSE_CASH,
    EVENT_TIMEOUT,
    EVENT_CANCEL
} ATM_Event_t;

// Biến lưu trạng thái hiện tại
static ATM_State_t currentState = STATE_IDLE;

// Hàm xử lý state machine
void ATM_HandleEvent(ATM_Event_t event) {
    switch(currentState) {
        case STATE_IDLE:
            if(event == EVENT_CARD_INSERT) {
                // Đọc thẻ
                if(ReadCard() == CARD_VALID) {
                    currentState = STATE_CARD_INSERTED;
                    DisplayMessage("Enter PIN");
                }
            }
            break;
            
        case STATE_CARD_INSERTED:
            if(event == EVENT_PIN_ENTER) {
                if(ValidatePin()) {
                    currentState = STATE_PIN_ENTERED;
                    DisplayOptions();
                } else {
                    currentState = STATE_IDLE;
                    EjectCard();
                }
            } else if(event == EVENT_TIMEOUT || event == EVENT_CANCEL) {
                currentState = STATE_IDLE;
                EjectCard();
            }
            break;
            
        case STATE_PIN_ENTERED:
            if(event == EVENT_OPTION_SELECT) {
                currentState = STATE_OPTION_SELECTED;
                DisplayMessage("Enter Amount");
            } else if(event == EVENT_CANCEL) {
                currentState = STATE_IDLE;
                EjectCard();
            }
            break;
            
        case STATE_OPTION_SELECTED:
            if(event == EVENT_AMOUNT_ENTER) {
                if(CheckBalance()) {
                    currentState = STATE_AMOUNT_ENTERED;
                    DispenseCash();
                } else {
                    DisplayMessage("Insufficient funds");
                }
            } else if(event == EVENT_CANCEL) {
                currentState = STATE_IDLE;
                EjectCard();
            }
            break;
            
        case STATE_AMOUNT_ENTERED:
            if(event == EVENT_DISPENSE_CASH) {
                currentState = STATE_IDLE;
                EjectCard();
                DisplayMessage("Thank you!");
            }
            break;
            
        default:
            currentState = STATE_IDLE;
            break;
    }
}

// Hàm main loop
int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    while(1) {
        ATM_Event_t event = GetNextEvent(); // Đọc event từ queue
        ATM_HandleEvent(event);
        HAL_Delay(10);
    }
}
Ưu điểm:
  • Đơn giản, dễ hiểu cho người mới
  • Không cần cấp phát bộ nhớ động
  • Phù hợp với FSM nhỏ (< 10 states)
Nhược điểm:
  • Code dài khi có nhiều trạng thái
  • Khó mở rộng và bảo trì
  • Không reusable

3.2. Method 2: State Table (Lookup Table)

// Định nghĩa function pointer cho state handler
typedef ATM_State_t (*StateHandler_t)(ATM_Event_t event);

// Khai báo các state handler functions
ATM_State_t IdleStateHandler(ATM_Event_t event);
ATM_State_t CardInsertedHandler(ATM_Event_t event);
ATM_State_t PinEnteredHandler(ATM_Event_t event);
ATM_State_t OptionSelectedHandler(ATM_Event_t event);
ATM_State_t AmountEnteredHandler(ATM_Event_t event);

// State table - ánh xạ state -> handler
static const StateHandler_t stateTable[] = {
    [STATE_IDLE] = IdleStateHandler,
    [STATE_CARD_INSERTED] = CardInsertedHandler,
    [STATE_PIN_ENTERED] = PinEnteredHandler,
    [STATE_OPTION_SELECTED] = OptionSelectedHandler,
    [STATE_AMOUNT_ENTERED] = AmountEnteredHandler
};

// Implement các handler
ATM_State_t IdleStateHandler(ATM_Event_t event) {
    if(event == EVENT_CARD_INSERT) {
        if(ReadCard() == CARD_VALID) {
            DisplayMessage("Enter PIN");
            return STATE_CARD_INSERTED;
        }
    }
    return STATE_IDLE; // Giữ nguyên trạng thái
}

ATM_State_t CardInsertedHandler(ATM_Event_t event) {
    if(event == EVENT_PIN_ENTER) {
        if(ValidatePin()) {
            DisplayOptions();
            return STATE_PIN_ENTERED;
        } else {
            EjectCard();
            return STATE_IDLE;
        }
    } else if(event == EVENT_TIMEOUT || event == EVENT_CANCEL) {
        EjectCard();
        return STATE_IDLE;
    }
    return STATE_CARD_INSERTED;
}

// State machine engine
void ATM_StateMachine(ATM_Event_t event) {
    static ATM_State_t currentState = STATE_IDLE;
    
    if(currentState < (sizeof(stateTable) / sizeof(stateTable[0]))) {
        currentState = stateTable[currentState](event);
    }
}
Ưu điểm:
  • Code gọn gàng, dễ mở rộng
  • Tách biệt logic từng state
  • Dễ test từng state handler riêng lẻ
  • Performance tốt (lookup O(1))

3.3. Method 3: 2D State-Event Table

// Function pointer type cho transition
typedef void (*TransitionFunc_t)(void);

// Entry cho state table
typedef struct {
    ATM_State_t nextState;
    TransitionFunc_t action;
} StateTableEntry_t;

// 2D State-Event Table
static const StateTableEntry_t stateEventTable[5][7] = {
    // STATE_IDLE
    {
        [EVENT_CARD_INSERT] = {STATE_CARD_INSERTED, OnCardInserted},
        [EVENT_CANCEL] = {STATE_IDLE, NULL}
    },
    // STATE_CARD_INSERTED
    {
        [EVENT_PIN_ENTER] = {STATE_PIN_ENTERED, OnPinValidated},
        [EVENT_TIMEOUT] = {STATE_IDLE, OnCancel},
        [EVENT_CANCEL] = {STATE_IDLE, OnCancel}
    },
    // STATE_PIN_ENTERED
    {
        [EVENT_OPTION_SELECT] = {STATE_OPTION_SELECTED, OnOptionSelected},
        [EVENT_CANCEL] = {STATE_IDLE, OnCancel}
    },
    // STATE_OPTION_SELECTED
    {
        [EVENT_AMOUNT_ENTER] = {STATE_AMOUNT_ENTERED, OnAmountEntered},
        [EVENT_CANCEL] = {STATE_IDLE, OnCancel}
    },
    // STATE_AMOUNT_ENTERED
    {
        [EVENT_DISPENSE_CASH] = {STATE_IDLE, OnCashDispensed}
    }
};

// State machine với 2D table
void ATM_ProcessEvent(ATM_Event_t event) {
    static ATM_State_t currentState = STATE_IDLE;
    
    const StateTableEntry_t *entry = &stateEventTable[currentState][event];
    
    // Thực hiện action nếu có
    if(entry->action != NULL) {
        entry->action();
    }
    
    // Chuyển sang state mới
    currentState = entry->nextState;
}
Ưu điểm:
  • Rất gọn gàng và dễ đọc
  • Dễ visualize state transitions
  • Performance tốt nhất (direct lookup)
  • Dễ generate từ tool hoặc diagram
Phương pháp Code Size RAM Usage Maintainability Best For
Switch-Case Lớn Thấp Thấp FSM đơn giản < 5 states
State Table Vừa Vừa Cao FSM trung bình 5-15 states
2D Table Nhỏ Cao (nếu sparse) Rất cao FSM phức tạp > 15 states

4. Hierarchical State Machine (HSM)

4.1. Khái niệm

Hierarchical State Machine là FSM mở rộng cho phép các state lồng nhau (nested states), giúp tái sử dụng code và giảm duplicate logic. Đặc biệt hữu ích khi nhiều states có chung behavior.

4.2. Ví dụ: Motor Control System

// Định nghĩa state hierarchy
typedef enum {
    // Super states
    STATE_OFF,
    STATE_ON,
    
    // Sub-states của ON
    STATE_ON_IDLE,
    STATE_ON_RUNNING,
    STATE_ON_BRAKING
} MotorState_t;

// Handler cho super state ON - xử lý common behavior
bool Motor_OnStateHandler(MotorEvent_t event) {
    // Common handling cho tất cả sub-states của ON
    if(event == EVENT_POWER_OFF || event == EVENT_EMERGENCY_STOP) {
        motorCtx.speed = 0;
        MotorHardwareOff();
        motorCtx.currentState = STATE_OFF;
        return true; // Event đã được xử lý
    }
    return false; // Delegate cho sub-state
}

// Sub-state handlers
void Motor_IdleHandler(MotorEvent_t event) {
    // Kiểm tra super state trước
    if(Motor_OnStateHandler(event)) {
        return;
    }
    
    // Xử lý riêng cho IDLE
    if(event == EVENT_START) {
        motorCtx.speed = 100;
        MotorHardwareStart();
        motorCtx.currentState = STATE_ON_RUNNING;
    }
}
Lưu ý: HSM giúp tránh duplicate code khi nhiều states cần xử lý chung một event (như EMERGENCY_STOP). Thay vì check ở mỗi sub-state, chỉ cần check một lần ở super state.

5. Best Practices & Common Pitfalls

5.1. Best Practices

Practice Mô tả
Entry/Exit Actions Luôn có hàm entry/exit cho mỗi state để init và cleanup resources
Guard Conditions Kiểm tra điều kiện trước khi transition để tránh invalid states
Default Handling Luôn có default case xử lý unexpected events
State Logging Log state transitions để debug dễ dàng
Timeout Handling Mỗi state nên có timeout để tránh bị stuck

5.2. Common Pitfalls

Pitfall 1: State Explosion
// Tạo quá nhiều state cho mọi combination
enum States {
    IDLE, RUNNING, RUNNING_WITH_LED_ON,
    RUNNING_WITH_LED_OFF, RUNNING_FAST,
    // ... 50 states ...
};
// => Nên tách riêng concerns, dùng concurrent FSM
enum MotorStates { IDLE, RUNNING };
enum LedStates { LED_OFF, LED_ON };
// 3 FSM độc lập thay vì 1 FSM với 50 states
Pitfall 2: Blocking Operations
// Blocking delay
void RunningStateHandler(void) {
    MotorOn();
    HAL_Delay(5000); // BLOCKING!
    MotorOff();
}
// Nên sử dụng Timer để tránh Block các State khác
void RunningStateHandler(void) {
    static uint32_t startTime = 0;
    if(startTime == 0) {
        MotorOn();
        startTime = HAL_GetTick();
    }
    if((HAL_GetTick() - startTime) >= 5000) {
        MotorOff();
        startTime = 0;
        currentState = STATE_IDLE;
    }
}

6. Ví dụ hoàn chỉnh: Traffic Light System

// traffic_light.h
typedef enum {
    STATE_RED,
    STATE_YELLOW,
    STATE_GREEN
} TrafficState_t;

typedef struct {
    TrafficState_t currentState;
    uint32_t stateStartTime;
    uint32_t stateDuration;
} TrafficLight_t;

// State durations in milliseconds
#define RED_DURATION     5000
#define YELLOW_DURATION  2000
#define GREEN_DURATION   5000

void TrafficLight_Init(TrafficLight_t *light);
void TrafficLight_Update(TrafficLight_t *light);

// traffic_light.c
static void EnterRedState(void) {
    GPIO_WritePin(RED_LED_PORT, RED_LED_PIN, GPIO_PIN_SET);
    GPIO_WritePin(YELLOW_LED_PORT, YELLOW_LED_PIN, GPIO_PIN_RESET);
    GPIO_WritePin(GREEN_LED_PORT, GREEN_LED_PIN, GPIO_PIN_RESET);
}

static void EnterYellowState(void) {
    GPIO_WritePin(RED_LED_PORT, RED_LED_PIN, GPIO_PIN_RESET);
    GPIO_WritePin(YELLOW_LED_PORT, YELLOW_LED_PIN, GPIO_PIN_SET);
    GPIO_WritePin(GREEN_LED_PORT, GREEN_LED_PIN, GPIO_PIN_RESET);
}

static void EnterGreenState(void) {
    GPIO_WritePin(RED_LED_PORT, RED_LED_PIN, GPIO_PIN_RESET);
    GPIO_WritePin(YELLOW_LED_PORT, YELLOW_LED_PIN, GPIO_PIN_RESET);
    GPIO_WritePin(GREEN_LED_PORT, GREEN_LED_PIN, GPIO_PIN_SET);
}

void TrafficLight_Init(TrafficLight_t *light) {
    light->currentState = STATE_RED;
    light->stateStartTime = HAL_GetTick();
    light->stateDuration = RED_DURATION;
    EnterRedState();
}

void TrafficLight_Update(TrafficLight_t *light) {
    uint32_t currentTime = HAL_GetTick();
    
    // Check if current state duration expired
    if((currentTime - light->stateStartTime) >= light->stateDuration) {
        // Transition to next state
        switch(light->currentState) {
            case STATE_RED:
                light->currentState = STATE_GREEN;
                light->stateDuration = GREEN_DURATION;
                EnterGreenState();
                break;
                
            case STATE_GREEN:
                light->currentState = STATE_YELLOW;
                light->stateDuration = YELLOW_DURATION;
                EnterYellowState();
                break;
                
            case STATE_YELLOW:
                light->currentState = STATE_RED;
                light->stateDuration = RED_DURATION;
                EnterRedState();
                break;
        }
        
        light->stateStartTime = currentTime;
    }
}

// main.c
int main(void) {
    HAL_Init();
    SystemClock_Config();
    GPIO_Init();
    
    TrafficLight_t trafficLight;
    TrafficLight_Init(&trafficLight);
    
    while(1) {
        TrafficLight_Update(&trafficLight);
        HAL_Delay(10);
    }
}

    ➤ Một ví dụ khác về State Machine - Bài toán điều khiển động cơ!


7. Tools và Resources

7.1. State Machine Design Tools

Tool Mô tả Link
draw.io Vẽ state diagram miễn phí app.diagrams.net
PlantUML Generate diagram từ text plantuml.com
QM Tool Generate C code từ diagram (Quantum Platform) state-machine.com
Mermaid Markdown-based diagram cho docs mermaid.js.org

    ➤ Bạn cũng có thể sử dụng các công cụ Generate Code C từ State Machine giống như StateSmith!

7.2. Frameworks

  • QP/C Framework: Professional FSM framework từ Quantum Leaps
  • SMC (State Machine Compiler): Generate code từ .sm files
  • TinyFSM: Lightweight C++ template-based FSM

Kết luận

    State Machine là một kỹ thuật lập trình quan trọng và hữu ích trong embedded systems. Bằng cách tổ chức code theo các trạng thái và chuyển đổi rõ ràng, bạn có thể:

  • Giảm thiểu lỗi logic và bug
  • Tăng khả năng đọc hiểu và bảo trì code
  • Dễ dàng mở rộng và thêm features mới
  • Tạo ra hệ thống deterministic và reliable

Lựa chọn phương pháp implementation phù hợp với độ phức tạp của project: Switch-Case cho FSM đơn giản, State Table cho FSM trung bình, và 2D Table hoặc HSM cho hệ thống phức tạp. Đừng quên áp dụng các best practices và tránh các pitfalls phổ biến để tạo ra code chất lượng cao.

Note: Hãy luôn bắt đầu với state diagram trước khi code. Điều này giúp bạn visualize toàn bộ system và phát hiện các edge cases sớm.

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

Đăng nhận xét

Mới hơn Cũ hơn
//
Avatar

Đăng nhập

Người dùng mới? Đăng ký ngay

hoặc

Bằng việc tạo tài khoản, bạn đồng ý với Chính sách bảo mật & Chính sách Cookie.