news 2026/9/5 7:53:23

STM32蓝牙遥控循迹小车实战:从硬件选型到代码调试全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32蓝牙遥控循迹小车实战:从硬件选型到代码调试全解析

简介:本资源是一套面向嵌入式初学者与课程设计者的STM32F103C8T6智能小车综合实验程序,聚焦蓝牙手机APP遥控与红外循迹双功能实现,解决硬件驱动适配、多模块协同控制及KEIL工程调试等典型实践难点。压缩包共43个文件,含KEIL工程核心文件(uvproj/uvopt)、编译输出文件(axf/hex/map)、源码文件(c/h)、汇编与链接脚本(s/sct)、电路参考图(jpg)及三份关键文档:《程序说明(必看).txt》《APP使用说明.docx》《智能小车蓝牙遥控-循迹实验.doc》,结构完整,便于工程复现与原理理解。资源大小为2.83MB,已获3912人学习下载,配套安卓APP安装包与L293D驱动电路图,覆盖从硬件连接、固件烧录到手机端控制的全流程,特别适合课程实验、毕业设计及电子竞赛备赛使用。 STM32 小车这个项目,其实是我这两年玩嵌入式觉得“投入产出比”最高的一个。它不像点灯那么无聊,也不像做四轴那样门槛高,一套 STM32F103C8T6 最小系统板、两个电机、一个蓝牙模块、一组红外传感器,就能把单片机、通信、传感器、电机控制这些嵌入式核心知识全串起来。今天要聊的这套“蓝牙遥控+循迹”实验程序源代码,就是围绕这块板子做的,手机 App 能控制前进后退转弯,切到自动模式后小车又能沿着黑线自己跑,代码结构很干净,适合学生做课设、毕设,也适合刚入门的朋友当练手项目。

代码本身不复杂,但里面涉及的知识点不少:串口中断、PWM调速、五路红外循迹、状态机切换、蓝牙配对协议,每一样拆开都能写一篇文章。接下来我按从硬件选型到代码调试的顺序,把整个项目从头到尾拆一遍,里面有原理、有代码思路、也有我实际踩坑换来的经验,照着做基本能一次跑通。

1. 项目整体思路与硬件选型

1.1 为什么选 STM32F103C8T6

很多人第一次接触 STM32,选型时就卡住了。F103C8T6 这颗芯片能成为“入门神板”,我觉得就三个原因:便宜、资料多、外设够用。

先说便宜。一块最小系统板,几块钱到十几块钱就能到手,比 Arduino 的官方板便宜太多,就算烧坏了也不心疼。再说资料多,STM32F103 系列是意法半导体的经典型号,网上中文手册、原理图、例程一抓一大把,遇到问题搜一下基本都有答案,这对新手来说太重要了。最后是外设够用,这颗芯片集成了 USART、I2C、SPI、ADC、定时器 PWM 等常用外设,做小车需要的串口通信、PWM 电机调速、GPIO 读传感器,它全都有,完全不用外扩芯片。

我见过不少人一上来就选 F407 或者 H7,觉得性能越强越好,结果连基础工程都搭不明白,光配置时钟就折腾一星期。做小车这种项目,F103C8T6 的性能已经绰绰有余,代码逻辑再复杂,72MHz 的主频也完全跑得动。用合适的芯片做合适的事,这是嵌入式开发里很重要的一课。

1.2 做一台智能小车需要哪些硬件

这个项目是“蓝牙遥控 + 循迹”二合一,所以硬件上要兼顾通信、运动和感知三部分。我把清单整理了一下:

模块型号/规格作用
主控板STM32F103C8T6 最小系统板处理逻辑、输出控制
电机驱动L298N 或 TB6612FNG驱动直流电机,实现正反转和调速
电机带减速箱的直流电机 ×2 或 ×4提供动力
蓝牙模块HC-05 或 HC-06接收手机 App 指令
循迹传感器五路红外循迹模块(TCRT5000)检测地面黑线
电源18650 锂电池 ×2 或 7.4V 锂电池给主控和电机供电
车架亚克力或铝合金小车底盘承载所有模块

