☰
2024电赛H题自动行驶小车:MSPM0G3507+JY61P工程实现与避坑指南
2026/10/18 14:22:11 网站建设 项目流程

简介:针对2024年全国大学生电子设计竞赛H题开发的自动行驶小车完整工程源码,采用德州仪器MSPM0G3507作为主控,配合JY61P九轴姿态传感器与OLED显示模块,完整实现姿态解算、双电机差速转向和速度控制、运行状态实时显示等功能。资源包为zip格式,共五十个文件,压缩后仅一百四十六KB,包含C源文件、头文件、SysConfig外设配置、CCS调试配置、Makefile构建脚本和readme说明文档,文件按驱动层、控制层、串口协议层和字模数据模块组织,目录清晰,便于直接导入工程编译运行。目前已有六十五人学习参考,适合电子设计竞赛备赛、嵌入式课程设计原型及运动控制入门实践。工程已预置可用的调试配置与说明文档,开发者无需从头搭建环境即可验证;JY61P串口解析与OLED显示联动,可直观监测姿态角度,PWM驱动部分亦给出调速与转向调整参照,整体是一份开箱即用、可继续扩展的赛题方案。

1. 2024电赛H题自动行驶小车:从选题到工程的完整闭环

如果你准备做2024电赛H题自动行驶小车,却又不想在MSPM0G3507的底层配置上耗掉比赛的大半时间,那么这个方案的核心价值就是:把MSPM0G3507主控、JY61P姿态传感器、OLED显示模块整合成一套能直接改、直接跑的工程框架。H题考的是小车的循迹稳定性、路口识别准确率和姿态反馈实时性,不是考你从零写寄存器。整车的控制逻辑可以拆成三块:传感器采集、决策状态机、电机执行,其中JY61P负责把车身姿态告诉你,OLED负责把关键参数实时显示在车顶上,需要用时扫一眼即可。这篇笔记按我实际做过的方案来讲——硬件怎么接、代码怎么组织、参数怎么整定、哪些地方容易翻车,照着搭完,剩下的就是针对题目细则调策略。

2. 系统拆解与硬件选型:先把车搭成能跑的状态

2.1 整车架构与主控资源分配

电赛H题自动行驶小车通常要求沿指定路径行驶、路口识别并按规则转向或停车。常见的做法是四轮或两轮差速底盘,配合灰度循迹传感器、编码器测速、JY61P做姿态反馈,OLED做人机交互显示。整车电源用两节18650串联(7.4V),经稳压模块分出5V给电机驱动、3.3V给主控和传感器,这是最稳妥的供电方案,不要试图让主控直接带电机。

MSPM0G3507的资源分配是整套工程的地基。我的分配方式如下:

  • 核心时钟80MHz,SysTick做系统时基,控制周期定为10ms;
  • 灰度传感器接ADC通道,连续采样取均值,避免单次采样抖动;
  • 电机驱动用定时器输出两路PWM,分别控制左右轮,频率设为10kHz,避免 audible noise;
  • JY61P接UART串口,波特率115200,每10ms读一帧姿态数据;
  • OLED走I2C,因为显示刷新不需要高频,I2C能省GPIO。

这里的核心逻辑是:把实时性要求高的任务(循迹、电机控制)放在定时器中断里,把显示这样非实时任务放到主循环。很多同学把OLED显示也塞进中断,结果循迹周期被拉长,小车过弯明显迟钝。

2.2 MSPM0G3507的引脚分配与初始化清单

用TI的SysConfig工具可以图形化配置引脚和外设,生成初始化代码,但这不代表你可以不管引脚冲突。以下是这套方案里实际用到的引脚分配表,供你对照自己的板子检查,避免I2C和UART引脚复用冲突这种低级问题。

LF —— 左灰度传感器 ADC 输入(请按实际板子改为对应引脚) RF —— 右灰度传感器 ADC 输入 PWM_L —— 左电机 PWM 输出 PWM_R —— 右电机 PWM 输出 SDA —— OLED I2C 数据线(上拉4.7k) SCL —— OLED I2C 时钟线(上拉4.7k) TX —— JY61P 数据接收(主控RXD,注意交叉连接) RX —— JY61P 指令发送(主控TXD)

