这次我们来看一个针对全国大学生电子设计竞赛(电赛)的实战解决方案——“新版双车跟随满分套餐”,它直接对应2022年电赛B题“小车跟随行驶系统”。对于正在备赛或对智能车控制感兴趣的开发者来说,这个方案的价值在于它提供了一个经过验证、可复现的完整技术栈,从硬件选型、核心算法到调试技巧,覆盖了拿高分的关键路径。
这个“套餐”的核心不是空泛的理论,而是能让你快速搭建、跑通并优化一套稳定可靠的双车跟随系统。它重点关注如何在有限的竞赛时间内,实现前车(领航车)与后车(跟随车)之间稳定的距离与速度协同控制,并应对直线、弯道、加减速等复杂场景。本文将带你拆解这套方案的技术内核,从系统框架、硬件配置、核心算法到软件实现与调试,提供一个可落地的实操指南。
如果你正在准备电赛控制类题目,或者对基于视觉/超声波/红外等传感器的跟随系统开发感兴趣,这篇文章可以直接参考。我们将重点关注系统的可实施性、关键参数的调试方法以及如何避开常见的“坑”。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 对应赛题 | 2022年全国大学生电子设计竞赛B题《小车跟随行驶系统》 |
| 系统目标 | 实现后车对前车的自动跟随,保持设定距离,并同步完成直线、弯道、加减速、停车等动作。 |
| 核心传感器 | 通常包含测距模块(如超声波、红外、激光TOF)、图像传感器(摄像头)、编码器等。 |
| 核心控制器 | 主流采用STM32系列单片机(如F4/F7/H7)作为主控,负责传感器数据融合与运动控制。 |
| 核心算法 | PID控制(距离、速度)、图像处理(巡线、目标识别)、数据融合滤波(如卡尔曼滤波)。 |
| 通信方式 | 双车间可采用无线通信(如蓝牙、Wi-Fi、2.4G)传输状态信息,或完全依赖本地传感器实现自主跟随。 |
| 开发环境 | Keil MDK、STM32CubeMX、OpenMV IDE(若使用OpenMV)、Python(上位机调试)。 |
| 方案特点 | 模块化设计,软硬件解耦;提供关键参数调试方法;包含抗干扰与稳定性处理策略。 |
| 适合场景 | 电赛备赛、课程设计、智能车创新实践、自动跟随机器人原型开发。 |
2. 适用场景与使用边界
这个“满分套餐”方案主要服务于特定目标人群和场景:
适合谁:
- 电赛参赛学生:尤其是备战控制类赛题的队伍,需要一套经过验证的参考实现来加速开发。
- 嵌入式与机器人初学者:希望通过一个完整的项目,学习传感器应用、电机控制、PID算法和系统集成。
- 高校指导教师:寻找可用于教学或培训的典型综合实践案例。
- 智能车爱好者:想自己制作一套双车跟随系统,了解其核心技术原理。
能解决什么问题:
- 缩短开发周期:提供清晰的硬件清单和软件框架,避免从零开始的摸索。
- 明确调试方向:给出PID参数整定、传感器数据滤波、通信协议设计等关键环节的实操方法。
- 提升系统稳定性:包含应对地面反光、通信干扰、电机打滑等实际问题的处理经验。
- 瞄准评分要点:方案设计会紧扣当年赛题的评分标准,帮助在关键得分点上做到位。
不适合什么场景:
- 追求极限性能的竞赛:如专业级RoboMaster或智能车竞速赛,其对动态性能和算法优化要求更高,本方案更偏向教学与基础实现。
- 完全商业化的产品开发:方案未考虑大规模生产、成本极致优化、长期可靠性认证等工程问题。
- 纯理论研究:方案侧重工程实现与调试,对控制理论、状态估计等算法的深层数学原理探讨有限。
安全与合规边界:
- 实验应在开阔、安全的场地进行,避免在公共道路或人流量大的区域测试。
- 注意小车电池安全,使用安全的锂电池并做好充放电管理,防止短路或过热。
- 若使用无线通信,需遵守所在地无线电管理规定,使用免许可频段,并控制发射功率。
- 本方案为学术与实践参考,用于参赛需遵守竞赛规则,独立完成并注明参考来源。
3. 环境准备与前置条件
在动手之前,需要准备好软硬件环境。以下清单基于常见的电赛智能车平台配置。
硬件准备清单:
- 主控芯片:至少两片STM32系列单片机(如STM32F407/F429/F767)。F4系列性价比较高,F7/H7系列性能更强,适合处理图像。
- 小车底盘:两个,需包含电机、车轮、电机驱动模块(如TB6612、DRV8833)。
- 电源模块:7.4V或12V锂电池,搭配降压模块(如LM2596)为单片机和传感器提供5V/3.3V电源。
- 测距传感器(核心):
- 超声波模块:如HC-SR04,成本低,但易受环境声波干扰,测量频率较低。
- 红外测距:如夏普GP2Y0A系列,精度较好,但受环境光影响。
- 激光TOF:如VL53L0X/VL53L1X,精度高、响应快,抗干扰能力强,是当前优选。
- 视觉传感器(可选但推荐):用于辅助巡线或识别前车标记。
- OpenMV Cam:内置MicroPython,图像处理库丰富,开发便捷。
- OV7725等摄像头+单片机处理:成本低,但需要自行编写图像处理算法。
- 编码器:安装在电机上,用于测量车轮实际转速,实现闭环速度控制。
- 无线模块(可选):如需双车通信同步状态,可选用蓝牙模块(HC-05)、Wi-Fi模块(ESP8266)或NRF24L01 2.4G模块。
- 调试工具:ST-Link/V2下载调试器、USB转TTL串口模块、万用表、示波器(用于观察PWM波形)。
软件与环境准备:
- 集成开发环境(IDE):
- Keil MDK-ARM:用于STM32的程序开发、编译和调试。需安装对应芯片的Device Family Pack。
- STM32CubeMX:用于图形化配置单片机引脚、时钟、外设,生成初始化代码,极大提升开发效率。
- 串口调试助手:如XCOM、SSCOM,用于查看传感器数据、打印调试信息。
- 图像处理环境(如果使用OpenMV):安装OpenMV IDE。
- 上位机软件(可选):可使用Python(PyQt/PySide)或C#编写,用于实时绘制小车运动曲线、PID参数在线整定。
通用检查清单:
- [ ] 所有硬件模块供电电压核对正确(5V/3.3V)。
- [ ] 电机驱动模块的输入输出线连接牢固,无短路。
- [ ] 单片机最小系统(晶振、复位电路)工作正常。
- [ ] 下载器能正常连接并识别单片机。
- [ ] 准备一个平整、有清晰引导线(黑色电工胶带)的测试场地。
4. 系统框架设计与硬件连接
一个典型的双车跟随系统可以分为“感知-决策-控制-执行”四个层次。这里我们设计一个以本地传感器自主跟随为主,无线通信为辅的框架。
系统框架图(逻辑描述):
前车(领航车): [电机驱动] <-速度指令- [STM32主控] -> [编码器反馈] | [无线发射模块] (可选,发送状态如速度、位置) | [自身运动控制逻辑] (按预定路径或手动控制行驶) 后车(跟随车): [激光TOF/超声波] -> [距离数据] -\ -> [数据融合与滤波] -> [PID控制器] -> [PWM输出] -> [电机驱动] -> 车轮 [摄像头] -> [图像数据] -> [巡线/识别] -/ (距离环) (速度环) [编码器] -> [速度反馈] --------------------------/ [无线接收模块] (可选,接收前车状态)硬件连接关键点(以后车STM32为例):
- 激光TOF (VL53L0X):连接I2C接口(如PB6/PB7)。注意上拉电阻,并确保供电稳定。
- 电机驱动模块:PWMA、PWMB连接STM32的定时器通道引脚(如TIM1_CH1, TIM1_CH2)。AIN1/AIN2, BIN1/BIN2连接普通GPIO控制方向。
- 编码器:A、B相分别连接定时器的编码器接口引脚(如TIM3_CH1, TIM3_CH2)。
- OpenMV摄像头:通过串口(UART)与STM32通信,发送处理后的结果(如中线偏差、目标距离)。
- 无线模块:如NRF24L01,连接SPI接口和几个GPIO控制脚(CE, CSN)。
使用STM32CubeMX进行初始配置:
- 新建工程,选择你的STM32具体型号。
- 配置时钟树,将系统时钟设置到最高频率(以发挥芯片性能)。
- 引脚配置:
- 启用用于PWM输出的定时器(如TIM1),并配置对应通道为“PWM Generation CHx”。
- 启用用于编码器接口的定时器(如TIM3),模式设为“Encoder Mode”。
- 启用I2C1用于激光TOF。
- 启用UART2用于与OpenMV通信。
- 启用SPI1用于NRF24L01。
- 将控制电机方向的GPIO设为输出模式。
- 生成代码,选择MDK-ARM V5作为工具链。
5. 核心算法实现与代码解析
双车跟随的核心是控制算法。我们将距离控制作为外环,速度控制作为内环,形成串级PID。
5.1 距离PID控制
后车通过测距传感器获取与前车的实时距离distance_current,与设定的目标距离distance_target比较得到误差e_distance。
// 距离PID结构体定义 (pid.c / pid.h) typedef struct { float Target; // 目标值 float Current; // 当前值 float Error; // 当前误差 float LastError; // 上次误差 float Integral; // 积分项 float Kp, Ki, Kd; // PID参数 float Output; // 输出值(本例中作为速度环的目标速度) float IntegralLimit; // 积分限幅 float OutputLimit; // 输出限幅 } PID_TypeDef; // 距离PID计算函数 float PID_Calculate(PID_TypeDef *pid) { pid->Error = pid->Target - pid->Current; // 积分项,并做限幅防止积分饱和 pid->Integral += pid->Error; if (pid->Integral > pid->IntegralLimit) pid->Integral = pid->IntegralLimit; if (pid->Integral < -pid->IntegralLimit) pid->Integral = -pid->IntegralLimit; // 微分项(使用不完全微分可增加平滑性) float derivative = pid->Error - pid->LastError; // PID输出 pid->Output = pid->Kp * pid->Error + pid->Ki * pid->Integral + pid->Kd * derivative; // 输出限幅 if (pid->Output > pid->OutputLimit) pid->Output = pid->OutputLimit; if (pid->Output < -pid->OutputLimit) pid->Output = -pid->OutputLimit; pid->LastError = pid->Error; return pid->Output; } // 在主循环或定时中断中调用 void Control_Loop(void) { // 1. 读取当前距离(例如从VL53L0X) float current_distance = VL53L0X_GetDistance(); distance_pid.Current = current_distance; distance_pid.Target = 30.0f; // 设定目标距离为30cm // 2. 计算距离PID,输出为目标速度 float target_speed = PID_Calculate(&distance_pid); // 3. 将目标速度传递给速度环PID speed_pid.Target = target_speed; // ... 速度环计算 }5.2 速度PID控制
速度环的目标值来自距离环的输出。当前速度通过编码器测量得到。
// 编码器速度计算(在定时中断中) extern int32_t encoder_count; // 编码器计数值 int32_t last_count = 0; void TIMx_IRQHandler(void) { // 假设定时器中断频率为100Hz if (TIM_GetITStatus(TIMx, TIM_IT_Update) != RESET) { int32_t diff = encoder_count - last_count; float current_speed = (float)diff / (COUNTS_PER_REV * WHEEL_CIRCUMFERENCE) * 100; // 单位:cm/s last_count = encoder_count; speed_pid.Current = current_speed; speed_pid.Target = target_speed_from_distance_pid; // 来自距离环 float pwm_output = PID_Calculate(&speed_pid); // 将pwm_output赋值给电机PWM占空比 MOTOR_SetPWM(pwm_output); TIM_ClearITPendingBit(TIMx, TIM_IT_Update); } }5.3 数据滤波处理
传感器数据(尤其是超声波、红外)存在噪声,需要进行滤波。这里以简单的滑动平均滤波和一阶低通滤波为例。
// 滑动平均滤波(用于距离数据) #define FILTER_LEN 5 float distance_filter_buffer[FILTER_LEN] = {0}; uint8_t filter_index = 0; float Moving_Average_Filter(float new_value) { distance_filter_buffer[filter_index] = new_value; filter_index = (filter_index + 1) % FILTER_LEN; float sum = 0; for (int i = 0; i < FILTER_LEN; i++) { sum += distance_filter_buffer[i]; } return sum / FILTER_LEN; } // 一阶低通滤波(用于速度数据) float Low_Pass_Filter(float new_value, float old_value, float alpha) { // alpha = dt / (dt + RC),通常取0.1~0.3 return alpha * new_value + (1 - alpha) * old_value; }5.4 图像处理辅助(OpenMV示例)
如果使用OpenMV进行巡线,可以将其作为横向控制的参考,与距离控制结合。
# OpenMV Cam 主程序 (main.py) import sensor, image, time, pyb from pyb import UART # 初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) # 160x120 sensor.skip_frames(time = 2000) uart = UART(3, 115200) # 使用UART3与STM32通信 while(True): img = sensor.snapshot() # 1. 巡线:寻找黑线中线 line = img.get_regression([(0, 0, 0, 255, 255, 255)], robust = True) # 二值化找线 if line: img.draw_line(line.line(), color = (255, 0, 0)) # 计算中线偏差(图像中心x坐标 - 线中心x坐标) center_x = line.x1() + (line.x2() - line.x1()) // 2 bias = 80 - center_x # 假设图像中心x=80 # 2. (可选)识别前车上的色块或AprilTag # ... # 通过串口发送数据:格式如 "Bias,距离\n" data = "%d,%d\n" % (bias, distance_read_from_sensor) uart.write(data) time.sleep(10) # 控制发送频率STM32端解析OpenMV数据,可以将横向偏差bias作为一个修正量,叠加到左右轮的速度差上,实现同时跟随和巡线。
6. 软件调试与参数整定实战
算法实现后,调试是成败的关键。遵循“先独立,后联合;先开环,后闭环”的原则。
步骤1:电机与编码器基础测试
- 编写代码让电机以固定PWM值正反转,观察车轮转动是否顺畅,方向是否正确。
- 测试编码器计数:在电机空转和受阻力时,查看编码器计数值变化是否灵敏、连续。确保编码器接口配置正确。
步骤2:速度环PID整定
- 将距离环断开,直接给速度环一个固定的目标速度(如20 cm/s)。
- 先调P:将Ki, Kd设为0。逐渐增大Kp,直到电机出现“嗡嗡”声但还未开始持续振荡,此时系统响应较快但有静差。
- 再调I:逐渐加入Ki,用于消除静差。观察小车能否稳定在目标速度。注意Ki太大会引起超调或振荡。
- 最后调D:加入Kd,用于抑制振荡,使速度曲线更平滑。微分项对噪声敏感,通常取值较小。
- 通过串口将目标速度、实际速度、PWM输出等数据实时发送到上位机(如Python的Matplotlib)绘制曲线,直观观察响应过程。
步骤3:距离环PID整定
- 将前车静止放置,后车置于稍远位置。
- 速度环参数暂时固定(使用步骤2调好的参数)。
- 整定方法同速度环:先P后I再D。目标距离设为固定值(如30cm)。观察后车能否平滑地接近并稳定在目标距离处,无超调或持续振荡。
- 关键点:距离环的输出是速度环的目标值,因此距离环的输出限幅
OutputLimit应设置为一个合理的最大跟踪速度。
步骤4:联合调试与动态测试
- 让前车开始缓慢匀速运动,观察后车跟随情况。
- 测试前车加减速:后车应能及时调整速度,保持距离稳定。
- 测试前车转弯:如果系统纯靠距离控制,转弯时后车容易撞到前车内侧。此时需要引入图像处理的横向偏差进行辅助纠偏,或者使用两个测距传感器(一左一右)进行差速控制。
- 测试不同地面材质(木板、地砖、地毯)对编码器打滑的影响,必要时加入速度观测器或自适应控制。
7. 通信协议与双车协同(可选进阶)
如果采用双车无线通信方案,需要设计简单的通信协议。
NRF24L01 简单数据帧设计:前车(发射端)周期性地将自身状态(如编码器速度、舵机角度)打包发送。
// 数据包结构体 typedef struct { uint16_t speed; // 前车速度 int16_t steering; // 前车转向角(如有) uint8_t checksum; // 校验和 } CarData_Packet_t; // 发送函数(前车) void NRF24L01_TxData(void) { CarData_Packet_t tx_data; tx_data.speed = get_current_speed(); tx_data.steering = get_steering_angle(); tx_data.checksum = tx_data.speed ^ tx_data.steering; // 简单异或校验 NRF24L01_WriteTxPayload((uint8_t*)&tx_data, sizeof(tx_data)); NRF24L01_TxMode(); // 切换到发射模式并发送 } // 接收函数(后车) if (NRF24L01_RxPacket((uint8_t*)&rx_data) == RX_OK) { // 验证校验和 if (rx_data.checksum == (rx_data.speed ^ rx_data.steering)) { // 使用前车速度作为自身速度参考,或用于预测前车轨迹 // 可以结合本地传感器数据,进行数据融合(如卡尔曼滤波) } }后车可以利用收到的前车速度信息,实现更精准的速度跟随,甚至提前预判前车运动趋势。
8. 常见问题与排查方法
在开发过程中,你几乎一定会遇到以下问题。这里提供排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 电机不转或只振动 | 1. PWM频率不对 2. 电机驱动使能信号错误 3. 电源功率不足 | 1. 用示波器看PWM波形和频率(通常10kHz-20kHz) 2. 检查驱动芯片的IN/EN引脚电平 3. 测量电池电压带载后的跌落 | 1. 调整定时器分频和重载值 2. 核对驱动芯片真值表 3. 更换容量更大的电池或降低电机电流 |
| 编码器计数不准或不动 | 1. 编码器AB相接反 2. 定时器编码器模式配置错误 3. 硬件接触不良 | 1. 手动转动电机,观察计数方向是否正确 2. 检查CubeMX中TIM的Encoder Mode设置 3. 重新焊接或按压接线 | 1. 交换A、B相接线 2. 确认计数边沿和极性 3. 确保连接可靠 |
| 测距传感器数据跳动大 | 1. 环境干扰(超声波回声、红外杂光) 2. 供电不稳 3. 通信受干扰(I2C) | 1. 在静止状态下观察数据 2. 用示波器测量传感器VCC 3. 检查I2C上拉电阻,缩短接线 | 1. 增加软件滤波(如中值、均值) 2. 电源增加滤波电容 3. 降低I2C速度,采用屏蔽线 |
| 小车跟随振荡(来回撞) | 1. 距离环PID参数过冲,尤其是P太大 2. 速度环响应太慢 3. 传感器数据延迟大 | 1. 观察距离误差曲线 2. 测试速度环阶跃响应 3. 测量传感器数据更新周期 | 1. 降低距离环P和D,适当增加I 2. 先优化速度环动态性能 3. 选择更快传感器或降低控制频率 |
| 转弯时后车撞前车内侧 | 纯距离控制无法处理横向偏移 | 观察转弯时两车轨迹 | 1. 增加横向控制(视觉巡线) 2. 使用两个测距传感器,计算差速 3. 前车通过无线发送转向意图,后车预判 |
| 无线通信丢包严重 | 1. 距离过远或有遮挡 2. 频道干扰 3. 电源干扰 | 1. 拉近距离测试 2. 更换通信频道 3. 通信模块电源加磁珠和电容 | 1. 确保比赛场地内通信可靠 2. 选择空旷频段 3. 优化电源设计,通信线与电机电源线分开 |
| 系统运行一段时间后死机 | 1. 堆栈溢出 2. 中断冲突 3. 看门狗未喂狗 | 1. 检查.map文件查看内存使用 2. 检查中断优先级和嵌套 3. 检查看门狗配置 | 1. 增大堆栈大小 2. 合理分配中断优先级 3. 确保主循环或定时器及时喂狗 |
9. 竞赛技巧与最佳实践
基于“满分套餐”的思路,要想在电赛中获得好成绩,除了功能实现,还需注意以下工程细节:
- 模块化编程:将电机驱动、PID控制、传感器读取、通信协议等写成独立的
.c/.h文件,方便调试和复用。使用#ifdef DEBUG宏来控制调试信息的打印。 - 参数可配置化:将所有PID参数、阈值、速度限制等定义为宏或存储在全局结构体中,便于在上位机调试时通过串口指令动态修改。
- 设计调试上位机:使用Python (PySerial + PyQt) 快速制作一个简易上位机,能实时绘制距离、速度曲线,并能发送参数修改指令。这在调试PID时效率倍增。
- 准备多种传感器预案:赛题可能对传感器有限制。提前测试超声波、红外、激光TOF等多种方案的性能,并封装好驱动,以便快速切换。
- 重视机械结构与安装:确保传感器(特别是测距和摄像头)安装牢固,且测量方向与小车中轴线平行。降低小车重心,防止急停急启时晃动影响测量。
- 电源管理:电机启动瞬间电流很大,会引起电压跌落,可能导致单片机复位。建议单片机电源与电机驱动电源隔离(使用二极管或独立降压模块),并在单片机电源入口加大电容。
- 比赛策略:
- 先稳后快:先调出一套能在各种情况下稳定跟随的参数,再考虑优化跟随速度和响应时间。
- 分步得分:仔细阅读赛题评分标准。确保“基本要求”部分(如直线跟随、指定距离停车)绝对稳定,再挑战“发挥部分”(如弯道、变速、绕障)。
- 现场适应性调试:比赛场地光线、地面摩擦系数可能与实验室不同。预留时间,准备几组针对不同场景(强光、弱光、光滑地面)的参数,现场快速切换测试。
10. 总结与下一步
这套“新版双车跟随满分套餐”的核心价值在于提供了一条从零到稳定运行的清晰路径。它不仅仅是代码和电路的堆砌,更包含了系统设计思想、调试方法论和竞赛实战经验。
你最应该优先验证的是速度环的稳定性。一个响应快速且平稳的速度环是整个跟随系统的基础。在调好速度环的基础上,叠加距离环,系统成功率会高很多。
最容易踩的坑往往是硬件层面:电源不稳、接线虚焊、传感器安装角度不对、编码器分辨率设置错误。务必养成“先硬件,后软件;先单元,后集成”的调试习惯。
完成基础跟随后,可以考虑以下几个方向进行深化和扩展:
- 状态估计与滤波:引入卡尔曼滤波融合编码器、IMU(惯性测量单元)数据,获得更精准的车体姿态和速度估计。
- 轨迹预测与预瞄控制:前车通过无线发送未来一段时间的预期轨迹点,后车进行轨迹跟踪,实现更平滑的跟随,尤其在弯道。
- 多传感器融合:结合摄像头(识别前车或车道线)、多个测距传感器,构建更全面的环境感知,提高系统鲁棒性。
- 自适应控制:根据地面摩擦系数变化或电池电压下降,在线微调PID参数,保持性能一致。
建议将本文提及的代码框架、调试方法和问题排查清单收藏备用。电赛备赛时间紧,一个可靠的参考方案能帮你节省大量试错时间。记住,最好的学习是在理解这套系统的基础上,动手把它做出来,并根据自己的硬件和需求进行调整优化。祝你备赛顺利,取得好成绩!