目录
(3)EXTI_EXTICRm寄存器(GPIO端口引脚选择寄存器)
(1)__HAL_UART_ENABLE_IT(使能中断/状态标志位)
(2)__HAL_UART_GET_FLAG(获取中断/状态标志位)
(3)__HAL_UART_CLEAR_FLAG(清楚中断/状态标志位)
一、处理器的工作模式
Cortex-M核的工作模式:
异常模式 线程模式
Cortex-A核的工作模式:
ARM-v7架构设计的A核的工作模式:
非特权模式: User(用户模式)
特权模式:
1.非异常模式:Sys
2.异常模式
IRQ模式(普通中断模式)
FIQ模式(快速中断模式)
SVC模式(超级管理员模式)
ABT模式(中止访问模式)
UDF模式(未定义异常模式)
ARM-v8架构设计的A核的工作模式:
在v7架构的基础上多以下两个工作模式:
MON(电源管理模式)
HYP(虚拟化技术模式)
1、异常源
异常源是指触发处理器执行异常事件对应的处理逻辑的源头
异常源是异常事件的集合
异常源下支持很多异常事件
|-------|---------------------|-------------------------|
| 异常模式 | 异常源 | 异常源下的异常事件 |
| IRQ模式 | 一系列触发处理器进入IRQ模式的异常源 | 普通中断,定时器中断 |
| FIQ模式 | 一系列触发处理器进入FIQ模式的异常源 | 内核中断,高优先级中断 |
| SVC模式 | 复位异常源 | 上电复位,按键复位 |
| SVC模式 | 软中断异常源 | swi执行软中断指令 |
| ABT模式 | 数据终止访问异常源 | 非法访问或使用没有权限的数据(段错误) |
| ABT模式 | 指令终止访问异常源 | 汇编指令格式错误、 汇编指令访问没有权限的空间 |
| UDF模式 | 未定义异常源 | 指令/变量未被定义过 |
1、5种异常模式对应七种异常源
2、每种异常源下存在很多的异常事件
3、SVC模式下的复位异常源的优先级等级最高,处理器优先处理
2、异常向量表

异常向量表:就是异常源的集合
当产生异常事件后,处理器会检测当前这个异常事件属于哪个异常源
此时处理器会根据异常源去异常向量表中对应的地址,查找对应的异常源中对应的异常事件
注意:
1、处理器只会根据异常向量表中固定的地址查找异常源,异常源地址是不能随意变化的
2、异常向量表共占内存空间32字节,每个异常源占用4字节存放异常源地址,保留了4字节作为预留空间
为什么需要引入异常向量表?
由于异常触发后,最终需要执行异常处理函数
而对于函数而言,当开发板/系统上电复位后,函数的地址、参数的地址、变量的地址可能都会发生改变,
对于CPU而言,他无法知道变化后的地址
此时,就需要固定一片内存空间地址,用于引导CPU找到最后的异常处理函数
所以,ARM公司开发并生产内核时,固定了一片32个字节的地址空间,
专门用于存放各个异常源的地址,他就是异常向量表
二、异常处理流程

1、自动保存现场(CPU自动完成)
自动保存现场需要做四件事:
1、将CPSR寄存器内的值保存到SPSR_<MODE>寄存器中
2、修改CPSR寄存器中的位:
1.修改CPSR寄存器中的I位和F位(选择是否屏蔽IRQ模式和FIQ模式)
2.修改CPSR寄存器中的T位(选择使用ARM汇编指令集/Thumb汇编指令集)
3.修改CPSR寄存器中的M位(选择需要进入的异常模式)
3、将函数返回地址保存到LR链接寄存器中
4、将PC计数寄存器指向异常向量表
2、手动返回现场(程序员手动完成)
1、将SPSR_<MODE>寄存器中的值赋值给CPSR寄存器
2、将LR链接寄存器中的值赋值给PC计数寄存器
3、ARM汇编代码模拟异常事件处理流程
.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

