1. 状态机在嵌入式系统中的应用价值我第一次接触状态机是在大三参加电子设计竞赛时当时要做一个自动售货机的控制系统。面对各种按钮输入、出货电机控制和找零逻辑代码很快变成了一团乱麻。直到学长建议我用状态机重构才真正体会到这种设计思想的精妙之处。状态机State Machine特别适合处理具有明确状态划分和状态转移条件的系统。比如电梯控制它永远处于等待、关门、运行、开门、停留等有限状态中的某一个状态之间的转换规则也非常明确。用传统的前后台编程方式代码中会充满大量的if-else嵌套而状态机则通过清晰的状态划分和转移条件让代码结构变得一目了然。在STM32G431这类资源有限的嵌入式平台上状态机还有两个独特优势一是节省内存只需要维护当前状态变量二是实时性好状态判断都是确定性的布尔运算没有复杂算法带来的延迟。我在实际项目中测试过同样的电梯控制逻辑用状态机实现比传统方式节省约30%的Flash空间响应速度提升20%以上。2. 电梯控制系统的状态划分2.1 核心状态定义根据蓝桥杯赛题要求我们需要为一个四层电梯设计控制系统。经过多次调试优化我将系统划分为6个核心状态状态0等待按键电梯空闲时停留在这个状态检测四个楼层按键输入。这里有个细节处理当按键按下时除了设置目标楼层标志还会点亮对应的LED指示灯。为了防止重复响应代码中特别加入了current_floor!目标楼层的判断。状态1按键缓冲这是很多人容易忽略的关键状态。实际电梯都有个特性——在按下按键后的短时间内我们设为1秒仍可以继续选择其他楼层。实现方法是每次有新按键按下都重置计时器只有连续1秒无操作才会进入关门流程。状态2关门启动这个状态会触发两个硬件操作拉低PA5引脚控制门电机同时启动TIM17的PWM输出占空比50%模拟电机转速。这里用到了STM32的硬件PWM功能相比软件模拟更加精准。2.2 运动状态处理状态3方向判断这是整个系统最复杂的逻辑部分。需要扫描setfloor数组判断是否存在更高或更低的目标楼层。我采用了分层判断策略先检查上方所有楼层再检查下方楼层。当上下都有需求时设定先上后下的常见电梯调度策略。状态4楼层移动根据方向判断结果启动TIM16的PWM上行80%占空比下行60%占空比驱动模拟电机。这里有个视觉反馈技巧使用LED流水灯效果指示运行方向通过移位操作实现跑马灯效果代码中flow_led_state变量就是控制这个效果的。2.3 到达处理流程状态5开门等待到达目标楼层后需要完成三个操作清除该楼层标志、熄灭对应LED、启动开门计时。这里我增加了一个用户体验细节——让楼层数字闪烁两次通过HAL_Delay配合LCD清屏实现。状态6停留延时保持开门状态2秒后系统不会立即回到初始状态而是进入关门流程状态2形成闭环控制。只有所有楼层需求都满足后才会回到状态0等待新指令。3. STM32G431的硬件驱动实现3.1 外设初始化配置在CubeMX中需要配置以下关键外设/* PWM配置示例 */ htim16.Instance TIM16; htim16.Init.Prescaler 79; // 1MHz计数频率 htim16.Init.CounterMode TIM_COUNTERMODE_UP; htim16.Init.Period 999; // 1kHz PWM htim16.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim16); sConfig.OCMode TIM_OCMODE_PWM1; sConfig.Pulse 800; // 初始占空比80% sConfig.OCPolarity TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(htim16, sConfig, TIM_CHANNEL_1);GPIO方面需要注意PA4方向控制高电平上行低电平下行PA5门电机使能PC8-PC11楼层LED指示灯PB0-PB2,PA0四个楼层按键输入3.2 按键消抖处理原始代码中使用定时器中断实现按键检测这里分享一个更稳定的改进方案void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-InstanceTIM2) { static uint8_t key_state[4] {0}; uint8_t current_key HAL_GPIO_ReadPin(key_gpio_port, key_pin); for(int i0; i4; i) { if((current_key (1i)) ! (key_state[i] 0x01)) { key_state[i] (key_state[i]1) | (current_keyi 0x01); if((key_state[i] 0x0F) 0x0F) { // 连续4次检测到按下 key[i].key_flag 1; } } } } }3.3 PWM控制技巧电梯运行需要模拟不同速度通过调节PWM占空比实现关门速度TIM17 50%占空比上行速度TIM16 80%占空比下行速度TIM16 60%占空比实际项目中可以加入加速度控制void set_motor_speed(uint8_t dir, uint16_t target_duty) { static uint16_t current_duty 0; uint16_t step (target_duty current_duty) ? 5 : -5; while(current_duty ! target_duty) { current_duty step; __HAL_TIM_SET_COMPARE(htim16, TIM_CHANNEL_1, current_duty); HAL_Delay(50); } }4. 调试技巧与常见问题4.1 状态跟踪方法调试状态机时最容易遇到的问题就是状态卡死。我总结了几种调试手段LCD状态显示在屏幕固定区域打印当前状态变量值如sprintf(status_text, S%d F%d D%d, process_status, current_floor, dir); LCD_DisplayStringLine(Line7, status_text);LED编码法用不同LED组合表示状态LED1闪烁状态0LED2闪烁状态1以此类推SWD调试在关键状态转换处设置断点观察变量变化4.2 典型问题解决方案问题1按键响应不稳定原因机械按键抖动导致多次触发解决采用上述改进的消抖算法或者硬件RC滤波问题2电梯运行时间不准原因使用HAL_Delay导致时间累积误差解决改用硬件定时器if(HAL_GetTick() - start_time 6000) { // 6秒时间到 }问题3LCD显示闪烁原因频繁全屏刷新解决使用局部更新LCD_ClearLine(Line3); // 只清除需要修改的行 LCD_DisplayStringLine(Line3, new_text);4.3 性能优化建议将频繁调用的函数声明为__inline如LED显示函数使用位带操作替代HAL_GPIO函数#define LED_PORT ((__IO uint32_t*)0x42000000 (GPIOC_BASE 0x0C)*32) void LED_display_fast(uint8_t leds) { *LED_PORT (leds 8); }开启编译器优化选项-O2在完成这个项目后我发现状态机不仅是一种编程方法更是一种思维模式。当面对任何具有明确状态划分的系统时先在纸上画出状态转换图往往能事半功倍。特别是在准备蓝桥杯这类竞赛时良好的状态机设计可以让代码结构更清晰调试更轻松这在紧张的比赛环境中尤为重要。