STM32驱动HC-SR04超声波测距模块详解

CH_SR04

一、简介

1.产品特点

HC_SR04超声波测距模块可提供2cm-400cm的非接触式测距感测功能,测距精度高达3mm;模块包括超声波发射器,接收器与控制电路。

基本工作原理:

(1)采用IO口TRIG触发测距,需要给最少10us的高电平。

(2)模块自动发送8个40kHz的方波,自动检测是否有信号返回。

(3)有信号返回,通过IO口ECHO输出一个高电平,高电平持续时间就是超声波从发射到返回的时间。

距离计算公式:uS/58=厘米,uS/148=英尺,距离=高电平时间*声速(340m/s)/2。

建议测量周期为60ms以上,以防止发射信号对回波信号的影响。

注:此模块不易带电连接,若要带电连接,则需将模块的GND先连接,否则容易接线错误影响正常使用。测距时,被测物体的面积不少于0.5平方米且平面尽量要求平整,否则影响测量结果。

2.实物图

VCC:供5V电源

GND:为地线

TRIG:触发控制信号输入

MCHO:回响信号输出

实物图

3.电气特性

电气参数

HC-SR04超声波模块

工作电压

DC 5V

工作电流

15mA

工作频率

40kHz

最远射程

4m

最近射程

2cm

测量角度

15°

输入触发信号

10uSTTL脉冲

输出回响信号

输出TTL电平信号

规格尺寸

45*20*15mm

4.时序图

时序图

以上时序图表明只需要提供一个10us以上的脉冲触发信号,该模块内部将发出8个40kHz周期电平并检测回波。一旦检测到有回波信号则输出回响信号,回响信号的脉冲宽度与所测的距离成正比。

视频演示

以上对HC_SR04超声波测距模块的介绍在说明书中都有介绍。以下开始对HC_SR04进行配置。

二、程序设计

1.准备

硬件:

(1)单片机最小系统(笔者用的是 STM32F103ZET6 开发板)

(2)HC-SR超声波测距模块

(3)0.96寸OLED屏

(4)杜邦线

主芯片相关外设:

(1)RCC时钟

(2)GPIO

(3)TIM定时器

(4)EXTI外部中断

(5)Systick系统滴答定时器

开发环境: KEIL_5

2.硬件连接

HC-SR04超声波模块

TRIG

PD_0

MCHO

PD_1

3.驱动编写

编程思路:

使能GPIO,AFIO,TIM,EXTI。

①TRIG为主机发送触发信号端,可将对应io配置为输出模式,并拉低电平。触发信号:io口输出高电平持续10us以上再输出低电平。

②MCHO为从机输出回响信号端,由主机接收信号,可将对应io配置为输入模式。配置外部中断,检测io电平变化。

③使用TIM6,在MCHO上升沿时启动定时器,记录MCHO高电平持续时间;在下降沿时关闭定时器,并清空计数器,保存高电平持续时间。

④使用TIM7,定时发送触发信号。

⑤配置中断,设置中断优先级,编写中断服务函数。

⑥main函数负责向oled输出实测距离。

(1)GPIO引脚和外部中断配置

void HC_SR04_PinInit(void)
{
    //1.配置GPIO模式
    GPIO_InitTypeDef hc_sr04_gpioInit;
    hc_sr04_gpioInit.GPIO_Pin = GPIO_Pin_1;
    hc_sr04_gpioInit.GPIO_Mode = GPIO_Mode_IPD;        //下拉输入模式
    hc_sr04_gpioInit.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD,&hc_sr04_gpioInit);
    
    hc_sr04_gpioInit.GPIO_Pin = GPIO_Pin_0;
    hc_sr04_gpioInit.GPIO_Mode = GPIO_Mode_Out_PP;    //通用推挽输出
    GPIO_Init(GPIOD,&hc_sr04_gpioInit);
    HC_SR04_TRIG_0;//拉低电平
    
    //2.配置外部中断
    EXTI_InitTypeDef hc_sr04_extiInit;
    hc_sr04_extiInit.EXTI_Line = EXTI_Line1;
    hc_sr04_extiInit.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
    hc_sr04_extiInit.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//双边沿对齐
    hc_sr04_extiInit.EXTI_LineCmd = ENABLE;//使能
    EXTI_Init(&hc_sr04_extiInit);
    
    //3.选择外部中断线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource1);//选择PD1作为外部中断线
    //EXTI_GenerateSWInterrupt(EXTI_Line1);//产生一个软件中断
    EXTI_ClearFlag(EXTI_Line1);//清除挂起标志位
}

