STM32F4超声波测距实现避障功能
STM32F4超声波测距实现避障功能
你有没有遇到过这样的场景:机器人一头撞上墙,或者扫地机在沙发底下卡住动弹不得?😅 其实问题就出在“眼睛”不够亮——环境感知能力太弱。而今天我们要聊的这个小系统,正是让设备“睁眼看世界”的第一步: 用STM32F4 + HC-SR04实现超声波避障 。
别看它结构简单,背后可藏着不少工程智慧。从硬件时序到中断处理,再到滤波算法和动作决策,每一步都决定了你的小车是优雅绕行,还是原地懵圈💥。
超声波测距是怎么“听”出距离的?
我们先来聊聊那个小小的HC-SR04模块。它长得像一对“电子耳朵”,其实更像一个会喊话又会听回音的家伙。
工作流程特别像你在山谷里大喊一声:“喂——”,然后数着秒等“喂——”回来。只不过它用的是 40kHz的超声波 ,人耳听不见,但精度很高。
整个过程就这么几步:
-
我们给它的
Trig脚发个 至少10μs的高电平 ,相当于说:“开始测!” - 它自己啪啪啪连发8个超声波脉冲;
-
回波回来后,它的
Echo脚就会变成高电平,持续的时间正好等于声音往返的时间; - 我们只要算出这个高电平持续了多久,就能知道距离。
📏 距离公式长这样:
$$
\text{Distance (cm)} = \frac{\text{Time (μs)} \times 0.034}{2}
$$别忘了除以2哦,因为这是来回一趟的距离!声音在空气中跑得差不多是340m/s,也就是0.034 cm/μs。
听起来挺简单的对吧?但真要让它稳定工作,可不是接上线就完事了。
硬件细节不能马虎
先看看HC-SR04的关键参数:
| 参数 | 值 |
|---|---|
| 工作电压 | 5V(注意!) |
| 测量范围 | 2~400 cm |
| 分辨率 | 0.3 cm |
| 响应频率 | ~60Hz(两次测量间隔 ≥60ms) |
⚠️
重点来了
:
虽然HC-SR04的
Trig
引脚可以接受3.3V逻辑输入(STM32输出刚好匹配),但它的
Echo
输出是
5V高电平
!而STM32F4的IO口最大只能承受3.6V……直接连上去?轻则读数不准,重则芯片报废😱。
✅ 解决方案有两个:
- 加个
分压电阻
(比如1kΩ+2kΩ),把5V降到3.3V左右;
- 或者上
电平转换芯片
(如TXS0108E),更稳妥但也更贵。
另外提醒一句:别让电机电源和传感器共地不干净,否则一启动电机,超声波就读出一堆“鬼影数据”👻。
时间测量靠谁?定时器才是幕后英雄!
你说,能不能用
HAL_Delay()
或for循环来测时间?理论上能,但实际上——
误差大到没法用
!
为什么?因为软件延时受CPU调度、中断优先级影响太大。哪怕只差几个微秒,距离偏差就可能超过1厘米,这还怎么精准避障?
所以我们得请出STM32的大杀器: 输入捕获(Input Capture)功能 。
TIM2出手,精确捕捉边沿
我们选了TIM2通道1来监听
Echo
引脚的变化。配置要点如下:
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84 - 1; // 168MHz / 84 = 2MHz → 每tick = 0.5μs
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFFFFFF; // 32位自动重载
HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_1);
预分频设为83(即84-1),这样定时器每0.5μs加一次数。再配合输入捕获中断,就能在上升沿和下降沿瞬间抓取时间戳。
动态切换边沿检测,代码这么写:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
if (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_SET) {
// 上升沿 → 开始计时
echo_start = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
// 改为下降沿触发
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
} else {
// 下降沿 → 结束计时
echo_end = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
pulse_duration = (echo_end - echo_start) * 0.5f; // 单位:μs(因为每tick=0.5μs)
distance_cm = (pulse_duration * 0.034) / 2.0f;
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
// 恢复上升沿检测
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
}
}
}
👉 这里有个小技巧:通过修改CCER寄存器动态切换捕获极性,避免使用外部中断或其他复杂机制,简洁高效!
微秒级延时怎么做?别用SysTick了!
你可能会想:触发信号只需要10μs高电平,随便Delay一下就行了吧?
但标准
HAL_Delay()
最小单位是1ms,显然不行。我们可以用另一个定时器(比如TIM5)来做微秒延时:
TIM_HandleTypeDef htim5;
void MX_TIM5_Init(void) {
htim5.Instance = TIM5;
htim5.Init.Prescaler = 84 - 1; // 2MHz计数频率
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
htim5.Init.Period = 0xFFFFFFFF;
HAL_TIM_Base_Start(&htim5);
}
void delay_us(uint32_t us) {
uint32_t start = __HAL_TIM_GET_COUNTER(&htim5);
while ((__HAL_TIM_GET_COUNTER(&htim5) - start) < us);
}
这样调用就OK啦:
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET);
delay_us(10);
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET);
💡 小贴士:如果你追求极致精度,还可以开启DWT(Data Watchpoint and Trace)单元的Cycle Counter,直接读CPU周期数。不过移植性较差,适合调试阶段使用。
主控逻辑:从测距到避障决策
现在我们已经能稳定拿到前方距离了,接下来就是“大脑”做判断的时候了。
系统整体架构大概是这样:
[STM32F4]
│
├─ 控制Motor Driver → 驱动左右轮
└─ 触发HC-SR04 → 获取前方障碍物距离
↓
决策引擎 → 前进 / 停止 / 后退 / 转向
主循环非常直观:
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM5_Init();
float threshold = 20.0f; // 避障阈值:20cm
while (1) {
Ultrasonic_Trigger(); // 发射一次
HAL_Delay(60); // 符合模块建议周期
if (distance_cm > 2 && distance_cm < 400) {
if (distance_cm < threshold) {
Motor_Stop();
HAL_Delay(100);
Motor_Backward();
HAL_Delay(500);
Motor_TurnLeft(); // 随便转个方向逃出生天 😂
HAL_Delay(300);
} else {
Motor_Forward(); // 安全,继续前进
}
} else {
Motor_Forward(); // 数据异常,默认前进
}
}
}
看起来很完美?但现实往往没那么理想……
实战中的坑,一个都不能少
❌ 问题1:数据跳来跳去,像抽风一样?
这是最常见的问题。空气流动、表面材质(比如窗帘吸音)、多路径反射都会导致回波不稳定。
🔧 解法:上滤波!
推荐一个简单高效的 滑动平均滤波器 :
#define FILTER_SIZE 5
float dist_buffer[FILTER_SIZE] = {0};
int idx = 0;
float apply_filter(float raw) {
float sum = 0.0f;
dist_buffer[idx] = raw;
for (int i = 0; i < FILTER_SIZE; i++) sum += dist_buffer[i];
idx = (idx + 1) % FILTER_SIZE;
return sum / FILTER_SIZE;
}
然后在主循环中替换原始值:
distance_cm = apply_filter((pulse_duration * 0.034) / 2.0f);
效果立竿见影,数据平稳多了📊。
❌ 问题2:只会看前面,侧面撞墙怎么办?
单个传感器只能感知正前方,容易陷入“盲区”。
🔧 解法: 三向布局 !
加两个额外的HC-SR04,分别朝左前和右前安装,形成三角探测区。主控根据三个方向的距离综合判断转向策略:
- 前方近,左侧远 → 右转
- 前方近,右侧远 → 左转
- 四面楚歌?原地打转求生🤣
当然,也可以考虑换成带舵机旋转的单探头方案,节省IO资源。
❌ 问题3:反应慢半拍,等发现时已经撞上了?
这通常是由于主循环阻塞造成的。比如用了
HAL_Delay()
太久,或者其他任务占用了CPU。
🔧 解法思路:
- 使用
非阻塞延时
(基于定时器中断或FreeRTOS延迟);
- 把测距任务封装成独立任务,用RTOS调度;
- 或者结合DMA+定时器触发多路采样,提升并发能力。
✅ 设计建议清单
| 项目 | 推荐做法 |
|---|---|
| 供电设计 | 给HC-SR04单独加LDO稳压5V,避免电机拉低电压 |
| IO保护 | Echo脚串联220Ω电阻 + 并联3.3V稳压二极管 |
| 安装角度 | 保证传感器水平,倾斜会导致测距偏大 |
| 温度补偿 |
外接DS18B20,动态调整声速:
$$ v = 331.5 + 0.6T \,\text{(m/s)} $$ T为摄氏温度 |
| 校准方法 | 实际测量不同距离下的返回值,拟合修正系数 |
为什么STM32F4特别适合这类应用?
你可能会问:为啥非要用STM32F4?便宜的F1系列不行吗?
当然不是不行,而是F4真的强太多了:
-
Cortex-M4+FPU
:浮点运算不用模拟,
distance_cm = ...这种计算更流畅; - 最高180MHz主频 :响应更快,支持更高采样率;
- 多个通用定时器(TIM2~TIM5) :轻松实现一路测距+多路PWM输出;
- HAL库成熟稳定 :开发效率高,移植方便;
- 支持RTOS :未来扩展路径清晰。
换句话说, 它既能让初学者快速入门,又能支撑进阶开发需求 ,简直是教学与实战两开花🌸。
结语:一个小功能,通往智能世界的入口
别小看这个“嘀——嘀——”测距的小系统。它其实是一个完整的 感知-决策-执行闭环 :
- 感知:HC-SR04获取环境信息;
- 决策:MCU判断是否需要避障;
- 执行:驱动电机做出反应。
而这正是所有智能移动设备的核心逻辑。今天的避障小车,明天可能就是AGV物流车、巡检机器人,甚至是自主导航的无人机🚀。
而且一旦掌握了这套方法论,你会发现——换激光雷达?只是换个传感器驱动;加陀螺仪融合定位?不过是多几个I2C通信而已。
所以啊,嵌入式开发的魅力就在于: 从最基础的一行代码开始,一步步搭起通往智能世界的桥梁 。
你现在写的每一行
HAL_GPIO_WritePin()
,都在悄悄改变机器“看”世界的方式👀。
Keep coding, keep exploring! 💻✨
更多推荐
所有评论(0)