STM32——通用定时器控制超声波传感器HCSR04

HCSR-04介绍

HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测
距精度可达高到的非接触式距离感测功能,测距精度可达高到 3mm ;模块包括超声波发射器、接收器与控制电路。
网上资料都说这个模块精度很高,但是我实际使用确感觉很一般,我用89C52和STM32F103C8T6分别写过测距的程序,但是能精确测量的范围都在100cm以内,具体原因没有细究。
STM32——通用定时器控制超声波传感器HCSR04_第1张图片

工作原理

STM32——通用定时器控制超声波传感器HCSR04_第2张图片
使用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。
还有很多博客没写。。
如果有小伙伴知道为什么我的程序精度不高欢迎在评论区告知我(✪ω✪)

你可能感兴趣的:(STM32,stm32,单片机,arm)