USART中断为例
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
..........//省略大量代码直接跳到最后面
/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
/* UART in mode Transmitter end --------------------------------------------*/
if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
UART_EndTransmit_IT(huart);
return;
}
}
static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart)
{
/* Disable the UART Transmit Complete Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_TC);
/* Tx process is ended, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Tx complete callback*/
huart->TxCpltCallback(huart);
#else
/*Call legacy weak Tx complete callback*/
HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
中断服务函数(ISR) (如
USART1_IRQHandler
)内调用HAL_UART_IRQHandler(&huart1);此函数末尾调用UART_EndTransmit_IT(UART_HandleTypeDef *huart)函数,再在此函数能最终调用发送完成回调函数(两种方式实现回调)
两种回调模式
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
huart->TxCpltCallback(huart); // 方式1:调用用户动态注册的回调函数
#else
HAL_UART_TxCpltCallback(huart); // 方式2:调用传统的弱定义回调函数
#endif
二、两种回调模式的区别
1. 动态注册回调模式 (USE_HAL_UART_REGISTER_CALLBACKS == 1
)
- 触发机制:
通过函数指针动态注册回调函数,允许运行时灵活修改回调行为。 - 配置方法:
c
/* 用户代码中注册回调函数 */ void My_TxCpltCallback(UART_HandleTypeDef *huart) { // 自定义处理逻辑 } /* 在初始化后注册 */ huart1.TxCpltCallback = My_TxCpltCallback;
- 优势:
- 支持多个UART实例共用同一回调函数模板
- 可在运行时切换不同回调实现
2. 传统弱定义回调模式 (USE_HAL_UART_REGISTER_CALLBACKS == 0
)
- 触发机制:
调用弱符号__weak void HAL_UART_TxCpltCallback()
,用户通过重写弱函数实现自定义。 - 配置方法:
c
/* 用户重写弱函数(通常在main.c) */ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { // 处理USART1发送完成 } }
- 优势:
- 代码结构简单直观
- 无需额外内存存储函数指针
三、关键宏定义 USE_HAL_UART_REGISTER_CALLBACKS
- 定义位置:
在stm32xx_hal_conf.h
中配置:c
#define USE_HAL_UART_REGISTER_CALLBACKS 1 // 启用动态注册模式
- 工程影响:
- 设为
1
时:HAL库会为每个UART实例维护回调函数指针表(增加约20字节RAM开销) - 设为
0
时:使用传统弱函数方式(节省内存)
- 设为
四、底层实现对比
特性 | 动态注册回调模式 | 传统弱函数模式 |
---|---|---|
函数调用方式 | 通过结构体中的函数指针(huart->TxCpltCallback ) | 直接调用弱函数 |
内存开销 | 每个UART实例需要存储函数指针 | 无额外开销 |
灵活性 | 支持运行时动态修改回调 | 编译时固定 |
多实例区分 | 通过注册不同的回调函数实现 | 需在回调函数内手动检查huart->Instance |
五、典型应用场景
1. 动态注册模式适用场景
c
// 案例:多个UART实例需要不同处理逻辑
void UART1_Callback(UART_HandleTypeDef *huart) { /* 处理UART1 */ }
void UART2_Callback(UART_HandleTypeDef *huart) { /* 处理UART2 */ }
// 初始化时分别注册
huart1.TxCpltCallback = UART1_Callback;
huart2.TxCpltCallback = UART2_Callback;
2. 传统弱函数模式适用场景
c
// 案例:单一UART实例或简单应用
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
// 所有UART共用此回调
if(huart->Instance == USART1) {
// 特殊处理USART1
}
}
六、配置注意事项
-
一致性原则
同一工程中所有外设最好统一使用同一种模式(避免混用导致维护困难)。 -
模式切换代价
从传统模式切换到动态注册模式需修改两点:- 在
hal_conf.h
中启用宏 - 在代码中显式注册所有回调函数
- 在
-
错误处理
动态注册模式下,若未注册回调直接启用中断,函数指针为NULL
会导致硬故障。安全做法:c
if(huart->TxCpltCallback != NULL) { huart->TxCpltCallback(huart); }
七、HAL库的实现演进
- 早期版本:仅支持弱函数回调方式
- HAL V1.2.0+ :引入动态注册机制,增强灵活性
- 兼容性:宏定义确保新旧代码可共存