我们都知道声音是由物体振动产生的,人能听到的频率在20Hz~20kHz。频率小于20Hz的叫次声波,频率大于20kHz的叫超声波。
超声波可以在空气、液体和固体中传播,可以被物体反射、折射、散射等,并且有频率高、波长短、绕射现象小、方向性好等特点。从而提供了丰富的用途,医学影像、清洁物品、材料检测、非接触测距等等。
本次我们要讲的超声波传感器就是非接触测距,用于测量物体与传感器之间的距离,可用于车辆安全(倒车雷达),安防系统(检测到移动物体并触发警报),工业自动化(物体定位、检测和避障)等等。
本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!
本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):
https://www.lxlinux.net/e/stm32/hc-sr04-tutorial.html
如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。
往期教程,有兴趣的小伙伴可以看看。
作者简介 |
---|
大家好,我是良许,博客里所有的文章皆为我的原创。 下面是我的一些个人介绍,欢迎交个朋友: · 211工科硕士,国家奖学金获得者; · 深耕嵌入式11年,前世界500强外企高级嵌入式工程师; · 书籍《速学Linux作者》,机械工业出版社专家委员会成员; · 全网60W粉丝,博客分享大量原创成体系文章,全网阅读量累计超4000万; · 靠自媒体连续年入百万,靠自己买房买车。 |
我本科及硕士都是学机械,通过自学成功进入世界500强外企。我已经将自己的学习经验写成了一本电子书,超千人通过此书学习并转行成功。现在将这本电子书免费分享给大家,希望对你们有帮助:
电子书链接:https://www.lxlinux.net/1024.html
超声波传感器有很多的信号:HC-SR04、UC-025、UC-026、UC-015、US-100等等,它们之间大同小异,无非是工作参数有点不一样,像是工作的电压或温度、探测距离或精度有点差别,引脚是一样的,都是4个引脚(US-100 多一个 GND 引脚),引脚顺序和功能也是一样的。
大家在学习和工作中可以自行选择合适的型号,这里我为大家介绍最常见的 HC-SR04 这个型号。
现在市面上的 HC- SR04 有新版和旧版,我们介绍的是新版。新版性能比老版的精度更高,测距范围更远,可达6米,高于一般超声波测距模块。采用 CS-100A 超声波测距 SOC 芯片,高性能,工业级,宽电压,价格在4块钱左右。
HC-SR04 工作参数:
接线如下:
HC-SR04 | STM32 | 备注 |
---|---|---|
VCC | 3.3/5V | 外接直流电源 |
Trig | 任意一个GPIO口 | 输入端 |
Echo | 任意一个GPIO口 | 输出端 |
GND | GND | 接地 |
超声波测距的工作原理其实很简单,传感器发送超声波,超声波碰到障碍物反弹回来,被传感器接收到。芯片算出发送和接收的时间间隔,再利用公式:s = v × t,看下面示意图,所以实际距离 = 测量距离 / 2 = 速度 × 时间 / 2。
顺便一提,超声波在空气中的传播速度大概是 343m/s,传播速度受到环境条件的影响,如温度、湿度和气压等。
超声波模块上的两个超声波探头,一个是发送端,负责发送超声波;一个是接收端,负责接收超声波。
接下来我们详细的介绍下超声波模块的工作时序,明白了时序以后才知道怎么写代码。
正常测距时序图:
如果觉得太生涩了,我给大家准备了趣味描述:
超出测距范围时序图:
当测量距离超过 HC-SR04 的测量范围时,Echo 任会输出高电平,宽度约为66ms,后转为低电平。
本教程使用的硬件如下:
单片机: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 |
接好如下图:
我们将 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引脚 */ }
需要初始化一个定时器,用于测量 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); }
按照超声波的工作时序,
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
串口输出如下,这是我用本子在超声波前来回移动的数据。记得给板子上电哦,光 STLink 供电可不够。
另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。
刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!
推荐阅读:
欢迎关注我的博客:良许嵌入式教程网,满满都是干货!