目录
1 常见数据传输形式
无条件形式
查询方式
中断方式(今日份主角)
DMA(直接存储器访问方式)
2 中断相关概念
中断是一种硬件机制,在这种机制中,设备会在就绪条件达成后主动通知CPU它需要引起注意,中断可以随时发生,需要CPU立即处理。因此,当CPU通过指示中断请求线收到中断信号时,CPU停止当前进程并通过将控制权传递给服务设备的中断处理程序来响应该中断。
2.1 中断的定义
2.1.1 中断源
引发中断的事件
2.1.2 主程序
当前正在执行的程序
2.1.3 断点
主程序被暂停的位置
2.1.4 中断服务程序
事件所对应的处理程序
2.2 中断的作用及优点
作用:
解决了处理器和外部设备的数据传输问题
优点:
- 分时操作:可以分时为多个外部设备服务,提高了处理器的利用率
- 实时相应:处理器能够及时处理系统的随机事件,提高了系统的实时性
- 可靠性高:具有处理设备故障及掉电等突发事件的能力,提高系统的可靠性
2.3 中断优先级和中断嵌套
抢占优先级高的(数字越小,优先级越高)最先执行,且可打断低优先级的中断,抢先执行(嵌套中断的核心);当两个抢占优先级相同的中断源同时触发时,次优先级高的先执行(注意是同时触发,否则按照自然顺序依次执行),且不支持抢占
2.4 中断服务程序和中断向量表
中断服务程序(ISR)就是指在中断发生时所执行的特定程序,一般由用户编写
为了区分中断源,系统为每一个中断源分配了一个唯一的编号,这个编号称为中断类型号,按照中断类型号,从小到大将所有中断服务程序的入口地址依次排列,得到中断向量表,中断服务程序的入口地址称为中断向量
中断向量表通常位于存储器的零地址处。当某个中断源发出中断申请时,处理器根据识别到的中断类型号在中断向量表中寻找对应的表项,表项的内容就是该中断所对应的中断服务程序的入口地址,然后跳转到该地址执行相应功能函数
2.5 中断响应过程
- 中断源发出中断请求
- 判断处理器是否允许中断,以及该中断源是否被屏蔽
- 多个中断源同时申请时,需要进行优先级排队
- 处理器暂停当前主程序,保护断点地址和处理器当前状态,根据中断类型号,查找中断向量表,转到对应的中断服务程序
- 执行具体的中断处理函数
- 恢复被保护的处理器状态,执行中断返回指令,回到被暂停程序的断点地址处
3 STM32中断系统
3.1 嵌套向量中断控制器(NVIC)
中断可以由硬件触发,也可以通过软件触发。在ARM处理器中,把能够打断当前代码执行流程的事件分为异常和中断两类
- 中断是由内核外部产生的,一般是硬件触发,比如外部中断和定时器中断
- 异常是内核自身产生的,大多由软件触发,比如除法出错异常,预取指失败等
在基于Cortex-M内核设计的ARM处理器中,提供了一个专用的硬件模块:嵌套向量中断控制器(NVIC),用来管理全部的异常和中断,该模块以最小的中断延迟提供灵活的中断管理功能。
- 紧耦合的NVIC能够达到低延迟的中断响应处理
- 中断向量入口地址直接进入内核
- 紧耦合的NVIC接口
- 允许中断的早期处理
- 处理晚到的较高优先级中断
- 支持中断尾部链接功能
- 自动保存处理器状态
- 中断返回时自动恢复,无需额外指令开销
3.2 STM32中断优先级设置
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位,即最多256个不同的优先级。不过实际硬件一般没有这么多个中断号,也无需使用这么多中断源。例如STM32F103RBT6芯片上,只使用了其中4位来设置优先级。根据优先级组的设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。对4位的分配,使得STM32F103RB有以下几种优先级组模式配置。
优先级分组 |
抢占优先级 |
次优先级 |
第0组:NVIC_PriorityGroup_0 |
无(0位) |
0~15(4位) |
第1组:NVIC_PriorityGroup_1 |
0~1(1位) |
0~7(3位) |
第2组:NVIC_PriorityGroup_2 | 0~3(2位) |
0~3(2位) |
第3组:NVIC_PriorityGroup_3 | 0~7(3位) |
0~1(1位) |
第4组:NVIC_PriorityGroup_4 | 0~15(4位) |
无(0位) |
3.3 外部中断控制器(EXTI)与外部中断
在STM32内部专门设计了一个外部中断控制器(EXTI)管理一些涉及GPIO引脚电平变化或者来自RTC和USB等外设的唤醒事件而引起的中断,EXTI提供了23个外部中断线,其中EXTI_0~EXTI_15用于GPIO引脚产生的中断,其余EXTI_16 ~ EXTI_22用于RTC和USB等外设的唤醒事件
EXTI结构图:
通过GPIO引脚产生的中断简称为外部中断
4 基于Cubemx的外部中断嵌套实验
硬件平台:STM32F411RCT6-NANO开发板
编译环境:cubemx + keil5
1 Cubemx新建工程(略) 可参考【小趴菜STM32开发笔记1】---- GPIO
2 配置系统时钟
3 配置引脚
3.1 配置4个LED引脚为GPIO_Output
【Tip】自定义引脚标签
3.2 配置4个按键为中断模式(GPIO_EXTIx)
3.3 设置中断优先级
【这里中断号5和中断号9共用一条中断线】
4 生成代码(略)可参考【小趴菜STM32开发笔记1】---- GPIO
5 进入Keil,补充代码
5.1 HAL库的中断处理流程
在 MX_GPIO_Init() 中调用HAL库使能中断函数和设置中断优先级:
在 HAL_NVIC_EnableIRQ() 中调用 NVIC_EnableIRQ()
当触发中断时,处理器查表跳转到中断服务程序 EXTIx_IRQHandler(void)
中断服务程序 EXTIx_IRQHandler (void) 中调用外部中断通用处理函数HAL_GPIO_EXTI_IRQHandler (uint16_t GPIO_Pin) ,该函数作为HAL库提供的外部中断接口函数,参量为触发中断的引脚
系统自带了一个弱引用的中断回调函数,通过__weak修饰,没有任何执行代码,仅有一个避免编译警告的语句:UNUSED(GPIO_Pin)
需要在main.c中重写中断回调函数 HAL_GPIO_EXTI_Callback() ,并补全中断所需要执行的任务函数
5.2 工程源码
5.2.1 main函数
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
//定义4个计数变量,用于LED闪烁次数计数
uint8_t LED0_Count;
uint8_t LED1_Count;
uint8_t LED2_Count;
uint8_t LED3_Count;
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
//初始时刻4个LED同时闪烁
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin);
HAL_Delay(500);
}
}
5.2.2 中断回调函数
//微秒级的延时
void delay_us(uint32_t delay_us)
{
volatile unsigned int num;
volatile unsigned int t;
for (num = 0; num < delay_us; num++)
{
t = 11;
while (t != 0)
{
t--;
}
}
}
//毫秒级的延时
void delay_ms(uint16_t delay_ms)
{
volatile unsigned int num;
for (num = 0; num < delay_ms; num++)
{
delay_us(1000);
}
}
/* USER CODE BEGIN 4 */
//重写中断回调服务函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY0_Pin) //判断中断源为KEY0
{
while(KEY0_Pin ==0);
for(LED0_Count=0;LED0_Count<20;LED0_Count++) //翻转20次,闪烁10次
{
delay_ms(100);
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
}
if(GPIO_Pin == KEY1_Pin) //判断中断源为KEY1
{
while(KEY1_Pin ==0);
for(LED1_Count=0;LED1_Count<20;LED1_Count++)
{
delay_ms(100);
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
}
}
if(GPIO_Pin == KEY2_Pin) //判断中断源为KEY2
{
while(KEY2_Pin ==0);
for(LED2_Count=0;LED2_Count<20;LED2_Count++)
{
delay_ms(100);
HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
}
}
if(GPIO_Pin == KEY3_Pin) //判断中断源为KEY3
{
while(KEY3_Pin ==0);
for(LED3_Count=0;LED3_Count<20;LED3_Count++)
{
delay_ms(100);
HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin);
}
}
}
/* USER CODE END 4 */
这里在中断回调函数里调用了重构的阻塞延时函数,那为什么不直接用HAL库自带的HAL_Delay 进行延时呢?
答:因为HAL_Delay 的延时是通过嘀嗒定时器(system tick timer)的定时器中断实现的,而嘀嗒定时器的中断优先级在默认情况下是低于外部中断优先级的,因此在触发外部中断执行回调函数时,遇到优先级更低的HAL_Delay ,那么请问这时候低优先级的HAL_Delay 能否打断高优先级的中断抢先执行呢?肯定是不能的吧,因此程序就卡死在HAL_Delay这一步,无法往下继续运行。
当然,除了重构延时函数外,也可以增大嘀嗒定时器的中断优先级
6 实验现象
外部中断嵌套
4个按键分别控制4个LED翻转10次,key0和key1可以抢占key2和key3的执行