我们都知道声音是由物体振动产生的,人能听到的频率在20Hz~20kHz。频率小于20Hz的叫次声波,频率大于20kHz的叫超声波。
超声波可以在空气、液体和固体中传播,可以被物体反射、折射、散射等,并且有频率高、波长短、绕射现象小、方向性好等特点。从而提供了丰富的用途,医学影像、清洁物品、材料检测、非接触测距等等。
本次我们要讲的超声波传感器就是非接触测距,用于测量物体与传感器之间的距离,可用于车辆安全(倒车雷达),安防系统(检测到移动物体并触发警报),工业自动化(物体定位、检测和避障)等等。
1. 源码下载及前置阅读
本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!
本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):
如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。
- 【STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html】
- 【一文教你使用MDK开发工具:https://www.lxlinux.net/e/stm32/mdk-development-tool-tutorial.html】
- 【零基础快速上手STM32开发(手把手保姆级教程):https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html】
往期教程,有兴趣的小伙伴可以看看。
- 【手把手教你玩转蓝牙模块(原理+驱动):https://www.lxlinux.net/e/stm32/bluetooth-turorial.html】
- 【手把手教你玩转ESP8266(原理+驱动):https://www.lxlinux.net/e/stm32/esp8266-tutorial.html】
- 【手把手教你玩转DHT11(原理+驱动):https://www.lxlinux.net/e/stm32/dht11-tutorial.html】
作者简介 |
---|
大家好,我是良许,博客里所有的文章皆为我的原创。 下面是我的一些个人介绍,欢迎交个朋友: · 211工科硕士,国家奖学金获得者; · 深耕嵌入式11年,前世界500强外企高级嵌入式工程师; · 书籍《速学Linux作者》,机械工业出版社专家委员会成员; · 全网60W粉丝,博客分享大量原创成体系文章,全网阅读量累计超4000万; · 靠自媒体连续年入百万,靠自己买房买车。 |
我本科及硕士都是学机械,通过自学成功进入世界500强外企。我已经将自己的学习经验写成了一本电子书,超千人通过此书学习并转行成功。现在将这本电子书免费分享给大家,希望对你们有帮助:
2. HC-SR04介绍
超声波传感器有很多的信号:HC-SR04、UC-025、UC-026、UC-015、US-100等等,它们之间大同小异,无非是工作参数有点不一样,像是工作的电压或温度、探测距离或精度有点差别,引脚是一样的,都是4个引脚(US-100 多一个 GND 引脚),引脚顺序和功能也是一样的。
大家在学习和工作中可以自行选择合适的型号,这里我为大家介绍最常见的 HC-SR04 这个型号。
2.1 HC-SR04型号介绍
现在市面上的 HC- SR04 有新版和旧版,我们介绍的是新版。新版性能比老版的精度更高,测距范围更远,可达6米,高于一般超声波测距模块。采用 CS-100A 超声波测距 SOC 芯片,高性能,工业级,宽电压,价格在4块钱左右。
2.2 HC-SR04工作参数及引脚介绍
HC-SR04 工作参数:
- 探测距离:2~600cm
- 探测精度:0.1cm±1%
- 感应角度:<15°
- 输出方式:GPIO
- 工作电压:DC 3~5.5V
- 工作电流:5.3mA
- 工作温度:-40~85℃
接线如下:
HC-SR04 | STM32 | 备注 |
---|---|---|
VCC | 3.3/5V | 外接直流电源 |
Trig | 任意一个GPIO口 | 输入端 |
Echo | 任意一个GPIO口 | 输出端 |
GND | GND | 接地 |
3. HC-SR04工作原理
3.1 原理简述
超声波测距的工作原理其实很简单,传感器发送超声波,超声波碰到障碍物反弹回来,被传感器接收到。芯片算出发送和接收的时间间隔,再利用公式:s = v × t,看下面示意图,所以实际距离 = 测量距离 / 2 = 速度 × 时间 / 2。
顺便一提,超声波在空气中的传播速度大概是 343m/s,传播速度受到环境条件的影响,如温度、湿度和气压等。
超声波模块上的两个超声波探头,一个是发送端,负责发送超声波;一个是接收端,负责接收超声波。
3.2 原理详述
接下来我们详细的介绍下超声波模块的工作时序,明白了时序以后才知道怎么写代码。
- 正常测距时序图:
- 单片机给超声波模块发送大于 10us 的高电平的触发信号;
- 超声波模块收到触发信号后 Trig 端发送 8个40kHz 的超声波脉冲;
- Echo 端由低电平转为高电平,同时开始发送超声波;
- 超声波模块检测到返回信号,Echo 端由高电平转为低电平;
- Echo 端高电平宽度即为超声波传播时间。
如果觉得太生涩了,我给大家准备了趣味描述:
- 超出测距范围时序图:
当测量距离超过 HC-SR04 的测量范围时,Echo 任会输出高电平,宽度约为66ms,后转为低电平。
4. 编程实战
4.1 硬件接线
本教程使用的硬件如下:
-
单片机:STM32F103C8T6
-
超声波传感器:HC-SR04
-
串口:USB 转 TTL
-
烧录器:ST-LINK V2
HC-SR04 | STM32 | USB 转 TTL |
---|---|---|
VCC | 3.3/5V | |
Trig | B6 | |
Echo | B7 | |
GND | G | |
A10 | TX | |
A9 | RX | |
G | GND |
烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章【STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html】。
ST-Link V2 | STM32 |
---|---|
SWCLK | SWCLK |
SWDIO | SWDIO |
GND | GND |
3.3V | 3V3 |
接好如下图:
4.2 初始化引脚
我们将 Trig 引脚设置为推挽式输出,Echo 引脚设置为浮空输入。为什么这样设置呢?大家可以对照下表的 GPIO 的八种工作模式看看。
模式名称 | 性质 | 特征 |
---|---|---|
浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空,则电平不确定 |
上拉输入 | 数字输入 | 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 |
下拉输入 | 数字输入 | 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 |
模拟输入 | 模拟输入 | GPIO 无效,引脚直接接入内部 ADC |
开漏输出 | 数字输入 | 可输出引脚电平,高电平为高阻态,低电平接 VSS |
推挽输出 | 数字输入 | 可输出引脚电平,高电平接 VDD,低电平接 VSS |
复用开漏输出 | 数字输入 | 由片上外设控制,高电平为高阻态,低电平接VSS |
复用推挽输出 | 数字输入 | 由片上外设控制,高电平接VDD,低电平接VSS |
引脚初始化代码如下:
void HCSR04_GPIO_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
Trig_GPIO_CLK_ENABLE(); /* Trig引脚使能 */
Echo_GPIO_CLK_ENABLE(); /* Echo引脚使能 */
/* Trig低电平 */
HAL_GPIO_WritePin(Trig_GPIO_PORT, Trig_GPIO_PIN, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = Trig_GPIO_PIN; /* Trig引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
GPIO_InitStruct.Pull = GPIO_NOPULL; /* 浮空 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速 */
HAL_GPIO_Init(Trig_GPIO_PORT, &GPIO_InitStruct); /* 初始化Trig引脚 */
GPIO_InitStruct.Pin = Echo_GPIO_PIN; /* Echo引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入 */
GPIO_InitStruct.Pull = GPIO_NOPULL; /* 浮空 */
HAL_GPIO_Init(Echo_GPIO_PORT, &GPIO_InitStruct); /* 初始化Echo引脚 */
}
4.3 初始化定时器
需要初始化一个定时器,用于测量 Echo 高电平宽度,这里我们初始化了通用定时器2。
void TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0}; /* 定时器设置结构体 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2; /* 通用定时器2 */
htim2.Init.Prescaler = 71; /* 预分频系数 */
htim2.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
htim2.Init.Period = 65535; /* 自动装载值 */
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim2);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
}
4.4 测量距离
按照超声波的工作时序,
- 单片机给超声波模块发送大于 10us 的高电平的触发信号;
- Trig 的 8个40kHz 的超声波脉冲,不用管,我们不需要;
- Echo 端由低电平转为高电平,开启定时器;
- 超声波模块检测到返回信号,Echo 端由高电平转为低电平,关闭定时器;
- 得到超声波来回的总距离,进行计算,得到实际测量距离。
void HCSR04_Get_Length (void)
{
int total_time=0; //超声波来回的总时间
float distance=0; //实际测量距离
HCSR04_GPIO_init();
HAL_GPIO_WritePin(Trig_GPIO_PORT,Trig_GPIO_PIN,GPIO_PIN_SET); //拉高
__HAL_TIM_SetCounter(&htim2, 0); //定时器归零
delay_us(15);
HAL_GPIO_WritePin(Trig_GPIO_PORT,Trig_GPIO_PIN,GPIO_PIN_RESET); //拉低
while(HAL_GPIO_ReadPin(Echo_GPIO_PORT,Echo_GPIO_PIN)==GPIO_PIN_RESET); //Echo转到高电平
HAL_TIM_Base_Start(&htim2); //启动定时器
while(HAL_GPIO_ReadPin(Echo_GPIO_PORT,Echo_GPIO_PIN)==GPIO_PIN_SET); //Echo转回低电平
HAL_TIM_Base_Stop(&htim2); //停止定时器
total_time = __HAL_TIM_GetCounter(&htim2); //得到高电平持续时间
distance = total_time * 0.01715; // //算出测量距离(343*0.000001*100/2 = 0.01715)
printf("dis : %.2f cm\r\n",distance);
}
有的同学可能会好奇,这个“ * 0.01715 ”是什么,因为
实际距离 = 测量距离 / 2
= 速度 × 总时间 / 2。
= 343(m/s) * total_time(us)/ 2
= 343(m/s) * total_time(us) * 0.000001(1s=1000000) * 100(1m=100cm)/2
= 343 * 0.000001 * 100 / 2
= 0.01715
所以我们就直接写 0.01715 啦,减轻一点计算负担,虽然本身也没多少。
.h文件内容如下:
#ifndef __HCSR04_H__
#define __HCSR04_H__
#include "stdio.h"
#include "stm32f1xx.h"
/* 引脚定义 */
#define Trig_GPIO_PORT GPIOB
#define Trig_GPIO_PIN GPIO_PIN_6
#define Trig_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define Echo_GPIO_PORT GPIOB
#define Echo_GPIO_PIN GPIO_PIN_7
#define Echo_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
void HCSR04_GPIO_init(void);
void TIM2_Init(void);
void HCSR04_Get_Length (void);
#endif
4.5 最终效果
串口输出如下,这是我用本子在超声波前来回移动的数据。记得给板子上电哦,光 STLink 供电可不够。
5. 小结
通过本文的学习与实践,相信大家已经了解并掌握 HC-SR04 的特性和使用,能够更好地应用于嵌入式开发。希望 HC-SR04 可以成为您的得力助手,让我们一起玩转 HC-SR04,peace and love!
另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。
有收获?希望老铁们来个三连击,给更多的人看到这篇文章
推荐阅读:
欢迎关注我的博客:良许嵌入式教程网,满满都是干货!