news 2026/10/21 23:09:43

STM32F103ZET6循迹小车实战:从硬件选型到PID调参全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 以上,如果和主控共用一路 LDO,STM32 会因为电压跌落而复位,表现就是「一加速就重启」。我一般会单独给电机驱动供 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 检测电池电压,在代码里做占空比补偿。

我自己的习惯是每次改完参数只动一个变量,改完跑三圈,记录丢线次数和单圈时间。丢线次数比单圈时间更重要,因为一次丢线可能直接冲出赛道。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 23:44:00

632流程变革框架:从PPT到可执行流程资产的落地方法论

简介&#xff1a;本资源是美的集团632项目核心成果——《EPF企业流程框架规划设计方案》PPT课件&#xff0c;面向企业管理咨询从业者、流程优化工程师、数字化转型负责人及高校工商管理/信息管理专业师生。内容系统呈现了美的集团2013–2015年推进的端到端流程变革实践&#xf…

作者头像 李华
网站建设 2026/10/21 16:16:16

libigl几何处理库入门指南:安装配置、核心模块与实战避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 7:15:14

基于python的空调销售数据可视化系统

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 1. 背景与意义 在数字化营销与精细化运营的时代&#xff0c;销售数据的价值日益凸显。对于空调这类季节性、地域性特征明显的商品&#xff0c;其销售数据背后隐藏着市场…

作者头像 李华