目录

Day07_STM32-单片机-中断

目录

Day07_STM32 单片机 - 中断

一、处理器的工作模式

1.1 Cortex-M 核工作模式


1.异常模式
2.线程模式

1.2 Cortex-A 核工作模式


ARM-v7架构:
    1.非特权模式:User模式
    2.特权模式:
        2.1 非异常模式:Sys模式
        2.2 异常模式:
            IRQ模式 - 普通中断异常模式
            FIQ模式 - 快速中断异常模式
            SVC模式 - 超级管理异常模式
            ABT模式 - 中止访问异常模式
            UDF模式 - 未定义异常模式

ARM-v8架构:
    多两种:MON模式 - 安全监管模式、HYP模式 - 虚拟化技术模式

二、异常处理流程

https://i-blog.csdnimg.cn/direct/604c176401ea4153a2bb3f6169d24484.png

图 1 异常处理流程图

2.1 异常源

异常源:触发处理器执行异常事件对应的处理逻辑的源头(集合);

异常源支持很多异常事件。

=================================================================

  1. 5 种异常模式对应 7 种异常源;
  2. 每种异常源存在很多异常事件 - 异常源就是异常事件的集合;
  3. SVC 模式下复位异常源优先等级最高处理器优先处理

https://i-blog.csdnimg.cn/direct/b7882c16680c47789bea4b16d66cac41.png

2.2 自动保护现场 - CPU 自动完成

自动保护现场需要做 4 件事

1.将 CPSR 寄存器中的值保存到 SPSR_ 寄存器中
2.修改 CPSR 寄存器中的位:
        2.1 修改 CPSR 寄存器中的 I / F 位(是否屏蔽 IRQ / FIQ 模式);
        2.2 修改 CPSR 寄存器中的 T 位(是否使用 ARM / Thumb 汇编指令集);
        2.3 修改 CPSR 寄存器中的 M 位(进入对应的异常模式)。
3.将函数的返回地址保存到 LR 寄存器中;
4.PC 寄存器指向异常向量表。

2.3 手动恢复现场 - 程序员手动完成

  1. 将 SPSR_ 寄存器中的值恢复到 CPSR 寄存器中;
  2. 将 LR 寄存器中的值赋值给 PC 寄存器。

2.4 异常向量表

地址    异常类型
0x1C    FIQ
0x18    IRQ
0x14    (Reserved)
0x10    Data Abort
0x0C    Prefetch Abort
0x08    Software Interrupt
0x04    Undefined Instruction
0x00    Reset


异常向量表:异常源的集合

        当发生异常事件后,处理器检测当前异常事件所属异常源,就需要给各个异常源分配不同的内存空间地址,处理器去寻址查找。

异常向量表占内存空间的 32 个字节每个异常源占 4 个字节空间,同时有 4 个字节的空间保留。


https://i-blog.csdnimg.cn/direct/c72f76c0933e4efd83c7b11edfb086b2.png

图 2 异常处理的流程

为什么引入异常向量表

在异常出发后,最后是需要执行异常处理的函数;

        而在开发板 / 系统上电复位后,函数的地址、参数的地址、变量的地址可能发生改变,对于 CPU 无从知晓变化后的地址,所以可以固定一片内存空间地址,引导 CPU 找到异常处理函数。

ARM 公司在开发并生产内核时,固定一片 32 个字节的地址空间,存放各个异常源的地址,称为异常向量表

2.5 汇编代码模拟异常处理流程


.text		
 
.global _start		
	
	_start:			
		@ 构建异常向量表
		b reset_handler
		b undefined_handler
		b swi_handler
		b prefetch_handler
		b data_handler
		b .	@ 占4个字节空间操作
		b irq_handler
		b fiq_handler
 
reset_handler:
	@ 为SVC模式下的SP寄存器给定一个内存空间地址
	ldr sp, =0x40000820
	
	@ 从SVC模式切换到User模式下,执行用户代码
	msr cpsr, #0xD0
	
	@ 用户代码,以下相当于main函数
	mov r0, #0x1
	mov r1, #0x2
	
	@ 软中断异常事件触发
	@ swi 2这条汇编指令执行完毕后,需要看到的效果
	@ 1、CPSR寄存器中的值是否被保存到SPSR寄存器中
	@ 2、CPSR寄存器中的值是否被改变(IFTM位)
	@ 3、LR寄存器中是否保存函数的返回地址
	@ 4、PC是否指向异常向量表中软中断异常源的地址
	swi 2
	
	add r2, r1, r0
	
 
undefined_handler:
 
