记得第一次接触五路循迹小车是在大三的智能车竞赛备赛期间。当时为了赶进度,我特意选了门"水课",躲在教室最后一排捣鼓STM32F4开发板。现在回想起来,那段边上课边调试的日子真是既紧张又充实。
五路循迹小车的核心硬件其实很简单:
硬件连接花了我一个下午的时间,最头疼的是五路循迹模块的安装位置。经过多次测试,我发现模块间距最好控制在2-3cm,离地高度保持在1cm左右。这个距离既能保证检测精度,又不会因为地面反光导致误判。
初始的代码思路参考了CSDN上的一篇博客,用的是经典的switch-case状态机。传感器检测到黑线输出0,白线输出1,五个传感器的状态组合成5位二进制数。比如01110表示中间三个传感器检测到黑线,说明小车正对赛道中央。
c复制u8 Get_Infrared_State(void) {
u8 state = 0;
state = (u8)(GPIO_ReadInputData(TRACK_PORT) >> 5) & 0x001f;
return state;
}
这个状态读取函数很巧妙,通过位操作一次性获取五个传感器的状态。我当时觉得这个设计简直完美,直接照搬到了自己的代码里。
硬件组装完成,代码也"借鉴"得差不多了,我信心满满地把程序烧录进开发板。按下启动键的瞬间,期待中的流畅运行没有出现,取而代之的是一个"帕金森"症状的小车——它不停地抽搐、抖动,活像个喝醉的老头。
问题出在哪里?我检查了所有硬件连接,测试了每个传感器的输出,甚至重新焊接了所有接口,但问题依旧。那段时间我几乎住在了实验室,连做梦都是小车抽搐的画面。
经过三天三夜的调试,我终于发现了问题根源:switch-case的状态判断方式存在严重缺陷。当小车快速移动时,传感器状态可能在两个case之间快速切换,导致电机控制信号不断跳变。比如从00111(右偏)到01111(急右转)的过渡过程中,电机会经历停止-加速-停止的循环,这就是"帕金森"现象的由来。
c复制switch(state) {
case 0b00111:
Motor_Speed_Adjust(STARTER_SPEED-400,STARTER_SPEED+400);
break; //右偏3级
case 0b01111:
Motor_Opposite(L);
break; //右偏4级
...
}
更糟糕的是,这种状态机的设计把传感器数据当作整体处理,忽略了每个传感器的独立特性。实际调试中发现,中间三个传感器的权重应该更大,而边缘两个传感器更适合作为紧急纠偏的触发条件。
发现问题后,我决定彻底重构代码。新的方案采用if-else条件判断,单独处理每个传感器的状态组合。这个改动看似简单,实则需要对传感器响应特性有深入理解。
首先,我重新设计了传感器数据读取方式。不再使用位操作打包数据,而是单独读取每个传感器的值:
c复制LED_1 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2);
LED_2 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3);
LED_3 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4);
LED_4 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5);
LED_5 = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6);
然后,根据实际赛道测试结果,我总结出几个关键状态判断规则:
对应的代码实现如下:
c复制if(LED_3==0 && LED_2==1 && LED_4==1) {
runCarLittleLeft(); //轻微左偏
} else if(LED_3==0 && LED_2==0 && LED_4==1) {
runCarLeft(); //中度左偏
} else if(LED_5==0) {
runCarHardLeft(); //紧急左转
}
...
这种判断方式虽然代码量增加了,但响应更加精准。我还引入了状态记忆机制,当检测到连续多次相同偏移时,会逐步加大纠偏力度,避免突然的方向变化导致小车失控。
算法重构只是第一步,真正的挑战在于参数调试。记得有整整一周,我每天下午都泡在实验室,拿着秒表和笔记本,一遍遍测试不同速度下的循迹效果。
几个关键参数的调试经验:
调试过程中最头疼的是弯道处理。90度直角弯需要特殊处理,我的解决方案是:
这个方案经过三十多次测试才最终确定,成功率能达到85%以上。有时候小车会在弯道"思考人生"(原地停顿),这时候需要适当增加电机启动加速度。
c复制void runCarHardRight() {
// 右急转处理
PWM_SetDuty(MOTOR_L, 100); //左电机全速
PWM_SetDuty(MOTOR_R, 0); //右电机停止
HAL_Delay(800); //保持800ms
}
调试过程中最深的体会是:理论再完美,也需要实际测试来验证。有时候明明逻辑正确的代码,实际运行就是不如预期。这时候需要耐心观察小车的每一个动作,找出其中的规律。
经过基础调试后,我又尝试了几种优化方案,效果显著:
加权平均算法:给中间传感器分配更高权重
c复制int weighted_state = LED_1*1 + LED_2*2 + LED_3*3 + LED_4*2 + LED_5*1;
if(weighted_state > 6) runCarLeft();
else if(weighted_state < 3) runCarRight();
动态速度调整:直道加速,弯道减速
c复制if(LED_1==1 && LED_5==1) {
// 直道状态
current_speed = min(max_speed, current_speed+5);
} else {
// 弯道状态
current_speed = max(min_speed, current_speed-10);
}
异常状态处理:增加超时机制和错误恢复
c复制if(++error_count > 50) {
// 持续50次异常状态后复位
runCarStop();
HAL_Delay(1000);
error_count = 0;
}
这些优化让小车在正式比赛中表现出色。最终版本不仅能够流畅循迹,还能在直道上达到1.5m/s的速度,过弯成功率提升到90%以上。
在三个月的小车调试过程中,我积累了不少"血泪教训",这里分享几个典型问题:
传感器误触发:可能是环境光干扰,解决方法有:
电机响应迟缓:检查以下几点:
电源干扰:表现为随机复位或传感器读数异常
调试中最重要的是保持耐心。有时候改一个参数要测试几十次,记录每次的表现,找出最优解。建议准备一个调试日志本,记录每次修改的内容和效果。
这个五路循迹小车项目让我对嵌入式开发有了更深的理解:
硬件与软件的协同:不能只关注代码,硬件特性同样重要。比如红外传感器的响应时间、电机的启动电压等,都会直接影响软件效果。
实时性考量:嵌入式系统对响应速度有严格要求。我的初版代码就是因为状态判断不够及时,导致控制滞后。
调试技巧:学会使用LED、串口等辅助调试手段。我在关键节点加了LED指示,通过不同闪烁模式判断程序状态。
问题分解能力:遇到复杂问题要会拆解。小车跑不好可能是机械、电路、算法等多方面原因,要逐一排查。
这些经验对我后续做更复杂的嵌入式项目帮助很大。现在回头看,那个在实验室熬夜调试的夏天,是我技术成长最快的时期。