用状态机搞定蓝桥杯嵌入式电梯题:STM32G431实战避坑指南
状态机在蓝桥杯嵌入式电梯控制中的实战应用
第一次接触蓝桥杯嵌入式比赛的电梯控制题时,我被那些复杂的时序逻辑搞得晕头转向。按键响应、楼层切换、开关门动画、LED指示...这些看似简单的功能组合在一起,却形成了一个令人头疼的状态迷宫。直到我掌握了状态机编程思想,才发现原来这类问题可以如此优雅地解决。
1. 状态机:嵌入式开发的思维利器
状态机(State Machine)是嵌入式系统开发中最强大的思维工具之一。它特别适合处理那些具有明确状态划分和状态转移逻辑的问题,比如电梯控制、交通信号灯、自动售货机等场景。
1.1 状态机的基本概念
状态机由三个核心要素组成:
- 状态(State):系统在某一时刻所处的状况。比如电梯的"等待按键"、"关门中"、"上行中"、"开门中"等。
- 事件(Event):触发状态转移的条件。比如"按键按下"、"定时器超时"等。
- 动作(Action):状态转移时执行的操作。比如"启动电机"、"点亮LED"等。
在STM32G431上实现状态机时,我们通常使用枚举类型定义状态,用switch-case结构处理状态转移:
typedef enum { STATE_IDLE, STATE_WAIT_KEY, STATE_CLOSING_DOOR, STATE_MOVING, STATE_OPENING_DOOR, STATE_WAITING } ElevatorState; ElevatorState currentState = STATE_IDLE;1.2 为什么电梯控制适合用状态机
蓝桥杯第八届的电梯题目具有以下特点,使其成为状态机的理想应用场景:
- 明确的阶段性:电梯运行可以清晰地划分为多个阶段(等待、关门、移动、开门等)
- 时序严格:每个阶段都有明确的时间要求(如关门4秒、移动6秒等)
- 事件驱动:状态转移由特定事件触发(按键、定时器等)
- 状态互斥:同一时间只能处于一个主要状态
通过状态机,我们可以将复杂的控制逻辑分解为离散的状态和清晰的转移条件,大大降低代码的复杂度。
2. 电梯控制状态机设计与实现
2.1 状态定义与转移图
根据题目要求,我们将电梯控制划分为6个主要状态:
- 空闲状态(IDLE):等待按键输入
- 按键等待状态(WAIT_KEY):1秒内可继续接收其他楼层按键
- 关门状态(CLOSING_DOOR):执行关门动作,持续4秒
- 移动状态(MOVING):上下行移动,持续6秒
- 开门状态(OPENING_DOOR):执行开门动作,持续4秒
- 停留等待状态(WAITING):在目标楼层停留2秒
状态转移图如下(用文字描述):
[IDLE] --按键按下--> [WAIT_KEY] --1秒超时--> [CLOSING_DOOR] --4秒超时--> (判断方向) --> [MOVING] --6秒超时--> [OPENING_DOOR] --4秒超时--> [WAITING] --2秒超时--> [CLOSING_DOOR] (循环直到无目标楼层) --> [IDLE]2.2 状态机实现的关键代码
在STM32G431上,我们使用一个全局变量记录当前状态,在主循环中处理状态转移:
void Elevator_Process(void) { static uint32_t stateEnterTime = 0; switch(currentState) { case STATE_IDLE: // 检测按键并设置目标楼层 if(AnyKeyPressed()) { SetTargetFloor(GetPressedKey()); currentState = STATE_WAIT_KEY; stateEnterTime = HAL_GetTick(); } break; case STATE_WAIT_KEY: // 1秒内可继续接收其他按键 if(KeyPressedDuringWait()) { SetTargetFloor(GetPressedKey()); stateEnterTime = HAL_GetTick(); // 重置等待时间 } // 1秒超时转移到关门状态 if(HAL_GetTick() - stateEnterTime >= 1000) { currentState = STATE_CLOSING_DOOR; StartClosingDoor(); stateEnterTime = HAL_GetTick(); } break; // 其他状态处理类似... } }2.3 状态处理中的硬件操作
每个状态通常需要操作特定的硬件外设:
| 状态 | GPIO操作 | PWM/TIMER使用 | LED显示 |
|---|---|---|---|
| 关门 | PA5置低 | TIM17 PWM 50% | 停止闪烁 |
| 上行 | PA4置高 | TIM16 PWM 80% | 向上流水灯 |
| 下行 | PA4置低 | TIM16 PWM 60% | 向下流水灯 |
| 到达 | - | - | 楼层LED闪烁2次 |
硬件初始化代码示例:
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(htim_pwm->Instance == TIM16) { __HAL_RCC_TIM16_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF1_TIM16; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } // 其他TIM初始化... }3. 状态机实现的常见问题与优化
3.1 定时器管理的技巧
在状态机实现中,时间管理至关重要。我们需要注意:
- 使用
HAL_GetTick()获取系统时间,避免阻塞式延时 - 关键时间参数定义为常量,便于调整:
#define DOOR_OPERATION_TIME 4000 // 门操作4秒 #define FLOOR_MOVE_TIME 6000 // 层间移动6秒 #define FLOOR_WAIT_TIME 2000 // 楼层等待2秒- 对于需要精确计时的情况,可以使用硬件定时器中断
3.2 方向判断逻辑优化
原题要求当同时有上行和下行请求时,先处理上行。我们可以优化方向判断逻辑:
uint8_t DetermineDirection(uint8_t currentFloor, uint8_t* targetFloors) { uint8_t hasUp = 0, hasDown = 0; // 检查上方楼层 for(uint8_t i = currentFloor + 1; i <= MAX_FLOOR; i++) { if(targetFloors[i]) { hasUp = 1; break; } } // 检查下方楼层 for(uint8_t i = currentFloor - 1; i >= MIN_FLOOR; i--) { if(targetFloors[i]) { hasDown = 1; break; } } if(hasUp && !hasDown) return DIR_UP; if(hasDown && !hasUp) return DIR_DOWN; if(hasUp && hasDown) return DIR_UP; // 同时有上下请求时先上后下 return DIR_STOP; }3.3 状态机的可扩展性
良好的状态机设计应该便于功能扩展。比如要增加紧急停止功能,只需:
- 添加紧急状态
- 在任何状态下检测紧急按钮
- 转移到紧急状态时执行安全操作(停止电机、打开门等)
case STATE_EMERGENCY: StopAllMotors(); OpenDoorImmediately(); BlinkEmergencyLED(); if(EmergencyCleared()) { currentState = STATE_IDLE; } break;4. 状态机调试与性能优化
4.1 状态跟踪与调试
调试状态机时,可以添加状态日志:
const char* StateToString(ElevatorState state) { static const char* strings[] = { "IDLE", "WAIT_KEY", "CLOSING_DOOR", "MOVING", "OPENING_DOOR", "WAITING" }; return strings[state]; } void LogStateTransition(ElevatorState oldState, ElevatorState newState) { printf("[%lu] State change: %s -> %s\n", HAL_GetTick(), StateToString(oldState), StateToString(newState)); }4.2 性能优化技巧
减少全局变量:使用结构体封装相关变量
typedef struct { ElevatorState state; uint8_t currentFloor; uint8_t targetFloors[MAX_FLOOR+1]; uint32_t stateEnterTime; } ElevatorController;事件队列:使用队列处理异步事件
typedef struct { EventType type; uint32_t timestamp; uint8_t data; } Event; #define EVENT_QUEUE_SIZE 10 Event eventQueue[EVENT_QUEUE_SIZE];状态处理函数指针:替代switch-case
typedef void (*StateHandler)(void); StateHandler stateHandlers[] = { HandleIdleState, HandleWaitKeyState, HandleClosingDoorState, // ... }; void Elevator_Run(void) { stateHandlers[currentState](); }
4.3 资源使用统计
通过合理设计,状态机实现通常具有较低的资源占用:
| 资源类型 | 使用量 | 说明 |
|---|---|---|
| Flash | ~5-10KB | 取决于状态复杂度 |
| RAM | 100-500B | 主要存储状态变量 |
| CPU | <5% | 非阻塞式设计负载低 |
在STM32G431上,这样的状态机实现完全在资源允许范围内,还能留有充足余量处理其他任务。
