当前位置: 首页 > news >正文

攻克蓝桥杯(4)——第八届蓝桥杯嵌入式省赛电梯调度算法实战解析

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/7200=10kHz htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 10000-1; // 10kHz/10000=1Hz htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // TIM4 PWM配置 htim4.Instance = TIM4; htim4.Init.Prescaler = 72-1; // 72MHz/72=1MHz htim4.Init.CounterMode = TIM_COUNTERMODE_UP; htim4.Init.Period = 1000-1; // 1MHz/1000=1kHz 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_level<3) { 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 i=0; i<t; 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; } } }

问题3:LCD显示闪烁

  • 原因:频繁刷新导致
  • 优化方案:只在数据变化时更新显示
if(last_level != now_level) { sprintf((char *)str1," %d",now_level); LCD_DisplayStringLine(Line4,str1); last_level = now_level; }

在资源优化方面,我做了以下改进:

  1. 将部分字符串常量改为指针引用,减少内存占用
  2. 使用位域结构体压缩标志位存储
  3. 合理使用const修饰符,将常量放入Flash而非RAM
  4. 优化算法时间复杂度,避免多层嵌套循环

5. 完整系统集成与测试

将所有模块集成后,需要进行系统级测试。我设计了以下几种测试场景:

  1. 基本功能测试

    • 从1楼依次按下2、3、4楼,验证上行顺序
    • 从4楼依次按下3、2、1楼,验证下行顺序
    • 混合按下不同楼层,验证调度逻辑
  2. 边界条件测试

    • 在当前楼层按下相同楼层(应无响应)
    • 快速连续按下多个楼层
    • 电梯运行过程中新增请求
  3. 压力测试

    • 同时按下所有楼层按键
    • 长时间运行测试稳定性

测试过程中发现的一个有趣现象是:当电梯正在上行时,如果按下比当前楼层低的楼层,该请求会被正确加入下行队列,但不会立即响应,而是等完成所有上行请求后再处理。这正好体现了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库文件

在编码规范方面,我特别注意以下几点:

  1. 全局变量加前缀"g_"便于识别
  2. 函数名使用动宾结构,如"get_current_floor()"
  3. 关键代码段添加详细注释
  4. 保持一致的缩进风格(4个空格)
  5. 复杂逻辑拆分为小函数,每个函数只做一件事

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. 进阶优化思路

完成基础功能后,还可以考虑以下优化方向:

  1. 动态权重调度: 为不同楼层请求设置优先级,例如:
    • 长时间等待的请求提高优先级
    • 紧急呼叫最高优先级
typedef struct { uint8_t floor; uint32_t wait_time; // 等待时间 uint8_t priority; // 优先级 } ElevatorRequest;
  1. 能耗优化: 根据运行状态调整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%占空比 } }
  1. 预测算法: 基于历史数据预测可能请求,提前准备响应
  2. 多电梯协同: 扩展为多电梯系统,实现负载均衡

在资源允许的情况下,还可以增加更多实用功能:

  • 语音提示功能
  • 故障自检与恢复
  • 远程监控接口
  • 能耗统计显示

这个项目让我深刻体会到,嵌入式开发不仅仅是写代码,更需要考虑硬件特性、实时性要求和资源限制之间的平衡。调试过程中,逻辑分析仪和STM32CubeMonitor工具帮了大忙,它们可以直观地展示系统运行状态,快速定位问题。

http://www.jsqmd.com/news/1085691/

相关文章:

  • 从零到一:EFK在K8S环境下的日志收集实战部署
  • GetQzonehistory终极指南:如何一键找回QQ空间消失的青春记忆
  • 如何做好测试?(八)可靠性测试:从理论到实战的电商系统稳定性保障
  • 你总是说服不了别人?高手都在用隐性心理话术,隐性思维操控术原理篇+策略篇+6份稀缺赠品,是你掌控人性的秘钥!
  • PHP反序列化漏洞深度解析:从原理到应急响应与加固实战
  • DDrawCompat:Windows 10/11上经典DirectX游戏兼容性修复方案
  • 如何快速掌握网盘直链下载助手:九大网盘免客户端下载的完整实战手册
  • 从滑动相关到匹配滤波器:DMF捕获原理与FPGA实现权衡
  • 无线传能中的负载调制与包络检波
  • Akagi:终极雀魂AI辅助工具完整使用指南,提升麻将水平的智能助手
  • 瑞萨RZT2L-RSK开发套件FSP示例项目深度解析与实战指南
  • 实战解析 NFS缓存机制与Pod间文件同步延迟的排查与优化
  • Win11 下 PHPstudy 一站式部署与避坑指南
  • 天龙八部GM工具:轻松掌控游戏世界的终极助手
  • Elsevier Tracker:让学术投稿进度监控变得简单高效
  • 如何用MusicFree插件打造你的专属音乐聚合中心
  • 互联网大厂 Java 求职面试:技术与场景的碰撞
  • B站视频下载神器:解锁大会员4K和充电专属内容的终极方案
  • 从JiraWhitelist逻辑缺陷到内网漫游:CVE-2019-8451 SSRF漏洞深度剖析
  • 从入门到精通:redis-cli命令行实战全解析
  • Go语言国密全栈方案gmsm实战:从算法到TLS的完整指南
  • 开源音乐聚合终极方案:MusicFreePlugins完整指南
  • 致创协与黑客松组织者:让每一个想法,都有机会被看见!
  • 【信息科学与工程学】信息科学领域——第八十八篇 云数据中心解决方案的关键技术01
  • PostgreSQL JOIN 优化指南
  • 分频器实战:从秒脉冲到任意分频的Verilog实现与仿真
  • 国内大模型与国外大模型的差距在哪里
  • 基于LLM的知识图谱自动构建系统:从非结构化数据到结构化知识的智能转换
  • 华为MSTP、Eth-Trunk、VRRP融合组网:从原理到高可用企业网实战
  • 从质点、刚体到机械臂:一文读懂自由度的物理本质与工程应用