用STM32CubeMX和HAL库复刻蓝桥杯嵌入式省赛电梯调度题(附完整工程)
2026/6/9 7:22:38 网站建设 项目流程

基于STM32CubeMX的电梯调度系统实战开发指南

在嵌入式系统开发领域,蓝桥杯竞赛一直是检验学习者实战能力的重要舞台。其中电梯调度题目因其综合性强、贴近实际应用场景而备受关注。本文将带领读者从零开始,使用STM32CubeMX和HAL库完整实现一个四层电梯调度系统,涵盖外设配置、算法实现到调试优化的全流程。

1. 开发环境搭建与基础配置

1.1 硬件平台选择与初始化

CT117E开发板作为蓝桥杯嵌入式竞赛的官方平台,集成了STM32F103系列MCU和丰富的外设接口。开发前需准备:

  • STM32CubeMX v6.x或更高版本
  • Keil MDK-ARM开发环境
  • ST-Link/V2调试器
  • 四层电梯模拟模块(按键、LED指示灯等)

关键外设配置表

外设类型功能说明配置参数
GPIO楼层按键输入上拉输入模式
TIM3系统计时基准1kHz时钟,中断使能
TIM4PWM生成通道1,72MHz/7200=10kHz
RTC实时时钟LSE时钟源,日历模式
// CubeMX生成的时钟配置代码片段 void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 配置HSE振荡器 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; HAL_RCC_OscConfig(&RCC_OscInitStruct); // 配置系统时钟 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); }

1.2 HAL库使用注意事项

使用HAL库开发时需特别注意:

  • 中断优先级配置:确保关键外设(如定时器)具有适当优先级
  • 延时函数使用:避免在中断服务例程中直接调用HAL_Delay()
  • 外设初始化顺序:先初始化时钟和外设,再启用中断

提示:调试阶段可启用HAL库的assert功能,有助于快速定位配置错误。

2. 电梯调度核心算法实现

2.1 请求队列管理机制

电梯系统需要高效处理来自不同楼层的请求。我们采用以下数据结构:

// 电梯状态数据结构 typedef struct { uint8_t current_floor; // 当前楼层(1-4) uint8_t target_floors[4]; // 目标楼层队列 uint8_t direction; // 0-停止 1-上行 2-下行 uint8_t door_status; // 0-关闭 1-开启 } ElevatorState; // 请求处理函数 void process_floor_request(uint8_t floor) { if(floor == elevator.current_floor) return; // 检查是否已存在相同请求 for(int i=0; i<4; i++) { if(elevator.target_floors[i] == floor) return; } // 添加到请求队列 for(int i=0; i<4; i++) { if(elevator.target_floors[i] == 0) { elevator.target_floors[i] = floor; break; } } // 自动确定运行方向 if(floor > elevator.current_floor) { elevator.direction = 1; } else if(floor < elevator.current_floor) { elevator.direction = 2; } }

2.2 扫描调度算法优化

针对四层电梯场景,我们实现优化的扫描算法:

  1. 上行阶段
    • 收集所有高于当前楼层的请求
    • 按升序依次服务
  2. 下行阶段
    • 收集所有低于当前楼层的请求
    • 按降序依次服务
  3. 方向切换规则
    • 完成当前方向所有请求后检查反向请求
    • 无请求时保持静止状态
