🌱 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 |
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);
}
}
- Đơ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)
- 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);
}
}
- 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;
}
- 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;
}
}
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
// 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
// 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.
>>>>>> 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 😊
