一、中断
1.1.什么是中断
STM32中的中断是指在程序正常运行时,外部事件或内部异常强行打断当前执行流程,转而执行相应处理程序,完成后再返回原流程的机制。在STM32中,中断是指当外设或处理器内部发生某些事件时,处理器暂停当前执行的程序,转而执行预定义的中断服务程序,处理完毕后再返回原程序继续执行的机制。
1.2.中断的意义与作用
中断的意义就是能高效处理紧急程序,不会一直占用CPU资源。
作用:
- 实时控制:在确定时间内对相应事件做出响应。
- 故障处理:检测到故障,需要第一时间处理。
- 数据传输:为了不确定到来的数据提供保障
1.3.中断优先级基本概念
- 抢占优先级(pre):高抢占优先级可以打断正在执行的低抢占优先中断
- 响应优先级(sub):当抢占优先级相同时,响应优先级高的先执行,但是不能相互打断
- 抢占和响应都相同的情况下,自然优先级越高的先执行
- 自然优先级:中断向量表的优先级
- 数值越小,表示优先级越高越先执行
1.4.中断优先级分组
以下表格是中断优先级分组,优先级分组为5组,是因为STM32的IPR(中断优先级寄存器)只使用高四位。
| 优先级分组 | AIRCR[10:8] | IPRx bit[7:4] | 分配结果 |
|---|---|---|---|
| 0 | 111 | None :[7:4] | 0位抢占优先级,4位响应优先级 |
| 1 | 110 | [7] :[6:4] | 1位抢占优先级,3位响应优先级 |
| 2 | 101 | [7:6] :[5:4] | 2位抢占优先级,2位响应优先级 |
| 3 | 100 | [7:5] :[4] | 3位抢占优先级,1位响应优先级 |
| 4 | 011 | [7:4] :None | 4位抢占优先级,0位响应优先级 |
下表为中断优先级举例:(假设分组是2)
首先看抢占优先级,抢占优先级数值越低,越先执行,因此编号3和7排到前二名;再看响应优先级,响应优先级数值越低,越先执行,因此编号7最先执行;再看编号6和-1,它们的抢占优先级和响应优先级都一样,就要看自然优先级,自然优先级数值越低,越先执行,因此编号-1要排到第3个执行,编号6要排到最后执行。总结,先考虑抢占优先级,再考虑响应优先级,最后考虑自然优先级。
| 编号 | 自然优先级 | 对应外设 | 抢占 | 响应 | 执行顺序 |
|---|---|---|---|---|---|
| 3 | 10 | RTC | 2 | 1 | 2 |
| 6 | 13 | EXTI0 | 3 | 0 | 4 |
| 7 | 14 | EXTI1 | 2 | 0 | 1 |
| -1 | 6 | Systick | 3 | 0 | 3 |
PS:如果EXTI0或Systick正在执行,RTC或EXTI1可以打断它;如果EXTI0正在执行,Systick不可以打断它。
二、NVIC
2.1.NVIC的基本概念
NVIC全称:Nested vectored interrupt controller(嵌套向量中断控制器)是STM32中用于管理和控制中断的核心组件,负责中断优先级分配、使能/禁用控制以及中断嵌套处理等功能。NVIC是STM32处理器中负责处理和管理中断的核心组件,它通过配置中断优先级、使能中断源以及处理中断嵌套等功能,确保系统能够高效响应各类中断请求。NVIC支持256个中断(16内核+240外部),支持256个优先级,并允许裁剪,ST公司把256个优先级裁剪为16个。下图是F1和F4型号的中断和中断优先级对比:
| 型号 | 内核中断 | 外部中断 | 中断优先级 |
|---|---|---|---|
| STM32F103xx | 10 | 60 | 16 |
| STM32F407xx | 10 | 82 | 16 |
图中内核中断为10,意味着有6个中断被保留。无论是内核中断还是外部中断,它都有相应的中断服务函数,也就是中断发生时,CPU需要执行相应的中断程序,中断服务函数就是该中断程序的一个入口。这些中断服务函数被定义在中断向量表里。
2.2.中断向量表
中断向量表是指在一块固定的内存,以4字节对齐,存放各个中断服务函数程序的首地址,当发生中断时,CPU会自动执行对应的中断服务函数,main函数往往是最后执行的,也就是优先级最低。
下图是内核中断服务函数(10个):


下图是外部中断服务函数(60个):

