三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

电赛平衡车循迹控制:从PID算法到STM32嵌入式系统实现

电赛平衡车循迹控制:从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控制、传感器融合)应用于一个综合实践项目。
  • 机器人爱好者:对两轮自平衡车、自动循迹机器人感兴趣,想深入了解其底层原理与实现细节。

使用边界与注意事项:

  1. 非通用机器人平台:方案针对“平衡”+“循迹”场景优化,机械结构(如重心位置、轮距)对平衡性能影响极大,直接套用于其他车模可能需大幅调整。
  2. 算法非最优解:采用的串级PID是经典可靠方案,但并非唯一或最高性能的方案(如可探索LQR、模糊PID等)。本文旨在提供一套稳定、可实现的入门到进阶路径。
  3. 硬件依赖性:性能高度依赖传感器精度(MPU6050校准、红外管一致性)和执行器响应(电机性能、编码器分辨率)。
  4. 安全与测试:调试时务必确保车体有安全防护(如加装护栏),防止高速旋转的轮子或倾倒的车体造成伤害或设备损坏。在开阔、平整的场地进行测试。

3. 环境准备与前置条件

在动手焊接和编程前,需要准备好以下软硬件环境。一份清晰的清单能避免后续很多麻烦。

3.1 硬件物料清单

这是实现该系统的最小硬件集合,建议在采购时预留一些备用件。

类别型号/规格数量备注
主控核心STM32F407VET6 或 GD32F4501核心板或最小系统板,确保引脚资源充足。
姿态传感器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 软件与开发环境

  1. 集成开发环境(IDE)
    • Keil MDK-ARM:传统且广泛使用,需安装对应器件包(如STM32F4xx_DFP)。
    • STM32CubeIDE:ST官方免费工具,集成CubeMX图形化配置,推荐新手使用。
    • PlatformIO(VSCode插件):跨平台,库管理方便,适合喜欢现代编辑器的开发者。
  2. 固件库/硬件抽象层(HAL)
    • 如果使用STM32,建议使用STM32CubeMX生成初始化代码,并选择HAL库或LL库。HAL库易用,LL库效率更高。
    • 如果使用GD32,需使用官方的固件库。
  3. 串口调试助手:如XCOM、SSCOM、Putty等,用于查看传感器数据、调试信息和PID输出值。
  4. 版本控制(可选但推荐):使用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 机械结构组装要点

  1. 重心:电池(最重的部件)应尽可能低,并靠近车体中心轴线。可以安装在底盘板下方。
  2. 传感器布局:MPU6050安装在车体中心平面的PCB上。五路红外传感器阵列安装在前方,离地高度约1-1.5cm,通过实验调整至对赛道黑白对比度最敏感的位置。
  3. 电机安装:确保两个电机轴平行,车轮安装牢固,避免运行时打滑或偏心。
  4. 走线:电源线(电机)与信号线(传感器)尽量分开捆扎,避免干扰。使用扎带固定,防止卷入车轮。

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; }

各环作用与调试顺序:

  1. 角度环(内环):快速响应车体倾斜,维持直立。Kp起主要作用,Kd抑制震荡。先调此环,用手扶住小车,给一个轻微干扰,观察它能否快速回正且不振荡。
  2. 速度环(外环):消除角度环的静态误差,使小车能长时间静止。Ki起主要作用。调好角度环后,加入速度环,观察小车是否会在一个位置附近轻微移动而不是跑飞。
  3. 循迹环:控制转向。Kp决定转向灵敏度。在平衡和速度稳定的基础上加入,让小车能跟随路径弯曲。

6. 调试流程与效果验证

调试是电赛项目的重中之重。必须遵循“先单元,后集成;先静态,后动态”的原则。

