本篇文章需要就着ARM Cortex-M3 Cortex-M4 权威指南和STM32F7参考手册来阅读,
最好有现成的Stm32F7的工程,当然其他的也可以
Cortex-M3/Cortex-M4内核支持256个中断,其中包含16个内核中断(系统异常)和240个外部中断(IRQ),并且都具有256级可编程中断设置
Cortex-M处理器都会提供一个用于中断处理的嵌套向量中断控制器(NVIC);除了中断还有其他需要服务的事件称之为异常,按照ARM的说法中断也是一种异常
在参考手册中搜索向量表,可查看所有中断,1~15共16个内核中断(内核中断也可以称之为系统异常),其中复位,NMI,硬件错误中断优先级为(-3 ~ -1),-3的优先级最高
外部中断(中断编号16 ~ 255),不同的厂商对外部中断的定义可能是不用的,STM32F4/F7并没有使用CM4/7内核Dev全部东西,而是使用了其中的一部分,STM32F7x一共有118个中断,10个内核中断和108个可屏蔽中断,可以在stm32f767xx.h
中看到结构体IRQn_Type
,其中就是对外部中断的定义
STM32对中断进行分组0~4,不同的分组有不同的抢占优先级和相应优先级长度,
CPSIE I; //清除PRIMASK(使能中断)
CPSID I; //设置PRIMASK(禁止中断)
MOVS R0, #1
MSR PRIMASK, R0 ;//将1写入PRIMASK禁止所有中断
MOVS R0, #0
MSR PRIMASK, R0 ;//将0写入PRIMASK使能所有中断
CPSIE F; //清除FAULTMASK(使能中断)
CPSID F; //设置FAULTMASK(禁止中断)
MOVS R0, #1
MSR FAULTMASK, R0 ;//将1写入PRIMASK禁止所有中断
MOVS R0, #0
MSR FAULTMASK, R0 ;//将0写入FAULTMASK使能所有中断
MOVS R0, #0X60
MSR BASEPRI, R0 ;//将60写入BASEPRI禁止优先级低于60的所有中断
MOVS R0, #0
MSR BASEPRI, R0 ;//将0写入FAULTMASK使能优先级低于60的所有中断
端口 | 对应中断 |
---|---|
EXTI 线 0~15: | 对应外部 IO 口的输入中断。 |
EXTI 线 16: | 连接到 PVD 输出。 |
EXTI 线 17: | 连接到 RTC 闹钟事件。 |
EXTI 线 18: | 连接到 USB OTG FS 唤醒事件。 |
EXTI 线 19: | 连接到以太网唤醒事件。 |
EXTI 线 20: | 连接到 USB OTG HS(在 FS 中配置)唤醒事件。 |
EXTI 线 21: | 连接到 RTC 入侵和时间戳事件。 |
EXTI 线 22: | 连接到 RTC 唤醒事件。 |
EXTI 线 23: | 连接到 LPTIM1 异步事件 |
因为STM32的中断非常多,所以必须设置中断的优先级,设置完优先级之后编写中断服务函数,进行中断服务函数的挂载。
中断的使用基本流程为
1)使能 IO 口时钟。
2)调用函数 HAL_GPIO_Init 设置 IO 口模式,触发条件,使能 SYSCFG 时钟以及设置 IO
口与中断线的映射关系。
3)配置中断优先级(NVIC),并使能中断。
4)在中断服务函数中调用外部中断共用入口函数 HAL_GPIO_EXTI_IRQHandler。
5)编写外部中断回调函数 HAL_GPIO_EXTI_Callback 实现控制逻辑。
下文为过程详细介绍
首先,我们要使用 IO 口作为中断输入,所以我们要使能相应的 IO 口时钟,之后
设置 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //外部中断,上升沿触发
GPIO_Initure.Pull=GPIO_PULLDOWN; //默认下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
之后设置中断优先级
HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); //抢占优先级为 2,子优先级为 1
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线 2
们配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是
在 HAL 库中事先有定义的。有 7 个,分别为:
void EXTI0_IRQHandler();
void EXTI1_IRQHandler();
void EXTI2_IRQHandler();
void EXTI3_IRQHandler();
void EXTI4_IRQHandler();
void EXTI9_5_IRQHandler();
void EXTI15_10_IRQHandler();
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是 HAL 库把中断处理过程进行了简单封装,也就是中断处理的回调函数。
//使用如下
//中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); //调用中断处理公用函数
}
在使用 HAL 库的时候,我们也可以跟使用标准库一样,在中断服务函数中编写控制逻辑。但 是 HAL 库 为 了 用 户 使 用 方 便 , 它 提 供 了 一 个 中 断 通 用 入 口 函 数HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用回调函数HAL_GPIO_EXTI_Callback。
HAL_GPIO_EXTI_IRQHandler 函数定义:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
该函数实现的作用非常简单,通过入口参数 GPIO_Pin 判断中断来自哪个 IO 口,然后清除相应的中断标志位,最后调用回调函数 HAL_GPIO_EXTI_Callback()实现控制逻辑。所以我们编写中断控制逻辑将跟串口实验类似,在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler,然后在回调函数HAL_GPIO_EXTI_Callback 中通过判断中断是来自哪个 IO 口编写相应的中断服务控制逻辑。
事件、中断事件 中断三个概念或术语。这三个概念彼此关联,有时会让人有点混淆或犯迷糊。
拿一件生活中的事情打比方对上述三个概念做个基本的粗略理解,比如一老师在教室里给学生们上课。课堂上的学生可能做出各种行为动作,比方做笔记、打哈气、翻书包、讲小话等,我们把这些行为统称为事件,其中有些行为老师往往只是视而不见,继续他的上课;而有些行为可能导致老师的上课中止,比方讲小话,并对学生的相关行为予以警告、批评或纠正等,然后继续上课。我们把老师因为学生的某些行为而中止授课,并产生后续动作,之后接着上课的这个过程理解为中断或中断响应。我们把可能导致老师上课中断的学生行为理解为中断事件。
结合上面的比方,不难理解中断事件是一种可以导致中断发生的事件,中断则是因为中断事件的发生而导致的后续行为过程。事件与中断事件是包含关系,即事件可分为中断事件或非中断事件。而中断事件与中断之间属于前后关联的因果关系,虽有关联,但二者在时序上、行为上并不一样。
中断服务函数名是固定的,在启动代码里面已经定下来了。
书写中断服务函数的时候注意的问题:
//本程序是通过外部中断实现,四个按键按下从而控制LED灯呈现不同效果的程序
#include "exti.h"
#include "delay.h"
#include "led.h"
#include "key.h"
//中断初始化
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
//几个按键相关的时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启时钟GPIOA
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启时钟GPIOC
__HAL_RCC_GPIOH_CLK_ENABLE(); //开启时钟GPIOH
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //上升沿触发
GPIO_Initure.Pull=GPIO_PULLDOWN; //下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_IT_FALLING; //下降沿触发
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3 下降沿触发,上拉
HAL_GPIO_Init(GPIOH,&GPIO_Initure);
//中断线0优先级设置
HAL_NVIC_SetPriority(EXTI0_IRQn,2,0); //优先级为2,子优先级为1
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线0
//中断线2优先级设置
HAL_NVIC_SetPriority(EXTI2_IRQn,2,1);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
//中断线3优先级设置
HAL_NVIC_SetPriority(EXTI3_IRQn,2,2);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
//中断线13优先级设置
HAL_NVIC_SetPriority(EXTI15_10_IRQn,2,3);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
//中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); //调用中断公用处理函数
}
void EXTI2_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2); //调用中断公用处理函数
}
void EXTI3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); //调用中断公用处理函数
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); //调用中断公用处理函数
}
//中断处理公用函数的回调函数
//中断处理公用函数在stm32f7xx_hal_gpio.c中定义,并且调用HAL_GPIO_EXTI_Callback(GPIO_Pin);
//在hal库中所有外部中断服务函数都会调用此函数
//Pram:GPIO_Pin-中断引脚
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static u8 led0sta=1,led1sta=1;
delay_ms(50); //消抖
switch(GPIO_Pin)
{
case GPIO_PIN_0:
if(WK_UP==1) //控制LED0 LED1 互斥点亮
{
led1sta=!led1sta;
led0sta=!led1sta;
LED1(led1sta);
LED0(led0sta);
}
break;
case GPIO_PIN_2:
if(KEY1==0) //同时控制 LED1
{
led1sta=!led1sta;
LED1(led1sta);
};
break;
case GPIO_PIN_3:
if(KEY0==0) //同时控制LED0 LED1
{
led1sta=!led1sta;
led0sta=!led0sta;
LED1(led1sta);
LED0(led0sta);
}
break;
case GPIO_PIN_13:
if(KEY2==0) //LED0翻转
{
led0sta=!led0sta;
LED0(led0sta);
}
break;
}
}