尧图精选

STM32三路超声波自动跟随小车设计与实现

🕒 发布时间:2026/9/8 22:29:50 📁 来源:尧图网络
简介面向嵌入式学习者的STM32三路超声波自动跟随小车完整工程基于STM32与三路超声波传感器实现障碍检测与自动跟随涵盖控制策略、传感器应用及电机驱动等核心内容适合作课程设计、毕业设计或智能车入门实践。资源包共309个文件压缩包8.45MB以c/h源文件为主包含大量编译中间文件o、crf、s等以及hex、uvproj、ewp等工程配置文件可对照源码、配置与输出文件快速理解工程结构和调试流程。目前已有2399人学习下载。项目工程不仅给出主函数和硬件配置还包含Keil/IAR等工程文件、调试脚本与map文件便于使用者分析编译链接细节和内存分布同时可结合描述中的PID或模糊控制思路自行优化跟随策略是掌握STM32嵌入式开发与超声测距应用的实用参考。 我这次做的小车从立项到跑通前后折腾了两周半。核心目标很明确用STM32做主控靠三路超声波传感器实现对人体的实时测距和方向判断让小车能自动跟着人走既不掉队也不怼人。过程中踩了不少坑也把网上零散的资料整理成了自己的代码框架。这篇就按我的实现思路把这台三路超声波自动跟随小车的硬件选型、控制逻辑、代码细节和调参经验完整捋一遍。如果你正准备做类似的STM32项目或者拿它当毕设底子这篇文章里提到的很多细节可以直接用。1. 三路超声波方案它到底比单路、双路强在哪1.1 自动跟随的需求难点拆解先说结论只靠一个超声波模块做出来的东西充其量叫避障小车不叫跟随小车。单路超声波放在正前方时小车能知道前方有没有障碍物、距离多远但它完全不知道目标在左边还是右边。双路超声波如果左右各放一个可以判断目标偏哪边可一旦目标离得很近或者偏离角度超过传感器波束角小车就会丢失目标原地愣住。自动跟随本质上要解决三个同时发生的问题目标有多远、目标在哪个方向、我该用多大速度往哪个方向跑。三路超声波恰好覆盖了这三个需求——正前方一路负责测纵向距离左右两路负责测横向偏移。三路数据组合起来就能判断出目标大致处于小车前方扇形区域的哪个位置从而决策出前进、后退、左转、右转、停止五个基本动作。我最终选的是 HC-SR04 超声波模块三块同型号。之所以不用单总线集成的激光雷达或 TOF 传感器一是成本问题三块 HC-SR04 加起来不到三十块钱二是这个项目本来就是练手性质HC-SR04 的触发和回波逻辑够简单配合定时器输入捕获就能做精确测距非常适合理解测距原理。如果你的预算充裕可以把传感器换成 US-015 或者 JSN-SR04T代码逻辑基本不变只是电气参数和波束角略有差异。1.2 硬件清单与接线规划我在硬件选型上没有堆料全部按够用且好调的标准来器件型号/规格数量作用主控STM32F103C8T6 最小系统板1核心控制定时器捕获、PWM输出超声波模块HC-SR045V供电3前、左、右三向测距电机驱动L298N 双路H桥1驱动两个直流减速电机电机3-6V 直流减速电机2两轮差速驱动电池7.4V 18650 电池组两节串联1整车供电降压模块LM2596S 降压模块17.4V 降到 5V 给主控和传感器供电车架亚克力三层板小车底盘1平台支撑这里 CH1 到 STM32 的接线我直接给出来免得新手对着数据手册翻半天传感器信号STM32引脚备注前路 TrigPA0通用GPIO输出前路 EchoPA1定时器TIM2_CH2输入捕获左路 TrigPA2通用GPIO输出左路 EchoPA3定时器TIM2_CH3输入捕获右路 TrigPA4通用GPIO输出右路 EchoPA5定时器TIM2_CH4输入捕获左电机 PWMPA6TIM3_CH1右电机 PWMPA7TIM3_CH2左电机方向PB0/PB1L298N IN1/IN2右电机方向PB10/PB11L298N IN3/IN4有一点必须提HC-SR04 的 Echo 回波引脚输出的是 5V 高电平而 STM32F103 的 GPIO 是 5V 容忍的可以直接接不会烧引脚。但如果是其它型号的单片机先确认是否 5V 容忍不是的话必须加电平转换或分压电阻。2. 传感器布局与跟随决策表小车是怎么想出方向的2.1 三路探头的物理布局与测量范围布局方式直接决定决策逻辑能不能成立。我采用的是正前方 0 度 左右各 45 度的布局前路传感器平行于车头指向正前方左右两路分别向外侧偏转 45 度安装。HC-SR04 的波束角大约 15 度偏转 45 度之后左右两路的探测区域会在车前方形成交叉覆盖。三路传感器能覆盖车身正前方大约 120 度的扇形区域目标只要在这个范围内至少有一路传感器能测到有效距离。安装高度方面我踩了个坑传感器离地太近会把地面当成障碍物尤其是走过瓷砖缝隙或者地面有轻微凸起时回波会直接误判。后来把传感器安装在离地 8 到 10 厘米的高度倾斜角往下压 5 度左右让波束平行于人体小腿高度效果明显改善。还有一个容易忽略的点三路传感器的 Trig 和 Echo 信号线要避免和电机电源线走在一起。HC-SR04 的 Echo 回波是微秒级的高电平脉宽电机启停时的电流尖峰如果串进信号线测距值会出现偶发抖动。我在布局时把信号线全部走车架上层电源线走下层的过线槽两者保持 3 厘米以上间距实测干扰小了很多。2.2 五状态跟随决策逻辑传感器的数据读到之后不能直接拿去驱动电机中间必须有一个状态决策层。我定义了一个五状态有限状态机停止、前进、后退、左转、右转。决策输入是三路距离值输出是电机 PWM 占空比和方向电平。先设定几个关键阈值参数数值含义STOP_DIST20cm小于此距离认为目标太近或已撞到停车FOLLOW_MIN30cm跟随区间下限小于此距离开始后退FOLLOW_MAX50cm跟随区间上限大于此距离开始前进OFFSET_DEAD15cm左右距离差小于此值不修正方向避免频繁摆动跟随的核心逻辑分两层判断。第一层是纵向距离判断取前路距离值 F如果 F 小于 20cm无论左右如何立即停车如果 F 在 30cm 到 50cm 之间纵向不动作保持当前速度如果 F 大于 50cm前进F 小于 30cm 且大于 20cm后退。第二层才是方向判断取左右两路距离 L 和 R如果 L 比 R 大超过 15cm说明目标偏右需要右转反过来则左转。当左右差在正负 15cm 以内时认为目标在正前方不修正方向。先判距离、再判方向这个顺序很重要。如果先判方向遇到目标很近的情况小车可能一边停车一边原地转向姿态非常奇怪。先保证纵向距离安全再解决横向偏差控制逻辑会顺很多。2.3 状态机的代码骨架状态机本身不复杂关键是要写得可读、易调参。我的实现里把每个状态对应的动作封装成了独立函数主循环里只做状态判定 动作执行两件事typedef enum { STATE_STOP, STATE_FORWARD, STATE_BACKWARD, STATE_LEFT, STATE_RIGHT } FollowState; FollowState follow_state_machine(float dist_front, float dist_left, float dist_right) { if (dist_front STOP_DIST) { return STATE_STOP; } if (dist_front FOLLOW_MIN) { return STATE_BACKWARD; } if (dist_front FOLLOW_MAX) { return STATE_FORWARD; } float offset dist_left - dist_right; if (offset OFFSET_DEAD) { return STATE_RIGHT; } if (offset -OFFSET_DEAD) { return STATE_LEFT; } return STATE_STOP; }这个函数返回的是目标状态真正执行时还要根据目标状态设定电机方向和 PWM。有一点需要额外说明为什么在 30cm 到 50cm 区间内且左右平衡时选择停止而不是原地等待因为在跟随场景下这个区间是理想跟随时距此时目标相对于小车基本静止小车不需要补充位移。如果此时继续前进会迅速突破 20cm 的安全距离导致停车-后退-前进的来回震荡。3. 从定时器捕获到 PWM 输出代码实现全链路3.1 超声波测距为什么不建议用 DelayHC-SR04 的测距原理不难Trig 引脚给 10 微秒以上的高电平模块内部自动发出 8 个 40kHz 的超声波脉冲同时 Echo 引脚拉高当模块收到回波Echo 拉低。Echo 高电平的持续时间就是超声波从发射到返回的总时间距离等于时间乘以声速再除以 2。网上很多教程直接用一个 GPIO 口测 Echo 高电平时间方法是while(ECHO_PIN 1) { time; }。这种做法在单路测距时勉强能跑但在三路跟随场景下问题很大Echo 高电平持续时间最长能到 30ms 级别如果主循环里死等三路回波总共可能要阻塞近 100ms电机控制的实时性完全没法保证。小车会出现在目标已经转向时控制指令还停留在上一帧的状态。我改用 STM32 的定时器输入捕获功能来测脉宽。原理是把定时器配置成捕获模式Echo 引脚出现上升沿时记录当前计数值下降沿时再次记录两个计数值之差除以定时器时钟频率就是高电平持续时长。整个过程不阻塞 CPU主循环可以同时处理电机控制。以下是关键配置代码用的是标准库TIM2 的四个通道里我用了通道 2、3、4 来接三路 Echovoid TIM2_Capture_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_3 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseInitStructure.TIM_Period 0xFFFF; TIM_TimeBaseInitStructure.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz计数周期 1us TIM_TimeBaseInitStructure.TIM_ClockDivision 0; TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInit(TIM2, TIM_ICInitStructure); // 通道3、4配置类似省略 TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2); TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); TIM_Cmd(TIM2, ENABLE); }这里有一个设计细节我把 TIM2 配置成「上升沿复位从模式」意思是每当通道 2 出现上升沿时定时器计数值自动清零。这样当下降沿中断发生时直接读取TIM_GetCapture2(TIM2)拿到的就是整个高电平的宽度不需要额外保存上升沿计数值再相减。这个技巧对三路测距尤其有用因为每路 Echo 都对应一个独立的捕获寄存器互不干扰。3.2 超声波轮流触发三路不打架的关键三路 HC-SR04 如果同时触发回波会互相串扰A 路发出的声波被 B 路接收或者多路回波叠加导致测距值无规律跳动。解决办法是轮流触发每一路之间间隔至少 60ms。这里有个经验值超声波在空气中的传播速度约 340m/s实测距离若按 4.5m 最大量程算单次测距往返时间约 26ms。间隔取 60ms 既保证了上一路回波完全结束又留出了余量。我的主循环框架大致是这样while (1) { // 轮流触发三路超声波 trigger_ultrasonic(TRIG_FRONT); delay_ms(60); dist_front get_distance(ECHO_FRONT); trigger_ultrasonic(TRIG_LEFT); delay_ms(60); dist_left get_distance(ECHO_LEFT); trigger_ultrasonic(TRIG_RIGHT); delay_ms(60); dist_right get_distance(ECHO_RIGHT); // 距离平滑滤波 dist_front smooth_filter(dist_front, 0.3); dist_left smooth_filter(dist_left, 0.3); dist_right smooth_filter(dist_right, 0.3); // 状态判定与电机控制 state follow_state_machine(dist_front, dist_left, dist_right); motor_control(state); }距离计算公式也一并给出float get_distance(uint16_t echo_pin) { uint32_t pulse_width TIM_GetCapture2(TIM2); // 单位微秒 float distance_cm pulse_width * 0.0343f / 2.0f; return distance_cm; }0.0343 是声速 343m/s 换算成 cm/us 的值。这个参数在不同温度下略有变化如果需要高精度可以加温度补偿但对于跟随小车来说这个精度完全够用。3.3 电机的差速控制左转右转其实是一边快一边慢两轮差速驱动小车的转向原理很简单想让小车右转就让左轮转得快、右轮转得慢甚至反转想让小车左转则反过来。我用 TIM3 的通道 1 和通道 2 输出两路 PWM通过改变占空比控制速度。PWM 频率我选的是 1kHz。这个频率是直流减速电机的典型工作区间既能保证扭矩输出稳定又不会产生刺耳的啸叫。1kHz 以上的 PWM 频率会降低电机感性负载的电流纹波但驱动芯片的开关损耗也会增加1kHz 在这个项目里是性价比最高的选择。电机控制函数如下void motor_control(FollowState state) { switch (state) { case STATE_STOP: set_motor_left(0, 0); set_motor_right(0, 0); break; case STATE_FORWARD: set_motor_left(SPEED_NORMAL, 1); set_motor_right(SPEED_NORMAL, 1); break; case STATE_BACKWARD: set_motor_left(SPEED_NORMAL, 0); set_motor_right(SPEED_NORMAL, 0); break; case STATE_LEFT: set_motor_left(SPEED_TURN, 0); // 左轮反转 set_motor_right(SPEED_TURN, 1); // 右轮正转 break; case STATE_RIGHT: set_motor_left(SPEED_TURN, 1); set_motor_right(SPEED_TURN, 0); break; } }set_motor_left(speed, direction)的第一个参数是 PWM 占空比比较值第二个参数控制电机方向。我用的 L298N 模块IN1/IN2 的高低位组合决定正反转ENA/ENB 接 STM32 的 PWM 输出控制速度。SPEED_NORMAL我取的 3200在 0-9999 的 ARR 区间里约占 32%这个值在实际测试中能让小车以约 0.4m/s 的速度跟随既不会太慢让目标走远也不会太快导致反应不及。SPEED_TURN我取 2800转向稍微降一点速减少急转时的离心感。4. 调试过程实录三路超声波小车最常见的三个坑4.1 三路传感器互相干扰导致距离跳变我最初把三路超声波同时触发结果串口打印出来的距离值非常诡异正前方明明站着人显示 80cm人走开后反而变成 40cm。排查过程是这样的先用排除法把左右两路的 Trig 线拔掉只保留前路测距恢复正常。再把左路接回去不触发它仍然正常。最后确认问题出在同时触发上。原因很直观三路传感器同时发射 40kHz 超声波声波在空间里叠加A 路的回波可能被 B 路的接收头捕捉到。由于三路声波发射时间一致干扰波和真实回波混在一起测出来的脉宽毫无规律。解决方案就是我在 3.2 写的轮流触发。这里额外补充一个细节左右两路的安装角度是 45 度声波打到人体后反射路径更复杂回波信号比前路弱更容易受干扰。所以我在软件里加了数据有效性判断距离值小于 2cm 或大于 400cm 时判定为无效数据丢弃且不更新距离变量沿用上一次的合法值。这样即使偶尔测到异常值也不会导致小车突然抽搐。4.2 电机启动瞬间 STM32 复位这个坑是最隐蔽的。现象是小车原地静止时一切正常一给前进指令电机启动STM32 就重启串口打印的初始化信息反复出现。用万用表量电池电压空载 7.4V接上电机后瞬间掉到 6V 以下但 STM32 的供电是经过 LM2596 降到 5V 的按理说不会受这么大影响。问题出在共地回了上L298N 的 GND 虽然和 STM32 的 GND 接了但电机的大电流脉冲会在这个公共地上产生压降导致 STM32 的参考地瞬间被抬升5V 供电实际上变成了 4.2V 甚至更低主控复位。解决办法分三步第一给 L298N 的电机电源输入端并联一个大电容我用的是 470uF 电解电容加 0.1uF 陶瓷电容吸收电机启动时的瞬时大电流第二保证 L298N 的逻辑供电5V和 STM32 的供电隔离我用的是双路输出降压模块一路给主控一路给电机驱动逻辑电路第三PID 调速时避免 PWM 从 0 突然跳到目标值软件里做了加速斜坡每 20ms 增加一小步占空比让电流平缓上升。4.3 目标跟丢之后的搜索策略小车跟人的过程中人转弯时很容易突然跑出超声波覆盖范围三路传感器全测不到有效距离。如果此时不做任何处理小车会停车等待体验很差。我加了一个丢失计时器逻辑当连续 10 个控制周期内三路距离都无效时认为目标已丢失。此时小车进入搜索模式先原地右转 90 度等待 500ms再左转 180 度扫描一遍。如果在搜索过程中任何一路测到有效距离立即退出搜索模式回到正常跟随状态。这个策略要注意一个问题单靠超声波搜索目标本质上是个盲搜如果目标站在小车正后方三路传感器全都测不到小车永远找不到目标。我做了一个折中搜索模式持续 5 秒后自动停车等待人工干预。想彻底解决这个问题可以考虑加入一个舵机带动超声波模块做 180 度扫描但这已经超出三路固定传感器的范畴了可以作为下一个改进方向。5. 调参经验与下一步能玩出的花活5.1 实测调参记录哪些参数最值得反复试跟随小车的好不好用一半靠硬件一半靠参数。我在测试中积累了几个关键调节点参数初始值最终值调整原因FOLLOW_MIN25cm30cm25cm 时人稍向前倾就容易触发后退太灵敏FOLLOW_MAX60cm50cm跟随时距超过 50cm 后小车反应有延迟缩小到 50cm 更跟手OFFSET_DEAD10cm15cm10cm 时左右轻微晃动就触发转向左右来回摆动明显SPEED_NORMAL400032004000 时起步太冲人走慢速时小车反复前进后退传感器离地6cm9cm6cm 时地面反光严重测距跳动频繁OFFSET_DEAD 的调整尤其值得多说两句。这个参数本质上是个滞回比较器控制左右转向的灵敏度。调小了小车对目标的横向偏移反应太灵敏人稍微侧身它就转向行驶轨迹是之字形的调大了目标已经偏到很侧边了也不转向容易跟丢。我的经验是先定一个值跑直道观察小车轨迹是不是直线如果不是就逐步加大 OFFSET_DEAD直到直道轨迹平稳。5.2 功能扩展方向让跟随小车从能跑到好用三路超声波自动跟随小车做到这一步已经具备了完整的感知、决策、执行闭环。这个平台后续可以玩的方向很多我列几个我认为性价比最高的第一加入闭环速度控制。当前速度是开环的电池电量不同、地面摩擦不同都会影响实际速度。用编码器电机加 PID 闭环可以让小车在坡道和地毯上也保持恒定速度。第二给小车加一个蜂鸣器和 LED 灯条用不同声音和光效提示当前状态——跟随中、目标丢失、电量低——实用性一下就上来了。第三把主控从 STM32F103C8T6 升级到 STM32F407 或加一块 ESP8266 模块把状态数据无线发到手机 App 上远程监控小车运行日志调试效率能高一个档次。还有一个我自己很感兴趣的进阶方向用卡尔曼滤波把三路超声波数据和 MPU6050 的角速度数据融合解决超声波数据毛刺多、刷新率低的问题。这样小车在快速转向时也能保持平滑的姿态估计跟随体验会接近工业级的 AGV 小车。这个项目的完整工程文件包括 HARDWARE 驱动层代码、CORE 核心文件、SYSTEM 系统文件、USER 用户代码以及原理图和 PCB 设计文件全部整合在一个工程包内。如果你正在准备毕业设计或者想系统练一遍 STM32 开发直接拿这套工程去改比从零写要省太多时间。我的建议是先把超声波测距和电机控制的底层驱动跑通再一步步套上状态机逻辑最后再来调参整个过程会非常顺畅。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →