ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

2024电赛H题自动行驶小车代码解析:STM32状态机与PID控制实战

2024电赛H题自动行驶小车代码解析:STM32状态机与PID控制实战 简介面向电子设计竞赛参赛者这份代码包记录了2024年电赛H题的完整实现方案采用32位微控制器STM32F10x系列进行系统设计覆盖从电路驱动到上层算法的核心代码适合备赛学生对照学习、移植与二次开发。包内共254个文件约8.49MB以C源文件45个.c、头文件47个.h及编译中间文件.o/.crf/.d为主同时包含Keil工程配置.uvprojx、链接脚本、启动文件、固件.axf等便于直接打开工程查看完整代码结构。其中大量stm32f10x_*.c文件集中展示了定时器、ADC、I2C、CAN、Flash、RCC等外设的初始化与调用方法。目前已有774人学习、浏览该资源其工程布局规范、外设驱动齐全不仅能帮助理解电赛H题的解题思路也可作为嵌入式开发中STM32底层驱动编写的实用参考资料。 上个月整理移动硬盘翻出来一个叫“24电赛H题代码(20241020).zip”的压缩包。看到这个文件名我愣了一下2024年10月20日刚好是2024年全国大学生电子设计竞赛H题赛期结束后两周那时候我把比赛里写的代码、调参记录、赛场注意事项一股脑打包了进去。这两天重新解压出来看发现这个包对准备电赛、或者想入门自动行驶小车的人来说确实是个不错的参考资料所以干脆写一篇文章把包里的设计思路和关键代码彻底讲透。先说清楚2024年电赛H题是“自动行驶小车”任务可以简单概括为让一辆小车在没有遥控器干预的情况下自己沿着赛道跑完全程中途要处理弯道、路口、停车区还要能识别并绕开障碍物。网上能搜到的代码方案很多但这个包的价值不在于“能跑”而在于它把从底层驱动到上层逻辑的分层做得比较清楚改起来很方便。这篇文章适合几类人看正在备赛电赛控制题的学生、想用STM32做小车项目的嵌入式初学者以及那些已经下载了这个代码包但不知道怎么下手、怎么改的人。我在重新翻代码的时候顺便把几个核心模块重新验证了一遍下面按我自己复盘时的顺序从题目拆解、代码结构、关键算法、编译烧录到问题排查一条条说清楚。1. 拿到代码包先别急着解压看懂H题到底在考什么1.1 2024年电赛H题“自动行驶小车”的任务拆解H题属于控制类题目每年都有一大批队伍选它因为门槛相对低、拿奖概率高但想把分数拿满并不容易。2024年的题目核心就是“自动行驶”整个赛道设置了直道、弯道、十字路口以及停车位小车出发后要按顺序完成行驶最终在指定位置停下。评分点主要有几个行驶是否压线、路口识别是否准确、停车精度是否达标、全程是否在规定时间内完成。我自己的理解是这道题本质上考的是“感知 决策 执行”三个环节的闭环。感知负责获取当前位置信息决策负责判断下一步该直行、转弯还是停车执行负责让电机按预期转动。任何一个环节出现延迟或误差小车就会在赛场上给你表演“画龙”。所以写代码之前一定要先把这三个环节拆开想清楚否则上来就写main函数后面一定会乱。1.2 压缩包里的目录结构每一层都是干嘛的这个压缩包解压之后打开是标准的STM32工程结构我当时用的是Keil MDK芯片是STM32F103C8T6整个代码分成了四个层次24电赛H题代码/ |-- Core/ // 启动文件、系统时钟、中断服务 |-- Hardware/ // 电机、灰度、超声波、OLED等硬件驱动 |-- System/ // 延时、串口、定时器等基础底层 |-- User/ // main.c、控制逻辑、状态机 |-- Doc/ // 调参记录、赛场注意事项 -- readme.txt // 代码版本说明Hardware和User的分层是我刻意做的。Hardware层只做一件事把寄存器和GPIO封装成好用的接口函数比如Motor_SetSpeed(20)、Gray_GetValue(1)调用方不需要关心底层寄存器怎么配。User层负责策略比如状态机的跳转、PID参数的调整。这样的好处很明显比赛现场如果发现传感器接错了引脚只需要改Hardware层User层逻辑完全不用动。我见过不少队伍把所有代码全堆在main.c里一改引脚就要在1000多行代码里找比赛期间真的会疯。1.3 平台选型逻辑为什么是STM32F103C8T6 灰度传感器经常有人问我为什么不用树莓派或者OpenMV非要用STM32我的理由其实很现实一是电赛从发题到交作品只有四天三夜树莓派和摄像头的组合虽然上限高但调试时间成本太高二是对于“循迹 避障 停车”这个任务灰度传感器完全够用实时性比摄像头好得多处理逻辑简单代码量也少。具体配置上我选的是STM32F103C8T6最小系统板理由就是便宜、资料多、坏了不心疼。电机用带霍尔编码器的直流减速电机驱动芯片用TB6612循迹用5路灰度传感器避障用HC-SR04超声波状态显示用0.96寸OLED。整套下来成本不到150块性能足够完成题目要求。摄像头方案我也预留了接口如果后续想升级可以直接把灰度换成OpenMV串口接上就能用。2. 代码核心设计思路小车是怎么“想”问题的2.1 五段式状态机整车逻辑的主干打开User/main.c你会看到主循环里不是一坨流水账式的顺序代码而是一个状态机。这是整包代码最核心的设计思路。我把小车的整个行为拆成了五个状态初始化、循迹行驶、路口处理、避障绕行、停车结束。switch (current_state) { case STATE_INIT: System_Init(); OLED_ShowString(0, 0, Ready); Delay_ms(1000); current_state STATE_TRACK; break; case STATE_TRACK: Track_Cycle(); // 循迹行驶 速度闭环 if (ultrasonic_distance 15.0f) { current_state STATE_AVOID; // 前方有障碍切换避障 } else if (stop_line_detect()) { current_state STATE_STOP; // 检测到停车线准备停车 } break; case STATE_AVOID: Avoid_Obstacle(); // 固定方向绕行完成后回到赛道 current_state STATE_TRACK; break; case STATE_STOP: Motor_SetSpeed(0, 0); OLED_ShowString(0, 2, Stop); current_state STATE_STOP; // 停车后保持状态 break; }状态机的优势在于每个状态只做一件纯粹的事状态之间的切换条件非常明确查问题的时候只要看“当前在哪个状态、为什么切不过去”就行。比赛现场最怕的是小车在一个路口反复横跳后来查出来就是状态切换条件写得太宽松加上一个“连续N次满足条件才切换”的防抖判断就解决了。2.2 循迹与转向PD控制是灵魂循迹模块是整个小车的核心我当时用的是位置式PD控制。5路灰度传感器的输出经过阈值判定后变成0和10代表在白底上1代表压到了黑线。我给每路灰度一个权重系数从左到右分别是-4、-2、0、2、4然后算出加权偏差int16_t error 0; int16_t weight[5] {-4, -2, 0, 2, 4}; for (int i 0; i 5; i) { error weight[i] * gray_state[i]; } // 位置式PD int16_t output (int16_t)(Kp * error Kd * (error - last_error)); last_error error;这个偏差值可以直接映射到左右轮的差速上。比如error为正说明车偏右了那就把右轮速度降一点、左轮速度提一点小车自然往左修正。PD参数无需精确整定先给一个初始值比如Kp 8Kd 10然后在小车上实测调整直到小车走直道不抖、过弯不飘。很多队伍栽在这里一上来就追求“完美参数”实际上没有一个参数能适应所有场地比赛前必须根据现场光线和地面重新校准。2.3 停车与避障条件判断的优先级怎么定停车和避障这两个动作容易打架比如小车检测到前方15厘米有障碍物同时又看到了停车线该听谁的我的方案是避障优先于停车。道理很简单停车线是固定元素错过了还有补救机会但障碍物是真实存在的撞上去就直接翻车扣分了。所以主循环里先用超声波判断是否进入避障状态再判断是否触发停车标志。停车检测我写了一个stop_line_detect()函数核心思路是“连续多帧检测到全部灰度都压线”。因为正常赛道上的白底和黑线宽度是固定的只有停车线区域才会让所有传感器同时压线。单纯检测一次不可靠我要求连续80次循环约0.3秒都满足才确认是停车线这样就过滤掉了短黑块和路口干扰。3. 关键模块实现细节每一段代码该怎么看、怎么改3.1 电机驱动与PWM输出TIM定时器配置里的坑Hardware/motor.c里面做了三件事初始化引脚、配置PWM输出、封装调速函数。TB6612的驱动逻辑很简单AIN1和AIN2决定电机正反转PWMA决定速度。我在代码里把方向引脚和PWM引脚分开管理避免后续调线时互相影响。PWM频率我配置的是20kHz这个频率不是为了炫技而是因为直流减速电机的电感特性决定了频率太低比如1kHz电机会发出明显的“嗡嗡”声扭矩不平稳频率太高又会增加MOS管的开关损耗。20kHz是实测下来比较舒服的区间噪音小、调速线性度好。Keil里配置TIM时有个容易踩的坑如果你用通用定时器TIM3的CH1和CH2输出两路PWM初始化的时候一定记得把TIM_OCInitStructure.TIM_OCMode设置成TIM_OCMode_PWM1我见过有人用默认的TIM_OCMode_Timing结果引脚一直没波形查了半天才发现是模式没配对。3.2 灰度传感器采集与阈值自适应环境校准的重要性灰度传感器本质上是红外反射式传感器黑线反射回去的红外线少输出电平高白色地面反射多输出电平低。但这个输出值受环境光、地面材质、传感器高度的影响极大所以固定阈值是行不通的。我在Hardware/gray.c里写了一个Gray_Calibration()函数思路是上电后让小车分别对准白底和黑线各采集20次ADC值取平均然后阈值取两者的中间值。这样每次开机都自动校准一遍环境变了也不怕。比赛现场有个细节容易被忽略传感器离地面的高度最好保持在1.5到2厘米之间太高会丢失信号太低会刮到地面凸起。我试过用双面胶直接粘跑一圈就掉了后来换成3D打印的小支架稳定多了。3.3 编码器测速与速度闭环让小车走得又直又稳光有PD转向还不够如果左右电机本身的转速不一致小车在直道上就会往一边偏。所以我加了编码器测速用定时器的编码器模式采集AB相脉冲然后计算实际速度再通过增量式PI控制让两轮速度趋近一致。void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); speed_right (int32_t)(TIM_GetCounter(TIM2) * 0.5f); TIM_SetCounter(TIM2, 0); } }这里有个关键点编码器计数会向两个方向累积所以用编码器模式之前一定要调用TIM_SetCounter(TIM2, 0)清零否则算出来的是累计值而不是周期速度。速度环的PI参数我设置为一组比较保守的值PI控制的目标不是追求极速而是保证一致性Kp给0.5左右Ki给0.1左右跑起来手感比较稳。4. 编译烧录与联调实录从报错到跑通的完整过程4.1 Keil工程恢复路径、芯片型号、下载器配置如果你下载了这份代码包第一件事不是双击打开main.c,而是检查Keil的工程配置。很多人打开后发现编译报一大堆错往往是因为三个地方没配对工程路径有中文、芯片型号选择错误、Debug下载器没设对。我用的是Keil MDK 5芯片选择的STM32F103C8Debug那栏选ST-LinkUtilities设置里也要勾上Reset and Run烧录完成后自动复位运行。这个“Reset and Run”很关键不勾的话每次烧录完还得手动按一下复位键比赛现场很浪费时间。工程路径要保持全英文不要放在桌面带中文路径的文件夹里否则编译器会随机抽风报一些莫名其妙的问题。我自己就吃过这个亏最后把整个工程复制到D:\H_Car\下面问题立刻消失。4.2 串口调试助手必备数据可视化排错法控制类代码最怕的就是“黑盒”小车跑歪了你不知道它内部在想什么。所以我在System/debug.c里封装了串口打印功能通过USART1输出灰度状态、超声波距离、当前状态机状态和左右轮目标速度。实测下来串口输出是最直观的排错方式比OLED刷新快得多。联调的时候我通常把小车放在赛道起点然后拿着手机打开蓝牙串口模块看数据流。比如发现小车在某个弯道突然停下来串口显示当前状态变成了STATE_AVOID但实际超声波距离显示100厘米那就说明距离读取函数里有毛刺需要加个中值滤波。这些小问题如果不加调试输出靠肉眼在赛场上看一辈子也看不出来。超声波的读取我这里多写一句HC-SR04的时序很容易受到串口中断干扰导致读到异常大值。我在代码里对距离做了防抖处理连续三次读到的值都在5厘米以内才认为真的碰到了障碍物。否则小车会因为在弯道处墙壁反射的超声波误判而突然紧急刹车。4.3 现场联调时间线3个小时从死机到稳定比赛现场联调那天的记录还留在Doc/调参记录.txt里我大概还原一下时间线给大家一个预期下午两点开始铺赛道第一轮测试小车刚起步就冲出赛道查串口数据发现是灰度阈值没有重新校准现场光线比实验室亮太多第二轮校准后能跑直道了但进弯道时前后抖动把Kd参数从10调到了15稳定性明显改善第三轮测试停车发现小车冲过停车线才刹车把停车检测的防抖次数从80改成了200才做到稳稳停在停车区内第四轮加上避障测试超声波避障逻辑和循迹逻辑打架又花了一个小时把状态机优先级调整清楚。三小时联调让我体会到代码写得好不好不是看编译通过就算完而是看改起逻辑来顺不顺手。这也是为什么我一直强调模块化比赛现场一切都在变只有代码结构清晰才能做到“改一处、通一处”。5. 常见问题排查速查表根据我在群里帮各路学弟看代码的经验这个代码包下载后大家最容易遇到的问题主要集中在表里这几类直接对照着查就行。问题现象可能原因解决办法编译报错缺头文件Keil的Include路径没加全在魔术棒C/C选项卡里把Hardware、System、User三个文件夹都加进Include Paths烧录时报No target connectedST-Link驱动没装或接线错误重新插拔ST-Link检查SWDIO、SWCLK、GND三条线是否接对电机不动PWM模式没配成PWM1检查TIM_OCInitStructure.TIM_OCMode是否为TIM_OCMode_PWM1小车只往一边转左右轮方向引脚接反调换TB6612的AIN1/AIN2或BIN1/BIN2接线灰度值不稳定环境光干扰或传感器高度不合适重新执行Gray_Calibration调整传感器高度到1.5-2厘米小车直道跑偏左右电机转速不一致检查编码器接线和速度PI参数确保两轮设定速度一致过弯时车身剧烈摆动PD中Kd过小修正过量增大Kd或者减小速度以减少惯性影响停车线检测误触发防抖次数不够提高stop_line_detect中的count阈值超声波距离异常读值毛刺或串口中断干扰加上中值滤波或连续多帧判断还有一个很多人会忽略的问题压缩包解压的时候如果杀毒软件拦截了某些文件会导致工程不完整。最好先把整个zip解压到固定目录再排除杀毒扫描然后再打开工程。我看到有几个群友在百度上搜索“zip密码移除工具”其实这个包没设密码解压不了多半是下载过程中文件损坏重新下载一次就好。最后再分享一个小技巧整套代码里我后来回头看最值钱的不是某个算法而是readme.txt里的调参纪律。电赛这几天参数一定会被反复改如果每次改完都能把“在什么环境下改的、改成多少、效果怎么样”记下来你会省下大量重复试错的时间。我自己比赛时有个习惯每个版本的代码在User/main.c开头都加一行宏定义注释比如#define VERSION 20241020_BACKUP_15cm_stop这样现场就算调崩了也能快速回退到稳定版本。这套代码虽然是以2024年H题为背景写的但拆开之后你会发现灰度循迹、超声波避障、编码器测速、状态机逻辑这些都是几乎所有智能小车项目通用的底层能力。哪怕2026年的题目换了花活底层的感知、决策、执行架构大概率还是这套框架。如果你想拿这个包做扩展优先推荐两个方向一是把灰度传感器升级成OpenMV摄像头在路口识别和赛道记忆上会有质的提升二是加一个IMU惯性测量单元让小车在失去灰度线的瞬间还能靠陀螺仪维持一小段航向这是应对大跨越赛道段的杀手锏。能把这个包里的代码吃透你基本就掌握了嵌入式控制系统的核心套路。先用状态机把大问题拆成小问题再用传感器获取真实数据最后用控制算法让执行器精确工作这不仅是电赛H题的打法也是我做嵌入式项目这么多年最常用的思考方式。本文还有配套的精品资源点击获取
返回列表