void elevator_scheduler(void) { static uint32_t last_move_time = 0; // 电梯运行状态机 switch(elevator.direction) { case 0: // 停止状态 if(find_next_request()) { elevator.direction = (next_floor > elevator.current_floor) ? 1 : 2; } break; case 1: // 上行状态 if(++elevator.current_floor == next_floor) { serve_floor(elevator.current_floor); if(!find_next_request_in_direction()) { elevator.direction = find_opposite_request() ? 2 : 0; } } last_move_time = HAL_GetTick(); break; case 2: // 下行状态 if(--elevator.current_floor == next_floor) { serve_floor(elevator.current_floor); if(!find_next_request_in_direction()) { elevator.direction = find_opposite_request() ? 1 : 0; } } last_move_time = HAL_GetTick(); break; } // 超时保护 if((HAL_GetTick() - last_move_time) > 60000) { elevator.direction = 0; } }

3. 关键外设驱动实现

3.1 按键检测与消抖处理

采用状态机方式实现可靠的按键检测:

// 按键状态定义 typedef enum { KEY_IDLE, KEY_PRESSED, KEY_CONFIRMED, KEY_RELEASED } KeyState; // 按键检测状态机 void detect_key_press(uint16_t pin, GPIO_TypeDef* port) { static KeyState key_state = KEY_IDLE; static uint32_t press_time = 0; switch(key_state) { case KEY_IDLE: if(HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_RESET) { press_time = HAL_GetTick(); key_state = KEY_PRESSED; } break; case KEY_PRESSED: if(HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET) { key_state = KEY_IDLE; } else if((HAL_GetTick() - press_time) > 50) { key_state = KEY_CONFIRMED; process_floor_request(get_floor_by_pin(pin)); } break; case KEY_CONFIRMED: if(HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET) { key_state = KEY_RELEASED; } break; case KEY_RELEASED: key_state = KEY_IDLE; break; } }

3.2 PWM控制电机驱动

使用TIM4生成PWM信号控制电梯模拟电机:

// 电机控制函数 void control_elevator_motor(uint8_t direction) { switch(direction) { case 1: // 上行 HAL_GPIO_WritePin(EL_DOWN_GPIO_Port, EL_DOWN_Pin, GPIO_PIN_RESET); HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 5000); // 50%占空比 break; case 2: // 下行 HAL_GPIO_WritePin(EL_UP_GPIO_Port, EL_UP_Pin, GPIO_PIN_RESET); HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 3000); // 30%占空比 break; default: // 停止 HAL_TIM_PWM_Stop(&htim4, TIM_CHANNEL_1); break; } }

4. 系统集成与调试技巧

4.1 状态可视化实现

通过LCD实时显示电梯运行状态:

void update_lcd_display(void) { char lcd_buf[20]; // 显示当前楼层 sprintf(lcd_buf, "Floor: %d", elevator.current_floor); LCD_DisplayStringLine(LINE1, (uint8_t*)lcd_buf); // 显示运行方向 const char* dir_str = ""; switch(elevator.direction) { case 1: dir_str = "UP "; break; case 2: dir_str = "DOWN"; break; default: dir_str = "STOP"; break; } LCD_DisplayStringLine(LINE2, (uint8_t*)dir_str); // 显示目标楼层队列 sprintf(lcd_buf, "Targets: %d %d %d %d", elevator.target_floors[0], elevator.target_floors[1], elevator.target_floors[2], elevator.target_floors[3]); LCD_DisplayStringLine(LINE3, (uint8_t*)lcd_buf); }

4.2 常见问题排查指南

开发过程中遇到的典型问题及解决方案:

问题现象可能原因解决方法
按键响应不稳定消抖处理不当增加硬件滤波电容,优化软件消抖算法
电梯运行卡顿定时器配置错误检查TIM3时钟配置,确认中断优先级
LCD显示异常初始化时序问题确保LCD初始化延迟足够,检查数据线连接
PWM输出不稳定自动重装载值设置不当调整ARR和PSC寄存器值,确保频率合适

注意:调试复杂状态机时,建议使用LED指示灯或串口打印关键状态变量,有助于快速定位逻辑错误。

5. 工程优化与扩展思路

5.1 性能优化策略

  1. 中断优化
    • 将时间关键代码放入定时器中断
    • 使用DMA传输减轻CPU负担
// 定时器中断处理示例 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM3) { static uint16_t counter = 0; if(++counter >= 100) { // 100ms时间基准 counter = 0; elevator_scheduler(); } detect_all_keys(); } }

5.2 功能扩展方向

  1. 紧急停止功能

    • 添加硬件急停按钮
    • 实现安全中断处理
  2. 能耗统计模块

    • 记录运行时间和启停次数
    • 估算能耗并显示
  3. 网络监控接口

    • 通过串口或蓝牙传输状态数据
    • 开发上位机监控界面

在完成基础功能后,可以尝试将工程移植到FreeRTOS等实时操作系统,实现更复杂的任务调度和资源管理。实际测试中发现,合理的任务划分能显著提高系统响应速度,例如将按键扫描、状态更新和电机控制分别放在不同优先级的任务中处理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询