基于STM32和HC-SR04模块实现超声波测距功能
最近在学STM32做个简单的应用实践一下,顺便水一篇文章。
本文用的单片机是STM32F103C8T6,超声波测距模块是HC-SR04,显示测距结果用的是0.96寸OLED屏模块。
效果展示
下图中小于10cm时的显示结果有点问题,代码已修复并更新
修复后的结果:
视频演示:https://www.bilibili.com/video/BV1Sg411Z7ex/
基于STM32和HC-SR04模块实现超声波测距功能
HC-SR04硬件概述
HC-SR04超声波距离传感器的核心是两个超声波传感器。一个用作发射器,将电信号转换为40 KHz超声波脉冲。接收器监听发射的脉冲。如果接收到它们,它将产生一个输出脉冲,其宽度可用于确定脉冲传播的距离。就是如此简单!
该传感器体积小,易于在任何机器人项目中使用,并提供2厘米至400厘米(约1英寸至13英尺)之间出色的非接触范围检测,精度为3mm。
Operating Voltage工作电压 | 直流5V |
---|---|
Operating Current工作电流 | 15毫安 |
Operating Frequency运行频率 | 40K赫兹 |
Max Range最大范围 | 4m |
Min Range最小范围 | 2厘米 |
Ranging Accuracy测距精度 | 3毫米 |
Measuring Angle测量角度 | 15度 |
Trigger Input Signal触发输入信号 | 10µS TTL脉冲 |
Dimension尺寸 | 45 x 20 x 15毫米 |
HC-SR04超声波传感器引脚
让我们看一下它的引脚排列。
VCC 是HC-SR04超声波距离传感器的电源,我们连接了5V的供电。
Trig (Trigger) 引脚用于触发超声波脉冲,下面例程中用的GPIOB5,所以连接STM32的GPIOB5。
Echo 回声当接收到反射信号时,引脚产生一个脉冲。脉冲的长度与检测发射信号所需的时间成正比,下面例程中用的GPIOB6,所以连接STM32的GPIOB6。
GND 应该连接到STM32的地。
HC-SR0如何工作?
当持续时间至少为10 µS(10微秒)的脉冲施加到触发引脚时,一切就开始了。响应于此,传感器以40 KHz发射八个脉冲的声音脉冲。这种8脉冲模式使设备的“超声特征”变得独一无二,从而使接收器能够将发射模式与环境超声噪声区分开。
八个超声波脉冲通过空气传播,远离发射器。同时,回声引脚变为高电平,开始形成回声信号的开始。
如果这些脉冲没有被反射回来,则回波信号将在38毫秒(38毫秒)后超时并返回低电平。因此38 ms的脉冲表示在传感器范围内没有阻塞。
如果这些脉冲被反射回去,则在收到信号后,Echo引脚就会变低。这会产生一个脉冲,其宽度在150 µS至25 mS之间变化,具体取决于接收信号所花费时间。
HC-SR04的时序图如下:
然后,将接收到的脉冲的宽度用于计算到反射物体的距离。这可以通过我们在初中学到的简单的距离-速度-时间方程来解决。
距离=速度x时间
接线
将HC-SR04和0.96寸OLED屏连接到STM32。
HC-SR04 | STM32 |
---|---|
VCC | 5V |
Trig | GPIO PB5 |
Echo | GPIO PB6 |
Gnd | Gnd |
OLED | STM32 |
---|---|
VCC | 3.3V |
GND | GND |
SCL | GPIO PB12 |
SDA | GPIO PB13 |
温度对距离测量的影响
尽管HC-SR04对于我们的大多数项目来说都相当准确,例如入侵者检测或接近警报;但是有时候您可能想设计一种要在户外或在异常炎热或寒冷的环境中使用的设备。在这种情况下,您可能要考虑到空气中的声速随温度,气压和湿度而变化的事实。
由于声音因素进入HC-SR04距离计算的速度,因此可能会影响我们的读数。如果已知温度(°C)和湿度,请考虑以下公式:
声速 m/s = 331.4 +(0.606 * 温度)+(0.0124 * 湿度)
购买地址
本文所用到的模块购买地址如下:
STM32F103C8T6开发板:https://s.click.taobao.com/8SoQMVu
ST-LINK V2仿真器:https://s.click.taobao.com/FEuPMVu
HC-SR04模块:https://s.click.taobao.com/Ing88Vu
0.96寸OLED模块:https://s.click.taobao.com/o4fPMVu
面包板:https://s.click.taobao.com/dBjPMVu
面包板专用跳线:https://s.click.taobao.com/7eG88Vu
程序
我是用的的ST标准库写的程序,文章中放出主要的程序,完整的工程文件请点下面链接下载。
完整工程文件下载:https://url.zeruns.tech/HCSR04
提取码:d9xr
main.c
#include "stm32f10x.h" // Device header#include "Delay.h"#include "OLED.h"#include "Timer.h"#include "HCSR04.h"uint64_t numlen(uint64_t num)//计算数字的长度{ uint64_t len = 1; // 初始长度为1 for(; num > 9; ++len) // 判断num是否大于9,否则长度+1 num /= 10; // 使用除法进行运算,直到num小于1 return len; // 返回长度的值}int main(void){OLED_Init();//初始化OLED屏Timer_Init();//初始化定时器HC_SR04_Init();//初始化超声波测距模块OLED_ShowString(1, 1, "Distance:");//OLED屏输出字符串while (1){int Distance_mm=sonar_mm();//获取距离测量结果,单位毫米(mm)int Distance_m=Distance_mm/1000;//转换为米(m)为单位,将整数部分放入Distance_mint Distance_m_p=Distance_mm%1000;//转换为米(m)为单位,将小数部分放入Distance_m_pOLED_Clear_Part(2,1,16);//将OLDE屏第2行清屏OLED_ShowNum(2, 1,Distance_m,numlen(Distance_m));//显示测量结果的整数部分OLED_ShowChar(2, 1+numlen(Distance_m), '.');//显示小数点if(Distance_m_p<100){//判断是否小于100毫米OLED_ShowChar(2, 1+numlen(Distance_m)+1,'0');//因为单位是米,所以小于10cm时要加0OLED_ShowNum(2, 1+numlen(Distance_m)+2,Distance_m_p,numlen(Distance_m_p));//显示测量结果的小数部分OLED_ShowChar(2, 1+numlen(Distance_m)+2+numlen(Distance_m_p), 'm');//显示单位}else// https://blog.zeruns.tech{OLED_ShowNum(2, 1+numlen(Distance_m)+1,Distance_m_p,numlen(Distance_m_p));//显示测量结果的小数部分OLED_ShowChar(2, 1+numlen(Distance_m)+1+numlen(Distance_m_p), 'm');//显示单位}OLED_Clear_Part(3,1,16);//将OLDE屏第3行清屏OLED_ShowNum(3, 1,Distance_mm,numlen(Distance_mm));//显示单位为毫米的距离结果OLED_ShowString(3, 1 + numlen(Distance_mm), "mm");Delay_ms(300);//延时300毫秒}}
Timer.c
#include "stm32f10x.h" // Device header//blog.zeruns.techvoid Timer_Init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//启用TIM3时钟TIM_InternalClockConfig(TIM3);//设置TIM3使用内部时钟TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//定义结构体,配置定时器TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//设置1分频(不分频)TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//设置计数模式为向上计数TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//设置最大计数值,达到最大值触发更新事件,因为从0开始计数,所以计数10次是10-1,每10微秒触发一次TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//设置时钟预分频,72-1就是每 时钟频率(72Mhz)/72=1000000 个时钟周期计数器加1,每1微秒+1TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器(高级定时器才有,所以设置0)TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);//初始化TIM3定时器TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除更新中断标志位TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//开启更新中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组NVIC_InitTypeDef NVIC_InitStructure;//定义结构体,配置中断优先级NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;//指定中断通道NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//中断使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//设置抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//设置响应优先级NVIC_Init(&NVIC_InitStructure);// https://blog.zeruns.techTIM_Cmd(TIM3, ENABLE);//开启定时器}/*void TIM3_IRQHandler(void)//更新中断函数{if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)//获取TIM3定时器的更新中断标志位{TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//清除更新中断标志位}}*/
Timer.h
#ifndef __TIMER_H#define __TIMER_Hvoid Timer_Init(void);#endif
HCSR04.c
#include "stm32f10x.h"#include "Delay.h"/*我的博客:blog.zeruns.tech具体使用说明请到我博客看*/#define Echo GPIO_Pin_6//HC-SR04模块的Echo脚接GPIOB6#define Trig GPIO_Pin_5//HC-SR04模块的Trig脚接GPIOB5uint64_t time=0;//声明变量,用来计时uint64_t time_end=0;//声明变量,存储回波信号时间void HC_SR04_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//启用GPIOB的外设时钟GPIO_InitTypeDef GPIO_InitStructure;//定义结构体GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//设置GPIO口为推挽输出GPIO_InitStructure.GPIO_Pin = Trig;//设置GPIO口5GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置GPIO口速度50MhzGPIO_Init(GPIOB,&GPIO_InitStructure);//初始化GPIOBGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//设置GPIO口为下拉输入模式GPIO_InitStructure.GPIO_Pin = Echo;//设置GPIO口6GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化GPIOBGPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//输出低电平Delay_us(15);//延时15微秒}int16_t sonar_mm(void)//测距并返回单位为毫米的距离结果{uint32_t Distance,Distance_mm = 0;GPIO_WriteBit(GPIOB,Trig,1);//输出高电平Delay_us(15);//延时15微秒GPIO_WriteBit(GPIOB,Trig,0);//输出低电平while(GPIO_ReadInputDataBit(GPIOB,Echo)==0);//等待低电平结束time=0;//计时清零while(GPIO_ReadInputDataBit(GPIOB,Echo)==1);//等待高电平结束time_end=time;//记录结束时的时间if(time_end/100<38)//判断是否小于38毫秒,大于38毫秒的就是超时,直接调到下面返回0{Distance=(time_end*346)/2;//计算距离,25°C空气中的音速为346m/sDistance_mm=Distance/100;//因为上面的time_end的单位是10微秒,所以要得出单位为毫米的距离结果,还得除以100}return Distance_mm;//返回测距结果}float sonar(void)//测距并返回单位为米的距离结果{uint32_t Distance,Distance_mm = 0;float Distance_m=0;GPIO_WriteBit(GPIOB,Trig,1);//输出高电平Delay_us(15);GPIO_WriteBit(GPIOB,Trig,0);//输出低电平while(GPIO_ReadInputDataBit(GPIOB,Echo)==0);time=0;while(GPIO_ReadInputDataBit(GPIOB,Echo)==1);time_end=time;if(time_end/100<38){Distance=(time_end*346)/2;Distance_mm=Distance/100;Distance_m=Distance_mm/1000;}return Distance_m;}void TIM3_IRQHandler(void)//更新中断函数,用来计时,每10微秒变量time加1{if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)//获取TIM3定时器的更新中断标志位{time++;TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//清除更新中断标志位}}
HCSR04.h
#ifndef __HCSR04_H#define __HCSR04_Hvoid HC_SR04_Init(void);int16_t sonar_mm(void);float sonar(void);#endif