STM32_超声波测距(HC_SR04)超详细讲解!!!

一、前言

        在本节学习中,先带大家认识超声波的测距功能以及应用,再介绍一下配置时需要用到的STM32的输入捕获模式,最后配置代码实现超声波进行测距,完成一个超声波测距项目。在这个项目开始之前,需要掌握定时器如何配置的知识点,不会的同学可以看上一篇文章。

二、超声波简介

什么是超声波?

        超声波是一种频率高于人耳可听范围(大约20 kHz以上)的声波。我们本章介绍是HC_SR04,它是一种常见的超声波传感器模块,用来测量障碍物与传感器的距离。

什么情况下使用超声波?

在人们日常生活中,测量距离(汽车的倒车雷达)、检查物体的内部结构(像超声波检查胎儿那样),甚至清洁精密的物品。

HC_SR04测距实现

接下来我们将详细介绍如何实现超声波测距。

超声波测距原理

        超声波测距的基本原理是首先发出一定频率的超声波,超声波遇到障碍物后反射回来,而当模块接收到反射回来的超声波后,只要将超声波从发送到接收的时间差乘以声速除以2就可以测到与障碍物的距离了。

公式:

距离=时间差×声速​÷2

        其中,声速在空气中的速度约为340米/秒(取决于温度和湿度),时间差是超声波从发射到接收的总时间,除以2是因为超声波往返经过了该距离两次。

详细给大家讲一下这个时间差是如何得到的?(配合代码)

(1)HC_SR04上的Trig引脚发送一个高脉冲信号(如上图代码所示)时,Echo瞬间拉高电平

(2)此时在传输高脉冲信号的过程中,Trig还是低电平,Echo保持高电平

(3)当传感器接收到从物体反射回来的超声波时,Echo引脚会被拉低为低电平,Echo上高电平的持续时间就是超声波在往返路途中消耗的时间。

TRIG_Send(1);
us_delay(20);
TRIG_Send(0);

上面这三行代码表示的就是Trig发送一个高脉冲信号。

        !!!需要注意的事:由于声波扩散传播的特性,如果超声波扩散范围内有比被测物体更近的障碍物,超声波就会被提前反射,测得的就是模块到障碍物的距离了。因而在在使用超声波测距时,最好只有一个被测物障碍物,这样才能提高数据的正确性。

HC_SR04硬件配置

  1. 超声波传感器(HC-SR04)

    • Trig引脚:用于发送超声波信号的触发引脚。
    • Echo引脚:用于接收反射回来的超声波信号的引脚。
    • GND引脚:接地线
    • VCC引脚:供给电压5v
  2. STM32微控制器

    • 选择任意两组GPIO引脚,一组作为Trig输出引脚,另一组作为Echo输入引脚。
    • 使用定时器(比如TIM4)来计算时间间隔。

三、输入捕获模式

为什么要引用输入捕获模式?

     根据上面我们需要计算的时间Echo的高电平持续时间构成的,所以我们需要去测量Echo引脚上高电平的持续时间,听着很简单,不就是去检测Echo口引脚上的电平变化吗?只要当读取到Echo引脚上是高电平时,就让定时器开始计数;当读取到Echo引脚上是低电平时,就读取定时器的数值,就能得到我们想要的时间差,但是这样也有一个很大的弊端:程序软件循环读取GPIO硬件是否检测到高/低电平,再读取此时定时器的数值,需要耗费时间,使得数据不够正确严谨。因此我们需要使用一个得到时间更加精确的方式--使用输入捕获模式。

        用一句话介绍输入捕获模式:当定时器输入通道上检测到上升沿(或者下降沿时),立刻将此时计数器的数值记录到捕获寄存器中,以待程序读取。

       对于通用定时器和高级定时器来说,每个输入通道都有它自己的捕获寄存器。输入捕获可以在输入信号上出现上升沿或者下降沿时,将此时计数器的值捕获到捕获寄存器中,并且要借用另一个输入通道的捕获寄存器进行输入捕获,捕获到自己的捕获寄存器中叫做直接模式,借用另一个通道捕获寄存器叫做间接模式。