swi_handler:
	@ 压栈保存现场,保存局部变量和函数返回地址
	stmfd sp!, {r0-r1, lr}
	
	@ 软中断异常源下第一个异常事件的处理函数
	mov r0, #0xff
	mov r1, #0xee
	add r2, r1, r0
	
	@ 出栈恢复现场,恢复局部变量和函数返回地址
	@ 还需要恢复SPSR寄存器中的值给到CPSR寄存器
	@ ^的作用:用于将SPSR寄存器中的值赋值给CPSR寄存器
	ldmfd sp!, {r0-r1, pc}^
	
prefetch_handler:
 
data_handler:
 
irq_handler:
 
fiq_handler:
	
	
	stop:			
		
		b stop		
.end		

当异常发生时,CPU 根据异常的类型,找到异常向量表中对应异常源的固定地址,随后跳转到改地址去执行相应的异常处理函数,这和异常源的名字无关,名字只是帮助开发者理解的符号。


三、分析按键的电路图


KEY1 - PC9
KEY2 - PC8
KEY3 - PC5

https://i-blog.csdnimg.cn/direct/175f6ecb86c14de2b41c7d4f45d6deb0.png

图 3 按键抖动示意图

        GPIO 检测按键的抬起和按下低电平),中断检测按键抬起 / 按下的时机上升沿下降沿)。

四、分析芯片手册

https://i-blog.csdnimg.cn/direct/154e5115abf04183b1aa2af94157d966.png

图 4 NVIC 和 EXTI

EXTI:外部中断和事件控制器

NVIC:嵌套向量中断控制器

嵌套:中断的嵌套,指的是中断具备优先级等级,高优先级等级的中断信号可以打断低优先级的中断信号。

向量:所有异常中断统一由向量表进行管理。


在使用 Cortex-M 核(MCU),中断处理一般使用 EXTI + NVIC 来实现

在使用 Cortex-A 核(MPU),中断处理一般使用 EXTI + GIC(GICC+CPID) 来实现

4.1 EXTI 介绍

1)介绍

https://i-blog.csdnimg.cn/direct/a4a16e5b2f284cc184a465db53dd4851.png

图 5 EXTI 介绍

        外部中断和事件控制器(EXTI)通过可配置的事件输入来管理各个 CPU 和系统唤醒。它向电源控制提供唤醒请求,并向 CPU NVIC 生成中断请求以及向 CPU 事件输入生成事件。对于 CPU,需要额外的事件生成模块(EVG)来生成CPU事件信号。

2)主要特性

https://i-blog.csdnimg.cn/direct/39db3ad85ef34159b64124bfd5ed75a9.png

图 6 EXTI 主要特性

  • 支持 26 个输入事件编号;
  • 可选择的边沿触发方式 - 上升沿触发、下降沿触发、上升沿下降沿都触发;
  • 中断挂起状态标志位 - 上升沿和下降沿的中断状态在硬件层面被分开记录和处理;
  • 支持中断信号和输入事件的屏蔽功能;
  • 支持多个 GPIO 端口的选择。

4.2 输入事件编号

https://i-blog.csdnimg.cn/direct/96c691de3300443e8dd3772090eef6d5.png

图 7 输入事件编号列表

输入事件编号(Event Input Number)是硬件层用于唯一标识外部中断/事件(EXTI)源的数字ID,每个编号对应一条独立的EXTI线(如编号0对应EXTI[0])。其核心作用是通过数字映射将物理引脚的电平变化与系统中断/事件处理逻辑精准关联。

4.3 EXTI 内部框图

https://i-blog.csdnimg.cn/direct/d0fa93ac79ed4dc9b7d8c5b4de1a9605.png

图 8 EXTI 内部框图

https://i-blog.csdnimg.cn/direct/96a12ec61a184cb283f9a812b6444c67.png

图 10 EXTI 内部框图描述

  EXTI 外设控制器中有一个寄存器块,这个寄存器块被连接在 AHB3 总线上(EXTI 被连接在 AHB3 总线上)。
        输入事件触发块用于提供输入事件边沿触发的逻辑,也就是需要设置输入事件的触发方式为上升沿触发 / 下降沿触发 / 双边沿触发。
        输入事件屏蔽块用于提供输入事件的分配(可以分配给 PWR 用于系统唤醒、可以分配给 CPU 下的 NVIC 用于中断控制、也可以分配给 EVG 用于事件输出、还可以把上述操作屏蔽掉)。
        IO 端口选择块提供了 GPIO 端口选择的功能(也就是让 EXTI 知道是哪个 GPIO 端口产生了中断信号)。

4.4 寄存器功能分析

https://i-blog.csdnimg.cn/direct/d71d5f64080a47688648afe0fc84d270.png

