基于STM32的超声波测距(外部中断+定时器)

基于STM32的超声波测距(外部中断+定时器)

首先说明一下我使用的硬件:
stm32f103c8t6最小系统、0.96寸OLED、超声波模块HC-SR04。

再就是程序设计的一个思路:
超声波模块的使用说明已经指出,给TRIG引脚一个不少于10微秒的高电平,模块自动发送8个40KHz的方波,接收到返回信号后引脚ECHO会输出高电平,其距离为ECHO引脚高电平时间*声速/2,每次测量间隔建议60ms以上。由此,我们可提取到需要使用到的三个芯片功能:1、定时器,2、外部中断,3、IO输出。

下面是配置代码,进行一一介绍(不想看我哔哔的最下方有完整得超声波配置.c和.h +_+ ):
1、外部中断和IO输出都是使用的GPIO所以使用一个配置函数
1.1 GPIO宏定义参数:

//超声波模块引脚配置
#define ULTRASOUND_GPIO 						GPIOA
#define ULTRASOUND_GPIO_CLK 				RCC_APB2Periph_GPIOA
#define ULTRASOUND_TRIG 						GPIO_Pin_1
#define ULTRASOUND_ECHO 						GPIO_Pin_0

//超声波模块ECHO中断配置
#define ULTRASOUND_GPIOSourceGPIO   GPIO_PortSourceGPIOA
#define ULTRASOUND_PINSOURCE        GPIO_PinSource0
#define ULTRASOUND_EXTI_LINE				EXTI_Line0
#define ULTRASOUND_EXTI_IRQHandler  EXTI0_IRQHandler	//ECHO引脚中断服务函数

1.2 GPIO端口配置函数Ultrasound_GPIO_Conf():
这里要注意,中断的触发方式为上下边沿,用处在中断服务函数里面体现。

static void Ultrasound_GPIO_Conf(void)//端口配置
{
	EXTI_InitTypeDef EXTI_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(ULTRASOUND_GPIO_CLK | RCC_APB2Periph_AFIO,ENABLE);//打开GPIOA和端口复用时钟
	
	GPIO_EXTILineConfig(ULTRASOUND_GPIOSourceGPIO,ULTRASOUND_PINSOURCE);//配置中断线A-0
	
	GPIO_InitStructure.GPIO_Pin = ULTRASOUND_ECHO;//ECHO端口配置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//输入下拉
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(ULTRASOUND_GPIO,&GPIO_InitStructure);
	GPIO_ResetBits(ULTRASOUND_GPIO, ULTRASOUND_ECHO);//拉低ECHO
	
	GPIO_InitStructure.GPIO_Pin = ULTRASOUND_TRIG; //TRIG端口配置
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//复用推挽输出
  GPIO_Init(ULTRASOUND_GPIO, &GPIO_InitStructure);//初始化GPIOA.9
	
	EXTI_InitStructure.EXTI_Line = ULTRASOUND_EXTI_LINE; //配置中断线A-0
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//上下边沿触发
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //配置中断A-0
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}

2、定时器配置(定时器在这里比较忙,它要进行返回信号高电平的计时,和测距间隔时间计时,所以要配置中断)
2.1 定时器宏定义参数:

//超声波模块定时器配置
#define ULTRASOUND_TIM 							TIM2
#define ULTRASOUND_TIM_CLK 					RCC_APB1Periph_TIM2
#define ULTRASOUND_TIM_IRQ      		TIM2_IRQn
#define ULTRASOUND_TIM_IRQHandler		TIM2_IRQHandler	//TIM2中断服务函数

2.2 定时器配置函数Ultrasound_TIM_Conf():
这里值得注意的是,在配置中断的时候必须要清除中断标志位,不然程序会死在NVIC_Init函数里面

