基于stm32的超声波HC-SR04测距仪含距离报警(温度补偿)
基于STM32的超声波测距仪(含温度补偿与报警功能),文章末尾有实验代码开源。
前言:本文主要是紧跟前一篇文章中有关超声波HC-SR04模块进行的扩展实验,主要涉及超声波测距的温度补偿(DHT11),并搭配蜂鸣器的距离报警。整体实验较为简单,为常用的几个传感器模块相互配合协调工作。(文章最后有实验代码)
硬件设备:STM32F103ZET6;OLED;超声波模块:HC-SR04;温湿度模块DHT11;Beep
硬件实物图:
效果图:
引脚连接:
超声波HC-SR04模块:
VCC --> VCC
GND --> GND
Trig --> PA5
Echo --> PA0
DHT11模块:
DATA --> PB1
VCC --> 3.3V
GND --> GND
OLED模块:
VCC --> 3.3V
GND --> GND
SCL --> PB6
SDA --> PB7
一、超声波测距模块简介
HC-SR04超声波模块主要是通过GPIO的输出方式进行控制。具体的原理以及编程思路与过程,大家可以去看一下本人这篇实验文章,内容十分详尽。
这里主要想和大家补充说明的是:温度对于超声波测距的影响
超声波测距的工作原理:
超声波传感器是将超声波信号转换成其他能量信号(通常是电信号)的传感器。超声波是指频率大于20kHz的在弹性介质中产生的机械震荡波,其具有指向性强、能量消耗缓慢、传播距离相对较远等特点,因此常被用于非接触测距。
其原理是超声波传感器发射一定频率的超声波,借助空气媒质传播,到达测量目标或障碍物后反射回来,经反射后由超声波接收器接收脉冲,其所经历的时间即往返时间,往返时间与超声波传播的路程的远近有关。
当考虑温度对声速影响时,测试传输时间可以得出距离例如:
测得的时间为t单位为s,T为实际温度单位为℃,v为超声波在介质中的传播速度单位为m/s。
二、DHT11模块简介
DHT11 是广州奥松有限公司生产的一款湿温度一体化的数字传感器。在本实验中主要是为了测量坏境温度,利用该环境温度去校准此时的超声波声速。由于篇幅有限,对该模块不熟悉的朋友可以去看看笔者的另一篇文章,基于stm32的太空人温湿度时钟项目——DHT11(HAL库)_混分巨兽龙某某的博客-CSDN博客_太空人时钟代码https://blog.csdn.net/black_sneak/article/details/125472822?spm=1001.2014.3001.5501 上方文章较为系统的介绍了DHT11的使用和编程,这里就不过进行介绍了。
三、蜂鸣器BEEP模块简介
蜂鸣器(Beep)是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。
按其驱动方式的原理分,可分为:有源蜂鸣器(内含驱动线路,也叫自激式蜂鸣器)和无源蜂鸣器(外部驱动,也叫他激式蜂鸣器)。
按构造方式的不同,可分为:电磁式蜂鸣器和压电式蜂鸣器;
特别注意:蜂鸣器不是扬声器
蜂鸣器:可以通过PWM调节的方式去改变频率,模拟出类似“多来米发索拉西”的效果
扬声器:扬声器又称“喇叭”。是一种十分常用的电声换能器件,在发声的电子电气设备中都能见到它。它才是日常生活中,人们播放音乐所有的电子器件。
对于蜂鸣器的驱动,其实非常简单。只需要令其2个引脚之间产生电压差即可。笔者这里直接用了正点原子精英板上的蜂鸣器,直接对PB8引脚进行控制即可。
板载BEEP原理图:
对BEEP模块感兴趣的读者朋友可以试试用PWM调节去控制BEEP模块,感受一下BEPP放出的另类音乐。
四、OLED模块
关于OLED的使用与原理不熟悉的笔者欢迎去笔者另一篇文章学习。【强烈推荐】基于stm32的OLED各种显示实现(含动态图)_混分巨兽龙某某的博客-CSDN博客_stm32使用oled显示屏手把手教你彻底搞懂基于stm32的OLED的使用,教程中包含各种API函数的使用。满足几乎所有OLED显示的需要。文章末尾附带源码,强烈推荐!!!https://blog.csdn.net/black_sneak/article/details/125418537?spm=1001.2014.3001.5501
五、CubexMX配置
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);
3.1、GPIO配置:PB1设置为普通输出(DHT11的DATA接线引脚);
3.2、GPIO配置:PA5接到了HC-SR04的TRIG触发引脚,默认输出低电平
3.3、GPIO配置:PB8板子上默认接通BEEP的引脚,默认低电平;
4、 TIM1配置:由上面可知HC-SR04和DTH11的使用都需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;
5、TIM2配置:设置定时器TIM2每1us向上计数一次,通道1为上升沿捕获并连接到超声波模块的ECHO引脚,记得开启定时器中断(涉及到捕获中断+定时器溢出中断)。
6、I2C2配置:作为OLED的通讯方式;
7、时钟树配置:
8、工程配置
六、代码
6.1、超声波HC-SR04模块代码
其实,超声波HC-SR04的驱动就是基于GPIO口的调用。同时,由于超声波测距模块是基于超声波的物理性质,去进行距离测量,故此其精度受到很多因素影响(这里我们考虑温度堆砌影响)。
HC-SR04.h:
#ifndef HCSR04_H_
#define HCSR04_H_
#include "main.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"
#include "tim.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);
Tims_delay_us(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 */
6.2、温湿度DTH11模块代码
DTH11.H代码:
#ifndef __DHT11_H__
#define __DHT11_H__
/* Private includes ----------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "stdio.h"
#include "tim.h"
#include "stm32f1xx.h"
/* Private define ------------------------------------------------------------*/
#define DHT11_PIN_SET HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET) // ??GPIO??
#define DHT11_PIN_RESET HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET) // ??GPIO??
#define DHT11_READ_IO HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9) // DHT11 GPIO??
#define DLY_TIM_Handle (&htim2) // ?????
/* Private variables ---------------------------------------------------------*/
/* Private typedef -----------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
void DHT11(void);
void DHT11_START(void);
unsigned char DHT11_READ_BIT(void);
unsigned char DHT11_READ_BYTE(void);
unsigned char DHT11_READ_DATA(void);
unsigned char DHT11_Check(void);
static void DHT11_GPIO_MODE_SET(uint8_t mode);
void delay_us(uint16_t nus);
float data_compensate(); //★补偿函数
#endif
DTH11.C代码:
#include "dht11.h"
#include "oled.h"
#include "hc-sr04.h"
/**
* @brief DHT11Çý¶¯º¯Êý
* @param void
* @retval None
*/
void DHT11(void)
{
DHT11_READ_DATA();
HAL_Delay(50); // ãÐÉèÑÓ³Ù
}
/**
* @brief ????????????
* @param void
* @retval None
*/
void DHT11_START(void)
{
DHT11_GPIO_MODE_SET(0); // ?????????
DHT11_PIN_RESET; // ??????
HAL_Delay(20); // ???? 18 < ms > 30
DHT11_GPIO_MODE_SET(1); // ?????????,??DHT11??
} // ?????????,GPIO -> 1
/**
* @brief ?????? 1bit
* @param void
* @retval 0/1
*/
unsigned char DHT11_READ_BIT(void)
{
while(!DHT11_READ_IO); // ????????
Tims_delay_us(40); // ????????
if(DHT11_READ_IO) // ????????????? 1
{
while(DHT11_READ_IO); // ????????
return 1;
}
else // ??????? 0
{
return 0;
}
}
/**
* @brief ???????? 1byte / 8bit
* @param void
* @retval temp
*/
unsigned char DHT11_READ_BYTE(void)
{
uint8_t i,temp = 0; // ??????
for(i=0; i<8 ;i++)
{
temp <<= 1;
if(DHT11_READ_BIT()) // 1byte -> 8bit
{
temp |= 1; // 0000 0001
}
}
return temp;
}
/**
* @brief ?????????? 5byte / 40bit
* @param void
* @retval 0/1/2
*/
float DHT11_READ_DATA(void)
{
uint8_t i;
uint8_t data[5] = {0};
DHT11_START(); // ????????
if(DHT11_Check()) // ??DHT11??
{
while(!DHT11_READ_IO); // ??DHT11????????
while(DHT11_READ_IO); // ??DHT11????????
for(i=0; i<5; i++)
{
data[i] = DHT11_READ_BYTE(); // ?? 5byte
}
if(data[0] + data[1] + data[2] + data[3] == data[4])
{
//温度显示
OLED_ShowCN_STR(0,2,5,2);
OLED_ShowStr(32,2,":",2);
OLED_ShowNum(40,2,data[2],2,16);
OLED_ShowCN_STR(59,2,7,1);
OLED_ShowCN_STR(0,6,11,3);
OLED_ShowStr(48,6,":",2);
OLED_Showdecimal(55,6,data_compensate(data[2]),3,2,16);
OLED_ShowStr(100,6,"cm",2);
// //ʪ¶ÈÏÔʾ
// OLED_ShowCN_STR(0,6,2,2);
// OLED_ShowStr(32,6,":",2);
// OLED_ShowNum(40,6,data[0],2,16);
// OLED_ShowStr(59,6,"HR",2);
return data_compensate(data[2]); // ??????
}
else
{
return 0; // ??????
}
}
else // ??DHT11???
{
return 2;
}
}
/**
* @brief ????????????(??DHT11?????)
* @param void
* @retval 0/1
*/
unsigned char DHT11_Check(void)
{
Tims_delay_us(40);
if(DHT11_READ_IO == 0) // ???DHT11??
{
return 1;
}
else // ???DHT11???
{
return 0;
}
}
/**
* @brief ??????
* @param mode: 0->out, 1->in
* @retval None
*/
static void DHT11_GPIO_MODE_SET(uint8_t mode)
{
if(mode)
{
/* ?? */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_1; // 9???
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // ????
GPIO_InitStruct.Pull = GPIO_PULLUP; // ????
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
else
{
/* ?? */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = GPIO_PIN_1; // 9???
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // Push Pull ??????
GPIO_InitStructure.Pull = GPIO_PULLUP; // ????
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; // ??
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
}
}
float data_compensate(int data)
{
float newspeed = 331.45+0.607*data;
Hcsr04Info.distance = (Hcsr04Info.high_level_us / 1000000.0) * newspeed / 2.0 * 100.0;
return Hcsr04Info.distance;
}
///**
// * @brief ?????us,Prescaler -> 72-1
// * @param us: <= 65535
// * @retval None
// */
//void Tims_delay_us(uint16_t nus)
//{
// __HAL_TIM_SET_COUNTER(DLY_TIM_Handle, 0);
// __HAL_TIM_ENABLE(DLY_TIM_Handle);
// while (__HAL_TIM_GET_COUNTER(DLY_TIM_Handle) < nus)
// {
// }
// __HAL_TIM_DISABLE(DLY_TIM_Handle);
//}
/**
* @brief ???? us , ??? 72M ?????
* @param us: <= 4294967295
* @retval None
*/
void Coarse_delay_us(uint32_t us)
{
uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
while (delay--)
{
;
}
}
6.3、BEEP报警显示模块代码
BEEP.H代码:
#ifndef __BEEP_H
#define __BEEP_H
#include "main.h"
void alarm();
#endif
BEEP.C代码:
#include "BEEP.h"
#include "oled.h"
#include "gpio.h"
#include "dht11.h"
void alarm()
{
OLED_ShowCN_STR(0,0,0,5);
if(DHT11_READ_DATA() < 10)
{
OLED_ShowCN_STR(90,0,0,1);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
}
else if(DHT11_READ_DATA() >= 10)
{
OLED_ShowCN_STR(90,0,1,1);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
}
}
6.4、TIM定时重写us级
tim.c代码重写:配置自己的us定时器
/* USER CODE BEGIN 1 */
void Tims_delay_us(uint16_t nus)
{
__HAL_TIM_SET_COUNTER(DLY_TIM_Handle, 0);
__HAL_TIM_ENABLE(DLY_TIM_Handle);
while (__HAL_TIM_GET_COUNTER(DLY_TIM_Handle) < nus)
{
}
__HAL_TIM_DISABLE(DLY_TIM_Handle);
}
/* USER CODE END 1 */
6.5、OLED代码
OLED.C代码:
6.6、main函数代码:
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM1_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
OLED_CLS();
HAL_Delay(1000);
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 */
//修正前显示
DHT11();
Hcsr04Start();
OLED_ShowCN_STR(0,4,8,3);
OLED_ShowStr(48,4,":",2);
OLED_Showdecimal(55,4,Hcsr04Read(),3,2,16);
OLED_ShowStr(100,4,"cm",2);
//警报部分
alarm();
}
七、实验效果
超声波测距仪(含温度补偿)——报警
八、总结
通过实验可以发现,经过温度补偿后的超声波测量距离与未进行补偿的超声波测量距离还是存在明显差距的。笔者进一步加大了超声波测量的距离,发现测量距离为500cm时,两者的距离误差在70cm左右(考虑到笔者新版HC-SR04的测量极限距离为600cm)。故此,在条件允许的情况下,建议读者朋友可以将温度对声速的影响考虑进去。
补充:目前市面上有可以进行水下测距的超声波模块,注意编程的时候,声速要变换成水下声速1500m/s的基数。
源码地址:链接:https://pan.baidu.com/s/1JIxJ63ZVBrYuZUkkW2oBrA 提取码:m7mk
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)