这里重点说两个容易踩坑的地方。电机驱动我建议直接用 TB6612FNG,它比 L298N 体积小、压降低,逻辑输入兼容 3.3V,能和 STM32 直接对接,不用像 L298N 那样担心电平匹配问题。电源部分要注意“共地”,电机电源和单片机电源的负极必须连在一起,否则逻辑信号无法形成回路,电机转不了,这是我见过新手踩得最多的问题。

手机 App 方面,这套源代码用的是串口蓝牙助手类的通用 App,不用自己专门写 App,Android 上装一个“蓝牙串口助手”就能用,iOS 上也有类似的工具。App 里能自定义按键发送的字符串,比如“前进”发 0x01,“后退”发 0x02,单片机的串口接收中断解析这些指令,然后去控制电机,逻辑链路非常清晰。

1.3 程序整体框架:一个工程里装两种模式

这套源代码最值得借鉴的地方,是它把“遥控”和“循迹”两种模式放在一个工程里,通过按键或蓝牙指令切换。主循环是一个大的状态机:

  • 默认处于遥控模式,等待手机 App 发指令,执行前进、后退、左转、右转、加速、减速;
  • 收到特定的模式切换指令后,进入循迹模式,单片机不再等待串口数据,而是循环读取五路红外传感器的电平,按照“偏离-纠偏”的逻辑调整两个轮子的速度,让小车沿着黑线走。

为什么要做成状态机而不是直接把两种逻辑写在一起?因为这两种模式的输入源完全不同:遥控模式的“输入”是串口数据,循迹模式的“输入”是传感器电平。如果混在一起写,主循环里既要轮询串口又要轮询传感器,逻辑会乱,还容易出现“遥控时传感器乱触发、循迹时蓝牙乱入”的干扰问题。状态机把输入源和对应处理逻辑解耦,代码可读性和稳定性都高很多。这个思想以后做复杂项目也一直用得上。

2. 蓝牙遥控模块:手机 App 怎么连、怎么控

2.1 HC-05 与 HC-06 的区别和配对设置

蓝牙模块是手机和单片机之间的“电话线”。市面上常见的有 HC-05 和 HC-06 两种,外观长得几乎一样,但使用方式有区别:HC-05 可以做主机也可以做从机,支持 AT 指令配置主从模式;HC-06 默认只能做从机,适合这种“手机主动连接单片机”的场景。一般小车用 HC-06 就够了,价格也更便宜。

第一次使用要先设置波特率,通常 HC-05/HC-06 出厂默认是 9600 或 38400,而程序里串口初始化的波特率必须和模块保持一致。我习惯先把模块通过 USB-TTL 转接器接到电脑,用串口助手发 AT 指令配置好,再接到单片机上。AT 模式下 HC-05 的按键要按住再上电才会进入 AT 模式,HC-06 不需要按键,直接上电就是 AT 模式。

AT 指令我常用的就几条:

AT // 测试通信 AT+NAME=Car // 修改蓝牙名称 AT+UART=9600,0,0 // 设置波特率9600 AT+ROLE=0 // 设为从机模式(HC-05用)

配置好后,模块的 STATE 指示灯会慢闪,手机打开蓝牙就能搜到名为“Car”的设备,配对密码一般是 1234 或 0000。配对之后,模块的 TX 接单片机 PA10(USART1_RX),RX 接 PA9(USART1_TX),注意交叉连接,GND 共地。

2.2 手机 App 的串口数据是怎么发出去的

手机和蓝牙模块建立连接之后,App 界面上的按键其实就是在“向串口写数据”。这背后是蓝牙 SPP 协议(串口仿真协议),它把蓝牙无线传输封装成了类似有线串口的数据流,手机端发送的每一个字节,蓝牙模块都能从 TX 引脚原样输出出来。

