Skip to content

中断的使用方法

一. 弱函数中断处理

1. 中断的概念

中断是指CPU在执行程序的过程中,由于内部或外部事件的出现,暂时中止当前程序的执行,转而去执行处理该事件的程序,处理完毕后,再返回被中止的程序处继续执行。

只讲一下,如何使用中断,来处理数据的逻辑.尤其是在FreeRTOS系统中,中断的使用就需要特别注意. 在裸机系统中,可以参考FreeRTOS的方法.

2. 基本逻辑

我们利用串口中断来举例说明一下.

  1. 首先,需要在 Stm32cubemx 中,打开串口中断配置. 只有一个全局中断,不能分接收中断和发送中断.

  2. 中断需要一个中断回调函数,这个函数是由HAL库调用的. 我们只需要实现这个函数就可以了.

c

// 发送中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)//这里需要判断串口实例
    {
        // 处理发送完成后的逻辑
    }
}


// 接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)//这里需要判断串口实例
    {
        // 处理接收到的数据
    }
}
  1. 在主函数中,需要开启串口中断.
c
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_data, 1); // 开启接收中断
HAL_UART_Transmit_IT(&huart1, (uint8_t *)&tx_data, 1); // 开启发送中断
  1. 使用中断的逻辑过程.
  • 当使用 HAL_UART_Transmit_IT 发送数据时, 如果要通过中断接收数据的话,则需要在发送数据之前调用 HAL_UART_Receive_IT 函数开启接收中断.这就是为什么接收中断在发送中断的前面的原因.
  • 当执行完 HAL_UART_Transmit_IT 函数之后, 会调用 HAL_UART_TxCpltCallback 函数. 在这个函数中,可以处理发送完成后的逻辑.
  • 当接收到数据时, 会调用 HAL_UART_RxCpltCallback 函数. 在这个函数中,可以处理接收到的数据.
  1. 数据如何接收?
  • 在使用 HAL_UART_Receive_IT 开启接收中断时, HAL_UART_Receive_IT 这个函数会指定接收数据的缓冲区. 以及接收数据的长度. 当接收到数据时, HAL库会函数会自动将接收到的数据保存到指定的缓冲区中,并调用 HAL_UART_RxCpltCallback 函数.

  • HAL_UART_RxCpltCallback 函数中,接收到的数据会保存在 rx_data 变量中. 我们只需要在 HAL_UART_RxCpltCallback 函数中,处理 rx_data 变量就可以了.

  • rx_data 这个缓冲区一定是一个全局变量.

  1. 只使用接收中断,不使用发送中断.
  • 如果感觉发送中断对自己没有用处,只想使用接收中断,那么只需要使用 HAL_UART_Transmit 函数发送数据,就不会触发发送中断.但是只要在调用这个函数之前调用了 HAL_UART_Receive_IT 函数,就会触发接收中断.

3. 应用场景

  1. 我想在串口发送完数据之后,等待接收到数据,并根据接收到的数据,来执行不同的逻辑. 这里就需要处理一下, 如何等待接收到数据呢?由于数据接收是异步的. 所以,我们需要使用一个标志位来表示数据是否接收完成.在 HAL_UART_RxCpltCallback 函数中,设置标志位为1,然后在主函数中,通过轮询的方式,判断标志位是否为1,如果是1,则表示数据接收完成,可以执行相应的逻辑.
c
volatile uint8_t rx_flag = 0; // 接收标志位,由于多个函数要使用这个变量 ,所以这个变量必须定义为全局变量.为了防止编译器优化,所以使用 volatile 关键字

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)//这里需要判断串口实例
    {
        rx_flag = 1; // 设置接收标志位为1
    }
}

int work(void)
{
    // 初始化串口
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_data, 1); // 开启接收中断
    HAL_UART_Transmit_IT(&huart1, (uint8_t *)&tx_data, 1); // 开启发送中断

    while (1)
    {
        if(rx_flag == 1) // 判断接收标志位是否为1
        {
            // 执行相应的逻辑
            rx_flag = 0; // 清除接收标志位
        }
    }
    
}

