《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距

4.1 理论分析

4.1.1超声波概述

人类耳朵能听到的声波频率为20Hz~20000Hz。当声波的振动频率小于20Hz或大于20KHz时,我们便听不见了。因此,我们把频率高于20000赫兹的声波称为“超声波”(ultrasonic)。通常的超声波频率为1兆赫兹~5兆赫兹。它的方向性好,穿透能力强,易于获得较集中的声能,在水中传播距离远,可用于测距、测速、清洗、焊接、碎石、杀菌消毒等。在医学、军事、工业、农业上有很多的应用。

超声波发生器可以分为两大类:一类是用电气方式产生超声波,一类是用机械方式产生超声波

电气方式包括压电型、磁致伸缩型和电动型等;机械方式有加尔统笛、液哨和气流旋笛等。它们所产生的超声波的频率、功率和声波特性各不相同,因而用途也各不相同。目前较为常用的是压电式超声波发生器。

压电式超声波发生器实际上是利用压电晶体的谐振来工作的,其外观结构与内部结构分别如图所示。该传感器有两个压电陶瓷(压电晶片)和一个共鸣器(共振板), 当其两极外加脉冲信号,且频率等于压电陶瓷的固有振荡频率时,压电陶瓷将会发生共振,并带动共鸣器振动产生超声波。反之,如果两电极间未外加电压,当共振板接收到超声波时,将迫使压电陶瓷振动,将机械能转换为电信号,这时它就成为超声波接收器了。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第1张图片

图1超声波模块内部结构简图

4.1.2超声波测距工作原理

目前超声波测距的方法有多种:如时间差测距法、相位测距法、声波幅值测距法。最常用就是时间差测距法,本文的测距方法就是时间差测距法

超声波发射器向某一方向发射超声波,在发射的同时开始计时,超声波在空气中传播,途中碰到障碍物就立即返回,超声波接收器收到反射波就立即停止计时。根据时间差和超声波的速度可以估算出发射位置到障碍物位置的距离。超声波在空气中的传播速度为340 m/ s,根据计时器记录的时间t,就可以计算出发射点距障碍物的距离S ,即:S = 340t / 2。这就是所谓的时间差测距法。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第2张图片

图2超声波测距基本原理

超声波指向性强,在介质中传播的距离较远,利用超声波测距往往比较迅速、方便、计算简单、易于做到实时控制,并且在测量精度方面能达到工业实用的要求,因此在移动机器人的研制上也得到了广泛的应用。

超声波测距传感器规格很多,测试距离也从远到近都有,价格相差也较大,一般机器人爱好者使用的都是测量范围在最小几厘米到最大几米之间。超声波传感器一般作用距离较短,普通的有效探测距离都在5-10m之间,精度约5毫米。但是会有一个最小探测盲区,一般在几十毫米。因为声波的特性,所以超声波传感器受环境影响比较小,使用场合比较广泛。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第3张图片

图3超声波测距范围

超声波测距的优点在于测量范围较大,且不是使用光学信号,所以被测物体的颜色对于测量结果没有影响。但还是常常会受到外界环境的其它因素影响,依靠声速测距,所以对于一些影响声速的因素较敏感,比如温度、风等。最大允许角度较小。在不同的室温下,声速是不同的,因而会给测距带来误差,所以一般的模块测距精度都难以小于5毫米。但很自然的会想到采用温度补偿的方式,也即额外再添加一个温度传感器,根据不同温度下的声速来计算,从而可以补偿温度变化带来的误差。表1列出了几种不同温度下的声速。在使用时,如果温度变化不大,则可认为声速是基本不变的。如果测距精度要求很高,则应通过温度补偿的方法加以校正

表1声速与温度的关系
温度 -30 -20 -10 0 10 20 30 100
声速m/s 313 319 325 323 338 344 349 386

根据以上数据可以进行线性拟合,得到如下公式:

V = 331.4 + 0.607 T ( 摄 氏 温 度 ) V = 331.4 + 0.607 T (摄氏温度) V=331.4+0.607T()