(2)TIM配置

/*
    \brief:    基本TIM初始化
    \param:    TIMx: where x can be 1 to 17 to select the TIM peripheral.
                psc:预分频系数
                arr:重装载值
    \retval:    none
*/
static void TIMx_Init(TIM_TypeDef* TIMx,uint16_t psc,uint16_t arr)
{
    //1.复位TIM
    TIM_DeInit(TIMx);
    //2.配置TIM
    TIM_TimeBaseInitTypeDef TIMx_timeBaseInit;
    TIMx_timeBaseInit.TIM_Prescaler = psc-1;                //设置预分频系数为psc
    TIMx_timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;    //向上计数
    TIMx_timeBaseInit.TIM_RepetitionCounter = 0;            //重复计数值
    TIMx_timeBaseInit.TIM_Period = arr;                        //重装载值
    TIMx_timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIMx,&TIMx_timeBaseInit);
    //3.中断配置
    TIM_ITConfig(TIMx,TIM_IT_Update,ENABLE);                //使能更新中断
    TIM_ClearFlag(TIMx,TIM_FLAG_Update);                    //清除挂起标志
    //4.失能定时器
    TIM_Cmd(TIMx,DISABLE);
}

(2)配置中断优先级

/*
    \brief:    配置中断优先级
    \param:    NVIC_IRQChannel:中断号
                PreemptionPriority:抢占优先级
                SubPriority:子优先级
    \retval:    none
*/
static void nvic_InitConfig(uint8_t IRQChannel,uint8_t PreemptionPriority,uint8_t SubPriority)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);//配置优先级分组
    NVIC_InitTypeDef ch_sr04_NVIC_init;
    ch_sr04_NVIC_init.NVIC_IRQChannel = IRQChannel;
    ch_sr04_NVIC_init.NVIC_IRQChannelPreemptionPriority = PreemptionPriority;
    ch_sr04_NVIC_init.NVIC_IRQChannelSubPriority = SubPriority;
    ch_sr04_NVIC_init.NVIC_IRQChannelCmd = ENABLE;    //使能中断
    NVIC_Init(&ch_sr04_NVIC_init);
}

(3)初始化HC_SR04

使用外部中断需要开启AFIO时钟。

触发信号发送周期建议大于60ms,此处为200ms定时发送触发信号。

/*
    \brief:    CH_SR04超声测距模块初始化配置
    \retval:    none
*/
void HC_SR04_Init(void)
{
    //1.打开外设时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);//使能TIM6
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7,ENABLE);//使能TIM7
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);//使能GPIOD
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能AFIO
    //2.GPIO初始化
    HC_SR04_PinInit();
    //3.TIM初始化
    TIMx_Init(TIM6,72,0xFFFF);    //72分频 1us 用于记录MCHO高电平时间
    TIMx_Init(TIM7,720,20000);    //200ms发一次脉冲
    TIM_Cmd(TIM7,ENABLE);
    //4.配置中断优先级
    nvic_InitConfig(EXTI1_IRQn,1,0);
    nvic_InitConfig(TIM6_IRQn,2,0);
    nvic_InitConfig(TIM7_IRQn,3,0);
}
//触发信号
void HC_SR04_TriggerSignal(void)
{
    HC_SR04_TRIG_1;
    Delay_us(20);    //延时10us以上
    HC_SR04_TRIG_0;
}

(4)中断服务函数

为了使用us计数,将TIM6进行72分频,最大计数值为65535us,而MCHO高电平最大持续时间为400*58=23200us,所以将TIM6的重装载寄存器的值设为0xFFFF,完全够用于记录MCHO的高电平持续时间。如果TIM一次计数到溢出所用的时间小于23200us,可定义一个变量记录一次回响信号TIM6进中断的次数,再进行时间计算。

本文中的tim6_IT_count就显得有些多余。

