STM32学习(MCU控制)(NVIC)
文章目录
- 中断【重点,难点】
- 1. 中断是什么
- 2. Cortex-M3 中断分类
- 3. 外部中断 / EXTI
- 3.1 外部中断 / EXTI 概述
- 3.2 外部中断判断逻辑框图
- 3.3 按键功能实现外部中断控制 EXTI
- 3.3.1 按键原理图
- 3.3.2 AFIO 时钟使能
- 3.3.3 外部中断线和按键对应关系
- 3.3.4 外部中断上升沿或者下降沿触发寄存器配置
- 3.3.5 外部中断屏蔽寄存器
- 3.3.6 中断挂起寄存器
- 4. IRQn 和 IRQHandler
- 4.1 IRQn 中断请求编号
- 4.2 IRQHandler 中断请求处理函数/句柄
- 5. 外部中断控制实现
- 5.1 外部中断配置函数使能函数
- 5.2 外部中断处理函数实现
- 6. 内部中断
- 6.1 内部中断概述
- 6.2 内部中断对应 IRQn 和 IRQHandler
- 7. 中断优先级
- 7.1 中断优先级概述
- 7.2 中断优先级配置
- 7.3 涉及到的函数
- 8. USART 操作中断优化
- 8.1 USART 中断优化思路
- 8.2 数据接收完成问题
中断【重点,难点】
1. 中断是什么
中断 ==> Interrupt。STM32 寄存器开发中,标志为描述如果是 IE (Interrupt Enable) 结尾,大多数情况下都是针对于某个中断的开启/关闭控制
中断操作是 STM32 中针对于特定的事件/行为,进行特定的处理操作。需要【触发机制】和【注册行为】。
- 【触发机制】程序执行过程中,中断出现,触发其他操作
- 【注册行为】注册当前 STM32 执行过程中,关注的中断内容是哪一个。
常用概念
- 断点 :注册的中断触发位置,一般对应电平跳变,寄存器标志位变化。
- 中断源 : 当前中断注册的功能函数/中断服务函数
- 压栈 : 中断触发,会将断点对应函数压栈操作,转换后台函数。


2. Cortex-M3 中断分类
Cortex-M3 内核中断分类
- 内核中断/内部中断:芯片内部中断,也可以称之为【异常 Exception】。可以任务是程序在正常情况下,出现不同于平常的数据情况,可以是错误,可以是标记。一般关注的是寄存器中的中断标志位。
- 外设中断:Cortex-M3 内核 预留了外部中断控制线,可以通过外部中断控制线高低电平切换**,出现上升沿和下降沿进行中断触发。**
3. 外部中断 / EXTI
3.1 外部中断 / EXTI 概述
- 外部中断线一共 EXTI0 ~ EXTI19
- 其中 EXTI0 ~ EXTI15 对应 GPIO 口,规则是 EXTI0 对应所有的 GPIO 组中 0 引脚,例如 PA0 ~ PG0
- 其他 EXTI16 ~ EXTI19 暂时不使用。


3.2 外部中断判断逻辑框图
- 外部中断线通过
- 上升沿触发选择寄存器
- 下降沿触发选择寄存器
- 决定当前中断的触发方式。需要根据电路分析,找到默认的电平情况下,选择合理的触发方式。

3.3 按键功能实现外部中断控制 EXTI
3.3.1 按键原理图

- PA0 ==> GPIO 工作模式为【下拉输入模式】
- PE2 PE3 PE4 ==> GPIO 工作模式为【上拉拉输入模式】
- 对应时钟配置寄存器是 APB2ENR 位2 IOA 位6 IOE
3.3.2 AFIO 时钟使能
- 根据目录分析,当前 EXTI 外部中断线,控制中需要设计到 AFIO 相关寄存器。AFIO 是 IO 引脚复用控制寄存器。
- 其中 AFIO_EXTICR1 ~ AFIO_EXTICR4 是用于控制外部中断线内容
- AFIO 对应的时钟是 APB2,需要进行使能操作。

