当前位置: 首页 > news >正文

STM32HAL 快速入门(十九):UART 编程(二)—— 中断方式实现收发及局限分析

前言

大家好,这里是 Hello_Embed。上一篇我们用查询方式实现了 UART 收发,但存在 “数据不及时读取易丢失” 的问题。本篇将介绍中断方式—— 通过硬件中断主动通知 CPU 处理收发,减少 CPU 资源占用,同时分析其在复杂场景下的局限性,为下一篇 “中断 + 环形缓冲区” 的改进方案铺垫。

一、中断方式的核心优势与函数

中断方式的核心是 “硬件触发中断,CPU 仅在需要时处理”,无需轮询状态寄存器,显著提升效率。HAL 库中 UART 中断相关的核心函数如下:

功能函数作用回调函数(中断完成后触发)
中断发送HAL_UART_Transmit_IT启动中断发送,配置后立即返回HAL_UART_TxCpltCallback(发送完成)
中断接收HAL_UART_Receive_IT启动中断接收,配置后立即返回HAL_UART_RxCpltCallback(接收完成)
二、CubeMX 配置:使能 UART 中断

在查询方式配置的基础上,只需额外使能 UART 中断(NVIC),步骤如下:

  1. 进入 “Connectivity→USART1→NVIC Settings”,勾选 “Enabled” 使能 USART1 中断:
    请添加图片描述

  2. 其他配置(波特率 115200、8 位数据位等)与查询方式一致,生成代码。

三、中断发送:从启动到完成的流程

中断发送的核心是 “CPU 启动发送后即可处理其他任务,发送完成后通过回调函数通知”,具体流程如下:

1. 启动中断发送:HAL_UART_Transmit_IT

函数定义与核心逻辑:

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
{if (huart->gState == HAL_UART_STATE_READY)  // 检查UART是否空闲{huart->pTxBuffPtr = pData;                // 记录发送数据地址huart->TxXferSize = Size;                 // 总长度huart->TxXferCount = Size;                // 剩余长度(初始等于总长度)huart->gState = HAL_UART_STATE_BUSY_TX;   // 标记为发送中__HAL_UART_ENABLE_IT(huart, UART_IT_TXE); // 使能TXE中断(TDR寄存器空)return HAL_OK;}return HAL_BUSY;  // 若UART忙碌,返回忙状态
}

关键:函数仅配置参数并使能中断,不直接发送数据,实际发送由 TXE 中断完成。

2. 中断服务函数:数据发送的核心执行

当 TDR 寄存器为空(数据已转移到移位寄存器)时,触发 TXE 中断,执行流程如下:

  1. 中断入口:USART1_IRQHandler(异常向量表中的串口 1 中断入口);
    请添加图片描述

  2. 跳转至 HAL 库通用处理函数:HAL_UART_IRQHandler(&huart1)

  3. 核心发送逻辑(UART_Transmit_IT):

// 从缓冲区取1字节写入TDR寄存器
huart->Instance->DR = (uint8_t)(*huart->pTxBuffPtr++ & 0x00FF);
huart->TxXferCount--;  // 剩余长度减1if (huart->TxXferCount == 0)  // 若所有数据发送完成
{__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);  // 关闭TXE中断__HAL_UART_ENABLE_IT(huart, UART_IT_TC);    // 使能TC中断(发送完成)
}
  • TXE 中断:每发送 1 字节触发一次(除最后 1 字节),共触发Size-1次;
  • TC 中断:最后 1 字节从移位寄存器发送完成后触发,标记整个发送流程结束。
3. 发送完成回调:HAL_UART_TxCpltCallback

TC 中断触发后,HAL 库会调用发送完成回调函数(需用户重定义,默认是weak弱函数),用于通知 “发送已完成”。
usart.c中重定义回调函数:

/* USER CODE BEGIN 1 */
static volatile int g_tx_cplt = 0;  // 发送完成标志(volatile确保中断与主程序可见)// 发送完成回调:置位标志
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{if (huart == &huart1)  // 确认是USART1g_tx_cplt = 1;
}// 等待发送完成:主程序中调用,避免轮询
void Wait_Tx_Complete(void)
{while (g_tx_cplt == 0);  // 等待标志置位g_tx_cplt = 0;           // 复位标志
}
/* USER CODE END 1 */
4. 主程序调用:中断发送示例