2.3.NVIC相关寄存器
| NVIC相关寄存器 | 位数 | 寄存器个数 | 备注 |
|---|---|---|---|
| 中断使能寄存器(ISER) | 32 | 8 | 32*8=240个外部中断,每个位控制一个中断置1 |
| 中断除能寄存器(ICER) | 32 | 8 | 32*8=240个外部中断,每个位控制一个中断置0 |
| 应用程序中断及复位控制寄存器(AIRCR) | 32 | 1 | 位[10:8]只有这三个位控制优先级分组 |
| 中断优先级寄存器(IPR) | 8 | 240 | 8个位对应一个,而STM32只使用高4位 |
2.4.NVIC的工作原理
下图是NVIC的工作原理,假设有4个外部中断来影响程序执行,外部中断进入NVIC后首先经过ISER和ICER寄存器来决定是否给该中断使能,如下图可知只有外部中断2、3、4使能,并进入IPR(中断优先级寄存器),STM32只使用高4位来控制一个优先级,再由AIRCR(应用程序中断及复位控制寄存器)控制优先级分组,最后到CPU来处理,其中内核中断最后一个到来,内核中断不用经过使能或失能,直接来到SHPR(系统异常优先级寄存器),该寄存器与IPR同一级别,最后CPU再处理内核中断。

2.5.NVIC的使用
- 设置中断分组:寄存器AIRCR[10:8],HAL_NVIC_SetPriorityGrouping
- 设置中断优先级:寄存器IPRx bit[7:4],HAL_NVIC_SetPriority
- 使能中断:寄存器ISERx,HAL_NVIC_EnableIRQ
2.5.1.HAL_NVIC_SetPriorityGrouping
设置中断分组,大概是通过以下流程来操控AIRCR寄存器来设置分组。


2.5.2.HAL_NVIC_SetPriority
该函数有三个参数,第一个填入外部中断的位置(该编号可查阅STM32F10xxx参考手册_V10(中文版)第132页);第二个填入抢占优先级;第三个填入响应优先级。主要操作的是IPR(中断优先级寄存器)。


2.5.3.HAL_NVIC_EnableIRQ
该函数主要实现请求使能该中断,通过ISER(中断使能寄存器)控制特定位置1使能。


三、EXTI
3.1.EXTI基本概念和主要特性
EXTI全称是External interrupt / event Controller,外部(扩展)中断事件控制器。可管理 20 个中断请求 / 生产事件线,即F1系列共有20条EXTI线,支持 GPIO 引脚及内部外设信号触发,实现中断响应或事件触发功能。每一条EXTI线都可以单独配置:选择类型(中断或事件)、触发方式(上升沿、下降沿或双边沿触发)、支持软件触发、开启 / 屏蔽、有挂起状态位。
3.1.1.中断和事件的理解
- 中断:需要进入NVIC,有相应的中断服务函数,需要CPU处理
- 事件:不需要进入NVIC,仅用于内部硬件自动控制,如:TIM、DMA、ADC
3.2.EXTI支持的外部中断 / 事件请求
- EXTI线0 ~ 15:对应GPIO PIN0 ~ 15
- EXTI线16:PVD输出
- EXTI线17:RTC闹钟事件
- EXTI线18:USB OTG FS唤醒事件
- EXTI线19:以太网唤醒事件(互联型板子)
3.3.EXTI工作原理
下图是EXTI的工作原理图,当有一个外部信号由输入线进入EXTI,来到边沿检测电路,由上升沿或下降沿触发选择器寄存器决定,并在寄存器里相应位置置1来决定,双边沿触发就由两个寄存器相同位置置1,通过边沿检测电路,来到或门电路,无论是软件中断事件寄存器是否置1,只要有信号即可通过这个或门电路,该外部信号通过或门电路后来到请求挂起寄存器,并在相应位置置1,说明有中断来了,该中断是否能来到NVIC,由中断屏蔽寄存器来决定,因此需要经过一个与门电路;该外部型号也有可能来到事件屏蔽寄存器,是否让设备发生事件触发是由时间屏蔽寄存器来决定。

①:边沿检测
②:软件触发
③:中断屏蔽 / 清除
④:事件屏蔽
3.4.EXTI相应的寄存器
3.4.1.下降沿触发选择寄存器(EXTI_FTSR)