static void Ultrasound_TIM_Conf(void)//定时器配置
{
	uint16_t PrescalerValue;//分频系数
	NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;

	RCC_APB1PeriphClockCmd(ULTRASOUND_TIM_CLK,ENABLE);//打开ULTRASOUND_TIM时钟
	
	PrescalerValue = (uint16_t)((SystemCoreClock / 2)  / 1000000) - 1;//计算分频系数(设定为:每秒计数100万次,每计一次为1us)
	TIM_TimeBaseStruct.TIM_Period = 0xFDE8;//重载寄存器的值,定时周期
	TIM_TimeBaseStruct.TIM_Prescaler = PrescalerValue;//预分频
	TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;//时钟切割
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
	TIM_TimeBaseInit(ULTRASOUND_TIM,&TIM_TimeBaseStruct);//初始化ULTRASOUND_TIM
	
	TIM_ClearITPendingBit(ULTRASOUND_TIM,TIM_IT_Update);//清除中断标志位,配置时必须加上
	TIM_ITConfig(ULTRASOUND_TIM, TIM_IT_Update, ENABLE);//事件更新中断配置
	
	NVIC_InitStructure.NVIC_IRQChannel = ULTRASOUND_TIM_IRQ; //配置ULTRASOUND_TIM中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	

	TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
}

3、超声波模块初始化函数(将上面写的两个函数调用一下下就好了)

void Ultrasound_Init(void)//超声波模块初始化
{
	Ultrasound_GPIO_Conf();//超声波控制端口配置
	Ultrasound_TIM_Conf();//超声波控制计数器配置
}

4、超声波启动函数
这里比较尴尬,使用定时器和中断的目的就是尽量没有延时,但这里出现了12us的延时(若小伙伴有好的方法,敬请出招)

void Ultrasound_start(void)//超声波模块启动
{
	GPIO_SetBits(ULTRASOUND_GPIO, ULTRASOUND_TRIG);//拉高TRIG
	
	ULTRASOUND_TIM->CNT = 0x0000;	//清空计数器计数寄存器值
	ULTRASOUND_TIM->ARR = 0xffff; //重载值重新设定
	TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
	while(ULTRASOUND_TIM->CNT != 12);//大于10us,此处等待12us,
	
	GPIO_ResetBits(ULTRASOUND_GPIO, ULTRASOUND_TRIG);//拉低TRIG
}

5、距离获取函数
这里比较花里胡哨,因为进行了一下数据筛选。ULTRASOUND_DIS_INDEX是一个宏定义,它是我设定的获取多少次数据,进行一次数据筛选。

float Ultrasound_GetDistance(void)//获取距离数据
{
	uint8_t i,j;//循环变量
	float distance_temp;//排序的临时变量
	for(i = 0;i < ULTRASOUND_DIS_INDEX - 1;i ++ )//降序排列,找出最大值和最小值
	{
		for(j = 0;j < ULTRASOUND_DIS_INDEX - 1;j ++)
		{
			if(ultrasound_data[j] < ultrasound_data[j + 1])
			{
				distance_temp = ultrasound_data[j + 1];
				ultrasound_data[j + 1] = ultrasound_data[j];
				ultrasound_data[j] = distance_temp;
			}
		}	
	}
				
	distance_temp = 0;//清零
		
	for(i = 2; i < ULTRASOUND_DIS_INDEX - 2;i ++)//去掉两个最大值和两个最小值
	{
		distance_temp += ultrasound_data[i];//获取的距离值累加
	}
	
	distance_temp /= (ULTRASOUND_DIS_INDEX - 4);//求平均值
	return distance_temp;
}

5、定时器中断服务函数
比较简单,调用一下超声波启动函数就好。

void ULTRASOUND_TIM_IRQHandler(void)//超声波所用TIM中断服务函数
{
	if(TIM_GetITStatus(ULTRASOUND_TIM,TIM_IT_Update) == SET)//判断是否有事件更新
	{
		Ultrasound_start();//启动超声波模块
	}
	TIM_ClearITPendingBit(ULTRASOUND_TIM,TIM_IT_Update);//清除中断标志位
}

5、外部中断服务函数(敲黑板,比较绕请细品)
这里总体说一说此程序比较理想的执行情况:

(1)超声波被调用初始化函数,定时器被设定为可中断,并且定时器开始计数;

(2)计数到溢出,触发定时器中断,它会调用超声波启动函数Ultrasound_start();