式中, T T T为实际温度单位为 ℃ ℃ V V V为超声波在介质中的传播速度单位为 m / s m/s m/s

根据测得的实际温度矫正环境中的真实声速,以减小测量误差。这样的模块测距精度就可以达到1~2毫米了,而且同时能当做温度传感器来使用,但价格也会稍高。

超声波测距传感器可能是机器人中运用最为广泛的传感器之一了,它不仅便宜,拥有丰富的库,而且测量结果也较为可靠。我们都知道蝙蝠夜行时躲避障碍物的原理,下面以一款市面上最为成熟和常见的超声波测距模块HC-SR04为例来说明。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第4张图片

图4超声波模块HC-SR04实物图

当然啦,如果环境比较复杂,建议使用带有温度补偿的模块,这里推荐US-100模块。该超声波模块没有自带温度补偿,以340m/s的室温下声速作为参照基准,同个扩展库捕捉脉冲宽度获得超声波来回的时间后,通过简单的运算获得距离。

4.1.3 HC-SR04 测距原理

HC-SR04超声波测距模块可提供2cm-400cm的非接触式距离感测功能,HC-SR04模块性能稳定,测度距离精确,能和国外的SRF05、SRF02等超声波测距模块相媲美。模块高精度,盲区(2cm)超近,测距精度可达3mm(这个是厂家介绍,一般测距要3cm以上,精度有时要到达5-6mm),包括发射器、接收器与控制电路,它是一种压电式传感器,利用电致伸缩现象而制成。在压电材料切片上(如石英晶体、压电陶瓷、钛酸铅钡等)施加交变电压,使它产生电致伸缩振动而产生超声波。当外加交变电压的频率等于晶片的固有频率而产生共振,这时产生的超声波最强。压电式超声波接收器一般是利用超声波发生器的逆效应进行工作的,其结构和超声波发生器基本相同,有时就用同一个换能器兼作发生器和接收器两种用途。当超声波作用到压电晶片上时使晶片伸缩,在晶片的两个界面上便产生交变电荷后转换成电压经放大送到测量电路,最后记录或显示出来。

HC-SR04超声波测距模块的基本工作原理:

(1)采用IO口TRIG 触发测距,给最少10us的高电平信号。
(2)模块自动发送8个40khz的方波,自动检测是否有信号返回;
(3)有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2。

HC-SR04测距时序图如下:

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第5张图片

图5超声波HC-SR04测距时序图

以上时序图表明你只需要提供一个 10uS 以上脉冲触发信号,该模块内部将发出 8 个 40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。建议测量周期为 60ms 以上,以防止发射信号对回响信号的影响

【注1】此模块不宜带电连接, 若要带电连接, 则先让模块的 GND 端先连接, 否则会影响模块的正常工作。

【注2】测距时,被测物体的面积不少于 0.5 平方米且平面尽量要求平整,否则影响测量的结果。

4.2 实验详解

4.2.1实验目的

1.通过实验掌握STM32 芯片定时器的配置方法
2.掌握超声波的测距原理

4.2.2实验设备

硬件:PC 机一台;STM32开发板一套; 超声波模块一个
软件:Windows 10系统,Keil5集成开发环境

4.2.3硬件连接

HC-SR04模块电路图如下图所示:

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第6张图片

图6 HC-SR04模块电路图

TL074:四路低噪声 JFET 输入通用运算放大器,放大接收信号及控制。
MAX232:MAX232芯片是美信(MAXIM)公司专为RS-232标准串口设计的单电源电平转换芯片,使用+5v单电源供电。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第7张图片

图7 T1 T2输入输出的典型曲线

超声波模块利用232芯片能够输出正负压信号给超声波发生器件供电,以此来达到最大电压差(约13~14V)给超声波器件供电,增大超声波发送功率。

STC11:STC单片机,处理逻辑信号。

STM32与HC-SR04模块连接示意图如下:

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第8张图片

图8 STM32与HC-SR04模块连接图

4.2.4 Lib_V3.5.0库实现

