ARTICLE DETAIL

资讯详情

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

电赛H题智能小车方案:MSPM0G3507+8路灰度+MPU6050完整复盘

电赛H题智能小车方案:MSPM0G3507+8路灰度+MPU6050完整复盘 2024年电赛H题赛场上我们组的小车在第一轮测试时差点直接冲出赛道——不是算法不对而是8路灰度传感器里有两路装反了左转信号被识别成了右转。当时旁边队伍用的STM32加5路灰度方案调参调到凌晨也没能把急弯稳定过完。说实话H题考的核心不是谁会调库而是面对一套具体硬件你能不能快速把任务拆清楚、把传感器数据用好、把状态机理顺。这篇文章把我们参赛的方案完整复盘一遍主控用TI的MSPM0G3507感知用8路灰度加MPU6050从任务拆解、硬件搭建、算法原理到现场踩坑全部摊开讲。产品组和准备参加下一届电赛的同学都可以直接拿这套思路做参考底稿。1. 电赛H题任务拆解方案设计的出发点1.1 从赛题要求反推核心功能模块2024年H题智能小车这类赛题第一件事不是写代码而是把赛题要求拆成一张功能清单。我们当时拿到题目后先画了一个粗糙的流程小车从起点发车沿引导线行驶途中要经过直道、急弯、坡道最后在指定区域停车。部分年份还会加避障、语音播报、LED显示之类的附加项。拆完之后清单就变成了这样沿地面引导线稳定行驶必须这是基础分在弯道处不冲出赛道必须这是区分度所在坡道识别与动力补偿加分/必须否则坡道容易停住终点停车判定必须不能跑过终点附加功能障碍物规避、显示、播报视题目要求这五项拆完之后方案的核心矛盾就暴露了整个系统的关键不是MCU算得有多快而是传感器能不能在各种光照、坡度、速度条件下稳定给出可信的车辆状态。MCU只是执行者传感器数据质量才是决定成败的环节。所以我们在主控选型上没有过于纠结性能参数而是围绕外设够用、开发快、稳定三个标准来选。最终选了TI的MSPM0G3507。1.2 为什么选择MSPM0G3507 8路灰度 MPU6050这套组合先说主控MSPM0G3507。这是一颗基于ARM Cortex-M0内核的MCU主频最高80MHzFlash有128KBSRAM有32KB。M0内核听起来不如M3/M4高端但在小车场景里完全够用——因为我们的核心控制频率就是100Hz到400Hz真正吃算力的姿态解算也消耗不了多少资源。关键是它集成了一些对小车特别友好的外设12位ADC多通道采集灰度传感器的模拟量可以直接喂进来省掉外部ADC。多个16位高级定时器支持PWM输出和正交编码器接口电机调速、测速一条龙。多路I2C、UART、SPIMPU6050和蓝牙模块同时挂也没压力。官方SDK提供了完整的驱动库函数寄存器操作被封装得很好。再说传感器。8路灰度负责地面感知MPU6050负责车身姿态感知。这两者组合起来覆盖了小车在赛道上的两个核心状态我在线的什么位置灰度我当前是平路还是在坡道、有没有侧翻倾向姿态。对比同场很多队伍用的5路灰度方案8路的优势不是在数量上多三个数字而是覆盖宽度更大、冗余度更高。赛道引导线一般是2cm左右的黑线5路传感器如果间距没算好车身稍微偏一点就容易出现全线失守的情况8路传感器可以做更密的排布我们用的是15mm间距无论车身怎么偏至少有两三路传感器能压到线上。这套组合从根上降低了丢线这个最致命故障的发生概率。2. 硬件搭建车体结构、安装布局与电源系统2.1 车体选型与传感器布局间距、高度与遮挡车体我们用的是四轮底盘前轮转向通过差速实现也就是左右轮独立驱动靠PWM给不同占空比实现转向。这种底盘结构简单、转向半径小在急弯场景下比前轮舵机转向更灵活。车体上的关键不是车本身而是传感器的安装位置和方法。灰度传感器安装安装在车底前方越靠前越好这样留给控制算法的反应时间越长。我们装在前轮轴线前方5cm左右的位置。传感器底面距离地面高度控制在1cm到1.5cm之间。高于2cm红外反射信号衰减严重阈值很难调低于0.5cm遇到路面轻微起伏容易剐蹭。8个传感器排成一条垂直于车头方向的直线间距15mm总覆盖宽度约105mm。引导线宽度2cm理论上车身左右偏移45mm以内都不会丢线。注意传感器上方必须加遮光罩。灰度传感器本质是红外反射强度检测环境光里的红外成分会对结果造成极大干扰。我们是直接用黑胶带把传感器模块的侧面缝隙封住的实测在强光直射和阴暗处切换时阈值漂移从原来的不可用变成了基本稳定。MPU6050安装固定在车体几何中心偏前一点的位置要求安装面水平。如果装歪了后续所有角度数据都会有一个固定偏置需要额外做校准才能消除。用双面泡沫胶贴好后再用扎带加固防止高速运动时脱落。电赛测试场地难免有颠簸传感器松动带来的数据漂移比器件本身的噪声更让人崩溃。2.2 电源设计一块电池喂饱所有模块电源是整个系统里最容易被忽视、又最容易出问题的环节。很多队伍小车跑着跑着突然复位十有八九是电源跌落。我们的供电方案分三级电源链路11.1V锂电池 → 降压模块 → 5V母线 → 再经LDO → 3.3V给MCU和传感器。具体参数如下电池3S锂电池标称11.1V容量2200mAh。电机驱动直接采用TI的DRV8833或者TB6612FNG这类驱动芯片宽压输入可以直接从电池取电内部稳压逻辑处理好后给电机供电。TB6612FNG我们验证下来比一体化的L298N模块效率高不少发热小而且体积小得多。5V母线用MP1584降压模块把电池电压降到5V给舵机、无线模块、OLED屏这类外设用。3.3V电源MCU、灰度传感器、MPU6050统一从3.3V取电用AMS1117-3.3把5V降下来。这个系统里最关键的规范是电机驱动的地线和MCU地线必须在电源输入端单点汇合不要形成环路。我们在初版PCB上因为地线布局不合理电机一启动MCU的ADC就跳字后来把地线重新走了星形拓扑才解决。3. 8路灰度循迹从原始信号到方向偏差的完整计算链3.1 灰度传感器的检测原理与阈值标定灰度传感器内部是一个红外发射管加一个红外接收管。发射管持续发射红外光光线照射到地面后反射回来接收管根据反射光强度输出不同的电压/电流信号。黑线吸收红外光多、反射弱白底反射强由此区分黑与白。我们的灰度模块输出的是模拟电压信号接入MSPM0G3507的ADC引脚。ADC采集后得到的数值范围大概是0到409512位分辨率然后需要设定一个阈值把连续的模拟值二值化为0或1低于阈值为检测到黑线高于阈值为白色路面。这个阈值不是随手定的。我们写了一个简单的标定代码小车静止放在赛道上分别读取8路传感器压在白底和黑线上的ADC值各采样50次取平均阈值取两者的中间值。每到一个新场地必须先做一遍这个操作。这一步看起来基础但它是整个巡线算法的基石。阈值标定不准后面所有逻辑全是白搭。我们到赛场后第一件事就是重新标定阈值原因很简单不同场馆的灯光色温不同地面材质不同红外反射特性差异极大。3.2 加权平均算法把8个0/1变成一个连续偏差值8路灰度传感器二值化之后我们得到的是一个长度为8的数组比如[0,0,0,1,1,0,0,0]。如果只是判断有没有看到线那这个数组足够但要做PID控制我们需要一个连续变化的偏差量——车偏左了偏差值是负多少偏右了是正多少。我们用的是加权平均法。给8个传感器分别赋予权重-3.5、-2.5、-1.5、-0.5、0.5、1.5、2.5、3.5对应传感器索引0到7左侧为负、右侧为正然后计算position Σ(index_i × value_i) / Σ(value_i) error position - 3.5其中index_i是传感器索引value_i是二值化后的0或1。如果检测到黑线的传感器是第3路和第4路索引3和4则position (3×1 4×1) / (1 1) 3.5 error 3.5 - 3.5 0偏差为0表示线正好在车身正中间。如果只检测到第2路position 2 error 2 - 3.5 -1.5偏差为负说明线偏左车需要向左修正。这段计算的精髓在于即使同时多路传感器触发也能算出一个平滑的中心位置不会出现数字跳变。这就为后续PID控制提供了连续、稳定的输入量。还有一个细节如果8路全为0全线失守我们不返回0而是保留上一次的偏差值。这是因为在急弯处车身可能因为离心力短暂偏离引导线如果此时把偏差归零PID会把车子当成完美居中结果越跑越偏。保留上次偏差配合稍后要说的MPU6050转向辅助能让车子盲走一小段把线找回来。3.3 PID控制律设计比例、积分、微分的分工偏差计算出来了接下来就是控制。我们用的是增量式PID输出控制量加在左右电机的PWM占空比上。base_speed 45%占空比直道巡航速度 correction Kp × error Ki × ∫error dt Kd × d(error)/dt left_pwm base_speed correction right_pwm base_speed - correction三个参数各司其职Kp比例决定响应速度。太小了车子转弯迟缓太大了车子会左右震荡。我们从0.2开始试逐步加到0.6左右比较合适。Ki积分消除稳态误差。在长弧弯道处单纯比例控制会有固定的跟踪误差积分项负责把这个误差磨掉。但积分项有一个需要特别注意的地方必须加限幅。我们在调试时遇到过一个问题积分项累积到非常大弯道结束进入直道后车还在持续大角度转向直接冲出了赛道。给积分限幅我们限到±20%占空比之后这个问题立刻消失。Kd微分)抑制超调。在进入弯道的瞬间偏差变化率很大微分项会提前打一个反向修正让车的转向更平稳。Kd的副作用是放大噪声所以它对数据平滑度要求高我们是配合滑动平均滤波一起用的。调参顺序有讲究先把Ki和Kd置0只调Kp让车子能走但可能震荡再加大Kd消除震荡最后加Ki修正弧线误差。这样一步步来比盲目调三个参数容易得多。4. MPU6050在比赛车上的真实用途不只是一颗姿态传感器4.1 数据获取与预处理MPU6050的配置与校准MPU6050是一颗六轴传感器内部集成三轴陀螺仪和三轴加速度计通过I2C接口与MSPM0G3507通信设备地址是0x68或0x69取决于AD0引脚的电平。初始化配置主要做三件事设置陀螺仪量程。我们设成±500°/s这个量程范围对小车来说足够分辨率比±2000°/s档位高很多急弯时车身角速度一般也就一两百°/s完全不会溢出。设置加速度计量程为±2g。坡度检测需要读重力加速度在各轴的分量±2g的灵敏度最高测角精度更好。配置数字低通滤波器。MPU6050内部自带数字低通滤波器我们设置为20Hz滤掉高频振动噪声。上电后第一件事是校准零偏。陀螺仪的零偏是真实存在的即使传感器静止不动输出的角速度也不是0而是有一个固定偏置。我们把小车水平静止放置连续采集陀螺仪2000个样本取平均值作为零偏之后每次读取时减去这个值。这个过程必须在每次上电后执行因为零偏会随温度漂移。加速度计的校准也类似但更简单静止时三轴加速度的平方和应该等于1g²如果偏差大说明安装面歪了或者传感器有问题。4.2 实际应用一坡道检测与动力补偿坡道是电赛小车最常见的杀手。很多队伍的小车在平路上跑得飞快一到坡道就减速甚至停住原因是电机输出扭矩不够或者PWM占空比太小。我们的方案是提前检测坡道在进入坡道前就提前加大动力而不是等车轮转速掉下来再反应。怎么检测坡道加速度计的X轴和Z轴数据可以算出俯仰角pitch atan2(acc_y, acc_z) × 180 / π平路上重力加速度主要落在Z轴垂直于地面Y轴分量接近0所以 pitch 接近0度。上坡时车身抬头Y轴分量增大Z轴分量减小pitch 逐渐变大。我们设定当 pitch 8° 持续100ms判定为进入坡道此时把base_speed从45%提升到65%同时把Kp调大一点提高转向响应。坡道上的巡线和平地略有不同因为车身倾斜时灰度传感器的离地高度会改变检测到的数值会整体偏移阈值需要做动态修正。我们实测发现坡度15°时灰度值比平地上偏大约30%所以二值化阈值需要降低20%左右否则会出现坡道上全线报白的假丢线。下坡时更危险。车速会因为在重力作用下持续加快如果不限制速度容易在坡底冲出赛道。我们的策略是检测到俯仰角为负且绝对值超过8°说明在下坡把最大PWM占空比限制在50%同时利用PID的微分项提前准备转向修正。4.3 实际应用二静止判断与终点停车判定电赛H题通常会要求小车在指定区域停车。用灰度传感器检测停车线是一种方法但在某些赛题设定中停车区并没有明显的色块标记。这时候MPU6050提供的信息就非常有用。我们的停车判定逻辑分为两步第一步速度骤降检测。正常行驶时陀螺仪的Z轴角速度在一个范围内波动因为车在转向加速度计X轴和Y轴也有持续的微小变化。当小车进入停车程序并开始制动时加速度计会在极短时间内检测到一个明显的反向加速度脉冲。第二步静止确认。停车后陀螺仪三轴角速度会收敛到0附近经过零偏校准后我们连续采样50个周期每个周期5ms如果50个周期内三轴角速度绝对值都小于3°/s就确认车已经停稳。这个逻辑比单纯依赖灰度检测停车线要稳健得多。因为停车线可能在光照变化下检测不稳定而MPU6050的静止判断完全不依赖地面特征只关心车本身的状态。我们在调试中还发现了一个衍生用途当小车在弯道处因为速度太快出现侧翻倾向时MPU6050的横滚角roll会快速增大。我们在逻辑中加了一个保护当roll 30°时立即切断动力。这在现场测试中至少救了我们两次。5. 软件架构与关键代码实现5.1 基于状态机的整体程序结构小车程序不能写成一坨顺序执行的while循环因为比赛过程中会有很多个状态启动、寻线、进坡、出坡、停车、结束。我们用的是状态机架构。状态定义如下状态含义触发条件行为START启动等待上电/按键等待2秒自检LINE_FOLLOW常规巡线灰度正常PID巡线UPHILL上坡中pitch 8°持续100ms提升动力、修正阈值DOWNHILL下坡中pitch -8°持续100ms限速、加强转向STOPPING停车程序检测到终点/收到停车指令减速至停止HALT停车完成角速度持续为0锁定车轮、亮灯主循环的工作很简单查状态 → 执行对应状态的控制逻辑 → 更新输出。所有传感器数据的采集放在定时器中断里以5ms为周期200Hz保证控制和采集严格同步。主循环里只做状态判断和逻辑切换不直接读传感器。这套架构的好处是显而易见的任何时刻程序只关心当前状态应该做什么不会出现在巡线状态下误触发了停车逻辑这种问题。而且比赛现场如果要临时调整逻辑只需要改某一个状态的处理函数不会牵连其他部分。5.2 核心代码灰度偏差、姿态更新与状态机整合下面给出我们代码里最核心的几个函数片段基本可以照着抄。灰度偏差计算// 8路灰度ADC原始值 uint16_t gray_adc[8]; // 全局变量保存最近一次偏差用于丢线保持 float last_error 0.0f; // 二值化阈值每到一个新场地需要重新标定 uint16_t threshold[8] {2048, 2048, 2048, 2048, 2048, 2048, 2048, 2048}; // 计算巡线偏差 float calc_line_error(void) { float sum 0.0f; float weighted_sum 0.0f; uint8_t active_cnt 0; for (uint8_t i 0; i 8; i) { // 低于阈值认为检测到黑线 if (gray_adc[i] threshold[i]) { // 权重-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5 float weight (float)i - 3.5f; weighted_sum weight; sum 1.0f; active_cnt; } } // 全部丢线保留上次偏差 if (active_cnt 0) { return last_error; } float position weighted_sum / sum; // 连续位置 last_error position; // 保存 return position; }MPU6050数据读取与俯仰角/角速度计算// 陀螺仪零偏上电校准 float gyro_zero[3] {0.0f, 0.0f, 0.0f}; // 读取MPU6050原始数据并换算 void mpu6050_update(float *gyro, float *acc, float *pitch) { int16_t raw_gyro[3]; int16_t raw_acc[3]; // 读取I2C寄存器具体寄存器地址略 mpu6050_read_raw(raw_gyro, raw_acc); // 陀螺仪±500°/s量程灵敏度65.5 LSB/(°/s) for (int i 0; i 3; i) { gyro[i] ((float)raw_gyro[i] / 65.5f) - gyro_zero[i]; } // 加速度计±2g量程灵敏度16384 LSB/g float ax (float)raw_acc[0] / 16384.0f; float az (float)raw_acc[2] / 16384.0f; // 俯仰角 *pitch atan2f(ax, az) * 57.29578f; }状态机主循环typedef enum { STATE_START, STATE_LINE_FOLLOW, STATE_UPHILL, STATE_DOWNHILL, STATE_STOPPING, STATE_HALT } car_state_t; car_state_t g_state STATE_START; float g_pitch 0.0f; float g_gyro_z 0.0f; float g_line_error 0.0f; void main_loop(void) { switch (g_state) { case STATE_START: // 启动自检等待2秒后进入巡线 if (millis() - start_time 2000) { g_state STATE_LINE_FOLLOW; } break; case STATE_LINE_FOLLOW: // 坡道检测 if (g_pitch 8.0f) { g_state STATE_UPHILL; } else if (g_pitch -8.0f) { g_state STATE_DOWNHILL; } else { // 常规巡线PID control_line_follow(g_line_error); } // 停车条件检测示例 if (check_stop_condition()) { g_state STATE_STOPPING; } break; case STATE_UPHILL: control_uphill(g_line_error, g_pitch); if (g_pitch 5.0f) g_state STATE_LINE_FOLLOW; break; case STATE_DOWNHILL: control_downhill(g_line_error, g_pitch); if (g_pitch -5.0f) g_state STATE_LINE_FOLLOW; break; case STATE_STOPPING: control_stop(); if (is_car_stopped()) g_state STATE_HALT; break; case STATE_HALT: // 车辆锁定等待复位 break; } }这套代码在MSPM0G3507上跑起来整个主循环耗时和中断开销都不大。我们实测控制周期稳定在5msCPU占用率不到30%。6. 比赛调试实录实测中踩过的坑与解决过程6.1 灰度传感器左右装反最基础也最坑的错误前面开头提到过我们第一轮测试时小车直接冲出了赛道排查了很久才发现是8路灰度中有两路传感器的引脚顺序接反了。具体表现是第2路实际对应的是物理位置第5路的信号程序里读到的偏差方向和实际位置完全相反。当时PID控制器的Kp越大车跑得越偏。排查过程很顺利但也很有代表性。我们先用一个简单测试程序把8路灰度值实时打印到OLED上然后用一块黑色纸板从传感器阵列左侧慢慢移到右侧。正常情况下应该看到从第0路到第7路逐个触发。结果画面里先是第5路触发最后才是第2路瞬间就知道问题是接线或者排线顺序错了。这个坑的教训是硬件焊接和接线完成后第一步不是跑算法而是做逐通道验证。我们从那以后定了一个规矩每块新板子焊好先用最笨的测试程序逐路点亮LED验证确认物理位置和逻辑索引一一对应再进入算法开发。这个习惯后来帮我们省了大量排查时间。6.2 MPU6050数据跳变与滑动滤波MPU6050接上后我们遇到了一个幽灵问题小车静止不动时MPU6050输出的角度会偶尔突然跳变几度然后又回到正常。先怀疑是电源干扰在3.3V上加了LC滤波也不见好再怀疑是I2C时序问题降低了I2C时钟频率到100kHz依然存在。最后用示波器同时测了MPU6050的INT引脚和电机PWM输出才发现问题根源电机的PWM开关频率正好落在MPU6050内部振动频段附近产生了机械共振。电机转动时车身有一个固定频率的微小振动MPU6050把这个机械振动当成了真实的加速度变化。解决方案分两路机械层面在MPU6050与车体之间加了一层1mm厚的硅胶垫切断高频振动的传导路径。算法层面在读取加速度计数据后加一个滑动平均滤波窗口长度取10对应50ms时间常数。这个窗口不能在模拟量采样之前加否则会引入滞后我们只对MPU6050的数据做不对灰度数据做。加了这两层处理后静止时的角度波动从±2.5°降到了±0.5°以内坡道检测的误判率大幅下降。6.3 PID积分限幅防止过弯杀的必选项调试过程中最危险的一次是小车在长弧弯道行驶时表现完美但一进入直线突然猛打方向冲出赛道。查了很长时间最后把问题定位在积分项上。长弧弯道里车子持续偏向一侧积分项不断累积出弯后引导线突然回归中央此时偏差反向了但积分项残留的旧账还在导致输出量还停留在之前的修正方向把车推向赛道外面。解决方式在前面讲PID时提过就是给积分项加限幅。这里再补充一个细节积分限幅的值不是拍脑袋定的要匹配base_speed的大小。我们base_speed是45%PWM占空比电机的最大修正量是±40%积分项如果限制在±20%以内即使出弯反打也不会出现剧烈的方向突变。如果限幅太大比如±35%积分项仍然可能压过比例项问题还会复现。6.4 电池电压下降引发的越跑越慢问题测试到第20分钟时我们发现同一套程序小车的直道速度明显下降了。用万用表一测11.1V电池的电压已经掉到9.8V左右。电机在全速转动时电压跌落严重PWM占空比虽然没变但实际加到电机两端的有效电压降低了转速自然上不去。这个问题最有效的处理方式是在程序里根据电池电压动态调整base_speed。我们用MCU的ADC采集电池电压通过电阻分压后接入在软件里做映射。电压高于10.5V时base_speed设为45%电压低于9.5V时逐步提升到52%。这样能在电池快耗尽时保持车速的一致性避免最后几个测试圈因为动力不足翻车。由于不能用升压电路大幅拉高电压效率损失和发热都是问题所以这个软件补偿方案是最务实的。7. 写在最后这套方案还能往哪些方向扩展如果下一届比赛或者类似题目要做改进我个人觉得有两个方向值得探索。第一个方向是把灰度数据和MPU6050数据做更深度的融合而不是各自独立使用。目前我们是灰度算偏差、姿态做状态切换两者在逻辑上没有交叉。实际上在急弯时如果灰度丢线了完全可以利用陀螺仪的角速度积分来估算一个虚拟位置让车辆在短暂的丢线期内保持正确转向直到重新捕获引导线。这个思路我们在调试时验证过效果很不错只是当时不敢在正式比赛时用怕代码复杂度太高出bug。如果提前做好充分测试它可能成为过急弯的一个大杀器。第二个方向是引入位置估计。MPU6050的加速度计可以用来辅助里程计配合电机编码器做一个不那么精确但够用的轨迹推算。特别是在终点停车和指定区域停车的赛题要求下提前估算出跑了多少距离、还差多少停车能让停车精度从看运气变成可预期。我们这套方案最大的价值不在于某一个传感器或者某一个算法有多先进而在于它把一个复杂赛题拆成了清晰可控的模块每个模块都有明确的输入、输出和判断逻辑。比赛时间有限能稳定跑完才是硬道理。赛场上那一堆没跑完的车大多数不是死在算法复杂度上而是死在某个细节没处理好——比如光电传感器被环境光干扰或者电池电压波动导致复位。把这些细节排查清楚你的方案就赢了一半。
返回列表