HAL库中STM32F7的串口中断响应过程简析

电子说

1.2w人已加入

描述

中断是计算机系统最重要的组成机制之一,在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,重新生成代码。

FreeRTOS

需要说明的是,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的中断服务程序。

FreeRTOS

由上图可以看出,USART1的中断向量偏移地址为0x000000D4,默认是从零地址开始偏移,所以实际地址也为0x000000D4。中断向量表这个地址上存储的中断向量是中断服务程序USART1_IRQHandler的入口地址。看过之前文章关于启动代码的分析就应该知道,在启动代码里定义好了中断向量表,中断向量地址是由链接器生成符号地址后装入中断向量表的。

那么接下来的调用过程是这样的:

FreeRTOS

5. 小结

基于HAL库的串口中断的基本流程就是这样,但是并没有深入去查看相关库函数的实现过程,想全面掌握的话还需要去仔细阅读库函数源码。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分