uint16_t tim6_IT_count=0;
//ECHO 中断线服务函数
void EXTI1_IRQHandler(void)
{
    if(RESET != EXTI_GetITStatus(EXTI_Line1))
    {
        EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志位
        if(HC_SR04_ECHO())                    //上升沿
        {
            TIM_Cmd(TIM6,ENABLE);            //启动定时器
        }    
        else                                //下降沿
        {
            TIM_Cmd(TIM6,DISABLE);            //关闭定时器
            Dist_cm = (tim6_IT_count*0xFFFF+TIM_GetCounter(TIM6))/58.0;//计算距离
            TIM_SetCounter(TIM6,0x00);        //清空计数器
            tim6_IT_count=0;                //计数数清零
        }
    }
}
//基本定时器TIM6 辅助MCHO高电平期间计数
void TIM6_IRQHandler(void)
{
    if(RESET != TIM_GetITStatus(TIM6,TIM_IT_Update))
    {
        TIM_ClearITPendingBit(TIM6,TIM_IT_Update);//清除标志位
        tim6_IT_count++;
    }
}
//基本定时器TIM7 定时发送CH_SR04载波发送
void TIM7_IRQHandler(void)
{
    if(RESET != TIM_GetITStatus(TIM7,TIM_IT_Update))
    {
        TIM_ClearITPendingBit(TIM7,TIM_IT_Update);//清除标志位
        HC_SR04_TriggerSignal();//触发信号
    }
}

三、实验结果

测量值不会很准确,当测量距离小于2cm时,或者测量斜面时,数值会跳动很大。如果需要增加精准度,可通过排序取中间值或其他方式减小误差。

四、代码

hc_sr04.c


#include "hc_sr04.h"

/*
    配置GPIO
    \pin:        ECHO  -  PD1  触发控制信号
                TRIG  -  PD0  回响信号
*/
#define HC_SR04_ECHO() GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_1)//读取输入电平
#define HC_SR04_TRIG_0 GPIO_ResetBits(GPIOD,GPIO_Pin_0) //写0
#define HC_SR04_TRIG_1 GPIO_SetBits(GPIOD,GPIO_Pin_0)    //写1

float Dist_cm;//测量距离