用“蓝牙串口助手”这类 App 时,底部一般有两种发送方式:一种是“文本模式”,直接输入字符发送,比如按键盘上的 A 键发送字符“A”;另一种是“十六进制模式”,可以精确发送 0x01、0x02 这样的字节。我的建议是统一用十六进制模式,因为字符模式会有编码问题,假如发送中文“前进”,不同的 App 可能用 GBK 或 UTF-8 编码,单片机收进来一帧数据还得自己解析编码,很麻烦。直接约定好:0x01 前进、0x02 后退、0x03 左转、0x04 右转,单片机按字节解析,简单可靠。

还有一点,App 里一般可以设置按键的“发送内容”和“发送模式”,有的 App 不支持按住发送、只支持点击发送,实际用起来会有点别扭。我试过很多个,比较顺手的是“Serial Bluetooth Terminal”和“蓝牙串口助手”,前者自定义按键很方便,可以设置“按下时发送”和“松开时发送”,适合做无极调速;后者更轻量,适合快速测试。

2.3 主控端串口中断接收:不能丢字节的关键

单片机接收蓝牙数据的核心,是串口中断。为什么不用轮询?假设主循环正在处理电机控制,轮询读串口可能漏掉中间的字节,而蓝牙连接后手机一旦连发数据,数据是连续进来的,丢一个字节整个指令就乱了。串口中断则是一收到字节马上跳到中断服务函数处理,天然不会丢数据。

代码里一般是这样处理的:

uint8_t rx_buf[16]; uint8_t rx_index = 0; void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); rx_buf[rx_index++] = data; if(rx_index >= 16) rx_index = 0; } }

这里有个细节:中断服务函数里尽量只做“收数据、存缓冲”这两件事,不要直接在中断里写电机控制逻辑。因为中断服务函数执行时间越长,主循环的实时性越受影响,而且如果在中断里调用 HAL_Delay 这类延时函数,整个系统就卡死了。正确做法是中断里存数据,主循环里设置一个标志位,比如rx_flag = 1,然后在主循环中统一处理缓冲区的指令。这样把“接收”和“处理”分离,是嵌入式编程里很重要的习惯。

如果发现收到的字节乱码,先检查两边的波特率是不是一样,再看 TX/RX 有没有接反。蓝牙模块的引脚一般是 3.3V 或者 5V 容忍,但最好还是确认一下,HC-05 的 RX 引脚如果不小心接错,模块烧了就不划算了。

3. 循迹模块:五路红外循迹原理与调参

3.1 红外反射检测是怎么回事

“循迹”听起来高大上,原理其实很简单:地面放一条黑色胶带,小车底部安装一排红外传感器,每个传感器由红外发射管和接收管组成。发射管持续发射红外光,红外光照到地面后反射回来,接收管根据反射光的强弱输出不同的电平。

关键点在于颜色对红外光的反射率差异。白色或浅色地面反射率高,接收管能收到较强的反射光,输出低电平;黑色胶带吸收红外光,反射率低,接收管收到的光很弱,输出高电平。单片机就是靠“读电平高低”来判断传感器当前是在黑线上方还是在白底上方。这个原理和家里的自动感应水龙头、扫地机器人避障是同一个套路。

TCRT5000 是这类模块里用得最多的传感器,一颗一颗的小模块很便宜,五个排成一排就构成了“五路循迹传感器”。五路比两路或三路的好处是:能更早地发现偏离趋势,在车头刚偏一点时就能检测到,不用等车完全跑偏了才纠偏,循迹稳定性和过弯能力都强不少。

3.2 接线与阈值标定:不是焊上就能跑

五路循迹模块一般引出 7 个引脚:VCC、GND 和 5 个数字输出引脚(OUT1~OUT5)。把输出引脚接到 STM32 的 GPIO 上,比如 PC0~PC4,上电后用万用表量一下模块的输出电平即可判断是否工作。