高级定时器:STM32 的 TIM1TIM8

通用定时器:STM32 的 TIM2TIM3TIM4TIM5

图片来源:哔哩哔哩上keysking博主

再给大家详细的介绍一下输入捕获模式的整体运行方式:

如图,我们使用输入通道TI1的输入捕获模式,边沿检测器设为上升沿,定时器启动计数后,若输入到输入通道TI1的信号出现一个上升沿,进入输入滤波器(去除输入信号中的噪声、干扰),边沿检测器立即检测到上升沿,随后一个触发信号通过TI1FP1,进入预分频器进行处理信号频率,传递到捕获寄存器1,最后捕获寄存器检测到直接将计数器的值赋值到自身,并且将计数器设置为0,这样我们就得到了上升沿出现时定时器的时刻,但是一个边沿检测器只能设置为上升沿/下降沿,无法设为双边沿。我们就需要在边沿检测器上再引用一条线TI1FP2,借用捕获寄存器2,并且设置为下降沿捕获,获取到下降沿出现时定时器的时刻,二者相减,我们就能获取到高电平的持续时间啦。

四、代码配置

        我们这里要用到main.c、main.h、HC_SR04.c、HC_SR04.h、SysTick.h、SysTick.c。SysTick.h、SysTick.c这两个文件的代码配置我不给大家介绍,但会附上相关代码。大家只要知道这个只是让我们能使用延时函数就行了。

HC_SR04.c文件

#include "HC_SR04.h"
#include "stm32f10x.h"
#include "SysTick.h"//用于延时的头文件,提供了一些所需要的延时函数
extern uint16_t mscount = 0;
void HC_SR04Config(void)
{
    GPIO_InitTypeDef GPIO_hcsr04init;
	TIM_TimeBaseInitTypeDef TIM_hcsr04init;
    NVIC_InitTypeDef NVIC_hcsr04init;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	
	//Trig PB11
    GPIO_hcsr04init.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_hcsr04init.GPIO_Pin = GPIO_Pin_11;//配置GPIOB的第11引脚为推挽输出模式,连接HC-SR04 
    //Trig引脚,用来触发超声波信号。
	GPIO_hcsr04init.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,  &GPIO_hcsr04init);
	
	//ECHO PB10 
    GPIO_hcsr04init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_hcsr04init.GPIO_Pin = GPIO_Pin_10;//配置GPIOB的第10引脚为浮空输入模式,连接HC-SR04
    //Echo引脚,用来接收超声波返回信号。
	GPIO_Init(GPIOB,  &GPIO_hcsr04init);
	
    //配置定时器TIM4
	TIM_hcsr04init.TIM_ClockDivision = TIM_CKD_DIV1 ;
	TIM_hcsr04init.TIM_CounterMode   = TIM_CounterMode_Up;
	TIM_hcsr04init.TIM_Period        = 1000-1;//定时器每计数1次表示1微秒。
	TIM_hcsr04init.TIM_Prescaler     = 72-1;//预分频器为71(72MHz / 72 = 1MHz)

    TIM_TimeBaseInit(TIM4,&TIM_hcsr04init);
	//配置定时器溢出中断,并禁用定时器(等到需要时启用)。
    TIM_ITConfig(TIM4,TIM_IT_Update, ENABLE);
	TIM_Cmd(TIM4,DISABLE);
	
    //配置定时器溢出中断的NVIC优先级和使能TIM4的中断。
	NVIC_hcsr04init.NVIC_IRQChannel                   = TIM4_IRQn  ;
	NVIC_hcsr04init.NVIC_IRQChannelPreemptionPriority = 0 ;
	NVIC_hcsr04init.NVIC_IRQChannelSubPriority        = 0;
	NVIC_hcsr04init.NVIC_IRQChannelCmd                = ENABLE;
	NVIC_Init(&NVIC_hcsr04init);
}

