STM32F103ZET6循迹小车实战:从硬件选型到PID调参全解析 简介这是一套面向智能车竞赛与嵌入式学习者的STM32循迹小车完整工程以72MHz主频的STM32F103ZET6为核心控制器机械结构采用舵机加双编码电机方案赛道覆盖直道、环岛、S弯与连续直角弯等典型元素。程序通过一字排列的灰度传感器阵列识别黑线并判断车身位置以位置式PID作为主控算法分别驱动舵机转向与双电机差速实现弯道平稳行驶适合想深入理解传感器采集、PWM输出与闭环控制的进阶学习者参考。资源包共207个文件约5.48MB包含36个c源文件、38个h头文件、38个crf与39个d编译中间文件、39个o目标文件以及uvprojx工程、sct分散加载、hex与axf可执行文件、map映射表等完整保留了Keil工程结构与编译产物便于直接打开复现与二次修改。目前已有8434人学习下载可从中获取赛题级控制方案、PID调参思路与工程组织方式。1. 从一堆散件到能跑的车STM32F103ZET6 循迹小车到底在做什么很多人第一次拿到 STM32F103ZET6 循迹小车套件时都会经历同一个瞬间电机、轮子、红外对管、驱动板、主控板摊了一桌子接线图看着都懂一上电要么原地打转要么冲出赛道。问题往往不在某个模块坏了而在于「感知—决策—执行」这条链路没有真正闭环。循迹小车本质是一个极简的嵌入式控制系统红外传感器阵列把黑线位置变成电平信号STM32F103ZET6 读取这些信号并做偏差判断再通过 PWM 控制左右轮差速让车体持续回到黑线中心。它适合刚接触 STM32 的开发者练手也适合作为 PID 控制、传感器融合、电机驱动的入门载体。这篇文章不讲空泛概念而是按我实际调试的顺序把选型、接线、代码、参数和翻车点一次讲透让你能照着复现一台稳定循迹的车。2. 硬件选型与接线为什么 ZET6 不是唯一选择但它是最好起步的2.1 主控、传感器、驱动和电机的匹配逻辑STM32F103ZET6 属于 F1 系列里的高配型号144 引脚、512KB Flash、64KB SRAM定时器资源丰富光是通用定时器和高级定时器就能同时输出多路 PWM。循迹小车真正吃紧的不是算力而是定时器通道和 GPIO 数量。常见做法是用 TIM1 或 TIM3 输出两路 PWM 给左右电机用 TIM2 做微秒级延时或编码器接口红外传感器阵列直接接普通 GPIO 并开启外部中断或定时扫描。电机驱动常用 L298N 或 TB6612前者便宜但压降大后者效率高但接线更细。电机选直流减速电机减速比一般在 1:48 到 1:120 之间转速太低循迹迟钝太高容易冲线。提示ZET6 的 5V 容忍引脚有限红外模块和驱动模块的电源最好独立走一路不要从主控板 3.3V 直接拉大电流。2.2 最小系统接线表与电源处理下面这张表是我在面包板和洞洞板上都验证过的接线方式引脚可以根据实际 PCB 调整但电源和地线的处理逻辑不变。模块引脚接到 ZET6说明红外传感器 1OUTPA0左外侧黑线输出高或低取决于模块红外传感器 2OUTPA1左内侧红外传感器 3OUTPA2中间红外传感器 4OUTPA3右内侧红外传感器 5OUTPA4右外侧L298NIN1/IN2PB0/PB1左电机方向L298NIN3/IN4PB2/PB3右电机方向L298NENA/ENBPA8/PA9TIM1_CH1/CH2 PWM电源12V电机驱动电池正负极电源5V传感器经降压模块电源3.3V主控板载稳压电源部分是最容易翻车的地方。电机启动瞬间电流能到 1A 以上如果和主控共用一路 LDOSTM32 会因为电压跌落而复位表现就是「一加速就重启」。我一般会单独给电机驱动供 12V再用一片 MP1584 或 LM2596 降到 5V 给传感器主控板自己用 3.3V 稳压。所有模块的地必须共地否则 PWM 信号没有参考电平电机时转时不转。2.3 传感器布局与阈值初调五路红外是循迹小车里最稳的配置三路容错太低七路以上对新手焊接和调试都不友好。传感器离地高度建议 8 到 15 毫米太高容易受环境光干扰太低会刮到地面。上电后先别写代码用万用表或串口打印每一路在黑白地上的电平确认黑线对应的是 0 还是 1。很多模块输出逻辑相反代码里判断条件写反了车就会往反方向修正。// 红外传感器读取与简单阈值判断 // 假设黑线输出低电平白地输出高电平 #define SENSOR_PORT GPIOA #define S1 GPIO_Pin_0 #define S2 GPIO_Pin_1 #define S3 GPIO_Pin_2 #define S4 GPIO_Pin_3 #define S5 GPIO_Pin_4 uint8_t read_sensor_mask(void) { uint8_t mask 0; if (GPIO_ReadInputDataBit(SENSOR_PORT, S1) Bit_RESET) mask | 0x01; if (GPIO_ReadInputDataBit(SENSOR_PORT, S2) Bit_RESET) mask | 0x02; if (GPIO_ReadInputDataBit(SENSOR_PORT, S3) Bit_RESET) mask | 0x04; if (GPIO_ReadInputDataBit(SENSOR_PORT, S4) Bit_RESET) mask | 0x08; if (GPIO_ReadInputDataBit(SENSOR_PORT, S5) Bit_RESET) mask | 0x10; return mask; }这段代码把五路电平压成一个字节后面做偏差计算时直接查表或按位判断比逐个 if 清晰得多。参数上唯一要改的是Bit_RESET和Bit_SET取决于你的模块在黑线上输出什么。如果读出来全是 0 或全是 1先查供电和地再查传感器上的电位器有没有调到临界点。3. 控制逻辑落地从五路红外到差速 PWM 的完整代码路径3.1 偏差建模把传感器状态映射成方向误差五路传感器一共 32 种组合但真正有意义的是黑线落在哪个位置。常见做法是给每一路分配一个权重比如最左 -2、左 -1、中 0、右 1、最右 2然后对当前触发的传感器权重求平均得到偏差值 error。如果五路都没触发说明丢线需要根据上一次的 error 决定往哪边找。这个模型比「左偏就右转」的硬编码更平滑也更容易接 PID。// 五路权重偏差计算 // 返回 -2 到 2 之间的偏差值255 表示丢线 int8_t calc_error(uint8_t mask) { const int8_t weight[5] {-2, -1, 0, 1, 2}; int8_t sum 0; uint8_t count 0; for (uint8_t i 0; i 5; i) { if (mask (1 i)) { sum weight[i]; count; } } if (count 0) return 255; // 丢线标志 return sum / count; }权重表可以根据赛道宽度和传感器间距微调。如果车在直道上左右摆说明权重跨度太大把 ±2 改成 ±1.5 或缩小中间传感器的权重。如果弯道跟不上说明权重不够需要加大外侧权重或提高控制频率。3.2 PWM 差速控制左右轮速度怎么算拿到 error 之后最直接的做法是给左右轮一个基础速度加修正量。基础速度决定直道快慢修正系数决定转向灵敏度。我一般先用纯比例控制跑通再考虑加积分和微分。下面这段代码把 error 映射成左右轮 PWM 占空比并做了限幅防止占空比超过定时器周期。// 左右轮 PWM 差速控制 // base_speed: 基础速度 0-100 // kp: 比例系数先取 15 左右试 void motor_control(int8_t error, uint8_t base_speed) { int16_t left, right; if (error 255) { // 丢线按上一次方向原地找线 left 0; right 0; return; } int16_t correction error * 15; // kp 15 left base_speed correction; right base_speed - correction; // 限幅到 0-100 if (left 100) left 100; if (left 0) left 0; if (right 100) right 100; if (right 0) right 0; set_pwm(TIM1, 1, left); // 左轮 set_pwm(TIM1, 2, right); // 右轮 }base_speed建议从 40 开始试太快会直接冲出去太慢电机可能不转。kp是玄学参数不同电机、不同电池电压下都不一样。电池从满电到没电同样的占空比转速会差很多所以要么加电压检测做补偿要么接受性能衰减。3.3 定时器与中断配置控制周期怎么定控制周期决定了系统的响应速度。太快传感器来不及稳定太慢车已经冲出赛道才修正。常见做法是用 TIM2 或 SysTick 产生 5ms 到 10ms 的中断在中断里读传感器、算 error、更新 PWM。这样主循环可以空出来做串口调试或按键处理。// TIM2 中断配置5ms 控制周期 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); uint8_t mask read_sensor_mask(); int8_t error calc_error(mask); motor_control(error, 45); } }中断里不要放串口打印或延时否则会拖慢控制周期。调试时可以把 error 和 mask 存到全局变量在主循环里打印。如果发现车反应迟钝先看中断是不是被其他高优先级任务阻塞了。4. 避坑与排查循迹小车最常见的五个翻车现场4.1 上电就原地打转现象电机一上电就全速转车原地打转或直接冲出去。原因通常是方向引脚初始化电平不对或者 PWM 占空比初始值给了 100。解决在初始化时把 IN1 到 IN4 全部拉低PWM 比较值设为 0等传感器读完第一帧再启动电机。另外检查 L298N 的使能跳线帽有没有插上有些模块默认不使能。4.2 直道上左右摆像喝醉一样现象车能循迹但在直道上频繁左右修正走不直。原因是比例系数 kp 太大或者传感器权重跨度太大。解决先把 kp 降到 8 到 10再把外侧权重从 ±2 改成 ±1观察摆动是否减小。如果还是摆检查传感器离地高度是否一致五路模块有没有装歪。4.3 弯道直接冲出去现象直道正常一到弯道就冲出去完全不修正。原因是控制周期太长或者基础速度太快。解决把控制周期从 10ms 降到 5ms基础速度从 60 降到 40再试。如果弯道是急弯五路传感器可能不够用需要加宽传感器间距或换成七路。4.4 电机时转时不转伴随主控复位现象电机启动瞬间主控重启或者电机声音断断续续。原因是电源被拉垮电机和主控共用一路电源。解决电机驱动单独供电主控和传感器用另一路所有地共地。在电机电源两端并一个 470uF 到 1000uF 的电解电容吸收启动电流冲击。4.5 黑线识别不稳定受环境光影响大现象同一段赛道室内灯一开一关传感器输出就变了。原因是红外模块的阈值电位器调得太临界或者环境光太强。解决把传感器离地高度降到 8mm 左右减少环境光进入重新调电位器让黑白地的电平差至少 1V 以上如果还是不行给传感器加黑色热缩管遮光。5. 进阶调参与验证让循迹从能跑变成跑得稳5.1 用串口波形看 error 和 PWM 的关系调车最有效的手段不是盯着车看而是把 error 和左右 PWM 打到串口用上位机画波形。正常循迹时 error 应该在 0 附近小幅波动PWM 左右差不超过 20。如果 error 长时间饱和在 ±2说明 kp 不够或基础速度太低如果 error 高频抖动说明传感器有噪声或控制周期太短。// 主循环里打印调试数据 printf(err%d, L%d, R%d\r\n, error, left_pwm, right_pwm);串口波特率用 115200打印频率控制在 20Hz 左右太快会拖慢主循环。观察波形时重点看 error 过零是否平滑如果每次过零都伴随剧烈振荡就要降低 kp 或加一点微分。5.2 从纯比例到 PD 的过渡条件纯比例控制能跑但直道容易等幅振荡弯道容易超调。加微分项可以预测 error 的变化趋势提前减速或反向修正。我一般会在 error 波动超过 ±1 且频率稳定时加 D 项kd 从 kp 的 1/10 开始试。积分项在循迹里很少用因为赛道不是长时间单向偏差积分容易饱和。参数作用起步值调整方向base_speed直道速度40摆振就降迟钝就升kp转向力度12冲线就降不拐就升kd阻尼1.5振荡就升反应慢就降控制周期响应速度5ms丢线就缩短抖动就加长5.3 赛道验证从直线到 S 弯的逐步加压不要一上来就跑完整赛道。先跑 1 米直线确认不摆再跑 90 度直角弯确认能拐再跑 S 弯确认连续修正不丢线最后跑完整闭环。每换一种赛道先降速跑通再逐步提速。电池电压从 12V 降到 10V 时同样的 PWM 转速会下降如果比赛要求稳定最好加一路 ADC 检测电池电压在代码里做占空比补偿。我自己的习惯是每次改完参数只动一个变量改完跑三圈记录丢线次数和单圈时间。丢线次数比单圈时间更重要因为一次丢线可能直接冲出赛道。希望帮到你。本文还有配套的精品资源点击获取