图 11 EXTI 功能描述

中断的挂起:中断信号到达,等待被处理,需要执行这个中断信号对应的逻辑代码。也就是执行中断信号对应逻辑代码前,需要判断这个中断信号是否发生,即这个中断是否被挂起。

4.5 GPIO 端口选择

https://i-blog.csdnimg.cn/direct/d7d04df70e5442c0a545423614eea0f3.png

图 12 EXTI 多路选择(GPIO 端口选择)

4.6 寄存器分析

1)EXTI_FTSR1 寄存器

https://i-blog.csdnimg.cn/direct/1152e776287e4b429f0a38935a0c536f.png

图 13 EXTI_FTSR1

下降沿触发选择寄存器

写入 0:PC9 引脚下降沿触发不使能
写入 1:PC9 引脚下降沿触发使能

2)EXTI_FPR1 寄存器

https://i-blog.csdnimg.cn/direct/10749cdf5b1e4bd0b7f45a8bf368a250.png

图 14 EXTI_FPR1

https://i-blog.csdnimg.cn/direct/00e59fa574914f09894420330f7ea040.png

图 15

下降沿触发挂起寄存器

rc_w1权限:
r    c_w1
写入0:
        没有影响
**写入1:**人为清除对应的值


**读 0:**没有下降沿触发请求存在
        也就是下降沿没有触发,中断没有挂起
**读 1:**下降沿触发请求存在
        也就是下降沿触发,中断被挂起


        当我们检测按键是否按下时,也就是检测中断是否被挂起,也就是读取这个寄存器对应位是否为 1:

读取到 1:就代表中断被挂起,可以执行处理逻辑

读到到 0:就代表中断没有被挂起,不需要执行处理逻辑

        当我们读取到中断被挂起后,为了让中断不一直处于被挂起状态,需要人为手动解除中断的挂起。

3)EXTI_EXTICRm 寄存器

https://i-blog.csdnimg.cn/direct/4eeabfcc2a8543a791328a578301b830.png

图 16 EXTI_EXTICRm

https://i-blog.csdnimg.cn/direct/e8ba9acdf1a24dc1bb805da5edd3bcd5.png

图 17 GPIO 端口的选择

         EXTI_CRm 寄存器有 4 个(m = 1 ~ 4),一个寄存器管理 4 个 EXTI 线,其中每 8 位管理一个 EXTI 线(如 PA0、PB0……、PJ0 共属一条 EXTI0 线,往 8 位写入从 0x00 ~ 0x09 可以选择从 A ~ J 的对应引脚触发中断。

4)EXTI_IMR1 寄存器

https://i-blog.csdnimg.cn/direct/38c05f97c07e4c269b4ecf24a39c90ab.png

图 18 EXTI_IMR1

EXTI 中断屏蔽寄存器

按键 KEY1 的引脚是 PC9,所属中断线为 EXTI9,对应输入事件的编号是 9.

https://i-blog.csdnimg.cn/direct/a732262adf064eb89c63b8f3e0d9c5a6.png

图 19 EXTI_IMR1 - IM9 位

  写入 0 是屏蔽对应输入事件产生的中断的信号,所以我们需要向 Bit 9 写入 0b1使能 EXTI9 的中断信号。

4.7 NVIC 章节

https://i-blog.csdnimg.cn/direct/1b699a0d739f492f87c9c8b4f5211ba7.png

图 20 NVIC 嵌套向量控制器主要特性

中断号:每个中断信号例如 EXTI0 ~ EXTI15 在被 NVIC 检测时,都具有自己的一个唯一标识符,就是中断号。

NVIC 支持 140 个可屏蔽的中断通道以及 16 个可编程控制的优先级等级。

优先级等级的数字越小,代表优先级越高

https://i-blog.csdnimg.cn/direct/ba609e118d824334858efa8f7f97b9cd.png

图 21 NVIC 嵌套向量表


五、CubeMX 配置

https://i-blog.csdnimg.cn/direct/d6a1b5398bcb4397b1b1f4d07ee32f52.png

图 22 步骤 1

https://i-blog.csdnimg.cn/direct/205aba50350b471f94bb1568f4d4e0b2.png

图 23 步骤 2

NVIC 的作用:

  1. 使能对应的中断信号(是否屏蔽中断信号)
  2. 为对应的中断信号设置优先级等级
  3. 检测中断信号是否触发(中断信号挂起寄存器)

抢占式优先级等级(Preemption Priority)

        也就是中断优先级等级,分为 0 - 15,数字越小,抢占式优先级等级越高。