(3)超声波启动后,有回波信号,将触发外部中断(上边沿),进入外部中断直接关掉定时器中断和定时器,判断确实有回波信号,进入到(检测到返回信号)代码段,清空定时器的CNT寄存器,重设重载值ARR寄存器,开启定时器。此时,定时器进行ECHO引脚得高电平持续时间检测;

(4)再一次中断来临(下降沿),还是直接关掉定时器中断触发和定时器,这次进入(返回信号结束)代码段,首先判断定时器是不是计数的高电平时间。若是,就判断我的获取次数是否已满,满了就置位flag, 没有满,就将定时器CNT寄存器里面的值提出来参与距离公式运算得到当前所测距离。执行到最后继续开启定时器中断和定时器,并重新设定重载值。此时定时器进行间隔时间计数。所以,定时器计数到溢出中断后,又会去调用一次超声波启动函数Ultrasound_start();

(5)外部中断函数则在此等待下一次工作来临;

void ULTRASOUND_EXTI_IRQHandler(void)	//超声波ECHO引脚所用中断服务函数
{
	if(EXTI_GetITStatus(EXTI_Line0)!=0)	//判断是否真是触发中断
	{	
		TIM_ITConfig(ULTRASOUND_TIM, TIM_IT_Update, DISABLE);//关闭计数器中断
		TIM_Cmd(ULTRASOUND_TIM,DISABLE);	//关闭计数器
		if(GPIO_ReadInputDataBit(ULTRASOUND_GPIO, ULTRASOUND_ECHO) == 1)	//检测到返回信号
		{
			ULTRASOUND_TIM->CNT = 0x0000;	//清空计数器计数寄存器值
			ULTRASOUND_TIM->ARR = 0xffff;//重载值重设为计数最大值
			TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
		}
		
		if(GPIO_ReadInputDataBit(ULTRASOUND_GPIO, ULTRASOUND_ECHO) == 0 )	//返回信号结束
		{
			if(ULTRASOUND_TIM->ARR == 0xffff) //需要确定计数器当前是不是在计数返回信号时间
			{
				if(d_count == ULTRASOUND_DIS_INDEX) //判断获取数据的次数
				{
					d_count = 0;	//清空次数计数值
					ultrasound_flag = 1;	//置位超声波标志位
				}
				else
				{
					ultrasound_flag = 0;	//清空超声波标志位
					ultrasound_data[d_count] = (float)(ULTRASOUND_TIM->CNT*34000)/1000000/2;	//计算距离,单位cm。并将数据存入待处理数据数组
					d_count++;	//次数计数值自增
				}
			}
			ULTRASOUND_TIM->ARR = 0xFDE8;//重载值为计数64ms
			TIM_ITConfig(ULTRASOUND_TIM, TIM_IT_Update, ENABLE);//开启计数器中断
			TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
		}
		EXTI_ClearITPendingBit(EXTI_Line0);	//清除LINE上的中断标志位
	}
}

6、主函数

#include "common.h"

char display_buff[100] = {0};
int main()
{
	SysTickInit();//系统定时器设置
	NVIC_PriorityGroupConfig(0);//中断分组
	OLED_Init();//0.96寸OLED初始化
	Ultrasound_Init();//超声波模块初始化
	
	while(1)	
	{		
		if(ultrasound_flag == 1)
		{
			sprintf(display_buff,"distance: %-5.2fcm    ",Ultrasound_GetDistance());//将数据打印到显示缓冲区
			OLED_Print(0,8,(uint8_t*)display_buff,TYPE6X8,TYPE6X8);//显示数据
		}
	} 
}

7、下面是超声波配置完整的.c和.h
== ultrasound.c ==

#ifndef __ULTRASOUND_H
#define __ULTRASOUND_H

#include "common.h"

//超声波模块引脚配置
#define ULTRASOUND_GPIO 						GPIOA
#define ULTRASOUND_GPIO_CLK 				RCC_APB2Periph_GPIOA
#define ULTRASOUND_TRIG 						GPIO_Pin_1
#define ULTRASOUND_ECHO 						GPIO_Pin_0