main.c中发送字符串,通过Wait_Tx_Complete等待发送完成:

/* USER CODE BEGIN PV */
extern void Wait_Tx_Complete(void);  // 声明等待函数
/* USER CODE END PV *//* USER CODE BEGIN 2 */
char *str = "Hello_Embed (Interrupt Tx)!\r\n";
/* USER CODE END 2 */while (1)
{// 启动中断发送HAL_UART_Transmit_IT(&huart1, (uint8_t *)str, strlen(str));Wait_Tx_Complete();  // 等待发送完成(不占用CPU,仅在完成后继续)
}

实验结果:串口工具可稳定接收字符串,证明中断发送成功:
请添加图片描述

四、中断接收:从启动到完成的流程

中断接收的逻辑与发送类似:启动接收后,CPU 可处理其他任务,接收完成后通过回调函数通知。

1. 启动中断接收:HAL_UART_Receive_IT

函数会使能 RXNE 中断(RDR 寄存器非空),等待数据到来:

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{// 类似发送函数,记录接收缓冲区地址、长度,使能RXNE中断__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);  // 关键:使能接收非空中断return HAL_OK;
}
2. 中断服务函数:数据接收的核心执行

当 RDR 寄存器有数据(接收完成 1 字节)时,触发 RXNE 中断,执行流程如下:

  1. 中断入口同样是USART1_IRQHandler,跳转至HAL_UART_IRQHandler
  2. 核心接收逻辑(UART_Receive_IT):
// 从RDR寄存器读取1字节到缓冲区
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & 0x007F);
huart->RxXferCount--;  // 剩余接收长度减1if (huart->RxXferCount == 0)  // 若接收完成
{__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);  // 关闭RXNE中断HAL_UART_RxCpltCallback(huart);              // 调用接收完成回调
}
3. 接收完成回调:HAL_UART_RxCpltCallback

需用户重定义,用于标记接收完成:

/* USER CODE BEGIN 1 */
static volatile int g_rx_cplt = 0;  // 接收完成标志void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if (huart == &huart1)  // 确认是USART1g_rx_cplt = 1;
}// 等待接收完成
void Wait_Rx_Complete(void)
{while (g_rx_cplt == 0);  // 等待标志置位g_rx_cplt = 0;           // 复位标志
}
/* USER CODE END 1 */
4. 主程序调用:中断接收示例

目标:接收电脑发送的字符,加 1 后返回:

/* USER CODE BEGIN 2 */
char *str1 = "Please enter a char : \r\n";
char c;  // 存储接收的字符
extern void Wait_Tx_Complete(void);
extern void Wait_Rx_Complete(void);
/* USER CODE END 2 */while (1)
{// 发送提示信息HAL_UART_Transmit_IT(&huart1, (uint8_t *)str1, strlen(str1));Wait_Tx_Complete();// 启动中断接收1字节HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);Wait_Rx_Complete();  // 等待接收完成// 字符加1后返回c += 1;HAL_UART_Transmit_IT(&huart1, (uint8_t *)&c, 1);Wait_Tx_Complete();HAL_UART_Transmit_IT(&huart1, (uint8_t *)"\r\n", 2, 1000);Wait_Tx_Complete();
}
五、中断方式的局限性:仍存在数据丢失

上述代码在简单场景下可工作,但快速发送多字节时,仍会出现数据丢失,原因如下:

  • 接收完成后,需重新调用HAL_UART_Receive_IT才能继续接收下一字节;
  • 若 CPU 正在执行耗时操作(如HAL_UART_Transmit_IT发送返回数据),未及时重新使能接收中断,新数据会覆盖 RDR 寄存器中的旧数据,导致丢失。
    例如,电脑快速发送 “123”,单片机仅收到 “1” ,只返回 “2”:
    请添加图片描述
总结