注意:
- 外部唤醒线是边沿触发的,这些线上不能出现毛刺信号
- 在写EXTI_FTSR寄存器时,在外部中断线上的上升沿信号不能被识别,挂起位也不会被置位
- 在同一中断线上,可以同时设置上升沿和下降沿触发。即任一边沿都可触发中断
3.4.2.上升沿触发选择寄存器(EXTI_RTSR)


注意:
- 外部唤醒线是边沿触发的,这些线上不能出现毛刺信号
- 在写EXTI_RTSR寄存器时,在外部中断线上的上升沿信号不能被识别,挂起位也不会被置位
- 在同一中断线上,可以同时设置上升沿和下降沿触发。即任一边沿都可触发中断
3.4.3.中断屏蔽寄存器(EXTI_IMR)


3.4.5.挂起寄存器(EXTI_PR)
该寄存器自动置1挂起等待中断屏蔽寄存器的允许,如果手动置1就是清除当前挂起。


四、AFIO
4.1.AFIO简介
AFIO全称Alternate Function IO,复用功能IO,主要用于重映射和外部中断映射配置。配置AFIO寄存器之前,要使能AFIO时钟:__HAL_RCC_AFIO_CLK_ENABLE();对应RCC_APB2ENR寄存器位0。下面是常用的AFIO寄存器:
- 调试IO配置:AFIO_MAPR[26:24],这三个位配置JTAG / SWD的开关状态
- 重映射配置:AFIO_MAPR,部分外设IO重映射配置
- 外部中断配置:AFIO_EXTICR1~4,配置EXTI中断线0 ~ 15对应到哪个具体IO口
4.2.EXTI和IO
EXTI和IO口的关系如下图所示,AFIO_EXTICRx寄存器一共有4个(1 ~ 4),一个AFIO_EXTICRx寄存器里面只有4个EXTIx,因此4个AFIO_EXTICRx和4个EXTIx一共控制16个IO口(0 ~ 15),EXTI的尾缀与IO口的引脚号有关。

4.3.外部中断配置寄存器 1(AFIO_EXTICR1)
该寄存器高16位保留,只用到低16位,里面的EXTIx只用[3:0]这三位来控制A ~ G组的IO。还有剩下三个一样的寄存器以此类推。


AFIO_EXTICR1:EXTI0、EXTI1、EXTI2、EXTI3
AFIO_EXTICR2:EXTI4、EXTI5、EXTI6、EXTI7
AFIO_EXTICR3:EXTI8、EXTI9、EXTI10、EXTI11
AFIO_EXTICR4:EXTI12、EXTI13、EXTI14、EXTI15
五、使用中断
5.1.如何使用中断
下图是使用中断的大致步骤:

5.2.EXTI的配置步骤(GPIO外部中断)
- 使能GPIO时钟
- 设置GPIO输入模式:上拉/下拉/浮空
- 使能AFIO时钟
- 设置EXTI和IO对应关系:选择PA ~ PK组,和EXTI输入线,AFIO_EXTICR / SYSCFG_EXTICR
- 设置EXTI屏蔽,上/下沿
- 设置NVIC:设置优先级分组、设计优先级、使能中断
- 设计中断服务函数:编写对应中断的中断服务函数,清中断标志
步骤2 ~ 5使用HAL_GPIO_Init即可完成。
5.3.EXTI的HAL库设置步骤(GPIO外部中断)
- 使能GPIO时钟:使用__HAL_RCC_GPIOx_CLK_ENABLE
- GPIO / AFIO / EXTI:使用HAL_GPIO_Init,一步到位
- 设置中断分组:使用HAL_NVIC_SetPriorityGrouping,此函数仅需设置一次
- 设置中断优先级:使用HAL_NVIC_SetPriority
- 使能中断:使用HAL_NVIC_EnableIRQ
- 设计中断服务函数:编写EXTIx_IRQHandler,中断服务函数,清除中断标志
STM32仅有:EXTI0 ~ 4、EXTI9_5、EXTI15_10,7个外部中断服务函数。
5.4.HAL库中断回调处理机制介绍

六、通过按键来控制灯的亮灭
6.1.按键
以下是STM32F103zet6精英版的按键电路图,KEY_UP是按下输出高电平,上升沿,不按下的时候为高阻态,电平不稳定,因此要接一个下拉电阻;KEY0、KEY1与KEY_UP相反。