三、分析芯片手册(EXTI、NVIC)
NVIC:嵌套向量中断控制器
EXTI:外部中断和事件控制器
嵌套:中断的嵌套,指的是中断具备优先级等级,高优先级等级的中断信号可以打断低优先级的中断信号
向量:所有异常中断都由异常向量表统一管理
在Cortex-M核中,中断处理一般使用 EXTI 和 NVIC 这两个寄存器实现
在Cortex-A核中,中断处理一般使用 EXTI 和 GIC(GICC+CPID) 这两个寄存器实现
1、外部中断章节介绍(EXTI)

1)输入事件编号

2)EXTI的主要特点

3)EXTI框图分析
4)寄存器功能分析

中断挂起:是指中断信号到达,等待被处理,需要执行这个中断信号对应的逻辑代码
中断挂起标志位:是表示当前中断信号到达等待处理的标志位
中断:就是判断中断标志位是否被挂起,挂起后执行中断信号对应的异常事件处理代码
5)GPIO端口选择

6)寄存器分析
以KEY1 - PC9为例,需要实现
KEY1按下(产生下降沿信号)时,LED灯点亮
对EXTI外设控制器中控制PC9引脚的寄存器做出处理:
1、需要找到PC9接入到EXTI外设控制器的IO端口选择寄存器(找这个寄存器中用于代表PC9引脚的位)
2、将PC9配置为下降沿触发方式
3、设置不屏蔽PC9引脚产生的中断信号
4、检测PC9引脚是否触发中断,也就是PC9引脚产生的下降沿信号是否被硬件挂起
(1)EXTI_FTSR1寄存器(下降沿触发寄存器)
(2)EXTI_FPR1寄存器(下降沿触发标志寄存器)
当我们检测按键是否按下时,也就是检测中断是否被挂起,也就是读取这个寄存器对应位是否为1
读取到1:就代表中断被挂起,可以执行处理逻辑
读到到0:就代表中断没有被挂起,不需要执行处理逻辑
当我们读取到中断被挂起后,为了让中断不一直处于被挂起状态,需要人为手动解除中断的挂起
(3)EXTI_EXTICRm寄存器(GPIO端口引脚选择寄存器)
(4)EXTI_IMR1寄存器(输入事件频闭寄存器)
2、中断嵌套向量章节介绍(NVIC)
1)NVIC的主要特点

2)中断号

四、标准库实现外部中断
1、标准库初始化外部中断
2、标准库代码
五、HAL库实现外部中断
1、STM32CudeMX配置初始化
1)配置为外部中断触发
外部中断是GPIO输入模式中的一种,并不属于复用模式,配置为双边沿触发

2)使能NVIC下的EXTI的中断信号

3)配置中断触发的优先级
不要配置外部中断的优先级和系统自带中断的优先级同级,这会影响系统的绝对优先,这里优先级设置成 1 即可

2、STM32CudeMX初始化代码
在工程中的启动文件中(唯一的.s文件),有异常向量表,根据异常向量表可以找到,对应的异常中断函数,在发生异常事件时,CPU也是先找到启动文件中的异常向量表 ,根据异常向量表查找对应的异常事件函数 。可以在异常事件的函数中编写处理异常的代码,也可以在HAL库提供的处理异常事件的空函数中编写代码。
1)异常处理流程
2)API接口(异常处理函数)
__weak void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
功能:
HAL库提供用于执行下降沿中断信号触发后需要执行的弱函数(支持被重写)
参数:
GPIO_Pin:触发下下降沿中断信号的GPIO引脚编号
返回值:
无
__weak void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
功能:
HAL库提供的用于执行上升沿中断信号触发后需要执行的弱函数(支持被重写)
参数:
GPIO_Pin:触发下下降沿中断信号的GPIO引脚编号
返回值:
无
3)示例代码
六、标准库配置串口中断
1、标准库初始化串口中断
2、标准库操作串口中断
七、HAL库配置串口中断
1、串口中断执行流程
2、串口发送中断(不常用)
1)API接口函数
(1)串口发送中断执行函数
HAL_StatusTypeDef
HAL_UART_Transmit_IT
(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
功能:
HAL库提供的用于通过串口进行中断的方式发送数据的函数
参数:
huart:USART1的句柄对象
pData:需要发送的数据
Size:需要发送的数据的大小,单位为字节
返回值:
函数执行成功,返回HAL_OK(0)
函数执行失败,返回错误码
(2)串口发送中断回调函数
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
功能:
HAL库提供的用于串口发送中断触发后,执行的回调函数(弱函数,可以被重写)
当串口发送中断函数执行完毕后,这个函数会被调用
当使用串口发送中断函数发送完完整数据后,此时发送中断的中断信号触发,这个函数才被执行
参数:
huart:USART1的句柄对象
返回值:
无
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------(不常用)
__weak void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
这个函数是,当使用串口发送中断函数发送一半数据后,此时发送中断的中断信号触发,这个函数被执行
2)示例代码
3)代码效果

