25 年电赛 E 题复盘:巡线小车踩坑记录

· 编辑: 小A · · 预计阅读 11 分钟 · 138 阅读
Lonely DanceVexento
内容已通过安全审核

这篇文章复盘的是我在 2025 年全国大学生电子设计竞赛 E 题“简易自行瞄准装置”准备过程中的一段小车巡线经历。

E 题要求装置包含自动寻迹小车和瞄准模块两部分,其中小车的巡迹和电机控制必须使用 TI MSPM0 系列 MCU。小车需要沿 100cm x 100cm 的正方形黑线逆时针自动寻迹,圈数 N 可以设定,基本要求中 1 到 5 圈可调,发挥部分还有 1 圈 20s、2 圈 40s 等时间约束。

我在这个阶段主要负责小车底盘组装和车辆调试。最开始我的目标很简单:先让车沿黑线跑起来。但实际做下来才发现,巡线小车看起来是一个基础题,真正难的地方不在于“写一个 PID”,而在于实车遇到大弯、直角弯、传感器误判、转向计数不可靠时,怎么一步一步把问题拆开。

我负责的部分

这辆小车不是只写代码。我参与最多的是偏工程落地的部分:

  • 组装和调整小车底盘;
  • 安装红外传感器并调整位置;
  • 完成电机、驱动、主控和外设接线;
  • 烧录 MSPM0G3507 程序并做实车调试;
  • 根据车辆跑出来的现象修改巡线和转向逻辑;
  • 用 OLED 状态显示辅助判断传感器、电机和计数状态。

后期代码迭代中,我也使用 AI 辅助生成和修改部分代码,但核心判断还是来自实车现象:车什么时候卡死、哪个传感器触发、转向什么时候该退出、圈数为什么会误判,这些都必须靠现场调试验证。

MSPM0G3507 开发板——密密麻麻的杜邦线连接着传感器和电机驱动
MSPM0G3507 开发板——密密麻麻的杜邦线连接着传感器和电机驱动

第一版:基础巡线能跑,但遇到大弯会卡死

最开始的巡线逻辑比较直接:红外传感器检测黑线,程序根据偏移状态调整左右轮 PWM,让车沿着线走。

在直线和小幅弯道上,这个思路基本能跑。车能识别黑线,也能根据左右偏差做一些纠正。但一遇到大弯或者接近直角弯,问题就明显了:车会卡在弯道附近,不是转不过去,就是来回摆动,最后停在一个很尴尬的位置。

当时的问题本质上是:我把“正常巡线”和“处理大弯”混在同一套逻辑里了。

普通巡线需要的是小幅度、连续的修正;而大弯需要的是更明确的转向动作。如果还用同一个 PID 逻辑去处理,车在弯道处会不断根据瞬时传感器状态修正,导致动作不够果断。

这个阶段给我的第一个教训是:巡线不是一个状态,至少要区分“正常巡线”和“正在转弯”。

第二版:改成四路红外传感器分工

后来我把红外巡线改成四路传感器,并给它们做了明确分工:

O1 | O2 | O3 | O4

其中:

  • O1、O4 负责判断是否需要进入转向;
  • O2、O3 负责稳定巡线;
  • 传感器检测到黑线为 0,检测到白地为 1。

这个改动是整个项目里比较关键的一步。因为它不是简单地“多加两个传感器”,而是把任务拆开了:

  • 中间两个传感器 O2/O3 只关心车有没有压在线附近;
  • 两侧传感器 O1/O4 只关心车是不是进入了需要大幅转向的位置。

正常巡线时,我只根据 O2/O3 计算误差:

O2O3 状态 车辆状态 处理方式
00 基本居中 直行
01 偏左 向右纠正
10 偏右 向左纠正
11 脱线 按上一次偏差方向强纠正

这样改完以后,车在直线和普通弯道上的表现更稳定了。因为 O1/O4 不再干扰细小纠偏,O2/O3 可以专注做中线判断。

第三版:转弯时屏蔽 PID,改成独立转向流程

只做传感器分工还不够。因为 O1/O4 一旦检测到大弯,接下来如果继续让 PID 参与,车还是可能在弯道处左右犹豫。

所以我后面加了一个转弯状态:

turn_state = 0  正常巡线
turn_state = 1  右转
turn_state = 2  左转

当 O1 检测到黑线时,进入左转;当 O4 检测到黑线时,进入右转。进入转弯状态后,程序暂时跳过正常 PID,直接执行转向动作。

当前代码里采用的是原地转向:

  • 右转:左侧轮前进,右侧轮后退;
  • 左转:右侧轮前进,左侧轮后退。

这比普通差速转弯更粗暴,但对直角弯更有效。因为题目的轨迹是正方形,四个角本身就需要比较明确的转向动作。相比让 PID 慢慢修,原地转向更容易让车重新对准下一段黑线。

