ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32与串级PID的循迹小车完整设计方案

基于STM32与串级PID的循迹小车完整设计方案 前两天在整理 2024 年电赛 H 题的复刻思路发现不少同学卡在同一个地方传感器都能读到数据但小车就是走不直转弯总是冲出赛道。这次分享的方案基于 STM32F103C8T6 最小系统板配合 MPU6050 六轴姿态传感器和五路灰度传感器用角度环加位置环的串级 PID 控制把循迹小车的稳定性和过弯能力一起解决。这个方案的选型思路很直接STM32F103C8T6 便宜、资料多、最小系统板十几块就能买到MPU6050 负责提供车体姿态角灰度传感器负责检测赛道黑线位置控制策略不是单环 PID而是位置环输出期望角度、角度环跟随期望角度的串级结构。这样做的好处是循迹偏差越大小车转弯的目标角度就越大而且转弯过程中姿态不会被瞬间冲击打乱。如果你正在准备电赛、课程设计或者想把手里的 STM32 小车从“能跑”改成“稳着跑”这篇文章可以直接收藏。下面会按硬件选型、引脚规划、MPU6050 姿态解算、五路灰度偏差计算、串级 PID 实现、调试排错的顺序完整展开所有代码都是可直接移植到 Keil 工程里的 C 代码风格。1. 核心能力速览项目项方案说明主控STM32F103C8T6 最小系统板Cortex-M3 内核72MHz 主频Flash 64KBRAM 20KB姿态传感器MPU6050六轴三轴陀螺仪 三轴加速度计I2C 接口循迹传感器五路灰度传感器可采用 TCRT5000 红外反射模块控制策略角度环 位置环串级 PID内环快、外环慢执行机构TB6612 / L298N 电机驱动模块 直流减速电机开发环境Keil MDK / STM32CubeMX 生成初始化代码 HAL 库或标准外设库调试方式USART1 串口打印数据可用匿名上位机或串口波形助手观察显示扩展可选 0.96 寸 OLED软件 I2C 显示角度和偏差是否支持 API不涉及 HTTP 接口数据通过串口输出可转发给蓝牙/ESP8266 做无线调试是否支持批量任务硬件控制类项目不支持“批量生成”但支持批量调参、批量记录调试数据适合场景电赛备赛、循迹小车课程设计、STM32 综合训练、PID 控制学习从复刻角度看这个项目的门槛不算高。STM32F103C8T6 国产替代芯片很多几乎所有开发板厂家都能买到MPU6050 模块也便宜接线就四根线。真正花时间的不是硬件而是把姿态数据调稳定、把两个 PID 环的参数调到“既不震荡也不迟钝”。2. 适用场景与使用边界这个方案适合三类人第一类是电赛备赛队伍。2024 年 H 题这类任务重点考察小车能不能稳定完成循迹和行驶五路灰度加 MPU6050 是兼顾成本和可靠性的典型方案。比赛中时间紧代码模块化比堆功能更重要。第二类是 STM32 进阶学习者。如果你已经会点灯、会按键中断、会定时器 PWM接下来最值得学的就是“传感器读数 控制算法 电机执行”这条完整链路。这个项目正好覆盖 I2C、ADC 或 GPIO 输入、定时器 PWM、串口调试、PID 控制这些知识点。第三类是想做平衡小车或者四轮循迹小车的爱好者。角度环和位置环的串级结构跟平衡车的直立环加速度环非常像。学会这套控制思想后面移植到两轮自平衡车上速度会快很多。使用边界也要说清楚这个方案做的是教学和竞赛复刻。如果要用在商业产品或需要对外交付的项目上需要重新评估器件选型、安全保护和电磁兼容设计。灰度传感器在强光、反光地面、浅色地毯上容易误判MPU6050 存在温漂和零漂长时间运行需要定期校准。另外参考往届代码时要注意竞赛规则自己理解后重写驱动不要把别人的成品代码原样提交。涉及开源代码也要保留协议版权信息不能直接去除版权声明后商用。3. 硬件准备与引脚规划先列一套完整的硬件清单硬件型号/规格数量说明主控板STM32F103C8T6 最小系统板1价格低资料多ST-LinkST-Link V2 下载调试器1程序下载和调试传感器MPU6050 模块1I2C 接口注意模块供电电压循迹模块五路灰度传感器模块1数字量或模拟量输出建议选带电位器调阈值的版本电机驱动TB6612FNG 模块1比 L298N 体积小、压降小电机带减速箱的直流电机 2 个2转速和扭矩要匹配小车重量电源18650 电池 7.4V 或 11.1V1注意电机驱动和主控用独立供电降压模块AMS1117-3.3V / 低压差降压1给 MPU6050 和主控供电调试串口USB 转 TTL 模块1串口打印查看角度和偏差显示OLED 0.96 寸可选1显示角度、偏差、运行状态引脚规划是复刻时最容易踩坑的地方。STM32F103C8T6 只有 PA0-PA15、PB0-PB15 两组 IO外设复用要提前查好数据手册。下面给出一套示例引脚分配实际接线时需按你的板子调整。外设引脚说明MPU6050 SCLPB6硬件 I2C1 或软件 I2C 均可MPU6050 SDAPB7硬件 I2C1 或软件 I2C 均可五路灰度传感器 D1-D5PA0-PA4数字量输入或 ADC 输入左电机 PWMPA6TIM3_CH1左电机方向 IN1PB12GPIO 输出左电机方向 IN2PB13GPIO 输出右电机 PWMPA7TIM3_CH2右电机方向 IN3PB14GPIO 输出右电机方向 IN4PB15GPIO 输出电机驱动 STBYPB11高电平使能调试串口 TXPA9USART1_TX调试串口 RXPA10USART1_RXOLED SCL可选PB8软件 I2COLED SDA可选PB9软件 I2C注意PA9 和 PA10 虽然也能复用为定时器 PWM 通道但串口调试对循迹小车更重要优先保留串口。PWM 用 TIM3 的 PA6/PA7 比较合理不冲突。如果使用带霍尔编码器的电机编码器 A/B 相需要接在支持编码器模式的定时器通道上比如 TIM2 的 CH1/CH2 或 TIM4 的 CH1/CH2引脚要根据具体板卡分配。单片机供电部分要单独强调电机驱动模块的大电流不要从 STM32 最小系统板的 3.3V 引脚取电。建议电机驱动用 7.4V 或 11.1V 电池直接供电主控板用独立降压模块供电MPU6050 可以直接用主控板的 3.3V。所有模块的 GND 必须共地否则串口和 I2C 数据会出现乱码。4. MPU6050 姿态解算与数据读取MPU6050 是整个控制闭环的“内环反馈源”角度数据不准后面 PID 调得再好也没用。MPU6050 输出的是原始六轴数据不能直接拿来当角度用必须做姿态解算。常见的两种解算方式第一种是 DMP 解算也就是 MPU6050 内部 DMP 引擎直接输出四元数再转换成欧拉角。优点是 CPU 占用极小STM32F103C8T6 跑起来很轻松稳定性也不错缺点是需要移植 DMP 驱动库文件比较多编译时容易出问题。第二种是互补滤波或 Mahony 滤波算法。将加速度计解算出的角度与陀螺仪积分出的角度做融合代码量小逻辑透明适合学习 PID 时把数据结构看明白。从复刻效率角度看推荐先用 DMP 方案跑通再把滤波算法换成自己写的 Mahony。因为 DMP 方案的坑基本都被社区填完了用最短时间拿到稳定的角度数据后续调试 PID 才有时间。下面的代码是 DMP 方案的主循环读取框架接口函数名以你移植的驱动为准/* MPU6050 欧拉角数据读取示例 */ float pitch, roll, yaw; void MPU6050_Task(void) { if (mpu_dmp_get_data(pitch, roll, yaw) 0) { /* 拿到角度pitch 为俯仰角roll 为横滚角yaw 为偏航角 */ printf(pitch:%.2f roll:%.2f yaw:%.2f\r\n, pitch, roll, yaw); } }如果选择软件 I2C 驱动 MPU6050IO 口可以任意指定PB6/PB7 仍然可以保留给其他功能。但要注意 MPU6050 的 I2C 地址默认是 0x68AD0 引脚接高电平时地址变成 0x69。模块上一般有 AD0 引脚焊接或跳线时需要确认。如果你用的模块地址是 0x68但代码里写的是 0x69I2C 扫描会一直失败。姿态解算的验证方法很简单把小车放在桌面上让车体前后倾斜串口助手里的 pitch 应该平滑变化左右倾斜时 roll 平滑变化。如果数据跳变剧烈先检查 MPU6050 供电是不是稳定再检查 I2C 线长和上拉电阻。MPU6050 模块和主控之间的杜邦线不建议超过 20 厘米线越长波形越差。5. 五路灰度传感器循迹偏差计算五路灰度传感器的作用是告诉控制环“车子的中心线偏离赛道黑线多少”。这组数据不能只判断“看到线/没看到线”要转换成连续偏差值才能作为位置环的输入。每个灰度模块内部都有红外发射管和接收管黑线吸收红外光反射到接收管的光强弱白底反射强。数字量模块上有一个电位器可以调节触发阈值。调试时先把小车放在黑线上用螺丝刀慢慢旋转电位器直到对应输出指示灯刚好翻转。数字量输出的五路数据可以用 GPIO 直接读取。下面是最常用的权重法计算偏差/* 五路灰度传感器偏差计算 */ #define SENSOR_NUM 5 #define ACTIVE_LINE 0 /* 黑线有效电平需要根据模块确认是 0 还是 1 */ const int weight[SENSOR_NUM] {-2, -1, 0, 1, 2}; uint8_t sensor[SENSOR_NUM]; int get_line_deviation(void) { int sum 0; int active_count 0; for (int i 0; i SENSOR_NUM; i) { if (sensor[i] ACTIVE_LINE) { sum weight[i]; active_count; } } if (active_count 0) { return DEVIATION_LOST; /* 丢失黑线需要特殊处理 */ } return sum / active_count; /* 偏差范围-2 到 2 */ }这个偏差值的含义是当中间第三路传感器压住黑线时偏差为 0黑线偏到最左边时偏差为 -2偏到最右边时偏差为 2。控制器看到负偏差就向左转看到正偏差就向右转偏差绝对值越大转弯目标角度越大。如果灰度模块输出的是模拟电压也可以接在 ADC 上读取再设置一个软件阈值把 0-4095 的 ADC 值转成 0/1。用 ADC 的优势是可以在代码里调阈值不用每次拧电位器缺点是多占几个 ADC 通道而且不同环境的反射率会影响阈值。实际使用中五路数字量模块更省事。循迹数据验证方法把小车断电用手拿着在黑线上方左右平移串口打印的偏差值应该从 -2 走到 2。如果偏差一直在 0 附近不动检查传感器方向是不是装反了。如果某一路始终不触发检查这一路的 GPIO 配置和线序。6. 角度环与位置环控制实现这是整个项目最关键的部分。先说清楚控制结构。很多新手做循迹小车只写一个 PID摄像头或灰度偏差直接映射成 PWM 差速这样在小误差时也能跑但弯道稍微急一点就会甩尾。原因是汽车转向本身需要“目标角度”和“实际角度”的闭环灰度偏差只能代表“该往哪转”不能代表“转了多少”。正确的思路是串级 PID外环位置环内环角度环位置环输入是灰度传感器计算出的偏差期望值是 0输出是小车期望的转向角度。角度环输入是 MPU6050 解算出的实际角度期望值是位置环输出的目标角度输出是左右电机的 PWM 差速控制量。用图形化理解就是位置环决定“我想把车头转到多少度”角度环决定“我如何快速、柔顺地到达这个角度”。串级 PID 的伪代码框架如下/* 串级 PID 伪代码框架 */ PID_t pos_pid; /* 外环位置环目标值为 0 */ PID_t ang_pid; /* 内环角度环目标值来自外环输出 */ float target_angle; float pwm_diff; float current_angle; /* 主循环固定周期执行 */ void control_loop(float dt) { /* 1. 计算循迹偏差 */ int deviation get_line_deviation(); if (deviation DEVIATION_LOST) { /* 黑线丢失进入跟丢恢复逻辑 */ return; } /* 2. 外环位置环偏差转换为目标角度 */ target_angle pid_update(pos_pid, 0.0f, (float)deviation, dt); if (target_angle MAX_TARGET_ANGLE) { target_angle MAX_TARGET_ANGLE; } if (target_angle -MAX_TARGET_ANGLE) { target_angle -MAX_TARGET_ANGLE; } /* 3. 内环角度环目标角度与实际角度做 PID */ current_angle get_roll(); /* 或根据需要读取 yaw */ pwm_diff pid_update(ang_pid, target_angle, current_angle, dt); pwm_diff constrain(pwm_diff, -MAX_PWM_DIFF, MAX_PWM_DIFF); /* 4. 输出到左右电机 */ set_left_motor(BASE_SPEED pwm_diff); set_right_motor(BASE_SPEED - pwm_diff); }PID 控制器本体建议写成通用结构体方便同时维护两个环/* 通用 PID 控制器 */ typedef struct { float kp; float ki; float kd; float integral; float prev_error; float integral_limit; } PID_t; float pid_update(PID_t *pid, float target, float current, float dt) { float error target - current; pid-integral error * dt; if (pid-integral pid-integral_limit) { pid-integral pid-integral_limit; } else if (pid-integral -pid-integral_limit) { pid-integral -pid-integral_limit; } float derivative (error - pid-prev_error) / dt; float output pid-kp * error pid-ki * pid-integral pid-kd * derivative; pid-prev_error error; return output; }积分限幅非常重要。位置环的积分如果无限累积小车在弯道会越转越狠最后冲出赛道。角度环的积分如果过大静止状态下电机也会一直震动。一般建议积分限幅先设成输出限幅的 30% 左右再根据实际表现调整。需要特别说明的是角度环的反馈量并不一定固定用 roll 或 yaw。如果是四轮小车做平面转向角度环反馈可以选 MPU6050 解算出的 yaw 角灰度偏差决定 yaw 的目标值这种方案对车体转向角度控制更直观。如果是两轮自平衡小车角度环反馈则用 pitch 角目标是让车体维持在 0 度附近速度环或位置环再叠加前进后退。用户项目中提到“角度环和位置环”如果是循迹小车按上面“位置环输出目标角、角度环执行转向角”的结构理解最合适如果是平衡车把外环改成速度环反馈量来自编码器即可。两种结构本质相同都是串级 PID。PID 参数整定建议分两步走第一步只调内环角度环。把循迹偏差固定为一个常数用一个开关模拟触发观察角度环能否快速到达目标角度。如果角度出现高频震荡说明 Kp 太大或者 Kd 不够如果角度到达目标值很慢说明 Kp 太小。第二步再调外环位置环。把内环调稳后位置环的 Kp 可以从很小的值开始慢慢加。外环的响应速度必须比内环慢否则会出现“目标角度大幅度跳动实际角度跟不上”的问题。7. 调试流程与效果验证嵌入式控制类项目的调试必须按层级来不要一上来就把所有传感器和 PID 都打开。推荐调试顺序是电源和下载 - 传感器数据 - 电机执行 - 角度环 - 位置环 - 整体循迹。7.1 电源与下载验证先用 ST-Link 连接 STM32F103C8T6 最小系统板烧一个最简单的 LED 闪烁程序确认下载链路没问题、板子供电正常。如果板子连接不上先检查 ST-Link 驱动、接线顺序和 BOOT0 跳线。7.2 传感器数据验证把 MPU6050 和五路灰度传感器的读取代码分别烧进板子串口打印数据。这一步不要做任何 PID 输出只确认传感器数据物理上是对的。判断标准验证项操作预期结果MPU6050 供电和 I2C上电后看串口能打印角度不报错姿态方向一致性向前倾斜小车pitch 朝一个方向平滑变化灰度传感器方向一致性向左平移小车偏差值从 0 变成负数灰度传感器阈值用手遮挡单个探头对应指示灯翻转串口数值变化7.3 电机执行验证写一段测试代码让左右电机分别正转、反转、停止。确认方向引脚逻辑和 PWM 极性正确。注意TB6612 的 STBY 引脚如果没拉高电机永远不会转。这是非常常见的低级错误。7.4 角度环验证把角度环 PID 代码打开固定目标角度为 0用手转动小车车身观察 PWM 输出是否让电机往反方向纠正。如果用手掰动车身后电机没有反应检查角度环反馈量是不是读到了实际角度。7.5 位置环与循迹验证把两个环都打开把小车放在赛道上用手推一下辅助起步。此时小车应该能根据灰度偏差自动修正方向。如果小车左右震荡先减小位置环 Kp如果小车贴合黑线时蛇形前进说明角度环响应过慢或者位置环 Kp 太大。7.6 串口波形观察调 PID 参数最有效的手段是串口波形。把目标值和当前值都通过串口打印出来用匿名上位机或者串口波形助手画出曲线。看到曲线在目标值附近高频震荡就减小 Kp看到曲线缓慢逼近目标但有稳态误差就加 Ki。不要靠感觉改参数有波形图一次就能定位问题。/* 串口打印调试数据示例 */ printf(target:%.2f current:%.2f pwm:%d dev:%d\r\n, target_angle, current_angle, pwm_diff, deviation);8. 常见问题与排查方法问题现象可能原因排查方式解决方案ST-Link 下载失败驱动未装、接线错误、BOOT0 状态不对检查设备管理器、ST-Link 接口定义重新插拔 ST-Link确认 SWDIO/SWCLK/GND 接线MPU6050 读取超时 or 返回 0xFFI2C 接线错误、模块供电不足、地址不对用 I2C 扫描确认设备地址检查 3.3V 供电和共地尝试 0x68/0x69MPU6050 角度漂移严重未做校准、模块松动、电源纹波大静止观察数据看漂移速度上电静止 5 秒做零偏校准固定好模块灰度传感器某一路始终不变化电位器阈值调错、线序接反、接错 IO用万用表测模块输出电平重新调整电位器核对代码中的 IO 定义电机不转STBY 没拉高、PWM 未输出、方向引脚接反单独测试引脚电平把 STBY 接高电平检查 TIM3 PWM 配置电机转动但小车跑偏左右电机转速不一致、PWM 映射方向反了让左右电机空载对比转速调整 PWM 极性或者在代码里交换输出小车高频震荡内环 Kp 过大、外环 Kp 过大、控制周期不稳定串口波形观察震荡频率降低 Kp增大 Kd固定控制周期循迹过弯冲出赛道外环 Kp 太小、目标角度限幅太小、速度过快观察弯道中目标角度是否饱和增大位置环 Kp提高 MAX_TARGET_ANGLE降低基础速度黑线丢失后小车乱跑没有处理 DEVIATION_LOST 状态检查代码分支跟丢时先直线减速再尝试向最后偏差方向寻找黑线启动瞬间小车抖动PID 积分预置了错误值、PWM 突变上电后先清积分和输出在电机使能前把 PID 输出清零9. 最佳实践与竞赛建议第一个建议是把代码按模块拆开。传感器驱动、控制算法、电机输出、调试输出分成独立文件不要全堆在 main.c 里。电赛时间紧张后面换型号、换引脚、换传感器时模块化代码能省半天时间。第二个建议是固定控制周期。PID 算法的 dt 依赖定时器中断而不是 delay 循环。建议用定时器中断实现 5ms 或者 10ms 的控制周期在中断里读取传感器、计算 PID、输出 PWM。如果控制周期不稳定同一组 PID 参数今天能跑明天就会抖。第三个建议是做好数据记录。比赛调试时不要光看车跑得好不好要录下关键曲线。调好一组 PID 参数后把参数和波形截图存下来作为已知可行配置。后续改机械结构或换电机后用这一组参数做起点再微调效率高很多。第四个建议是关注边界情况。灰度传感器全丢、电池电压下降、地面反光变化这些情况都会影响循迹效果。全丢的时候先减速直线行驶同时记录当前位置连续丢线超过一定时间再停车避免小车冲出去太久。电池电压下降会导致 PWM 输出实际功率下降建议把串口打印电池电压比赛前通过电压判断剩余电量。合规方面也一样重要。参考往届代码和开源项目时建议先把原理理解清楚再用自己的代码风格重写一遍。往届电赛作品如果包含未开源的算法直接抄代码提交存在违规风险。模块和传感器只做正常用途不要拆卸改造影响安全。10. 总结与下一步这个项目最值得尝试的点是“串级 PID 在真实小车上的效果”。你会发现单环 PID 容易走极端要么反应迟钝要么左右摆头而角度环加位置环的组合能把“转多快”和“转到哪里”分开控制控制逻辑会清晰很多。最容易踩的坑有三个一是 MPU6050 数据没调稳就开始调 PID导致问题定位混乱二是 STBY 引脚忘了拉高电机不转还以为 PWM 配置错了三是 PID 参数一改就全盘乱调没有记录波形和参数。先把这三个坑绕过去项目进度会顺利很多。下一步可以从这几个方向继续扩展给小车加蓝牙遥控模块方便远程急停和模式切换把灰度传感器数据通过串口保存到上位机做赛道地图重建条件允许的话在 STM32F103C8T6 上移植 FreeRTOS把传感器采集、控制计算、调试输出拆成独立任务为后续更复杂的图像循迹任务做铺垫。如果对姿态解算本身感兴趣可以把 DMP 驱动替换成自写的 Mahony 滤波器彻底搞懂四元数和欧拉角的转换过程。这套方案属于硬件的“够用”级别没有上摄像头、没有上 RTOS也没用高端芯片但恰好证明了 STM32F103C8T6 这种入门级 MCU 也能完成稳定的闭环循迹。先把这套代码跑熟后面再堆功能就有底气了。如果你复刻过程中有更好的调参思路欢迎在评论区交流。
返回列表