最近在学STM32做个简单的应用实践一下,顺便水一篇文章。
本文用的单片机是STM32F103C8T6,超声波测距模块是HC-SR04,显示测距结果用的是0.96寸OLED屏模块。代码是标准库的。
电子/单片机技术交流群:820537762
效果展示
下图中小于10cm时的显示结果有点问题,代码已修复并更新
修复后的结果:
视频演示:https://www.bilibili.com/video/BV1Sg411Z7ex/
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.com/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_m
int Distance_m_p=Distance_mm%1000; //转换为米(m)为单位,将小数部分放入Distance_m_p
OLED_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时要加0
OLED_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.com
{
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.com
void 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微秒+1
TIM_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.com
TIM_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_H
void Timer_Init(void);
#endif
HCSR04.c
#include "stm32f10x.h"
#include "Delay.h"
/*
我的博客:blog.zeruns.com
具体使用说明请到我博客看
*/
#define Echo GPIO_Pin_6 //HC-SR04模块的Echo脚接GPIOB6
#define Trig GPIO_Pin_5 //HC-SR04模块的Trig脚接GPIOB5
uint64_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口5
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置GPIO口速度50Mhz
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //设置GPIO口为下拉输入模式
GPIO_InitStructure.GPIO_Pin = Echo; //设置GPIO口6
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB
GPIO_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/s
Distance_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_H
void HC_SR04_Init(void);
int16_t sonar_mm(void);
float sonar(void);
#endif
推荐阅读
- 高性价比和便宜的VPS/云服务器推荐:https://blog.zeruns.com/archives/383.html
- ESP8266开发环境搭建及项目演示:https://blog.zeruns.com/archives/526.html
- Arduino读取DHT11,DHT22,SHTC3温湿度数据:https://blog.zeruns.com/archives/527.html
- 学生专属优惠权益大全,教育优惠:https://blog.zeruns.com/archives/557.html
- 怎样搭建个人博客:https://blog.zeruns.com/archives/218.html
- 使用NPS搭建内网穿透服务器,带Web面板:https://blog.zeruns.com/archives/660.html
31 条评论
想问下oledscl和sca引脚为什么接到PB12和PB13上,用iic不应该接到PB6和PB7上吗?
用的软件I2C,接任何IO都可以
感谢
我想请问一下为什么是38ms呀,明明25ms以后就都超出了测距范围了
是因为考虑到不同温度湿度下对声速的影响嘛? ,取可测范围内的最低声速算出在可测范围内的最长时间嘛?
感谢分享。
我打开有五个报错
Hardware\HCSR04.c(26): warning: #188-D: enumerated type mixed with another type
哥,我用你的代码,但是那个距离显示就一直是在0和1循环,不能测距离
我也是,有解决办法了吗?
博主您好,我用您的代码做避障小车。用pwm占空比调节车速,我一旦把占空设置高了距离测量就会卡到10mm左右。设置低一些就没影响,请问这个怎么解决
为啥我的数据一直在0.08与0.10之间跳动
我也是的,一直都是0.01和0.08之间跳动,用的型号也是c8t6,太离谱了
不改板子的话有什么办法解决吗
哥们,换个新版的SR04,19年以后的那种,应该可以的
我也是这个情况,有什么办法解决不
哥们,不需要的,问题就是HC-SR04有新版和老版,用新版的应该就可以的,没记错的话是19年以后出的那种,SR04+,带一个+号。一直想回答你,但是进不去博主网页,挂梯子才进来。
不知道,你是不是改了代码,或者接错线了
我知道了 有新版的和老版的 新版的可以了
因为在RCT6用的时候倒是没问题,应该不是SR04的问题。我再找找问题吧,感谢
不同型号的单片机,主频,定时器,等等参数不一样,用同一个程序就会出问题,换型号了就根据实际情况改一下程序
代码:直接用的您的代码,代码没动。
接线:既然OLED能够正常显示那不就说明OLED没接错;SR04部分VCC接的5V,trigPB5,echoPB6
那就奇怪了,我代码测试过肯定是没问题的,可能你硬件有问题吧
oled没有距离返回,是什么原因
程序改过没,没改过就可能是你接线有问题
感谢分享,赞一个
物联网专业毕业的我表示全还给老师了
哈哈