蓝桥杯嵌入式备赛:如何用状态机思想重构第八届省赛电梯调度程序?
蓝桥杯嵌入式备赛:状态机重构电梯调度程序的工程实践
在嵌入式系统开发中,状态机(Finite State Machine,FSM)是一种强大的设计模式,特别适合处理具有明确状态转换逻辑的系统。蓝桥杯嵌入式竞赛中的电梯调度问题,正是状态机应用的典型场景。本文将从一个资深嵌入式工程师的角度,分享如何用状态机思想重构第八届省赛的电梯调度程序,提升代码的可维护性和可扩展性。
1. 状态机设计基础
状态机由三个核心要素构成:状态集合、事件集合和转移规则。在电梯控制系统中,这些要素可以直观地映射到实际业务逻辑:
- 状态集合 :包括空闲(IDLE)、上行(UP)、下行(DOWN)、开门(OPENING)、关门(CLOSING)、等待(WAITING)等
- 事件集合 :包含楼层按键事件(FLOOR_BUTTON)、到达目标楼层事件(ARRIVAL)、定时器超时事件(TIMEOUT)等
- 转移规则 :定义在特定状态下,当某个事件发生时,系统如何转换到新状态
typedef enum {
STATE_IDLE,
STATE_UP,
STATE_DOWN,
STATE_OPENING,
STATE_CLOSING,
STATE_WAITING
} ElevatorState;
typedef enum {
EVT_FLOOR_BUTTON,
EVT_ARRIVAL,
EVT_TIMEOUT
} ElevatorEvent;
与传统标志位控制相比,状态机具有明显优势:
| 对比维度 | 标志位控制 | 状态机控制 |
|---|---|---|
| 代码可读性 | 逻辑分散,难以追踪 | 状态集中管理,流程清晰 |
| 可维护性 | 修改一处可能影响全局 | 状态独立,修改影响局部 |
| 扩展性 | 新增功能需重构逻辑 | 只需添加新状态和转移 |
| 调试难度 | 标志位组合爆炸,难排查 | 当前状态明确,易追踪 |
2. 电梯状态机建模
2.1 状态定义与转换
基于题目要求,我们首先建立电梯的核心状态模型:
- IDLE :电梯静止在某一楼层,等待用户输入
- WAITING :已接收用户指令,等待1秒后开始运行
- UP/DOWN :电梯正在上行或下行
- OPENING :到达目标楼层,正在开门
- CLOSING :完成开门,正在关门
状态转换图如下:
[IDLE] -- FLOOR_BUTTON --> [WAITING] -- TIMEOUT(1s) --> [UP/DOWN]
[UP/DOWN] -- ARRIVAL --> [OPENING] -- TIMEOUT(2s) --> [CLOSING]
[CLOSING] -- TIMEOUT(4s) --> [IDLE]
2.2 事件处理机制
在状态机实现中,事件处理是核心逻辑。我们采用事件队列机制:
#define MAX_EVENTS 10
typedef struct {
ElevatorEvent events[MAX_EVENTS];
uint8_t head;
uint8_t tail;
} EventQueue;
void enqueueEvent(EventQueue *q, ElevatorEvent evt) {
if ((q->tail + 1) % MAX_EVENTS != q->head) {
q->events[q->tail] = evt;
q->tail = (q->tail + 1) % MAX_EVENTS;
}
}
ElevatorEvent dequeueEvent(EventQueue *q) {
ElevatorEvent evt = EVT_NONE;
if (q->head != q->tail) {
evt = q->events[q->head];
q->head = (q->head + 1) % MAX_EVENTS;
}
return evt;
}
3. 状态机实现细节
3.1 状态转移表实现
使用状态转移表可以清晰地表达状态转换逻辑:
typedef struct {
ElevatorState currentState;
ElevatorEvent event;
ElevatorState nextState;
void (*action)(void);
} StateTransition;
const StateTransition transitionTable[] = {
{STATE_IDLE, EVT_FLOOR_BUTTON, STATE_WAITING, startWaitingTimer},
{STATE_WAITING, EVT_TIMEOUT, STATE_UP, startMovingUp},
{STATE_WAITING, EVT_TIMEOUT, STATE_DOWN, startMovingDown},
{STATE_UP, EVT_ARRIVAL, STATE_OPENING, stopElevator},
{STATE_DOWN, EVT_ARRIVAL, STATE_OPENING, stopElevator},
{STATE_OPENING, EVT_TIMEOUT, STATE_CLOSING, startClosingDoor},
{STATE_CLOSING, EVT_TIMEOUT, STATE_IDLE, completeCycle}
};
3.2 主循环与状态处理
主循环负责处理事件和状态转移:
void elevatorMainLoop(void) {
static ElevatorState currentState = STATE_IDLE;
EventQueue eventQueue = {0};
while(1) {
ElevatorEvent evt = dequeueEvent(&eventQueue);
if (evt != EVT_NONE) {
for (int i = 0; i < sizeof(transitionTable)/sizeof(transitionTable[0]); i++) {
if (transitionTable[i].currentState == currentState &&
transitionTable[i].event == evt) {
if (transitionTable[i].action) {
transitionTable[i].action();
}
currentState = transitionTable[i].nextState;
break;
}
}
}
// 其他系统任务
HAL_Delay(10);
}
}
4. 关键功能实现
4.1 楼层调度算法
在状态机框架下实现SCAN电梯调度算法:
- 收集所有上行和下行请求
- 按照当前方向处理同方向请求
- 到达端点后反向处理剩余请求
void updateTargetFloors(void) {
if (currentDirection == DIR_UP) {
// 找出所有大于当前楼层的目标
for (int i = currentFloor + 1; i <= MAX_FLOOR; i++) {
if (floorRequests[i]) {
addTargetFloor(i);
}
}
} else {
// 找出所有小于当前楼层的目标
for (int i = currentFloor - 1; i >= MIN_FLOOR; i--) {
if (floorRequests[i]) {
addTargetFloor(i);
}
}
}
}
4.2 定时器管理
使用硬件定时器处理各种超时事件:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim == &htim3) {
static uint32_t counter = 0;
counter++;
// 1秒等待超时
if (currentState == STATE_WAITING && counter >= 1000/10) {
enqueueEvent(&eventQueue, EVT_TIMEOUT);
counter = 0;
}
// 2秒开门超时
if (currentState == STATE_OPENING && counter >= 2000/10) {
enqueueEvent(&eventQueue, EVT_TIMEOUT);
counter = 0;
}
}
}
4.3 外设控制集成
将LED、按键等外设控制集成到状态机中:
void updateFloorIndicator(uint8_t floor) {
// 关闭所有楼层LED
HAL_GPIO_WritePin(F1_LED_GPIO_Port, F1_LED_Pin, GPIO_PIN_RESET);
// ...其他楼层LED
// 点亮当前楼层LED
switch(floor) {
case 1: HAL_GPIO_WritePin(F1_LED_GPIO_Port, F1_LED_Pin, GPIO_PIN_SET); break;
// ...其他楼层
}
// 更新LCD显示
char str[20];
sprintf(str, " %d", floor);
LCD_DisplayStringLine(Line4, str);
}
5. 调试与优化技巧
5.1 状态追踪调试
添加状态日志输出帮助调试:
const char* stateToString(ElevatorState state) {
switch(state) {
case STATE_IDLE: return "IDLE";
case STATE_WAITING: return "WAITING";
// ...其他状态
default: return "UNKNOWN";
}
}
void logStateTransition(ElevatorState oldState, ElevatorState newState) {
char logMsg[50];
sprintf(logMsg, "State change: %s -> %s",
stateToString(oldState), stateToString(newState));
debugUartSend(logMsg);
}
5.2 内存优化
对于资源受限的嵌入式系统,优化状态机内存使用:
- 使用位域压缩状态标志
- 合理设计事件队列大小
- 使用const修饰状态转移表节省RAM
typedef struct {
uint8_t currentFloor : 3; // 使用3位表示1-4层
uint8_t direction : 1; // 0=down, 1=up
uint8_t state : 3; // 主要状态编码
} CompactElevatorState;
5.3 实时性保障
确保状态机响应实时性:
- 关键事件使用中断触发
- 长耗时操作分步执行
- 状态处理函数保持简短
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == F1_Pin) {
enqueueEvent(&eventQueue, EVT_FLOOR_BUTTON);
floorRequests[1] = 1;
}
// ...处理其他按键
}
6. 工程实践建议
在实际项目中应用状态机时,有几个经验值得分享:
-
状态粒度把控 :状态不是越多越好,也不是越少越好。我通常建议每个状态对应一个明确的"行为模式",比如"正在上行"是一个状态,而不需要把"上行中经过每层"都作为独立状态。
-
事件设计原则 :事件应该代表"已经发生的事情",而不是"应该发生的事情"。例如"按键按下"是事件,而"需要去3楼"是状态机内部的判断逻辑。
-
调试技巧 :在LCD上实时显示当前状态和最近事件,可以大幅提高调试效率。我在实际项目中会保留一个调试界面,即使产品发布后也很有用。
-
性能考量 :在STM32F103这类资源有限的MCU上,状态机的实现要尽量轻量。避免在状态处理函数中进行复杂计算,必要时可以使用查表法优化性能。
更多推荐

所有评论(0)