根据HC-SR04 测距原理,测距流程如下:TRIG发一个10us以上的高电平,ECHO等待高电平输出。一有输出就可以开定时器计时,当ECHO检测到低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离。具体流程图如下:

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第9张图片

图9测距流程图

距离的计算公式如下:

距 离 = ( 高 电 平 时 间 ∗ 声 速 ( 340 M / S ) ) / 2 ( 单 位 : m ) 距离 = (高电平时间*声速(340M/S))/2(单位:m) =((340M/S))/2(m)

接下来看看代码是如何实现的,这里只讲HC-SR04 测距相关的内容。

1.GPIO和定时器初始化

/**
 * @brief  初始化定时器3
 * @param  None
 * @retval None
 */
void HCRS04_Tim3_Config(void)
{
     
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_ICInitTypeDef TIM3_ICInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
  //TIM_DeInit(TIM3);//复位TIM3定时器 
  TIM_TimeBaseStructure.TIM_Period =  7199;
  TIM_TimeBaseStructure.TIM_Prescaler = 199; 
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM3, & TIM_TimeBaseStructure);
    
	//初始化Timer3_CH1(PA6)为PWM输出
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 7150; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  //CH1预装载使能
	
	//初始化Timer3_CH2(PA7)为脉宽捕获
	TIM3_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择输入端
	TIM3_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
	TIM3_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
	TIM3_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	  //配置输入分频,不分频 
	TIM3_ICInitStructure.TIM_ICFilter = 0x00;	  //IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM3, &TIM3_ICInitStructure);
	
}

/**
 * @brief  初始化超声波IO口
 * @param  None
 * @retval None
 */
void HCRS04_GPIO_Config(void)
{
     
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;			/*PA6--TRIG1*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	/*复用推挽输出*/
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;		/*PA7--ECHO3*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;/*浮空输入模式*/	 
	GPIO_Init(GPIOA, &GPIO_InitStructure);

}

/**
 * @brief  初始化超声波中断
 * @param  None
 * @retval None
 */
void HCRS04_NVIC_Config(void)
{
     

	NVIC_InitTypeDef NVIC_InitStructure;
	
	//使能定时器3通道2捕获中断
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);   //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
	TIM_ITConfig(TIM3, TIM_IT_CC2,ENABLE);   //不允许更新中断,允许CC2IE捕获中断	
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
  TIM_ClearFlag(TIM3, TIM_FLAG_Update); 
  //TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
}

关于GPIO的初始化没啥好说的,PA6做输出,对应的是TRIG,用于触发测距信号;PA7作为输入,对应的是ECHO,需要进行输入捕获来获取高电平的时间。

这里需要配置定时器3,这里使用PWM输出来触发测距信号。

根据前面的参数配置,我们可以算出TIM的输出周期:

T I M = 1 / ( T c l k / ( p s c + 1 ) ) ∗ ( a r r + 1 ) TIM=1/(Tclk/(psc+1))*(arr+1) TIM=1/(Tclk/(psc+1))(arr+1)

arr=7199 psc=199 Tclk=72Mhz

T I M = 1 / ( 72 M h z / ( 200 ) ) ∗ ( 7200 + 1 ) = 20 m s TIM=1/(72Mhz/(200))*(7200+1)=20ms TIM=1/(72Mhz/(200))(7200+1)=20ms

占空比为 7150 / 7200 = 99 7150/7200=99% 7150/7200=99,因此完全能达到触发的要求。

2.定时器的通道2进行脉宽捕获,获取高电平时间

/**
  * @brief  This function handles TIM3 interrupt request.
  * @param  None
  * @retval None
  */