转弯退出条件也很重要。我没有让它一触发就马上退出,而是让车进入转向流程一段时间后,再根据 O2/O3 是否重新检测到黑线来判断是否回到正常巡线。

这个逻辑可以概括为:

O1/O4 触发大弯
-> 进入转弯状态
-> 屏蔽 PID,执行原地转向
-> 等待一小段时间,避免刚触发就误判退出
-> O2/O3 重新检测到黑线
-> 恢复正常巡线

这个改动解决了“遇到大弯卡死”的核心问题。它让我意识到,很多嵌入式控制问题不是调大某个参数就能解决,而是要重新设计状态切换。

IMU 判断转弯次数的问题

后期我本来希望用 IMU 来判断转弯次数或姿态变化。理论上,如果能稳定得到角度变化,就可以更准确地知道小车转了几次弯、是否完成一圈。

但实际调试时,IMU 没有达到我预期的稳定效果。可能受安装、初始化、漂移、读取时机或者融合算法影响,它没法稳定地作为圈数判断依据。对于比赛准备来说,这类问题很现实:理论上更好的方案,如果短时间内调不稳,就会拖慢整个系统。

所以我没有继续把主要逻辑压在 IMU 上,而是换了一个更贴合赛道规则的办法:用有效转弯次数判断圈数。

第四版:用“有效转弯次数”替代 IMU 圈数判断

E 题小车轨迹是正方形,一圈基本对应四次主要转弯。既然 IMU 判断角度不稳定,那我可以反过来利用赛道结构:只要小车稳定识别并完成四次有效转弯,就可以认为它跑完了一圈。

于是我设计了有效转弯计数逻辑:

  • O1/O4 触发后,不立刻计数;
  • 只有进入转弯流程并持续一段时间后,才认为这是一次有效转弯;
  • 同一次转弯过程中只允许计数一次;
  • 目标转弯次数 = 目标圈数 x 每圈转弯数。

代码中的核心变量包括:

volatile int valid_turn_threshold = 30;
volatile int valid_turn_count = 0;
volatile int turn_count_updated = 0;
volatile int target_laps = 1;
volatile int turns_per_lap = 4;
volatile int required_turns = 0;

这套方案的好处是简单、稳定、容易解释。它不依赖 IMU 的连续角度输出,而是依赖小车已经必须完成的动作:进入转弯、执行转弯、重新回到巡线。

valid_turn_count >= required_turns 时,程序就认为目标圈数完成,然后停止电机。

这个方案也有边界:它默认赛道是一圈四个主要转弯。如果赛道形状变化,turns_per_lap 就要重新设定。但对于 E 题这种 100cm x 100cm 的正方形轨迹,它比当时没有调稳的 IMU 更可靠。

圈数设定和自启动

因为题目要求圈数 N 可以设定,所以我在程序里加入了设置模式:

  • 上电后先进入圈数设置;
  • 短按按键增加目标圈数;
  • 长按确认;
  • OLED 显示确认信息和倒计时;
  • 倒计时结束后自动启动小车。

这个功能看起来不复杂,但对实际测试很有用。比赛测试时,人不能一直干预小车,启动流程越清楚,越不容易因为操作失误浪费测试机会。

OLED 上我显示了传感器状态、系统开关、误差、PWM、当前转弯方向、有效转弯次数和目标次数。这些信息在调试时非常关键。比如车卡住时,我可以直接看它到底是没有检测到黑线,还是已经进入转弯状态但没退出。

OLED 显示屏——调试时最直观的状态窗口,显示传感器值、电机状态和转弯计数
OLED 显示屏——调试时最直观的状态窗口,显示传感器值、电机状态和转弯计数

我踩过的几个坑

1. PWM 数值和速度关系容易看反

这个项目里 PWM 数值越小,电机速度越快。刚开始如果按“数值越大速度越快”的直觉理解,就会把纠偏方向推反。

所以我后来调 PID 时,会先手动推导左右轮 PWM 的变化,再观察车轮实际动作。这个过程虽然慢,但能避免方向性错误。

2. 大弯不能只靠加大 PID 参数

遇到大弯卡死时,我一开始会想到调大 PID 或增加误差值。但调到一定程度后会发现,直线也会变得不稳定。

后来我才意识到,大弯不是普通偏差,应该切到独立转弯状态。这个认知比单纯调参更重要。

3. IMU 方案理论更好,但短期不一定最稳

IMU 用来判断姿态变化听起来更高级,但如果漂移、安装、读取和融合没有处理好,它反而会让系统更不稳定。

在比赛准备阶段,可靠性比方案“看起来高级”更重要。有效转弯计数虽然朴素,但它和赛道结构强相关,更容易在实车上跑通。

