概要
了解外部中断的基本概念后,就需要尝试对其进行应用,本文将从对射式红外传感器出发了解如何使用固件库实现对中断的配置和应用,本文可以说是对B站UP江协科技项目的复现,旨在弄清中断的应用思路,其中用到的资料大家可以去视频课程自取。
PS:本文程序是在已配置好启动文件的基础STM32项目后,添加江协科技的OLED显示模块下进行编程的。
对射式红外传感器与元器件连接
四个引脚
对射式红外传感器拥有四个引脚
VCC:正极引脚,用于为传感器模块供电。通常连接到5V或3.3V电源。
GND:负极引脚,用于完成电路的电源回路。它应该连接到电源的地线。
DO:反映模块沟槽处遮挡情况,无遮挡时开关指示灯亮输出低电平,有遮挡时开关指示灯不亮输出高电平
AO:模拟输出引脚,本处不做使用
接线图(引脚连接可以自己定义,本处只做参考)
项目创建与模块代码编写
模块化编程:依旧是在Hardware外设模块文件夹下创建对应.C和.H文件并完成通用的头文件引到和头文件保护
#ifndef __CountSensor_H
#define __CountSensor_h
#endif
初始化配置
除去基本的时钟分配外,需要结合外部中断的工作方式进行一系列的初始化配置,结合上图可知。本次使用GPIO的复用功能进行外部中断的触发,那么首先就需要配置GPIO和AFIO进行端口复用,和选择哪一条EXTI中断线,最后需要配置NVIC优先级分组等设置。
//初始化配置
void CountSensor_Init(void)
{
//配置系统时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE );//GPIO配置时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//AFIO配置时钟
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);//将PB14配置成上拉输入
//AFIO初始化
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//开启PB14的中断复用功能
//EXTI初始化
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体
EXTI_InitStructure.EXTI_Line=EXTI_Line14 ;//选定中断线
EXTI_InitStructure.EXTI_LineCmd=ENABLE; //使能选定的中断线
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//将次中断线作为中断触发线
EXTI_InitStructure.EXTI_Trigger= EXTI_Trigger_Falling;//触发中断的方式选定为下降沿触发
EXTI_Init(&EXTI_InitStructure);//根据上述选定的值进行初始化
//NVIC中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置优先级分组为2分组
//此时抢占优先级可以设置为0~3,响应优先级0~3
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
NVIC_InitStructure.NVIC_IRQChannel= EXTI15_10_IRQn; //选择为EXTI15_10配置nvic线路
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //为选定的NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; //配置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; //配置响应优先级
NVIC_Init(&NVIC_InitStructure); //根据机构体的值进行初始化
}
关于GPIO的库函数配置本处不做过多介绍,详细配置思路可以查看我之前的文章:STM32点灯
值得注意的是,本次需要分配系统时钟的部分只有GPIO和AFIO而EXTI和NVIC并没有进行分配系统时钟操作,理由分别是:
EXTI与Cortex-M内核紧密相连,具有自己的时钟源,这个时钟由时钟树自动设置。
而NVIC是Cortex-M内核的一部分,不需要分配系统时钟。
AFIO作为GPIO的引脚复用模块其函数在gpio.h文件中能找到,本处使用的是将GPIO复用为EXTI外部中断线使用的函数是GPIO_EXTILineConfig跳转到定义可以看到
这个函数的第一个参数是选择复用哪一个GPIO组(A~G),本处是GPIOB。
第二个参数是选择具体的哪一个pin_口(0~15),本处是14,即PB14
关于EXTI的相关函数可以在exti.h文件中查到,
它们的功能分别是
1,清除已设置好的EXTI配置
2,根据结构体的赋值初始化EXTI配置
3,对结构体的赋值进行“清除”操作,初始化为默认值
4, 产生一个软件中断
5,检查指定的外部中断线的标志位是否被置位
6,清除外部中断标志位
7,检查指定的外部中断线是否真正触发了中断(是否被置位是否被屏蔽)
8,清除指定的外部中断线的中断挂起位
本处使用EXTI_Init函数进行初始化,值得注意的是这个函数跟GPIO_Init的使用类似,都是先定义一个结构体,在根据结构体的赋值,完成函数的配置
后面NVIC的相关配置也是类似的结构体形式 需要注意的是,NVIC在系统内核中,相关函数需要在misc.h中查找
函数功能:
1,配置优先级分组
2,根据指定结构体初始化NVIC
3,设置向量表位置和偏移量
4,选择系统进入低功耗模式的条件
5,配置SysTick时钟源
本处主要使用了配置优先级分组和初始化NVIC
值得注意的是这个分组的选择跟抢占优先级占的位数有部分联系,这样后续选择分组时只需要根据抢占优先级分配的位数来填写就行。
关于NVIC结构体赋值就是配置NVIC的哪一条线,抢占优先级和响应优先级的数值,以及是否使能
这样一来,基本的初始化配置就完成了。
中断函数
完成配置后就可以考虑中断函数的编写了,我们需要实现在中断触发时实现计数,那么只需要引入一个变量对其实现每触发一次中断累加一次即可
需要注意的是中断函数的命名是固件库规定好的,无须声明满足触发条件时会自动触发。
uint16_t CountSensor_Count; //全局变量,用于计数
//中断函数
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line14)==SET)//确认是否是14号线触发的中断
{
//消除电平抖动导致数据乱跳
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==0)
{
CountSensor_Count++;//计数
}
}
EXTI_ClearITPendingBit(EXTI_Line14);//清除中断标志位
//中断标志位必须清除
//避免程序一直在执行中断函数
}
使用EXTI_GetFlagStatus查看EXTI14号线的标志位是不是置位状态,以确保这个中断时是EXTI15_10线组中的14号线也就是有对射式红外传感器触发的
使用GPIO_ReadInputDataBit对PB14引脚电平进行检测,本次采用下降沿中断触发方式,一旦电平发生抖动很容易多次触发中断。也可以考虑加入延时函数进行进一步消抖。
最后使用中断函数时需要及时清除标志位,确保中断单次触发只执行一次。
这么一来就实现了中断计数,为了减少变量的声明,可以考虑将计数值也封装成一个函数(可以不封装)
//获取计数值
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
最后不要忘了在CountSensor.h文件进行函数的声明和添加必要的注释
#ifndef __CountSensor_H
#define __CountSensor_h
// 初始化函数
// 功能:实现对GPIO,EXTI,NVIC的配置
// 参数:viod(无参数)
// 返回值:void(无返回)
void CountSensor_Init(void);
//中断函数无须声明
// 获取计数值
// 功能:返回对射式红外传感器的中断触发次数
// 参数:void
// 返回值:uint16_t(无符号整形)
uint16_t CountSensor_Get(void)
#endif
最后在main.c中编写主函数逻辑,关于OLED的函数模块可以去B站UP江协科技自取
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
CountSensor_Init(); //计数传感器初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:
while (1)
{
OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不断刷新显示CountSensor_Get的返回值
}
}