尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32八合一智能小车实战:硬件选型与代码调试全解析

STM32八合一智能小车实战:硬件选型与代码调试全解析 简介本资源是一套完整的STM32智能小车多功能开发套件面向嵌入式初学者、课程设计学生及电子竞赛备赛者解决多模态智能控制功能集成难、代码移植性差、硬件选型无依据等实际问题。压缩包含2000个文件主体为889个.h头文件与325个.c源文件HAL库驱动与应用逻辑辅以.ioc配置文件、.uvprojx工程文件、.hex烧录镜像及大量编译中间文件.o/.axf/.map等完整覆盖CubeMX图形化配置到固件部署全流程总大小75.35MB。已有2775人学习下载说明其在实践教学与项目复现中具备广泛参考价值。用户可直接编译运行全部八大功能——循迹、跟随、避障、测速、蓝牙遥控、WiFi远程控制、4G联网通信及语音识别响应并基于详尽中文注释快速理解模块间交互逻辑所有代码兼容标准库移植配套硬件清单明确器件型号与连接关系显著降低二次开发门槛。 网上关于STM32智能小车的帖子很多但多数只讲单功能——要么循迹要么蓝牙遥控碰到“全功能整合”的需求反而找不到一份完整的参考。我这次把循迹、跟随、避障、测速、蓝牙、WiFi、4G、语音识别全部放到一台F103小车上了前后折腾了三个多月代码从几百行膨胀到三千多行。这篇总结把硬件选型、系统架构、各部分代码逻辑、调试经验全部梳理出来适合已经会点STM32基础、想做一个完整项目的人也适合备赛或者做毕设的同学参考。需要说明的是下面所有源代码不是噱头是能直接编译运行的工程级代码片段。单片机型号是STM32F103ZET6标准库HAL库混着用但思路完全可以移植到C8T6、F407甚至GD32上。硬件清单部分我会把每个模块选型的理由和踩过的坑也一并写出来毕竟很多问题不是代码问题是选型问题。1. 整车硬件选型清单与采购避坑1.1 主控与底盘的核心搭配主控我选了STM32F103ZET6不是最小系统板是带底板的那种开发板。为什么不选C8T6纯粹是因为这个项目后期要挂的模块太多C8T6的引脚和Flash都紧巴巴的。ZET6有144个引脚、512KB Flash、64KB RAM做这种多功能小车不会出现引脚不够用还得天天改映射的尴尬局面。如果预算有限或者只做单一功能C8T6也够但八功能合并的话我还是建议直接上ZET6。底盘用的是四驱亚克力底盘就是网上最常见的那个蓝色透明板子配四个TT马达。四驱的好处不只是力气大重点在于后期如果加摄像头、机械臂这些负载动力余量足。TT马达不带编码器所以我额外买了带霍尔测速功能的马达减速箱版本后面测速功能靠它实现不用自己改装省了很多事。驱动板选的是TB6612FNG不是L298N。这一条要重点说L298N的压降太大了两节18650满电8.4V进去到电机端可能只有6V多而且L298N自身发热严重跑十分钟板子烫手。TB6612的MOS管压降只有零点几伏同样电池供电到电机端的电压几乎不损失发热少了非常多。驱动能力方面TT马达的工作电流在200mA左右堵转电流也就1A上下TB6612单路最大1.2A完全够用。1.2 传感器件选型细节循迹我用了五路灰度传感器不是两路也不是三路。五路的好处是过十字路口的时候能判断车辆姿态两路的话到了十字路口直接就懵了不知道自己到底在横线还是竖线。我买的模块是TTL电平输出白线输出低电平黑地输出高电平后面代码里也按这个逻辑写。如果买到的模块极性和我相反代码里取反就行。避障模块用的是两个红外避障传感器装在车头左右两侧探测距离在2~30cm可调。为什么不直接用超声波避障超声波扫盲区大、速度慢近距避障反应不过来。红外模块的响应时间是毫秒级的左右各装一个小车侧前方有障碍能立刻感知配合减速逻辑就能很顺滑地绕开。超声波留给跟随用各司其职。超声波选的是HC-SR04装在舵机云台上。舵机是SG909g那种小舵机力气对于这种小体积的超声波模块来说足够了。云台要自己用亚克力片或者3D打印件搭一个我用的是3D打印的20克左右装在车头正前方。测速模块是霍尔传感器型号LM393磁铁装在TT马达的轮轴上每转一圈输出一个脉冲轮子每转一圈的脉冲数跟电机减速比有关我用的1:48减速电机霍尔盘每圈输出约13个脉冲这个数值一定要根据自己买的电机型号实测校准不能直接抄别人的。1.3 通信模块选型蓝牙、WiFi、4G怎么定蓝牙模块选了HC05经典蓝牙不是HC06也不是BLE。HC06只能做从机不能发AT指令配置而HC05既能做主机也能做从机能通过AT指令改名称、改波特率、改配对密码后期如果要当一个“遥控主站”去控制别的蓝牙设备还能切主机模式。手机APP控制方面经典蓝牙的SPP协议支持面很广网上大多数蓝牙调试APP都支持HC05不用自己写APP。WiFi模块选了ESP8266型号ESP-01S刷的是官方AT固件。为什么不用ESP32主控已经是STM32了ESP8266在这里只干一件事透传数据。它把STM32串口发过来的数据通过WiFi转发到局域网里的PC或者手机调试工具上不需要额外的算力和IO口AT指令就能搞定这是最轻量可靠的做法。如果后面想做局域网视频图传那再单独挂摄像头模块不在这次讨论范围内。4G模块用的是SIM7600CE全网通支持TCP/IP透传和MQTT协议。这个模块有点贵而且需要SIM卡和流量做演示的时候其实很少用到但它解决了两个蓝牙和WiFi解决不了的需求一是小车完全脱离局域网只要有4G信号就能被远程控制二是可以走MQTT把数据推到云平台实现真正的物联网闭环。如果只是课堂作业或者实验室演示WiFi就够了4G是往产品化方向走的加分项。语音识别模块用的是SU-03T离线语音识别模块天问的不用联网、不用训练平台账号直接对接串口就能用。为什么不用LD3320LD3320的识别率在安静环境下还可以但稍微有点噪声就容易误判而且它的命令词设置是通过官方的上位机软件写入的要注册账号、要下IDE流程繁琐。SU-03T有自己的配置工具把唤醒词、命令词、串口输出的返回值配置好烧录进去就能用识别速度也在百毫秒级更适合这种嵌入式小车场景。1.4 电源系统的设计与容量估算很多新手在这里翻车。我见过有人用USB供电给小车跑结果一启动电机就复位这就是典型的供电不足。这台车的用电设备分三类电机、舵机、逻辑电路。电机四个正常工作电流总计约800mA堵转峰值可能到4A舵机SG90堵转电流约700mA正常转动时几十毫安主控板、传感器、蓝牙、WiFi、语音模块逻辑部分合计约300mA电池我用了两节18650串联标称电压7.4V容量2600mAh。电机驱动直接接电池正极逻辑电路通过降压模块降到5V和3.3V。不要用一节18650直接给逻辑部分供电因为电机启动瞬间电压会被拉低到4V以下单节18650充满4.2V被拉到3V多时STM32的3.3V稳压芯片输出就不稳定了很容易复位。降压方案5V用MP1584这种DC-DC模块效率高、压差大也能带得动别用7805线性稳压电池8.4V输入时7805发热严重功耗全浪费在发热上了。3.3V从5V再经过一片AMS1117-3.3就可以因为逻辑部分电流不大线性稳压的损耗可以接受。容量续航也可以估算一下平均电流大约1.2A2600mAh的18650实际可用容量大约2000mAh保护板会限制放电到2.5V每节理论续航约1.6小时实际跑起来走走停停、电机频繁启停大概能支撑1小时左右。这个数字对比赛和演示来说足够了。2. 系统架构与电气连接从一堆模块到一台能跑的车2.1 整车供电拓扑与电平匹配整车的供电拓扑是这样的两节18650串联成7.4V电池组经过一个总开关后分成三路。第一路直接进TB6612的VM电源脚给电机供电第二路进MP1584降压成5V给舵机、传感器模块、语音模块、蓝牙模块供电第三路从5V再经AMS1117降到3.3V供STM32主控板的逻辑部分和ESP8266。供电拓扑确定了接下来是电平匹配问题。STM32的IO是3.3V电平但很多模块是5V电平比如五路灰度传感器和HC-SR04它们的信号输出高电平是5V。如果直接把5V引脚接到STM32的3.3V引脚上长期运行有烧引脚的风险。虽然F103的数据手册说引脚耐压是5V容忍FT引脚但不是所有引脚都支持5V容忍所以我在这几个模块的信号线上串了1kΩ电阻做限流一来保护引脚二来不影响数字信号读取。这个做法在工程中很常见比用电平转换芯片要省事实测也没有出现信号识别问题。2.2 引脚分配表与定时器资源规划引脚分配是整个项目里最需要提前规划的事因为F103的多路外设共用引脚比如USART1的TX/RX在PA9/PA10同时也复用为TIM1的通道。如果等到接线的时候再临时改往往只能改程序重映射麻烦得很。我最终确定的引脚分配表如下功能模块信号STM32引脚备注TB6612PWMA/PWMBPA8/PA11TIM1通道1/通道2PWM输出TB6612AIN1/AIN2/BIN1/BIN2PB12/PB13/PB14/PB15方向控制通用GPIOTB6612STBYPB11高电平使能五路循迹DO1~DO5PC0~PC4普通输入带上拉红外避障OUT_L/OUT_RPC5/PC6普通输入HC-SR04Trig/EchoPA6/PA7Echo用TIM3的输入捕获SG90舵机PWM信号PA1TIM2通道2霍尔测速S1/S2PB6/PB7TIM4通道1/通道2编码器模式HC05蓝牙TXD/RXDPA10/PA9USART1ESP8266TXD/RXDPA3/PA2USART2SIM7600TXD/RXDPB11/PB10USART3SU-03TTXD/RXDPC11/PC10UART4OLEDSCL/SDAPB8/PB9I2C1用于显示状态这里有个容易踩的坑PC13、PC14、PC15这三个引脚在ZET6上是RTC和TAMPER引脚做普通IO用的时候会有额外的限制比如不能做ADC输入但做普通数字输出和输入是没问题的只是要避开它们做高速PWM或定时器输入。我这版没用到这几个引脚但如果你拿的是最小系统板要特别注意这个限制。2.3 为什么每个传感器都要“共地”接线时最容易忽略但最致命的一个问题共地。所有模块的GND必须和STM32的GND连在一起否则信号线上的参考电平不一致读到的数据会乱跳。尤其当你外接一个DC-DC模块给传感器供电、又用另一路电源给主控供电的时候如果不共地模块的输出高电平是相对于模块自己电源的单片机读这个电平就完全对不上了。我的做法是在底板上飞了一根粗的GND母线所有模块的GND就近接到这根母线上主控板的GND也接上去。注意不要把所有GND都堆到主控板上的一个GND引脚上那样电流集中容易在GND线上产生压差导致各个模块之间的地电位不一致。3. 八大功能的工程实现与核心代码逻辑3.1 循迹功能五路灰度传感器的阈值校准与PID转向循迹的原理很简单灰度传感器通过红外发射管照射地面接收管根据反射光强判断当前颜色。白线反光强、黑地反光弱模块输出数字电平。但不同环境的光照、不同地面的反光度差别很大所以必须先做阈值校准不能直接拿固定的比较电平去判断。我这里的五路模块输出的是开关量直接读GPIO就能得到每一位的0/1状态。循迹的核心不只是“检测到白线就转向”而是要把五路传感器的状态读出来转换成小车的转向偏差量再用PID去控制转向打角和速度差。典型的状态处理逻辑如下uint8_t line_sensor[5]; int8_t get_line_offset(void) { // 假设白线输出0黑地输出1 // 五路传感器位置左、中左、中、中右、右 // 用加权算法计算偏差中心为0最左为-4最右为4 static const int8_t weights[5] {-4, -2, 0, 2, 4}; int8_t offset 0; uint8_t count 0; for (int i 0; i 5; i) { if (line_sensor[i] 0) { // 检测到白线 offset weights[i]; count; } } if (count 0) return 99; // 丢失线返回异常值 return offset / count; }控制部分就是一个位置式PID输出量是左右轮的PWM占空比差值int16_t pid_line_control(int8_t target_offset) { static int16_t integral 0; static int8_t last_error 0; int8_t error target_offset; integral error; if (integral 200) integral 200; if (integral -200) integral -200; int16_t output KP_LINE * error KI_LINE * integral KD_LINE * (error - last_error); last_error error; return output; }调试循迹时有个经验P值先从小往大调调到小车开始左右轻微震荡然后加一点D值抑制震荡最后加一点I值消除过弯时的稳态误差。如果直线段跑得稳、弯道出线通常是P不够大或者速度太快不要急着加D先把基础速度降下来。3.2 避障功能红外传感器检测与分级减速策略两个红外避障模块我装在车头两侧朝斜前方探测。模块上有一个电位器可以调距离阈值我调到大约15cm左右也就是前方15cm内有障碍物时模块输出低电平不同模块极性不同要实测确认。避障的逻辑不能简单做成“检测到障碍就急转弯”那样小车会一顿一顿的。我用了分级策略先减速再判断障碍在哪一侧然后转向避让。uint8_t obstacle_left, obstacle_right; void obstacle_avoid(void) { obstacle_left GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_5); obstacle_right GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6); if (obstacle_left 0 obstacle_right 0) { // 两侧都有障碍倒车右转 set_motor_speed(-BASE_SPEED, BASE_SPEED); } else if (obstacle_left 0) { // 左侧障碍减速右转 set_motor_speed(BASE_SPEED * 0.4, BASE_SPEED * 0.8); } else if (obstacle_right 0) { // 右侧障碍减速左转 set_motor_speed(BASE_SPEED * 0.8, BASE_SPEED * 0.4); } else { // 无障碍恢复前进 set_motor_speed(BASE_SPEED, BASE_SPEED); } }避障的最佳状态不是每次都急打方向而是让小车在接近障碍时就有一个柔和的转向趋势。所以我把红外传感器的安装方向稍微朝外偏了15度左右这样小车还没正面撞上障碍就已经感知到侧前方的障碍物提前减速实际跑起来轨迹非常顺滑。这个安装角度的细节比调代码更影响体验。3.3 超声波跟随功能舵机云台扫描与动态PID调速跟随功能用超声波测距舵机云台负责扫描。核心代码分两段第一段是舵机云台扫描第二段是根据距离和方位控制小车运动。超声波测距的代码是HC-SR04的标准时序拉高Trig引脚10微秒以上然后等待Echo引脚返回高电平高电平持续时间乘以声速340m/s再除以2就是距离。float ultrasonic_get_distance(void) { float distance; uint32_t time_us; GPIO_SetBits(GPIOA, GPIO_Pin_6); // Trig拉高 delay_us(15); GPIO_ResetBits(GPIOA, GPIO_Pin_6); // Trig拉低 // Echo用定时器输入捕获测量高电平时间 time_us ic_get_echo_time(); // 返回微秒数 distance time_us * 0.034 / 2.0; // 单位cm return distance; }跟随的控制策略很多人想复杂了。最简单可靠的方案是舵机云台先扫描180度找到最近的一个目标记录下目标在左中右哪个方位然后控制小车转向同时根据距离控制前后速度距离大于40cm加速前进距离在15cm到40cm之间保持当前速度距离小于15cm减速或停止如果希望小车跟得更稳可以加一个简单的比例控制速度 K * (目标距离 - 当前距离)目标距离设为25cm左右。K值根据实际情况调我用的是K2也就是距离差10cm时速度差为20%PWM占空比。跟随模式有个坑空旷环境下超声波可能扫到远处墙壁导致小车误判目标。我的解决办法是限制有效测距范围只认20cm到150cm之间的目标超过这个范围跳过。这个简单的范围限制让系统的误触发率大幅下降。3.4 测速功能M法测速实现与里程计算霍尔测速模块的输出接到STM32的定时器编码器模式引脚上用定时器自带的编码器接口进行脉冲计数不需要额外占用CPU中断精度也很高。M法测速的原理是在固定的时间窗口内统计脉冲数然后用脉冲数除以每圈脉冲数得到圈数再乘以轮子周长得到这段时间走过的距离除以时间就是速度。这种方式在高转速下精度很高低转速下由于脉冲个数少分辨率不够。但TT马达的转速在100~300rpm之间每圈13个脉冲10ms的测量窗口也有13个脉冲以上完全够用。#define PULSES_PER_REV 13 #define WHEEL_DIAMETER 6.5f // 单位cm float get_speed_cm_s(void) { uint16_t pulse_count; float speed; pulse_count TIM_GetCounter(TIM4); // 读取计数 TIM_SetCounter(TIM4, 0); // 清零 speed (float)pulse_count / PULSES_PER_REV * 3.14159f * WHEEL_DIAMETER; // 假设采样周期是100ms所以速度 脉冲数/每圈脉冲数 * 周长 / 0.1s speed speed / 0.1f; return speed; }测速数据可以用来做闭环调速也可以用来记录里程。我在项目里把里程数据实时显示在OLED上同时通过蓝牙把速度和里程发到手机APP演示效果很直观。测速模块还有一个额外的用途当小车检测到轮子被卡住速度远低于设定值且持续一段时间时自动停止电机这个逻辑在比赛里很实用能防止电机堵转烧毁驱动板。3.5 蓝牙控制HC05串口通信与指令协议设计蓝牙模块挂在USART1上波特率9600。很多人蓝牙连不上、收不到数据一半以上的原因是波特率没对上HC05默认是9600但如果你之前用AT指令改过波特率手机APP也得改成对应的波特率。蓝牙控制的指令协议我设计得非常简单一字节命令一字节结束符。比如F表示前进B表示后退L表示左转R表示右转S表示停止X表示退出遥控模式。这样在手机上用任意一个串口调试APP按下对应按键就能控制小车不需要专门写一个复杂的数据帧协议。串口接收用中断收到一个字节就存入环形队列主循环里解析。不要在主循环里用阻塞式接收轮询否则你按键按下去以后小车要等几十毫秒才有反应手感极其糟糕。// 环形队列串口中断里写入主循环里读取 volatile uint8_t uart_rx_buf[64]; volatile uint8_t uart_rx_head 0, uart_rx_tail 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE)) { uint8_t data USART_ReceiveData(USART1); uart_rx_buf[uart_rx_head] data; uart_rx_head (uart_rx_head 1) % 64; } } void process_bluetooth_cmd(uint8_t cmd) { switch (cmd) { case F: set_motor_speed(BASE_SPEED, BASE_SPEED); break; case B: set_motor_speed(-BASE_SPEED, -BASE_SPEED); break; case L: set_motor_speed(-BASE_SPEED * 0.5, BASE_SPEED); break; case R: set_motor_speed(BASE_SPEED, -BASE_SPEED * 0.5); break; case S: set_motor_speed(0, 0); break; default: break; } }蓝牙配对还有个细节HC05默认配对密码是1234如果连不上先试这个。另外HC05的STATE引脚会输出连接状态接一个LED指示灯连接成功后灯会变成慢闪调试时非常直观。3.6 WiFi与4G透传AT指令控制与TCP数据通道ESP8266和SIM7600在架构上可以归为一类都是“AT指令控制串口透传”。ESP8266挂在USART2上SIM7600挂在USART3上两个模块之间通过串口切换选择互不干扰。ESP8266的初始化流程是这样的void esp8266_init(void) { uart2_send_string(AT\r\n); // 测试模块是否在线 delay_ms(200); uart2_send_string(ATCWMODE1\r\n); // 设置为STA模式 delay_ms(200); uart2_send_string(ATCWJAP\SSID\,\PASSWORD\\r\n); // 连接WiFi delay_ms(2000); uart2_send_string(ATCIPSTART\TCP\,\192.168.1.100\,8080\r\n); // 建立TCP连接 delay_ms(500); uart2_send_string(ATCIPMODE1\r\n); // 进入透传模式 delay_ms(100); uart2_send_string(ATCIPSEND\r\n); }透传模式下的收发非常爽ESP8266收到的网络数据会直接通过串口发给STM32STM32往串口写什么ESP8266就原样发到TCP服务器上。这意味着蓝牙那套指令协议完全不用改只需要把指令的收发接口从USART1换成USART2就实现了从蓝牙遥控到WiFi局域网遥控的平滑切换。4G模块的流程类似只是多了SIM卡检测和网络注册的步骤我用的是ATCSQ查信号强度ATCREG?查网络注册状态然后ATNETOPEN打开数据连接最后也是TCP透传。4G模块启动时间比WiFi长很多模块上电后要等10秒以上才能AT指令正常响应程序里必须加一个超时重试机制否则一上电就发AT指令模块还没初始化完直接就卡死在那里。3.7 语音识别功能SU-03T离线命令词控制语音模块SU-03T通过UART4和STM32通信波特率9600。模块离线识别不需要联网识别到预设的命令词后会通过串口输出配置好的十六进制数据。比如我配置了这样一组命令词命令词串口输出数据对应动作“小智小智”0xAA 0xAA唤醒OLED亮屏“全速前进”0x01全速前进“倒退”0x02后退“左转”0x03左转“右转”0x04右转“刹车停车”0x05停车“启动循迹”0x10切换到循迹模式“启动跟随”0x11切换到跟随模式接收端就是一个简单的状态机收到0xAA 0xAA就认为是唤醒广播收到的单字节数据就查表执行对应的动作。void process_voice_cmd(uint8_t data) { switch (data) { case 0x01: set_motor_speed(MAX_SPEED, MAX_SPEED); break; case 0x02: set_motor_speed(-MAX_SPEED, -MAX_SPEED); break; case 0x03: set_motor_speed(-MAX_SPEED * 0.5, MAX_SPEED); break; case 0x04: set_motor_speed(MAX_SPEED, -MAX_SPEED * 0.5); break; case 0x05: set_motor_speed(0, 0); break; case 0x10: running_mode MODE_LINE_FOLLOW; break; case 0x11: running_mode MODE_FOLLOW; break; } }语音识别的使用要注意一个习惯问题SU-03T需要先说唤醒词“小智小智”听到回应后再说命令词不要上来就直接喊“全速前进”那样大概率识别不到。这个交互习惯要在演示前就跟观众讲清楚。4. 多传感器融合时的资源冲突与优先级调度4.1 定时器资源规划F103的定时器够不够用八个功能全部打开的时候定时器资源非常紧张。我这台小车的定时器占用如下TIM1两路PWM输出左轮和右轮速度控制TIM2一路PWM输出舵机云台TIM3一路输入捕获超声波EchoTIM4编码器模式两路霍尔测速TIM6或TIM7系统心跳用于控制周期的定时中断这么算完F103ZET6的8个定时器几乎全部用上了C8T6虽然定时器数量相同但引脚映射和复用资源更紧凑。所以我说如果做全功能选ZET6的另一个隐形好处就是定时器通道和GPIO的映射组合更灵活。TIM3既被超声波用又被其他功能占用的坑我遇到过超声波测距的Echo接在PA7上PA7重映射后是TIM3通道2但如果引脚初始化时配置的是普通输入而不是复用功能输入捕获就永远读不到高电平。这个排查花了我大半个晚上最终还是用逻辑分析仪才发现Echo引脚确实有5V高电平但STM32里定时器根本没捕获到原因是GPIO模式配置错误。4.2 串口资源分配与中断优先级USART1接HC05USART2接ESP8266USART3接SIM7600UART4接SU-03T。四个串口同时用中断优先级必须规划好否则响应就会相互干扰。原则是控制类指令优先级高于数据传输类。蓝牙和语音是控制指令中断优先级设高WiFi和4G是数据流优先级设低一些。NVIC配置NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 抢占优先级2位子优先级2位 // USART1 蓝牙抢占优先级0子优先级0 // UART4 语音抢占优先级1子优先级0 // USART2 WiFi抢占优先级2子优先级0 // USART3 4G抢占优先级2子优先级1中断里不要做耗时操作比如在USART1的中断服务函数里调用printf或者写OLED那样会拖慢系统响应。中断里只做“收数据、放环形队列”这件事具体处理放到主循环。4.3 功能优先级设计默认模式与切换机制这台小车有多种运行模式手动遥控、循迹、避障、跟随、语音控制。它们不是平等的我定了一个优先级顺序语音和蓝牙的“停车”指令优先级最高任何模式下都立即生效避障优先级高于循迹因为避障处理的是安全问题循迹优先于跟随因为循迹是相对固定的路径跟随是动态目标实现方式是在主循环里有个状态机每个模式下维护自己的控制函数但每轮循环都会先检查“急停”标志和“模式切换”标志while (1) { // 1. 先处理急停 if (emergency_stop_flag) { set_motor_speed(0, 0); emergency_stop_flag 0; running_mode MODE_MANUAL; continue; } // 2. 处理模式切换指令通过蓝牙、语音或按键 if (mode_switch_cmd ! 0) { running_mode mode_switch_cmd; mode_switch_cmd 0; } // 3. 根据当前模式执行控制逻辑 switch (running_mode) { case MODE_MANUAL: manual_control(); break; case MODE_LINE_FOLLOW: line_follow_control(); break; case MODE_AVOID: obstacle_avoid(); break; case MODE_FOLLOW: follow_control(); break; default: break; } delay_ms(10); // 10ms控制周期 }4.4 主循环架构前后台系统如何组织整个工程用的是典型的前后台系统裸机没有跑RTOS。前台是定时器中断承担超声波测距触发、编码器脉冲计数、霍尔测速采样这些硬实时任务后台是主循环承担指令解析、模式切换、OLED刷新、PID计算这些软实时任务。这种架构最大的好处是逻辑清晰、调试简单。如果跑FreeRTOS八个功能拆成八个任务任务间的同步和优先级设计反而成了新的复杂度来源。对F103这颗芯片来说裸机完全能应付这个应用场景没必要为了“显得高级”而上操作系统。主循环的周期我控制在10ms也就是100Hz控制频率。这个频率对电机控制来说足够了PWM本身的频率是10kHz但速度环和转向环的控制周期不需要那么快100Hz的更新率响应已经很快。如果主循环里任务太多导致超过10ms可以选择去掉OLED刷新或者把OLED刷新降到20Hz优先保证控制环的实时性。5. 实测中踩过的坑从现象到根因5.1 HC05蓝牙连不上的完整排查链路这是最常见的问题我整理一下当时踩坑的完整排查过程。现象手机搜索不到HC05或者能搜到但连接后发指令没反应。第一步先确认模块是否正常上电。HC05的VCC是3.6V到6V我直接给5V没问题但有些模块用3.3V供电也能工作却可能不稳定。查看板载LED慢闪表示AT模式快闪表示可配对常亮表示已连接。如果LED不亮先查电源。第二步引脚接线。HC05的TXD要接STM32的RXDRXD接STM32的TXD这是交叉接法很多新手会习惯性地同名前接结果完全收不到数据。第三步检查波特率。HC05默认波特率9600但如果模块之前被别人改过配置就可能是38400或者115200。把HC05的KEY引脚拉高重新上电进入AT模式用USB转TTL串口工具连接发AT指令查一下当前参数。注意AT模式下波特率固定是38400这是HC05的硬件特性。第四步如果手机能搜到但连接失败检查配对密码。默认是1234或者0000如果被改过用ATPSWD?查询。第五步如果连接成功但发送没反应大概率是STM32串口的波特率和模块不一致或者串口中断没有正确初始化。用串口调试助手直接连接HC05的TXD看STM32有没有往上发数据就能定位是哪一边的问题。排查这类问题一定要有链路思维手机到蓝牙模块是一段蓝牙模块到STM32是一段逐段定位不要在那里瞎猜。5.2 舵机抖动与电压跌落问题跟随模式下SG90舵机带着超声波云台扫描时会出现明显的抖动严重时小车还会重启。用万用表测舵机电源端发现电压在扫描瞬间从5V掉到4.2V幅度很大。原因分析SG90堵转电流接近700mA而我从MP1584输出的5V还要给其他模块供电舵机启动瞬间的大电流把5V母线电压拉低了STM32那边的3.3V也受影响单片机就复位了。解决措施第一在舵机电源端并联一个470uF的电解电容利用电容的电荷储备来缓冲瞬态压降。第二把舵机电源从5V母线上分出来单独走一段较粗的线避免舵机的电流波动传导到逻辑电路。第三在代码里给舵机加了渐变速度控制不让舵机瞬间从0度扫到180度而是按每步2度的速度移动把瞬时电流峰值降下来。实测下来加了电容和渐变控制后电源电压的波动控制在0.2V以内问题彻底解决。这提醒我电机和舵机的瞬态电流远比稳态电流大给它们供电的线路一定要单独走不能和逻辑电路挤在一起。5.3 循迹在强光下乱跑有一段时间小车在室内灯光下循迹正常一到阳光直射的走廊就跑飞。排查后发现是灰度传感器的阈值判断问题。灰度传感器的核心是红外发射管和接收管强光环境下的红外干扰很大白线和黑地的反射信号差异被压缩模块输出的数字电平开始乱跳。我的解决思路是改用ADC模式读取灰度传感器的模拟输出每个传感器实时采样然后做动态阈值校准。具体做法上电后让用户手动把小车放在白线区域按一下按键记录白色值再放到黑色区域按一下按键记录黑色值运行时的判断阈值取黑白值的中位数。这个校准过程只需5秒但效果立竿见影——无论强光还是暗光环境小车循迹都很稳定。uint16_t line_adc_white[5], line_adc_black[5]; uint16_t line_threshold[5]; void line_sensor_calibrate(void) { // 等待用户按键采集白线值 for (int i 0; i 5; i) { line_adc_white[i] adc_read(i); } // 等待用户按键采集黑线值 for (int i 0; i 5; i) { line_adc_black[i] adc_read(i); } for (int i 0; i 5; i) { line_threshold[i] (line_adc_white[i] line_adc_black[i]) / 2; } }这组的校准逻辑对所有光电类传感器都适用包括光电门、光敏电阻、红外对射思路都是一样的先标定环境上下限再从中取阈值不要用固定阈值硬拼环境变化。5.4 STM32延时函数卡死问题代码里用HAL_Delay()或者自写的delay_ms()时偶尔出现整个程序卡死的情况。排查后发现两个原因第一个是SysTick中断优先级和串口中断优先级配置不当。如果用HAL库的HAL_Delay()它依赖SysTick中断而SysTick的优先级默认是15最低。如果串口中断抢占优先级更高串口数据一直进来SysTick中断就一直被抢占HAL_Delay()里的while循环永远等不到SysTick计数减到0程序就“卡死”了。解决办法是把SysTick的优先级调高或者不用HAL_Delay改用DWT或者TIM定时来做延时。第二个原因是中断里调用了延时函数。比如在串口中断里调HAL_Delay(10)这就极端危险因为如果一个字节数据导致中断进入延时延时期间新的中断都被阻塞缓冲区溢出系统就乱套了。铁律中断服务函数里绝不调用任何延时函数更不做串口打印、OLED刷新这类阻塞操作。5.5 PID参数整定的血泪经验这个项目里循迹和跟随都用了PID参数整定我花了很长时间。最大的体会是先只调P让系统稳定下来再加I和D。循迹功能的P值整定过程是这样的初始P10小车沿直线走的时候非常迟缓弯道完全跟不上加大到P30小车开始有轻微的左右震荡但过弯基本能跟上继续加到P50直线段震荡明显、车头来回摆最终取P35加一点D5来抑制震荡效果最好。I值我设得比较小I2只在长直道有轻微偏移时慢慢修正。跟随功能的速度PID略有不同这里控制的是速度不是转向角所以我用了增量式PID输出是PWM的变化量这样天然带有积分记忆不会因为突然的累计误差导致输出超调。经验总结PID的调试顺序必须是先P后I再D不要上来就三个参数一起调那只会让系统发散得莫名其妙。另外每次只调一个参数记录下现象再改下一个。靠感觉调参数是最浪费时间的事一定要做实验记录。6. 源代码工程规划与扩展思路6.1 工程目录组织与代码模块划分一套八功能小车的代码如果全部堆在main.c里几千行代码会非常难维护。我的工程结构是这样划分的Project/ ├── App/ # 应用层 │ ├── main_control.c # 主循环、模式状态机 │ ├── line_follow.c # 循迹控制 │ ├── avoid.c # 避障控制 │ └── follow.c # 跟随控制 ├── Driver/ # 驱动层 │ ├── motor.c # TB6612电机驱动 │ ├── servo.c # SG90舵机驱动 │ ├── ultrasonic.c # HC-SR04测距 │ ├── line_sensor.c # 灰度传感器 │ ├── hall_speed.c # 霍尔测速 │ └── ir_avoid.c # 红外避障 ├── Module/ # 模块层 │ ├── bluetooth.c # HC05通信协议 │ ├── esp8266.c # WiFi透传 │ ├── sim7600.c # 4G透传 │ └── voice.c # SU-03T语音识别 ├── BSP/ # 板级支持包 │ ├── led.c │ ├── key.c │ ├── oled.c │ └── usart.c └── User/ └── main.c # 入口、外设初始化分层的思想是Driver层只管最底层的寄存器操作比如让电机转起来、让舵机转到指定角度Module层定义模块级的通信和数据接口比如解析蓝牙指令、封装WiFi透传APIApp层决定“这个时刻该干什么”比如当前模式是循迹还是跟随。这样每一层都可以单独测试出了问题也容易定位。6.2 调试技巧串口调试、逻辑分析仪和printf重定向开发过程中最依赖的工具是串口调试。我把F103的USART1蓝牙在调试阶段暂时拆出来当调试串口用通过一个USB转TTL线连接到PC这样就能在代码里用printf输出调试信息。重定向printf的方式是重写fputc函数int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }调试循迹时我每隔100ms打印一次五个传感器的原始ADC值和判定后的二进制状态这样不用看小车跑圈直接在电脑上就能分析传感器数据是否正确。调试超声波时我打印云台角度、测距值、目标方位配合串口曲线工具能直观看到跟随控制的实时表现。逻辑分析仪是排查串口问题最有力的工具。有一次蓝牙连不上用逻辑分析仪抓了USART1的TX引脚波形发现波特率实际上是38400而不是9600一下定位到问题——模块之前被人改过配置。如果你手头有逻辑分析仪调试UART、I2C、SPI这类协议故障时效率会提升数倍。6.3 从智能小车到更完整的机器人平台这个项目做完之后后续的扩展方向很明确一是把控制板升级到更强的主控。如果需要在车上跑视觉识别F103的算力是远远不够的。可以把主控换成树莓派或者K210STM32继续担任电机控制和传感器采集的“实时层”树莓派运行图像识别和路径规划STM32和树莓派之间用串口或者CAN总线通信。这种“双芯片”架构在机器人竞赛里很常见STM32负责底层务实树莓派负责上层智能。二是接一个带AI加速的摄像头模块比如K210的Maix Cam。K210可以通过串口把识别到的目标坐标发给STM32让小车实现巡线之外的“找色块”“追踪人脸”“循球”等功能。本届竞赛里很火的“工创赛智能物流小车”项目核心思路就是这样STM32做运动控制视觉模块做目标识别。三是如果想让小车联网能力再强一点可以把ESP8266升级为ESP32走MQTT协议连接云平台这样小车的数据就能被远程监控手机APP端也可以通过云平台下发指令彻底摆脱局域网限制。这个时候4G模块的作用就又体现了——WiFi覆盖不到的区域用4G网络作为备用链路数据传输的可靠性会大大提升。做完这个八功能项目之后我最大的体会是单片机开发入门其实不难难的是把多个模块放到一台设备上让它们协同工作。硬件上要提前规划供电和引脚分配软件上要设计清晰的模块接口和状态机。如果你也打算做一台全功能智能小车建议先从小功能单元跑通再逐步整合这样每个模块都能单独验证最后联动的时候才不会出现“一启动就全部崩掉”的无奈局面。希望这份总结能帮你少走一些弯路。本文还有配套的精品资源点击获取
返回列表