6.1 分模块调试

  1. 电源测试:上电后,用万用表测量各点电压(3.3V, 5V, 电机驱动电压)是否稳定、无毛刺。
  2. 传感器测试
    • MPU6050:通过串口打印原始加速度计和陀螺仪数据。静止时,Z轴加速度约+1g或-1g,XY轴接近0;绕Y轴旋转时,陀螺仪Y轴数据变化。
    • 红外对管:打印每路传感器的数字或模拟值。分别在白纸和黑线上移动,观察数值跳变是否清晰、阈值是否合理。
    • 编码器:手动转动电机,打印编码器计数值,检查是否正反转计数正确。
  3. 执行器测试
    • 电机驱动:编写测试程序,分别控制两个电机正转、反转、停止,PWM值从0到最大,观察电机响应是否平滑、有无异响。
  4. 控制算法调试(关键)
    • 先不装车轮,用手扶测试:这是最安全的调试方式。
    • 调角度环:将小车扶至平衡位置,然后轻微倾斜,观察PWM输出是否反向抵抗倾斜。逐步增大Kp直到出现小幅振荡,然后加入Kd抑制。通过串口波形工具(如VOFA+)观察角度和输出曲线至关重要。
    • 调速度环:加上速度环后,目标是让小车在平衡位置“钉住”。如果小车朝一个方向持续加速,说明速度环Ki极性反了或过大。
    • 调循迹环:将小车放在赛道上,用手推动它沿直线走,观察转向输出是否能让它纠正方向。弯道处,偏差增大,转向输出应增大。

6.2 整机联调与“循迹一圈”验证

当各模块单独工作正常后,进行整机动态测试。

  1. 静态启动:将小车笔直放在赛道起点。上电后,它应能自己保持平衡。如果往一边倒,检查机械重心是否对称,或角度环零点是否校准。
  2. 直线循迹:轻轻推一下小车,它应在保持平衡的同时,沿着黑线前进。如果跑偏,调整循迹环的Kp
  3. 弯道循迹:测试90度弯、S弯。观察过弯时是否平滑,有无冲出赛道或剧烈振荡。可能需要根据偏差大小动态调整循迹PID参数,或加入微分项抑制过冲。
  4. 全程任务验证
    • 起点识别:可以在起点设置特殊标记(如加宽的黑线、十字交叉线),通过红外阵列的特殊状态(如中间三路同时检测到黑线)来识别一圈完成。
    • 停止逻辑:识别到起点后,将速度环的目标速度设为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. 优化建议与进阶思路

当基本功能实现后,可以考虑以下优化来提升系统的鲁棒性和性能,这在电赛中获得高分至关重要。

  1. 传感器融合优化
    • 将互补滤波升级为卡尔曼滤波,能更优地处理噪声,得到更平滑、准确的角度估计。
    • 对于红外循迹,可以尝试使用摄像头(如OpenMV)进行图像识别,获取更前瞻、更丰富的路径信息,以应对更复杂的赛道。
  2. 控制算法进阶
    • 模糊PID:将PID参数KpKiKd设计为误差e和误差变化率ec的函数,使系统在不同状态(如直道、弯道、启动、停止)下自动切换参数,适应能力更强。
    • 串级PID参数自整定:编写简单的上位机,通过无线模块(如蓝牙、Wi-Fi)实时调整PID参数并观察波形,大幅提高调试效率。
  3. 运动规划
    • 对于“循迹一圈”任务,可以引入简单的路径记忆状态机。例如,识别到起点/终点线后,执行一个优雅的停止动作(如原地旋转一定角度),而不仅仅是急停。
    • 根据赛道信息(可通过第一次慢速巡线学习获得),在弯道前预减速,在直道加速,优化全程用时。
  4. 系统稳定性增强
    • 软件死区:在电机输出PWM值很小时(如绝对值小于5),直接输出0,避免电机“嗡嗡”响却不转的堵转现象。
    • 异常保护:程序中加入倾角过大保护(如角度>30度立即切断电机PWM)、堵转保护(编码器速度长时间为0但PWM很大)、超时保护(任务长时间未完成则复位)。
    • 无线调试与监控:集成蓝牙模块(如HC-05)或Wi-Fi模块(ESP8266),将关键数据(角度、速度、偏差、PID输出)实时发送到手机或电脑上位机,实现脱离串口线的远程调试。

完成“车载平衡滚球运动控制系统”的循迹任务,是一个对综合能力极佳的锻炼。它强迫你将抽象的控制理论、具体的传感器电路、实时的嵌入式编程和细致的机械调试融为一体。最大的挑战往往不是代码本身,而是在无数次参数调整和现象观察中,建立起对系统动态特性的直觉。从让小车颤颤巍巍地站起来,到它能稳健地巡线一周,这个过程带来的成就感是纯粹的。建议你在基本功能跑通后,务必花时间打磨稳定性和应对异常情况的能力,这才是电赛中获得好评的关键。

← 返回列表