回调函数
回调函数是 HAL 库等框架中,由用户实现、但在特定事件(如定时器周期到达、GPIO 触发)发生时由框架自动调用的函数。
作用是 "分离通用底层逻辑与用户业务逻辑"------ 比如定时器中断中,HAL 库自动完成标志位判断、清除等通用操作,用户只需在回调函数里写 LED 翻转、数据读取这类具体需求,不用关心底层细节。
简单说,回调函数就像给框架留的 "接口":你提前写好要做的事(比如 LED 翻转),告诉框架 "当某个事件发生时,就调用我写的这个函数",后续事件触发后,框架会自动执行你预留的逻辑,大大简化了代码编写和底层操作的复杂度。
定时器------标准库
定时器初始化完成后记得在主函数中完成初始化!!!
标准库要求用户直接编写完整的中断服务函数 (例如
TIM2_IRQHandler),所有逻辑(包括标志位判断、清除、业务处理)都需要在这个函数中手动实现,没有 "回调函数" 的概念。例如,标准库实现 TIM2 定时中断的结构是:
// 标准库:中断服务函数需用户完全手动编写 void TIM2_IRQHandler(void) { // 1. 判断中断标志 if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { // 2. 清除中断标志 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 3. 业务逻辑(如LED翻转) GPIO_TogglePin(GPIOE, GPIO_Pin_15); } }如何判断是否产生了中断重叠现象?
就是刚进中断就清除标志位,中断退出之前,再检查一下标志位,如果又置1了,说明中断重叠。
如何得知定时中断执行的时长?
只需要在函数退出之前,读取一下计数器
定时器------HAL库
HAL 库的回调函数机制:
HAL 库通过 "底层中断服务函数 + 上层回调函数" 的分层设计,
将通用逻辑(标志判断、清除)封装在 HAL 库内部(如
HAL_TIM_IRQHandler),用户只需实现回调函数 (如
HAL_TIM_PeriodElapsedCallback)来处理业务逻辑,无需关心底层细节。
在HAL 库环境 下,不需要也不建议使用TIM_GetITStatus(TIM2, TIM_IT_Update) == SET来判断中断;HAL 库的
HAL_TIM_IRQHandler函数会自动完成所有已使能的定时器中断标志位的判断与清除 ,用户在回调函数(如HAL_TIM_PeriodElapsedCallback)中无需再处理标志位,只需专注于业务逻辑(如 LED 翻转、数据读取等)即可。
记得开启中断,初始化阶段完成
//手动开启TIM2定时器中断(关键步骤) HAL_TIM_Base_Start_IT(&htim2);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&htim2))
    {
	   ID++;
    }
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        ID++;
    }
}
        1.
htim->Instance == TIM2
htim是TIM_HandleTypeDef类型的指针,它的Instance成员直接指向定时器的硬件寄存器基地址 (例如TIM2对应的寄存器基地址是TIM2这个宏定义)。- 这种写法是通过寄存器基地址来判断定时器实例,是 HAL 库中更 "底层" 且通用的判断方式,适用于所有定时器外设。
 2.
htim == &htim2
htim2是用户在初始化时定义的TIM_HandleTypeDef类型的结构体变量(例如TIM_HandleTypeDef htim2;)。- 这种写法是通过比较结构体变量的地址 来判断,要求用户在代码中明确定义了对应的
 htim2变量,且该变量是 TIM2 的配置句柄。区别与选择建议
