ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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不用自己专门写 AppAndroid 上装一个“蓝牙串口助手”就能用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 // 测试通信 ATNAMECar // 修改蓝牙名称 ATUART9600,0,0 // 设置波特率9600 ATROLE0 // 设为从机模式HC-05用配置好后模块的 STATE 指示灯会慢闪手机打开蓝牙就能搜到名为“Car”的设备配对密码一般是 1234 或 0000。配对之后模块的 TX 接单片机 PA10USART1_RXRX 接 PA9USART1_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没看到线偏差 0C 在线偏差 0R1 在线偏差 -1L1 在线偏差 1R2 在线偏差 -2L2 在线偏差 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预分频 7172MHz 时钟算下来 PWM 频率就是 72MHz / (72 × 1000) 1kHz。如果觉得电机声音大可以把 Period 改为 719Prescaler 改为 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.3VHC-05 推荐接 5VHC-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 的最大值是受限的如果基础速度是 60correction 最大能给到 ±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让小车能适应不同光线的跑道。每一步扩展都是在现有工程上做增量开发这套代码的框架完全撑得住。做嵌入式就是这样从小项目里积累的方法和习惯最后都会在大项目里回报你。本文还有配套的精品资源点击获取
返回列表