这里有个特别容易忽略的点:循迹传感器必须先调阈值,再写程序。大多数模块板上有一个电位器,可以调节灵敏度,也就是红外灯亮度的阈值。调法很简单:把小车放在白色地面上,拿螺丝刀旋转电位器,直到输出指示灯刚好熄灭;再把小车推到黑线上,确认指示灯亮起。每个传感器都这样单独调一遍,保证“黑白分明”。

如果把所有传感器都放在白色地面上,发现某个指示灯还是亮的,说明这个传感器的阈值调得偏高,环境光干扰很大;反之放在黑线上灯不亮,说明阈值调得太低,灵敏度不够。我第一次做的时候偷懒没调阈值,程序逻辑写对了但小车一上跑道就乱跑,把传感器遮住、打开手电筒实验,才发现是阈值和环境光的问题。调完之后整个车稳了一大截。

实际调试时还有一个经验:环境光对红外传感器影响很大,太阳光直射或实验室的强光下,传感器可能会“误判”,建议给传感器加一个遮光罩,或者尽量在室内固定光线条件下使用。规则比赛里大家用的都是类似的做法。

3.3 循迹策略:分段 PID 与状态机

五路传感器从左到右编号 L2、L1、C、R1、R2。当小车沿着黑线正中间走时,只有中间的 C 压到黑线,输出为 1,其他四个都在白底上,输出为 0。用二进制表示就是 00100。小车一旦往左偏,右边传感器 R1 或 R2 会压到黑线,变成 00011 或 00110,这时候就要给右边轮子减速或左边的轮子加速,把车身拉回中间。

最简单的逻辑是“查表法”,把五种常见状态写死:

switch(sensor_value) { case 0x04: // 00100 正中间,直行 left_speed = base_speed; right_speed = base_speed; break; case 0x02: // 00010 偏右,左转纠偏 left_speed = base_speed + 20; right_speed = base_speed - 20; break; case 0x08: // 01000 偏左,右转纠偏 left_speed = base_speed - 20; right_speed = base_speed + 20; break; // 其余情况 default: break; }

但查表法在小车速度稍高的时候就会抖:要么纠偏力度不够冲出去,要么力度太大左右乱摆。改进的办法是给偏差量化打分,然后套一个 PD 或 PID 控制器。把五路传感器的位置映射成偏差值,比如:

  • 全是 0(没看到线):偏差 = 0
  • C 在线:偏差 = 0
  • R1 在线:偏差 = -1
  • L1 在线:偏差 = +1
  • R2 在线:偏差 = -2
  • L2 在线:偏差 = +2

偏差值越大说明偏得越狠,控制器输出一个纠偏速度差值。我用过一个简化版 PD 控制,效果就很好:

int error = get_error(); // 读取偏差 int derivative = error - last_error; // 偏差变化率 int correction = Kp * error + Kd * derivative; left_speed = base_speed + correction; right_speed = base_speed - correction; last_error = error;

Kp 和 Kd 怎么调?我一般是先把 Kd 设为 0,只调 Kp,从小往大加,直到小车能在直线段稳定走;然后加上 Kd,用来抑制抖动。如果小车在直线上蛇形走,Kd 太小或者 Kp 太大;如果过弯甩出去,Kp 不够或者 base_speed 太高。这个过程没有捷径,只能反复试,但每次只改一个参数,记录效果,这样调出来最快。

4. 核心代码实现与关键细节

4.1 电机驱动代码:PWM 调速和正反转逻辑

这辆小车的动力来自直流减速电机,我用的是 TB6612FNG 驱动,它的输入输出关系很简单:IN1、IN2 控制一路电机的方向,PWMA 控制这一路的速度。两个电机分别对应左右轮,这是差速转向的关键,左轮和右轮速度不一致,车就转弯了。

电机驱动的初始化主要做三件事:配置 PWM 定时器、配置方向控制 GPIO、初始化占空比。PWM 我选定时器 TIM2 的通道 1 和通道 2,输出模式是 PWM1,频率设 10kHz 左右,这个频率对直流电机很合适,既能平滑调速又听不到明显啸叫。

void Motor_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); // PA0、PA1 复用为 PWM 输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 定时器配置 TIM_TimeBaseStructure.TIM_Period = 999; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_OC2Init(TIM2, &TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); }