对比项 htim->Instance == TIM2htim == &htim2依赖条件 仅依赖定时器的寄存器基地址(由芯片定义) 依赖用户定义的 TIM_HandleTypeDef变量(如htim2)通用性 更高(适用于所有定时器,无需关心变量名) 较低(需确保变量名与定时器实例一一对应) 代码可读性 直接体现 "判断定时器硬件实例" 的意图 需结合变量定义才能明确对应关系 
GPIO的中断回调------HAL库
触发条件
| 对比项 | External Interrupt Mode(中断模式) | External Event Mode(事件模式) | 
|---|---|---|
| CPU 参与度 | 需要 CPU 响应中断,执行回调函数 | 无 CPU 参与,纯硬件联动外设 | 
| 延迟性 | 存在中断响应延迟(微秒级) | 硬件级触发,延迟极低(纳秒级) | 
| 应用场景 | 需要 CPU 处理逻辑(如状态判断、数据解析) | 仅需硬件联动外设(如定时器启动、DMA 触发) | 
在 CubeMX 完成配置后,无需在函数中手动初始化中断的底层硬件逻辑 (如 EXTI 寄存器、NVIC 优先级等),这些都由 CubeMX 生成的代码自动完成。你只需关注使能中断和实现回调函数这两个关键步骤,具体如下:
使能外部中断(CubeMX 生成代码已包含,无需手动写)
CubeMX 生成的**MX_GPIO_Init()** 函数会自动初始化 A1 引脚的中断配置,包括:
- 配置 GPIO 为外部中断模式(上升沿 / 下降沿触发)前提记得****先上下拉;
 - NVIC中配置 EXTI 控制器的中断线映射;
 - 配置 NVIC 的中断使能和优先级(即你在 CubeMX NVIC 界面中设置的参数)。
 
实现中断回调函数(需要用户手动写)
在用户代码中重写HAL_GPIO_EXTI_Callback函数,处理 A1 引脚中断触发后的逻辑:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_1)  // 判断是A1引脚触发的中断
    {
        // 在这里编写中断触发后的业务逻辑,例如:
        // 读取引脚电平、控制LED、执行特定任务等
        HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_15);  // 示例:翻转PE15的LED
    }
}
        串口------HAL库
配置

找定义,进去后一直往下翻,找串口接受的回调函数

串口接收的中断回调函数实现:
回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) // ???????????? USART1 { /* 任务!! */ HAL_UART_Receive_IT(&huart1, (uint8_t*)rx_data, sizeof(rx_data)); // ???? UART ???? } }记得重新开启接收中断:HAL_UART_Receive_IT(...)
由于 HAL 库的串口中断接收是 "一次性" 的(每次接收完成后会自动关闭中断),必须在回调函数末尾重新调用该函数,才能保证后续数据能继续触发接收中断,实现持续接收。
初始化:记得在主函数住启用串口中断,如下
// 在main函数初始化后调用,启动USART1的中断接收(接收1字节到rx_data缓冲区) HAL_UART_Receive_IT(&huart1, (uint8_t*)rx_data, 1);使能串口接收中断,当接收到指定长度(如 1 字节)数据后,自动触发
HAL_UART_RxCpltCallback回调函数。
串口发送的中断
发送的函数也可以配合接收的一些函数去使用,但是注意理清楚状态
HAL_UART_TxCpltCallback是串口发送完成后的通知机制 。当HAL_UART_Transmit_IT启动的异步发送过程全部完成(所有字节发送完毕),HAL 库会自动调用该回调函数,用于:告知用户 "数据已发送完成"(例如设置发送完成标志位
tx_done = 1);触发后续操作(例如发送下一包数据、释放缓冲区、更新状态指示灯等)。
// 1. 启动异步发送
HAL_UART_Transmit_IT(&huart1, (uint8_t*)tx_data, len);  // 启动发送,立即返回
// 2. 发送过程由硬件中断自动完成(无需CPU干预)
// 3. 发送完成后,HAL库自动调用回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)  // 确认是USART1的发送完成
    {
        // 例如:标记发送完成,用于主循环判断
        tx_complete_flag = 1;
        
        // 例如:继续发送下一包数据
        // HAL_UART_Transmit_IT(&huart1, next_tx_data, next_len);
    }
}
        如果仅需 "发送数据" 而无需关心 "是否发送完成"(例如简单的调试信息发送),可以不实现回调函数,
HAL_UART_Transmit_IT仍能正常发送数据。但在多数场景下(如连续发送多包数据、需要确保数据发送成功后再执行下一步),必须通过回调函数获取发送完成的通知,否则无法判断发送状态。