//超声波模块ECHO中断配置
#define ULTRASOUND_GPIOSourceGPIO   GPIO_PortSourceGPIOA
#define ULTRASOUND_PINSOURCE        GPIO_PinSource0
#define ULTRASOUND_EXTI_LINE				EXTI_Line0
#define ULTRASOUND_EXTI_IRQHandler  EXTI0_IRQHandler	//ECHO引脚中断服务函数

//超声波模块定时器配置
#define ULTRASOUND_TIM 							TIM2
#define ULTRASOUND_TIM_CLK 					RCC_APB1Periph_TIM2
#define ULTRASOUND_TIM_IRQ      		TIM2_IRQn
#define ULTRASOUND_TIM_IRQHandler		TIM2_IRQHandler	//TIM2中断服务函数

#define ULTRASOUND_DIS_INDEX 				10

extern uint8_t ultrasound_flag;//获取完成标志位变量
extern float ultrasound_data[ULTRASOUND_DIS_INDEX];//距离数据存储数组

void Ultrasound_Init(void);//模块使用初始
void Ultrasound_start(void);//启动一次
float Ultrasound_GetDistance(void);//获取距离数据

#endif


== ultrasound.c ==

#include "ultrasound.h"

float ultrasound_data[ULTRASOUND_DIS_INDEX] = {0};//数据存储
uint16_t d_count = 0;//获取数据次数计数变量
uint8_t ultrasound_flag = 0;//获取完成标志位变量

static void Ultrasound_GPIO_Conf(void)//端口配置
{
	EXTI_InitTypeDef EXTI_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(ULTRASOUND_GPIO_CLK | RCC_APB2Periph_AFIO,ENABLE);//打开GPIOA和端口复用时钟
	
	GPIO_EXTILineConfig(ULTRASOUND_GPIOSourceGPIO,ULTRASOUND_PINSOURCE);//配置中断线A-0
	
	GPIO_InitStructure.GPIO_Pin = ULTRASOUND_ECHO;//ECHO端口配置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//输入下拉
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(ULTRASOUND_GPIO,&GPIO_InitStructure);
	GPIO_ResetBits(ULTRASOUND_GPIO, ULTRASOUND_ECHO);//拉低ECHO
	
	GPIO_InitStructure.GPIO_Pin = ULTRASOUND_TRIG; //TRIG端口配置
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//复用推挽输出
  GPIO_Init(ULTRASOUND_GPIO, &GPIO_InitStructure);//初始化GPIOA.9
	
	EXTI_InitStructure.EXTI_Line = ULTRASOUND_EXTI_LINE; //配置中断线A-0
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//上下边沿触发
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //配置中断A-0
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}

static void Ultrasound_TIM_Conf(void)//定时器配置
{
	uint16_t PrescalerValue;//分频系数
	NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;

	RCC_APB1PeriphClockCmd(ULTRASOUND_TIM_CLK,ENABLE);//打开ULTRASOUND_TIM时钟
	
	PrescalerValue = (uint16_t)((SystemCoreClock / 2)  / 1000000) - 1;//计算分频系数(设定为:每秒计数100万次,每计一次为1us)
	TIM_TimeBaseStruct.TIM_Period = 0xFDE8;//重载寄存器的值,定时周期
	TIM_TimeBaseStruct.TIM_Prescaler = PrescalerValue;//预分频
	TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;//时钟切割
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
	TIM_TimeBaseInit(ULTRASOUND_TIM,&TIM_TimeBaseStruct);//初始化ULTRASOUND_TIM
	
	TIM_ClearITPendingBit(ULTRASOUND_TIM,TIM_IT_Update);//清除中断标志位,配置时必须加上
	TIM_ITConfig(ULTRASOUND_TIM, TIM_IT_Update, ENABLE);//事件更新中断配置
	
	NVIC_InitStructure.NVIC_IRQChannel = ULTRASOUND_TIM_IRQ; //配置ULTRASOUND_TIM中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	

	TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
}

void Ultrasound_Init(void)//超声波模块初始化
{
	Ultrasound_GPIO_Conf();//超声波控制端口配置
	Ultrasound_TIM_Conf();//超声波控制计数器配置
}

