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

news/2025/9/22 18:37:45/文章来源:https://www.cnblogs.com/ljbguanli/p/19105822

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--;
// 剩余长度减1
if (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) // 确认是USART1
g_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--;
// 剩余接收长度减1
if (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) // 确认是USART1
g_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://www.mzph.cn/news/909994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有关网站建设的外文参考文献公司装修报价

vp9协议梳理-header头文件 本文是对vp9视频码流中header中包含的语法元素的一个分类整理,及其对具体的解码过程的影响的分析。 这里写目录标题 vp9协议梳理-header头文件1. Vp9码流中的header头文件2. profile3. show_existing_frame, frame_to_show_map_idx4. fr…

做兽设的网站wordpress网盘主题

高级分布式系统汇总:高级分布式系统目录汇总-CSDN博客 自动化是关于一切人造系统自动、智能、自主、高效和安全运行的科学与技术 计算机控制技术是实现自动化的主要方法和手段 分布式控制技术是伴随着机器大工业生产而诞生的特殊计算机控制技术 计算机控制系统 …

微网站域名未备案网站加速

在Python字典中键是唯一的,但是业务需求是将不同的数据传递到不同的接口,接口列表中存在3个相同的接口,需要将3个接口对应的数据合并一同发送,逻辑实现如下 merge_dict {}for file in files:path os.path.join(folder_path, fil…

梅林网站建设沈阳工伤保险做实在哪个网站

3.康耐视VisionPro高级脚本系列教程-3.脚本编辑错误和运行错误调试方法,break和Contitinuee的差别_哔哩哔哩_bilibili 其实人生就是“有时有意思,有时没意思”。 心里有太多的不甘心,太多的苦水,是没法再吃学习的苦&#xff0c…

温州高端企业网站建设旅游网站内容

T1:虽说大家都被cdq限制住了思维,我一个注意到了排列有问题的还是凉了,这个我没有一点办法了。 链接:cdq T2:留坑,太毒了 T3:考场上就不觉得能写,虽然心里想到一些做法,还…

梅州网站建设中国建设人才信息网站查询

为什么要仿真随机路由? 路由器测试中,为了最大程度还原现网路由情况,评估路由器在现网环境下稳定工作各项指标,需要对导入路由进行离散仿真,目前路由仿真可分为导入路由与生成路由两种方式,导入路由需要现…

游戏ui设计网站东莞网站建设招聘

SQL表值函数和标量值函数的区别 写sql存储过程经常需要调用一些函数来使处理过程更加合理,也可以使函数复用性更强,不过在写sql函数的时候可能会发现,有些函数是在表值函数下写的有些是在标量值下写的,区别是表值函数只能返回一个…

企业网站建设的经验心得做网站的实践报告

一、需求及项目准备 二、系统框图 三、硬件接线 四、语音模块配置 五、模块测试 一、需求及项目准备 语音接入控制各类家电,如客厅灯、卧室灯、风扇Socket网络编程,实现Sockect发送指令远程控制各类家电烟雾警报监测, 实时检查是否存在煤气…

贪心算法应用:多重背包启发式疑问详解

贪心算法应用:多重背包启发式疑问详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mon…

中标公示查询网站网络营销服务外包

加速时如何换挡,您知道吗?为了使换挡过程顺利进行,变速器内齿轮平稳啮合,必须掌握好发动机转速,在适当时机推动变速杆操纵齿轮啮合。为此,要通过反复练习,一边踩踏油门踏板,一边听发动机运转声…

划重点|云栖大会「AI 原生应用架构论坛」看点梳理

AI 潮正以不可阻挡之势重塑千行百业。兴奋与喧嚣过后,如何将强大的 AI 能力,真正高效、可靠地融入企业业务、从“可用”走向“好用”等问题是所有企业和技术人必须解决的问题。 答案正在指向一个全新的范式——AI 原…

君子如水,心中有火:vivo本心而为30周年

「 水之灵的战术应变,水之韧的战略坚守,水之谦的价值观底色 」 不同时代、不同背景下的企业,有着不同的生存哲学。 在过去三十年中国企业迅猛发展的历程中,中国很多科技企业大都是在强调竞争,把对手干掉,最终赢家…

Margin 塌陷问题如何解决?触发BFC。BFC的概念和触发条件

1️⃣ 什么是 Margin 塌陷 【现象】两个垂直方向相邻的块级盒子(兄弟或父子)之间的margin 会合并为其中的最大值,而不是两者相加。【影响】兄弟元素:上下margin合并为其中的最大值  父子元素:如果父元素没有paddi…

网站开发目的与意义wordpress 您没有足够的权限访问该页面.

如果需要一个全局对象,如对话框、系统日志、显卡等设备的驱动程序对象、一台PC连接一个键盘等。这样的全局对象只能是一个且是全局的,这就是单例模式,如何实现呢?1 不能在类外部通过构造函数新建对象:构造函数的访问方…

返利网站怎么做做我的狗在什么网站上看

以下内容摘自笔者即将出版的最新著作《深入理解计算机网络》一书。本书将于12月底出版上市,敬请留意!! 本书原始目录参见此文:http://winda.blog.51cto.com/55153/1063878 5.3.2 循环冗余校验检错方案 上节介绍的奇偶校验码&#…

哪些网站做品牌折扣的建站公司技术服务费

在开发工程中线程可以帮助我们提高运行速度,Android开发中我知道的线程有四个一个是老生长谈的Thread,第二个是asyncTask,第三个:TimetTask,第四个是Looper,四个多线程各有个的有点,Thread的运行速度是最快的,AsyncTas…

什么企业做网站比较方便呢微信注册小程序步骤

1, 概述 1.1 课题背景 本系统由说书客面向广大民营药店、县区级医院、个体诊所等群体的药品和客户等信息的管理需求,采用SpringSpringMVCMybatisEasyui架构实现,为单体药店、批发企业、零售连锁企业,提供有针对性的信息数据管理…

邵东网站开发微信小程序h5开发

flex如何做响应式设计Responsive design is not just about the web that automatically adjusts to different screen resolutions and resizeable images, but designs that are crucial for web performance.自适应设计不仅涉及可自动适应不同屏幕分辨率和可调整大小图像的网…

郑州门户网站建设哪家好有了自己的域名怎么做网站

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: 随着互联网技术的不断…

广州建站模板诚信网站平台建设方案

在无限的整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...中找到第 n 个数字。 注意: n 是正数且在32为整形范围内 ( n < 231)。 示例 1: 输入: 3 输出: 3 示例 2: 输入: 11 输出: 0 说明: 第11个数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是0&#xff0c;它是…