这里 TIM_Period 设置为 999,预分频 71,72MHz 时钟算下来 PWM 频率就是 72MHz / (72 × 1000) = 1kHz。如果觉得电机声音大,可以把 Period 改为 719,Prescaler 改为 19,频率就变成 5kHz。关于频率,我实测下来 1kHz 到 20kHz 都能工作,但是 1kHz 以下电机会有明显的“哒哒”声和顿挫感,10kHz 以上基本听不到声音,所以尽量往高了设。

正反转逻辑就是控制 IN1、IN2 的电平组合:

左侧电机 IN1左侧电机 IN2左轮状态
高电平低电平正转(前进)
低电平高电平反转(后退)
低电平低电平刹车
高电平高电平惰性

控制速度只需要改变 PWM 的占空比,也就是 CCR 寄存器的值。封装成一个函数后,上层控制逻辑就变得很简洁:

void Motor_SetSpeed(int left_speed, int right_speed) { // 速度值范围 -100 ~ 100,负数为反转 if(left_speed >= 0) { GPIO_SetBits(GPIOC, GPIO_Pin_0); GPIO_ResetBits(GPIOC, GPIO_Pin_1); TIM_SetCompare1(TIM2, left_speed * 10); } else { GPIO_ResetBits(GPIOC, GPIO_Pin_0); GPIO_SetBits(GPIOC, GPIO_Pin_1); TIM_SetCompare1(TIM2, (-left_speed) * 10); } // 右侧同理 }

4.2 蓝牙指令解析:协议越简单越好用

蓝牙遥控的协议我建议做得尽量简单。很多人喜欢定义一长串帧头、帧尾、校验位,结果自己都被绕晕了。对于一个演示项目,单字节命令已经足够,收到什么就执行什么。比如:

void Process_Command(uint8_t cmd) { switch(cmd) { case 0x01: // 前进 Motor_SetSpeed(60, 60); break; case 0x02: // 后退 Motor_SetSpeed(-60, -60); break; case 0x03: // 左转 Motor_SetSpeed(-30, 60); break; case 0x04: // 右转 Motor_SetSpeed(60, -30); break; case 0x05: // 停车 Motor_SetSpeed(0, 0); break; case 0x06: // 切换到循迹模式 mode = TRACK_MODE; break; case 0x07: // 切换到遥控模式 mode = REMOTE_MODE; break; default: break; } }

这里有一个细节:左转和右转的时候,不管是“差速转向”还是“原地转向”,效果差异很大。如果是在跑道上高速循迹,转弯要用差速,也就是两边都保持正转,只改变速度差;如果是遥控掉头,可以用原地转向,一边正转一边反转。这两种方式在遥控模式下我可以主动控制,循迹模式下程序内部通过 PID 输出的是差速值,天然就是差速转向,所以模式切换时逻辑不会冲突。

解析指令的时候,还要防止“多字节粘连”的问题。有的手机 App 会连续发送多个字节,比如 0x01 0x02 同时到达,如果程序每收一个字节就立即执行,车可能还没执行完 0x01 就被 0x02 打断。稳妥一点的做法是:收到第一个字节后清空缓冲,然后判断缓冲区的首个有效命令来执行,或者干脆在协议里加一个简单的“确认位”。在这个程度的项目里,我一般每个按键只发一个字节,就够用了。

4.3 循迹代码的核心流程:别在中断里做控制

循迹模式的主循环结构,我建议这样搭:

while(1) { if(mode == TRACK_MODE) { uint8_t sensor = Read_Sensor(); // 读取五路传感器,得到一个5位二进制数 int error = Get_Error(sensor); // 映射为偏差值 int correction = PID_Calculate(error); // PID计算 Motor_SetSpeed(base_speed + correction, base_speed - correction); } else { // 遥控模式:处理串口缓冲区命令 if(rx_flag) { Process_Command(rx_buf[0]); rx_flag = 0; rx_index = 0; } } }

这个结构有两个优点。第一,循迹控制和遥控控制完全隔离,互不干扰。第二,主循环每次迭代只做一件事,实时性有保障。循迹控制中最重要的其实是读取传感器的时机:传感器要等到小车真正开起来、车身稳定之后再读取,刚上电或者刚起步的瞬间,电机电流大、电压波动,传感器的读数可能不稳定,所以一般会在主循环里加一个 10ms 左右的延时,让系统稳定。

读传感器的方式有两种选择:轮询 GPIO 或者用外部中断。对于五路循迹,轮询就够。GPIO 判断速度极快,在单片机上不过几百纳秒的事,而小车的控制周期是几十毫秒,远远够用。

uint8_t Read_Sensor(void) { uint8_t value = 0; // PC0~PC4 对应 L2, L1, C, R1, R2 if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0)) value |= 0x01; if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1)) value |= 0x02; if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_2)) value |= 0x04; if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_3)) value |= 0x08; if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_4)) value |= 0x10; return value; }

4.4 源码工程结构:怎么把文件组织得干净

拿到源码工程后,第一件事不是急着改代码,而是先看懂文件结构。这套程序沿用标准库的工程组织方式,主目录下有 User、Hardware、Core 等文件夹。我按经验给朋友建议的拆分方式是这样的:

  • User/main.c:主函数、初始化调用、主循环
  • User/stm32f10x_it.c:中断服务函数
  • Hardware/uart.c:串口初始化、发送字节、接收缓冲管理
  • Hardware/motor.c:电机 GPIO/PWM 初始化、速度设置
  • Hardware/bt.c:蓝牙模式切换和通信协议
  • Hardware/track.c:循迹传感器读取、偏差计算、PID 控制
  • System:标准外设库的底层文件,一般不用动

这样组织之后,每个文件的职责非常单一,改电机代码不用去翻循迹逻辑,加一个新传感器也不用动主程序。嵌入式项目一大通病就是“所有代码堆在 main.c 里”,几千行代码放一个文件,出了问题非常难查,规范地分文件写是让项目可维护的第一步。

拿到别人的代码时还有个经验:不要“一次性全部读懂”再动手。先把工程编译通过、下载到板子里跑起来,确认基本功能正常,然后按功能模块逐个看代码。这样有了实际现象对应的基础,看代码会容易很多。我见过不少朋友把源码从头到尾看了一遍,结果还是不知道从哪里改起,就是因为没有“现象”作为锚点。

5. 调试踩坑与常见问题

5.1 常见问题速查表

我把这个项目里高频出现的问题整理成了一张表,基本上覆盖了新手会踩到的所有坑:

现象可能原因排查方法
手机搜不到蓝牙蓝牙模块没上电、AT 模式没退出检查指示灯是否慢闪,上电时不要按按键
能连接但车不动TX/RX 接反、波特率不匹配、供电不足按住蓝牙按键测试串口回环,发字符看是否原样返回
蓝牙控制时车乱抖电源共地问题、PWM 频率太低确认电机电源和单片机电源共地,提高定时器 PWM 频率
循迹直接冲出跑道传感器阈值没调、base_speed 太高按 3.2 节方法逐个调阈值,降低基础速度
循迹蛇形走PID 参数不合适Kp 太大或 Kd 太小,按“先 Kp 后 Kd”的顺序重调
左边转弯正常右边不转PWM 通道没使能、驱动板某路接线松用万用表量驱动板 IN 脚电压,确认信号是否到达
上电后程序不跑BOOT0 跳线帽没设对、没接复位确认 BOOT0 接 GND,用杜邦线手动复位一次
手机 App 连上后立刻断开蓝牙模块电流不够换个独立供电 5V 电源,不要从单片机 3.3V 引脚取电

5.2 排查蓝牙不工作的三个步骤

蓝牙连接的问题,80% 集中在硬件连接和配置上,而不是代码。我排查的顺序永远是固定的,这也是老工程师教我的“先从物理层查起”:

第一步,查供电。蓝牙模块的 VCC 接 5V 还是 3.3V?HC-05 推荐接 5V,HC-06 接 3.3V 或 5V 都可以,但如果从单片机板载的 3.3V LDO 取电,蓝牙搜索时瞬间电流可能拉垮电压,导致单片机复位或者蓝牙掉线。最稳妥的做法是蓝牙单独从 5V 电源引脚供电,板上自带的稳压芯片负责稳到 3.3V。

第二步,查串口回环。把蓝牙模块的 TX 和 RX 先用杜邦线短接,手机发数据,如果手机能收到自己发的数据,说明蓝牙模块和手机之间的通路没问题。然后把蓝牙接到单片机,写一个“收到什么发回什么”的测试程序,手机发一个字符,如果收到回显,说明单片机串口配置正确。这两步做完,就能把问题定位到是无线侧还是串口侧。

第三步,查波特率。如果回环正常但单片机收到的数据是乱码,基本就是波特率不匹配。手机 App 连接时也能设置波特率,很多人只在 AT 指令里设了 9600,但 App 连接后用 115200 通信,自然乱码。这个坑特别隐蔽,因为它不影响连接,只影响数据内容,用“回环测试”正好能发现。

5.3 循迹小车怎么调整速度:从基础速度到 PID 的参数联动

“循迹小车怎么调整速度”这个问题,我每次活动都能碰到,它其实有两层含义:一是调基础速度,二是调过弯速度。

基础速度(base_speed)决定了小车在直线段的巡航速度。这个值不要一开始就设得很高,我建议从 30% 占空比开始试,等直线稳定了再逐步往上加。循迹小车的极限速度取决于赛道的最小转弯半径,弯越急,能跑的巡航速度就越低,硬要把基础速度调高,小车到弯道就冲出去。

过弯速度是 PID 输出和基础速度的叠加效果。PD 控制里 correction 的最大值是受限的,如果基础速度是 60,correction 最大能给到 ±60,那转弯时内侧轮最高可以到 0,外侧轮到 120,这就是原地转向了,在循迹中会导致过度纠正。所以实际使用时要限制 correction 的输出范围,我常用的是:

if(correction > 40) correction = 40; if(correction < -40) correction = -40;

这样即使基础速度是 60,内侧轮最低也就是 20,不会完全停下来,小车能保持稳定而平滑的过弯姿态。速度调整的核心原则是:先调 Kp 让车稳定,再调 Kd 让车平滑,最后加基础速度,加到再次出现抖动就降速 10%。按这个顺序调,小车能跑的比较快且稳。

另外,五路传感器的安装位置会影响整个循迹系统的响应。传感器离地面高度一般控制在 1~2cm,太近了容易刮到地面,太远了红外反射信号弱。另外,传感器阵列要尽量和小车的中轴线垂直,左右对称,不然小车会“天生跑偏”,PID 的偏差计算也不准。

实际使用中的体会与扩展建议

这套代码跑通之后,我用它做了好几轮的改造,对这个项目的感受是:它的价值不只是“能跑”,而是它把嵌入式里最常用的几块知识点串成了一个闭环。从硬件接线到串口通信,从 GPIO 读取到 PWM 控制,从状态机设计到 PID 调参,每一块单独拿出来都不难,但组合在一起,就能逼着你真正理解系统是怎么协同工作的。

我给正在做课设或者自学的人几个具体建议。一是不要急着改代码,先原样编译下载,对照我上面说的步骤把每个现象都验证一遍,这个“验证”过程本身就是学习。二是改代码时一次只改一个点,比如先改蓝牙指令、再改电机速度,不要同时改 PID 和循迹逻辑,否则出了问题不知道是哪个改动引起的。三是一定要学会用一个串口调试助手,把这个当调试工具箱的核心工具。

如果后续想扩展,方向也挺多:在手机上用 Android Studio 写一个专属 App,把虚拟摇杆和模式切换按钮都集成进去;给小车加一个 OLED 屏幕,显示当前模式、速度、传感器状态;或者把循迹算法从 PD 换成自适应 PID,让小车能适应不同光线的跑道。每一步扩展,都是在现有工程上做增量开发,这套代码的框架完全撑得住。做嵌入式就是这样,从小项目里积累的方法和习惯,最后都会在大项目里回报你。

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

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

海康威视SDK开发实战:从登录预览到人脸识别抓图全流程解析

简介&#xff1a;本资源是一套基于海康威视SDK实现视频预览与人脸识别抓图的完整C#开发工程&#xff0c;面向安防系统集成开发者、智能监控应用学习者及高校相关专业实践者&#xff0c;解决海康设备接入、实时流预览、人脸检测触发与图像自动保存等核心开发问题。压缩包共63个文…

作者头像 李华
网站建设 2026/9/3 11:59:54

近红外光谱检测仪全解析:从硬件原理到建模应用实践

这次我们来看一个很经典的分析仪器&#xff1a;近红外光谱检测仪。它不是新概念&#xff0c;也不是靠复杂算法博眼球&#xff0c;核心价值非常直接&#xff1a;不破坏样品、不接触样品、几秒到几十秒内拿到光谱&#xff0c;配合模型实现水分、蛋白、脂肪、糖度等组分快速测定。…

作者头像 李华
网站建设 2026/9/3 19:13:36

IT学习真实反馈指南:从学习日志到Bug描述的方法论

在北京学 IT 的同学&#xff0c;经常遇到一个共同的问题&#xff1a;学了一段时间&#xff0c;知识点好像都听懂了&#xff0c;但真到做项目或面试时&#xff0c;又说不出自己到底学会了什么。很多同学想找带自己的老师“龙哥”反馈真实的学习情况&#xff0c;却又不知道怎么反…

作者头像 李华
网站建设 2026/9/3 19:45:31

沈阳壁挂炉维修上门服务-欧米到家解决不点火、异响及故障代码

核心导读壁挂炉同时涉及燃气、燃烧、电控、水路和排烟系统。出现不点火、不供暖、热水忽冷忽热、压力下降、频繁补水、运行异响、漏水、风机不转或故障代码反复出现时&#xff0c;不建议用户自行拆机、短接保护装置或反复复位。欧米到家在沈阳提供燃气壁挂炉故障检测、维修、清…

作者头像 李华
网站建设 2026/9/3 15:11:14

中国矢量地图SHP从格式转换到裁剪分割实用指南

简介&#xff1a;中国矢量地图&#xff08;SHP格式&#xff09;是一套覆盖国界、省界、市界、县界四级行政边界的GIS基础数据资源&#xff0c;面向GIS开发者、城乡规划师、地理数据分析人员及科研学者&#xff0c;可直接用于地图制图、区划分析、人口与经济数据可视化、城乡与交…

作者头像 李华
网站建设 2026/9/4 8:08:43

Arduino I2C库封装实战:原理、安装与避坑指南

简介&#xff1a;本资源是Arduino平台I2C通信开发的核心支持库&#xff0c;面向嵌入式初学者、电子爱好者及物联网项目开发者&#xff0c;解决Arduino与各类I2C外设&#xff08;如RTC、温湿度传感器、LCD、EEPROM等&#xff09;稳定通信的底层驱动需求。压缩包共4个文件&#x…

作者头像 李华