手把手教你玩转超声波传感器(原理+驱动)

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

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

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

1. 源码下载及前置阅读

本文首发 良许嵌入式网 :www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

www.lxlinux.net/e/stm32/hc-...

如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。

往期教程,有兴趣的小伙伴可以看看。

作者简介
大家好,我是良许,博客里所有的文章皆为我的原创。 下面是我的一些个人介绍,欢迎交个朋友: · 211工科硕士,国家奖学金获得者; · 深耕嵌入式11年,前世界500强外企高级嵌入式工程师; · 书籍《速学Linux作者》,机械工业出版社专家委员会成员; · 全网60W粉丝,博客分享大量原创成体系文章,全网阅读量累计超4000万; · 靠自媒体连续年入百万,靠自己买房买车。

我本科及硕士都是学机械,通过自学成功进入世界500强外企。我已经将自己的学习经验写成了一本电子书,超千人通过此书学习并转行成功。现在将这本电子书免费分享给大家,希望对你们有帮助:

电子书链接: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-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 原理详述

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

正常测距时序图:

  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-SR04 STM32 USB 转 TTL
VCC 3.3/5V
Trig B6
Echo B7
GND G
A10 TX
A9 RX
G GND

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

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

引脚初始化代码如下:

c 复制代码
 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。

c 复制代码
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. 得到超声波来回的总距离,进行计算,得到实际测量距离。
c 复制代码
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文件内容如下:

c 复制代码
#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 随你挑!

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

推荐阅读:

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

相关推荐
银河麒麟操作系统20 分钟前
【银河麒麟高级服务器操作系统】有关dd及cp测试差异的现象分析详解
java·linux·运维·服务器·前端·网络
杭州_燕十三1 小时前
milkv-duo256m ubuntu 系统构建与运行
linux·ubuntu·sg2002
滴水之功1 小时前
VMware ubuntu12.04怎么设置静态IP联网
linux·ubuntu
昨天今天明天好多天2 小时前
【Go】Linux、Windows、Mac 搭建Go开发环境
linux·windows·golang
难以触及的高度3 小时前
Linux中所有和$有关的操作
linux·服务器·前端
Chicheng_MA4 小时前
Linux DNS 协议概述
linux·dns
2018_like菜4 小时前
esxi8 虚拟机使用ubuntu22模板后 没有ip配置文件,只有ipv6链接正常使用
linux·服务器·网络
fnd_LN4 小时前
Linux shell的七大功能 ---自动补齐、管道机制、别名
linux·运维·服务器
菜菜小蒙5 小时前
【Linux】信号
linux·运维·服务器
ziqibit5 小时前
linux 测试硬盘性能
linux·运维·服务器