响应式优先级等级(Sub Priority)

        由于抢占式优先级等级只有 0 - 15,对于中断信号过多的情况,会存在抢占式优先级等级相同的情况,可以设置一个次优先级等级进行二次划分。

https://i-blog.csdnimg.cn/direct/ba3fb7a20cf141eaaf24c0b38683928c.png

图 24 步骤 3

https://i-blog.csdnimg.cn/direct/01f1062230fb479cb9349262480e3e9c.png

图 25 步骤 4

https://i-blog.csdnimg.cn/direct/562ac63ff17c48ccb3166c75bc75114e.png

图 26 步骤 5


六、逻辑代码


                DCD     EXTI0_IRQHandler                 ; EXTI Line0 interrupt
                DCD     EXTI1_IRQHandler                 ; EXTI Line1 interrupt
                DCD     EXTI2_IRQHandler                 ; EXTI Line2 interrupt
                DCD     EXTI3_IRQHandler                 ; EXTI Line3 interrupt
                DCD     EXTI4_IRQHandler                 ; EXTI Line4 interrupt
                DCD     EXTI5_IRQHandler                 ; EXTI Line5 interrupt
                DCD     EXTI6_IRQHandler                 ; EXTI Line6 interrupt
                DCD     EXTI7_IRQHandler                 ; EXTI Line7 interrupt
                DCD     EXTI8_IRQHandler                 ; EXTI Line8 interrupt
                DCD     EXTI9_IRQHandler                 ; EXTI Line9 interrupt
                DCD     EXTI10_IRQHandler                ; EXTI Line10 interrupt
                DCD     EXTI11_IRQHandler                ; EXTI Line11 interrupt
                DCD     EXTI12_IRQHandler                ; EXTI Line12 interrupt
                DCD     EXTI13_IRQHandler                ; EXTI Line13 interrupt
                DCD     EXTI14_IRQHandler                ; EXTI Line14 interrupt
                DCD     EXTI15_IRQHandler                ; EXTI Line15 interrupt

在 startup_stm32u575xx.s 汇编文件中,EXTI9_Handler 就是 EXTi9 异常源对应的异常处理函数。



void EXTI9_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI9_IRQn 0 */
 
  /* USER CODE END EXTI9_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
  /* USER CODE BEGIN EXTI9_IRQn 1 */
 
  /* USER CODE END EXTI9_IRQn 1 */
}

HAL_GPIO_EXTI_IRQHandler(GPIO_PIN); 是 HAL 库提供的处理 EXTI9 异常源产生中断的中断处理函数。


void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_RISING_IT(GPIO_Pin) != 0U)
  {
    __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Rising_Callback(GPIO_Pin);
  }
 
  if (__HAL_GPIO_EXTI_GET_FALLING_IT(GPIO_Pin) != 0U)    // 判断下降沿中断信号是否触发 == 下降沿中断信号被挂起
                                                         // if( EXTI->FPR1 & (0x1 << 9) == 1)                                                         
  {
    __HAL_GPIO_EXTI_CLEAR_FALLING_IT(GPIO_Pin);    // 清除下降沿中断挂起标志位
                                                    // EXTI->FPR1 |= (0x1 << 9);
    HAL_GPIO_EXTI_Falling_Callback(GPIO_Pin);      // 下降沿中断信号触发后,需要执行的逻辑代码的回调函数
  }
}

__weak void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin) 使用 __weak 修饰的函数,称之为弱函数;

__weak的作用:使用__weak修饰的函数,表示当前函数可以被重写。

6.2 API 接口

1)HAL_GPIO_EXTI_Falling_Callback 下降沿触发回调函数


__weak void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
功能:

        HAL 库提供的用于执行下降沿中断信号触发后需要执行的弱函数(支持被重写)

参数:

        GPIO_Pin:触发下下降沿中断信号的GPIO引脚编号

返回值:

        无

2)HAL_GPIO_EXTI_Rising_Callback 上升沿触发回调函数


__weak void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
功能:

        HAL 库提供的用于执行上降沿中断信号触发后需要执行的弱函数(支持被重写)

参数:

        GPIO_Pin:触发上升沿中断信号的GPIO引脚编号

返回值:

        无

6.3 HAL 库代码编写

在 stm32u5xx_it.c 文件中重写上升沿和下降沿触发回调函数:


void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_9)
    {
		HAL_GPIO_WritePin(GPIOC,GPIO_PIN_4,0);
    }
}
 
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
     if(GPIO_Pin == GPIO_PIN_9)
    {
		HAL_GPIO_WritePin(GPIOC,GPIO_PIN_4,1);
    }
}

6.4 标准库代码编写


