攻克蓝桥杯(4)——第八届蓝桥杯嵌入式省赛电梯调度算法实战解析
2026/6/28 21:38:12
网站开发
1. 电梯调度算法基础与赛题解析第一次看到第八届蓝桥杯嵌入式省赛的电梯调度题目时我的内心是崩溃的。题目要求实现一个四层电梯的控制系统需要处理按键响应、运行方向判断、楼层排序等复杂逻辑。这不仅仅是简单的GPIO控制更考验我们对经典调度算法的理解和嵌入式实现能力。电梯调度算法的核心目标是高效响应乘客请求。在资源受限的STM32F103RBT6上实现时我们需要特别关注几个关键点首先是内存占用全局变量不宜过多其次是实时性算法不能有太高的时间复杂度最后是稳定性要避免死锁等异常情况。常见的电梯调度算法有SCAN电梯算法、LOOK算法、SATF最短寻道时间优先等。经过对比分析我选择了LOOK算法的变种来实现因为它在保证公平性的同时具有较好的效率。具体来说就是电梯会持续朝一个方向运行直到该方向没有请求时才会调转方向。2. 硬件平台与外设配置我使用的是官方指定的CT117E开发板主控为STM32F103RBT6。在CubeMX中的配置需要特别注意几个关键点首先是定时器的配置。我们需要TIM3用于通用计时处理1秒、6秒等时间逻辑TIM4产生PWM波控制电机模拟电梯升降TIM15用于按键消抖。具体参数设置如下// TIM3基础配置 htim3.Instance TIM3; htim3.Init.Prescaler 7200-1; // 72MHz/720010kHz htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 10000-1; // 10kHz/100001Hz htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; // TIM4 PWM配置 htim4.Instance TIM4; htim4.Init.Prescaler 72-1; // 72MHz/721MHz htim4.Init.CounterMode TIM_COUNTERMODE_UP; htim4.Init.Period 1000-1; // 1MHz/10001kHz PWM htim4.Init.ClockDivision TIM_CLOCKDIVISION_DIV1;GPIO配置方面四个楼层按键(F1-F4)需要设置为上拉输入模式LED控制引脚设置为推挽输出。特别注意要开启对应的GPIO时钟和中断如果需要// 按键GPIO配置 GPIO_InitStruct.Pin F1_Pin|F2_Pin|F3_Pin|F4_Pin; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);3. 核心算法实现细节3.1 请求队列管理电梯系统需要维护三个关键数组目标楼层数组tar_level、上行队列go_up和下行队列go_down。我使用了简单的数组结构来存储这些信息uint8_t tar_level[4]; // 目标楼层最大容量4 uint8_t go_up[3]; // 上行队列 uint8_t go_down[3]; // 下行队列 uint8_t up_cnt 0; // 上行计数器 uint8_t down_cnt 0; // 下行计数器当按下楼层按键时系统需要判断该请求是上行还是下行。这里有一个容易出错的细节同一楼层的请求在不同情况下可能属于不同方向。例如当前在1楼按下3楼是上行请求而当前在4楼按下3楼则是下行请求。if(HAL_GPIO_ReadPin(F3_GPIO_Port,F3_Pin) 0 now_level !3) { tar_level[cnt] 3; if(now_level3) { up_down[cnt]1; // 上行请求 } else { up_down[cnt]2; // 下行请求 } cnt; }3.2 调度策略实现核心调度逻辑在floor_rank()和up_down_jug()函数中实现。floor_rank()负责将原始请求分类到上行或下行队列up_down_jug()则决定电梯当前运行方向。void floor_rank(void) { int t strlen((char *)up_down); for(int i0; it; i) { if(up_down[i] 0x02) { go_down[q] tar_level[i]; // 加入下行队列 } else if(up_down[i] 1) { go_up[p] tar_level[i]; // 加入上行队列 } } memset(up_down,0,3); memset(tar_level,0,3); } void up_down_jug(void) { if(strlen((char *)go_up) 0) { flag_up 1; flag_down 0; // 设置上行标志 } else if(strlen((char *)go_down) 0) { flag_down 1; flag_up 0; // 设置下行标志 } else { flag_down flag_up 0; // 无请求 } }3.3 运行控制与楼层更新电梯运行控制是项目中最复杂的部分需要考虑多种状态转换。我使用了一个状态机模型主要包含以下几个状态等待状态无请求加速状态启动初期匀速运行状态减速状态接近目标楼层停靠状态开门、关门楼层更新逻辑在floor_add_dec()函数中实现每6秒改变一次楼层if(sec_6 1 (strlen((char *)go_up)0 || strlen((char *)go_down)0)) { if(flag_up !arrive_tar) { now_level; if(now_level go_up[up_cnt]) { arrive_tar 1; // 到达目标楼层 } } else if(flag_down !arrive_tar) { now_level--; if(now_level go_down[down_cnt]) { arrive_tar 1; // 到达目标楼层 } } }4. 调试技巧与性能优化在开发过程中我遇到了几个典型问题及解决方案问题1按键响应不灵敏原因机械按键存在抖动直接读取会导致多次触发解决方案增加300ms的延时消抖if(HAL_GPIO_ReadPin(F1_GPIO_Port,F1_Pin) 0) { HAL_Delay(300); // 消抖处理 if(HAL_GPIO_ReadPin(F1_GPIO_Port,F1_Pin) 0) { // 确认按键按下 } }问题2电梯运行不流畅原因主循环中处理太多任务导致响应延迟优化方案将部分功能移到定时器中断处理void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM3) { time_cnt; if(time_cnt 6000) { // 6秒到达 sec_6 1; time_cnt 0; } } }问题3LCD显示闪烁原因频繁刷新导致优化方案只在数据变化时更新显示if(last_level ! now_level) { sprintf((char *)str1, %d,now_level); LCD_DisplayStringLine(Line4,str1); last_level now_level; }在资源优化方面我做了以下改进将部分字符串常量改为指针引用减少内存占用使用位域结构体压缩标志位存储合理使用const修饰符将常量放入Flash而非RAM优化算法时间复杂度避免多层嵌套循环5. 完整系统集成与测试将所有模块集成后需要进行系统级测试。我设计了以下几种测试场景基本功能测试从1楼依次按下2、3、4楼验证上行顺序从4楼依次按下3、2、1楼验证下行顺序混合按下不同楼层验证调度逻辑边界条件测试在当前楼层按下相同楼层应无响应快速连续按下多个楼层电梯运行过程中新增请求压力测试同时按下所有楼层按键长时间运行测试稳定性测试过程中发现的一个有趣现象是当电梯正在上行时如果按下比当前楼层低的楼层该请求会被正确加入下行队列但不会立即响应而是等完成所有上行请求后再处理。这正好体现了LOOK算法的特点。6. 工程架构与代码规范为了提高代码可维护性我采用了模块化设计Elevator_Control/ ├── Inc/ │ ├── elevator.h // 主要数据结构与宏定义 │ ├── io_config.h // GPIO引脚定义 │ └── timer_config.h // 定时器配置 ├── Src/ │ ├── main.c // 主循环与初始化 │ ├── elevator.c // 核心调度算法 │ ├── io_control.c // 按键与LED控制 │ └── lcd_display.c // 显示相关函数 └── Drivers/ // HAL库文件在编码规范方面我特别注意以下几点全局变量加前缀g_便于识别函数名使用动宾结构如get_current_floor()关键代码段添加详细注释保持一致的缩进风格4个空格复杂逻辑拆分为小函数每个函数只做一件事7. 常见问题解决方案在实际开发中我遇到了几个典型问题这里分享解决方案问题1电梯运行方向判断错误现象有时会错误地改变运行方向原因标志位没有及时清除修复在完成所有同方向请求后再清除标志位if(up_cnt strlen((char *)go_up) up_cnt ! 0) { memset(go_up,0,3); up_cnt 0; flag_up 0; // 明确清除标志位 }问题2楼层显示跳变现象LCD显示的楼层号偶尔会跳变原因变量类型不匹配导致溢出修复统一使用uint8_t类型存储楼层信息uint8_t now_level 1; // 明确指定无符号类型问题3定时器中断冲突现象有时定时器中断会互相干扰解决方案合理设置中断优先级HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0); HAL_NVIC_SetPriority(TIM4_IRQn, 2, 0);8. 进阶优化思路完成基础功能后还可以考虑以下优化方向动态权重调度 为不同楼层请求设置优先级例如长时间等待的请求提高优先级紧急呼叫最高优先级typedef struct { uint8_t floor; uint32_t wait_time; // 等待时间 uint8_t priority; // 优先级 } ElevatorRequest;能耗优化 根据运行状态调整PWM占空比在匀速阶段降低能耗void adjust_pwm(uint8_t mode) { if(mode ACCELERATE) { __HAL_TIM_SET_COMPARE(htim4, TIM_CHANNEL_1, 800); // 80%占空比 } else if(mode CRUISE) { __HAL_TIM_SET_COMPARE(htim4, TIM_CHANNEL_1, 500); // 50%占空比 } }预测算法 基于历史数据预测可能请求提前准备响应多电梯协同 扩展为多电梯系统实现负载均衡在资源允许的情况下还可以增加更多实用功能语音提示功能故障自检与恢复远程监控接口能耗统计显示这个项目让我深刻体会到嵌入式开发不仅仅是写代码更需要考虑硬件特性、实时性要求和资源限制之间的平衡。调试过程中逻辑分析仪和STM32CubeMonitor工具帮了大忙它们可以直观地展示系统运行状态快速定位问题。