一、相关概念
(1)中断:在主程序运行过程中,出现了特定的事件(中断源),使得CPU暂停当前正在运行的程序,转而去调用相应的中断服务程序处理该事件,处理完成后又返回原来被暂停的位置继续运行
(2)中断源:可以引起中断的事件
(3)中断优先级:不同事件的重要程度。当有多个中断源同时申请中断,CPU会根据中断源的轻重缓急进行裁决,优先响应更加重要的中断源
(4)中断嵌套:当正在执行一个中断程序时,又被一个更高优先级的事件打断,暂停执行当前中断程序,转去处理新的中断程序,处理完成后再继续处理本中断的过程
中断嵌套的两条基本规则:
①低优先级的中断服务可被高优先级中断源中断,反之则不能
②任何一种中断一旦得到响应,不会被它的同级中断源的请求所中断
(5)中断向量:对应中断服务程序的入口地址
(6)中断处理过程包含中断请求、中断响应、中断服务及中断返回四个步骤
中断请求:中断源向CPU发出中断请求
中断响应:CPU允许中断,主程序或中断服务程序暂停,进入中断服务程序
中断服务:执行中断服务程序的过程
中断返回:中断服务程序执行完毕后回到主程序或者次级别中断服务程序的过程
二、中断系统结构
2.1 中断源及中断向量
STM32F103中断系统提供10个系统异常和60个可屏蔽中断源,具有16个中断优先级
可屏蔽中断源包括外部中断、定时器中断、串口中断、直接内存访问中断、模/数转换中断、集成电路总线中断、串行外设接口中断等
其中,外部中断由嵌套向量中断控制器NVIC和外部中断/事件控制器EXTI共同控制
中断向量表:定义一块固定的内存,以4字节对齐,存放各个中断服务函数程序的首地址
中断向量表定义在启动文件,当发生中断,CPU会自动执行对应的中断服务函数
2.2 嵌套向量中断控制器NVIC
NVIC是一个在Cortex-M3内核的中断控制器,M3 内核都 是支持 256 个中断( 16 个系统中断+ 240 个外部中断),并且具有 256 级的可编程中断设置。支持可裁剪。STM32F103C8T6支持10个系统中断和60个可屏蔽中断,提供16个可编程的优先级。中断发生时,自动获取中断服务程序的入口地址并直接调用
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register*/
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register*/
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register*/
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register*/
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register*/
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register*/
} NVIC_Type;
STM32的中断向量有两个属性,即抢占属性和响应属性,属性编号越小,优先级越高
中断优先级有寄存器组IPR(Interrupt Priority Registers)控制,包含15个32位的寄存器,一个可屏蔽中断占8bit,在这占用的8bit中只使用了高四位,可分为5组,即0、1、2、3、4组
抢占是指打断其他中断的属性,即中断嵌套。
判断两个中断的优先级时先看抢占优先级的高低,如果相同再看响应优先级的高低,如果都相同则看向量表中的优先级(自然优先级)
上电复位后,中断配置为4组,并且60个外部中断抢占优先级为0级,无响应优先级
2.3 外部中断/事件控制器EXTI(Extern Interrupt/Event Controller)
EXTI由20个产生事件/中断请求的边沿触发器组成,每根线都可以独立的配置输入类型(脉冲或挂起)和对应事件触发方式(上升沿、下降沿、双边沿),每根输入线都可以被独立屏蔽,由挂起寄存器保持状态线的中断请求
外部信号首先经过边沿检测单路,该边沿检测单路受两个平行的寄存器控制。为了使外部中断线上的事件能够产生中断,首先将设置两个边沿触发寄存器,并且将中断屏蔽寄存器置1以使能外部中断请求。当发出中断请求,该中断线对应的挂起标志位被置1,通过挂起寄存器写1操作可以清除中断请求。
不同分组的GPIO的相同引脚号被映射到同一外部中断/事件线上:
上图的电路符号:16选1数据选择器
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断,比如A0、B0、C0
通道数:16个GPIO_Pin,外加PVD输出(16)、RTC闹钟(17)、USB唤醒(18)、以太网唤醒(19)(互联型)
Cortex_M3内核可以通过内部或外部事件来唤醒内核,利用以上19根中断/事件请求线,可以配置任何I/O口、RTC闹钟和USB唤醒事件来唤醒CPU
触发响应方式:中断响应/事件响应(事件响应:外部中断信号不流向NVIC,而是通向其他外设,用来触发其他外设操作,比如,触发ADC转换)
2.4 AFIO
AFIO主要用于引脚复用功能的选择和重定义
在STM32中,AFIO主要完成以下任务:复用功能引脚重映射、中断引脚选择、调试I/O配置
三、EXTI寄存器
必须以字(32位)的方式操作这些寄存器,对于寄存器操作,常常适用&=、|=进行操作
3.1 中断屏蔽寄存器(EXTI_IMR)
3.2 事件屏蔽寄存器(EXTI_EMR)
3.3 上升沿触发选择寄存器(EXTI_RTSR)
3.4 下降沿触发选择寄存器(EXTI_FTSR)
3.5 软件中断事件寄存器(EXTI_SWIER)
3.6 挂起寄存器(EXTI_PR)
四、中断相关的库函数
4.1 标准库函数
4.1.1 NVIC分组函数:NVIC_PriorityGroupConfig()
4.1.2 NVIC初始化函数:NVIC_Init()
4.1.3 EXTI初始化EXTI_Init()
4.1.4 AFIO:将引脚映射到中断线上
4.1.5 core_m3.h中NVIC函数
ST公司设计STM32F103系列时,仅使用高四位来管理中断优先级(16个可编程优先级)
4.1.5.1 分组函数:NVIC_SetPriorityGrouping()
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
作用:设置优先级分组。
参数:PriorityGroup优先级分组组号,0~7(STM32F103:3~7有效)
举例:STM32的优先级分组设置为组5,则对应的代码如下:NVIC_SetPriorityGrouping(5);
4.1.5.2 中断优先级编码函数:NVIC_EncodePriority()
uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)
作用:设置抢占优先级和响应优先级的级别
参数:PriorityGroup优先级分组组号;
PreemptPriority:抢占优先级;
SubPriority:响应优先级
返回值:32位的编码值,编码值用于中断优先级设置
举例:优先级分组选择为组5,抢占优先级为2,响应优先级为2,代码如下:
u32 prio; prio = NVIC_EncodePriority(5,2,2);
4.1.5.3 中断优先级设置函数:NVIC_SetPriority()
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
作用:将优先级分组情况以及抢占优先级和响应优先级设置到响应的中断。
参数:IRQn :中断通道编号。
priority:是NVIC_EncodePriority函数的返回值
举例:设置串口1全局中断的优先级分组选择为组5,抢占优先级为2,响应优先级为2
对应的代码:
u32 prio;
prio = NVIC_EncodePriority(5,2,2);
NVIC_SetPriority(37,prio);
也可以写成NVIC_SetPriority(USART1_IRQn,prio);
4.1.5.4 中断使能/失能函数
void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);
参数:IRQn :中断通道编号,在"stm32f10x.h"中有定义
4.2 HAL库函数
4.2.1 中断优先级分组函数:HAL_NVIC_SetPriorityGiouping()
4.2.2 设置中断优先级:HAL_NVIC_SetPriority()
4.2.3 中断使能/失能函数
五、相关配置流程
5.1 寄存器配置
5.1.1 STM32
(1)打开GPIO、AFIO(使用外部中断,用于选择中断引脚)的时钟
(2)配置GPIO口,完成初始化
(3)配置AFIO,将引脚映射到中断线上
(4)配置EXTI,边沿触发方式、中断允许配置
(5)配置NVIC,完成分组、优先级设置、使能配置
/**
* @brief 初始化KEY
* @param 无
* @retval 无
* @remark 无
*/
void KEY_Init(void)
{
/*开启GPIOA、AFIO时钟*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
/*GPIO配置:PA4:上拉输入(CNF:10、MODE:00)*/
/*注:上下拉还需要配置ODR寄存器*/
GPIOA->CRL &= ~GPIO_CRL_MODE4; /*输入模式*/
GPIOA->CRL &= ~GPIO_CRL_CNF4_0; /*上/下拉输入*/
GPIOA->CRL |= GPIO_CRL_CNF4_1;
GPIOA->ODR |= GPIO_ODR_ODR4; /*上拉输入*/
/*AFIO配置,将对应引脚映射到外部中断线上*/
AFIO->EXTICR[1] &= ~AFIO_EXTICR2_EXTI4; /*清零*/
AFIO->EXTICR[1] |= AFIO_EXTICR2_EXTI4_PA;
/*配置EXTI:边沿触发方式、*/
EXTI->FTSR |= EXTI_FTSR_TR4; /*下降沿触发*/
EXTI->IMR |= EXTI_IMR_MR4; /*允许EXTI4中断*/
/*NVIC配置*/
NVIC_SetPriorityGrouping(5); /*设置优先级分组:全部为抢占优先级*/
NVIC_SetPriority(EXTI4_IRQn,NVIC_EncodePriority(5,1,1)); /*设置优先级*/
NVIC_EnableIRQ(EXTI4_IRQn); /*使能中断*/
}
void EXTI4_IRQHandler(void)
{
/*清除中断挂起位*/
EXTI->PR |= EXTI_PR_PR4;
Delay_ms(10); /*延时消抖*/
/*判断按键是否松手*/
if((GPIOA->IDR & GPIO_IDR_IDR4) == 0) /*按下仍未松手*/
{
while ((GPIOA->IDR & GPIO_IDR_IDR4) == 0); /*等待松手*/
/*延时消抖*/
Delay_ms(10);
/*LED反转*/
LED_Toggle();
}
}
5.1.2 GD32
1)打开GPIO、AFIO(使用外部中断,用于选择中断引脚)的时钟
(2)配置GPIO口,完成初始化
(3)配置AFIO,将引脚映射到中断线上
(4)配置EXTI,边沿触发方式、中断允许配置
(5)配置NVIC,完成分组、优先级设置、使能配置
/**
* @brief 初始化KEY(PA15)
* @param 无
* @retval 无
* @remark 无
*/
void KEY_Init(void)
{
/*开启GPIOA、AFIO时钟*/
RCU_APB2EN |= RCU_APB2EN_PAEN;
RCU_APB2EN |= RCU_APB2EN_AFEN;
/*关闭JTAG_DP,其占用PA15口(KEY键)*/
AFIO_PCF0 &= ~AFIO_PCF0_SWJ_CFG;
AFIO_PCF0 |= (0x2 << 24);
/*引脚配置:PA15,MD:00;CTL:10*/
GPIO_CTL1(GPIOA) &= ~GPIO_CTL1_MD15; /*输入模式*/
GPIO_CTL1(GPIOA) &= ~GPIO_CTL1_CTL15; /*清零*/
GPIO_CTL1(GPIOA) |= (0x2<<30); /*上下拉输入*/
GPIO_OCTL(GPIOA) |= GPIO_OCTL_OCTL15; /*上拉输入*/
/*AFIO配置,PA15映射到EXTI15*/
AFIO_EXTISS3 &= ~AFIO_EXTI15_SS;
/*配置EXTI:边沿检测、允许中断*/
EXTI_FTEN |= EXTI_FTEN_FTEN15; /*下降沿触发*/
EXTI_INTEN |= EXTI_INTEN_INTEN15; /*允许EXTI15中断*/
/*配置NVIC*/
NVIC_SetPriorityGrouping(5); /*设置优先级分组*/
NVIC_SetPriority(EXTI10_15_IRQn,NVIC_EncodePriority(5,1,1)); /*设置优先级*/
NVIC_EnableIRQ(EXTI10_15_IRQn); /*使能EXTI15*/
}
void EXTI10_15_IRQHandler(void)
{
/*清除中断挂起位*/
EXTI_PD |= EXTI_PD_PD15;
/*延时消抖*/
delay_1ms(10);
/*判断按键是否松手*/
if((GPIO_ISTAT(GPIOA) & GPIO_ISTAT_ISTAT15) == 0)/*按下仍未松手*/
{
while ((GPIO_ISTAT(GPIOA) & GPIO_ISTAT_ISTAT15) == 0); /*等待松手*/
/*延时消抖*/
delay_1ms(10);
LED_Toggle();
}
}
5.2 标准库配置
5.2.1 STM32
5.2.1.1 中断配置
(1)打开GPIO、AFIO(使用外部中断,用于选择中断引脚)的时钟
(2)配置GPIO口,完成初始化
(3)配置AFIO,将引脚映射到中断线上
(4)配置EXTI,完成初始化
(5)设置优先级分组(整个工程设置依次即可,若多次设置,以最后依次为准)
(6)配置NVIC,完成初始化
5.2.1.2 中断服务程序配置(外部中断为例)
(1)检测中断请求,EXTI_GetITStatus()
(2)中断服务内容
(3)清除中断请求,EXTI_ClesrITStatus()
5.2.2 GD32
/**
* @brief 初始化KEY(PA15)
* @param 无
* @retval 无
* @remark 无
*/
void KEY_Init(void)
{
/*开启GPIOA、AFIO时钟*/
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_AF);
/*关闭JTAG_DP,其占用PA15口(KEY键)*/
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP,ENABLE);
/*引脚配置:PA15,MD:00;CTL:10*/
gpio_init(GPIOA,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,GPIO_PIN_15);
/*AFIO配置,PA15映射到EXTI15*/
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA,GPIO_PIN_SOURCE_15);
/*配置EXTI:边沿检测、允许中断*/
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_enable(EXTI_15);
/*配置NVIC*/
NVIC_SetPriorityGrouping(5); /*设置优先级分组*/
NVIC_SetPriority(EXTI10_15_IRQn,NVIC_EncodePriority(5,1,1)); /*设置优先级*/
NVIC_EnableIRQ(EXTI10_15_IRQn); /*使能EXTI15*/
}
void EXTI10_15_IRQHandler(void)
{
/*清除中断挂起位*/
exti_flag_clear(EXTI_15);
;
/*延时消抖*/
delay_1ms(10);
/*判断按键是否松手*/
if(gpio_input_bit_get(GPIOA,GPIO_PIN_15) == RESET)/*按下仍未松手*/
{
while (gpio_input_bit_get(GPIOA,GPIO_PIN_15) == RESET); /*等待松手*/
/*延时消抖*/
delay_1ms(10);
LED_Toggle();
}
}
5.3 HAL库配置
HAL 库的 EXTI 外部中断的设置功能整合到 HAL_GPIO_Init 函数里面,而不是单独独立一个文件。所以我们的外部中断的初始化函数也是用 HAL_GPIO_Init 函数
配置步骤:
中断回调函数:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/*先判断是否EXTI4产生的中断请求*/
if(GPIO_Pin == GPIO_PIN_4)
{
/*执行相应业务逻辑*/
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(10);
while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET);
HAL_Delay(20);
//HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
}
}
}
六、应用实例
对射式红外传感器的DO接在B14引脚,实现每次遮挡,计数器加1
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Infrared_Sensor.h"
uint8_t count = 0;
int main()
{
Infrared_Init();
OLED_Init();
OLED_ShowString(1,1,"Count:");
while(1)
{
OLED_ShowNum(1,7,count,5);
}
}
Infrared_Sensor.c
#include "stm32f10x.h" // Device header
/**
* @brief 对射式红外传感器初始化
* @param 无
* @retval 无
*/
void Infrared_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //打开GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //打开AFIO的时钟,外部中断必须开始AFIO时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14); //将B14映射到外部中断14号线上
EXTI_InitTypeDef EXTI_InitStruct; //配置EXTI外设
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //配置NVIC为分组1
//即抢占优先级范围:0,响应优先级范围:0~7
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
NVIC_InitTypeDef NVIC_InitStruct; //配置NVIC外设
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
}
stm32f10x_exti.c
extern uint8_t count;
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
count++;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}