void EXTI9_IRQHandler(void)
{
    /* USER CODE BEGIN EXTI9_IRQn 0 */
    
    // 此处,可以直接写异常处理函数
    // 判断PC9引脚的下降沿中断信号是否触发 == 获取下降沿中断挂起标志位的值
    if ((EXTI->FPR1 & (0x1 << 9)) != 0) // if((EXTI->FPR1 & (0x1 << 9)) == 0x200)
    {
        // 进入if语句,代表下降沿中断触发
        // 清除PC9下降沿中断挂起标志位
        EXTI->FPR1 |= (0x1 << 9);
        
        // 执行PC9下降沿中断触发后的代码逻辑 == 点亮LED灯(PC4)
        GPIOC->ODR |= (0x1 << 4);
    }
    
    // 判断PC9引脚的上升沿中断信号是否触发 == 获取上升沿中断挂起标志位的值
    if ((EXTI->RPR1 & (0x1 << 9)) != 0) // if((EXTI->RPR1 & (0x1 << 9)) == 0x200)
    {
        // 进入if语句,代表上升沿沿中断触发
        // 清除PC9上升沿中断挂起标志位
        EXTI->RPR1 |= (0x1 << 9);
        
        // 执行PC9上升沿中断触发后的代码逻辑 == 熄灭LED灯(PC4)
        GPIOC->ODR &= (~(0x1 << 4));
    }
    /* USER CODE END EXTI9_IRQn 0 */
    // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
    /* USER CODE BEGIN EXTI9_IRQn 1 */
    
    /* USER CODE END EXTI9_IRQn 1 */
}

6.5 中断的处理流程

自动保护现场(CPU 自动完成)

异常向量表对应异常源


DCD     EXTI9_IRQHandler                 ; EXTI Line9 interrupt

异常源的对应处理函数


void EXTI9_IRQHandler(void)

异常事件处理函数


void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_RISING_IT(GPIO_Pin) != 0U)
  {
    __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Rising_Callback(GPIO_Pin);
  }
 
  if (__HAL_GPIO_EXTI_GET_FALLING_IT(GPIO_Pin) != 0U)    // 判断下降沿中断信号是否触发 == 下降沿中断信号被挂起
                                                         // if( EXTI->FPR1 & (0x1 << 9) == 1)                                                         
  {
    __HAL_GPIO_EXTI_CLEAR_FALLING_IT(GPIO_Pin);    // 清除下降沿中断挂起标志位
                                                    // EXTI->FPR1 |= (0x1 << 9);
    HAL_GPIO_EXTI_Falling_Callback(GPIO_Pin);      // 下降沿中断信号触发后,需要执行的逻辑代码的回调函数
  }
}

七、串口中断

7.0 准备工作

https://i-blog.csdnimg.cn/direct/b3b42380621a4d8fb4bbb506321e96b0.png

图 27 配置 USART1 中断使能

7.1 串口发送中断(不常使用)

1)API 接口

HAL_UART_Transmit_IT

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)

功能:

        HAL 库提供的用于通过串口进行中断的方式发送数据的函数。

参数:

        huart:USART1 的句柄

        pData:需要发送的数据

        Size:需要发送的数据大小,单位字节

返回值:

        成功返回 HAL_OK

        失败返回错误码

HAL_UART_TxCpltCallback

__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

功能

        HAL 库提供的用于串口发送中断触发后,执行的回调函数(可以被重写);

        当串口发送中断函数执行完毕后,会自动调用这个函数;

        当使用串口发送中断函数发送完完整数据后,此时发送中断的中断信号触发,这个函数才被执行。

参数

        huart:USART1 的句柄

返回值

        无


__weak void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)

        当使用串口发送中断函数发送一半数据后,此时发送中断的中断信号触发,这个函数被执行。

2)测试代码

在 stm32u575xx_it.c 文件中重写函数:

//串口发送中断对应的回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	HAL_UART_Transmit(&huart1,(uint8_t*)"nihao",sizeof("nihao"),1);
}
main.c 的 while(1) 中:

  while(1)
  {
		HAL_UART_Transmit_IT(&huart1,(uint8_t*)"111",3);
		HAL_Delay(1000);
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }

3)测试效果

https://i-blog.csdnimg.cn/direct/224ca8f8ec964ff38b86911a6ae92c13.png

图 28 串口发送中断运行结果

4)执行流程

自动保护现场(CPU 自动完成)

异常向量表对应异常源


DCD     USART1_IRQHandler                ; USART1 global interrupt

异常源的对应处理函数


void EXTI9_IRQHandler(void)

异常事件处理函数