4. 传感器触发不能马上计数

如果 O1/O4 一检测到黑线就计数,很容易重复计数。小车在一个弯角附近可能连续多次触发边缘传感器。

所以我加入了“进入转弯流程一段时间后才计为有效”的条件,并用 turn_count_updated 防止同一次转弯重复计数。

5. 调试显示不是附加功能

OLED 显示一开始看起来只是锦上添花,但后面实际调车时,它几乎就是最直接的调试工具。

如果没有 OLED,我只能猜测当前状态;有了 OLED,我能看到传感器值、转弯状态和计数是否变化,这会直接提高调试效率。

代码复盘中发现的问题

后来我重新看这份代码,也发现了一些应该改进的地方。

第一,速度档位数组存在下标隐患。数组有 10 个元素,但函数里用了 speed_levels[level],如果档位范围是 1 到 10,更合理的写法应该是 speed_levels[level - 1]

第二,部分注释和实际时间不完全一致。比如主循环按 10ms 延时计算,计数阈值是多少,就应该对应多少实际时间。嵌入式项目里这类注释很重要,因为后面调参时会直接影响判断。

第三,主循环里功能较多,包括按键、传感器读取、巡线、转弯、停车和 OLED 显示。后续如果继续维护,应该把巡线控制、转弯状态机、圈数统计和显示逻辑拆开。

这些问题不影响它作为当时的比赛准备代码跑起来,但如果把它当作长期项目维护,就需要继续整理。

调试工作台全景——笔记本、小车原型、WHEELTEC 电池、摄像头,一个完整的嵌入式开发现场
调试工作台全景——笔记本、小车原型、WHEELTEC 电池、摄像头,一个完整的嵌入式开发现场

这段经历对我的意义

这个项目让我对“电机控制软件”有了更实际的理解。

以前我可能会把重点放在算法上,比如 PID 怎么写、参数怎么调。但这次实车调试让我意识到,能跑起来的系统往往不是靠某一个算法,而是靠很多工程判断叠在一起:

  • 传感器怎么安装;
  • 哪些传感器负责巡线,哪些负责转向;
  • 什么时候该屏蔽 PID;
  • PWM 和实际速度关系是否确认;
  • 计数逻辑如何避免误触发;
  • 调试状态能不能被看见;
  • 理论方案不稳定时,能不能及时换成更可靠的方案。

这也是我想把它整理成博客文章的原因。它不是一个完美项目,但它能体现我在真实小车上做过调试、遇到过问题,并且根据问题改过控制逻辑。

对我现在想投的机器人电机控制软件实习方向来说,这段经历可以证明我至少做过这些事情:

  • 使用 MSPM0G3507 做 GPIO、PWM、按键、OLED 和传感器读取;
  • 根据实车现象调整电机控制逻辑;
  • 用 PID 做基础巡线纠偏;
  • 设计转弯状态机解决大弯卡死问题;
  • 在 IMU 方案不稳定时,换成有效转弯计数方案;
  • 能把调试过程整理成可复盘的技术文档。

后续如果继续做,我会怎么改

如果现在重新做这辆车,我会优先做几件事:

  1. 把巡线、转弯、计数、显示拆成独立模块;
  2. 修正速度档位数组下标和注释时间不一致的问题;
  3. 增加串口调参能力,减少反复烧录;
  4. 给关键状态加日志,记录每次转弯触发和退出时间;
  5. 如果时间允许,再重新整理 IMU 初始化和角度读取,把它作为辅助判断,而不是一开始就依赖它;
  6. 引入编码器速度反馈,把开环 PWM 控制逐步升级为速度闭环。

从比赛准备角度看,当时的有效转弯次数方案已经能解决核心问题;从长期工程角度看,它还可以继续向更完整的电机调试平台演进。

总结

这次 25 年电赛 E 题准备中,我最大的收获不是“写了一个巡线程序”,而是学会了在真实小车上根据现象调整控制结构。

最开始车只能完成基础巡线,遇到大弯会卡死;后来我把四路红外传感器拆分为 O1/O4 判断转向、O2/O3 稳定巡线;再后来因为 IMU 判断转弯次数不稳定,又改成基于赛道结构的有效转弯计数。这个过程其实就是一次小型的工程迭代:发现问题、定位原因、降低不确定性、选择更稳的实现方式。

它不是最复杂的项目,但它让我真正接触到了嵌入式控制里的现实问题:硬件不一定稳定,传感器不一定干净,理论方案不一定马上能落地,而软件要做的是把这些不确定性控制在能完成任务的范围内。

后续我会继续把这类项目复盘整理出来,作为自己从机电背景向机器人电机控制软件方向补齐能力的过程记录。

读者来信 (共 0 条)

写下回应

还没有回应,欢迎写下第一封来信。