STM32回调函数使用/定时器/GPIO/串口/

回调函数

回调函数是 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

  • htimTIM_HandleTypeDef 类型的指针,它的 Instance 成员直接指向定时器的硬件寄存器基地址 (例如 TIM2 对应的寄存器基地址是 TIM2 这个宏定义)。
  • 这种写法是通过寄存器基地址来判断定时器实例,是 HAL 库中更 "底层" 且通用的判断方式,适用于所有定时器外设。

2. htim == &htim2

  • htim2 是用户在初始化时定义的 TIM_HandleTypeDef 类型的结构体变量(例如 TIM_HandleTypeDef htim2;)。
  • 这种写法是通过比较结构体变量的地址 来判断,要求用户在代码中明确定义了对应的 htim2 变量,且该变量是 TIM2 的配置句柄。

区别与选择建议

对比项 htim->Instance == TIM2 htim == &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 仍能正常发送数据。但在多数场景下(如连续发送多包数据、需要确保数据发送成功后再执行下一步),必须通过回调函数获取发送完成的通知,否则无法判断发送状态。

相关推荐
稻草、6 小时前
合泰单片机之时基中断
单片机·嵌入式硬件
czy87874756 小时前
C语言实现状态模式
c语言·状态模式
盈创力和20076 小时前
物联网 “神经” 之以太网:温湿度传感器的工业级 “高速干道”
运维·服务器·网络·嵌入式硬件·以太网温湿度传感器
czy87874756 小时前
C语言实现迭代器模式
c语言·迭代器模式
GilgameshJSS7 小时前
STM32H743-ARM例程36-DNS
c语言·arm开发·stm32·单片机·嵌入式硬件
工具人55557 小时前
电脑插入USB一个端口都识别不出来
单片机·嵌入式硬件·电脑
KOAN凯擎小妹8 小时前
晶振信号质量:上升下降时间与占空比
单片机·嵌入式硬件·fpga开发·信息与通信
czy87874758 小时前
C语言实现策略模式
c语言·排序算法·策略模式
不觉晚秋9 小时前
极限挑战之一命速通哈夫曼树
c语言·数据结构··哈夫曼树