中断的使用方法
一. 弱函数中断处理
1. 中断的概念
中断是指CPU在执行程序的过程中,由于内部或外部事件的出现,暂时中止当前程序的执行,转而去执行处理该事件的程序,处理完毕后,再返回被中止的程序处继续执行。
只讲一下,如何使用中断,来处理数据的逻辑.尤其是在FreeRTOS系统中,中断的使用就需要特别注意. 在裸机系统中,可以参考FreeRTOS的方法.
2. 基本逻辑
我们利用串口中断来举例说明一下.
首先,需要在 Stm32cubemx 中,打开串口中断配置. 只有一个全局中断,不能分接收中断和发送中断.
中断需要一个中断回调函数,这个函数是由HAL库调用的. 我们只需要实现这个函数就可以了.
// 发送中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)//这里需要判断串口实例
{
// 处理发送完成后的逻辑
}
}
// 接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)//这里需要判断串口实例
{
// 处理接收到的数据
}
}- 在主函数中,需要开启串口中断.
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_data, 1); // 开启接收中断
HAL_UART_Transmit_IT(&huart1, (uint8_t *)&tx_data, 1); // 开启发送中断- 使用中断的逻辑过程.
- 当使用
HAL_UART_Transmit_IT发送数据时, 如果要通过中断接收数据的话,则需要在发送数据之前调用HAL_UART_Receive_IT函数开启接收中断.这就是为什么接收中断在发送中断的前面的原因. - 当执行完
HAL_UART_Transmit_IT函数之后, 会调用HAL_UART_TxCpltCallback函数. 在这个函数中,可以处理发送完成后的逻辑. - 当接收到数据时, 会调用
HAL_UART_RxCpltCallback函数. 在这个函数中,可以处理接收到的数据.
- 数据如何接收?
在使用
HAL_UART_Receive_IT开启接收中断时, HAL_UART_Receive_IT 这个函数会指定接收数据的缓冲区. 以及接收数据的长度. 当接收到数据时, HAL库会函数会自动将接收到的数据保存到指定的缓冲区中,并调用HAL_UART_RxCpltCallback函数.在
HAL_UART_RxCpltCallback函数中,接收到的数据会保存在rx_data变量中. 我们只需要在HAL_UART_RxCpltCallback函数中,处理rx_data变量就可以了.rx_data这个缓冲区一定是一个全局变量.
- 只使用接收中断,不使用发送中断.
- 如果感觉发送中断对自己没有用处,只想使用接收中断,那么只需要使用
HAL_UART_Transmit函数发送数据,就不会触发发送中断.但是只要在调用这个函数之前调用了HAL_UART_Receive_IT函数,就会触发接收中断.
3. 应用场景
- 我想在串口发送完数据之后,等待接收到数据,并根据接收到的数据,来执行不同的逻辑. 这里就需要处理一下, 如何等待接收到数据呢?由于数据接收是异步的. 所以,我们需要使用一个标志位来表示数据是否接收完成.在
HAL_UART_RxCpltCallback函数中,设置标志位为1,然后在主函数中,通过轮询的方式,判断标志位是否为1,如果是1,则表示数据接收完成,可以执行相应的逻辑.
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 关键字.
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_CALLBACKS | USE_HAL_USART_REGISTER_CALLBACKS |
|---|---|---|
| 适用模式 | 异步模式 (Asynchronous) | 同步模式 (Synchronous) |
| 功能范围 | 仅支持UART的异步通信功能 | 支持USART的全部功能,包括同步通信(如带时钟线) |
| 硬件关联 | 与UART模块关联 | 与USART模块关联 |
| 你的选择 | ✅ 应优先选择 | ❌ 仅在启用同步模式时使用 |
2.如何使用注册回调模式
当你定义了 USE_HAL_USART_REGISTER_CALLBACKS 为 1U 后,你需要按照以下步骤来使用:
定义回调函数:首先,你需要编写你自己的回调函数,其函数签名(参数列表和返回类型)必须与弱定义版本保持一致。
c// 例如,为USART1编写一个接收完成回调函数 void MyUSART1_RxCpltCallback(UART_HandleTypeDef *huart) { // 你的处理逻辑 }注册回调函数:在串口初始化(如
MX_USART1_UART_Init())之后,调用专门的注册函数将你的函数"告诉"HAL库。cint 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) { // 主循环 } }注销回调(可选):如果需要,也可以在运行时注销回调,恢复为默认的空操作。
cHAL_UART_UnRegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID);
何时选择注册回调模式?
- 项目结构复杂:当你的工程中有多个模块都需要使用串口,且希望各自的处理逻辑解耦时。
- 需要动态切换行为:例如,设备在不同工作模式下,对同一串口数据的处理方式不同,需要在运行时切换。
- 代码可维护性要求高:注册模式强制了更清晰的代码结构,便于团队协作和后期维护。
对于简单的、只有一个串口或处理逻辑单一的小型项目,使用传统的弱定义模式可能就足够了。但对于复杂的、需要良好架构的中大型项目,启用注册回调模式是一个更专业和灵活的选择。
希望这个解释能帮助你理解这个定义的作用。如果你正在设计一个具体的项目,可以分享更多细节,我们一起看看哪种模式更合适。