void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_RISING_IT(GPIO_Pin) != 0U)
  {
    __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Rising_Callback(GPIO_Pin);
  }
 
  if (__HAL_GPIO_EXTI_GET_FALLING_IT(GPIO_Pin) != 0U)    // 判断下降沿中断信号是否触发 == 下降沿中断信号被挂起
                                                         // if( EXTI->FPR1 & (0x1 << 9) == 1)                                                         
  {
    __HAL_GPIO_EXTI_CLEAR_FALLING_IT(GPIO_Pin);    // 清除下降沿中断挂起标志位
                                                    // EXTI->FPR1 |= (0x1 << 9);
    HAL_GPIO_EXTI_Falling_Callback(GPIO_Pin);      // 下降沿中断信号触发后,需要执行的逻辑代码的回调函数
  }
}

发送完成中断函数


static void UART_EndTransmit_IT(UART_HandleTypeDef *huart)

发送完成中断回调函数


__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

7.2 串口接收中断(经常使用)

串口接收中断实现 - 随时随地接收数据

1)API 接口

HAL_UART_Receive_IT


HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能
        HAL 库提供的用于开启串口接收中断,使用中断方式解说数据的函数。

参数
        huart:USART1 句柄对象

        pData:需要接收的数据

        Size:接收的数据字节大小,单位字节

返回值
        成功返回 HAL_OK

        失败返回错误码

HAL_UART_RxCpltCallback


__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

功能
        HAL 库提供的用于串口接收中断触发后,执行的回调函数,可以重写;

        当串口通过中断的方式接收完完整数据后,此时接收中断信号触发,这个函数被执行。

参数
        huart:USART1 句柄对象

返回值
        无

2)测试代码

在 main.c 中全局定义一个数据接收容器

uint8_t buf[5]={0}
在 main.c 中在开启串行接收中断

// 开启串口接收中断,并使用串口接收中断接收数据
HAL_UART_Receive_IT(&huart1, buf, sizeof(buf));
在 stm32u5xx_it.c 中重写 HAL_UART_RxCpltCallback 函数

// 串口接收中断对应的回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 回显接收到的数据
    extern uint8_t buf[5];
    HAL_UART_Transmit(&huart1, buf, sizeof(buf), 3);
    memset(buf, 0, sizeof(buf));
 
    // 再次开启串口接收中断,用于下一次数据接收
    HAL_UART_Receive_IT(&huart1, buf, sizeof(buf));
}
/* USER CODE END 1 */

3)测试效果

https://i-blog.csdnimg.cn/direct/af32506311e84097896d3567462d25a5.png

图 29 串口接收中断运行效果

7.3 串口的空闲中断(IDLE)(经常使用)

串口空闲中断实现 随时随地接收不定长接收数据  -  串口的接收中断 + 串口的空闲中断

串口的空闲中断:接收数据时,数据长度是未知的,使用空闲中断可以实现不定长数据的接收。

1)USART_CR1 寄存器

https://i-blog.csdnimg.cn/direct/3e566504fa5f4177abae8481f8e9bfd9.png

图 30 USART_CR1

https://i-blog.csdnimg.cn/direct/f544e6674edb4fd1add1d4551cbd5a97.png

图 31 IDLEIE 空闲中断状态使能位

USART_CR1 寄存器的第 4 位是 IDLEIE 空闲中断状态使能位:
        写入 0:不使能空闲中断状态标志位

写入 1:使能空闲中断状态标志位 - USART_ISR 寄存器的 Bit 4 IDLE 位

2)USART_ISR 寄存器

https://i-blog.csdnimg.cn/direct/d11c077d0ffc4a598db6c11960202672.png

图 32 USART_ISR

https://i-blog.csdnimg.cn/direct/7c4defd0012545b0b7f4478497f0e793.png

图 33 IDLE 空闲中断状态标志位

USART_ISR 寄存器的第 4 位是 IDLE 空闲中断状态标志位:
       读到 0:空闲中断信号未产生 - 数据没有到达或没有接收完毕

读到 1:空闲中断信号已产生 - 数据已经到达,并接收完毕

3)USART_ICR 寄存器

https://i-blog.csdnimg.cn/direct/097c1710f10148388a82311830a860ec.png

图 34 USART_ICR

https://i-blog.csdnimg.cn/direct/a3b8910ea87141b992c697f941c9886b.png

图 35 IDLECF 清除空闲中断状态标志位

USART_ICR 寄存器的第 4 位 IDLECF 是清除空闲中断状态标志位:
 写入 1:清除 USART_ISR 寄存器中的空闲中断状态标志位

4)空闲中断原理

https://i-blog.csdnimg.cn/direct/632ca3e894d847bdae47cde43d5523af.png

图 36 空闲中断原理

5)API 接口