3、串口接收中断(经常用)
使用串口接收中断可以实现随时收发
1)API接口函数
(1)串口接收中断启用函数
HAL_StatusTypeDef
HAL_UART_Receive_IT
(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能:
HAL库提供的用于开启串口接收中断,并使用中断的方式接收数据的函数
参数:
huart:USART1句柄对象
pData:需要接收的数据
Size:需要接收的数据的大小,单位为字节
返回值:
函数执行成功,返回HAL_OK
函数执行失败,返回错误码
(2)串口接收中断回调函数
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
功能:
HAL库提供的用于串口接收中断触发后,执行的回调函数(弱函数,支持被重写)
当串口通过中断的方式接受完完整数据后,此时接收中断信号触发,这个函数会被执行
参数:
huart:USART1句柄对象
返回值:
无
2)示例代码
3)代码效果

4、串口空闲中断(经常用)
串口空闲中断:是指串口中断和状态寄存器中的一个特殊标志位,叫做串口空闲标志位
通过串口空闲中断可以实现串口的不定长接收数据
串口空闲中断可以判断一次数据是否接收完毕
1)USART_CR1寄存器
2)USART_ISR寄存器
3)USART_ICR寄存器
4)串口空闲中断的工作原理

5)API接口函数
想要使用空闲中断标志位:
1、使能空闲中断标志位
2、读取空闲中断标志位是否被置1
3、如果被置1,清除空闲中断标志位,方便下一次硬件置1
4、需要执行的逻辑
(1)__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__:需要使能中断/状态标志位
相当于 USART1->CR1 |= (0x1 << 4);
(2)__HAL_UART_GET_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__:需要获取的中断/状态标志位
就相当于 USART1->ISR & (0x1 << 4)
(3)__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__:需要清除的中断/状态标志位
就相当于 USART1->ICR |= (0x1 << 4);
6)串口句柄对象提供的成员变量

__IO uint16_t RxXferCount;
RxXferCount用于接收数据时的计数,没接收1个字节,会减1
buf初始为1024个字节,RxXferCount = 1024
每当接收到1个字节后,RxXferCount会减1
举例:
接收了500个字节的数据
RxXferCount = 1024 - 500 = 524个字节(当前buf还可以接收数据的字节数)
实际接收的字节数 = 1024 - RxXferCount
uint8_t *pRxBuffPtr;
pRxBuffPtr用于接收数据时的指针偏移
当接受完完整数据后,需要将pRxBuffPtr的指向重新冲向buf首地址的为止
5、实现串口的实时不定长接收(示例代码)
使用 串口接收中断 + 串口空闲中断 = 实现串口的实时不定长接收数据
串口接收中断 用于实现实时接收数据
串口空闲中断 用于实现不定长接收数据
实现串口的实时不定长接收数据:
1、开启串口接收中断和串口空闲中断
2、在串口的异常处理函数中执行
1.判断空闲中断是否产生(若产生则代表数据接收完毕)
2.清楚空闲中断标志位
3.选哟的逻辑处理代码
4.重新开启串口接收中断,准备下一次数据的接收