初始化顺序上,先初始化时钟和GPIO,再初始化UART、I2C、ADC、定时器,最后初始化OLED和JY61P的通信握手。OLED的SSD1306驱动初始化序列比较固定,建议直接复用已验证的驱动代码,不要自己对着数据手册抠初始化时序,这块真的很容易卡住,还不好查。

2.3 JY61P与OLED:一个讲数据,一个讲状态

JY61P是一款六轴姿态传感器,内部融合了加速度计和陀螺仪的数据,输出的是已经解算好的欧拉角,不需要你在MCU上再跑卡尔曼滤波。它通过串口上报,数据帧格式固定,解析起来非常直接。选它而不是裸的MPU6050,是因为电赛环境下时间最贵,姿态融合算法看起来简单,调起来全是坑,直接买融合好的模块可以省至少两天。

OLED在这里不是装饰。比赛调试时,你需要实时看到当前灰度值、车体偏角、电机PWM占空比、路口状态。没有OLED,你只能靠串口助手连电脑看数据,但车在跑的时候线不能一直拖着,所以OLED的作用是让这些关键参数跟随小车走。实际调试时我会在OLED上轮流显示三屏:传感器原始值、控制状态、姿态角度,用按键切换。屏幕不大,一屏塞三行信息刚好。

3. 核心控制代码:循迹、PID与路口状态机

3.1 灰度传感器ADC采集与归一化

H题小车循迹最常用的是灰度传感器,原理是黑线反射率低、白底反射率高,ADC读到的值不同。但同一个传感器在不同环境光、不同地面材质下的原始值差异很大,所以代码里必须做归一化和阈值自适应。我一般会先让小车静止在黑白分界处采集两个参考值,存到全局变量里,运行时实时映射到0~100的归一化值。