void HC_SR04_PinInit(void)
{
    //1.配置GPIO模式
    GPIO_InitTypeDef hc_sr04_gpioInit;
    hc_sr04_gpioInit.GPIO_Pin = GPIO_Pin_1;
    hc_sr04_gpioInit.GPIO_Mode = GPIO_Mode_IPD;        //下拉输入模式
    hc_sr04_gpioInit.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD,&hc_sr04_gpioInit);
    
    hc_sr04_gpioInit.GPIO_Pin = GPIO_Pin_0;
    hc_sr04_gpioInit.GPIO_Mode = GPIO_Mode_Out_PP;    //通用推挽输出
    GPIO_Init(GPIOD,&hc_sr04_gpioInit);
    HC_SR04_TRIG_0;//拉低电平
    
    //2.配置外部中断
    EXTI_InitTypeDef hc_sr04_extiInit;
    hc_sr04_extiInit.EXTI_Line = EXTI_Line1;
    hc_sr04_extiInit.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
    hc_sr04_extiInit.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//双边沿对齐
    hc_sr04_extiInit.EXTI_LineCmd = ENABLE;//使能
    EXTI_Init(&hc_sr04_extiInit);
    
    //3.选择外部中断线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource1);//选择PD1作为外部中断线
    //EXTI_GenerateSWInterrupt(EXTI_Line1);//产生一个软件中断
    EXTI_ClearFlag(EXTI_Line1);//清除挂起标志位
}
/*
    \brief:    基本TIM初始化
    \param:    TIMx: where x can be 1 to 17 to select the TIM peripheral.
                psc:预分频系数
                arr:重装载值
    \retval:    none
*/
static void TIMx_Init(TIM_TypeDef* TIMx,uint16_t psc,uint16_t arr)
{
    //1.复位TIM
    TIM_DeInit(TIMx);
    //2.配置TIM
    TIM_TimeBaseInitTypeDef TIMx_timeBaseInit;
    TIMx_timeBaseInit.TIM_Prescaler = psc-1;                //设置预分频系数为psc
    TIMx_timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;    //向上计数
    TIMx_timeBaseInit.TIM_RepetitionCounter = 0;            //重复计数值
    TIMx_timeBaseInit.TIM_Period = arr;                        //重装载值
    TIMx_timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIMx,&TIMx_timeBaseInit);
    //3.中断配置
    TIM_ITConfig(TIMx,TIM_IT_Update,ENABLE);                //使能更新中断
    TIM_ClearFlag(TIMx,TIM_FLAG_Update);                    //清除挂起标志
    //4.失能定时器
    TIM_Cmd(TIMx,DISABLE);
}
/*
    \brief:    配置中断优先级
    \param:    NVIC_IRQChannel:中断号
                PreemptionPriority:抢占优先级
                SubPriority:子优先级
    \retval:    none
*/
static void nvic_InitConfig(uint8_t IRQChannel,uint8_t PreemptionPriority,uint8_t SubPriority)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);//配置优先级分组
    NVIC_InitTypeDef ch_sr04_NVIC_init;
    ch_sr04_NVIC_init.NVIC_IRQChannel = IRQChannel;
    ch_sr04_NVIC_init.NVIC_IRQChannelPreemptionPriority = PreemptionPriority;
    ch_sr04_NVIC_init.NVIC_IRQChannelSubPriority = SubPriority;
    ch_sr04_NVIC_init.NVIC_IRQChannelCmd = ENABLE;    //使能中断
    NVIC_Init(&ch_sr04_NVIC_init);
}
/*
    \brief:    CH_SR04超声测距模块初始化配置
    \retval:    none
*/
void HC_SR04_Init(void)
{
    //1.打开外设时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);//使能TIM6
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7,ENABLE);//使能TIM7
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);//使能GPIOD
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能AFIO
    //2.GPIO初始化
    HC_SR04_PinInit();
    //3.TIM初始化
    TIMx_Init(TIM6,72,0xFFFF);    //72分频 1us 用于记录MCHO高电平时间
    TIMx_Init(TIM7,720,20000);    //200ms发一次脉冲
    TIM_Cmd(TIM7,ENABLE);
    //4.配置中断优先级
    nvic_InitConfig(EXTI1_IRQn,1,0);
    nvic_InitConfig(TIM6_IRQn,2,0);
    nvic_InitConfig(TIM7_IRQn,3,0);
}
//触发信号
void HC_SR04_TriggerSignal(void)
{
    HC_SR04_TRIG_1;
    Delay_us(20);    //延时10us以上
    HC_SR04_TRIG_0;
}
uint16_t tim6_IT_count=0;
//ECHO 中断线服务函数
void EXTI1_IRQHandler(void)
{
    if(RESET != EXTI_GetITStatus(EXTI_Line1))
    {
        EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志位
        if(HC_SR04_ECHO())                    //上升沿
        {
            TIM_Cmd(TIM6,ENABLE);            //启动定时器
        }    
        else                                //下降沿
        {
            TIM_Cmd(TIM6,DISABLE);            //关闭定时器
            Dist_cm = (tim6_IT_count*0xFFFF+TIM_GetCounter(TIM6))/58.0;//计算距离
            TIM_SetCounter(TIM6,0x00);        //清空计数器
            tim6_IT_count=0;                //计数数清零
        }
    }
}
//基本定时器TIM6 辅助MCHO高电平期间计数
void TIM6_IRQHandler(void)
{
    if(RESET != TIM_GetITStatus(TIM6,TIM_IT_Update))
    {
        TIM_ClearITPendingBit(TIM6,TIM_IT_Update);//清除标志位
        tim6_IT_count++;
    }
}
//基本定时器TIM7 定时发送CH_SR04载波发送
void TIM7_IRQHandler(void)
{
    if(RESET != TIM_GetITStatus(TIM7,TIM_IT_Update))
    {
        TIM_ClearITPendingBit(TIM7,TIM_IT_Update);//清除标志位
        HC_SR04_TriggerSignal();//触发信号
    }
}

hc_sr04.h

#ifndef _HC_SR04_H_
#define _HC_SR04_H_

#include "stm32f10x.h"
#include "systick.h"

extern float Dist_cm;//测量距离 cm

void HC_SR04_Init(void);

#endif

main.c


#include "stm32f10x.h"
#include "hc_sr04.h"
#include "systick.h"
#include "oled.h"
#include <stdio.h>
#include <string.h>
/*
    超声测距实验
*/

int main(void)
{
    /*  相关外设初始化  */
    HC_SR04_Init();        //hc_sr04初始化
    systick_config();    //系统滴答
    OLED_Init();        //oled初始化
    
    uint8_t buff[10];
    while(1)
    {
        snprintf((char *)buff,10,"%0.1f%s",Dist_cm,"cm");    //拼接字符串
        OLED_Display_String(20,16,12,24,buff);                //oled显示字符串
        OLED_Refresh();        //刷新函数
        OLED_GRAM_Init();    //初始化oled缓存
        memset(buff,0,10);    //清零buff
    }
}

其他代码:略。

源码下载:

链接:https://pan.baidu.com/s/1mfZma1C0xWdlEr6bNz4KCg?pwd=1234

提取码:1234

文章如有错误,还望在评论区指正!

2023/1/13

物联沃分享整理
物联沃-IOTWORD物联网 » STM32驱动HC-SR04超声波测距模块详解

发表评论