BÀI 11 – STATE MACHINE: TỪ C enum ĐẾN FIRMWARE STM32 THỰC SỰ
Ở Bài 10, bạn đã học enum. Bây giờ chúng ta ghép:
enumswitchif- biến trạng thái
- hàm
struct
để tạo ra State Machine (máy trạng thái).
Đây là một trong những kiến thức quan trọng nhất khi bạn lập trình STM32 cho máy giặt, điều hòa, tủ lạnh, robot, động cơ, hệ thống công nghiệp…
1. State Machine là gì?
Hãy tưởng tượng một máy giặt.
Nó không thể làm tất cả mọi việc cùng lúc.
Nó lần lượt:
IDLE
↓
FILL_WATER
↓
WASH
↓
DRAIN
↓
RINSE
↓
SPIN
↓
FINISH
Mỗi thời điểm máy đang ở một trạng thái.
Ví dụ:
state = WASH;
Có nghĩa:
Hiện tại máy đang ở trạng thái GIẶT.
Khi điều kiện thích hợp xuất hiện:
state = DRAIN;
Máy chuyển sang trạng thái XẢ NƯỚC.
Đó chính là State Machine.
2. Tư duy quan trọng nhất
Một State Machine luôn có 3 thứ:
① STATE – Trạng thái hiện tại
Ví dụ:
IDLE
WASH
RINSE
SPIN
② INPUT – Điều kiện đầu vào
Ví dụ:
nút START
cảm biến mức nước
timer
cảm biến cửa
cảm biến lỗi
③ TRANSITION – Chuyển trạng thái
Ví dụ:
IDLE + START → WASH
WASH + timer hết → RINSE
RINSE + timer hết → SPIN
SPIN + timer hết → FINISH
3. Dùng enum tạo State
Ví dụ máy giặt:
typedef enum
{
WASH_IDLE,
WASH_FILL,
WASHING,
WASH_DRAIN,
WASH_RINSE,
WASH_SPIN,
WASH_FINISH,
WASH_ERROR
} WashState;
Sau đó:
WashState state;
Khởi tạo:
state = WASH_IDLE;
4. switch xử lý từng trạng thái
switch(state)
{
case WASH_IDLE:
break;
case WASH_FILL:
break;
case WASHING:
break;
case WASH_DRAIN:
break;
case WASH_RINSE:
break;
case WASH_SPIN:
break;
case WASH_FINISH:
break;
case WASH_ERROR:
break;
}
Đây là bộ xương của State Machine.
5. Nhưng State Machine phải chuyển trạng thái
Ví dụ:
case WASH_IDLE:
if(start_button == 1)
{
state = WASH_FILL;
}
break;
Có nghĩa:
Đang IDLE
↓
nhấn START
↓
chuyển sang FILL
6. Trạng thái FILL
Giả sử có cảm biến mức nước:
case WASH_FILL:
pump_water = 1;
if(water_level_ok == 1)
{
pump_water = 0;
state = WASHING;
}
break;
Sơ đồ:
water_level_ok
│
▼
WASH_FILL ─────────> WASHING
Trong thời gian chưa đủ nước:
WASH_FILL
vẫn được giữ nguyên.
7. Trạng thái WASHING
case WASHING:
motor = 1;
if(wash_timer_done == 1)
{
motor = 0;
state = WASH_DRAIN;
}
break;
Tức là:
WASHING
│
│ timer hết
▼
DRAIN
8. Đây chính là firmware
Bạn có thể bắt đầu thấy sự khác biệt giữa:
“Học C”
và:
“Lập trình firmware.”
Trong C thông thường:
printf("Hello");
Trong firmware:
state = WASH_DRAIN;
và trạng thái đó quyết định phần cứng phải làm gì.
9. Một State Machine hoàn chỉnh
Ví dụ đơn giản:
typedef enum
{
IDLE,
RUN,
STOP,
ERROR
} MachineState;
MachineState state = IDLE;
Sau đó:
while(1)
{
switch(state)
{
case IDLE:
if(start == 1)
{
state = RUN;
}
break;
case RUN:
motor = 1;
if(stop == 1)
{
motor = 0;
state = STOP;
}
if(error == 1)
{
motor = 0;
state = ERROR;
}
break;
case STOP:
motor = 0;
if(start == 1)
{
state = RUN;
}
break;
case ERROR:
motor = 0;
if(reset == 1)
{
state = IDLE;
}
break;
}
}
Bạn hãy nhìn kỹ đoạn này.
Đây đã là một firmware architecture rất cơ bản.
10. State Machine giống một “bộ não”
Có thể hình dung:
INPUT
│
▼
┌──────────────┐
│ STATE MACHINE│
└──────┬───────┘
│
▼
OUTPUT
Ví dụ STM32:
Button ──────┐
│
Sensor ──────┼──→ STATE MACHINE ──→ Motor
│ ├──→ Pump
Timer ───────┘ ├──→ Valve
└──→ LED
Đây là kiến trúc xuất hiện rất nhiều trong hệ thống nhúng.
11. Một ví dụ cực kỳ gần với Blue Pill của bạn
Giả sử bạn muốn dùng STM32 Blue Pill điều khiển:
- PC13 LED
- nút nhấn
- biến trở
- step motor 28BYJ-48
Ta có thể thiết kế:
typedef enum
{
MOTOR_IDLE,
MOTOR_RUN,
MOTOR_STOP,
MOTOR_ERROR
} MotorState;
Biến:
MotorState motor_state;
Ban đầu:
motor_state = MOTOR_IDLE;
12. IDLE
case MOTOR_IDLE:
if(start == 1)
{
motor_state = MOTOR_RUN;
}
break;
13. RUN
case MOTOR_RUN:
step_motor_run();
if(stop == 1)
{
motor_state = MOTOR_STOP;
}
break;
14. STOP
case MOTOR_STOP:
step_motor_stop();
if(start == 1)
{
motor_state = MOTOR_RUN;
}
break;
15. ERROR
case MOTOR_ERROR:
step_motor_stop();
LED_ON();
if(reset == 1)
{
motor_state = MOTOR_IDLE;
}
break;
Bây giờ chương trình có cấu trúc rất rõ:
┌──────────┐
┌────►│ IDLE │
│ └────┬─────┘
│ │ START
│ ▼
│ ┌──────────┐
│ │ RUN │
│ └────┬─────┘
│ │ STOP
│ ▼
│ ┌──────────┐
└─────│ STOP │
└──────────┘
ERROR
▲
│
phát hiện lỗi
16. Một điều rất quan trọng: State khác Output
Đây là lỗi người mới rất hay mắc.
Ví dụ:
motor_state = MOTOR_RUN;
Không có nghĩa trực tiếp rằng motor đã quay.
Nó chỉ nói:
“Bộ điều khiển đang ở trạng thái RUN.”
Sau đó State Machine quyết định:
case MOTOR_RUN:
step_motor_run();
break;
Cho nên:
STATE
↓
LOGIC
↓
OUTPUT
Đây là tư duy rất quan trọng.
17. Không nên viết kiểu này
Người mới thường viết:
if(button)
{
motor = 1;
}
if(sensor)
{
motor = 0;
}
if(timer)
{
motor = 1;
}
if(error)
{
motor = 0;
}
Khi chương trình lớn lên, bạn sẽ không biết:
Tại sao motor đang chạy?
Hoặc:
Điều kiện nào vừa tắt motor?
Đây là kiểu logic rải rác.
18. State Machine giúp giải quyết vấn đề đó
Thay vì:
if(...)
{
...
}
if(...)
{
...
}
if(...)
{
...
}
ta tổ chức:
switch(state)
{
case IDLE:
...
break;
case RUN:
...
break;
case STOP:
...
break;
case ERROR:
...
break;
}
Mỗi trạng thái có nhiệm vụ riêng.
19. State Machine + Function
Khi chương trình lớn hơn, ta không nên nhét tất cả vào main().
Ví dụ:
void Motor_Task(void)
{
switch(motor_state)
{
case MOTOR_IDLE:
break;
case MOTOR_RUN:
step_motor_run();
break;
case MOTOR_STOP:
step_motor_stop();
break;
case MOTOR_ERROR:
step_motor_stop();
break;
}
}
Trong while(1):
while(1)
{
Motor_Task();
}
Đây là bước rất quan trọng.
20. Nhiều State Machine cùng chạy
Một firmware STM32 thực tế có thể có:
main()
│
├── Button_Task()
│
├── Motor_Task()
│
├── Sensor_Task()
│
├── Display_Task()
│
├── Communication_Task()
│
└── Alarm_Task()
Ví dụ:
while(1)
{
Button_Task();
Motor_Task();
Sensor_Task();
Display_Task();
Communication_Task();
}
Đây là tư duy cooperative multitasking rất phổ biến trong firmware nhỏ.
Chưa cần RTOS vẫn có thể làm được rất nhiều thứ.
21. Một ví dụ cực thực tế: máy giặt
Ta có:
typedef enum
{
IDLE,
FILL,
WASH,
DRAIN,
RINSE,
SPIN,
FINISH,
ERROR
} WashState;
Luồng hoạt động:
START
↓
IDLE
↓
FILL
↓
WASH
↓
DRAIN
↓
RINSE
↓
SPIN
↓
FINISH
│
└──────→ IDLE
Nếu bất cứ lúc nào phát hiện lỗi:
ANY STATE
│
│ ERROR
▼
ERROR
Đây chính là cách tư duy để xây dựng firmware máy giặt.
22. State Machine không chỉ dành cho máy móc
Bạn có thể sử dụng nó cho:
Giao tiếp UART
WAIT_START
↓
RECEIVE
↓
CHECK
↓
PROCESS
↓
SEND
Menu LCD
MAIN_MENU
↓
SETTING
↓
EDIT
↓
SAVE
Fingerprint
IDLE
↓
WAIT_FINGER
↓
CAPTURE
↓
SEARCH
↓
MATCH / FAIL
↓
UNLOCK
Robot
IDLE
↓
FORWARD
↓
OBSTACLE
↓
TURN
↓
FORWARD
23. Một khái niệm cực kỳ quan trọng: Event
State Machine thường phản ứng với event.
Ví dụ:
BUTTON_START
TIMER_DONE
SENSOR_HIGH
UART_RECEIVED
ERROR_DETECTED
Ví dụ:
if(button_start)
{
state = RUN;
}
Ở đây:
BUTTON_START = EVENT
RUN = STATE
Bạn cần bắt đầu phân biệt hai khái niệm này.
24. Công thức tư duy
Hãy nhớ:
STATE + EVENT
↓
TRANSITION
↓
NEW STATE
Ví dụ:
IDLE + START
↓
RUN
hoặc:
RUN + ERROR
↓
ERROR
25. Bài tập thực hành Bài 11
Hãy tự viết State Machine cho công tắc vân tay mà bạn đang muốn làm với Blue Pill + DY50.
Thiết kế:
IDLE
WAIT_FINGER
READ_FINGER
CHECK_FINGER
ACCESS_GRANTED
ACCESS_DENIED
Luồng:
IDLE
↓
WAIT_FINGER
↓
Có ngón tay
↓
READ_FINGER
↓
CHECK_FINGER
├──────────────┐
│ │
▼ ▼
MATCH FAIL
│ │
▼ ▼
GRANTED DENIED
Bạn hãy thử tự viết:
typedef enum
{
IDLE,
WAIT_FINGER,
READ_FINGER,
CHECK_FINGER,
ACCESS_GRANTED,
ACCESS_DENIED
} FingerState;
Sau đó:
FingerState state;
và:
switch(state)
{
...
}
Đừng cố viết toàn bộ chương trình DY50 ngay. Mục tiêu của bài 11 là bạn phải hiểu được State Machine hoạt động như thế nào.
26. Bạn cần thuộc 5 dòng này
Nếu sau bài này bạn nhớ được 5 dòng sau thì đã đạt mục tiêu:
typedef enum
{
IDLE,
RUN,
ERROR
} State;
State state = IDLE;
switch(state)
{
case RUN:
state = ERROR;
Từ đây, chúng ta đã đi từ:
biến → con trỏ → mảng → struct → hàm → enum → State Machine.
Bài 12
Bài tiếp theo chúng ta sẽ học static, biến cục bộ, biến toàn cục và vùng nhớ của chúng trong STM32. Đây là phần rất quan trọng để bạn hiểu tại sao một biến trong firmware có thể nhớ giá trị sau khi hàm kết thúc, và tại sao cách khai báo biến lại ảnh hưởng đến RAM của Blue Pill.