HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测
距精度可达高到的非接触式距离感测功能,测距精度可达高到 3mm ;模块包括超声波发射器、接收器与控制电路。
网上资料都说这个模块精度很高,但是我实际使用确感觉很一般,我用89C52和STM32F103C8T6分别写过测距的程序,但是能精确测量的范围都在100cm以内,具体原因没有细究。
使用IO口触发,先由Trig口置高电平至少10us,模块内部就会发射8个40KHz的脉冲方波,之后Trig置低电平,由Echo接收方波,再根据回响电平持续的时间计算距离,原理非常简单。
只需要将一个IO口配置推挽输出作为Trig,一个IO口配置下拉输入作为Echo,因为当Echo收到回响信号会由低电平置高电平,因此默认设置Echo为低电平。距离= 高电平时间*声速(340M/S)/2;
先用SysTick软件延时控制Trig发出高电平,之后等待回响信号,当Echo收到信号,即开启定时器,但是我们这个程序是在定时器中运行的,利用同一个定时器TIM2,在收到信号后要先把定时器清零。之后得到电平持续时间,计算出距离后再次把定时器清零,并且开启定时器,下次定时器中断方能正常触发。
#include "HCSR04.h"
#include "stm32f10x.h" // Device header
#include "Usart.h"
#include "Buzzer.h"
int HCSR04_Distance1;
int HCSR04_Distance2;
#define RCC_HCSR04 RCC_APB2Periph_GPIOB
#define GPIO_HCSR04_Trig GPIOB
#define PIN_HCSR04_Trig1 GPIO_Pin_8
#define PIN_HCSR04_Trig2 GPIO_Pin_5
#define GPIO_HCSR04_Echo GPIOB
#define PIN_HCSR04_Echo1 GPIO_Pin_9
#define PIN_HCSR04_Echo2 GPIO_Pin_6
void HCSR04_Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清零
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
void HCSR04_Delay_ms(uint32_t xms)
{
while(xms--)
{
HCSR04_Delay_us(1000);
}
}
这里因为需要,我设置了两个超声波传感器。
首先宏定义引脚,再定义两个距离变量,以及初始化Systick软件延时函数。
void GPIO_HCSR04Init()
{
RCC_APB2PeriphClockCmd(RCC_HCSR04, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = PIN_HCSR04_Trig1 | PIN_HCSR04_Trig2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIO_HCSR04_Trig,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIN_HCSR04_Echo1 | PIN_HCSR04_Echo2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIO_HCSR04_Echo,&GPIO_InitStructure);
}
分别初始化两种功能的引脚,一种是推挽输出,一种是下拉输入。
void TIM_HCSR04Init()
{
//通用定时器2 3 4,APB1
//TIM2的通道1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);//选用内部时钟
TIM_TimeBaseInitTypeDef timBaseInit;
TIM_TimeBaseStructInit(&timBaseInit);
timBaseInit.TIM_ClockDivision = TIM_CKD_DIV1;
timBaseInit.TIM_CounterMode = TIM_CounterMode_Up;
timBaseInit.TIM_RepetitionCounter = 0;
timBaseInit.TIM_Period = 10000 - 1;
timBaseInit.TIM_Prescaler = 7200 - 1;
//7200分频,记10000次就是1秒
TIM_TimeBaseInit(TIM2,&timBaseInit);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//手动清除更新中断标志位,避免初始化后自动进入中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能TIM2更新中断
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef nvic_struct;
nvic_struct.NVIC_IRQChannel = TIM2_IRQn;
nvic_struct.NVIC_IRQChannelCmd = ENABLE;
nvic_struct.NVIC_IRQChannelPreemptionPriority = 1;
nvic_struct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&nvic_struct);
TIM_Cmd(TIM2,ENABLE);
}
先使能定时器,之后配置时基单元,将72MHz7200分频,得到计数器每加一就是1/10000s,记10000次就是一秒。之后配置NVIC通道,开启定时器中断,优先级随便选。
void StartMode1()
{
GPIO_ResetBits(GPIO_HCSR04_Trig,PIN_HCSR04_Trig1);//预先拉低
GPIO_SetBits(GPIO_HCSR04_Trig,PIN_HCSR04_Trig1);
HCSR04_Delay_us(20);
GPIO_ResetBits(GPIO_HCSR04_Trig,PIN_HCSR04_Trig1);//OK
}
void StartMode2()
{
GPIO_ResetBits(GPIO_HCSR04_Trig,PIN_HCSR04_Trig2);//预先拉低
GPIO_SetBits(GPIO_HCSR04_Trig,PIN_HCSR04_Trig2);
HCSR04_Delay_us(20);
GPIO_ResetBits(GPIO_HCSR04_Trig,PIN_HCSR04_Trig2);//OK
}
这里是Trig触发函数
void HCSR04Init()
{
GPIO_HCSR04Init();
TIM_HCSR04Init();
}
//集成一个函数,用来main函数初始化用
void HCSR04_GetDistance1()
{
unsigned int time = 0;
TIM_SetCounter(TIM2,0x0000);
StartMode1();
while(GPIO_ReadInputDataBit(GPIO_HCSR04_Echo , PIN_HCSR04_Echo1 ) == 0);
TIM_Cmd(TIM2,ENABLE); //Usart_SendStr("TIM2 ON ");
while(GPIO_ReadInputDataBit(GPIO_HCSR04_Echo , PIN_HCSR04_Echo1 ) == 1);
TIM_Cmd(TIM2,DISABLE); //Usart_SendStr("TIM2 OFF ");
//单位cm
//v = 340m/s = 34000cm/s = 34000cm/10^6us = 0.034cm/us
//s = vt/2
time = TIM_GetCounter(TIM2);
HCSR04_Distance1 = (int)time * 340 / 100 / 2;//cm
TIM_SetCounter(TIM2,0x0000);
}
void HCSR04_GetDistance2()
{
unsigned int time = 0;
TIM_SetCounter(TIM2,0x0000);
StartMode2();
while(GPIO_ReadInputDataBit(GPIO_HCSR04_Echo , PIN_HCSR04_Echo2 ) == 0);
TIM_Cmd(TIM2,ENABLE); //Usart_SendStr("TIM2 ON ");
while(GPIO_ReadInputDataBit(GPIO_HCSR04_Echo , PIN_HCSR04_Echo2 ) == 1);
TIM_Cmd(TIM2,DISABLE); //Usart_SendStr("TIM2 OFF ");
//单位cm
//v = 340m/s = 34000cm/s = 34000cm/10^6us = 0.034cm/us
//s = vt/2
time = TIM_GetCounter(TIM2);
HCSR04_Distance2 = (int)time * 340 / 100 / 2;//cm
TIM_SetCounter(TIM2,0x0000);
}
这里是计算距离的函数,这里有一个点要特别注意,不要在计算距离的代码中掺杂有软件延时时长的操作,例如串口打印,这个bug我找了好久,Echo一直收不到回响信号,因此我企图用串口打印来找到程序究竟卡在哪里,就如程序中的TIM2 ON 和TIM2 OFF那样,结果就只有TIM2 ON,运行不到OFF处。之后我一次性把打印都注释掉,只留下打印距离的,结果就成功了。。。因为这个bug我找了好几个小时。。。o(╥﹏╥)o
void TIM2_IRQHandler()
{
static int LightADTime = 0;//控制LightAD检测的间隔
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == 1)
{
LightADTime++;
if(LightADTime == 10)//
{
//这里是配合光敏传感器AD转换和看门狗中断的程序,后面会再补充
LightADTime = 0;
}
TIM_SetCounter(TIM2,0x0000);
HCSR04_GetDistance1();
HCSR04_GetDistance2();
if((HCSR04_Distance1 <= 20 || HCSR04_Distance1 >= 1000) )
{
BuzzerOn();
}
else if((HCSR04_Distance2 <= 80 || HCSR04_Distance2 >= 1000) )
{
BuzzerOn();
}
else BuzzerOff();
TIM_Cmd(TIM2,ENABLE);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
定时器中断函数,记得要先判断是不是进入的这条定时器中断通道,如果是,在中断函数执行之后要把标志位软件置零,不然会一直循环,出不去。
BuzzerOn是控制有源蜂鸣器的函数,蜂鸣器是低电平触发。
要注意的点就是不要在计算距离的时候执行有一定时长的操作,否则会一直收不到回响信号,甚至浪费你几个小时时间找bug。
还有很多博客没写。。
如果有小伙伴知道为什么我的程序精度不高欢迎在评论区告知我(✪ω✪)