Raspberry Pi Pico 2 状态机设计
在嵌入式编程中,状态机(State Machine) 是一种强大的设计模式,用于管理复杂的行为和逻辑。状态机通过将程序分解为多个状态(State) 和状态之间的转换(Transition),使得代码更易于理解、维护和扩展。状态机特别适合处理需要根据输入或事件改变行为的任务,例如控制机器人、自动化系统或交互式设备。
本文将分别使用 Arduino 风格(基于 Arduino-Pico 核心)和 C/C++ 风格(基于官方 Pico SDK)来详细介绍状态机的设计与实现,并提供多个实际案例。
1. 什么是状态机?
有限状态机(Finite State Machine, FSM) 是一种数学模型,用于描述系统在不同状态之间的行为。它由以下几个核心部分组成:
| 组成部分 |
描述 |
| 状态(State) |
系统在某一时刻的特定条件或模式,例如“LED 亮”、“LED 灭” |
| 事件(Event) |
触发状态转换的外部输入或内部条件,例如“按钮按下”、“计时结束” |
| 转换(Transition) |
从一个状态到另一个状态的变化规则 |
| 动作(Action) |
在进入状态、离开状态或状态转换时执行的操作 |
状态机可以帮助你将复杂的逻辑分解为简单的状态和转换,从而:
- 简化代码结构,提高可读性
- 避免使用大量嵌套的
if-else 或标志变量
- 更容易调试和扩展功能
- 更符合硬件控制的思维模式

