如果你正在准备电赛,特别是控制类题目,那么“双车跟随”这个经典赛题你一定不陌生。它看似简单——让一辆车跟着另一辆车走,但真正动手时,你会发现从传感器选型、算法调试到系统联调,每一步都暗藏玄机。很多队伍卡在精度和稳定性上,最终只能拿到基础分,与高分甚至满分失之交臂。
2022年电赛B题“双车跟随”就是一个典型。它要求两辆智能车在特定赛道上实现稳定的跟随,对距离、速度、路径的协同控制提出了极高要求。传统的解决方案往往依赖单一传感器(如超声波)和简单的PID控制,虽然能跑起来,但抗干扰能力弱,弯道易丢车,得分天花板很低。
今天这篇文章,要分享的是一套经过实战检验、旨在冲击满分的“新版双车跟随套餐”。这套方案的核心思路,不是堆砌最贵的硬件,而是通过多传感器信息融合与分层控制策略,在有限的成本和开发周期内,实现系统性能的最大化。我们将从系统架构拆解开始,深入到代码实现、参数整定和现场调试技巧,让你不仅“做得出来”,更能“做得漂亮”,稳稳拿下高分。
1. 为什么传统方案拿不到高分?问题本质分析
在深入新方案之前,我们必须先搞清楚,评分细则到底在考什么,以及旧方案为什么乏力。
1.1 电赛“双车跟随”的评分核心这类题目的评分点通常围绕以下几个维度:
- 基本功能分:前车启动,后车能跟随启动;前车停止,后车能平稳停止。这是及格线。
- 跟随性能分:这是拿高分的关键。包括:
- 距离保持稳定性:后车与前车能否在运动全程保持设定的距离(如30cm),误差越小,分数越高。
- 速度同步性:后车能否快速响应前车的加减速,且无超调或剧烈抖动。
- 弯道通过能力:在弧形赛道上,后车能否紧跟前车轨迹,不发生碰撞或偏离。
- 附加挑战分:可能包括变速跟随、绕过障碍、通信中断恢复等。
1.2 传统方案的典型瓶颈很多队伍的第一版方案是这样的:后车装一个超声波模块测距,用一个PID控制器根据距离误差计算电机PWM输出。
// 典型但脆弱的单PID控制逻辑 float distance = getUltrasonicDistance(); float error = targetDistance - distance; float pwm = Kp * error + Kd * (error - lastError); setMotorSpeed(pwm); lastError = error;这个方案的问题非常突出:
- 传感器单一,数据不可靠:超声波易受环境声波、赛道材质干扰,数据跳动大。一旦出现几个错误数据,车就可能失控。
- 控制模型过于简单:单PID只调节距离,忽略了前后车速度的内在联系。前车急加速时,后车可能因反应滞后而距离被拉得过大;前车急减速,又极易导致追尾。
- 无“预见性”:后车完全被动反应,不知道前车即将转弯。等超声波测到横向距离变化时,为时已晚,容易在弯道出界。
- 参数调试困难:
Kp, Ki, Kd三个参数互相耦合,在直道调好了,一到弯道就振荡,顾此失彼。
因此,要想冲击满分,必须构建一个更鲁棒、更智能的系统。我们的新方案正是针对以上痛点进行设计。
2. 新版满分方案核心:多传感器融合与双环控制
新方案的核心思想可以概括为:“眼睛”要多,“大脑”要分层。
2.1 多传感器配置——“看得更准、更全”我们为后车(跟随车)配置三类传感器,进行信息融合:
- 超声波测距模块:用于测量两车之间的直线距离。这是基础。
- 红外测距或激光TOF模块:建议至少安装两个,一左一右。它们不仅能辅助测距,更重要的是可以测量横向偏移量。当两车不在同一直线上时(尤其在弯道),红外传感器能感知到偏差,为转向控制提供依据。
- 编码器(必选):安装在后车电机上。它的作用是精确测量后车自身的实际速度,实现速度闭环控制。这是提升跟随平顺性的关键。
2.2 分层控制架构——“想得更清楚”我们抛弃单一PID,采用双闭环控制结构:
- 外环(距离环):输入是
目标距离与融合后的实际距离的误差,输出是后车的目标速度。这个环负责宏观上的“跟多远”。 - 内环(速度环):输入是
外环计算出的目标速度与编码器测量的实际速度的误差,输出是直接驱动电机的PWM值。这个环负责微观上的“跑多稳”。
此外,引入转向控制环:根据左右红外传感器测量的横向偏差,计算出一个修正的转向角度或差速值,使后车在弯道也能对准前车轨迹。
这种架构将距离控制和速度控制解耦,参数调试更清晰,系统响应更快、更平稳。
3. 系统搭建与硬件连接
3.1 硬件清单
- 主控芯片:STM32F103/F407系列(性能足够,资料丰富),或ESP32(自带Wi-Fi可用于调试)。
- 电机与驱动:直流减速电机 + L298N或TB6612电机驱动模块。
- 传感器:
- 超声波模块:HC-SR04。
- 红外测距模块:GP2Y0A21YK0F(模拟量输出)或VL53L0X激光TOF(I2C数字输出,精度更高)。
- 编码器:增量式光电编码器(AB相)。
- 电源:两节18650电池组,需搭配稳压模块(如LM2596)为单片机和传感器提供稳定的5V/3.3V。
- 车体:建议使用竞赛常用的三轮或四轮小车底盘,结构稳固。
3.2 接线示意图(以STM32为例)
后车系统连接: 超声波模块:Trig -> PA1, Echo -> PA2 左红外模块:Analog Out -> PA3 (ADC通道) 右红外模块:Analog Out -> PA4 (ADC通道) 编码器A相: -> PB6 (TIM4_CH1) 编码器B相: -> PB7 (TIM4_CH2) 电机驱动:IN1 -> PB8, IN2 -> PB9 (右轮) IN3 -> PB10, IN4 -> PB11 (左轮) PWM右轮 -> PA8 (TIM1_CH1) PWM左轮 -> PA9 (TIM1_CH2)注意:前车(引导车)可以是一个简单的遥控车或按固定程序运行的车,通常只需保证其能按预定路径行驶即可。
4. 软件设计与代码实现
我们使用STM32 HAL库进行开发,代码结构清晰,易于移植。
4.1 传感器数据采集与滤波原始传感器数据必须经过滤波才能使用。对于跳动较大的超声波和红外数据,采用滑动平均滤波或卡尔曼滤波。
// sensor_fusion.c // 滑动平均滤波结构体 typedef struct { float buffer[FILTER_LEN]; uint8_t index; float sum; } MovingAverageFilter; float MovingAverage_Update(MovingAverageFilter* filter, float newValue) { filter->sum -= filter->buffer[filter->index]; filter->buffer[filter->index] = newValue; filter->sum += newValue; filter->index = (filter->index + 1) % FILTER_LEN; return filter->sum / FILTER_LEN; } // 获取融合后的距离 float GetFusedDistance(void) { float dist_ultrasonic = Filter_UltraSonic(Read_UltraSonic()); float dist_ir_left = Filter_IR_Left(Read_IR_Left()); float dist_ir_right = Filter_IR_Right(Read_IR_Right()); // 简单的融合策略:以超声波为主,红外为辅进行校验和补偿 // 如果超声波数据异常(如超出量程),则使用红外数据的平均值 if (dist_ultrasonic > MAX_ULTRASONIC_RANGE || dist_ultrasonic < MIN_RANGE) { return (dist_ir_left + dist_ir_right) / 2.0f; } // 正常情况下,可加权平均 return 0.7f * dist_ultrasonic + 0.15f * dist_ir_left + 0.15f * dist_ir_right; } // 获取横向偏差(正值表示前车偏右) float GetLateralOffset(void) { float left_dist = Read_IR_Left(); float right_dist = Read_IR_Right(); // 假设两红外模块安装距离为D // 简化计算:偏差 = (右距离 - 左距离) / 2 return (right_dist - left_dist) / 2.0f; }4.2 双环PID控制器实现我们实现两个独立的PID控制器。
// pid_controller.c typedef struct { float Kp, Ki, Kd; float integral; float prev_error; float output_max; float output_min; } PIDController; void PID_Init(PIDController* pid, float kp, float ki, float kd, float max, float min) { pid->Kp = kp; pid->Ki = ki; pid->Kd = kd; pid->integral = 0.0f; pid->prev_error = 0.0f; pid->output_max = max; pid->output_min = min; } float PID_Update(PIDController* pid, float setpoint, float measurement, float dt) { float error = setpoint - measurement; // 比例项 float proportional = pid->Kp * error; // 积分项(抗饱和处理) pid->integral += error * dt; if (pid->integral > pid->output_max) pid->integral = pid->output_max; if (pid->integral < pid->output_min) pid->integral = pid->output_min; float integral = pid->Ki * pid->integral; // 微分项(用测量值微分,对设定值变化不敏感) float derivative = pid->Kd * (measurement - pid->prev_measurement) / dt; // 注意此处变量名 pid->prev_measurement = measurement; // 需要修改结构体,增加prev_measurement // 计算总输出并限幅 float output = proportional + integral - derivative; // 注意微分项符号 if (output > pid->output_max) output = pid->output_max; if (output < pid->output_min) output = pid->output_min; return output; }注:上述PID实现中微分项采用了测量值微分,能减少设定值突变带来的冲击。结构体需增加prev_measurement字段。
4.3 主控制逻辑集成在定时中断(如10ms)中执行主控制循环。
// main_control.c // 全局变量 PIDController pid_distance; // 外环PID PIDController pid_speed_left; // 左轮速度环PID PIDController pid_speed_right; // 右轮速度环PID float target_distance = 30.0f; // 目标跟随距离 30cm float current_speed_left, current_speed_right; // 由编码器计算得到 void Control_Loop_10ms(void) { // 1. 读取并融合传感器数据 float fused_distance = GetFusedDistance(); float lateral_offset = GetLateralOffset(); // 2. 外环:距离环计算目标速度 // 距离误差越大,需要的目标速度越大(带方向) float target_speed_base = PID_Update(&pid_distance, target_distance, fused_distance, 0.01f); // dt=0.01s // 3. 内环:速度环计算PWM,并加入转向修正 // 转向修正:横向偏差越大,左右轮速度差越大 float steering_correction = lateral_offset * STEERING_GAIN; // STEERING_GAIN为转向增益系数 float target_speed_left = target_speed_base - steering_correction; float target_speed_right = target_speed_base + steering_correction; // 限制目标速度范围 target_speed_left = constrain(target_speed_left, -MAX_SPEED, MAX_SPEED); target_speed_right = constrain(target_speed_right, -MAX_SPEED, MAX_SPEED); // 4. 速度环PID更新,输出PWM float pwm_left = PID_Update(&pid_speed_left, target_speed_left, current_speed_left, 0.01f); float pwm_right = PID_Update(&pid_speed_right, target_speed_right, current_speed_right, 0.01f); // 5. 驱动电机 Set_Motor_PWM(MOTOR_LEFT, pwm_left); Set_Motor_PWM(MOTOR_RIGHT, pwm_right); }5. 参数整定与调试技巧
这是从“能跑”到“跑得好”的关键一步。务必按顺序调试:
5.1 调试准备
- 将后车抬起,车轮悬空。
- 编写测试代码,让电机以固定PWM转动,确认编码器能正确读取速度。
- 单独测试每个传感器,确认其读数准确且稳定。
5.2 内环(速度环)参数整定
- 目标:让车轮速度能快速、无静差地跟随目标速度变化。
- 方法:
- 将外环PID参数全部设为0。
- 给
target_speed_base一个阶跃信号(如从0到100 RPM)。 - 先调
Kp:增大Kp直到电机开始振荡,然后回调至振荡临界点的80%。 - 再调
Kd:加入微分抑制振荡,使速度响应曲线平滑。 - 最后调
Ki:消除静差(实际速度与目标速度的稳态误差)。Ki不宜过大,否则会引起积分饱和和超调。
- 预期效果:电机能迅速达到指定转速,且转速平稳,无抖动。
5.3 外环(距离环)参数整定
- 目标:让后车能平稳地保持与前车的设定距离。
- 方法:
- 将前车置于静止状态。
- 后车从较远/较近的位置启动,向目标距离靠近。
- 先调
Kp:增大Kp能加快接近速度,但过大会在目标距离附近来回振荡。 - 调
Kd:增加微分作用可以抑制接近目标时的超调和振荡。 Ki在距离环中通常可以设得很小或为0,因为速度环已经能消除静差。
- 技巧:在调试外环时,可以实时通过串口打印
fused_distance和target_speed_base的波形,观察系统的响应特性。
5.4 转向环调试
- 目标:在弯道时,后车能自动修正航向,跟随前车轨迹。
- 方法:
- 将两车置于平行但有一定横向偏移的位置。
- 手动固定前车,让后车缓慢前进。
- 调整
STEERING_GAIN:增益太小,纠偏慢;增益太大,会在中心线附近来回“画龙”。找到一个能平滑消除偏移的值。
6. 现场比赛策略与稳定性保障
电赛现场环境复杂,以下策略能极大提高成功率:
6.1 上电初始化与自检编写完善的上电自检程序,通过LED或蜂鸣器指示各传感器状态。
void System_Self_Check(void) { if(Read_UltraSonic() == ERROR) { LED1_Blink(3); } // 超声波故障 if(Read_IR_Left() == ERROR) { LED2_Blink(3); } // 左红外故障 // ... 其他检查 if(All_Sensors_OK) { Buzzer_Beep(1); } // 自检通过 }6.2 抗干扰处理
- 软件滤波:必须使用。卡尔曼滤波对超声波数据效果显著。
- 数据有效性判断:如果传感器读数突然跳变到物理不可能的值(如距离小于0或大于5米),则丢弃该数据,使用上一次的有效值或预测值。
- 通信冗余:如果规则允许,可以在两车间增加简单的无线通信(如蓝牙、2.4G),让前车发送自身的速度或位置信息给后车,作为传感器数据的补充和校验。
6.3 参数现场微调比赛场地光线、地面摩擦力可能与实验室不同。准备一个简单的“参数微调模式”:
- 通过按键或串口命令,可以小幅增减
Kp,Kd,STEERING_GAIN等关键参数。 - 将几组针对不同场景(强光/弱光、光滑/粗糙地面)调好的参数保存在Flash中,现场一键切换。
6.4 安全保护机制
- 急停开关:必须设置硬件急停开关,防止意外。
- 软件限速:无论计算出的PWM多大,最终输出必须限制在一个安全范围内。
- 近距离保护:当融合距离小于某个危险值(如10cm)时,强制后车刹车。
7. 常见问题与故障排查
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 后车完全不动 | 1. 电源未接通或电压不足 2. 电机驱动模块使能端未拉高 3. 主控芯片未正常启动 | 1. 用万用表测量各点电压 2. 检查驱动模块ENA/ENB引脚 3. 检查单片机复位电路和晶振 | 1. 充电或更换电池 2. 将使能引脚接高电平 3. 重新下载程序,检查Boot引脚 |
| 后车剧烈振荡或“画龙” | 1. 速度环或距离环Kp过大2. 微分项 Kd过小或为03. 传感器数据噪声太大 | 1. 观察控制量输出波形 2. 打印传感器原始数据 | 1. 降低Kp,适当增加Kd2. 加强传感器滤波(如改用卡尔曼滤波) |
| 弯道跟随偏差大,撞到赛道边缘 | 1. 转向环增益STEERING_GAIN太小2. 红外传感器安装角度或高度不合适 3. 弯道速度过快 | 1. 观察横向偏移量计算是否正确 2. 实测红外传感器在不同距离和角度下的输出 | 1. 增大转向增益 2. 调整传感器安装,使其正对前方 3. 在弯道处通过程序适当降低目标速度 |
| 前车加速时,后车距离被迅速拉大 | 距离环响应太慢,Kp太小,或速度环已达到输出上限 | 打印target_speed_base和实际速度 | 1. 适当增大距离环Kp2. 检查电机功率和驱动电流是否足够 3. 提高速度环的输出限幅(需在电机安全范围内) |
| 编码器测速不准 | 1. 编码器线数设置错误 2. 定时器编码器模式配置错误 3. 机械连接松动 | 1. 手动转动车轮一圈,查看计数值 2. 检查TIMx的编码器接口配置 | 1. 在代码中修正编码器每转脉冲数 2. 检查GPIO配置是否为浮空输入 3. 紧固编码器与电机轴的连接 |
8. 进阶优化与拿分点
在基础功能稳定后,以下优化能让你在评分细节上拉开差距:
8.1 动态目标距离不是让后车死死保持30cm。当前车低速时,可以跟近一些(如25cm);前车高速时,跟远一些(如35cm),增加安全余量。这需要前车-后车通信或后车能估计前车速度。
8.2 弯道速度自适应在进入弯道前(通过检测横向偏差变化率),主动降低target_speed_base,让后车以更安全的速度过弯。
8.3 启动与停止的平滑过渡在启动和停车阶段,不要将target_distance直接设为固定值。可以设计一个“斜坡函数”,让目标距离在几秒钟内平滑过渡到设定值,避免冲击。
8.4 状态机设计将系统划分为不同的状态,如IDLE(待机)、ACCELERATING(加速跟随)、CRUISING(巡航)、DECELERATING(减速)、STOPPED(停止)。每个状态对应不同的控制参数和逻辑,使系统行为更清晰、更可控。
8.5 数据可视化与离线分析利用串口将关键数据(如融合距离、目标速度、实际速度、PWM输出)实时发送到电脑,用Python的Matplotlib绘制曲线。这是调试和优化最有力的工具,也能在答辩时向评委展示你的工作深度。
双车跟随项目是电赛中对控制系统综合能力的一次绝佳考验。它涉及感知、决策、执行的全链条。本文提供的“多传感器融合+双环PID+转向控制”方案,是一个在可靠性、性能和实现复杂度之间取得良好平衡的实战框架。它直指传统方案的软肋,通过分层的思想简化了调试难度。
真正的难点从来不是代码本身,而是对物理系统的理解和在庞杂问题中抓住关键矛盾的能力。建议你按照文章步骤,从硬件搭接、基础驱动开始,逐步增加控制环节,每步都充分测试。不要试图一次性调通所有功能。把参数整定过程当成与系统“对话”的过程,观察它对你的每一次参数调整作何反应。
最后,在比赛现场,稳定压倒一切。完成一套简洁、鲁棒的基础功能,远比追求复杂但不稳定的高级功能更有可能拿到高分。祝你备赛顺利,在比赛中稳定发挥,拿下理想的成绩。