void Ultrasound_start(void)//超声波模块启动
{
	GPIO_SetBits(ULTRASOUND_GPIO, ULTRASOUND_TRIG);//拉高TRIG
	
	ULTRASOUND_TIM->CNT = 0x0000;	//清空计数器计数寄存器值
	ULTRASOUND_TIM->ARR = 0xffff; //重载值重新设定
	TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
	while(ULTRASOUND_TIM->CNT != 12);//大于10us,此处等待12us,
	
	GPIO_ResetBits(ULTRASOUND_GPIO, ULTRASOUND_TRIG);//拉低TRIG
}

float Ultrasound_GetDistance(void)//获取距离数据
{
	uint8_t i,j;//循环变量
	float distance_temp;//排序的临时变量
	for(i = 0;i < ULTRASOUND_DIS_INDEX - 1;i ++ )//降序排列,找出最大值和最小值
	{
		for(j = 0;j < ULTRASOUND_DIS_INDEX - 1;j ++)
		{
			if(ultrasound_data[j] < ultrasound_data[j + 1])
			{
				distance_temp = ultrasound_data[j + 1];
				ultrasound_data[j + 1] = ultrasound_data[j];
				ultrasound_data[j] = distance_temp;
			}
		}	
	}
				
	distance_temp = 0;//清零
		
	for(i = 2; i < ULTRASOUND_DIS_INDEX - 2;i ++)//去掉两个最大值和两个最小值
	{
		distance_temp += ultrasound_data[i];//获取的距离值累加
	}
	
	distance_temp /= (ULTRASOUND_DIS_INDEX - 4);//求平均值
	return distance_temp;
}


void ULTRASOUND_EXTI_IRQHandler(void)	//超声波ECHO引脚所用中断服务函数
{
	if(EXTI_GetITStatus(EXTI_Line0)!=0)	//判断是否真是触发中断
	{
		
		TIM_ITConfig(ULTRASOUND_TIM, TIM_IT_Update, DISABLE);//关闭计数器中断
		TIM_Cmd(ULTRASOUND_TIM,DISABLE);	//关闭计数器
		if(GPIO_ReadInputDataBit(ULTRASOUND_GPIO, ULTRASOUND_ECHO) == 1)	//检测到返回信号
		{
			ULTRASOUND_TIM->CNT = 0x0000;	//清空计数器计数寄存器值
			ULTRASOUND_TIM->ARR = 0xffff;//重载值重设为计数最大值
			TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
		}
		
		if(GPIO_ReadInputDataBit(ULTRASOUND_GPIO, ULTRASOUND_ECHO) == 0 )	//返回信号结束
		{
			if(ULTRASOUND_TIM->ARR == 0xffff) //需要确定计数器当前是不是在计数返回信号时间
			{
				if(d_count == ULTRASOUND_DIS_INDEX) //判断获取数据的次数
				{
					d_count = 0;	//清空次数计数值
					ultrasound_flag = 1;	//置位超声波标志位
				}
				else
				{
					ultrasound_flag = 0;	//清空超声波标志位
					ultrasound_data[d_count] = (float)(ULTRASOUND_TIM->CNT*34000)/1000000/2;	//计算距离,单位cm。并将数据存入待处理数据数组
					d_count++;	//次数计数值自增
				}
			}
			ULTRASOUND_TIM->ARR = 0xFDE8;//重载值为计数64ms
			TIM_ITConfig(ULTRASOUND_TIM, TIM_IT_Update, ENABLE);//开启计数器中断
			TIM_Cmd(ULTRASOUND_TIM,ENABLE);	//开启计数器
		}
		EXTI_ClearITPendingBit(EXTI_Line0);	//清除LINE上的中断标志位
	}
}

void ULTRASOUND_TIM_IRQHandler(void)//超声波所用TIM中断服务函数
{
	if(TIM_GetITStatus(ULTRASOUND_TIM,TIM_IT_Update) == SET)//判断是否有事件更新
	{
		Ultrasound_start();//启动超声波模块
	}
	TIM_ClearITPendingBit(ULTRASOUND_TIM,TIM_IT_Update);//清除中断标志位
}

8、实验结果
基于STM32的超声波测距(外部中断+定时器)_第1张图片

**若有不足之处还请各位指出。

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