目录
基本概念
中断的意义
中断处理过程(背过)
中断体系结构
NVIC
1)管理中断事件(清除、挂起)
2)支持中断向量化处理(向量表)
3)支持中断嵌套 (优先级)
EXTI外部中断控制器
简介
EXTI的主要特性
框架流程图
按键中断编程步骤分析
原理图分析
CUBE配置
发送中断
要求
cube MX配置
程序编写
接收中断
为什么需要用中断进行接收
定长接收中断
串口不定长接收--->空闲中断实验
应用版
中断
基本概念
在处理器中,中断相当是对于突发事件的处理过程。
当遇到内部/外部的紧急事件需要处理时,暂时中止当前程序,转而去处理紧急事件,
待处理完毕后,再返回被打断的程序继续向下运行。
暂停:保护现场
继续:恢复现场
中断的意义
中断能够对突发事件进行及时处理,实现程序的并行化(时间片),进而提高CPU的工作效率。当发生突发事件时,比如外部触发的输入信号、定时器溢出等,中断会立即打断正在执行的程序,转而执行中断服务函数(ISR)来处理该事件。
关于并行化解释:
在STM32中,程序的并行化是指通过使用中断机制,使得当发生突发事件时,可以立即处理该事件,而不需要等待当前正在执行的程序完成。
传统的程序执行方式是顺序执行,即按照代码的顺序逐行执行。这种方式存在一个问题,即当程序执行某个任务时,如果发生了突发事件,程序需要等待当前任务执行完毕才能响应事件。这可能导致事件处理延迟,影响系统的响应速度。
而使用中断机制可以解决这个问题。当发生突发事件时,比如外部输入信号触发的中断,系统会立即打断当前正在执行的程序,转而执行与该事件相关的中断服务函数。中断服务函数可以快速响应事件并处理相应的逻辑,而无需等待当前任务的完成。
通过这种方式,程序的执行可以在某个任务被打断的同时,立即响应其他突发事件。这样就实现了程序的并行化处理,提高了系统的响应速度和效率。
为什么提高效率了?
因为CPU对于没有发生的事情是在不停的在轮询(询问有没有发生?)
中断能提高CPU的效率,同时能对突发事件做出实时处理。实现程序的并行化,实现嵌入式系统进程之间的切换
中断处理过程(背过)
中断处理过程
进入中断
处理器自动保存现场到堆栈里(堆栈——内存)
{PC, xPSR, R0-R3, R12, LR}
一旦入栈结束,ISR便可开始执行(中断服务程序)
退出中断
中断前的现场被自动从堆栈中恢复
一旦出栈完成,继续执行被中断打断的指令
出栈的过程也可被打断,使得随时可以响应新的中断而不再进行 现场保存
R0 - R12
R13 栈指针 SP : 指向栈顶地址
R14 链接寄存器 LR :存放返回地址
R15 程序计数器 PC :保存要执行的指令地址
示例:
老师正在讲课 (正常执行的主程序)
班主任叫我去开会 (产生一个外部中断信号)
暂停讲课并保存课堂笔记 (压栈保护现场)
根据会议室门牌号去开会 (根据中断向量表跳转到中断服务程序)
开会 (执行中断服务程序)
回到教师打开笔记继续讲课(恢复现场返回主程序继续执行)
中断体系结构
注:中断和异常的区别
•异常是在正常代码序列之外引起程序流控制更改的事件。当异常发生时,当前正在执行的程序被挂起,并执行与事件相关的一段代码(异常处理程序)。事件可以是外部的,也可以是内部的。当事件来自外部源时,它通常被称为中断或中断请求(IRQ)。几乎所有现代处理器都支持异常和中断。在微控制器中,中断也可以使用片上外设或通过软件产生。
•发生异常时执行的软件代码称为异常处理程序。如果异常处理程序与中断事件关联,那么它也可以被称为中断处理程序或中断服务程序(ISR)。异常处理程序是已编译程序映像中的程序代码的一部分。
当异常处理程序完成异常处理后,它将返回被中断的程序并恢复原始任务。因此,异常处理序列需要某种方式来存储被中断程序的状态,并允许在异常处理程序完成其任务后恢复该信息。这可以通过硬件机制来实现,也可以通过硬件和软件操作的组合来实现
NVIC
嵌套向量中断控制器(Nested Vectored Interrupt Controller)
参考手册(905页)
NVIC主要功能(背过)
负责管理中断 (CPU的小助理)
中断管理
支持异常及中断向量化处理
支持嵌套中断、优先级分配
NVIC的主要功能(开会举例):中断管理(开会)、向量化处理(到哪开会)、嵌套中断(高优先级的可以打断低优先级)
1)管理中断事件(清除、挂起)
每一个中断事件都有执行或禁止两种状态,由NVIC负责将中断事件标记为清除和挂起两种状态。 处理器的中断可以电平的形式的,也可以是脉冲形式的,这样中断控制器就可以处理任何中断源。
挂起:当置位中断挂起寄存器的时候,相应的中断将会被挂起,这是这个中断将不会立即执行,而是等待可执行的时候再执行;比如高低级别的中断同时产生,就先挂起低级别的中断,等高级别的中断执行完毕,解除并执行低级中断;
2)支持中断向量化处理(向量表)
当中断事件发生时,处理器会将PC(程序计数器 PC :保存要执行的指令地址)设置为一个特定地址(中断事件入口函数的地址),进而跳转到中断服务程序去执行,这就是个中断(异常)向量,因为每一个异常源或者中断事件都会对应一个服务程序的入口地址,将这些地址按照优先级进行排布后,组成的一张表就称为中断(异常)向量表。
3)支持中断嵌套 (优先级)
•NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
•抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
EXTI外部中断控制器
简介
EXTI(Extern Interrupt)外部中断 Intern
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
•支持的触发方式:上升沿/下降沿/双边沿/软件触发
•支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
•通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒等 26个输入事件
•触发响应方式:中断响应/事件响应
EXTI的主要特性
框架流程图
外部中断流程框图
编号1是信号输入线,EXTI支持产生多达26个外部事件/中断请求。
编号2是边沿检测电路,用于监测上升沿或下降沿信号。
它会根据上升沿触发选择寄存(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。
边沿检测电路以输入线作为信号输入端,监测是否有边沿跳变,检测到有边沿跳变输出有效信号 1 给编号 3 电路,否则输出无效信号0。
编号3是一个或门电路,信号来源是外部事件或者软件中断/事件寄存器产生。
允许我们通过程序控制EXTI_SWIER就可以启动中断/事件线
编号4是一个与门电路,信号来源是编号3送来的信号和中断屏蔽寄存器的值,
如果中断屏蔽寄存器为0,也不会将信号送到NVIC,
只有编号3送来了中断信号且中断屏蔽寄存器允许产生中断,才会将中断信号送入NVIC.
接下来我们来看看红色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。
产生事件线路是在编号3电路之后与中断线路有所不同,之前电路都是共用的。
编号5是一个与门电路,信号来源是编号3送来的信号和事件屏蔽寄存器的值,
如果事件屏蔽寄存器为0,不会将信号送到脉冲发生器,
只有编号3送来了信号且事件屏蔽寄存器允许产生事件,才会将信号送入脉冲发生器(编号6), 进而产生脉冲来控制外部设备做出动作。
这样我们可以简单的控制 EXTI_EMR 来实现是否要产生事件的目的
按键中断实验(外部中断)
按键中断编程步骤分析
按键中断控制风扇
原理图分析
CUBE配置
打开NVIC中断
找到中断处理函数
代码编写
串口中断实验(内部中断)
发送中断
要求
cube MX配置
程序编写
发送完成中断函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
接收中断
为什么需要用中断进行接收
在此串口控制数码管显示的代码执行过程中出现了如下问题:
在某些时刻,串口发送数据,并没有被接收,所以导致不能正确的接受指令并显示动作。
原因分析,此代码为轮询判断,当代码运行到延时位置时,如果此时串口发送,因为没有运行到接收函数,所以此时发送的数据不会被接收。
如果使用中断的形式进行接收,那无论程序运行到何处,只要串口接收一定数据都会立马打断当前程序的执行进入中断处理进行接收动作,所以使用中断的形式可以规避上面的问题。
串口接收中断,分为两种,一种是定长接收
定长接收中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
串口不定长接收--->空闲中断实验
当接收到数据之后会触发串口中断但是你确定不了,发送方是否发完数据,所以在接收中断的基础上加了一个空闲线的中断,使用空闲线中断确定数据是否传输完成,数据传输完成之后,就可以处理数据了
全局变量的定义
/* USER CODE BEGIN 0 */
uint8_t USART1_RxBuff[1024];
uint8_t USART1_RxCounter;
/* USER CODE END 0 */
- main函数while上面下使能函数
HAL_UART_Receive_IT(&huart1, USART1_RxBuff,1024);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//空闲中断
- 中断服务程序内写
extern uint8_t USART1_RxBuff[1024];
extern uint8_t USART1_RxCounter;
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET)//判断是否触发串口空闲中断{//int datalen = 0;//用于存储接收数据长度,这里的没有用,如果有需要可以弄全局变量__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除空闲中断标志
// datalen = 1024 - huart1.RxXferCount; //接收到多少数据//HAL_UART_Transmit(&huart1,(uint8_t*)USART1_RxBuff,datalen,10);//验证是否收到,正式功能里注释掉//memset(USART1_RxBuff,0,1024);//正式功能可以哪里使用数据,使用完清除
huart1.RxXferCount = 1024;//初始化接收的最大数据量
huart1.pRxBuffPtr = USART1_RxBuff;//定义缓冲区
USART1_RxCounter=1;//标识标识数据接收完毕HAL_UART_Receive_IT(&huart1, USART1_RxBuff,1024);}
应用版
main.c的修改
volatile uint8_t USART1_RxBuff[1024];
volatile uint8_t USART1_RxCounter=0;
HAL_UART_Receive_IT(&huart1, USART1_RxBuff,1024);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//空闲中断
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(USART1_RxCounter)
{
printf("正在处理数据:%s.....\n",USART1_RxBuff);
USART1_RxCounter=0;
memset( USART1_RxBuff,0,1024);
}
}
/* USER CODE END 3 */
中断服务程序修改
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
extern uint8_t USART1_RxBuff[1024];
extern uint8_t USART1_RxCounter;
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET)
//判断是否触发串口空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除空闲中断标志
huart1.RxXferCount = 1024;//初始化接收的最大数据量
huart1.pRxBuffPtr = USART1_RxBuff;//定义缓冲区
USART1_RxCounter=1;//标识标识数据接收完毕
HAL_UART_Receive_IT(&huart1, USART1_RxBuff,1024);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}