void TIM3_IRQHandler(void)
{
     
	if ((TIM3CH2_CAPTURE_STA & 0X80) == 0)		//还未成功捕获	
	{
     
		if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)		//捕获2发生捕获事件
		{
     
			TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);		//清除中断标志位
			if (TIM3CH2_CAPTURE_STA & 0X40)		//捕获到一个下降沿
			{
     
				TIM3CH2_CAPTURE_DOWNVAL = TIM_GetCapture2(TIM3);//记录下此时的定时器计数值
				if (TIM3CH2_CAPTURE_DOWNVAL < TIM3CH2_CAPTURE_UPVAL)
				{
     
					TIM3_T2 = 65535;
				}
				else
					TIM3_T2 = 0;
				tempup = TIM3CH2_CAPTURE_DOWNVAL - TIM3CH2_CAPTURE_UPVAL + TIM3_T2;		//得到总的高电平的时间
				TIM3CH2_CAPTURE_STA = 0;		//捕获标志位清零
				TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Rising); //设置为上升沿捕获		  
			}
			else //发生捕获时间但不是下降沿,第一次捕获到上升沿,记录此时的定时器计数值
			{
     
				TIM3CH2_CAPTURE_UPVAL = TIM_GetCapture2(TIM3);		//获取上升沿数据
				TIM3CH2_CAPTURE_STA |= 0X40;		//标记已捕获到上升沿
				TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Falling);//设置为下降沿捕获
			}
			//计算距离
			distance = tempup * 0.034; //标准值0.034
		}
	}
}

定时器的周期是20ms,因此最终的得到的距离是:

距 离 = ( T I M 3 的 计 数 器 ∗ 20 / 1000 s ∗ 声 速 ( 340 m / s ) ) / 200 = T I M 3 的 计 数 器 ∗ 0.034 ( 单 位 : c m ) 距离 = (TIM3的计数器*20/1000s*声速(340m/s))/200 = TIM3的计数器*0.034(单位:cm) =(TIM320/1000s(340m/s))/200=TIM30.034cm

3.最后,给出主函数的代码

/**
  * @brief     main
  * @param     None
  * @retval    
  */
int main(void)
{
     	
  /*时钟初始化*/
	SysTick_Init();
	
  /* USART1 配置模式为 115200 8-N-1,中断接收 */
	USART1_Config();
	
	/* 超声波初始化*/
	HCRS04_Config();
	
	printf("超声波测距\r\n");
	
	while(1)
  {
     
    Delay_ms(1000);
		printf("Distance: %f cm\r\n",distance);
  }
}

到此,HC-SR04 的测距基本就实现了,笔者在测试的时候发现,实际测试过程中有个别数据会异常偏小。这是由于HC-SR04 的“余震”导致的,具体解释如下:

1.电路串扰。超声波发射时的瞬间电流很大,例如某种工业级连续测距产品瞬间电流会有15A,通常的产品也能达到1A,瞬间这么大的电流会对电源有一定影响,并干扰接收电路。通过改善电源设计可以缓解这种情况,但在低成本设计中很难根除。所以每次发射完毕,接收电路还需要一段时间稳定工作状态。因此,在没稳定前,HC-SR04 的测的距离和实际的距离差别较大。

2.探头的余震。即使是分体式的,发射头工作完后还会继续震一会,这是物理效应,也就是余震。这个余震信号也会向外传播。如果你的设计是发射完毕后立刻切换为接收状态(无盲区),那么这个余震波会通过壳体和周围的空气,直接到达接收头、干扰了检测。
注:通常的测距设计里,发射头和接收头的距离很近,在这么短的距离里超声波的检测角度是很大的,可达180度。

2.壳体的余震。就像敲钟一样,能量仍来自发射头。发射结束后,壳体的余震会直接传导到接收头,当然这个时间很短,但已形成了干扰。另外,在不同的环境温度下,壳体的硬度和外形会有所变化,其余震有时长、有时短、有时干扰大、有时干扰小,这是设计工业级产品时必须要考虑的问题。

总之,就是HC-SR04 的各种不稳定因素导致测量精度偏差较大,消除上述现象最常用的方法就是在检测的时候多次循环检测,取平均值

重新修改定时器3的内容,代码如下:

/**
  * @brief  This function handles TIM3 interrupt request.
  * @param  None
  * @retval None
  */
