简介:本资源是一套完整的STM32智能小车多功能开发套件,面向嵌入式初学者、课程设计学生及电子竞赛备赛者,解决多模态智能控制功能集成难、代码移植性差、硬件选型无依据等实际问题。压缩包共2000个文件,75.35MB,涵盖889个头文件(h)、325个源码文件(c)及大量编译中间文件(o、d、crf、axf、hex等),主体采用STM32 HAL库+CubeMX生成框架,所有功能模块均带中文注释,结构清晰、接口规范,便于理解与向标准库迁移。目前已获2775人学习下载,覆盖循迹、红外/超声波避障、PID跟随、编码器测速、蓝牙串口遥控、WiFi远程控制、4G联网通信及LD3320语音识别等八大核心功能,配套完整硬件清单与引脚定义说明,可直接用于课程实验、毕业设计或智能车竞赛原型开发。 玩单片机这些年,陆陆续续做过不少小东西,但要说最能练手、最考验综合能力的项目,STM32智能小车绝对排得上前列。一个底盘上同时塞下循迹、跟随、避障、测速、蓝牙遥控、WiFi图传、4G远程、语音识别这些功能,基本把嵌入式开发里常见的传感器采集、电机控制、通信协议、状态机设计全过了一遍。这篇文章就把我完整做完的这版智能小车从硬件选型到各功能模块的代码实现,从头到尾梳理一遍,所有例程都是实测跑通的,硬件清单也会完整列出来。
很多朋友刚开始接触STM32小车,容易犯一个毛病:上来就买一堆模块,结果接线接得乱七八糟,代码东拼西凑,最后哪个功能都没调通。我当初也是这么过来的,所以这篇文章的顺序就按照“先整体设计、再硬件选型、然后逐个功能模块拆解、最后给调试经验”来写。不管你是准备做课程设计,还是想自己DIY一台功能齐全的小车,按这个思路走基本不会翻车。
1. 项目整体设计与系统架构
1.1 功能模块总览
这辆小车最终实现的八大功能,我列一下:
- 循迹:通过红外反射传感器识别赛道黑线,自动循迹行驶
- 跟随:基于超声波测距,动态跟踪前方移动目标
- 避障:超声波传感器扫描前方障碍物,自动规划转向绕行
- 测速:编码器+定时器输入捕获,实时测量左右轮转速
- 蓝牙:HC-05模块,手机App发送指令手动控制
- WiFi:板载ESP8266模块,局域网内通过TCP/HTTP协议远程控制
- 4G:板载4G Cat-1模块,实现公网远程控制和数据上报
- 语音识别:离线语音识别模块,直接喊话控制小车动作
这八个功能同时挂在一个STM32F103C8T6上,CPU主频72MHz,Flash 64KB,RAM 20KB,说紧张也紧张,说够用也够用。关键是要做好资源分配和模式切换,不能所有外设同时满载跑。
1.2 主控选型与资源分配
主控用的是最经典的STM32F103C8T6,也就是大家常说的“最小系统板”。为什么选它?便宜、资料多、引脚够用,而且网上例程一抓一大把。虽然现在STM32F407、H750性能更强,但对于这个小车项目来说,F103完全撑得住。
引脚资源分配我是这么规划的:
- PA0-PA3:编码器A/B相输入,TIM2和TIM3的通道1/2
- PA6-PA7:PWM输出,TIM3通道1/2,控制左电机
- PB0-PB1:PWM输出,TIM4通道3/4,控制右电机
- PA8-PA11:循迹模块5路数字信号输入
- PB6-PB7:I2C或UART接口,接OLED显示
- PB10-PB11:USART3,接蓝牙HC-05
- PA2-PA3:USART2,接ESP8266 WiFi模块
- PC10-PC11:USART4,接4G模块或语音识别模块
- PB12-PB14:SPI接口预留,方便后续扩展
实际接线时可以根据手上的模块微调,但一个原则必须守住:定时器的PWM输出通道和编码器输入通道不要冲突,否则调到你怀疑人生。
1.3 分时调度与工作模式切换
八个功能不可能同时全开,比如循迹的时候蓝牙遥控指令来了,到底听谁的?这就需要定好优先级。我用的方案是:外加一个拨码开关,手动选择当前工作模式:
- 模式1:自动演示(循迹优先,避障次之)
- 模式2:遥控模式(蓝牙/WiFi/4G指令优先)
- 模式3:跟随模式
- 模式4:语音控制模式
在代码里就是一个mode变量,主循环里根据mode的值调用不同的控制逻辑。这样各功能模块互不干扰,调试的时候也省心。
2. 硬件清单与接线要点
2.1 完整硬件清单
下面这张表是我实际用到的所有硬件,型号和数量都标出来了,照着买就行。
| 硬件 | 型号/规格 | 数量 | 用途说明 |
|---|---|---|---|
| 主控板 | STM32F103C8T6最小系统板 | 1 | 主控制器 |
| 电机驱动 | TB6612FNG | 1 | 驱动两个直流电机 |
| 直流电机 | 带AB相霍尔编码器,1:30减速比 | 2 | 动力+测速 |
| 小车底盘 | 2驱或4驱亚克力板底盘 | 1 | 车体平台 |
| 电池 | 7.4V 18650锂电池组 | 1 | 整机供电 |
| 降压模块 | LM2596S可调降压模块 | 2 | 输出5V给主控和传感器 |
| 循迹模块 | TCRT5000红外循迹传感器 | 5 | 循迹检测黑线 |
| 超声波模块 | HC-SR04 | 1 | 测距(避障/跟随) |
| 舵机 | SG90 | 1 | 超声波云台(可选) |
| 蓝牙模块 | HC-05 | 1 | 手机近距离遥控 |
| WiFi模块 | ESP8266-01S | 1 | 局域网/云端控制 |
| 4G模块 | 移远EC200S或Air724UG | 1 | 公网远程通信 |
| 语音识别模块 | LD3320或ASR01离线语音模块 | 1 | 离线语音指令识别 |
| 显示器 | OLED 0.96寸 I2C | 1 | 显示速度/模式/距离 |
| 蜂鸣器 | 有源蜂鸣器模块 | 1 | 提示音 |
| 开关 | 自锁开关 | 1 | 电源总开关 |
| 杜邦线 | 公母/公公若干 | 若干 | 接线 |
其中4G模块如果只是本地实验,可以先用SIM7600CE或者EC200S这类Cat-1模块,插一张物联网卡就能跑。注意:4G模块功耗比蓝牙WiFi高不少,供电要单独从降压模块出,别跟主控抢电,否则容易复位。
2.2 供电系统设计
供电是整个系统最容易出问题的地方。我刚开始直接把7.4V电池接到主控板Vin口,结果电机一转,芯片就复位,OLED屏闪几下就重启了。原因很简单:电机启动瞬间电流大,把电源电压拉垮了。
正确的供电方案应该是这样:
- 7.4V电池出线,先接总开关,然后分两路
- 一路直接给TB6612的VM供电(电机电源)
- 另一路经过LM2596降压到5V,给STM32主控板、传感器模块供电
- 主控板上的3.3V稳压再给ESP8266、OLED等模块供电
如果你用的是四驱底盘,电机电流更大,建议在电池和驱动板之间加一个大电容(1000μF以上),能有效抑制电压波动。
2.3 电机驱动与编码器接线
TB6612是MOS管驱动芯片,比老掉牙的L298N效率高、体积小,我强烈推荐。TB6612接线:
- VM接电池正极(7.4V)
- VCC接5V逻辑电源
- GND共地,必须和主控共地
- PWMA/PWMB接STM32的PWM输出引脚
- AIN1/AIN2、BIN1/BIN2接GPIO控制方向
- A01/A02接左电机,B01/B02接右电机
编码器电机一共6根线:电机两根(粗线),编码器两根电源(红黑),编码器AB相两根(白绿或黄蓝)。编码器电源一般接5V或者3.3V,具体看电机型号,我用的是5V供电,输出信号经分压后进STM32。如果编码器输出5V电平直接接引脚,STM32也能容忍,但稳妥起见加个10K电阻分压更安全。
接线这里有一个经常犯的错:编码器A/B相接错定时器通道。STM32的定时器输入捕获必须接到对应通道上,比如TIM2_CH1是PA0,TIM2_CH2是PA1,如果你把A相接PA1、B相接PA0,也能测出速度,但方向就反了,最后PID控制就乱套。建议统一:左电机编码器接TIM2,右电机接TIM3,对应的PA0/PA1、PA6/PA7。
3. 核心功能模块一:循迹系统
3.1 循迹传感器的原理与安装
循迹模块我用的是TCRT5000,本质是一个红外发射管加一个接收管。红外光照射到黑色表面时吸收多、反射少,接收管输出高电平;照射到白色地面时反射强,输出低电平。这样就能通过电平判断车轮是压在黑线上还是白地上。
5路循迹传感器的安装位置很有讲究。我一开始把5个传感器排成一条直线装在车头正前方,结果遇到小角度弯道时,车还没转过来,传感器已经全部偏出黑线,直接冲出赛道。后来改成弧形排列,中间三个靠近车头、两边两个稍微后缩,这样能更早感知弯道方向。
3.2 5路循迹的阈值校准
每个传感器的灵敏度不一样,安装高度、环境光也不一样,所以不能直接拿别人代码里的阈值硬套。我第一次用网上抄来的阈值,小车在深色地板上跑,全部传感器都输出低电平,直接“失明”。
校准方法很简单:把车放在白地上,逐个读取传感器的模拟量,记下白底值;再放到黑线上,记下黑线值,取中间值作为阈值。如果你用的是数字量输出的循迹模块,模块上有个电位器可以调节灵敏度,调到指示灯临界翻转的位置就行。
代码里我初始化时读一轮传感器,动态设置阈值:
uint16_t threshold[5] = {1500, 1500, 1500, 1500, 1500}; // ADC阈值 void LineSensor_Calibrate(void) { // 分别在白底和黑线上采样,更新阈值 for (int i = 0; i < 5; i++) { uint16_t white = Read_ADC(i); // 读白底值 Delay_Ms(50); uint16_t black = Read_ADC(i); // 读黑线值 threshold[i] = (white + black) / 2; } }3.3 循迹控制算法:从开关量到PID
最简单的循迹逻辑就是“开关量判断”:哪边传感器压到黑线,就往哪边打方向。这在小角度直道上还行,一到S弯就左右摇摆、冲出赛道。
我后来上了PID控制,效果立竿见影。核心思路:根据5路传感器的输出状态,计算一个“位置偏差值”,偏差范围比如从-100(最左)到+100(最右),中间对应-0。然后把这个偏差喂给PID控制器,输出调整量去修正左右电机的PWM占空比。
位置偏差的计算我推荐加权平均法。5路传感器从左到右分配权重-100、-50、0、50、100,哪一路检测到黑线(低电平),就用对应的权重值。多个传感器同时检测时取平均。
int8_t Get_LinePosition(void) { // 返回 -100~100 的位置偏差 int16_t weight_sum = 0; int16_t weight_total = 0; int8_t weights[5] = {-100, -50, 0, 50, 100}; for (int i = 0; i < 5; i++) { if (LineSensor_Read(i) == 0) { // 检测到黑线 weight_sum += weights[i]; weight_total++; } } if (weight_total == 0) { return -128; // 丢失黑线,返回特殊值 } return weight_sum / weight_total; }PID参数是调出来的,不是算出来的。我的经验是先从P开始调:P设小一点(比如0.5),观察小车转弯响应,慢慢加到2.0左右;然后再加I消除稳态误差,I设0.01就开始有效果;D用来抑制震荡,设0.1起步。注意每个项目底盘重量、电机响应速度不同,参数不能复制粘贴,必须现场调。
3.4 弯道丢失黑线的处理
循迹小车跑丢线是家常便饭。以前我的处理是直接停车,后来改进了一下:检测到所有传感器都离开黑线时,不立即停车,而是根据上一次的位置偏差方向,继续打方向跑一小段时间,如果还找不到线再停车。
// 主循环中的丢线处理 if (position == -128) { // 上次偏差在左边,继续左转找回黑线 Set_PWM(LEFT_SPEED - 30, RIGHT_SPEED + 30); lost_time++; if (lost_time > 100) { Motor_Stop(); } } else { lost_time = 0; }这个策略效果很明显,过急弯的通过率从五成提升到九成以上。
4. 核心功能模块二:测速与电机闭环控制
4.1 霍尔编码器与定时器测速原理
测速这部分是很多新手最懵的。编码器电机内部有一个码盘,电机转的时候码盘跟着转,霍尔传感器检测码盘上的磁极变化,输出方波脉冲。单位时间内数脉冲个数,就能算出转速。
STM32定时器有个编码器模式,硬件上直接解码A/B两相脉冲,不需要外部中断不停计数,解放CPU。初始化流程:
- 配置GPIO为复用推挽输入
- 配置TIM2/TIM3为编码器模式
- 设置计数范围,溢出周期
- 读取CNT寄存器得到脉冲数
关键代码:
void Encoder_Init(TIM_TypeDef* TIMx) { TIM_EncoderInterfaceConfig(TIMx, TIM_ENCODERMODE_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_SetAutoreload(TIMx, 65535); TIM_Cmd(TIMx, ENABLE); } int16_t Encoder_Get_Count(TIM_TypeDef* TIMx) { int16_t count = TIMx->CNT; TIMx->CNT = 0; // 读完后清零,下次重计 return count; }4.2 速度换算公式
从脉冲数到实际速度的换算公式:
转速(r/min) = 脉冲数 / (编码器线数 × 减速比 × 定时器采样周期(s) / 60)拿我用的电机举例:编码器线数13线(即每圈输出13个脉冲),减速比1:30,定时器每50ms读一次。那么:
- 电机轴每转一圈,编码器输出 = 13 × 30 = 390个脉冲
- 如果50ms内读到390个脉冲,说明电机输出轴转了一圈
- 轮子直径65mm,周长 = 0.065 × 3.14159 ≈ 0.204m
- 1分钟转60圈,速度 = 0.204 × 60 = 12.24 m/min
这个换算在PID速度闭环里用于给目标值赋值。比如你想让小车跑0.3m/s,目标就是每秒约1.47圈,对应50ms脉冲数约29个。
4.3 速度闭环PID控制算法实现
没有PID速度闭环时,小车电池电量足就跑得快、亏电了就跑得慢,直线根本走不直。加上速度闭环后,两个轮子各搞一个PID,让实际速度跟随目标速度。
速度环PID用增量式PID,输出的是PWM占空比的增量,这样不需要累加误差值,代码简单:
int16_t Speed_PID(int16_t target, int16_t current) { static int16_t error_prev = 0; static int32_t error_sum = 0; int16_t error = target - current; float Kp = 0.3f, Ki = 0.05f, Kd = 0.1f; error_sum += error; if (error_sum > 3000) error_sum = 3000; // 积分限幅 if (error_sum < -3000) error_sum = -3000; int16_t delta = Kp * error + Ki * error_prev + Kd * (error - error_prev); error_prev = error; return delta; // 叠加到原PWM上 }这个函数每个控制周期调用一次,控制周期可以用定时器中断固定10ms。PWM的限幅一定做好,我实测占空比超过90%电机就进入非线性区,再往上加也没意义,反而容易烧驱动。
4.4 直行跑偏的滤波处理
编码器数据会有少量抖动,尤其车轮空转或路面打滑时。我用了一阶低通滤波:
int16_t filtered_speed = 0.7f * filtered_speed + 0.3f * raw_speed;这个公式虽然简单,但能把高频抖动滤掉大半。直行时右轮和左轮的目标速度设成相同值,依靠PID分别闭环,就能保持相对直线。
5. 核心功能模块三:避障与跟随
5.1 超声波测距时序与代码实现
HC-SR04用起来就是一个“触发-回响”的过程:给Trig引脚一个10μs以上的高电平脉冲,模块内部发出8个40kHz声波脉冲,同时Echo引脚拉高;声波碰到障碍物返回,模块收到回波后Echo引脚拉低。Echo高电平的时间就是声波往返的时间。
距离(mm) = Echo高电平时间(μs) / 58
为什么除以58?因为声速约340m/s,往返距离是声波实际传播路径的两倍。Echo时间换算成距离:如果是1ms,声波走了340mm,实际距离是170mm,而1ms = 340/1000 mm/μs = 0.34mm/μs,除以2得到0.17mm/μs。所以距离 = 时间(μs) × 0.17mm ≈ 时间/5.88 ≈ 时间/58(单位mm)。这个近似在工程上完全够用。
代码用输入捕获或者直接延时读取都可以,我用了外部中断测量高电平宽度:
void Ultrasonic_Trigger(void) { GPIO_SetBits(GPIOB, GPIO_Pin_9); // Trig拉高 Delay_Us(20); GPIO_ResetBits(GPIOB, GPIO_Pin_9); // 拉低 } // 在Echo引脚的外部中断回调中测量高电平时间,然后换算距离 uint32_t echo_time; float Get_Distance_cm(void) { return echo_time * 0.017f; // 时间μs * 0.017 = 厘米 }5.2 避障控制策略:分段决策
避障只靠单一距离判断,小车会像无头苍蝇一样乱撞。我用的策略是把前方距离分成三个区间:
- 距离超过40cm:安全,全速前进
- 距离20-40cm:减速,同时看左右传感器数据决定微调方向
- 距离小于20cm:危险,停车后判断障碍物方位
如果超声波是固定的(只测正前方),那遇到侧面来车就检测不到。所以我加了一个SG90舵机,每0.5秒扫一次角度(0度、90度、180度),形成简单的三方向雷达。
舵机角度控制PWM占空比:SG90的周期是20ms,0度对应0.5ms高电平,90度对应1.5ms,180度对应2.5ms。配置定时器输出比较值换算一下就行。
5.3 跟随模式实现
跟随和避障的硬件一样,但控制逻辑完全相反:避障是遇到障碍物躲开,跟随是遇到障碍物保持距离跟着走。
我的跟随逻辑是:
- 距离大于30cm:加速前进
- 距离15-30cm:停车或缓慢前进
- 距离小于15cm:后退
为了让跟随更平滑,我把PID控制移植过来了:目标距离25cm,实际距离作为反馈,PID输出量控制小车速度。这样目标人物走快走慢,小车都能平滑跟随,不会一冲一停。
5.4 多传感器数据融合的小心得
超声波测距有一个弱点:对吸音材料(比如海绵、毛绒玩具、衣物)测不准,经常跳变。所以跟随模式下,我用滑动平均滤波,连续采5个数据取中值,基本能滤掉异常值。
uint16_t Sonar_Median_Filter(void) { uint16_t buf[5]; for (int i = 0; i < 5; i++) { buf[i] = Sonar_Read(); Delay_Ms(10); } // 冒泡排序取中值 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4 - i; j++) { if (buf[j] > buf[j+1]) { uint16_t tmp = buf[j]; buf[j] = buf[j+1]; buf[j+1] = tmp; } } } return buf[2]; }这个中值滤波比平均值滤波更能抗干扰,因为超声波偶尔会出现一个特别大或特别小的错误值,平均值会被拉偏,中值不会。
6. 无线通信模块:蓝牙、WiFi、4G、语音识别
6.1 蓝牙HC-05配置与手机遥控
HC-05是我最早调通的无线模块。上电后模块有两种状态:快闪是AT命令模式,慢闪是透传模式。默认波特率9600,长按模块上的按键上电就能进入AT模式。
AT模式常用指令:
- AT:测试通信,返回OK
- AT+NAME=Car:改蓝牙名称
- AT+PSWD=1234:设置配对密码
- AT+UART=115200,0,0:修改波特率
配置好之后进入透传模式,手机装一个“蓝牙串口助手”类的App,连接小车的蓝牙,发送指令控制小车。串口中断接收代码:
void USART3_IRQHandler(void) { uint8_t cmd; if (USART_GetITStatus(USART3, USART_IT_RXNE)) { cmd = USART_ReceiveData(USART3); // 解析指令 switch (cmd) { case 'F': Motor_SetSpeed(300, 300); break; // 前进 case 'B': Motor_SetSpeed(-300, -300); break; // 后退 case 'L': Motor_SetSpeed(-200, 200); break; // 左转 case 'R': Motor_SetSpeed(200, -200); break; // 右转 case 'S': Motor_SetSpeed(0, 0); break; // 停止 } } }蓝牙模块的连接不上问题,九成是波特率不匹配、共地没接、或者模块处在AT模式没切回来。HC-05上电时如果按键被按住(或者KEY引脚拉高),模块就一直在AT模式,这模式下手机是搜不到蓝牙的。
6.2 WiFi模块:局域网与TCP控制
ESP8266-01S虽然体积小,但功能完整,支持TCP/UDP/HTTP。我用它做两件事:一是连接手机热点或路由器,手机通过TCP客户端发指令;二是通过HTTP模式发指令,方便后期接入物联网平台。
ESP8266用AT指令控制,给串口发AT指令,模块就会连接WiFi网络。流程是:
- 重启模块:AT+RST
- 设置工作模式:AT+CWMODE=1(Station模式)
- 连接路由器:AT+CWJAP="SSID","密码"
- 建立TCP服务器或客户端:AT+CIPMUX=1,AT+CIPSERVER=1,8080
然后用手机或电脑的网络调试助手连接模块的IP,发指令控制小车。代码里要处理TCP数据带有的 +IPD 头,这是很多新手被卡住的地方。比如Arduino发过来的数据是+IPD,0,5:hello,需要先用冒号定位,再取后面的数据。
我踩过最大的坑是供电:ESP8266发射瞬间电流接近300mA,如果稳压芯片输出能力不足,会直接掉电重启,表现为模块连不上WiFi、发送AT指令超时。解决办法是给ESP8266单独用一个3.3V稳压芯片供电,不要在STM32板的3.3V引脚上硬扛。
6.3 4G模块:公网远程控制
如果说WiFi是局域网控制,那4G模块就是真正的公网远程控制。我在这个项目中用的是移远EC200S模块,支持Cat-1网络,插一张物联网卡就能上公网。4G模块和WiFi模块的代码逻辑很像,都是串口发AT指令,但多了拨号、找网、APN设置等步骤。
核心AT指令流程:
- AT\n 测试模块响应
- AT+CPIN? 检查SIM卡状态,返回READY表示卡识别成功
- AT+CSQ 查信号强度,返回类似 +CSQ: 18,0 就是有信号
- AT+CEREG? 查网络注册状态
- AT+CGDCONT=1,"IP","CMIOT" 设置APN
- AT+CGACT=1,1 激活PDP上下文
APN这个坑很大,不同运营商的物联网卡APN不一样。移动一般用CMIOT,联通用UNIM2M,电信用CTLTE。写错APN就是连不上网,但返回的错误信息还要看具体的模块固件。
4G拨号成功后,就可以用AT+CIPSTART建立TCP连接,连到公网服务器或者物联网云平台。我搭了一个简单的TCP服务器,小车定时上报电池电压和速度数据,手机端通过MQTT服务器发控制指令,小车就能在全球任何一个有4G信号的地方被控制。
6.4 语音识别模块:离线语音控制
语音识别我试验过两种方案。一种是LD3320,非特定人语音识别,不需要训练,但识别率一般而且价格略高。另一种是现在很火的离线语音识别模块(比如ASR01),它需要电脑端SDK训练词条,烧录到模块里,识别率非常高,而且价格便宜。
ASR01模块用起来很简单:接好串口后,模块通过麦克风听到唤醒词,然后进入识别状态,识别成功后通过串口把对应的指令码发出来。比如我训练的指令:
| 用户说 | 模块串口输出 |
|---|---|
| “小科小科” | 0xAA 0x01(唤醒) |
| “前进” | 0xAA 0x02 |
| “后退” | 0xAA 0x03 |
| “左转” | 0xAA 0x04 |
| “右转” | 0xAA 0x05 |
| “停止” | 0xAA 0x06 |
| “循迹” | 0xAA 0x07 |
| “避障” | 0xAA 0x08 |
主控串口收到0xAA开头的数据就做二次解析,把它当成一个遥控指令处理。整个流程和蓝牙遥控非常像,所以代码复用的程度很高,我专门封装了一个RemoteControl_Parse(uint8_t *buf, uint8_t len)函数,蓝牙、WiFi、4G、语音识别模块的指令最终都汇聚到这个函数里统一处理。
这样设计的好处是:新增一种通信模块,只要改串口初始化,其他控制逻辑完全不动。
6.5 多通信模块共存:串口资源分配
蓝牙、WiFi、4G、语音识别模块都是串口通信,而STM32F103C8T6只有3个USART和2个UART。串口分配如下:
- USART1:PA9/PA10,调试串口,连接USB转TTL
- USART2:PA2/PA3,接ESP8266 WiFi
- USART3:PB10/PB11,接HC-05蓝牙
- UART4:PC10/PC11,接4G模块
- UART5:PC12/PD2,接语音识别模块
问题来了:STM32F103C8T6没有引出PD2到排针(我用的板子),所以语音识别模块我改到USART2复用,WiFi用SPI接口接的ENC28J60网卡方案(这是另一套方案)。如果你也遇到串口不够用的情况,有两个解决方法:一是用支持更多串口的STM32F103ZET6,二是用软件模拟串口库。我建议直接换ZET6,软件模拟串口在波特率波动大时容易丢数据。
7. 软件架构与代码组织
7.1 分层结构与代码目录
代码写到3000行以上之后,再往前翻找函数就痛不欲生。我强烈建议按模块分文件夹组织代码:
Project/ ├── Core/ │ ├── main.c │ ├── stm32f1xx_it.c ├── Hardware/ │ ├── motor.c │ ├── encoder.c │ ├── ultrasonic.c │ ├── linesensor.c │ ├── servo.c ├── APP/ │ ├── control.c │ ├── pid.c │ ├── remote.c │ └── mode_switch.c ├── Middlewares/ │ ├── oled.c │ ├── bluetooth.c │ ├── wifi.c │ └── lte.c硬件驱动层只管读写寄存器、初始化外设,不关心业务逻辑;应用层调用驱动接口,实现具体的控制流程。这样调试循迹时不用管蓝牙,调蓝牙时不用管电机,非常清爽。
7.2 主循环调度:状态机设计
主循环我用状态机组织,每一帧检查传感器和通信数据,然后根据模式执行对应动作:
while (1) { Read_Sensors(); // 1. 采样所有传感器 Process_Remote(); // 2. 处理远程指令 Update_OLED(); // 3. 刷新显示 switch (work_mode) { case MODE_LINE: Run_LineRobot(); break; case MODE_AVOID: Run_Avoid(); break; case MODE_FOLLOW: Run_Follow(); break; case MODE_REMOTE: Run_Remote(); break; case MODE_VOICE: Run_Voice(); break; } }状态机的好处是逻辑清晰、可读性强、后期加新功能只需要新增一个case。
8. 常见问题排查与调试经验实录
8.1 循迹类问题
现象:传感器全部输出高电平(没检测到黑线)排查步骤:先量传感器供电是否正常(5V)→ 用万用表量输出引脚在黑白两色下的电平是否变化 → 调节电位器 → 检查安装高度(一般1-2cm最佳)。我遇到过一次是传感器全部接反了,输出端焊到了VCC上,怎么调都没用。
现象:小车循迹跑S型乱摆原因:P参数过大。先把PID的I、D归零,P降到0.3左右,跑起来如果还是摆,继续降到0.1。稳定后逐步加P,直到出现临界震荡,再回调到震荡值的60%.
8.2 电机与编码器类问题
现象:电机不转或转一下停一下先确认DIR和PWM引脚的组合是否正确——TB6612如果IN1=IN2=0,电机刹车,如果IN1=IN2=1,电机也是刹车。只有IN1≠IN2才转动。另外PWM频率不能太低,我设的是10kHz,太低会有明显噪音和扭矩下降。
现象:编码器读数为0先用手拨动车轮,检查CNT寄存器的值是否变化。如果不变,大概率是编码器A/B相没有接到定时器的正确通道,或者编码器供电没接。
8.3 无线通信类问题
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 蓝牙连接不上 | 模块在AT模式 | 重新给KEY引脚低电平后再上电 |
| 蓝牙连上但收不到数据 | 波特率不匹配 | 检查手机端和模块波特率是否一致 |
| WiFi AT指令超时 | 模块供电不足 | 独立3.3V电源供电,加100μF电容 |
| WiFi收到乱码 | 数据格式未解析 | 正确截取+IPD后的数据段 |
| 4G信号弱 | 天线没接好 | 检查IPEX天线是否扣紧,天线朝外 |
| 4G无法附着网络 | APN错误 | 确认运营商APN并修改 |
| 语音识别不响应 | 未配置唤醒词 | 重新烧录语音模型 |
8.4 供电类问题
现象:电机启动瞬间单片机复位这是最典型的供电问题。处理顺序:先量电池空载电压是否足够 → 用示波器看电机启动瞬间的电压跌落 → 在电池输出加1000μF电容 → 将驱动板电源和主控电源完全分开。我最后换了两个LM2596,一路专给电机驱动VM,一路专门给逻辑电路,系统再也没复位过。
8.5 超声波类问题
现象:超声波测距稳定在0或者一个大于量程的固定值HC-SR04测量结果为0,通常是Echo引脚没有正确捕获高电平时间,检查回波信号是否真的到了。测量结果为固定值比如400多厘米,通常是回声没回来,模块内部超时溢出。环境有强噪声或者探头被灰盖住也会这样。另外要留意超声波工作电压,接了5V才能正常工作,3.3V供电会出现各种各样诡异的输出。
9. 项目总结与后续扩展
这个项目做下来,我最大的感受是:STM32智能小车不是一个“跑通就完事”的项目,而是一个可以不断升级深挖的平台。八个功能全部调通,你对STM32的定时器、串口、中断、PWM、PID控制、通信协议这些知识的掌握程度,基本就到“熟练”级别了。
如果你做完这个基础版本想继续扩展,我列几个方向供参考:
第一,摄像头循迹。把红外循迹换成OpenMV或者K210摄像头,识别精度和场景适应性都会上一个台阶,但随之而来的是更大的计算量,建议换STM32H750或者加一个协处理器。
第二,加入RTOS。8个功能同时跑的时候,裸机主循环的实时性会打折扣。把FreeRTOS移植进来,超声波测距任务、PID控制任务、通信任务分别用不同的优先级调度,系统的响应会更稳定。
第三,接入物联网云平台。4G模块已经具备联网能力,再接上MQTT协议,把小车数据上报到云平台,再通过小程序或网页控制,你就能做出一个真正“远程可控”的智能车。
第四,加视觉导航。光流传感器、IMU惯导、里程计融合,做一个简单的室内定位导航系统。这个方向偏算法,对数学要求高,但收获也大。
最后再分享一个小技巧:所有串口通信的调试,我都建议先用USB转TTL在电脑上模拟对端设备,把AT指令流程全部跑通,再接上单片机。这样能把问题隔离在“通信协议”和“硬件连接”两层之外,调起来效率高得多。我当年就是因为跳过这步,直接在单片机上调4G模块,AT指令发出去半天没反应,结果排查了好几天,最终发现只是杜邦线接触不良。先电脑后单片机,这是一个能帮你避开大量无用功的习惯。
本文还有配套的精品资源,点击获取