🌟 前言与题目痛点分析
在2024年电赛(TI杯)H题或者历年的小车类题目中,**"循迹"**永远不是一条平滑的实线那么简单。出题人最喜欢在赛道上设置的障碍包括:
-
直角/锐角弯(常规PD算法极易冲出赛道)。
-
断线/虚线/全白区域(探头全白,小车瞬间"瞎眼"原地打转)。
-
光照干扰与赛道反光(探头读出乱码状态)。
今天,我们来深度解析一份直指国奖/高分 的底层逻辑代码。这份代码运行在TI MSPM0系列单片机上,核心亮点在于:巧妙结合了6路红外/灰度传感器与MEMS陀螺仪,在有线时精准压线,在断线全白时利用陀螺仪锁定航向角进行"交替盲走"!
🏗️ 核心架构:外环位置 + 内环速度(串级PID)
从小车的物理控制逻辑来看,代码采用了经典的串级控制思路:
-
内环(速度环): 读取左右轮编码器,使用 pid_A 和 pid_B 确保电机在电池电压波动、负载变化时,依然能稳稳输出目标转速。
-
外环(位置/方向环): 根据【循迹误差】或【陀螺仪角度误差】,计算出一个转向补偿量 turn_adj,叠加到基础速度上。
公式表达为:
左轮目标速度 = 基础速度 + 转向补偿量 turn_adj
右轮目标速度 = 基础速度 - 转向补偿量 turn_adj
🔍 亮点一:教科书级别的 6路循迹状态机设计
很多新手写循迹,喜欢写无数个 if...else,不仅运行效率低,一旦遇到噪点就容易跑飞。
我们来看看代码中的 TrackCar_GetError() 是怎么处理的:
1. 二进制位掩码与查表法
探头读回来的状态被压缩成了一个 6-bit 的字节(例如 0x20 代表最左边压线,0x0C 代表中间两路完美压线)。通过 switch-case 直接查表,速度极快!
2. 细腻的权重划分(PD控制的基础)
代码把赛道偏差分成了 7个级别 (从微调的 ±0.5 到极限边缘的 ±4.5)。
特别注意:代码处理了"3路同时压线"的情况(如 0x1C, 0x18, 0x38)。因为实际比赛的胶带很宽,小车在转弯时经常会同时压住3个探头,这种细腻的过渡能极大消除车身的"画龙"抖动。
3. 最强抗干扰:状态记忆(软件滤波)
codeC
default:
// 遇到不在字典里的状态(噪点)
// 【不进行任何赋值操作】,直接 break
break;
遇到反光读出类似 101010 的畸形状态怎么办?直接忽略! 此时返回的 car->previous_error 会保持上一次的正常偏差值。这就相当于加了一个物理惯性滤波器。
🧭 亮点二:杀手锏------基于陀螺仪的"交替盲走"逻辑
这是整份代码最精彩的地方!处理"断线全白"区域(如过十字路口、虚线或特定的90度盲转区)。
在 TIMER_0_INST_IRQHandler (1000Hz定时器) 中,代码做了一个优雅的判断:
codeC
if (sensor_status != 0x00) {
// ---------- 状态 A:有黑线,正常循迹 ----------
was_on_track = true;
float trk_err = TrackCar_GetError(&myCar);
turn_adj = (track_Kp * trk_err) + track_Kd * (trk_err - last_track_err);
// ...
} else {
// ---------- 状态 B:全白,准备盲走 ----------
// 1. 刚刚冲出黑线的瞬间触发
if (was_on_track == true) {
was_on_track = false;
// 目标角度交替翻转逻辑 (0 -> 105 -> 0 -> 105 ...)
if (current_target_yaw == 0.0f) {
current_target_yaw = 105.0f; // 考虑到机械过冲,设定为105度补偿
} else {
current_target_yaw = 0.0f;
}
}
// 2. 陀螺仪 PD 角度控制
float yaw_err = current_target_yaw - mySensor.yaw;
turn_adj = (yaw_Kp * yaw_err) + yaw_Kd * (yaw_err - last_yaw_err);
}
逻辑拆解:
-
脱线瞬间的捕捉: 利用 was_on_track 标志位,代码精准捕捉到了小车从有线变为全白的这一个瞬间。
-
交替打角: 比赛中常常要求小车走到断头路后转90度寻找下一条线。代码直接将目标角度 current_target_yaw 在 0° 和 105° 之间切换。(注:设为105°而不是90°,通常是作者实测时为了克服轮胎打滑或超调而加的经验补偿值!)
-
无缝切入角度环: 此时外环立刻由"灰度PD"接管为"陀螺仪角度PD"。小车不会像无头苍蝇一样自旋,而是笔直地(或按固定角度)往前冲,直到探头再次压线,切回循迹模式。
⚠️ 亮点三:老兵的避坑细节(工程素养)
如果只懂算法不懂底层硬件,电赛也是拿不到奖的。main.c 中隐藏了几个极其珍贵的排错经验:
-
上电防乱码延时:
codeC
// 等陀螺仪彻底启动稳定,避开上电乱码期 Delay_ms(1000);MEMS芯片上电有初始化和内部校准时间(通常几百毫秒)。如果不等它就绪直接开串口中断,很容易接收错位导致死机。
-
清空串口接收 FIFO:
codeC
while (DL_UART_Main_isRXFIFOEmpty(UART_1_INST) == false) { DL_UART_Main_receiveData(UART_1_INST); }开启中断前,狂读寄存器,把里面复位时的垃圾乱码全扔掉,确保第一帧数据绝对干净。
-
硬件错误自恢复:
codeC
default: // 遇到溢出或帧错误时,把垃圾数据读出来,清除硬件错误! DL_UART_Main_receiveData(UART_1_INST); break;UART通信极易产生 Overrun Error。一旦溢出标志位置位,很多单片机会永远卡死在中断里。在 default 分支里读一下寄存器,可以自动清除溢出标志,实现系统自愈。
-
停车清空I项(防溜车/疯跑):
codeC
// 强行清空 PID 的积分项 (I项),防止停车溜车 pid_A.integral = 0.0f; pid_B.integral = 0.0f;停止循迹时,不仅要让PWM输出为0,还要清空积分器。否则小车被卡住时积分器疯狂累加,下次启动瞬间会像脱缰野马一样飞出去。
💡 总结与启发
面对电赛 H题,这份代码给出了完美的答卷:
不惧虚线、不怕断头路、底层驱动极其强健。
对于即将参加后续比赛的同学们,建议大家把这套 "查表法循迹 + 陀螺仪补盲 + 串级PID控制" 的框架当作自己的祖传代码固化下来。在赛场上,只需要根据实际赛道调节 track_Kp(循迹灵敏度)和 yaw_Kp(转角灵敏度),就能从容应对90%的自动行驶题目!
创作不易,如果这份源码解析对你的电赛/毕业设计有启发,欢迎:
👍 点赞 ,给坚持开源分享的博主一点鼓励!
⭐️ 收藏 ,写不出循迹逻辑时拿出来抄作业!
💬 评论,有任何关于MSP0配置、PID调参的问题,随时在评论区交流!
祝大家代码一遍跑通,竞赛直取国一!🏆