6.2.EXTI初始化函数
下面是代码EXTI.h,重新定义了一些引脚名称、中断编号和中断服务函数:
#ifndef __EXTI_H
#define __EXTI_H
#include "./SYSTEM/sys/sys.h"
#define KEY0_INT_GPIO_PORT GPIOE
#define KEY0_INT_GPIO_PIN GPIO_PIN_4
#define KEY0_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
#define KEY0_INT_IRQn EXTI4_IRQn
#define KEY0_INT_IRQHandler EXTI4_IRQHandler
#define KEY1_INT_GPIO_PORT GPIOE
#define KEY1_INT_GPIO_PIN GPIO_PIN_3
#define KEY1_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
#define KEY1_INT_IRQn EXTI3_IRQn
#define KEY1_INT_IRQHandler EXTI3_IRQHandler
#define WKUP_INT_GPIO_PORT GPIOA
#define WKUP_INT_GPIO_PIN GPIO_PIN_0
#define WKUP_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define WKUP_INT_IRQn EXTI0_IRQn
#define WKUP_INT_IRQHandler EXTI0_IRQHandler
void extix_init(void);
#endif
下面代码是EXTI.c:
void extix_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
KEY0_GPIO_CLK_ENABLE(); //使能三个按键的时钟
KEY1_GPIO_CLK_ENABLE();
WKUP_GPIO_CLK_ENABLE();
gpio_init_struct.Pin = KEY0_INT_GPIO_PIN; //KEY0 的初始化
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; //中断模式的下降沿触发
gpio_init_struct.Pull = GPIO_PULLUP; //上拉输入
HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = KEY1_INT_GPIO_PIN; //KEY1 的初始化
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; //中断模式的下降沿触发
gpio_init_struct.Pull = GPIO_PULLUP; //上拉输入
HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = WKUP_INT_GPIO_PIN; //KEYUP 的初始化
gpio_init_struct.Mode = GPIO_MODE_IT_RISING; //中断模式上升沿触发
gpio_init_struct.Pull = GPIO_PULLDOWN; //下拉输入
HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct);
//下面优先级最高的是KEY0>KEY1>KEYUP
HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(KEY0_INT_IRQn);
HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2);
HAL_NVIC_EnableIRQ(KEY1_INT_IRQn);
HAL_NVIC_SetPriority(WKUP_INT_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(WKUP_INT_IRQn);
}
三个中断函数共用一个回调函数,按下KEY0,实现LED0亮5秒钟然后熄灭;按下KEY1,实现LED0电平翻转;按下KEYUP,实现蜂鸣器电平翻转;由上面优先级可知,如果按下KEY0,在LED0亮的5秒之内,按其他两个按键是没有反应的;按下KEY1,再按下KEY0,KEY0就会抢占CUP操作权,这时再次按下KEY1也实现不了电平翻转了,蜂鸣器也同理。
void KEY0_INT_IRQHandler(void) //中断服务函数
{
HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); //HAL库中断处理公共函数,里面包含HAL库数据处理回调函数
__HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN); //清除中断标志位
}
void KEY1_INT_IRQHandler(void) //中断服务函数
{
HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN); //HAL库中断处理公共函数,里面包含HAL库数据处理回调函数
__HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN); //清除中断标志位
}
void WKUP_INT_IRQHandler(void) //中断服务函数
{
HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN); //HAL库中断处理公共函数,里面包含HAL库数据处理回调函数
__HAL_GPIO_EXTI_CLEAR_IT(WKUP_INT_GPIO_PIN); //清除中断标志位
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(20);
switch(GPIO_Pin)
{
case KEY0_INT_GPIO_PIN:
if (KEY0 == 0)
{
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET);
delay_ms(5000);
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET);
}
break;
case KEY1_INT_GPIO_PIN:
if (KEY1 == 0)
{
LED0_TOGGLE(); //LED0电平翻转
}
break;
case WKUP_INT_GPIO_PIN:
if (WK_UP == 1)
{
BEEP_TOGGLE(); //蜂鸣器电平翻转
}
break;
}
}
6.3.主函数
#include "./stm32f1xx_it.h"
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/EXTI/exti.h"
int main(void)
{
HAL_Init();
sys_stm32_clock_init(RCC_PLL_MUL9);
delay_init(72);
usart_init(115200);
led_init();
beep_init();
extix_init();
LED0(0);
while (1)
{
printf("OK\r\n");
delay_ms(1000);
}
}
2660

被折叠的 条评论
为什么被折叠?