//Open_tim4()函数启用TIM4定时器,并将计数器清零。
void Open_tim4(void)
{
  TIM_SetCounter(TIM4,0);
  mscount = 0;//mscount用于记录中断的次数。
  TIM_Cmd(TIM4,ENABLE);

}
//Close_tim4()函数关闭TIM4定时器,停止计时。
void Close_tim4(void)
{
  TIM_Cmd(TIM4,DISABLE);
}
//这是定时器溢出中断处理函数,每次定时器溢出时,mscount的值会增加1。
void TIM4_IRQHandler(void)
{
   if( TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
   {
	  TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
	  mscount++;	 
   }
}
//该函数用于获取Echo信号的返回时间,计算总的时间(以微秒为单位),并将定时器的计数器清零。
int GetEcho_time(void)
{
  uint32_t t = 0;
  t = mscount*1000;
  t += TIM_GetCounter(TIM4);
  TIM4->CNT = 0;
  ms_delay(50);
  return t;
}
//获取超声波的距离
float Getlength(void)
{
	int i = 0;
	uint32_t t = 0;
	float length = 0;
	float sum = 0;
	while(i != 5)
    {
	TRIG_Send(1);
	us_delay(20);
    TRIG_Send(0);
	while(ECHO_Reci == 0);
	Open_tim4();
	i = i+1;
	while(ECHO_Reci == 1);
    Close_tim4();
	t = GetEcho_time();
	length = ((float)t / 58.0);
	sum = sum+length;
    }
	length = sum / 5.0;//取五次距离总和的平均距离
	return length;	
}

基于这个代码很多我给大家详细介绍一下

代码结构:

1. 启用和关闭定时器 (TIM4)
Open_tim4():

启用定时器 TIM4 并将计数器清零。定时器开始计时,用于计算从发射信号到接收信号的时间间隔。
Close_tim4():关闭定时器 TIM4,停止计时。
2. 定时器溢出中断处理 (TIM4_IRQHandler)
当定时器溢出时,mscount 会增加 1。
3. 计算回波时间 (GetEcho_time)
GetEcho_time():这个函数会返回从定时器开始计时到当前时间点的总时间。该时间是从上次定时器重置后计数的时间。此函数将定时器的计数器清零,确保下一次计时是从零开始。
4. 获取超声波的距离 (Getlength)
Getlength():这个函数是发送信号并接收回波信号,通过计算往返时间来得出距离。这里通过循环多次(5次),每次测量后取平均值来减少误差。

难点/疑惑点

extern uint16_t mscount = 0;

mscount 是一个外部变量,在TIM4_IRQHandler中更新,用来计算中断次数。

我们得到的时间实际是用t来表示
int GetEcho_time(void)
{
  uint32_t t=0;
  t=mscount*1000;
  t+=TIM_GetCounter(TIM4);
  TIM4->CNT =0;
  ms_delay(50);
  return t;
}

这段代码的意思实际是总时间t=中断次数*1000+计数器的时间

中断次数n*1000实际为n/毫秒

计数器的时间为不足为1的毫秒

 这是定时器溢出中断处理函数,每次定时器溢出时,mscount的值会增加1。

void TIM4_IRQHandler(void)
{
   if( TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
     {
      TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
      mscount++;     
     }
}

 if( TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)这个代码意思是定时器溢出了(不为0)

 TIM_ClearITPendingBit(TIM4, TIM_IT_Update);清除中断标志位

while(i != 5)
  {
    TRIG_Send(1);//Trig引脚发送高脉冲信号
    us_delay(20);
    TRIG_Send(0);
    while(ECHO_Reci == 0);
    Open_tim4();
    i = i+1;
    while(ECHO_Reci == 1);
    Close_tim4();
    t = GetEcho_time();
    length = ((float)t/58.0);
    sum = sum+length;
  }
    length = sum/5.0;//取五次距离总和的平均距离
    return length;    

 while(ECHO_Reci == 0);当Trig发送一个高脉冲信号时,Echo立马变成高电平,此时Echo的电平为1,则这行代码通过,执行下面的代码

while(ECHO_Reci == 1);Echo接收到trig发送回来的高脉冲信号,然后Echo有下降沿(高电平1变成低电平0),才会开始执行这个while语句下面的代码

HC_SR04.h文件

#include "stm32f10x.h"
void HC_SR04Config(void);
void Open_tim4(void);//开启TIM4定时器,并将计数器清零。这用于开始测量Echo信号的持续时间。
void Close_tim4(void);//关闭TIM4定时器。这用于结束测量Echo信号的持续时间。
int GetEcho_time(void);//获取Echo信号的持续时间。通过读取定时器计数器的值和中断次数来计算Echo信号  //的时间。
float Getlength(void);//计算并返回距离。通过多次触发测量,获取多次Echo信号的时间,求平均值,提高 
//测量的准确性。
void TIM4_IRQHandler(void);//IM4定时器中断处理函数,每当定时器溢出时会调用此函数。在函数内部,清 //除中断标志位并增加计数器变量 mscount,用于记录溢出次数。

#define ECHO_Reci  GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)
#define TRIG_Send(a) if(a) \
							        GPIO_SetBits(GPIOB, GPIO_Pin_11); \
                     else \
							      	GPIO_ResetBits(GPIOB, GPIO_Pin_11);
										 
										 

难点/疑惑点 

#define ECHO_Reci GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)

定义了一个宏 ECHO_Reci,用于读取GPIOB端口第10引脚的输入状态。这个宏方便了读取Echo引脚状态的操作。

#define TRIG_Send(a) if(a) \

                                                                GPIO_SetBits(GPIOB, GPIO_Pin_11); \

                                      else \

                                                                GPIO_ResetBits(GPIOB, GPIO_Pin_11);

定义了一个宏 TRIG_Send(a),用来设置或重置GPIOB端口第11引脚的输出状态。这个宏通过if-else语句实现:

  • 如果 a 为真(非零),则调用 GPIO_SetBits(GPIOB, GPIO_Pin_11) 将引脚设置为高电平。
  • 否则,调用 GPIO_ResetBits(GPIOB, GPIO_Pin_11) 将引脚设置为低电平。

main.c文件

#include "stm32f10x.h"
#include "main.h"
#include "HC_SR04.h"
#include "SysTick.h"



void delay(uint16_t time)
{

	uint16_t i = 0;
	while(time--)
	{
		i = 12000;
		while(i--);
	}

}

int  main()
{
	float Length = 0;
	HC_SR04Config();

	while(1)
	{
	Length = Getlength();
	printf("%.3f\r\n",Length);//表示保留三位小数,并在每次输出后换行。
	ms_delay(500);
	}		 
   
}

float Length = 0;

定义了一个浮点型变量Length,用于存储测量到的距离值。用浮点型是因为距离可能包含小数部分,而浮点型(float)可以存储小数值。

int储存整数值

    ms_delay(500);

我这里使用了ms_delay函数,其功能根据SysTick定时器实现的一个精确延时函数。我将在下面给代码发给大家,但我不做讲解,大家自行观看

 SysTick.c文件

#include "SysTick.h"
#include  "stm32f10x.h"

void ms_delay(uint32_t ms)
{
	 uint32_t i;
  SysTick_Config(72000);
  for(i=0;i<ms;i++)
  {
	  while(!((SysTick->CTRL)&(1<<16)));
	}
	  SysTick->CTRL &=-SysTick_CTRL_ENABLE_Msk;
}

void us_delay(uint32_t us)
{
   uint32_t i;
  SysTick_Config(72);
  for(i=0;i<us;i++)
  {
	  while(!((SysTick->CTRL)&(1<<16)));
	}
	  SysTick->CTRL &=-SysTick_CTRL_ENABLE_Msk;


}

 SysTick.h文件

#include  "stm32f10x.h"
void ms_delay(uint32_t ms);
void us_delay(uint32_t us);

这里数据的展现,我就不给大家展示了,大家可根据上面的代码自行配置,用串口或者数码管能看到与最近的障碍物之间的距离。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值