2. 状态机的基本结构
在 Pico 2 编程中,状态机通常通过 枚举类型 定义状态,并在主循环中使用 switch 语句 实现状态处理。
2.1 通用模板(Arduino 风格)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| enum State { STATE_A, STATE_B, STATE_C };
State currentState = STATE_A;
void setup() { }
void loop() { switch (currentState) { case STATE_A: if () { currentState = STATE_B; } break;
case STATE_B: if () { currentState = STATE_C; } break;
case STATE_C: if () { currentState = STATE_A; } break; } }
|
2.2 通用模板(C/C++ 风格)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include "pico/stdlib.h"
typedef enum { STATE_A, STATE_B, STATE_C } State;
State currentState = STATE_A;
int main() { stdio_init_all();
while (true) { switch (currentState) { case STATE_A: if () { currentState = STATE_B; } break;
case STATE_B: if () { currentState = STATE_C; } break;
case STATE_C: if () { currentState = STATE_A; } break; } sleep_ms(10); } return 0; }
|
核心思想:将系统的行为拆分为若干个离散的“状态”,每个状态下执行特定的动作,并在满足条件时切换到下一个状态。主循环只需不断查询当前状态并执行相应代码。
3. 实际应用案例
3.1 案例1:交通灯控制系统
设计一个简单的交通灯控制系统,包含红灯、黄灯和绿灯。状态转换规则:
- 红灯:持续 5 秒 → 切换到绿灯
- 绿灯:持续 5 秒 → 切换到黄灯
- 黄灯:持续 2 秒 → 切换回红灯
硬件连接
Arduino 风格实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| enum TrafficLightState { RED, YELLOW, GREEN };
TrafficLightState currentState = RED; unsigned long lastStateChangeTime = 0;
const int RED_PIN = 16; const int YELLOW_PIN = 17; const int GREEN_PIN = 18;
void setup() { pinMode(RED_PIN, OUTPUT); pinMode(YELLOW_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); digitalWrite(RED_PIN, HIGH); digitalWrite(YELLOW_PIN, LOW); digitalWrite(GREEN_PIN, LOW); lastStateChangeTime = millis(); }
void loop() { unsigned long currentTime = millis();
switch (currentState) { case RED: if (currentTime - lastStateChangeTime >= 5000) { currentState = GREEN; lastStateChangeTime = currentTime; digitalWrite(RED_PIN, LOW); digitalWrite(GREEN_PIN, HIGH); } break;
case GREEN: if (currentTime - lastStateChangeTime >= 5000) { currentState = YELLOW; lastStateChangeTime = currentTime; digitalWrite(GREEN_PIN, LOW); digitalWrite(YELLOW_PIN, HIGH); } break;
case YELLOW: if (currentTime - lastStateChangeTime >= 2000) { currentState = RED; lastStateChangeTime = currentTime; digitalWrite(YELLOW_PIN, LOW); digitalWrite(RED_PIN, HIGH); } break; } }
|
C/C++ 风格实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include "pico/stdlib.h" #include "hardware/timer.h"
typedef enum { RED, YELLOW, GREEN } TrafficLightState;
#define RED_PIN 16 #define YELLOW_PIN 17 #define GREEN_PIN 18
int main() { stdio_init_all(); gpio_init(RED_PIN); gpio_init(YELLOW_PIN); gpio_init(GREEN_PIN); gpio_set_dir(RED_PIN, GPIO_OUT); gpio_set_dir(YELLOW_PIN, GPIO_OUT); gpio_set_dir(GREEN_PIN, GPIO_OUT); TrafficLightState currentState = RED; absolute_time_t lastStateChangeTime = get_absolute_time(); gpio_put(RED_PIN, 1); gpio_put(YELLOW_PIN, 0); gpio_put(GREEN_PIN, 0); while (true) { absolute_time_t now = get_absolute_time(); uint64_t elapsed_us = absolute_time_diff_us(lastStateChangeTime, now); switch (currentState) { case RED: if (elapsed_us >= 5000000) { currentState = GREEN; lastStateChangeTime = now; gpio_put(RED_PIN, 0); gpio_put(GREEN_PIN, 1); } break; case GREEN: if (elapsed_us >= 5000000) { currentState = YELLOW; lastStateChangeTime = now; gpio_put(GREEN_PIN, 0); gpio_put(YELLOW_PIN, 1); } break; case YELLOW: if (elapsed_us >= 2000000) { currentState = RED; lastStateChangeTime = now; gpio_put(YELLOW_PIN, 0); gpio_put(RED_PIN, 1); } break; } sleep_ms(10); } return 0; }
|
关键点:使用 millis()(Arduino)或 get_absolute_time()(SDK)实现非阻塞延时,避免使用 delay() 阻塞整个程序。这是状态机设计中非常重要的技巧。
3.2 案例2:按钮控制的自动门系统
设计一个自动门控制系统,包含以下状态:
- 关闭(CLOSED):门关闭,等待检测到人
- 打开(OPEN):门完全打开,保持 5 秒后准备关闭
- 关闭中(CLOSING):门正在关闭,若检测到人则重新打开,否则完全关闭后回到 CLOSED
硬件连接
- 人体传感器(模拟 PIR 或按键模拟):GP0(输入,高电平表示检测到人)
- 门位置传感器(模拟限位开关):GP1(输入,高电平表示门完全关闭)
- 电机控制(模拟):GP2(输出,高电平表示电机正转开门,低电平表示电机反转关门)
Arduino 风格实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| enum DoorState { CLOSED, OPEN, CLOSING };
DoorState currentState = CLOSED; unsigned long openStartTime = 0; unsigned long closingStartTime = 0;
const int SENSOR_PIN = 0; const int LIMIT_SWITCH_PIN = 1; const int MOTOR_PIN = 2;
void setup() { pinMode(SENSOR_PIN, INPUT); pinMode(LIMIT_SWITCH_PIN, INPUT_PULLUP); pinMode(MOTOR_PIN, OUTPUT); digitalWrite(MOTOR_PIN, LOW); Serial.begin(115200); }
void loop() { bool personDetected = digitalRead(SENSOR_PIN) == HIGH; bool doorFullyClosed = digitalRead(LIMIT_SWITCH_PIN) == HIGH; unsigned long now = millis();
switch (currentState) { case CLOSED: if (personDetected) { currentState = OPEN; openStartTime = now; digitalWrite(MOTOR_PIN, HIGH); Serial.println("Opening door..."); } break;
case OPEN: if (now - openStartTime >= 5000) { currentState = CLOSING; closingStartTime = now; digitalWrite(MOTOR_PIN, LOW); Serial.println("Closing door..."); } break;
case CLOSING: if (personDetected) { currentState = OPEN; openStartTime = now; digitalWrite(MOTOR_PIN, HIGH); Serial.println("Person detected, reopening..."); } else if (doorFullyClosed) { currentState = CLOSED; digitalWrite(MOTOR_PIN, LOW); Serial.println("Door fully closed."); } break; } }
|
C/C++ 风格实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include "pico/stdlib.h"
typedef enum { CLOSED, OPEN, CLOSING } DoorState;
#define SENSOR_PIN 0 #define LIMIT_SWITCH_PIN 1 #define MOTOR_PIN 2
int main() { stdio_init_all(); gpio_init(SENSOR_PIN); gpio_init(LIMIT_SWITCH_PIN); gpio_init(MOTOR_PIN); gpio_set_dir(SENSOR_PIN, GPIO_IN); gpio_set_dir(LIMIT_SWITCH_PIN, GPIO_IN); gpio_set_dir(MOTOR_PIN, GPIO_OUT); gpio_pull_up(LIMIT_SWITCH_PIN); DoorState currentState = CLOSED; absolute_time_t openStartTime; absolute_time_t closingStartTime; gpio_put(MOTOR_PIN, 0); while (true) { bool personDetected = gpio_get(SENSOR_PIN); bool doorFullyClosed = gpio_get(LIMIT_SWITCH_PIN); absolute_time_t now = get_absolute_time(); switch (currentState) { case CLOSED: if (personDetected) { currentState = OPEN; openStartTime = now; gpio_put(MOTOR_PIN, 1); printf("Opening door...\n"); } break; case OPEN: { uint64_t elapsed = absolute_time_diff_us(openStartTime, now); if (elapsed >= 5000000) { currentState = CLOSING; closingStartTime = now; gpio_put(MOTOR_PIN, 0); printf("Closing door...\n"); } break; } case CLOSING: if (personDetected) { currentState = OPEN; openStartTime = now; gpio_put(MOTOR_PIN, 1); printf("Person detected, reopening...\n"); } else if (doorFullyClosed) { currentState = CLOSED; gpio_put(MOTOR_PIN, 0); printf("Door fully closed.\n"); } break; } sleep_ms(10); } return 0; }
|
设计要点:
- 使用
millis() / get_absolute_time() 实现非阻塞定时,避免程序在等待时无法响应事件。
- 在
CLOSING 状态中,同时检查“人是否还在”和“门是否已关到位”,体现了状态机处理并发条件的能力。
3.3 案例3:长按与短按识别(状态机进阶)
使用状态机识别按钮的短按和长按,执行不同动作。这是一个比较经典的状态机应用。
状态定义
- IDLE:等待按钮按下
- PRESS_DETECTED:检测到按钮按下,启动计时
- SHORT_PRESS:按钮松开且时间短于阈值,执行短按动作后回到 IDLE
- LONG_PRESS:按钮按下时间超过长按阈值,执行长按动作后等待松开
Arduino 风格实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| enum ButtonState { IDLE, PRESS_DETECTED, SHORT_PRESS, LONG_PRESS };
ButtonState state = IDLE; unsigned long pressStartTime = 0;
const int BUTTON_PIN = 0; const int LED_PIN = 25;
const unsigned long LONG_PRESS_TIME = 1000; const unsigned long DEBOUNCE_TIME = 50;
void setup() { pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); Serial.begin(115200); }
void loop() { bool buttonPressed = (digitalRead(BUTTON_PIN) == LOW); unsigned long now = millis();
switch (state) { case IDLE: if (buttonPressed) { state = PRESS_DETECTED; pressStartTime = now; } break;
case PRESS_DETECTED: if (!buttonPressed) { if (now - pressStartTime < LONG_PRESS_TIME) { state = SHORT_PRESS; } else { state = IDLE; } } else if (now - pressStartTime >= LONG_PRESS_TIME) { state = LONG_PRESS; Serial.println("Long press detected!"); digitalWrite(LED_PIN, HIGH); } break;
case SHORT_PRESS: digitalWrite(LED_PIN, HIGH); delay(100); digitalWrite(LED_PIN, LOW); Serial.println("Short press detected!"); state = IDLE; break;
case LONG_PRESS: if (!buttonPressed) { state = IDLE; } break; } }
|
C/C++ 风格实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include "pico/stdlib.h"
typedef enum { IDLE, PRESS_DETECTED, SHORT_PRESS, LONG_PRESS } ButtonState;
#define BUTTON_PIN 0 #define LED_PIN 25 #define LONG_PRESS_TIME_MS 1000 #define DEBOUNCE_TIME_MS 50
int main() { stdio_init_all(); gpio_init(BUTTON_PIN); gpio_init(LED_PIN); gpio_set_dir(BUTTON_PIN, GPIO_IN); gpio_set_dir(LED_PIN, GPIO_OUT); gpio_pull_up(BUTTON_PIN); ButtonState state = IDLE; absolute_time_t pressStartTime; while (true) { bool buttonPressed = (gpio_get(BUTTON_PIN) == 0); absolute_time_t now = get_absolute_time(); switch (state) { case IDLE: if (buttonPressed) { state = PRESS_DETECTED; pressStartTime = now; } break; case PRESS_DETECTED: { uint64_t elapsed_ms = absolute_time_diff_us(pressStartTime, now) / 1000; if (!buttonPressed) { if (elapsed_ms < LONG_PRESS_TIME_MS) { state = SHORT_PRESS; } else { state = IDLE; } } else if (elapsed_ms >= LONG_PRESS_TIME_MS) { state = LONG_PRESS; printf("Long press detected!\n"); gpio_put(LED_PIN, 1); } break; } case SHORT_PRESS: gpio_put(LED_PIN, 1); sleep_ms(100); gpio_put(LED_PIN, 0); printf("Short press detected!\n"); state = IDLE; break; case LONG_PRESS: if (!buttonPressed) { state = IDLE; gpio_put(LED_PIN, 0); } break; } sleep_ms(10); } return 0; }
|
进阶技巧:本例展示了状态机处理“时间相关”事件的强大能力。通过状态 PRESS_DETECTED 作为中间态,可以精确区分短按和长按,且代码结构清晰,易于扩展(如添加双击识别)。
4. 状态机设计要点与常见模式
4.1 非阻塞设计
状态机的核心优势之一就是避免使用 delay()。所有定时都应通过记录起始时间并检查时间差来实现,这样主循环可以持续运行,及时响应事件。
4.2 状态转换图
在编写代码前,建议先画出状态转换图,明确:
- 有哪些状态
- 每个状态下执行什么动作
- 什么事件会导致状态转换
例如交通灯的状态转换图:
1
| 红灯 ──(5秒)──> 绿灯 ──(5秒)──> 黄灯 ──(2秒)──> 红灯
|
4.3 进入/退出动作
有时需要在进入某个状态时执行初始化操作,或在离开时执行清理。可以在状态切换的代码位置直接添加这些动作,例如:
1 2 3 4 5 6 7 8 9 10 11
| case STATE_A: if (condition) { doExitActionA(); currentState = STATE_B; doEntryActionB(); } break;
|
4.4 层次化状态机
当系统非常复杂时,可以将状态机嵌套(一个状态内部又是一个子状态机)。例如自动门系统中,“OPEN”状态内部可能包含“正在开门”和“门已开”两个子状态。但通常建议先保持扁平化,直到复杂度确实需要时才引入层次化。
4.5 状态机的优缺点
| 优点 |
缺点 |
| 逻辑清晰,易于理解和调试 |
状态较多时 switch 代码会变长 |
避免大量标志变量和嵌套 if |
需要一定的设计规划 |
| 易于扩展新的状态 |
对初学者有理解门槛 |
| 非常适合事件驱动系统 |
处理并发事件需要仔细设计 |
5. 总结
状态机是一种强大的设计模式,特别适合处理复杂的逻辑和行为。通过将程序分解为多个状态和转换,你可以编写出更高效、可维护的 Pico 2 代码。本文介绍了:
- 状态机的基本概念(状态、事件、转换、动作)
- 通用代码模板(Arduino 风格和 C/C++ 风格)
- 三个实际案例:交通灯控制系统、自动门系统、长按短按识别
- 设计要点:非阻塞设计、状态转换图、进入/退出动作等
| 案例 |
核心知识点 |
| 交通灯 |
时间触发的状态转换 |
| 自动门 |
事件触发与状态中的条件监测 |
| 长按/短按 |
时间测量与中间状态设计 |
6. 练习与拓展
- 练习 1:为交通灯系统增加一个“夜间模式”状态(例如黄灯闪烁),通过外部开关触发状态切换。
- 练习 2:设计一个简单的自动贩卖机状态机(等待投币 → 选择商品 → 出货 → 找零 → 回到等待),并用串口模拟输入。
- 练习 3:将自动门案例中的电机控制替换为真实的舵机或步进电机,并增加障碍物检测传感器(如超声波)实现防夹功能。
- 练习 4:结合之前的条件语句知识,将状态机与
switch-case 结合,实现一个基于菜单的交互系统。
通过学习和实践状态机设计,你将能够更好地管理复杂的 Pico 2 项目,并提升代码质量与可维护性。