uint16_t adc_read_raw(void) { uint16_t sum = 0; for (uint8_t i = 0; i < 8; i++) { sum += adc_read_channel(LEFT_SENSOR_CH); } return sum >> 3; // 8次平均,抑制单次采样噪声 } int16_t sensor_normalize(uint16_t raw, uint16_t black_ref, uint16_t white_ref) { int32_t val = (raw - black_ref) * 100 / (white_ref - black_ref); if (val < 0) val = 0; if (val > 100) val = 100; return (int16_t)val; }

这段代码的逻辑是:ADC采集8次取平均,降低单次抖动;然后以黑参考值为0、白参考值为100做线性映射。参数说明:black_ref和white_ref需要在小车静止时实测更新,如果场地光线变化明显,可以在程序里增加按键校准逻辑,一键重新采样。别忘了黑线在白底上的实际归一化值通常在10~30之间,如果传感器离地太高导致黑白反差不足50,就需要调整安装高度。

3.2 差速转向:位置式PID参数与限幅

自动行驶小车的转向控制我用的是位置式PID,输入是左右灰度归一化值的差,输出是左右轮PWM的补偿量。为什么用差值作为误差?因为差值天然消除了共模干扰——当两个传感器同时受环境光影响,差值变化远小于绝对值变化。PID参数一般从P开始调,先给一个较小的P让车能沿直线走,再逐渐加大到过弯不丢线,最后加一点I来消除稳态偏差。

float pid_control(int16_t err) { static float integral = 0; float p_out = KP * err; integral += err * DT; if (integral > INT_LIMIT) integral = INT_LIMIT; if (integral < -INT_LIMIT) integral = -INT_LIMIT; float i_out = KI * integral; float d_out = KD * (err - last_err) / DT; last_err = err; return p_out + i_out + d_out; }

这段代码是典型的位置式PID。需要注意几点:DT是控制周期,必须和实际定时器中断周期一致,否则微分项会失真;INT_LIMIT限幅非常关键,积分饱和会让小车在长直线末端猛打方向;last_err必须用全局变量,中断里和主循环里同时调用时要注意临界区保护。

uint16_t pwm_left = PWM_BASE + (int16_t)pid_out; uint16_t pwm_right = PWM_BASE - (int16_t)pid_out; if (pwm_left > PWM_MAX) pwm_left = PWM_MAX; if (pwm_left < PWM_MIN) pwm_left = PWM_MIN; if (pwm_right > PWM_MAX) pwm_right = PWM_MAX; if (pwm_right < PWM_MIN) pwm_right = PWM_MIN;

PWM_BASE是基础占空比,一般设在电机刚启动不堵转的临界值偏上,比如40%。左右轮加上方向相反的补偿量,差速转向就出来了。PWM_MAX和PWM_MIN要分别限制上限和下限,下限防止占空比太低电机不转,上限防止电流过大。

这里分享一个血泪经验:两路PWM的占空比标称一样,实际电机转速可能差很多,这是电机制造误差,不是代码问题。所以PID的I项才有存在的价值——它在长直线上慢慢补偿两轮差异。

3.3 路口判断与状态机设计

H题的路口类型通常有直行、左转、右转、停车,有些年份还有环形路口。路口判断不能靠单帧数据,要用连续多帧确认,否则小车在正常循迹时稍微抖动就会被误判成路口。状态机是这套代码的控制核心,我的实现思路是:正常循迹态、路口试探态、路口动作态、停车态,四个状态互相迁移。

typedef enum { STATE_TRACK, // 正常循迹 STATE_INTERSECT, // 探测到路口,确认中 STATE_ACTION, // 正在执行转向/停车 STATE_STOP // 停车完成 } car_state_t; car_state_t state = STATE_TRACK; void state_machine_update(void) { switch (state) { case STATE_TRACK: if (intersect_count >= 3) { state = STATE_INTERSECT; intersect_count = 0; } break; case STATE_INTERSECT: // 连续确认路口类型,执行转向动作 if (intersect_confirmed) { state = STATE_ACTION; turn_start_ms = millis(); } break; case STATE_ACTION: // 转向动作执行完成后回到循迹 if (millis() - turn_start_ms > turn_duration_ms) { state = STATE_TRACK; } break; case STATE_STOP: // 停车等待,不复位 break; } }

这段代码的思路是:只有连续3次检测到路口特征才进入确认态,路口类型(左、右、直、停)在确认态里根据灰度传感器的组合模式判断。实际调试中,路口类型的误判多源于车头过路口时角度不正,导致传感器同时压到两条边线。我的对策是:在路口前提前减速,把车速从40%降至20%,让传感器有更多的时间采样判断。turn_duration_ms这个参数没有通用值,需要根据你的舵机或差速转速实测,我常用的起点是350ms,然后每50ms一档去试。

4. 接好JY61P与OLED:姿态数据进控制环

4.1 JY61P串口数据帧解析与校验

JY61P默认通过串口输出角度数据,输出的帧结构中包含帧头、Yaw、Pitch、Roll以及校验。写解析代码时,一个关键点是先检测帧头,再在帧内按固定偏移取数据,最后必须做校验,否则跳变的数据帧会把姿态角带偏。

uint8_t rx_buf[32]; uint8_t rx_len = 0; int16_t parse_angle(uint8_t *buf, uint8_t offset) { int16_t raw = (buf[offset] << 8) | buf[offset + 1]; return raw / 32768.0f * 180.0f; // 按模块手册换算成角度 } int jy61p_parse_frame(uint8_t *buf, uint8_t len) { if (len < 11 || buf[0] != 0x55 || buf[1] != 0x53) { return -1; // 帧头不匹配 } uint8_t sum = 0; for (uint8_t i = 0; i < len - 1; i++) sum += buf[i]; if (sum != buf[len - 1]) return -2; // 和校验失败 roll = parse_angle(buf, 2); pitch = parse_angle(buf, 4); yaw = parse_angle(buf, 6); return 0; }

这段代码的逻辑是:先看帧头0x55 0x53,再对整帧做8位累加和校验,校验通过才解析角度。参数说明:JY61P的角度数据是16位有符号数,大端字节序,最高位是符号位,换算公式是 raw / 32768 * 180。这个模块在快速转动时有短暂的数据延迟,不用过度滤波,但可以在解析后加一个简单的滑动平均,窗口大小3~5帧即可,窗口太大转弯响应会变慢。

4.2 姿态角怎么用:转向补偿与坡道检测

姿态角在H题小车里不只用来显示,我做两件事:

第一,转弯时用Yaw角做转向闭环。灰度循迹的本质是跟踪路径,但在路口转弯时灰度线可能短暂丢失,这时候用Yaw角做开环转向的补偿非常有效。转弯时我不断读取Yaw角,和目标角度对比,角度误差小于阈值就认为转向到位,回到循迹态。这个方案比单纯等固定时间更稳,因为电池电压变化会影响电机转速,固定时间的转向角度漂移很严重。

float yaw_start, yaw_error; void turn_by_angle(float target_yaw) { yaw_start = get_yaw(); while (1) { float current_yaw = get_yaw(); float delta = current_yaw - yaw_start; if (delta > target_yaw - 5 && delta < target_yaw + 5) { break; // 误差小于5度即到位 } } }

注意,yaw计算跨越±180°边界时,直接做差会出现从170°到-170°这种跳变,必须做角度归一化处理。我一般会写一个wrap_180函数,把误差限制在-180到+180之间,这是姿态角参与控制时最容易踩的坑,没有之一。

第二,用Pitch角检测坡道。如果题目场地有坡道,Pitch角会明显偏离水平位置,这时候可以加大PWM占空比来维持车速,避免上坡动力不足导致停车。

4.3 OLED显示:三屏轮播与关键数据刷新

OLED采用SSD1306驱动,128x64像素,I2C接口。显示架构我分成三层:底层是驱动层,提供画点、画线、画字符函数;中间是界面层,管理三个屏的绘制逻辑;顶层是数据层,把当前需要显示的变量填入对应界面。

在代码里,刷新策略比绘制函数本身更容易影响体验。OLED刷新频率低,全屏重绘一次大约需要20~30ms,所以不能在主循环里高频调用绘制。我的做法是:每200ms刷新一次当前屏幕,每2秒自动轮换屏幕,防止单屏数据长时间停更。

显示的三个屏分别是:

  • 屏1:左右灰度归一化值(两位整数)、PWM占空比,用于循迹调试;
  • 屏2:Yaw、Pitch、Roll角度(保留一位小数),用于姿态调试和坡道检测;
  • 屏3:当前状态机状态名、最近一次路口类型、总行驶距离(编码器累计值),用于比赛时快速判断小车是否按规则执行。

这个显示方案看起来简单,实际帮了大忙。比赛场地上你不能连电脑,所有参数调整都靠这块小屏幕反馈,它的价值在调试时体现得淋漓尽致。

5. 避坑指南:这套小车最常见的5个翻车现场

5.1 现象:JY61P回传角度缓慢漂移,Yaw角越跑越偏

原因:JY61P的Yaw角由陀螺仪积分得到,模块静止时积分漂移会逐渐累积。另一个常见原因是模块没有固定牢固,高速转弯时产生的振动传导给传感器,积分误差被放大。解决:先把模块用双面胶加扎带双重固定,尽量减少振动;然后在代码里增加静态漂移校准——在程序启动后采集20帧角度取平均作为零偏基准,减掉这个基准再参与控制。如果比赛允许,每圈回到起点时还可以按键清零Yaw角,这个功能非常实用。

5.2 现象:小车直线行驶莫名跑偏,但左右PWM设置完全相同

原因:这个现象基本排除PID问题,根源在电机本身。两个电机即使型号相同,实际转速-电压特性也有差异,再加上轮胎直径的微小差异,直线行驶会出现固定方向的偏航。解决:在PID控制中加入I项的积分补偿,或者做一次开环标定——把小车架起来,让两轮空转,调整PWM使两侧实测转速一致,测速可以用编码器或者干脆用手机慢动作拍轮子数圈数。这个标定值存成全局偏移量,每次启动加载。

5.3 现象:OLED显示白屏,I2C扫描不到设备地址

原因:这块我用过几款OLED模块,最常见的翻车点是I2C地址不对。SSD1306的地址可能是0x3C也可能是0x3D,取决于模块上地址电阻的配置。另一个原因就是上拉电阻缺失,I2C总线悬空导致通信失败。解决:先写一个I2C扫描程序,把总线上所有设备地址打印到串口,确认实际地址后修改驱动宏定义;检查SDA和SCL是否加了4.7k上拉电阻到3.3V;最后确认OLED的VCC供电是3.3V不是5V——有些模块标注5V兼容,实际接5V会让I2C电平不匹配主控。

5.4 现象:灰度传感器在光线变化时突然丢线,小车冲出赛道

原因:固定阈值的判黑逻辑在环境光变化时失效。比如从灯光区跑到阴影区,白底的ADC读数可能下降30%,原来判为白色的值现在被判成黑色。解决:必须在程序启动时做阈值自校准。我用的方法是在小车上电后读取当前环境下的黑白参考值,并实时用归一化映射替代固定阈值判断。同时每次进入状态机循环时,用最近50帧的灰度最大值和最小值动态更新白参考和黑参考,实现慢速自适应。这样即使比赛场地灯光变化,传感器也能自动调整。

5.5 现象:小车在高速转弯时突然复位重启

原因:电机启动瞬时电流大,如果电源稳压方案不合理,会把主控供电拉到复位阈值以下。很多人只用了一个LM1117-3.3线性稳压,这种方案在电机电流冲击下电压跌落严重。解决:电源方案改为DC-DC降压模块给电机驱动供电,主控供电单独用低 dropout 的 LDO,或者在电池输出端并联一个大容量电解电容(470~1000μF)和一个小容量的陶瓷电容。关键原则是电机回路和主控回路要分开走线,在PCB或洞洞板上从电池端就分叉,不要共用一段细导线。

6. 进阶技巧:用姿态数据做转向闭环,再也不会在路口走歪

H题拿到省奖和国奖的差距,往往不在基础循迹,而在路口处理的精确度和鲁棒性。这个方法是我调了三天车以后总结出来的:把JY61P的Yaw角从显示专用变成控制闭环的一部分,替代固定延时转向。

具体做法是,在路口确认类型后,记录当前Yaw角作为起始角,根据路口类型设定目标Yaw偏移量(左转90度、右转90度、直行0度、掉头180度),然后进入闭环转向态。每10ms读取一次当前Yaw角,计算和目标角度的差值,用这个差值做一个小比例控制,输出转向PWM补偿量。当差值绝对值小于5度时,判定转向完成,切回循迹态。

while (abs(yaw_diff) > 5) { current_yaw = get_yaw(); diff = wrap_180(current_yaw - target_yaw); turn_pwm = (int16_t)(diff * TURN_KP); set_motor_pwm(-turn_pwm, turn_pwm); delay_ms(10); } set_motor_pwm(0, 0);

这段代码里最关键的是wrap_180归一化。如果不做这个处理,当角度跨过±180度边界时,误差值会突变360度,转向控制瞬间反向,小车会原地打转。这是个真翻车场景,我在实验室调了整整一个下午才定位到这个问题。

转弯速度参数TURN_KP我建议从0.5开始试,如果转向过程中车头来回振荡,说明增益偏大,往下调;如果转到90度位置后停止但角度还没收敛,说明增益偏小或死区偏大。同时要限制最大转向PWM值,防止角度误差大时满占空比瞬间打死方向超出目标角度。

转向闭环还需要关注执行时机的细节:从进入路口到角度开始变化之间存在转向机构的响应死区。所以我在闭环转向开始前先给一个200ms的强制转向信号,让车头先动起来,然后再切入比例控制。这两个阶段的衔接做顺了,路口转向角度误差可以控制在正负3度以内,这个精度在电赛场地是大有优势的。

验证这个方法是否适合你的车,有一个简单测试:让车连续跑10次同一个直角弯,记录每次的Yaw结束角并求标准差。标准差小于3度说明转向一致性很好,如果标准差偏大,优先检查机械结构(轮轴松动、轮胎打滑)而不是调参数。机械问题不是代码能弥补的,这点是我带过好几个项目后最深的感受——先确认硬件稳定可靠,再谈算法优化,顺序反了只会白费力气。

希望这篇笔记能帮你把H题小车从图纸变成赛道上的稳定运行机器。硬件方案按这个搭不会有大坑,代码结构对着改就能用,真正需要花时间的地方在PID参数整定和路口策略微调,那些只能靠实跑累积数据慢慢磨。

本文还有配套的精品资源,点击获取

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

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

立即咨询