void TIM3_IRQHandler(void)
{
     
	if ((TIM3CH2_CAPTURE_STA & 0X80) == 0)		//还未成功捕获	
	{
     
		if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)		//捕获2发生捕获事件
		{
     
			TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);		//清除中断标志位
			if (TIM3CH2_CAPTURE_STA & 0X40)		//捕获到一个下降沿
			{
     
				TIM3CH2_CAPTURE_DOWNVAL = TIM_GetCapture2(TIM3);//记录下此时的定时器计数值
				if (TIM3CH2_CAPTURE_DOWNVAL < TIM3CH2_CAPTURE_UPVAL)
				{
     
					TIM3_T2 = 65535;
				}
				else
					TIM3_T2 = 0;
				tempup = TIM3CH2_CAPTURE_DOWNVAL - TIM3CH2_CAPTURE_UPVAL + TIM3_T2;		//得到总的高电平的时间

				TIM3CH2_CAPTURE_STA = 0;		//捕获标志位清零
				TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Rising); //设置为上升沿捕获		  
			}
			else //发生捕获时间但不是下降沿,第一次捕获到上升沿,记录此时的定时器计数值
			{
     
				TIM3CH2_CAPTURE_UPVAL = TIM_GetCapture2(TIM3);		//获取上升沿数据
				TIM3CH2_CAPTURE_STA |= 0X40;		//标记已捕获到上升沿
				TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Falling);//设置为下降沿捕获
			}
			//计算距离
			sum += tempup * 0.034; //标准值0.034
			
			if(count == 5)
			{
     
				distance = sum/5.0;
				count = 0;
				sum = 0.0;
			}
			else
			{
     
				count ++;
			}
		}
	}
}

当然啦,有兴趣的朋友可以深入研究,还可以更精度更高的测距模块。

4.2.5 HAL库实现

我们在串口的例子的基础上进行配置。

串口通信(HAL库)

这里需要配置TIM,关于TIM的内容,请参考笔者以前的博文:

TIM

由STM32与HC-SR04模块的硬件连接关系图,PA6做输出,对应HC-SR04模块的TRIG,用于触发测距信号;PA7作为输入,对应HC-SR04模块的ECHO,需要进行输入捕获来获取高电平的时间。那么接下来来一一配置。

1.设置RCC

设置高速外部时钟HSE,选择外部时钟源。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第10张图片

图10 RCC配置

2.时钟配置
笔者的板子使用的外部晶振为8MHz,选择外部时钟HSE 8MHz ,PLL锁相环9倍频后为72MHz,系统时钟来源选择为PLL,设置APB1分频器为 /2,这时候定时器的时钟频率为72Mhz。本文笔者使用的定时器是TIM3,TIM3挂在APB1上,不同的定时器挂在不同总线上的。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第11张图片

图11时钟配置

【注】TIM3的时钟源有两个选项

选项1 :Internal Clock 内部时钟
选项2 : ETR2 外部触发输入(ETR)(仅适用TIM2,3,4)

3.GPIO配置
上文使用PWM最为输出来触发测距信号,这里通过控制GPIO的高低电平来触发测距信号。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第12张图片

图12配置PA6为输出

将PA6配置为输出即可。

4.TIM3配置
本文要使用TIM3的通道2,因此需要将其使能。每个通道有很多模式,通道2选择输入捕获模式,当对应的通道打开后,对应的GPIO也会被使能。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第13张图片

图13使能TIM3的通道

【注】如果使能通道前通道中GPIO使用过,STM32CubeMX会自动将GPIO配置为重映射的GPIO。举个例子,当PB0被占用了,那么四个GPIO会重映射到PC6-PC9。

TIM参数配置如下:

  • Counter setting

Prtscaler (定时器分频系数) : 199
Counter Mode(计数模式) :Up(向上计数模式)
Counter Period(自动重装载值) : 7199
CKD(时钟分频因子) : No Division 不分频
选项: 可以选择二分频和四分频
auto-reload-preload(自动重装载) : Enable 使能

  • TRGO Output (TRGO) Parameters

Master/Slave Mode(MSM bit):Disable
TRGO:定时器的触发信号输出 在定时器的定时时间到达的时候输出一个信号(如:定时器更新产生TRGO信号来触发ADC的同步转换,)

  • Input Capture Channel (CH2)

