智能送药小车电赛实战:从OpenMV循迹到PID调参全解析
1. 智能送药小车的核心需求与设计思路参加电子设计竞赛的同学一定对智能小车类题目不陌生。2021年F题的智能送药小车就是一个典型代表它要求小车能够自主循迹、识别病房号码、准确装载和卸载药品并完成双车协同任务。这种题目看似简单实则对系统设计能力提出了全方位考验。我在实际开发中发现一个可靠的送药小车需要解决三大核心问题精准的循迹能力、稳定的转向控制以及可靠的药品装载机制。其中循迹是基础转向是关键装载是保障。这三者环环相扣任何一个环节出现问题都会导致任务失败。车型选择上三轮结构往往比四轮更灵活。我们测试过前轮舵机转向的四轮车也试过前轮万向轮的三轮车。实测下来三轮车在90度转向时表现更出色结构简单且转向半径小。特别是前轮采用牛眼轮的设计转向时几乎不会有打滑现象。2. OpenMV与灰度传感器的循迹方案对比2.1 OpenMV视觉循迹实战OpenMV作为一款嵌入式视觉模块在循迹方面有多种实现方式。最常用的是色块追踪和二值化处理。我们的方案是通过OpenMV检测黑色引导线计算中心偏移量作为误差信号。# OpenMV简单循迹示例 import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time 2000) while(True): img sensor.snapshot() # 二值化处理 img.binary([(0, 64)]) # 寻找最大色块黑线 blobs img.find_blobs([(0, 64)], pixels_threshold100, area_threshold100) if blobs: max_blob max(blobs, keylambda b: b.pixels()) # 计算中心偏移量 err max_blob.cx() - img.width()/2 # 通过串口发送误差值 print(err)这个误差值就是PID控制的输入。在STM32端我们建立了位置式PID控制器float location_pid_realize(_pid *pid, float actual_val) { pid-err pid-target_val - actual_val; pid-integral pid-err; pid-actual_val pid-Kp * pid-err pid-Ki * pid-integral pid-Kd * (pid-err - pid-err_last); pid-err_last pid-err; return pid-actual_val; }2.2 五路灰度传感器方案详解相比OpenMV灰度传感器方案更稳定可靠受光照影响小。我们使用的是五路灰度模块中间三路用于循迹两侧用于识别十字路口。// 五路灰度传感器误差计算 float PID_output(void) { if ((L21)(L10)(M0)(R10)(R20)) pid_track.err -3; else if ((L21)(L11)(M0)(R10)(R20)) pid_track.err -2; else if ((L20)(L11)(M0)(R10)(R20)) pid_track.err -1.5; // 其他状态判断... else pid_track.err 0; pid_track.output pid_track.Kp * pid_track.err pid_track.Ki * pid_track.integral pid_track.Kd * (pid_track.err - pid_track.err_last); return pid_track.output; }两种方案各有优劣OpenMV优势在于灵活性可以同时完成数字识别等任务灰度传感器响应更快稳定性更好适合高速场景实际比赛中建议准备两套方案根据现场环境灵活选择3. PID参数调试的实战技巧3.1 理解PID各参数的作用很多同学调参时很盲目其实应该先理解每个参数的意义Kp比例项决定系统对当前误差的反应强度。太小会导致响应迟缓太大会引起振荡Ki积分项消除稳态误差。但过大会导致超调严重Kd微分项预测误差变化趋势抑制振荡。但对噪声敏感我们的经验值是先调Kp再调Kd最后调Ki。具体步骤将Ki和Kd设为0逐步增大Kp直到小车开始轻微振荡保持Kp不变逐步增加Kd抑制振荡最后加入少量Ki消除稳态误差3.2 串级PID在转向控制中的应用单纯的循迹PID往往不够我们采用了串级控制外环位置环计算期望速度内环速度环控制电机实际转速// 串级PID实现 float outer_pid location_pid_realize(location_pid, position_err); float inner_pid speed_pid_realize(speed_pid, outer_pid - actual_speed);这种结构使系统响应更快抗干扰能力更强。调试时要先调内环再调外环确保内环响应速度远快于外环。4. 三种转向方案的深度解析4.1 开环转向的快速实现开环转向最简单直接通过实验测量出90度转向对应的脉冲数void Car_turn(int pluse, int mode, int pwm) { while(1) { if(mode 1) Load_Motor_PWM(-pwm, pwm); //左转 else Load_Motor_PWM(pwm, -pwm); //右转 if(pathLenth pluse) break; //达到目标脉冲数 } Load_Motor_PWM(0, 0); //刹车 }实测发现2000个脉冲约等于90度转向。这种方案简单可靠但受电池电量、地面摩擦等因素影响较大。4.2 闭环串级转向的精准控制更精准的方案是使用编码器反馈的位置环float position_pid position_pid_realize(pos_pid, target_angle - current_angle); float speed_pid speed_pid_realize(spd_pid, position_pid - current_speed);计算原理根据车轮直径和编码器分辨率计算每转脉冲数90度转向对应1/4圆周长的脉冲数通过PID使实际脉冲数跟踪目标值4.3 MPU6050的航向角控制使用陀螺仪可以直接测量转向角度float Angle_pid_realize(_pid *pid, float actual_val) { pid-err pid-target_val - actual_val; // 处理角度环绕问题 if(pid-err 180) pid-err - 360; if(pid-err -180) pid-err 360; // PID计算... return pid-actual_val; }这种方法最精准但需要处理好角度环绕问题比如从350度转到10度时。5. 双车通信与系统集成5.1 通信协议设计要点我们采用自定义数据包协议b3 b3 data1 data2 data3 5bb3 b3帧头data1-3有效数据5b帧尾void Bluetooth_Receive_Data(int16_t data) { static u8 state 0; if(state0 data0xb3) state1; else if(state1 data0xb3) state2; // 其他状态处理... else if(state5 data0x5b) { state0; Bluetooth_Data(); //处理有效数据 } }5.2 系统联调的常见问题在实际集成时最容易出现的问题循迹和转向的配合转向后要确保小车回到轨道中心数字识别的误判建议加入校验机制比如连续3帧相同才确认双车协同的时序主车完成卸货后再发信号让从车动作调试时要分模块测试确保每个功能单独工作正常后再进行集成。特别要注意电源稳定性我们曾因为电池电压下降导致电机出力不足小车无法完成转向。