赞
踩
基于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、实验结果
**若有不足之处还请各位指出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。