为了避免一直循环等待,造成当前线程完成死锁,可以增加一个超时功能. 指定等待的循环的次数. 全局标志位 rx_flag 必须是全局变量,并添加 volatile 关键字.

c
int work(void)
{
    int n = 10;
    // 初始化串口
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_data, 1); // 开启接收中断
    HAL_UART_Transmit_IT(&huart1, (uint8_t *)&tx_data, 1); // 开启发送中断

    while (n--)
    {
        if(rx_flag == 1) // 判断接收标志位是否为1
        {
            // 执行相应的逻辑
            rx_flag = 0; // 清除接收标志位
            break; // 跳出循环
        } else{
            vTaskDelay(100);//一定要使用这种方式进行等待.而不是使用 HAL_Delay()
        }
    }

}

HAL_Delay() 是阻塞的,会导致CPU完全被占用,程序MCU全部被占用,无法执行其他任务.

二.使用注册中断处理函数

上面使用的方法是使用默认的弱函数来处理中断. 还有一种更加灵活的处理中断的方式,那就是注册中断处理函数.

1.使用注册回调的条件

(1). 首先,需要在 Stm32cubemx 中,打开串口中断配置. 只有一个全局中断,不能分接收中断和发送中断. (2). 在 STM32CubeMX 中,在 Project Manager => Advanced Setting 中,在最右边的 Register CallBack 栏中,找到 UART 项,选择 Enable.

  • 注意一定要选择 UART 项,不要选择 USART 项.
特性USE_HAL_UART_REGISTER_CALLBACKSUSE_HAL_USART_REGISTER_CALLBACKS
适用模式异步模式 (Asynchronous)同步模式 (Synchronous)
功能范围仅支持UART的异步通信功能支持USART的全部功能,包括同步通信(如带时钟线)
硬件关联与UART模块关联与USART模块关联
你的选择✅ 应优先选择❌ 仅在启用同步模式时使用

2.如何使用注册回调模式

当你定义了 USE_HAL_USART_REGISTER_CALLBACKS1U 后,你需要按照以下步骤来使用:

  1. 定义回调函数:首先,你需要编写你自己的回调函数,其函数签名(参数列表和返回类型)必须与弱定义版本保持一致。

    c
    // 例如,为USART1编写一个接收完成回调函数
    void MyUSART1_RxCpltCallback(UART_HandleTypeDef *huart)
    {
        // 你的处理逻辑
    }
  2. 注册回调函数:在串口初始化(如MX_USART1_UART_Init()之后,调用专门的注册函数将你的函数"告诉"HAL库。

    c
    int main(void)
    {
        // ... 系统初始化
        MX_USART1_UART_Init(); // 初始化串口
    
        // 注册回调函数
        if (HAL_UART_RegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID, MyUSART1_RxCpltCallback) != HAL_OK) {
            // 注册失败处理
            Error_Handler();
        }
    
        // 启动中断接收
        HAL_UART_Receive_IT(&huart1, rx_buf, size);
    
        while (1) {
            // 主循环
        }
    }
  3. 注销回调(可选):如果需要,也可以在运行时注销回调,恢复为默认的空操作。

    c
    HAL_UART_UnRegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID);

何时选择注册回调模式?

  • 项目结构复杂:当你的工程中有多个模块都需要使用串口,且希望各自的处理逻辑解耦时。
  • 需要动态切换行为:例如,设备在不同工作模式下,对同一串口数据的处理方式不同,需要在运行时切换。
  • 代码可维护性要求高:注册模式强制了更清晰的代码结构,便于团队协作和后期维护。

对于简单的、只有一个串口或处理逻辑单一的小型项目,使用传统的弱定义模式可能就足够了。但对于复杂的、需要良好架构的中大型项目,启用注册回调模式是一个更专业和灵活的选择。

希望这个解释能帮助你理解这个定义的作用。如果你正在设计一个具体的项目,可以分享更多细节,我们一起看看哪种模式更合适。