目录
本人是嵌入式新手,学习了STM32的部分内容,最近想找些项目做,然后顺便学习了一下HC-SR04超声波模块,学习过程中也参考了一些大佬的文章。本文记录了自己的学习方法和学习过程,同时也希望能和各位大佬交流讨论。
部分代码参考来源 :
预期实现功能
在开始学习一样东西之前,我们肯定得先知道它能用来干什么,而超声波模块用来测距就是最常用的功能,所以我用他实现测距的功能,并把结果显示在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毫秒,防止发射信号对回响信号造成影响
}
}
本代码只显示了以毫米为单位的测量距离,有兴趣的同志可以自行转化为其他长度单位。
三、结果展示
误差有点大,但还算过的去。。。
四、总结
总体上来说,超声波模块真不算太难,不过我也用了挺长时间弄懂,还算是很有收获的。这也是我发的第一篇文章,谈不上教学,只是分享了我学习过程,有很多不足之处欢迎大佬们指出,以后我也会不定时更新一些模块的学习,希望能和大家一起进步!