Polarity Selection(输入捕获模式): Rising Edge(上升沿捕获)
IC Selection (映射模式):Direct(直接映射)
Prescaler Division Ratio(分频模式):No division(不分频)
Input Filter(配置输入滤波器):(不滤波)

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第14张图片

图14 TIM3参数配置

根据前面的参数配置,arr=999 psc=0 Tclk=72Mhz ,

T I M = 1 / ( 72 M h z / ( 200 ) ) ∗ ( 7199 + 1 ) = 20 m s TIM=1/(72Mhz/(200))*(7199+1)=20ms TIM=1/(72Mhz/(200))(7199+1)=20ms

和使用Lib_V3.5.0库的计算结果是一样的。

还需要开启中断。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第15张图片

图15开启TIM3中断

好了,到这里,配置就完成了,生成工程就行了。

下面看看测距的代码是如何实现的。

1.初始化GPIO、TIM3等
这部分是自动生成的,这里就不贴出来了

2.TRIG发一个10us以上的高电平

static void HC_SR04_Delayus ( __IO uint32_t ulCount )
{
     
	uint32_t i;
	for ( i = 0; i < ulCount; i ++ )
	{
     
		uint8_t uc = 12;     //设置值为12,大约延1微秒  	      
		while ( uc -- );     //延1微秒	
	}	
}

static void HC_SR04_Start_signal(void)
{
     
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);
	//延时20us
	HC_SR04_Delayus(20);

	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
}

通过GPIO的读写来触发测距信号,延时就用个简单延时就可以,不需要太精确。

3.输入捕获,得到高电平的时间

/**
  * @brief  This function handles TIM3 Capture Callback.
  * @param  None
  * @retval None
  */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
     
	TIM_IC_InitTypeDef sConfigIC = {
     0};
	
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
	
	if(htim->Instance == htim3.Instance)
	{
     
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
		{
     
			if(sCapture.ChannelEdge == 0)          //捕获上升沿
			{
     
				sCapture.RisingTime = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);                         //获取上升沿时间点    
				sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;//切换捕获极性
				HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2);
				
				HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);     //切换捕获极性后需重新启动
 
				sCapture.ChannelEdge = 1;          	 //上升沿、下降沿捕获标志位
			}
			else if(sCapture.ChannelEdge == 1)     //捕获下降沿
			{
     
				sCapture.FallingTime = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);                       //获取下降沿时间点
				sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;//切换捕获极性
				HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2);
				
				HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);   	 //切换捕获极性后需重新启动
 
				sCapture.HighTime = sCapture.FallingTime < sCapture.RisingTime ? sCapture.FallingTime + 0xffff - sCapture.RisingTime + 1 : sCapture.FallingTime - sCapture.RisingTime;
				 
				//清零
				sCapture.ChannelEdge = 0;		  			
				
				sCapture.FinishFlag = 1;  
				
				//高电平持续时间 = 下降沿时间点 - 上升沿时间点
				//计算超声波测量距离
				distance = (float)sCapture.HighTime * 0.034;

			}
		}
	}
}

4.最后在主函数中打印输出距离信息。

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
     
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
  
	/*使能定时3*/  
	// 启动输入捕获并开启中断
	HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
			
	printf("超声波测距\r\n");
		
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
     
    /* USER CODE END WHILE */
		HC_SR04_Start_signal();
		
		/* 完成测量高电平脉宽 */
    if(sCapture.FinishFlag)
		{
     
			printf("Distance: %f cm\r\n",distance);
			
			sCapture.FinishFlag = 0;	
			HAL_Delay(1000);
		}
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

注意一定开启输入捕获中断,其他就没事好说了。

4.2.6实验现象

不管是使用何种库,结果都是一样的。

《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第16张图片
《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距_第17张图片




代码获取方法

1.长按下面二维码,关注公众号[嵌入式实验楼]
2.在公众号回复关键词[STM32F1]获取资料
在这里插入图片描述

你可能感兴趣的:(《嵌入式》STM32开发指南,超声波,STM32,HAL,测距)