我们都知道声音是由物体振动产生的,人能听到的频率在20Hz~20kHz。频率小于20Hz的叫次声波,频率大于20kHz的叫超声波。

超声波可以在空气、液体和固体中传播,可以被物体反射、折射、散射等,并且有频率高、波长短、绕射现象小、方向性好等特点。从而提供了丰富的用途,医学影像、清洁物品、材料检测、非接触测距等等。

本次我们要讲的超声波传感器就是非接触测距,用于测量物体与传感器之间的距离,可用于车辆安全(倒车雷达),安防系统(检测到移动物体并触发警报),工业自动化(物体定位、检测和避障)等等。

1. 源码下载及前置阅读

本文首发 良许嵌入式网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

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-SR04STM32备注
VCC3.3/5V外接直流电源
Trig任意一个GPIO口输入端
Echo任意一个GPIO口输出端
GNDGND接地

3. HC-SR04工作原理

3.1 原理简述

超声波测距的工作原理其实很简单,传感器发送超声波,超声波碰到障碍物反弹回来,被传感器接收到。芯片算出发送和接收的时间间隔,再利用公式:s = v × t,看下面示意图,所以实际距离 = 测量距离 / 2 = 速度 × 时间 / 2。

顺便一提,超声波在空气中的传播速度大概是 343m/s,传播速度受到环境条件的影响,如温度、湿度和气压等。

超声波模块上的两个超声波探头,一个是发送端,负责发送超声波;一个是接收端,负责接收超声波。

3.2 原理详述

接下来我们详细的介绍下超声波模块的工作时序,明白了时序以后才知道怎么写代码。

  • 正常测距时序图:

  1. 单片机给超声波模块发送大于 10us 的高电平的触发信号;
  2. 超声波模块收到触发信号后 Trig 端发送 8个40kHz 的超声波脉冲;
  3. Echo 端由低电平转为高电平,同时开始发送超声波;
  4. 超声波模块检测到返回信号,Echo 端由高电平转为低电平;
  5. Echo 端高电平宽度即为超声波传播时间。

如果觉得太生涩了,我给大家准备了趣味描述:

  • 超出测距范围时序图:

当测量距离超过 HC-SR04 的测量范围时,Echo 任会输出高电平,宽度约为66ms,后转为低电平。

4. 编程实战

4.1 硬件接线

本教程使用的硬件如下:

  • 单片机:STM32F103C8T6
  • 超声波传感器:HC-SR04
  • 串口:USB 转 TTL
  • 烧录器:ST-LINK V2
HC-SR04STM32USB 转 TTL
VCC3.3/5V
TrigB6
EchoB7
GNDG
A10TX
A9RX
GGND

烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章【STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to...】。

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下图:

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 测量距离

按照超声波的工作时序,

  1. 单片机给超声波模块发送大于 10us 的高电平的触发信号;
  2. Trig 的 8个40kHz 的超声波脉冲,不用管,我们不需要;
  3. Echo 端由低电平转为高电平,开启定时器;
  4. 超声波模块检测到返回信号,Echo 端由高电平转为低电平,关闭定时器;
  5. 得到超声波来回的总距离,进行计算,得到实际测量距离。
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 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!


良许
1k 声望1.8k 粉丝