3.3.3 外部中断线和按键对应关系
按键设计到引脚分别对应的外表中断线为
KEY_UP ==> PA0 ==> EXTI0
KEY_2 ==> PE2 ==> EXTI2
KEY_1 ==> PE3 ==> EXTI3
KEY_0 ==> PE4 ==> EXTI4
【注意】在 AFIO 寄存器中,为了分别管理 EXTICR1 ~ EXTICR4 采用数组形式进行数据管理。底层代码
AFIO_EXTICR[0] <> 对应寄存器名称 AFIO_EXTICR1
AFIO_EXTICR[1] <> 对应寄存器名称 AFIO_EXTICR2
AFIO_EXTICR[2] <> 对应寄存器名称 AFIO_EXTICR3
AFIO_EXTICR[3] <> 对应寄存器名称 AFIO_EXTICR4- 底层原码内容```c /** * @brief Alternate Function I/O*/typedef struct{__IO uint32_t EVCR;__IO uint32_t MAPR;__IO uint32_t EXTICR[4];uint32_t RESERVED0;__IO uint32_t MAPR2; } AFIO_TypeDef;


3.3.4 外部中断上升沿或者下降沿触发寄存器配置
- 以下两个寄存器用于控制当前触发的中断方式
- EXTI_RTSR 控制对应外部中断控制线触发方式为 上升沿触发
- EXTI_FTSR 控制对应外部中断控制线触发方式为 下降沿触发


3.3.5 外部中断屏蔽寄存器
外部中断屏蔽寄存器是用于控制对应外部中断线,是否运行被 MCU 处理。如果需要使用对应的外部中断控制,必须开启中断允许。

3.3.6 中断挂起寄存器
利用 EXTI_PR 寄存器明确当前中断是否触发,并且在处理之后,给予 write 1操作,机械能挂起寄存器复位。

4. IRQn 和 IRQHandler
4.1 IRQn 中断请求编号
IRQn ==> Interrupt ReQuest number
在 Cortex-M 内核中设计的中断请求编号,具备唯一性,同时根据当前设备的小容量,中容量,大容量 支持的 IRQn 数量不同。
IRQn 是用于在 NVIC 中注册当前对应中断服务,同时绑定对应的中断处理函数。

以上是 EXTI 外部中断线对应的 IRQn,这里需要使用的有
- EXTI0_IRQn ==> EXTI0 ==> PA0 ==> KEY_UP
- EXTI2_IRQn ==> EXTI2 ==> PE2 ==> KEY2
- EXTI3_IRQn ==> EXTI3 ==> PE3 ==> KEY1
- EXTI4_IRQn ==> EXTI4 ==> PE4 ==> KEY0
4.2 IRQHandler 中断请求处理函数/句柄
IRQHandler==> Interrupt ReQuest Handler
- 中断请求处理函数/句柄。
- 在 Cortex-M 内核每一个中断请求,内核提供了对应的唯一处理函数,如果需要完成中断服务,必须实现对应的中断处理函数/句柄。

以上是 EXIT 外部中断 IRQn 对应的处理函数
- EXTI0_IRQn ==> EXTI0_IRQHandler ==> EXTI0 ==> PA0 ==> KEY_UP
- EXTI2_IRQn > EXTI2_IRQHandler> EXTI2 ==> PE2 ==> KEY2
- EXTI3_IRQn > EXTI3_IRQHandler> EXTI3 ==> PE3 ==> KEY1
- EXTI4_IRQn > EXTI4_IRQHandler> EXTI4 ==> PE4 ==> KEY0
5. 外部中断控制实现
5.1 外部中断配置函数使能函数
void Key_EXTI_Interrupt_Enable(void)
{// 因为 Key_Init 已经完成了 KEY 对应 GPIO 配置。// 1. AFIO 时钟使能,开启当前 IO 引脚复用功能。RCC->APB2ENR |= 0x01; /*2. 外部中断控制线配置EXTI0 ==> PA0 EXTI2 ==> PE2 EXTI3 ==> PE3 EXTI4 == PE4EXTI0 对应寄存器位置为 0000 ==> PA[0]EXTI2 对应寄存器位置为 0100 ==> PE[2]EXTI3 对应寄存器位置为 0100 ==> PE[3]EXTI4 对应寄存器位置为 0100 ==> PE[4]当前 AFIO 中的针对于 EXTICR 寄存器采用数组形式进行管理EXTI_CR1 ==> AFIO->EXTICR[0] ==> EXTI0 ~ EXIT3EXTI_CR2 ==> AFIO->EXTICR[1] ==> EXTI4 ~ EXIT7EXTI_CR3 ==> AFIO->EXTICR[2] ==> EXTI8 ~ EXIT11EXTI_CR4 ==> AFIO->EXTICR[3] ==> EXTI12 ~ EXIT15根据当前需求,需要配置 EXTI_CR1 ==> AFIO->EXTICR[0] ==> 0100 0100 0000 0000 ==> 0x44 << 8对应配置 EXTI0 EXTI2 EXTI3EXTI_CR2 ==> AFIO->EXTICR[1] ==> 0000 0000 0000 0100 ==> 0x04对应配置 EXTI4*/AFIO->EXTICR[0] |= 0x44 << 8;AFIO->EXTICR[1] |= 0x04;/*3. 配置当前中断触发方式EXTI0 ==> KEY_UP EXTI2 ==> KEY2 EXTI3 ==> KEY1 EXTI4 == KEY0下降沿 ==> KEY0 KEY1 KEY2EXTI2 EXTI3 EXTI4上升沿 ==> KEY_UPEXTI0 */EXTI->RTSR |= 0x01;EXTI->FTSR |= 0x07 << 2;/*4. 中断屏蔽寄存器,开启对应中断需求EXTI->IMR,需要开发 EXTI0 EXTI2 EXTI3 EXTI40001 1101*/EXTI->IMR |= 0x1D;/*5. 设置中断优先级和对应中断使能。需要利用 NVIC 提供的函数,已经当前中断对应的 IRQnvoid NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)设置指定中断对应的优先级,需要提供的参数是 IRQn 和 优先级整数IRQn_Type IRQn ==> Interrupt ReQuest number 中断请求编号uint32_t priority 中断优先级当前操作所有中断,全部使用 1 优先级。*/NVIC_SetPriority(EXTI0_IRQn, 1);NVIC_SetPriority(EXTI2_IRQn, 1);NVIC_SetPriority(EXTI3_IRQn, 1);NVIC_SetPriority(EXTI4_IRQn, 1);/*void NVIC_EnableIRQ(IRQn_Type IRQn);指定 IRQn 对应的中断使能,注册对应中断请求。IRQn_Type IRQn ==> Interrupt ReQuest number 中断请求编号*/NVIC_EnableIRQ(EXTI0_IRQn);NVIC_EnableIRQ(EXTI2_IRQn);NVIC_EnableIRQ(EXTI3_IRQn);NVIC_EnableIRQ(EXTI4_IRQn);
}
5.2 外部中断处理函数实现
外部中断处理函数是 Cortex-M 内核声明的标准函数,无需二次声明,仅需要完成对应的实现即可
/*
针对于 EXTI0 外部中断控制线,Handler 处理函数,当前函数为内核声明函数。
仅需要提供对应的实现。
函数要求无参数无返回值,且名称固定 EXIT0 ==> EXTI0_IRQHandlerEXTI0_IRQHandler ==> PA0 ==> KEY_UP 按键
*/
void EXTI0_IRQHandler(void)
{/*根据当前 EXTI_PR 寄存器判断当前中断是否触发如果 EXIT0 中断触发,对应 EXTI_PR 寄存器位 0 ==> PR0寄存器标志位为 1EXTI->PR & 0x01 判断当前 PR0 位置是否为 1*/if (EXTI->PR & 0x01){/*根据当前寄存器的使用规则,将 EXTI->PR 中 PR0 位置赋值为 1完成寄存器标志复位操作。*/EXTI->PR = 0x01;/*控制 DS0 对应 PB5 引脚,控制 LED0*/if (GPIOB->ODR & (0x01 <<5)){Led0_Ctrl(1);}else{Led0_Ctrl(0);}}
}/*
EXTI2_IRQHandler ==> PE2 ==> KEY2 按键
*/
void EXTI2_IRQHandler(void)
{if (EXTI->PR & (0x01 << 2)){EXTI->PR = 0x01 << 2;if (GPIOB->ODR & (0x01 << 8)){Beep_Ctrl(0);}else{Beep_Ctrl(1);}}
}/*
EXTI3_IRQHandler ==> PE3 ==> KEY1 按键
*/
void EXTI3_IRQHandler(void)
{
}/*
EXTI4_IRQHandler ==> PE4 ==> KEY0 按键
*/
void EXTI4_IRQHandler(void)
{
}
6. 内部中断
6.1 内部中断概述
内部中断在内核中称之为异常,一般关注的是指定寄存器的中断标志位。
- 内部中断中,有三个最高的中断优先级
- RESET 复位 对应优先级为 -3, 最高优先级,且无法修改
- NMI 不可屏蔽中断 对应优先级为 -2,且无法修改
- HardFault 硬件错误中断 对应优先级为 -1,且无法修改
- 在中断控制中,优先级数值越小,对应的优先等级越高!!!
- 其中较为重要的内部中断有 SysTick

6.2 内部中断对应 IRQn 和 IRQHandler
内部中断对应 IRQn,根据原码分析,缺少 RESET 和 HardFault 对应的 IRQn,是因为RESET 和 HardFault 对应的中断处理方式为硬件处理方式,与软件内容无关。

内部中断对应的 IRQHandler 函数。其中 Reset_Handler 和 HardFault_Handler 没有重写的必要性。因为非软件可以处理的异常情况。

- 其中较为重要的 SysTick_IRQn 和 SysTick_Handler 系统嘀嗒中断处理。
7. 中断优先级
7.1 中断优先级概述
中断优先级是由【占先优先级】和 【次级优先级】两部分组成
- 占先优先级,多个中断触发时,MCU 首先根据占先优先级判断,哪一个优先级高,其他中断进入等待等待状态。【占先优先级谁高谁执行】
- 次级优先级,同时存在多个相同 占先优先级 中断存在,此时 MCU 按照次级优先级来决定执行顺序。但是如果此时有其他中断正常执行,不允许打断中断执行效果。【同步进入,谁高谁执行,如果中断已执行,其他中断不得抢占】
7.2 中断优先级配置


根据当前 Cortex-M3 内核要求,对应的优先级有效位置是 4 位,并且是【高 4 位】。针对于 NVIC 支持的所有优先级分组策略中,有且只有 b111 b110 b101 b100 和 其他有效
- X 表示占先优先级
- Y 表示次级优先级
- Priority Group 分组,对应的当前占先优先级二进制位数
| 分组 | 二进制占位 | 占先区 | 次级区 | 占先个数 | 次级个数 |
|---|---|---|---|---|---|
| 4 | XXXX | 4 | 0 | 16 | 0 |
| 3 | XXXY | 3 | 1 | 8 | 2 |
| 2 | XXYY | 2 | 2 | 4 | 4 |
| 1 | XYYY | 1 | 3 | 2 | 8 |
| 0 | YYYY | 0 | 4 | 0 | 16 |
7.3 涉及到的函数
NVIC_SetPriorityGrouping 全局配置
void NVIC_SetPriorityGrouping(uint32_t bits);设置整个系统的中断优先级分组方案,决定抢占优先级和子优先级的位数分配。
关键特性
全局性:影响所有中断的优先级解释方式
一次性:通常只在系统初始化时设置一次,之后不应更改
基础性:为后续所有 NVIC_SetPriority 调用提供解释依据
NVIC_SetPriority 个体中断配置
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);在既定分组规则下,为特定中断源设置具体的优先级值。
关键特性
个体性:只影响指定的中断源
依赖性:其数值含义完全取决于 NVIC_SetPriorityGrouping 的设置
多次调用:可为每个中断源单独设置
案例分析
NVIC_SetPriorityGrouping(2);
// 整个系统中,对应的优先级组为 2 ,2 位占先控制, 2 位次级控制NVIC_SetPriority(EXTI0_IRQn, 5); // 0101
NVIC_SetPriority(EXTI1_IRQn, 6); // 0110// 此时 EXTI0_IRQn EXTI1_IRQn 中断同时触发
/*
优先级分析占先分析 EXTI0_IRQn 和 EXTI1_IRQn 两个中断占先都是 01次级分析 此时分析次级优先级 01 > 10EXTI0_IRQn 执行。
*/NVIC_SetPriority(EXTI2_IRQn, 2); // 0010
NVIC_SetPriority(EXTI3_IRQn, 10); // 1010// 此时 EXTI3_IRQn 正在执行,同时程序运行触发了 EXTI2_IRQn 中断
/*
优先级分析占先分析 EXTI2_IRQn 占先为 00 和 EXTI3_IRQn 1000 > 10EXTI3_IRQn 中断被【压栈】,执行 EXTI2_IRQn 中断内容
*/
8. USART 操作中断优化
8.1 USART 中断优化思路
利用 USART_CR 控制寄存器和 USART_SR 状态寄存器来判断当前 USART 的工作状态。
- 解决数据接收完成问题
8.2 数据接收完成问题
数据读取完成中断判断
CR 控制寄存器


SR 状态寄存器



目标 USART1 对应的 IRQn 和 IRQHandler 函数
- IRQn ==>
USART1_IRQn- IRQHandler ==>
void USART1_IRQHandler(void)
中断使能和配置
void USART1_Interrupt_Enable(void)
{/*当前 USART1 的控制寄存器中,打开 IDLEIE 数据总线空闲中断使能打开 RXNEIE 数据总线空闲中断使能*/USART1->CR1 |= (0x01 << 4) | (0x01 << 5);/*设置当前 USART1 对应的中断优先级为 0001 在全局优先级设置为 2 的情况下 占先 0 次级 1*/NVIC_SetPriority(USART1_IRQn, 1); // 0001 占先 0 次级 1/*告知当前 MCU 使能对应的 USART1_IRQn 中断*/NVIC_EnableIRQ(USART1_IRQn);
}
利用中断完成数据读取函数,已经配套的结构体数据存储
#define DATA_SIZE (256)typedef struct usart1_data
{u8 data[DATA_SIZE]; // 接受数据缓冲区u8 flag; // 数据处理标志位u16 count; // 读取到的有效字节个数
} USART1_Data;extern USART1_Data usart1_val;
/*
完成 USART1 对应的 USART1_IRQn 对应的中断处理函数
当前中断处理函数是用于接收的数据内容进行处置操作,将接收的数据
存储到 USART1_Data 结构体中,对应的 u8 data[DATA_SIZE] 数组
*/
void USART1_IRQHandler(void)
{u32 val = 0;/*usart1_val.flag ==> 1 表示当前数据接收完毕,同时已经回显到PC 端 USART 工具*/if (usart1_val.flag){// 对当前数据空间进行擦除,memset(&usart1_val, 0, sizeof(USART1_Data));}/*如果当前触发的中断为 【RXNE 中断】,表示数据在通过串口传递到 MCU 中*/if (USART1->SR & (0x01 << 5)){usart1_val.data[usart1_val.count++] = USART1->DR;/*当前接收到的有效字节个数 == DATA_SIZE,当前数据缓冲区数组已满*/if (DATA_SIZE == usart1_val.count){USART1_SendBuffer(usart1_val.data, usart1_val.count);usart1_val.flag = 1;}}/*如果当前数据总线空闲 【IDLE 中断】,表示数据传递完毕*/if (USART1->SR & (0x01 << 4)){/*表示当前数据接收已完成*/usart1_val.flag = 1;/*需要完成对于当前 USART1->SR IDLE 数据总线空闲中断标志位进行清除操作。【官方要求】1. 读取 USART1->SR 寄存器2. 读取 USART1->DR 寄存器*/val = USART1->SR;val = USART1->DR;// 将数据回显到 PC 端 USART 调试工具USART1_SendBuffer(usart1_val.data, usart1_val.count);}*
}
