基于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);//清除中断标志位
}
**若有不足之处还请各位指出。