中断方式通过 “硬件触发 + 回调通知” 减少了 CPU 轮询的资源占用,比查询方式更高效,但单纯的中断接收仍存在缺陷 ——未及时重新使能中断会导致数据丢失。因此需要改进中断方式的接收函数,这也是我下一篇笔记的内容,或者使用DMA的方式。

结尾

本文介绍了 UART 中断方式的收发流程,理解了回调函数的作用及 HAL 库中断处理的逻辑,同时明确了当前实现的局限性。下一篇我们将学习改进中断方式的方法,彻底解决 UART 数据丢失问题。
Hello_Embed 继续带你深入 UART 编程的进阶技巧,敬请期待~


文章转载自:

http://VK1dFYlN.nzdks.cn
http://zZztr3Fm.nzdks.cn
http://N1C0tJvn.nzdks.cn
http://WiLIhv3c.nzdks.cn
http://hLGcTump.nzdks.cn
http://b8iWgB7b.nzdks.cn
http://3AAkMFql.nzdks.cn
http://XinaH5jf.nzdks.cn
http://7hmwhQGW.nzdks.cn
http://5OHtCxn7.nzdks.cn
http://IfX6v28H.nzdks.cn
http://q3yu9o5K.nzdks.cn
http://Vl1COCzC.nzdks.cn
http://gv1rI10d.nzdks.cn
http://fxz3s5Wo.nzdks.cn
http://MfhIrDNe.nzdks.cn
http://vXQ1niHk.nzdks.cn
http://ql3AYFLc.nzdks.cn
http://QLN92oxn.nzdks.cn
http://RAWY0f6q.nzdks.cn
http://iSFKnGiB.nzdks.cn
http://MODd8Peu.nzdks.cn
http://0p6Iv7QK.nzdks.cn
http://wiGMtmGS.nzdks.cn
http://CCkqu36O.nzdks.cn
http://LRSyXA1N.nzdks.cn
http://qPhz4ov2.nzdks.cn
http://cM5CKz7C.nzdks.cn
http://BC6aWDzg.nzdks.cn
http://Aa8zNzy1.nzdks.cn
http://www.dtcms.com/a/369976.html

相关文章:

  • 【星闪】Hi2821 | PWM脉宽调制模块 + 呼吸灯例程
  • 具身智能模拟器:解决机器人实机训练场景局限与成本问题的创新方案
  • 【嵌入式】【科普】AUTOSAR学习路径
  • 大麦APP抢票-核心
  • Linux笔记---TCP套接字编程
  • SQL面试题及详细答案150道(81-100) --- 子查询篇
  • CentOS系统停服,系统迁移Ubuntu LTS
  • 基于Spring Boot的幼儿园管理系统
  • 《sklearn机器学习——聚类性能指标》Fowlkes-Mallows 得分
  • STAR-CCM+|雷诺数回顾
  • 设计整体 的 序分(三“释”)、正宗分(双“门”)和流通分(统一的通行表达式) 之3 “自明性”(腾讯元宝 之2)
  • MySQL集群高可用架构之组复制 (MGR)
  • GPT-5发布:统一智能体时代的开启——从“工具”到“协作者”的范式跃迁
  • 【iOS】push 和 present
  • 大数据毕业设计选题推荐-基于大数据的宫颈癌风险因素分析与可视化系统-Spark-Hadoop-Bigdata
  • 【xss基本介绍】
  • 无需公网IP,电脑随时与异地飞牛同步互联保持数据一致性
  • HTML 列表类型
  • 怎么用 tauri 创建一个桌面应用程序(Electron)
  • Redis《RedisSerializer》
  • 云原生的12个要素是什么?
  • uni-app倒计时公共组件 封装,倒计时组件
  • AI驱动的软件测试:革命性的自动化、缺陷检测与实验优化
  • 打包 Uniapp
  • Vue 项目性能优化实战
  • C++ Lambda 表达式完整指南
  • PlantSimulation 在汽车总装车间配送物流仿真中的应用
  • 浅谈前端框架
  • 深蓝汽车人事调整:邓承浩升任董事长,姜海荣出任首席执行官
  • github上传步骤