使用空闲中断标志位的步骤:
  1. 使能空闲中断状态标志位;
  2. 读取空闲中断状态标志位是否为 1 ;
  3. 如果是被置 1,先清除空闲中断状态标志位,方便下一次被硬件置 1 ;
  4. 执行中断后逻辑。
__HAL_UART_ENABLE_IT 中断 / 状态标志位使能宏函数

/** @brief  Enable the specified UART interrupt.
  * @param  __HANDLE__ specifies the UART Handle.
  * @param  __INTERRUPT__ specifies the UART interrupt source to enable.
  *          This parameter can be one of the following values:
  *            @arg @ref UART_IT_RXFF  RXFIFO Full interrupt
  *            @arg @ref UART_IT_TXFE  TXFIFO Empty interrupt
  *            @arg @ref UART_IT_RXFT  RXFIFO threshold interrupt
  *            @arg @ref UART_IT_TXFT  TXFIFO threshold interrupt
  *            @arg @ref UART_IT_CM    Character match interrupt
  *            @arg @ref UART_IT_CTS   CTS change interrupt
  *            @arg @ref UART_IT_LBD   LIN Break detection interrupt
  *            @arg @ref UART_IT_TXE   Transmit Data Register empty interrupt
  *            @arg @ref UART_IT_TXFNF TX FIFO not full interrupt
  *            @arg @ref UART_IT_TC    Transmission complete interrupt
  *            @arg @ref UART_IT_RXNE  Receive Data register not empty interrupt
  *            @arg @ref UART_IT_RXFNE RXFIFO not empty interrupt
  *            @arg @ref UART_IT_RTO   Receive Timeout interrupt
  *            @arg @ref UART_IT_IDLE  Idle line detection interrupt
  *            @arg @ref UART_IT_PE    Parity Error interrupt
  *            @arg @ref UART_IT_ERR   Error interrupt (frame error, noise error, overrun error)
  * @retval None
  */
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)   (\
                                                           ((((uint8_t)(__INTERRUPT__)) >> 5U) == 1U)?\
                                                           ((__HANDLE__)->Instance->CR1 |= (1U <<\
                                                               ((__INTERRUPT__) & UART_IT_MASK))): \
                                                           ((((uint8_t)(__INTERRUPT__)) >> 5U) == 2U)?\
                                                           ((__HANDLE__)->Instance->CR2 |= (1U <<\
                                                               ((__INTERRUPT__) & UART_IT_MASK))): \
                                                           ((__HANDLE__)->Instance->CR3 |= (1U <<\
                                                               ((__INTERRUPT__) & UART_IT_MASK))))

功能:

        HAL 库提供的用于使能中断 / 状态标志位的函数

参数:

        HANDLE:USART1 的句柄对象

        INTERRUPT:需要使能中断 / 状态标志位


__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE) == USART1->CR1 |= (0x1<<4)
__HAL_UART_GET_FLAG(HANDLE, FLAG) 获取中断 / 状态标志位的值宏函数

