中断是计算机系统最重要的组成机制之一,在ARM架构里,通常称为异常(Exception),在文档里是这么说的:
An exception can be caused by the execution of an exception generating instruction or triggered as a response to a system behavior such as an interrupt, memory management protection violation, alignment or bus fault, or a debug event.
意思是异常是由某些能够生成异常的指令(例如:SVC)或者是响应外部中断、内存冲突、对齐或总线错误、调试等系统行为导致的。
所以,在ARM架构里,中断指的是外设产生的需要系统优先处理的事件,是异常的一种。异常由NVIC(Nested Vectored Interrupt Controller)模块统一管理。
关于中断和异常的工作原理,在ARM架构下的工作方式等这里不做展开,需要复习这些知识点的建议找ARM内核架构的文档去看。这里主要以HAL库中STM32F7的串口中断响应过程为例,来看一下中断到底是怎么工作的,为什么能够提高系统运行效率。
1. CubeMX配置串口1工作在中断模式下
还是之前的点灯例程,按下图配置串口1,重新生成代码。
需要说明的是,ARM的中断优先级分为抢占式优先级和子优先级,STM32采用4个优先级位,也即4个优先级位都为抢占式优先级(FreeRTOS就是这样处理的)时,总共有16个优先级别,数值越小优先级越高。这里默认就行。
2. 生成代码分析
在生成的工程里Core->Src目录下,会多一个usart.c的源文件,里边有下面三个函数,把代码注释写出来:
//串口1初始化函数
void MX_USART1_UART_Init(void)
{
//设置串口1工作参数:115200bps,1个起始位,8个数据位,1个停止位,无校验位
//调用HAL的串口初始化函数HAL_UART_Init(&huart1)
}
//串口的MSP(MCU Support Package)初始化,这是与硬件相关的初始化,单独提出来,方便移植
//这是一个回调函数,会被HAL的串口初始化函数调用
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
//串口1相关时钟使能
//串口1相关GPIO初始化
//设置NVIC,使能串口1中断
}
//串口MSP的反初始化,调用这个函数会使串口失能,相关的时钟、引脚和中断恢复到复位状态
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
//失能串口1时钟
//失能串口1引脚
//失能串口1中断
}
在main.c中调用串口1初始化函数,程序运行时完成串口1的初始化。
另外,在stm32f7xx.c中,增加了下面的函数:
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
这是串口1 的中断服务程序,通过调用HAL库的串口中断处理程序HAL_UART_IRQHandler完成中断响应。这个中断服务程序完全可以针对该串口完成的具体功能自己去写,效率更高。调用HAL库完成中断处理更简单方便,可移植性更好。
另外要注意的是,调用HAL库完成中断处理的话,还需要自己重写中断处理的回调函数,HAL库里的回调函数是一个弱函数,本身并没有实现任何功能。这也很好理解,每个应用的需求都不同,不可能写出一个通用的中断处理函数。而且有一个回调用的弱函数在,就算是用户程序没有实现,也不会导致程序出错。
3. 添加代码实现功能
假设要实现一个最简单的情况,串口每接收到一个字节的数据,非n则计数器RxCounter加1,否则计数器清零。为了方便观察,我们按下边这样实现。
在main.c中定义两个全局变量,并申明usart.c中定义的串口1的句柄,如下:
uint8_t* Uart1RxBuff = 0;
uint8_t RxCounter = 0;
extern UART_HandleTypeDef huart1;
并在main函数的while循环前加如下代码,实现串口1每接收一个字节产生中断,接收的数据存放在Uart1RxBuff中。
if (HAL_UART_Receive_IT(&huart1, Uart1RxBuff, 1) != HAL_OK)
{
Error_Handler();
}
在usart.c中,声明main.c中定义的两个全局变量:
extern uint8_t* Uart1RxBuff;
extern uint8_t RxCounter;
并重新实现接收中断的回调函数如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/*判断是否是串口1中断*/
if(huart- >Instance == USART1)
{
/*判断接收的数据是否为n*/
if(*Uart1RxBuff != 'n')
{
RxCounter ++;
}
else
{
RxCounter = 0;
}
/*重新使能串口1接收中断*/
HAL_UART_Receive_IT(&huart1, Uart1RxBuff, 1);
}
}
而HAL库中的接收回调弱函数的代码如下:
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback can be implemented in the user file.
*/
}
另外, UNUSED是一个宏,定义如下:
#define UNUSED(X) (void)X /* To avoid gcc/g++ warnings */
好了,到此就完成了一个简单的串口接收中断处理的任务。能够判断接收的有效字符数。
4. 中断响应过程分析
串口1接收到一个字节的数据后,USART_ISR寄存器的RXNE位置1,如果对应的接收中断使能的话,则会向NVIC产生一个中断请求,NVIC根据中断源(USART1)去中断向量表相应的地址上找到中断向量(中断服务程序的入口地址),执行串口1的中断服务程序。
由上图可以看出,USART1的中断向量偏移地址为0x000000D4,默认是从零地址开始偏移,所以实际地址也为0x000000D4。中断向量表这个地址上存储的中断向量是中断服务程序USART1_IRQHandler的入口地址。看过之前文章关于启动代码的分析就应该知道,在启动代码里定义好了中断向量表,中断向量地址是由链接器生成符号地址后装入中断向量表的。
那么接下来的调用过程是这样的:
5. 小结
基于HAL库的串口中断的基本流程就是这样,但是并没有深入去查看相关库函数的实现过程,想全面掌握的话还需要去仔细阅读库函数源码。
-
FreeRTOS
+关注
关注
12文章
483浏览量
62045 -
串口中断
+关注
关注
0文章
64浏览量
13864 -
STM32F7
+关注
关注
1文章
48浏览量
8951 -
中断响应
+关注
关注
0文章
11浏览量
2950 -
HAL库
+关注
关注
1文章
121浏览量
6189
发布评论请先 登录
相关推荐
评论