电赛平衡车循迹控制:从PID算法到STM32嵌入式系统实现
这次我们来看一个典型的电赛控制类题目:车载平衡滚球运动控制系统,具体聚焦在任务二“循迹一圈”的实现上。对于参加电赛的同学来说,这类题目考察的核心不是算法有多前沿,而是系统能否在有限的硬件资源下稳定、可靠地跑完整个流程。它涉及机械结构、传感器融合、控制算法和嵌入式编程等多个环节,任何一个环节的短板都可能导致任务失败。
这篇文章将直接切入主题,拆解“循迹一圈”这个任务。我们会重点关注系统的核心构成、硬件选型与门槛、控制逻辑的实现步骤,以及如何在实际调试中验证效果、排查问题。如果你正在准备电赛,或者对平衡车、循迹控制感兴趣,这篇文章提供的从传感器信号处理到PID调参的完整思路,可以直接作为你的开发参考。
1. 核心能力速览(系统方案概览)
在开始具体实现前,我们先快速梳理一下完成“车载平衡滚球运动控制系统-循迹一圈”所需的核心能力和典型方案。这能帮助你快速判断自己手头的资源是否足够,以及方案的可行性。
| 能力项 | 说明与典型方案 |
|---|---|
| 核心任务 | 控制一个搭载滚球的平衡车平台,沿给定黑色轨迹线自动行驶一圈。 |
| 控制核心 | 32位微控制器(如STM32F4系列、GD32系列),负责传感器数据采集、融合算法、电机控制。 |
| 姿态感知 | MPU6050(陀螺仪+加速度计)是标配,用于获取车体俯仰角、角速度,实现自平衡。 |
| 循迹感知 | 多路红外对管(如五路TCRT5000)或灰度传感器阵列。五路方案是主流,兼顾精度与成本。 |
| 执行机构 | 带编码器的直流减速电机(至少两个),用于驱动车轮并实现速度闭环。 |
| 电源管理 | 独立、稳定的电源模块至关重要。常用方案:12V锂电池供电机,经降压模块(如LM2596)得到5V/3.3V供主控与传感器。 |
| 关键算法 | 1.平衡控制:角度环PID(有时结合角速度环)。 2.速度控制:编码器反馈的速度环PID。 3.循迹控制:根据红外阵列状态,计算偏差,进行转向环PID控制。 |
| 开发环境 | Keil MDK、STM32CubeIDE、PlatformIO等嵌入式开发环境。 |
| 调试门槛 | 需要具备基本的电路焊接、单片机编程、PID参数整定和串口调试能力。 |
| 成功标志 | 小车能自主保持平衡,并稳定、平滑地沿黑色轨迹线行驶,完成闭环路径后自动停止。 |
2. 适用场景与使用边界
这个项目方案主要面向以下场景和人群:
- 全国大学生电子设计竞赛(电赛)参赛者:特别是控制类题目,如2026年可能的赛题方向。本方案提供了一个完整的、可复用的技术框架。
- 嵌入式与自动控制学习者:希望将理论(如PID控制、传感器融合)应用于一个综合实践项目。
- 机器人爱好者:对两轮自平衡车、自动循迹机器人感兴趣,想深入了解其底层原理与实现细节。
使用边界与注意事项:
- 非通用机器人平台:方案针对“平衡”+“循迹”场景优化,机械结构(如重心位置、轮距)对平衡性能影响极大,直接套用于其他车模可能需大幅调整。
- 算法非最优解:采用的串级PID是经典可靠方案,但并非唯一或最高性能的方案(如可探索LQR、模糊PID等)。本文旨在提供一套稳定、可实现的入门到进阶路径。
- 硬件依赖性:性能高度依赖传感器精度(MPU6050校准、红外管一致性)和执行器响应(电机性能、编码器分辨率)。
- 安全与测试:调试时务必确保车体有安全防护(如加装护栏),防止高速旋转的轮子或倾倒的车体造成伤害或设备损坏。在开阔、平整的场地进行测试。
3. 环境准备与前置条件
在动手焊接和编程前,需要准备好以下软硬件环境。一份清晰的清单能避免后续很多麻烦。
3.1 硬件物料清单
这是实现该系统的最小硬件集合,建议在采购时预留一些备用件。
| 类别 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控核心 | STM32F407VET6 或 GD32F450 | 1 | 核心板或最小系统板,确保引脚资源充足。 |
| 姿态传感器 | MPU6050模块 | 1 | 带I2C接口,注意安装位置应尽量靠近车体中心轴。 |
| 循迹传感器 | TCRT5000红外对管模块 | 5 | 组成一字阵列,间距根据赛道宽度调整(通常15-20mm)。 |
| 电机驱动 | TB6612FNG 或 DRV8833 模块 | 1 | 双路直流电机驱动,支持PWM和方向控制。 |
| 执行电机 | 带编码器的直流减速电机 | 2 | 减速比根据车重和所需扭矩选择,编码器线数越高,速度控制越精细。 |
| 电源系统 | 18650锂电池(2串,7.4V) | 1组 | 供电机。需配电池盒和保护板。 |
| 降压模块(如LM2596) | 2 | 分别降至5V(供传感器、驱动模块)和3.3V(供主控)。 | |
| 车体结构 | 平衡小车底盘套件 | 1套 | 包含车轮、底盘板、电机支架等。重心设计要低。 |
| 调试工具 | USB转TTL串口模块 | 1 | 用于程序下载和调试信息输出。 |
| 万用表、示波器 | 各1 | 检查电源、信号质量必备。 | |
| 其他 | 杜邦线(公对公、公对母)、洞洞板、开关、排针、排母、扎带等 | 若干 | 用于电路连接与固定。 |
3.2 软件与开发环境
- 集成开发环境(IDE):
- Keil MDK-ARM:传统且广泛使用,需安装对应器件包(如STM32F4xx_DFP)。
- STM32CubeIDE:ST官方免费工具,集成CubeMX图形化配置,推荐新手使用。
- PlatformIO(VSCode插件):跨平台,库管理方便,适合喜欢现代编辑器的开发者。
- 固件库/硬件抽象层(HAL):
- 如果使用STM32,建议使用STM32CubeMX生成初始化代码,并选择HAL库或LL库。HAL库易用,LL库效率更高。
- 如果使用GD32,需使用官方的固件库。
- 串口调试助手:如XCOM、SSCOM、Putty等,用于查看传感器数据、调试信息和PID输出值。
- 版本控制(可选但推荐):使用Git(如GitHub Desktop)管理代码,便于回溯和团队协作。
4. 系统搭建与硬件连接
硬件连接的可靠性是整个系统稳定的基础。建议先在不接电机的情况下,完成所有传感器的供电和通信测试。
4.1 核心电路连接示意图
以下是基于STM32F4的主控连接示意,具体引脚可根据你的板卡资源调整:
主控(STM32F407) 外设模块 ---------------- ---------------------------- 3.3V <--> MPU6050.VCC, 红外阵列.VCC(部分模块需5V) GND <--> 所有模块的GND PB6/I2C1_SCL <--> MPU6050.SCL PB7/I2C1_SDA <--> MPU6050.SDA PA0-4 (ADC) <--> 红外对管1-5.AO (模拟输出,用于精细判断) 或 PC0-4 (GPIO) <--> 红外对管1-5.DO (数字输出,简单判断) PA8/TIM1_CH1 <--> TB6612.PWMA (左电机PWM) PA9/TIM1_CH2 <--> TB6612.PWMB (右电机PWM) PE5/GPIO <--> TB6612.AIN1 (左电机方向) PE6/GPIO <--> TB6612.AIN2 (左电机方向) PB0/GPIO <--> TB6612.BIN1 (右电机方向) PB1/GPIO <--> TB6612.BIN2 (右电机方向) PA5/TIM2_CH1 <--> 左电机编码器A相 PA6/TIM2_CH2 <--> 左电机编码器B相 PB4/TIM3_CH1 <--> 右电机编码器A相 PB5/TIM3_CH2 <--> 右电机编码器B相 VIN(7.4V) <--> TB6612.VM (电机电源) 5V输出 <--> TB6612.VCC, 红外阵列.VCC 3.3V输出 <--> STM32.VDD, MPU6050.VCC关键说明:
- 电源隔离:电机驱动电源(VM)与逻辑电源(VCC)务必分开,并在共地处用0欧电阻或磁珠单点连接,避免电机噪声干扰MCU。
- 编码器接口:使用定时器的编码器模式,可以自动计数,减轻CPU负担。
- 红外传感器:使用ADC读取模拟值可以更精确地判断偏离程度,而不仅仅是“在线”或“离线”。
4.2 机械结构组装要点
- 重心:电池(最重的部件)应尽可能低,并靠近车体中心轴线。可以安装在底盘板下方。
- 传感器布局:MPU6050安装在车体中心平面的PCB上。五路红外传感器阵列安装在前方,离地高度约1-1.5cm,通过实验调整至对赛道黑白对比度最敏感的位置。
- 电机安装:确保两个电机轴平行,车轮安装牢固,避免运行时打滑或偏心。
- 走线:电源线(电机)与信号线(传感器)尽量分开捆扎,避免干扰。使用扎带固定,防止卷入车轮。
5. 核心软件设计与控制逻辑
这是整个系统的“大脑”。我们将控制逻辑分解为几个相对独立的模块,便于调试和维护。
5.1 程序主框架与定时中断
系统需要高实时性的控制周期(通常1-5ms)。最佳实践是使用一个高优先级定时器中断作为控制核心。
// 伪代码,描述主循环和中断流程 int main(void) { // 1. 硬件初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 编码器定时器 MX_TIM3_Init(); MX_TIM5_Init(); // 平衡控制定时器,1kHz中断 MX_I2C1_Init(); // MPU6050 MX_ADC1_Init(); // 红外ADC MX_USART1_UART_Init(); // 调试串口 // 2. 模块初始化 MPU6050_Init(); PID_Init(&pid_balance, 0, 0, 0); // 初始化PID参数,后续调试 PID_Init(&pid_speed, 0, 0, 0); PID_Init(&pid_track, 0, 0, 0); // 3. 启动定时器中断 HAL_TIM_Base_Start_IT(&htim5); // 4. 主循环(处理非实时任务) while (1) { // 读取红外传感器状态并计算路径偏差(可放在中断,也可放这里) Track_Get_Error(&track_error); // 通过串口发送调试数据(如角度、偏差、PID输出) Debug_Send_Data(); HAL_Delay(10); // 非实时任务,周期可长 } } // TIM5 1ms中断服务函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM5) { // 1. 读取传感器数据 MPU6050_Read_Data(&pitch, &gyro_y); // 俯仰角,Y轴角速度 Encoder_Read_Speed(&speed_left, &speed_right); // 读取编码器速度 // 2. 平衡环PID计算(内环,响应最快) balance_output = PID_Calculate(&pid_balance, target_pitch, pitch); // 3. 速度环PID计算(外环,给定目标速度为0,维持静止) speed_target = PID_Calculate(&pid_speed, 0, (speed_left+speed_right)/2); // 4. 将速度环输出叠加到平衡环的目标上 final_balance_target = balance_output + speed_target; // 5. 循迹环PID计算(基于红外阵列计算的偏差) track_output = PID_Calculate(&pid_track, 0, track_error); // 6. 合成最终电机输出 motor_left = final_balance_target + track_output; // 左轮 motor_right = final_balance_target - track_output; // 右轮 // 7. 限幅并输出PWM Motor_Output(motor_left, motor_right); } }5.2 传感器数据读取与处理
MPU6050数据处理(姿态解算):直接读取的陀螺仪和加速度计数据需要融合。对于平衡车,通常使用互补滤波或卡尔曼滤波来估算俯仰角。互补滤波更简单易实现。
// 简易互补滤波示例 float Complementary_Filter(float acc_angle, float gyro_rate, float dt) { static float angle = 0; float K = 0.98; // 置信权重,需调试 // 加速度计角度(瞬时,易受震动影响) // acc_angle = atan2(accY, accZ) * 180/PI; // 陀螺仪积分角度(随时间漂移) angle = K * (angle + gyro_rate * dt) + (1 - K) * acc_angle; return angle; } // 在1ms中断中调用 pitch = Complementary_Filter(acc_angle, gyro_y, 0.001);五路红外循迹数据处理:将五路传感器的状态(0/1或模拟量)转化为一个连续的偏差值。常用加权法。
#define SENSOR_NUM 5 int sensor_val[SENSOR_NUM]; // 0:白线(反射弱), 1:黑线(反射强) int sensor_weight[SENSOR_NUM] = {-2, -1, 0, 1, 2}; // 权重值 int Track_Get_Error(void) { int sum = 0, count = 0; for (int i = 0; i < SENSOR_NUM; i++) { if (sensor_val[i] == 1) { // 检测到黑线 sum += sensor_weight[i]; count++; } } if (count == 0) { // 全部脱线,根据上一次偏差或特殊处理 return last_error * 2; // 或返回一个极大值 } last_error = sum; // 简单求和或求平均 return last_error; }如果使用ADC读取模拟值,可以设置一个阈值,并将模拟值映射为更精细的偏差。
编码器速度计算:利用定时器编码器模式自动计数,在固定周期(如10ms)内读取计数值差,换算成速度。
// 读取定时器CNT寄存器获取编码器计数值 int16_t cnt_now = TIM2->CNT; int16_t pulse = cnt_now - cnt_last; cnt_last = cnt_now; // 脉冲数除以时间、编码器线数、减速比,得到轮子转速(RPM或m/s) speed_left = (pulse * 60.0f) / (ENCODER_LINES * GEAR_RATIO * SAMPLE_TIME_MS / 1000.0f);5.3 串级PID控制器实现
平衡车通常采用角度-速度-转向三环串级PID控制。角度环是内环,速度环是外环,循迹环平行作用于最终输出。
typedef struct { float target; // 目标值 float actual; // 实际值 float err; // 当前误差 float err_last; // 上次误差 float err_sum; // 误差积分 float kp, ki, kd; // PID参数 float output; // 输出值 float output_max; // 输出限幅 } PID; void PID_Init(PID *pid, float kp, float ki, float kd, float max) { pid->kp = kp; pid->ki = ki; pid->kd = kd; pid->output_max = max; pid->err_sum = 0; pid->err_last = 0; } float PID_Calculate(PID *pid, float target, float actual) { pid->target = target; pid->actual = actual; pid->err = pid->target - pid->actual; // 积分分离:误差大时,取消积分防止饱和 if(fabs(pid->err) > INTEGRAL_SEPARATION_THRESHOLD) { pid->err_sum = 0; } else { pid->err_sum += pid->err; // 积分限幅 if(pid->err_sum > INTEGRAL_MAX) pid->err_sum = INTEGRAL_MAX; if(pid->err_sum < -INTEGRAL_MAX) pid->err_sum = -INTEGRAL_MAX; } pid->output = pid->kp * pid->err + pid->ki * pid->err_sum + pid->kd * (pid->err - pid->err_last); pid->err_last = pid->err; // 输出限幅 if(pid->output > pid->output_max) pid->output = pid->output_max; if(pid->output < -pid->output_max) pid->output = -pid->output_max; return pid->output; }各环作用与调试顺序:
- 角度环(内环):快速响应车体倾斜,维持直立。
Kp起主要作用,Kd抑制震荡。先调此环,用手扶住小车,给一个轻微干扰,观察它能否快速回正且不振荡。 - 速度环(外环):消除角度环的静态误差,使小车能长时间静止。
Ki起主要作用。调好角度环后,加入速度环,观察小车是否会在一个位置附近轻微移动而不是跑飞。 - 循迹环:控制转向。
Kp决定转向灵敏度。在平衡和速度稳定的基础上加入,让小车能跟随路径弯曲。
6. 调试流程与效果验证
调试是电赛项目的重中之重。必须遵循“先单元,后集成;先静态,后动态”的原则。
6.1 分模块调试
- 电源测试:上电后,用万用表测量各点电压(3.3V, 5V, 电机驱动电压)是否稳定、无毛刺。
- 传感器测试:
- MPU6050:通过串口打印原始加速度计和陀螺仪数据。静止时,Z轴加速度约+1g或-1g,XY轴接近0;绕Y轴旋转时,陀螺仪Y轴数据变化。
- 红外对管:打印每路传感器的数字或模拟值。分别在白纸和黑线上移动,观察数值跳变是否清晰、阈值是否合理。
- 编码器:手动转动电机,打印编码器计数值,检查是否正反转计数正确。
- 执行器测试:
- 电机驱动:编写测试程序,分别控制两个电机正转、反转、停止,PWM值从0到最大,观察电机响应是否平滑、有无异响。
- 控制算法调试(关键):
- 先不装车轮,用手扶测试:这是最安全的调试方式。
- 调角度环:将小车扶至平衡位置,然后轻微倾斜,观察PWM输出是否反向抵抗倾斜。逐步增大
Kp直到出现小幅振荡,然后加入Kd抑制。通过串口波形工具(如VOFA+)观察角度和输出曲线至关重要。 - 调速度环:加上速度环后,目标是让小车在平衡位置“钉住”。如果小车朝一个方向持续加速,说明速度环
Ki极性反了或过大。 - 调循迹环:将小车放在赛道上,用手推动它沿直线走,观察转向输出是否能让它纠正方向。弯道处,偏差增大,转向输出应增大。
6.2 整机联调与“循迹一圈”验证
当各模块单独工作正常后,进行整机动态测试。
- 静态启动:将小车笔直放在赛道起点。上电后,它应能自己保持平衡。如果往一边倒,检查机械重心是否对称,或角度环零点是否校准。
- 直线循迹:轻轻推一下小车,它应在保持平衡的同时,沿着黑线前进。如果跑偏,调整循迹环的
Kp。 - 弯道循迹:测试90度弯、S弯。观察过弯时是否平滑,有无冲出赛道或剧烈振荡。可能需要根据偏差大小动态调整循迹PID参数,或加入微分项抑制过冲。
- 全程任务验证:
- 起点识别:可以在起点设置特殊标记(如加宽的黑线、十字交叉线),通过红外阵列的特殊状态(如中间三路同时检测到黑线)来识别一圈完成。
- 停止逻辑:识别到起点后,将速度环的目标速度设为0,并逐渐减小平衡环的目标角度,让小车缓缓停下并保持直立。
- 重复性测试:多次运行,记录成功完成一圈的次数。分析失败原因(如打滑、传感器误判、参数漂移)。
7. 常见问题与排查方法
在调试和运行中,你几乎一定会遇到下面这些问题。这里提供排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 小车无法保持平衡,直接倒下 | 1. 角度环PID极性错误。 2. MPU6050数据错误或安装方向反了。 3. 电机输出线接反。 4. 机械重心太高。 | 1. 检查角度计算符号。 2. 打印MPU6050原始数据,检查静止和倾斜时变化趋势。 3. 交换电机两根线测试。 4. 降低电池等重物位置。 | 1. 将角度环输出乘以-1试试。 2. 检查I2C地址和初始化,确认安装的坐标轴方向。 3. 确保电机接线与程序正反转定义一致。 4. 重新设计机械结构。 |
| 小车平衡但剧烈抖动 | 1. 角度环Kp太大或Kd太小。2. 控制周期不稳定或太慢。 3. 电源功率不足,导致电机驱动电压波动。 | 1. 观察串口波形,看角度和PWM输出是否同频振荡。 2. 检查定时器中断配置和优先级。 3. 用示波器观察电机驱动电源电压。 | 1. 减小Kp,增大Kd。2. 确保中断服务函数执行时间短,优化代码。 3. 更换容量更大的电池,检查电源线接触电阻。 |
| 小车朝一个方向跑飞 | 1. 速度环Ki极性错误或过大。2. 左右电机机械差异或编码器误差导致速度测量不准。 3. 循迹环偏差计算错误,始终有固定偏差。 | 1. 暂时屏蔽速度环,看是否还跑飞。 2. 分别打印左右轮速度,看是否对称。 3. 打印红外传感器状态和计算的偏差值。 | 1. 反转速度环输出符号,或减小Ki。2. 对两个电机分别进行速度环PID参数微调。 3. 检查红外传感器阈值和安装高度,校准偏差计算权重。 |
| 循迹过弯时冲出赛道 | 1. 循迹环Kp太小,响应慢。2. 小车速度太快,惯性大。 3. 红外传感器安装太高或太低,检测范围小。 | 1. 观察过弯时的偏差和转向输出是否足够。 2. 降低速度环的目标速度或最大速度限制。 3. 测试传感器在不同高度下的检测效果。 | 1. 增大循迹环Kp,或加入微分项Kd预测趋势。2. 在弯道前引入减速逻辑。 3. 调整传感器安装高度至最佳灵敏度位置。 |
| 启动时抖动一下然后倒下 | 1. 初始化时角度读取有误,导致目标角度不对。 2. 电机初始PWM输出不为0,有冲击。 | 1. 上电后延迟几百毫秒再启动控制环,等待MPU6050数据稳定。 2. 检查程序初始化顺序,确保PWM输出初始化在最后,且初始占空比为0。 | 1. 增加软件启动流程:上电->延时->读取初始角度作为平衡点->使能控制环。 2. 在 Motor_Output函数中加入软启动,PWM从0缓慢增加到计算值。 |
| 编码器计数不准或为零 | 1. 编码器AB相接反。 2. 定时器未配置为编码器模式。 3. 接线松动或电源干扰。 | 1. 交换编码器A、B相线测试。 2. 检查定时器初始化代码,确认选择了编码器模式。 3. 用手转动电机,同时读取定时器CNT寄存器值。 | 1. 确保接线与程序设定的定时器通道对应。 2. 使用STM32CubeMX正确配置定时器为“Encoder Mode”。 3. 使用屏蔽线连接编码器,并确保共地良好。 |
8. 优化建议与进阶思路
当基本功能实现后,可以考虑以下优化来提升系统的鲁棒性和性能,这在电赛中获得高分至关重要。
- 传感器融合优化:
- 将互补滤波升级为卡尔曼滤波,能更优地处理噪声,得到更平滑、准确的角度估计。
- 对于红外循迹,可以尝试使用摄像头(如OpenMV)进行图像识别,获取更前瞻、更丰富的路径信息,以应对更复杂的赛道。
- 控制算法进阶:
- 模糊PID:将PID参数
Kp,Ki,Kd设计为误差e和误差变化率ec的函数,使系统在不同状态(如直道、弯道、启动、停止)下自动切换参数,适应能力更强。 - 串级PID参数自整定:编写简单的上位机,通过无线模块(如蓝牙、Wi-Fi)实时调整PID参数并观察波形,大幅提高调试效率。
- 模糊PID:将PID参数
- 运动规划:
- 对于“循迹一圈”任务,可以引入简单的路径记忆或状态机。例如,识别到起点/终点线后,执行一个优雅的停止动作(如原地旋转一定角度),而不仅仅是急停。
- 根据赛道信息(可通过第一次慢速巡线学习获得),在弯道前预减速,在直道加速,优化全程用时。
- 系统稳定性增强:
- 软件死区:在电机输出PWM值很小时(如绝对值小于5),直接输出0,避免电机“嗡嗡”响却不转的堵转现象。
- 异常保护:程序中加入倾角过大保护(如角度>30度立即切断电机PWM)、堵转保护(编码器速度长时间为0但PWM很大)、超时保护(任务长时间未完成则复位)。
- 无线调试与监控:集成蓝牙模块(如HC-05)或Wi-Fi模块(ESP8266),将关键数据(角度、速度、偏差、PID输出)实时发送到手机或电脑上位机,实现脱离串口线的远程调试。
完成“车载平衡滚球运动控制系统”的循迹任务,是一个对综合能力极佳的锻炼。它强迫你将抽象的控制理论、具体的传感器电路、实时的嵌入式编程和细致的机械调试融为一体。最大的挑战往往不是代码本身,而是在无数次参数调整和现象观察中,建立起对系统动态特性的直觉。从让小车颤颤巍巍地站起来,到它能稳健地巡线一周,这个过程带来的成就感是纯粹的。建议你在基本功能跑通后,务必花时间打磨稳定性和应对异常情况的能力,这才是电赛中获得好评的关键。
