简介:这是一份基于STM32F103ZET6单片机的智能小车完整设计文档,面向嵌入式、自动化及物联网方向学生,适合用作课程设计或毕业设计的参考资料。文档系统讲解了PWM调速、红外循迹、红外避障、超声波避障、障碍物跟随、红外遥控等功能的实现原理与编程方法,涵盖定时器输出PWM控制电机转速、红外传感器识别路径与障碍、超声波测距及遥控解码等关键技术,并配有硬件电路设计说明、程序流程图和软件代码讲解,帮助读者从硬件搭建到代码调试完整体验开发流程。内容还介绍了Keil开发环境与mcuisp下载工具的使用,以及实验调试步骤和常见问题处理思路。资源包共1个doc文件,大小34.15MB,文档共65页、约1.5万字,结构清晰,既能快速了解整体方案,也可按章节深入二次开发。目前已有1357人学习下载,是STM32智能车实战中一套高性价比的完整案例。
1. 一份能跑的 STM32F103ZET6 智能小车设计文档,从 PWM 调速到六种玩法都齐了
做课设或者入门嵌入式小车,最怕的不是写代码,而是拿到一份只有源码没有硬件说明的压缩包,焊完板子发现引脚对不上、供电又不足,最后跑不起来。这份基于 STM32F103ZET6 单片机的智能小车设计文档,65 页、一万五千多字,把硬件电路、软件代码、程序流程图全部放在一起,覆盖 PWM 调速、红外循迹、红外避障、超声波避障、障碍物跟随、红外遥控六项功能,适合正在做课程设计、毕业设计或者想自己从零搭一台小车的从业者。我拆完这份文档的直观感受是:它不是一个只能看不能用的 PPT 式论文,而是能让你照着原理图接线、对着流程图写状态机、最终把车跑起来的那种资料。
2. STM32F103ZET6 的资源盘点与选型逻辑:为什么这颗芯片被选中
2.1 512K Flash 与 64K RAM:对智能小车来说真的够用吗
STM32F103ZET6 是完整的 144 引脚封装,Cortex-M3 内核,最高工作频率 72MHz,程序存储器 512K 字节,SRAM 64K 字节,供电范围 2V 到 3.6V,工作温度 -40℃ 到 85℃。对于智能小车这种场景,代码通常只有几十 KB,主要占空间的是循迹判据、避障状态机、遥控解码表和调试用的字符串常量,512K Flash 完全够用,64K RAM 也远不会到紧张的程度。更关键的是它有 8 个 16 位定时器,其中两个是电机控制定时器,PWM 输出通道数量足够覆盖四个直流减速电机。
实际做小车时我们常用定时器产生四路 PWM 信号,分别控制左右两侧电机的速度。72MHz 主频下,定时器预分频和自动重装值可以灵活配置,PWM 频率通常在 10kHz 到 20kHz 之间,这个区间既能避开 audible noise,又能让电机驱动芯片的开关损耗保持在可接受范围。文档里对定时器资源的利用是比较合理的,没有出现多个功能抢同一个定时器的情况,这是新手容易踩坑的地方。
2.2 144 个引脚里实际用到多少:IO 分配的艺术
STM32F103ZET6 有 112 个多功能双向 I/O 口,几乎所有端口都容忍 5V 输入。很多人会觉得 144 脚是大材小用,但实际上,智能小车外设数量不少:四路红外循迹、两路红外避障、一路超声波触发与回波、舵机 PWM、红外遥控接收头、OLED 或 LCD1602 显示、四个电机的方向脚和使能脚,粗算下来需要二十多路 GPIO。如果选 48 脚的 STM32F103C8T6,IO 数量会非常紧张,尤其在还要预留扩展接口的情况下。
文档的核心板设计思路是围绕 STM32F103ZET6 搭最小系统,把 IO 全部引出到排针,再通过驱动板上的接口排母对接。这种做法的好处是模块之间解耦:核心板坏了只换核心板,驱动板电路问题不影响单片机最小系统。我在做类似项目时也习惯把 GPIO 分配先做成表格,再开始画原理图,否则焊完线才发现两个功能模块共用了同一个引脚,就只能飞线解决。
2.3 L293D 电机驱动芯片:老方案但仍值得用
文档选用的电机驱动芯片是 L293D,这是一款比较老的芯片,但至今在很多教学项目中仍然常见。L293D 是双全桥驱动器,每通道输出电流 600mA,峰值 1.2A,内部自带续流二极管,可以省掉外围的二极管阵列。相比更现代的 TB6612,L293D 的缺点是导通压降大、效率低,5V 供电时输出到电机的电压会低于电源电压,但它的优势是逻辑电源和电机电源可以分开、直插封装方便手工焊接、芯片价格便宜、资料多。
对于四路直流减速电机,每个电机额定电流通常在几百毫安以内,L293D 四通道刚好驱动四个电机,每个电机用一路全桥。文档中驱动板包含电源电路、电机驱动电路、功能模块接口,这个架构是典型的模块化设计。如果后续要换更大功率电机,可以整板替换驱动部分,核心板不用动。
2.4 供电架构:两节 18650 如何变出 5V 和 3.3V 两条电压轨
整车的动力源是两节 18650 锂离子电池串联,电压约 7.4V。驱动板上有电源电路,先产生 5V 给 L293D 逻辑部分和传感器模块使用,再通过 AMS1117 线性稳压器降到 3.3V 给 STM32F103ZET6 单片机供电。文档中明确写了核心板从驱动板取 5V,然后经 AMS1117 转 3.3V,这样核心板本身不需要额外接电池。
这里有个需要注意的细节:AMS1117 的输入输出压差在 7.4V 降到 3.3V 时接近 4.1V,如果整机电流较大,线性稳压器的功耗会集中发热,所以文档把 5V 作为中间电压轨,让大电流负载尽量走 5V,3.3V 只供给单片机和小功率传感器。我在实际调试中给电池充满电时电压能到 8.1V 左右,AMS1117 输入端最好加一个 100uF 电解电容,否则电机启动瞬间的电压跌落容易让单片机复位。
3. 硬件电路拆解:核心板、驱动板、循迹避障板如何分工协作
3.1 核心板最小系统:时钟、复位、下载接口一个都不能省
文档的核心板设计围绕 STM32F103ZET6 的最基本工作条件展开。最小系统包含电源电路、复位电路、外部晶振电路、程序下载接口、USB 接口、指示 LED 和模式选择接口。外部晶振用的是 8MHz,经过内部 PLL 倍频后得到 72MHz 系统时钟,这个频率是 F103 系列的上限,倍频系数通常设为 9。
复位电路是典型的 RC 复位,复位引脚通过 10K 电阻上拉到 3.3V,并联一个 100nF 电容到地。下载接口用的是 SWD 还是 JTAG,文档没有强制指定,常用的做法是留出 4 个引脚:SWDIO、SWCLK、GND、3.3V,加上复位脚共 5 针,这样用 ST-Link 下载速度比串口 ISP 快很多,也稳定很多。文档里的程序下载软件用的是 mcuisp,属于串口 ISP 下载方式,这种方案只需要 UART 引脚和 BOOT0 引脚可以配合,适合没有 ST-Link 的场景。
3.2 驱动板接口逻辑:PWM 输入、方向控制脚与电机接线对应关系
驱动板是连接核心板与执行机构的中枢。L293D 的每个通道有使能脚和两个输入脚,使能脚接 PWM 信号用于调速,两个输入脚决定电机正转、反转还是刹车。对于四路电机,需要四路 PWM 和八个方向控制脚,另外还要注意 GND 必须与单片机共地。
以下是我整理的一套典型引脚对应关系,与文档中模块化思路一致:
| 信号 | 方向 | 说明 |
|---|---|---|
| PWM_LEFT / PWM_RIGHT | 单片机 → L293D 使能脚 | 调节左右侧电机速度 |
| IN1~IN4 | 单片机 → L293D 输入脚 | 控制电机正反转 |
| 5V / GND | 驱动板 → 传感器模块 | 红外传感器供电 |
| 3.3V / GND | 驱动板 → 核心板 | 单片机电源 |
接线时最容易出问题的是左右侧电机的方向定义。如果左侧电机的 IN1 接高电平是前进,右侧电机接高电平却是后退,小车一启动就会原地打转。我在调试这种四驱小车时,习惯先在代码里写一个短测试程序,逐个通道正转一秒、反转一秒,确认四个轮子的方向都一致之后再调 PWM 差速。
3.3 红外循迹避障板:四路循迹与两路避障的检测原理
文档中的红外循迹避障板集成了四路红外循迹电路和两路红外避障电路。循迹的基本原理是红外发射管发出的红外光照射到地面,黑色轨迹线反射率低,白色地面反射率高,接收管接收到的反射光强不同,经过比较器电路处理后输出高电平或低电平。四路探头通常排成一排放在小车前部,中间两路用于对准黑线,左右两路用于检测偏移方向。
避障电路原理类似,只是发射管和接收管朝向前方。当障碍物出现在正前方时,红外光被障碍物反射回接收管,输出电平变化,单片机据此判断前方有阻挡。这里有一个天生的局限:红外避障对黑色物体不敏感,因为黑色吸收红外光,反射率极低,所以文档在红外避障之外还设计了超声波避障方案作为补充,超声波对颜色不敏感,测距更精确。
4. 常见问题排查:硬件焊接和供电环节的五个典型坑
4.1 核心板不上电,AMS1117 输出不是 3.3V 而是 5V
现象:给驱动板接上 18650 电池后,测量 AMS1117 输出脚,电压等于输入电压,大约 7V 多,核心板无法工作。
原因:AMS1117 的 GND 引脚没有形成完整回路,或者输入端和输出端的滤波电容漏接,导致芯片处于异常状态。另一个常见原因是芯片引脚焊反,AMS1117 封装上输入输出相邻,方向搞错后输出直接等于输入。
解决:先用万用表确认封装方向,AMS1117 的丝印缺口对应第一脚。再检查输入端对地是否有一个 10uF 以上电容,输出端对地是否有 100nF 电容。如果两个电容都在且方向正确,断开负载后测量输出端空载电压应为 3.3V。
4.2 四轮转速不一致,小车直线行驶跑偏
现象:PWM 占空比设置完全相同的数值,但小车前进时明显往一侧偏,速度越快偏得越厉害。
原因:四个直流减速电机的机械特性存在个体差异,即使同样电压下转速也不完全相同。另外 L293D 四个通道的导通压降有离散性,电池电压在负载波动下也会影响实际输出。
解决:不能只依赖软件给相同的 PWM 值,需要在实际行驶中做校准。我一般会在代码里加一个速度校准参数数组,先让小车直线空转,观察偏转方向,把偏慢一侧的 PWM 占空比逐步上调,直到直线行驶稳定。文档虽然没有细讲校准方法,但它的模块化结构允许在驱动板接口处更换电机,排查很方便。
4.3 循迹传感器在强光下失灵,白线黑线判断翻转
现象:室内正常光照下循迹工作正常,搬到窗边或者阳光直射区域,小车开始乱转,传感器输出全部接近同一电平。
原因:红外循迹模块的红外接收管对自然光中的红外成分敏感,强光干扰会淹没反射信号的差异。不同批次的红外对管一致性也有差异,阈值电压固定后,某些探头在黑白线之间的输出摆幅不够。
解决:最有效的办法是给传感器加遮光罩,让探头贴近地面且避免环境光直射。同时把比较器的阈值调整到黑白线输出的中间值,而不是固定用厂家默认电位器位置。文档中的红外循迹电路有四个探头,每个探头独立可调,调试时用万用表分别测黑线、白线下的输出电压,取中间值作为翻转阈值。
4.4 电机启动瞬间单片机复位,系统重启
现象:小车静止时一切正常,按下启动按钮后,还没动起来就复位了,反复重启。
原因:电机启动电流是堵转电流的数倍,瞬间从电池抽取大电流,导致电源电压跌落。如果供电线路较长、导线细或者电池内阻偏大,跌落幅度会更明显。当电压跌到 AMS1117 的 dropout 范围以下时,STM32 供电不稳直接复位。
解决:在电池输出端并联一个大容量电解电容,比如 470uF 到 1000uF,作为瞬态能量缓冲。同时把电机电源和逻辑电源的布线分开,L293D 的电机电源引脚和逻辑电源引脚各走各的路径,最后在某一点汇合。文档中驱动板把电源电路和电机驱动分区域布局,就是为了减少这种相互干扰。
4.5 用 mcuisp 下载程序失败,提示无法连接
现象:第一次使用 mcuisp 串口下载时,软件能打开串口,但一直提示连接不上芯片,重试多次依然失败。
原因:STM32F103ZET6 的串口 ISP 下载需要 BOOT0 引脚拉高、BOOT1 拉低,复位后进入系统存储器模式。很多下载失败案例都是 BOOT0 跳线没有接对,或者串口的 TX/RX 没有交叉连接,还有是波特率设置过高导致握手失败。
解决:把 BOOT0 跳线拨到 1,保持 BOOT1 为 0,手动按一下复位按键,让芯片重新进入 ISP 模式。串口线必须交叉,单片机 TX 接 USB 转串口的 RX,单片机 RX 接 USB 转串口的 TX。mcuisp 里波特率先选 115200,连接成功之后再考虑提速。
5. 软件实现要点:PWM 调速、循迹判据与超声波避障状态机
5.1 定时器输出 PWM:TIM2 四通道配置与占空比计算
文档用到 STM32F103ZET6 的定时器输出多路 PWM,这是整个调速功能的基础。常见做法是用 TIM2 的 CH1 到 CH4,或者 TIM3 的四通道,配合预分频和自动重装寄存器生成 10kHz 到 20kHz 的 PWM 波。PWM 占空比由比较寄存器 CCRx 决定,占空比越大,电机平均电压越高,转速越快。
下面是一段典型的 PWM 初始化代码,使用 TIM2 四通道输出,分别接 L293D 的四个使能脚:
// PWM_Init: 初始化 TIM2 四路 PWM 输出 void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能 TIM2 和 GPIOA 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA0~PA3 复用推挽输出,对应 TIM2_CH1~CH4 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 72MHz 经 72 分频得到 1MHz 计数频率 // 自动重装值 100,输出 10kHz PWM TIM_TimeBaseStructure.TIM_Period = 100 - 1; TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 四路 PWM 模式 1,占空比由 CCRx 决定,初始 50% TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 50; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_OC2Init(TIM2, &TIM_OCInitStructure); TIM_OC3Init(TIM2, &TIM_OCInitStructure); TIM_OC4Init(TIM2, &TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); }这段代码的逻辑是:先把 72MHz 系统时钟做 72 分频,定时器计数频率变成 1MHz,再让计数器从 0 数到 99,每 100 个计数溢出一次,所以 PWM 频率是 10kHz。CCR 写入 50,对应占空比 50%,电机处于半速。如果要把转速提到 80%,修改 CCR 为 80 即可。
实际使用时,方向控制脚 IN1 到 IN4 不能和 PWM 使能脚共用。PWM 只控制通断时间占比,方向由另一组 GPIO 决定。文档中的程序流程也按这个思路组织:先配置 GPIO 方向脚,再初始化 PWM,最后进入主循环根据传感器状态修改 CCR 值。
5.2 循迹逻辑:四路探头读数与转向差速的对应关系
循迹功能是智能小车最经典的场景。四路红外探头水平排列,中间两路瞄准黑线,左右两路负责检测偏移。当所有探头都在白底区域时,说明小车已经完全偏离线路,需要原地回退或大角度转向。常见判据是中间两路压线时直行,左侧探头出线时说明车辆右偏,需要加大左轮速度,反之亦然。
// Track_Follow: 根据四路循迹传感器输出控制左右轮速度 void Track_Follow(uint8_t s1, uint8_t s2, uint8_t s3, uint8_t s4) { // s1~s4 从左到右排列,输出 0 表示压黑线,1 表示在白底 if (s2 == 0 && s3 == 0) { // 中间两路压线,直行,左右轮等速 Set_Speed(BASE_SPEED, BASE_SPEED); } else if (s1 == 0) { // 最左侧探头压线,说明车头右偏,左轮减速或右轮加速 Set_Speed(BASE_SPEED - TURN_DELTA, BASE_SPEED + TURN_DELTA); } else if (s4 == 0) { // 最右侧探头压线,说明车头左偏,右轮减速或左轮加速 Set_Speed(BASE_SPEED + TURN_DELTA, BASE_SPEED - TURN_DELTA); } else { // 四路全白,丢失黑线,原地转圈找回 Set_Speed(-BASE_SPEED, BASE_SPEED); } }这里 Set_Speed 函数内部会根据参数正负决定 IN1 到 IN4 的方向电平,再根据传入速度值修改 TIM2 的 CCR1 到 CCR4。TURN_DELTA 是转向修正常量,一般取 BASE_SPEED 的 30% 到 50%;太小了修正强度不够,小车会冲出黑线,太大了车辆左右摇摆明显。文档中把这个逻辑画成了程序流程图,顺着流程走一遍就很容易理解状态切换条件。
循迹调试最直接的方法是先用串口把四路探头数值打出来,让小车手动推着走一圈,确认每个探头在黑白区域的输出稳定,再启用自动循迹。不要直接铺上赛道就跑,否则一旦探头阈值不对,小车会立刻乱窜,而且很难判断是硬件问题还是逻辑问题。
5.3 超声波避障:带舵机扫描的前方障碍物判断
文档中的超声波避障方案把测距模块装在车头,通过舵机控制朝向。平时超声波模块正对前方,连续测量距离;当距离小于安全阈值时,小车停车,然后舵机向左转 60 度测一次、向右转 60 度测一次,比较两侧距离,哪边空旷就往哪边转向,从而实现绕开障碍物。
// Obstacle_Avoid: 超声波测距并决策转向方向 void Obstacle_Avoid(void) { float dist = Get_Distance(); // 前方障碍距离, 单位 cm if (dist > SAFE_DISTANCE) { Set_Speed(BASE_SPEED, BASE_SPEED); // 安全距离外直行 return; } Set_Speed(0, 0); // 停车 Servo_Set_Angle(60); // 舵机转向左边 delay_ms(300); // 等待舵机到位 float dist_left = Get_Distance(); Servo_Set_Angle(-60); // 舵机转向右边 delay_ms(300); float dist_right = Get_Distance(); Servo_Set_Angle(0); // 舵机回正 if (dist_left > dist_right) { Set_Speed(-BASE_SPEED * 0.5, BASE_SPEED * 0.5); // 向左旋转 } else { Set_Speed(BASE_SPEED * 0.5, -BASE_SPEED * 0.5); // 向右旋转 } }Get_Distance 函数通过依次拉高和拉低 TRIG 引脚产生一个 10us 以上的起始脉冲,然后测量 ECHO 引脚高电平持续的时间,声速 340m/s,时间除以 58 就是厘米距离。舵机控制用的是另一个定时器输出 50Hz 的 PWM,脉宽在 0.5ms 到 2.5ms 之间对应 0 度到 180 度。这里要注意舵机相对超声波模块的安装方向,转左还是转右必须在车上实测确认,不是所有舵机安装方向都一致。
超声波避障的红外避障逻辑在文档中也有,红外避障更适合近距离检测,超声波适合中远距离,两者结合可以覆盖更全面。在程序里把红外避障的判断放在超声波之前,先用快速响应的红外检测紧急情况,再用超声波做精细避障路径规划,实用效果更好。
6. 串口打印调试法:把小车跑稳的最后一道保障
调试智能小车时,我最常用的工具不是示波器,而是串口打印。STM32F103ZET6 有多个 USART 接口,随便选一个空闲的串口通过 USB 转 TTL 连接到电脑,把传感器原始值、PWM 占空比、当前模式状态循环输出到串口终端,就能实时看到小车内部状态变化。
// 串口发送一个字符串, 用于调试输出 void Debug_Send(const char *str) { while (*str) { USART_SendData(USART1, *str++); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } }这段代码的核心是利用 USART1 的发送数据寄存器状态位,逐字节发送字符串。调试时在主循环里每隔 100ms 调用一次,打印四路循迹探头的电平、超声波距离、左右轮 PWM 值,这样小车出问题的时候,看串口数据就能定位是传感器异常还是控制逻辑异常。
我的习惯是:每次改动硬件接线或者换了新电机,都强制走一遍串口校准流程,把四路探头阈值重新测一遍,把左右轮等速 PWM 值重新配一遍,确认数据无误后再跑完整功能。有一次我换了一组新电池,电压高了 0.8V,循迹阈值全部漂移,如果不看串口数据根本想不到是电源问题导致传感器翻转点改变。从那以后我每次启动调试都先开串口助手,看 30 秒数据再放车上路,这个习惯帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取