使用HAL库进行基于STM32的超声波HC-SR04和红外测距模块的距离测量实验比较
前言:本文主要是为了日常普通场合下测距做的对比实验,本实验主要包含2种模块:超声波测距模块(HC-SR04)和红外测距模块(SHARP GP2Y0A21YK0F)。两种模块不管是测距原理和编程实验方式都是不相同的,其测距效果也存在很大差异。感兴趣的读者朋友,可以看看这篇文章,文章末尾有实验程序开源。
硬件设备:STM32F103C8T6;OLED;超声波模块:HC-SR04和红外测距模块:SHARP GP2Y0A21YK0F
硬件实物图:
效果图:
引脚连接:
超声波HC-SR04模块:
VCC –> VCC
GND –> GND
Trig –> PA5
Echo –> PA0
红外测距SHARP GP2Y0A21YK0F模块:
VCC –> VCC
GND –> GND
Vo(数据) –> PA1
一、超声波测距模块简介
超声波是振动频率高于20KHZ的机械波。它具有频率高、波长短、绕射现象小、方向性好、能够成为射线而定向传播等特点,应用广泛,适合大学生、工程师、技术人员以及电子爱好者等操作。
新版HC-SRO4,性能远超老版HC-SRO4、US-015,在测距精度高于老版HC-SRO4和US-015的情况下,测距范围更远,可达6米,远超一般超声波测距模块。采用CS-100A超声波测距SOC芯片,高性能,工业级,宽电压、低价格,只有普通超声波测距模块一半的价格,而性能远超普通超声波测距模块。性能与US-025相同,均采用CS100A芯片,接口完全兼容。
主要参数:
超声波测距芯片:CS100A
工作电压:DC 3V-5.5V
工作电流:5.3mA
工作温度:-40℃-85℃
输出方式:GPI0
感应角度:小于15度
探测距离:2cm~600cm
探测精度:0.1cm+1%
工作原理:
1、采用IO触发测量距离,给至少10us的高电平;
2、模块自动发送8个40khz的方波,自动检测是否有信号返回;
3、有信号返回、通过lo输出一高电平、高电平持续的时间就是超声波从发射到返回的时间,测试距离=(高电平时间*声速(340M/s)/2);
本模块有一个接口:4Pin供电及通信接口。为2.54mm间距的弯排针,如图所示:从左到右依次编号1、2、3、4,它们的定义如下∶
1号Pin:接VCC电源(直流3V-5.5V)。
2号Pin:接外部电路的Trig端,向此管脚输入一个10uS以上的高电平,可触发模块测距。
3号Pin:接外部电路的Echo端,当测距结束时,此管脚会输出一个高电平,电平宽度为超声波往返时间之和。
4号Pin:接外部电路的地。
超声波时序图:
以上时序图表明你只需要提供一个10uS以上脉冲触发信号该模块内部将发出8个40kHz周期电平并检测回波。
一旦检测到有回波信号则输出回响信号。
回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。
公式: us/58-厘米或者uS/148-英寸;或是:距离=高电平时间*声速(340M/S)/2;
特别注意:
1、此模块不宜带电连接,若要带电连接则先让模块的GND端先连接,否则会影响模块的正常工作。
2、测距时,被测物体的面积不小于0.5平方米且平面尽量要求平整,否则影响测量的结果。
二、红外测距模块简介
GP2YOA21YKOF是测距传感器单元PSD的集成组合构成(位置敏感探测器),IRED((红外发光二极管)和信号处理电路的各种物体的反射率,对环境温度和工作时间不易受到影响的距离检测由于采用三角测量方法,这个装置输出对应于该电压检测距离,所以这种传感器还可以来作为接近传感器。
工作电压:4.5~5.5V DC
消耗电流:典型30mA
工作温度:-10~60℃
储存温度:-40~70℃
测量距离:10~80cm(10cm内数据盲区)
★输出类型:模拟量输出型
引脚说明:
①Vo:模拟电压输出
②GND:地
③VCC:电源正
输出电压(v)与距离反射物体L (cm)
从上图就可以明显看出,该模块0~10cm内的电压与距离不成单调关系,故一般该传感器的有效值在10~80cm。
应用场合:
1、触摸式开关(卫浴设备,照明控制等)
2、机器人吸尘器
3.传感器用于节能(自动取款机,复印机,自动售货机)
4.游乐设备(机器人,街机游戏机)
三、OLED简介
篇幅有限,如果这方面不熟悉的朋友可以去看看本人的另一篇文章,十分相近,希望对您有所帮助。【强烈推荐】基于stm32的OLED各种显示实现(含动态图)_混分巨兽龙某某的博客-CSDN博客https://blog.csdn.net/black_sneak/article/details/125418537?spm=1001.2014.3001.5501
四、CubexMX配置
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);
3、ADC1配置:配置ADC-IN1模数转换——红外测距仪所用(PA1接Vo)。
4、 TIM2配置:设置定时器TIM2每1us向上计数一次,通道1为上升沿捕获并连接到超声波模块的ECHO引脚,记得开启定时器中断(涉及到捕获中断+定时器溢出中断)。
5、I2C2配置:作为OLED的通讯方式;
6、GPIO配置:PA5接到了HC-SR04的TRIG触发引脚,默认输出低电平
7、TIM1配置:由上面可知HC-SR04的使用需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;
8、时钟树配置:
注意:ADC的时钟频率不要过高,否则会影响精度(8~12Mhz)
9、工程配置
五、代码
5.1 超声波HC-SR04模块代码
其实,超声波HC-SR04的驱动就是基于GPIO口的调用。同时,由于超声波测距模块是基于超声波的物理性质,去进行距离测量,故此其精度受到很多因素影响。
影响因素:温度,传播介质及其物理属性等
本次实验就不进行超声波模块的精度补偿了,后面有空可以给大家出一个相关的文章。
HC-SR04.h:
#ifndef HCSR04_H_
#define HCSR04_H_
#include "main.h"
#include "delay.h"
typedef struct
{
uint8_t edge_state;
uint16_t tim_overflow_counter;
uint32_t prescaler;
uint32_t period;
uint32_t t1; // 上升沿时间
uint32_t t2; // 下降沿时间
uint32_t high_level_us; // 高电平持续时间
float distance;
TIM_TypeDef* instance;
uint32_t ic_tim_ch;
HAL_TIM_ActiveChannel active_channel;
}Hcsr04InfoTypeDef;
extern Hcsr04InfoTypeDef Hcsr04Info;
/**
* @description: 超声波模块的输入捕获定时器通道初始化
* @param {TIM_HandleTypeDef} *htim
* @param {uint32_t} Channel
* @return {*}
*/
void Hcsr04Init(TIM_HandleTypeDef *htim, uint32_t Channel);
/**
* @description: HC-SR04触发
* @param {*}
* @return {*}
*/
void Hcsr04Start();
/**
* @description: 定时器计数溢出中断处理函数
* @param {*} main.c中重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
* @return {*}
*/
void Hcsr04TimOverflowIsr(TIM_HandleTypeDef *htim);
/**
* @description: 输入捕获计算高电平时间->距离
* @param {*} main.c中重定义void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
* @return {*}
*/
void Hcsr04TimIcIsr(TIM_HandleTypeDef* htim);
/**
* @description: 读取距离
* @param {*}
* @return {*}
*/
float Hcsr04Read();
#endif /* HCSR04_H_ */
HC-SR04.c:
#include "hc-sr04.h"
Hcsr04InfoTypeDef Hcsr04Info;
/**
* @description: 超声波模块的输入捕获定时器通道初始化
* @param {TIM_HandleTypeDef} *htim
* @param {uint32_t} Channel
* @return {*}
*/
void Hcsr04Init(TIM_HandleTypeDef *htim, uint32_t Channel)
{
/*--------[ Configure The HCSR04 IC Timer Channel ] */
// MX_TIM2_Init(); // cubemx中配置
Hcsr04Info.prescaler = htim->Init.Prescaler; // 72-1
Hcsr04Info.period = htim->Init.Period; // 65535
Hcsr04Info.instance = htim->Instance; // TIM2
Hcsr04Info.ic_tim_ch = Channel;
if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_1)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_1; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_2)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_2; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_3)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_3; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_4)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_4; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_4)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_4; // TIM_CHANNEL_4
}
/*--------[ Start The ICU Channel ]-------*/
HAL_TIM_Base_Start_IT(htim);
HAL_TIM_IC_Start_IT(htim, Channel);
}
/**
* @description: HC-SR04触发
* @param {*}
* @return {*}
*/
void Hcsr04Start()
{
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
DelayUs(10); // 10us以上
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
}
/**
* @description: 定时器计数溢出中断处理函数
* @param {*} main.c中重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
* @return {*}
*/
void Hcsr04TimOverflowIsr(TIM_HandleTypeDef *htim)
{
if(htim->Instance == Hcsr04Info.instance) // TIM2
{
Hcsr04Info.tim_overflow_counter++;
}
}
/**
* @description: 输入捕获计算高电平时间->距离
* @param {*} main.c中重定义void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
* @return {*}
*/
void Hcsr04TimIcIsr(TIM_HandleTypeDef* htim)
{
if((htim->Instance == Hcsr04Info.instance) && (htim->Channel == Hcsr04Info.active_channel))
{
if(Hcsr04Info.edge_state == 0) // 捕获上升沿
{
// 得到上升沿开始时间T1,并更改输入捕获为下降沿
Hcsr04Info.t1 = HAL_TIM_ReadCapturedValue(htim, Hcsr04Info.ic_tim_ch);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, Hcsr04Info.ic_tim_ch, TIM_INPUTCHANNELPOLARITY_FALLING);
Hcsr04Info.tim_overflow_counter = 0; // 定时器溢出计数器清零
Hcsr04Info.edge_state = 1; // 上升沿、下降沿捕获标志位
}
else if(Hcsr04Info.edge_state == 1) // 捕获下降沿
{
// 捕获下降沿时间T2,并计算高电平时间
Hcsr04Info.t2 = HAL_TIM_ReadCapturedValue(htim, Hcsr04Info.ic_tim_ch);
Hcsr04Info.t2 += Hcsr04Info.tim_overflow_counter * Hcsr04Info.period; // 需要考虑定时器溢出中断
Hcsr04Info.high_level_us = Hcsr04Info.t2 - Hcsr04Info.t1; // 高电平持续时间 = 下降沿时间点 - 上升沿时间点
// 计算距离
Hcsr04Info.distance = (Hcsr04Info.high_level_us / 1000000.0) * 340.0 / 2.0 * 100.0;
// 重新开启上升沿捕获
Hcsr04Info.edge_state = 0; // 一次采集完毕,清零
__HAL_TIM_SET_CAPTUREPOLARITY(htim, Hcsr04Info.ic_tim_ch, TIM_INPUTCHANNELPOLARITY_RISING);
}
}
}
/**
* @description: 读取距离
* @param {*}
* @return {*}
*/
float Hcsr04Read()
{
// 测距结果限幅
if(Hcsr04Info.distance >= 500)
{
Hcsr04Info.distance = 500; //元器件资料说是600cm最高距离,这里保守一点
}
return Hcsr04Info.distance;
}
由于利用中断去读取定时测算的脉冲距离,所以这里需要重写定时器的中断服务函数。(这部分放在main.c最后即可)
/* USER CODE BEGIN 4 */
/**
* @description: 定时器输出捕获中断
* @param {TIM_HandleTypeDef} *htim
* @return {*}
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) //捕获回调函数
{
Hcsr04TimIcIsr(htim);
}
/**
* @description: 定时器溢出中断
* @param {*}
* @return {*}
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) //在中断回调函数中添加用户代码
{
Hcsr04TimOverflowIsr(htim);
}
/* USER CODE END 4 */
5.2 红外测距SHARP GP2Y0A21YK0F模块代码
其实,红外测距模块就是单纯的ADC读取,通过电压结合手册中电压与距离的关系公式得到计算的距离。
ranging.h:
#ifndef __SHARP2Y0A21_H
#define __SHARP2Y0A21_H
#include "main.h"
#define Adc1IN1Distance_READ_TIMES 10 //定义红外传感器读取次数,以便取平均值
void DistanceSensor_Init(void); //初始化红外传感器
float DistanceSensor_Get_Val(void); //读取红外传感器的值
#endif
ranging.c:
#include "sharp.h"
#include "adc.h"
#include "main.h"
#include "stdio.h"
//初始化ADC,不用修改
//这里我们仅以规则通道为例
//初始化传感器,需要修改端口和引脚号,这里是c出口,c1引脚,ADC1的IN1
void DistanceSensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIOA时钟
//先初始化ADC1通道11 IO口
GPIO_InitStructure.Pin = GPIO_PIN_1;//PA1
GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;//模拟输入
GPIO_InitStructure.Pull = GPIO_NOPULL ;//不带上下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
MX_ADC1_Init();//初始化ADC1
}
//PA1=IN1
float DistanceSensor_Get_Val(void)
{
uint32_t temp_val=0;
float distemp=0.0;
uint8_t t;
for(t=0;t<Adc1IN1Distance_READ_TIMES;t++)
{
temp_val+=HAL_ADC_GetValue(&hadc1); //读取ADC值,通道1
HAL_Delay(5);
}
temp_val/=Adc1IN1Distance_READ_TIMES;//得到平均值,这个是平均的ADC,
distemp=temp_val*3.3/4095;
//电压对应距离
distemp=(-13.2*distemp*distemp*distemp)+72.84*distemp*distemp-140*distemp+107.12;
return distemp;
}
5.3 OLED显示代码
OLED模块作为常用的数据输出显示模块,应该是必须要掌握的。如果有朋友不是很熟悉,可以去参考本人上面提到的那篇文章,十分详尽!!!
唯一不同的点:本实验的目的是需要对比一下超声波测距模块与红外测距模块的效果怎么样。其精度最高越好,这里就需要考虑到实现OLED显示含有小数的数字功能!
含有小数的数字显示API函数:
//z_len为整数显示位数,f_len为小数显示位数,size2为字体大小
void OLED_Showdecimal(u8 x,u8 y,float num,u8 z_len,u8 f_len,u8 size2)
{
u8 t,temp;
u8 enshow;
int z_temp,f_temp;
z_temp=(int)num;
//整数部分
for(t=0;t<z_len;t++)
{
temp=(z_temp/oled_pow(10,z_len-t-1))%10;
if(enshow==0 && t<(z_len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
continue;
}
else
enshow=1;
}
OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
}
//小数点
OLED_ShowChar(x+(size2/2)*(z_len),y,'.',size2);
f_temp=(int)((num-z_temp)*(oled_pow(10,f_len)));
//小数部分
for(t=0;t<f_len;t++)
{
temp=(f_temp/oled_pow(10,f_len-t-1))%10;
OLED_ShowChar(x+(size2/2)*(t+z_len)+5,y,temp+'0',size2);
}
}
5.4 main函数
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_I2C2_Init();
MX_ADC1_Init();
MX_TIM2_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
OLED_CLS();
Hcsr04Init(&htim2, TIM_CHANNEL_1); // 通道选择
Hcsr04Start(); // 启动超声波
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//显示实验对比
OLED_ShowCN_STR(30,0,0,4);
//红外测距显示
HAL_ADC_Start(&hadc1);
adcx=HAL_ADC_GetValue(&hadc1);
distance=DistanceSensor_Get_Val();
OLED_ShowCN_STR(0,2,4,5);
OLED_Showdecimal(80,2,distance,3,2,16);
//超声波测距显示
Hcsr04Start();
OLED_ShowCN_STR(0,4,9,5);
OLED_Showdecimal(80,4,Hcsr04Read(),3,2,16);
//显示单位
OLED_ShowCN_STR(30,6,14,5);
}
/* USER CODE END 3 */
}
六、实验效果
测距模块对比实验
经过本人由于拍摄不方便,但是效果还是很明显的。经过笔者反复标定测试发现,超声波HC-SR04测距的效果从反应速度,测量范围,精度等都略优于红外测距模块。而且,超声波模块比红外测距模块便宜很多,emmm,不是很理解其意义所作。当然,红外模块受外界因素的影响比超声波模块少很多,超声波模块在很多情况下是需要补偿校准的。
代码开源
链接:https://pan.baidu.com/s/1ouwFLF8ru9DROIFSZnEITg 提取码:dktp