/** @brief  Check whether the specified UART flag is set or not.
  * @param  __HANDLE__ specifies the UART Handle.
  * @param  __FLAG__ specifies the flag to check.
  *        This parameter can be one of the following values:
  *            @arg @ref UART_FLAG_TXFT  TXFIFO threshold flag
  *            @arg @ref UART_FLAG_RXFT  RXFIFO threshold flag
  *            @arg @ref UART_FLAG_RXFF  RXFIFO Full flag
  *            @arg @ref UART_FLAG_TXFE  TXFIFO Empty flag
  *            @arg @ref UART_FLAG_REACK Receive enable acknowledge flag
  *            @arg @ref UART_FLAG_TEACK Transmit enable acknowledge flag
  *            @arg @ref UART_FLAG_RWU   Receiver wake up flag (if the UART in mute mode)
  *            @arg @ref UART_FLAG_SBKF  Send Break flag
  *            @arg @ref UART_FLAG_CMF   Character match flag
  *            @arg @ref UART_FLAG_BUSY  Busy flag
  *            @arg @ref UART_FLAG_ABRF  Auto Baud rate detection flag
  *            @arg @ref UART_FLAG_ABRE  Auto Baud rate detection error flag
  *            @arg @ref UART_FLAG_CTS   CTS Change flag
  *            @arg @ref UART_FLAG_LBDF  LIN Break detection flag
  *            @arg @ref UART_FLAG_TXE   Transmit data register empty flag
  *            @arg @ref UART_FLAG_TXFNF UART TXFIFO not full flag
  *            @arg @ref UART_FLAG_TC    Transmission Complete flag
  *            @arg @ref UART_FLAG_RXNE  Receive data register not empty flag
  *            @arg @ref UART_FLAG_RXFNE UART RXFIFO not empty flag
  *            @arg @ref UART_FLAG_RTOF  Receiver Timeout flag
  *            @arg @ref UART_FLAG_IDLE  Idle Line detection flag
  *            @arg @ref UART_FLAG_ORE   Overrun Error flag
  *            @arg @ref UART_FLAG_NE    Noise Error flag
  *            @arg @ref UART_FLAG_FE    Framing Error flag
  *            @arg @ref UART_FLAG_PE    Parity Error flag
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->ISR & (__FLAG__)) == (__FLAG__))

功能:

        HAL库提供的用于获取中断 / 状态标志位对应值的函数

参数:

        HANDLE:USART1 的句柄对象

        FLAG:需要获取的中断 / 状态标志位


__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == USART1->ISR & (0x1<<4)
__HAL_UART_CLEAR_FLAG 清除中断 / 状态标志位的宏函数

/** @brief  Clear the specified UART pending flag.
  * @param  __HANDLE__ specifies the UART Handle.
  * @param  __FLAG__ specifies the flag to check.
  *          This parameter can be any combination of the following values:
  *            @arg @ref UART_CLEAR_PEF      Parity Error Clear Flag
  *            @arg @ref UART_CLEAR_FEF      Framing Error Clear Flag
  *            @arg @ref UART_CLEAR_NEF      Noise detected Clear Flag
  *            @arg @ref UART_CLEAR_OREF     Overrun Error Clear Flag
  *            @arg @ref UART_CLEAR_IDLEF    IDLE line detected Clear Flag
  *            @arg @ref UART_CLEAR_TXFECF   TXFIFO empty clear Flag
  *            @arg @ref UART_CLEAR_TCF      Transmission Complete Clear Flag
  *            @arg @ref UART_CLEAR_RTOF     Receiver Timeout clear flag
  *            @arg @ref UART_CLEAR_LBDF     LIN Break Detection Clear Flag
  *            @arg @ref UART_CLEAR_CTSF     CTS Interrupt Clear Flag
  *            @arg @ref UART_CLEAR_CMF      Character Match Clear Flag
  * @retval None
  */
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->ICR = (__FLAG__))

功能:

        HAL库提供的用于清除中断/状态标志位的函数

参数:

        HANDLE:USART1的句柄对象

        FLAG:需要清除的中断/状态标志位


__HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_IDLEF) == USART1->ICR |= (0x1<<4);

6)huart1 句柄对象提供的成员


  uint8_t                  *pRxBuffPtr;              /*!< Pointer to UART Rx transfer Buffer */
 
  __IO uint16_t            RxXferCount;              /*!< UART Rx Transfer Counter           */

RxXferCount 代表接收时的计数,接收 1 个字节,值会减 1

buf 初始为 1024 字节,则 RxXferCount = 1024

如果当前接收了 500 字节,则还能接收 1024 - RxXferCount = 524 个字节


pRxBuffPtr 是接收数据时的指针偏移

在接收完完整数据后,需要将 pRxBuffPtr 的指向重新指向 buf 的首地址

7)写法 1

stm32u5xxit.c

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  // 声明外部缓冲区,用于存储接收到的数据
  extern uint8_t buf[1024];
  // 用于记录接收数据的长度
  int rx_len=0;
	
 /* USER CODE END USART1_IRQn 0 */
  // 调用HAL库的USART中断处理函数,处理底层中断逻辑
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  // 检查是否发生了IDLE空闲中断(数据帧接收完成)
  if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=0)
  {
    // 清除IDLE中断标志位
    __HAL_UART_CLEAR_FLAG(&huart1,UART_CLEAR_IDLEF);
 
    // 计算接收到的数据长度:缓冲区总大小 - 剩余未填充的字节数
    rx_len=sizeof(buf)-huart1.RxXferCount;
    // 将接收缓冲区指针重新指向缓冲区起始位置
    huart1.pRxBuffPtr=buf;
    // 将接收到的数据通过USART1发送回去(实现回显功能)
    // 超时时间设置为5ms
    HAL_UART_Transmit(&huart1,buf,rx_len,5);
    // 清空接收缓冲区,为下一次接收做准备
    memset(buf,0,1024);
    // 重新开启USART1的中断接收模式,等待下一次数据
    HAL_UART_Receive_IT(&huart1,buf,1024);
  }
  /* USER CODE END USART1_IRQn 1 */
}

	uint8_t buf[1024];
    //开启串口接收中断
	HAL_UART_Receive_IT(&huart1,buf,sizeof(buf));
	//开启串口空闲中断
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);