基于STM32的HC-SR04超声波模块测距(新手级)

目录

预期实现功能

硬件设备

软件设备

一、了解HC-SR04超声波模块的工作原理

二、功能代码实现

三、结果展示

四、总结


        本人是嵌入式新手,学习了STM32的部分内容,最近想找些项目做,然后顺便学习了一下HC-SR04超声波模块,学习过程中也参考了一些大佬的文章。本文记录了自己的学习方法和学习过程,同时也希望能和各位大佬交流讨论。

部分代码参考来源 :

 HC-SR04超声波测距模块使用方法和例程(STM32)

江协科技STM32入门教程-2023版 细致讲解 中文字幕

预期实现功能

在开始学习一样东西之前,我们肯定得先知道它能用来干什么,而超声波模块用来测距就是最常用的功能,所以我用他实现测距的功能,并把结果显示在OLED屏上。

硬件设备

在确定我们的目标之后,我们就要准备好要用到的设备了:

STM32F103C8T6开发板、0.96寸OLED显示屏、HC-SR04超声波模块、ST-LINK V2仿真器、面包板、杜邦线。除了超声波模块,其他都是STM32的入门套件,所以本文主要对 HC-SR04超声波模块和定时器的设置部分进行展开。

软件设备

keil5 MDK


在做好以上准备工作后,我们就开始进入正戏了!

一、了解HC-SR04超声波模块的工作原理

学习模块的第一步就是先了解它的工作原理,否则无从下手。

我先查看了它的使用手册。(以下我只截取了重要的部分)

于是我总结了四个端口的作用

VCC: 这里接+5V电源。 

Trig(控制端):使用软件发送一个至少10us的高电平的触发信号,发送完毕后,模块内部会自动发出8个40KHz的脉冲。

Echo(接收端):模块内部的超声波发送后,模块的Echo端向IO口输出高电平,直到超声波反射回来后置为低电平。也就是说可以用这个高电平持续的时间计算距离。但是如果这些脉冲没有被反射回来,则输出回响信号将在38毫秒后超时并返回低电平。因此38ms的脉冲也对应着超声波模块能测量的最大距离。

GND:接地就行。

 二、功能代码实现

HCSR04.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#define Trig  GPIO_Pin_2  
#define Echo  GPIO_Pin_3

uint32_t time=0;			//声明变量,用来计时
uint32_t time_end=0;		//声明变量,存储回波信号时间

void HCSR04_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式
	GPIO_InitStructure.GPIO_Pin = Trig; // 选中A2口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	//初始化GPIOA				
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入模式
	GPIO_InitStructure.GPIO_Pin = Echo; // 选中A3口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA	
}


/**
  * 函    数:超声波测距
  * 参    数:无
  * 返 回 值:测量的距离。单位:mm
  */
int16_t Get_Distance(void)
{
	uint32_t Distance,Distance_mm = 0;
	
	GPIO_SetBits(GPIOA, Trig);  // 触发信号
	Delay_us(15); // 高电平至少持续10us以上
	GPIO_ResetBits(GPIOA, Trig); 
	
	while(GPIO_ReadInputDataBit(GPIOA,Echo)==0);	//等待低电平结束
	time=0;											//计时清零
	while(GPIO_ReadInputDataBit(GPIOA,Echo)==1);	//等待高电平结束
	time_end=time;									//记录结束时的时间
	
	if(time_end/100<38)	//判断是否小于38毫秒,大于38毫秒的就是超时,直接调到下面返回0
	{
		Distance=(time_end*340)/2;	//计算距离的公式
		Distance_mm=Distance/100;	//因为上面的time_end的单位是10微秒,所以要得出单位为毫米的距离结果,还得除以100
	}
	return Distance_mm;	 //返回测距结果
}

void TIM3_IRQHandler(void)	//更新中断函数,用来计时,每10微秒变量time加1
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)		//获取TIM3定时器的更新中断标志位
	{
		time++;
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);			//清除更新中断标志位
	}
}

Timer.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	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,加够1000000次的时间就是1秒
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到,所以设置0
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);	 //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);						//清除定时器更新标志位
	//TIM_TimeBaseInit函数末尾,手动产生了更新事件
	//若不清除此标志位,则开启中断后,会立刻进入一次中断
	//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "HCSR04.h"

/**
  * 函    数:超声波测距
  * 参    数:无
  * 返 回 值:测量的距离。单位:mm
  */
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();
	Timer_Init();
	HCSR04_Init();
	
	OLED_ShowString(1,1,"distance:");
	
	while(1)
	{
		int distance = Get_Distance();
		OLED_Clear_Part(2,1,16);			//将OLDE屏第2行清屏
		OLED_ShowNum(2, 1,distance,numlen(distance));		//显示单位为毫米的距离结果
		OLED_ShowString(2, 1 + numlen(distance), "mm");
		Delay_ms(200);						//延时200毫秒,即OLED显示屏的更新时间。至少延时60毫秒,防止发射信号对回响信号造成影响
	}
		
}

本代码只显示了以毫米为单位的测量距离,有兴趣的同志可以自行转化为其他长度单位。

三、结果展示

误差有点大,但还算过的去。。。

四、总结

       总体上来说,超声波模块真不算太难,不过我也用了挺长时间弄懂,还算是很有收获的。这也是我发的第一篇文章,谈不上教学,只是分享了我学习过程,有很多不足之处欢迎大佬们指出,以后我也会不定时更新一些模块的学习,希望能和大家一起进步!

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值