资讯详情

2020电赛ProblemC爬坡小车源码解析:从驱动到调参实战

📅 2026/9/13 17:26:00 | 华诺云谱 👁 阅读
2020电赛ProblemC爬坡小车源码解析:从驱动到调参实战
简介这份2020年电赛ProblemC爬坡小车源码包是一套基于MSP430F5529的完整嵌入式竞赛方案面向电子、计算机、自动化等专业学生适合正在备赛电赛或有嵌入式开发基础、希望研究小车爬坡与循迹算法的读者。压缩包共88个文件大小仅320KB其中包含main.c、pid.c、pwm.c、xunji.c等C源码与对应头文件另有Makefile、CCS工程文件和launch调试配置obj、out、map等编译链接产物也一并保留目录结构清晰便于阅读理解与二次开发。目前已有118人学习下载。通过这份源码可以快速掌握电赛ProblemC的整体框架包括传感器循迹、PID调速、PWM驱动及OLED显示等关键模块的实现思路所有代码均可直接编译使用适合在官方MSP430F5529平台上运行调试。对于希望深入嵌入式竞赛项目的同学这是一个很好的参考范例可结合官方题目要求自行修改参数、优化算法锻炼独立调试与排错能力。1. 2020电赛ProblemC爬坡小车源码解压之后先别急着烧录2020年电赛的ProblemC坡道行驶小车说白了就是让一辆电动车从平地出发、把速度提到够大再沿一条引导线冲上坡道最后在坡顶区域准确停下。题目的难点从来不在“跑得快”而在“带着巡线误差上坡不倒退、停车位置不冲出”。2020电赛ProblemC爬坡小车源码.zip这类包里装的就是一套围绕这个目标组织的嵌入式工程入口main、电机驱动、编码器测速、PID速度环、巡线算法、状态机。拿到它真正值得做的事不是“烧录成功就算完”而是先理清板级映射再把速度环和巡线环拆开调最后按自己的板子重做驱动层。这篇就按这个顺序讲让你在最短时间内把别人的代码变成自己能改、能上赛场的代码。2. 工程结构、引脚映射与编译前必须做的三处核对拿到压缩包后的常见动作是用7-Zip或Windows自带解压工具先看内部结构。源码包能不能在你的板子上跑关键不在IDE配置而在“板级映射”对不对得上。这一步做扎实后面改算法才有意义。2.1 典型的裸机源码目录与文件职责大部分电赛小车工程没有跑RTOS是一个前后台结构目录通常长这样Project/ ├─ README.txt # 接线说明、按键定义最值得先读 ├─ Hardware/ │ ├─ motor.c # 电机PWM输出与方向IO │ ├─ encoder.c # 定时器编码器模式读取轮速 │ ├─ line_sensor.c # 多路巡线传感器的采集与阈值化 │ ├─ mpu6050.c # 陀螺仪加速度计用于坡道角度判断 │ └─ oled.c # 参数显示调车时能看到内部状态 ├─ Control/ │ ├─ pid.c # 速度环/方向环共用的一组PID │ └─ strategy.c # 从发车到爬坡停车的状态机 └─ main.c # 主循环定时调度任务切换README.txt里往往写着哪些跳线帽要短接、哪个按键负责发车。没有README时就看头文件里的宏定义板级配置基本都以宏的形式集中放在一个bsp.h里这是拿到源码后第一个要改的地方。源码包能不能在你的板子上跑关键在“这个宏定义对应的引脚资源”和你的原理图是否一致。2.2 引脚映射表先确认时钟、PWM与编码器通道拿到任一小车源码第一步是在main.c或bsp.c里找板级资源配置。我一般会把它们抄成一张对照表再对照自己的原理图逐项打勾功能模块芯片引脚定时器/通道源码中的宏名说明左电机PWMPA8TIM1_CH1MOTOR_L_PWM_GPIO/MOTOR_L_PWM_CH驱动左侧半桥右电机PWMPA9TIM1_CH2MOTOR_R_PWM_GPIO驱动右侧半桥左编码器A/BPA0/PA1TIM2_CH1/CH2ENCODER_L_TIM正交编码器计数方向自动累积右编码器A/BPB6/PB7TIM4_CH1/CH2ENCODER_R_TIM测速用巡线传感器PC0~PC7GPIO输入LINE_SENSOR_PORT8路数字量1为黑线OLED I2CPB8/PB9I2C1OLED_I2C调参时打印数据其中最容易出问题的是引脚复用。源码里默认TIM1_CH1输出在PA8如果你的板子把PWM接到了PB13需要改的不只是一个宏而是GPIO_InitStructure.GPIO_Pin和GPIO_PinAFConfig两个地方都要同步改。只看宏名字就以为配置完成是移植电赛源码最常见的不靠谱习惯。2.3 编译下载前必查的三处冲突第一处是JTAG占用。很多板子的PB3、PB4、PA15默认被SWD/JTAG复用如果源码把方向控制引脚或编码器引脚放在这些脚上下载后引脚驱动无效反复烧录还会偶发失败。处理方法是在GPIO_Init之前把SWJ配置成只保留SWD或全关闭。第二处是定时器通道映射。编码器模式必须挂在具备输入捕获能力的引脚上且A、B两相必须落在同一个定时器的两个通道。若你的原理图把编码器接到普通GPIO上这不是代码问题是接线错误源码改不了。第三处是时钟频率。源码默认HSE8MHz、系统时钟72MHz时你的板子若改用12MHz外部晶振串口打印会乱码PWM频率也全部偏移。排查方法很简单先烧一个串口回环测试确认打印周期对不对再谈其它功能。提示编译之前先做“驱动层单测”。把main.c改成上电自检左右电机各转0.5秒、OLED显示编码器增量、巡线传感器实时值全部正确后再进入赛题逻辑。这个动作能直接省掉后续调试阶段一半的“程序到底跑没跑”的疑问。3. 上坡不溜车速度闭环与巡线转向的实现细节这段源码是整个包的核心价值。爬坡小车的本质是双轮差速模型速度环保证整体推进力转向环负责沿着引导线修偏。理解顺序也是先速度、再转向、最后看怎么叠加。3.1 编码器测速与10ms速度采样不管是AB相正交编码器还是磁编码器进入STM32的定时器编码器模式后计数寄存器会自动增减。测速时只需要在固定时基中断里做“读增量、清零”两件事TIM_HandleTypeDef htim_enc_l; // 编码器定时器句柄以左轮为例 // 10ms 定时器中断唤一次测速周期固定 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM_BASE) { // 专门用另一个基本定时器做时基 int32_t delta_l __HAL_TIM_GET_COUNTER(htim_enc_l); // 取当前计数值 __HAL_TIM_SET_COUNTER(htim_enc_l, 0); // 清计准备下一段 int16_t speed_l (int16_t)((delta_l * 100) / 10); // 换算单位速度 } }这里的核心设计是“增量清零”而不是“直接读累计值”。如果把累计值当成速度用低速时速度值长时间不变高速时又来回跳动因为每次读到的都是从启动到现在的总和。先把中断周期固定到10ms读到的就是这一段的速度增量之后的PID才有稳定输入。参数说明TIM_BASE是单独分配的基本定时器负责产生10ms时基不要和编码器定时器混用括号里的100 / 10只是示意实际要根据轮径和减速比换算成mm/s换算系数建议写成宏调车时只改一处。3.2 带积分分离的速度PID实现爬坡时负载突变速度PID如果带普通积分项很容易积分饱和误差大时积分一路累加等轮速追上来输出还在高位车就猛冲。更稳妥的方案是积分分离误差过大时直接停用积分float err_sum 0.0f; float err_last 0.0f; #define KP_SPEED 12.0f #define KI_SPEED 0.4f #define KD_SPEED 0.05f float pid_speed_update(float target_speed, float current_speed, float dt) { float err target_speed - current_speed; // 积分分离误差过大时清积分防止爬坡时饱和 if ((err 8.0f) || (err -8.0f)) { err_sum 0.0f; } else { err_sum err * dt; if (err_sum 1000.0f) err_sum 1000.0f; // 积分限幅 if (err_sum -1000.0f) err_sum -1000.0f; } float p_out KP_SPEED * err; float i_out KI_SPEED * err_sum; float d_out KD_SPEED * (err - err_last) / dt; err_last err; float pwm p_out i_out d_out; // 输出限幅给转向留出调节空间 if (pwm 85.0f) pwm 85.0f; if (pwm -85.0f) pwm -85.0f; return pwm; }这个函数的输入是目标速度和实测速度输出是百分比形式的PWM占空比。爬坡时最该关心的不是Kp而是PWM上限85这个值它限制速度环最多只能输出85%占空比剩下15%的空间专门留给转向修正。如果发现爬坡中途速度环输出经常顶着上限说明目标速度定太高了或动力裕量不够正确的做法是降目标速度而不是把上限放开到100%。3.3 巡线误差计算与差速转向多路巡线传感器最常见的做法是“权重求和”。8路传感器按位置排开左边权重为负、右边为正压到黑线的传感器计入求和// 8路传感器sensor[i]1 表示压到黑线 int16_t line_error(uint8_t sensor[8]) { const int8_t weight[8] {-7, -5, -3, -1, 1, 3, 5, 7}; int32_t weighted_sum 0; int32_t sensor_sum 0; for (int i 0; i 8; i) { if (sensor[i]) { weighted_sum weight[i]; sensor_sum 1; } } if (sensor_sum 0) return LINE_LOST; // 全部丢线优先处理 // 加权和除以激活数再用100/7归一化到 [-100, 100] return (int16_t)((weighted_sum * 100) / (sensor_sum * 7)); }先除以激活传感器数量再做归一化最后误差尺度基本不随传感器安装宽度变化。误差为负表示引导线偏左误差为正表示引导线偏右。把误差叠加到速度环输出上就成了转向策略int16_t base_pwm (int16_t)pid_speed_update(...); // 前进基量 int16_t steer (int16_t)(kp_line * err / 100); // 转向修正量kp_line默认给40左右 set_left_pwm(base_pwm steer); set_right_pwm(base_pwm - steer);base_pwm保证车有底部推进力steer在左右轮之间产生差速。需要注意的是基量不能太低否则转弯时内侧轮接近停止甚至反转整车会横着蹭而不是顺着弧线走。3.4 什么时候该给转向加PID误差直接乘以比例系数在高速时会抖动。我的经验是赛道直道多、弯道半径大直接用比例转向就够了如果赛道是连续S形弯道可以对误差再做一次PID但此时转向Kp要缩到原来的1/4到1/5并且积分必须限幅否则过弯后的残余积分会让车在直道上反复横摆。注意速度环和转向环不要共用同一个PID实例。两个环的采样周期和输出范围都不同掺在一起调参时参数互相污染现场会浪费大量时间。4. 爬坡调参顺序与现场排错跑完一圈就能确认方向直接上手改PID参数范围拉太大容易跑飞。按顺序调问题会按层次暴露出来。这一章的顺序是我在赛前调车时固定使用的。4.1 先标定电机死区与最大PWM每个电机的机械特性和驱动电路不同存在一个“死区电压”PWM占空比低于某个值电机根本不转。直接测一下最快// 电机死区标定从0%开始每300ms加1% for (int duty 0; duty 20; duty) { set_left_pwm(duty); set_right_pwm(duty); delay_ms(300); }观察OLED上的编码器计数从0变成非0的那个duty值就是实测死区。代码里set_left_pwm若直接收百分比死区值要作为偏置加到最终输出上。漏掉这一步低速段的PWM输出全部落在死区之内小车的起步会一顿一顿的爬坡时尤其明显。4.2 PID整定顺序先速度环再方向环最后加停车我的调参顺序永远是先速度环、后转向环。速度环的整定方法是给定固定目标速度让车在平台上直线跑观察编码器反馈速度与目标值的偏差。先设Kp10, Ki0, Kd0逐步增大Kp直到响应快、停车后无明显振荡再补一点Kd抑制过冲最后加小量Ki消除稳态误差。方向环在速度环稳定后再调。先设转向Kp1让车沿引导线走稳步加到不抖为止。顺序反过来的后果很典型方向环Kp偏大时车在直道上一路蛇形你分不清是转向太灵敏还是速度环没跟上。停车逻辑放在最后调。常见错误是用固定延时判断“到了没”这在电池电压变化后完全不成立可靠做法是用编码器累计距离设一个目标距离值到点就切换状态机。4.3 爬坡启动的斜坡策略很多源码包在发车瞬间直接给目标速度。若坡道起始就在起步点附近扭矩不够会打滑。更稳的做法是把目标速度做成斜坡每10ms抬一小格static int32_t target_speed_ramp(int32_t final_speed) { static int32_t cur_target 0; int32_t step final_speed / 50; // 50步爬到目标速度 if (cur_target final_speed) { cur_target step; if (cur_target final_speed) cur_target final_speed; } return cur_target; }每10ms调用一次约0.5秒到达全速。同时把加速度计的三轴倾角解算结果作为坡道判据检测到俯仰角从0变为非零时说明车头已接触坡道此时把目标速度切到爬坡速度避免在坡底犹豫减速。4.4 现场故障快速诊断表调试时遇到的绝大部分问题都能归到下面几类按顺序查比盲目改参数有效得多。现象可能原因优先检查点上电后程序运行但电机不转驱动板使能脚被配置成低电平有效检查H桥的EN引脚电平宏爬坡中途失速后退电池电压跌落速度环输出已限幅提高PWM上限或降低目标速度巡线左右甩尾转向环Kp过大输出饱和缩小转向Kp检查转向输出限幅停车位置每次差5cm以上停车检测用了固定时间未结合速度积分距离改用电编码器累计距离停车OLED数值全不变I2C地址不对或总线卡死扫描I2C地址确认器件地址调车时建议在车上留一个无线串口或OLED日志口周期性打印当前目标速度、实测速度、巡线误差。没有这些内部数据光看车跑遇到问题永远只能靠猜。5. 把2020电赛ProblemC源码包移植到新板子的快捷路径如果赛题编号换到别的年份任务里仍带“引导线定点停车”这些元素这个源码包能不能复用能但必须先把代码按层切开。5.1 代码中间层的划分方法原则是把源码分成三层驱动层只与芯片和板子有关控制层与车模有关但与题目相对独立任务逻辑层与题目相关。PID速度环、巡线误差算法属于控制层是最值得原样保留的代码电机、编码器、I2C驱动属于驱动层换板子就重写驱动状态机和停车判据属于任务逻辑层换题就改这一层。新板子的验证顺序应该按层来先烧一个“驱动层自检”程序确认电机、编码器、巡线传感器都能正确读写再单独验证控制层最后才跑完整赛题逻辑。每层独立验证出问题能立刻定位到楼层。5.2 移植和验证的检查清单验证驱动改动时要一项一项来别一次换掉所有东西。下面这个自检函数能同时暴露接线方向、编码器极性和传感器状态的问题void driver_selftest(void) { // 左右轮反向转动验证电机方向和编码器符号 for (int i 0; i 2; i) { set_left_pwm(20); set_right_pwm(-20); delay_ms(500); set_left_pwm(-20); set_right_pwm(20); delay_ms(500); } // 打印编码器增量确认方向一致 for (int i 0; i 10; i) { oled_printf(enc_l%d enc_r%d\r\n, read_encoder(MOTOR_L), read_encoder(MOTOR_R)); delay_ms(100); } }如果左右轮反向转动时编码器读数一边正一边负说明A/B相接反了先把接线修正再进控制层。这个函数只验证驱动层不涉及任何PID参数跑不过就修硬件不要绕过它。5.3 遇到zip解压失败时的安全处理现场流传的源码包偶尔会出现“error read zip archive”或解压到一半报错。别急着去找zip密码破解工具先确认下载完整性比对文件大小、用7-Zip的测试功能检查压缩包还不行就找发件人要原始压缩包。很多解压失败只是压缩软件版本过旧升级到新版7-Zip就能解决。源码包真有缺文件的情况对照硬件自己补驱动层反而更快不值得在损坏的压缩包上反复耗时间。移植完成后最后一步是回到底层参数重新核对引导线宽度、坡道角度、停止线位置与源码中参数值的对应关系。每个参数都以现场实测为准源码里的默认值只负责让车先动起来。把这一条写进调试记录连续跑五次的停车误差落在同一个区间内再谈下一步优化。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。