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

HAL库串口

串口中断模式

1. CubeMX 必须配置(2 步)

  1. 开启串口外设:USART1/2/3 选异步通信
  2. NVIC 中断使能
    • 勾选USARTx global interrupt
    • 配置抢占优先级 / 子优先级(别和定时器 / EXTI 冲突)

2. 核心原理(中断发送)

HAL 串口发送分三种:

  • 阻塞:HAL_UART_Transmit()卡死等发完
  • 中断:HAL_UART_Transmit_IT()主推
  • DMA:HAL_UART_Transmit_DMA()

中断发送:调用 IT 函数 → 触发发送中断 → 自动发数据 → 发完进发送完成回调

3. 用到的关键函数

① 触发中断发送(主函数 / 自己逻辑里调用)

// huart1 是你的串口句柄;pData数据首地址;Size长度 HAL_UART_Transmit_IT(&huart1, (uint8_t*)"Hello\r\n", 6);

② 发送完成回调(必须自己写,发完自动进)

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1)// 判断是哪个串口发完了 { // 串口1发送完成后,在这里写后续逻辑 // 比如:标记发送完成、触发下一包、翻转LED HAL_UART_Receive_IT(&huart2, rx_data, 2);// 重新开启中断接收 } }

最容易犯的 2 个错误

  • 接收中断只收一次原因:回调里没重新调用HAL_UART_Receive_IT

  • 连续发送死机原因:上一次还没发完,又调用发送函数解决:判断huart->gState == HAL_UART_STATE_READY

③ 底层中断入口(不用自己改,CubeMX 自动生成)

void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&huart1); // HAL清标志、调度回调 }

发送流程

你调用Transmit_ITHAL_UART_Transmit_IT(&huart1, data, len);

  • 使能TXE 发送寄存器空中断
  • 使能TC 发送完成中断
  • 把数据、长度存入句柄huart
  • 函数立刻返回,不阻塞主程序

→ 硬件发数据

每发完一个字节 触发TXE 中断全部发完 触发TC 中断

→ 进USARTx_IRQHandler中断服务函数

void USART1_IRQHandler(void)

{ HAL_UART_IRQHandler(&huart1); // 库函数处理 }

→ 库处理

  • 清除标志位
  • 自动发送下一个字节
  • 发完所有数据后

→ 发完 →TxCpltCallback串口发送完成中断回调函数

当串口用中断方式(HAL_UART_Transmit_IT)把数据全部发完后,系统会自动调用这个函数,告诉你:发送结束了!

你调用HAL_UART_Transmit_IT发数据数据在后台自动发,不卡主程序发完那一刻,CPU 自动跳进这个函数你在这里写:发送完成后要做的事

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // 发送完成!你在这里写逻辑 }

接收流程

先开Receive_IT→ 收到数据→ 进USARTx_IRQHandler→ 库处理→ 收完 →RxCpltCallback重新开接收

串口DMA模式

DMA = 不需要 CPU 参与,硬件自己搬运数据发串口 / 收串口

中断发送:CPU 每发一个字节都要进一次中断

DMA 发送:CPU 说 “开始发”,然后硬件自己发,发完通知一声CPU 完全解放

  • IDLE 空闲中断:串口检测到「一段时间没数据」,判定一帧报文收完
  • 组合实现:随便发多长,自动识别一帧数据,不用定死长度

二、DMA 需要配置什么(CubeMX)

1. 串口配置

  • 选 Async 异步模式
  • 波特率等正常设置

2. DMA Settings 点进去

发送 TX

  • Add →USART1_TX
  • 模式:Normal正常模式 /Circular循环模式
  • 数据宽度:Byte

接收 RX(可选)

  • Add →USART1_RX

3. 必须开 NVIC 中断

  • 开启 USART1 global interrupt
  • 开启 DMA 中断(默认会自动开)

三、DMA 发送用什么函数

启动 DMA 发送

HAL_UART_Transmit_DMA(&huart1, (uint8_t*)"Hello DMA\r\n", 11);

发送完成后自动进的函数

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // DMA 发完也进这里!和 IT 共用同一个回调! }

DMA 和中断发送共用同一个完成回调!


四、DMA 接收

开启 DMA 接收(通常初始化时开一次)

uint8_t rx_buf[100]; HAL_UART_Receive_DMA(&huart1, rx_buf, 100);

接收完成自动进

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 收满 100 字节进来这里 }

五、完整执行流程(DMA 发送)

  1. 你调用HAL_UART_Transmit_DMA
  2. CPU 立刻返回,去干别的
  3. DMA 硬件自动把数据搬到串口
  4. 发完 → 触发中断
  5. 进入HAL_UART_TxCpltCallback告诉你发完了

【DMA+IDLE 空闲中断】不定长接收

一、核心原理

  1. DMA:硬件搬运数据,收数据全程不占 CPU
  2. IDLE 空闲中断:串口检测到「一段时间没数据」,判定一帧报文收完
  3. 组合实现:随便发多长,自动识别一帧数据,不用定死长度

二、CubeMX 必配步骤

  1. 串口异步、配波特率
  2. DMA 设置:
    • USARTx_RX 新增 DMA,模式:Normal
    • 字节 8 位
  3. NVIC:
    • 开 USARTx 全局中断
    • 开 DMA 中断
  4. 开启:串口 IDLE 空闲中断(代码里初始化开启)

三.流程

全局变量定义(main.husart.c里)

// 定义接收缓冲区,大小根据你的最大一帧数据长度设置 #define UART2_RX_BUF_SIZE 256 // 一帧最多收256字节,可改 uint8_t uart2_rx_buf[UART2_RX_BUF_SIZE]; // DMA接收缓存 uint16_t uart2_rx_len = 0; // 一帧实际收到的长度 uint8_t uart2_rx_flag = 0; // 接收完成标志位

3. 初始化开启(main.cMX_USART2_UART_Init()之后调用)

// 开启串口2的DMA+IDLE不定长接收,只需要调用这一行! HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uart2_rx_buf, UART2_RX_BUF_SIZE);

4. 重写接收完成回调函数(核心!)

这个函数会在两种情况下自动调用:

  1. 串口检测到空闲(IDLE),一帧数据收完
  2. DMA 缓冲区满(收到了UART2_RX_BUF_SIZE字节)
/* USER CODE BEGIN 1 */ // 串口IDLE+DMA接收完成回调函数 void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if(huart->Instance == USART2) // 判断是串口2触发 { uart2_rx_len = Size; // Size就是这一帧的实际长度! uart2_rx_flag = 1; // 标记一帧接收完成 //关闭 “传了一半” 的提醒 HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT); // 重新开启接收,否则只能收一帧! HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uart2_rx_buf, UART2_RX_BUF_SIZE); } } /* USER CODE END 1 */

5. 主循环处理数据(业务逻辑放这里)

while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if(uart2_rx_flag == 1) { uart2_rx_flag = 0; // 清零标志位 // ✅ 这里就是你收到的不定长数据! // uart2_rx_buf[0...uart2_rx_len-1] 是有效数据 // uart2_rx_len 是这一帧的实际长度 // 示例1:把收到的数据原样发回去(回显测试) HAL_UART_Transmit_DMA(&huart2, uart2_rx_buf, uart2_rx_len); // 示例2:协议解析(比如Modbus、自定义协议) // uart_protocol_parse(uart2_rx_buf, uart2_rx_len); } /* USER CODE END 3 */ }

四、关键原理 & 避坑指南

1. 执行流程(一目了然)

  1. 你调用HAL_UARTEx_ReceiveToIdle_DMA→ 开启 DMA+IDLE
  2. 上位机发数据 → DMA 自动把数据搬到uart2_rx_buf
  3. 串口空闲(没数据了)→ 触发 IDLE 中断
  4. HAL 库自动调用HAL_UARTEx_RxEventCallback,传入实际长度Size
  5. 你在回调里标记完成,然后必须重新开启接收,等待下一帧

2. 必须注意的 3 个坑

  1. 回调函数是中断上下文!

    • 绝对不能写HAL_Delay()while(1)等阻塞 / 延时函数
    • 不能做耗时操作,只做标志位标记、长度保存
    • 业务逻辑(协议解析、数据处理)必须放到主循环里
  2. 必须重新开启接收!

    • 每次回调里都要再调用一次HAL_UARTEx_ReceiveToIdle_DMA
    • 否则只会收第一帧,之后就再也收不到了
  3. 缓冲区大小要合理

    • UART2_RX_BUF_SIZE要大于你最大一帧数据的长度
    • 否则会触发缓冲区满回调,数据被截断
http://www.cnnetsun.cn/news/1728661.html

相关文章:

  • 泥泞中的 RAG
  • 潘通色和标准色是?
  • 医药专利数据库推荐:聚焦靶点、原研药与核心专利
  • 从代码生成到解释:用 Copilot 逆向学习 Kotlin 的 3 种方法
  • 不止于算个数:手把手教你用C++分析惠斯通电桥实验的测量不确定度
  • 突破文件传输瓶颈:百度网盘秒传技术的深度解析与行业应用
  • MISSA-BP分类预测模型代码功能说明
  • linux——线程相关函数
  • DC-1靶场渗透实战:从信息收集到Linux提权全流程解析
  • 2025届最火的五大降重复率方案实际效果
  • opencv透视变换实战:从算法原理到图像矫正的完整实现
  • NextFaster 电商数据库设计深度解析:从集合到产品的完整架构指南
  • 瑞典隆德大学 AI 模型血检识别 5 种神经疾病
  • ClusterFuzz架构深度解析:可扩展模糊测试平台的10大核心组件揭秘
  • WarcraftHelper:突破魔兽争霸3性能瓶颈的5个实用优化技巧
  • Perplexica数据库选型指南:SQLite与PostgreSQL对比分析
  • MaaFramework多语言绑定技术深度解析:5种主流语言集成方案对比与实践指南
  • 避坑指南:Ecology9流程创建失败的7个常见错误及解决方案(附调试技巧)
  • 实战指南:基于快马平台生成物联网智能温控pid系统代码
  • 2026届毕业生推荐的六大AI写作助手推荐榜单
  • 告别虚拟机!在Windows 11的WSL2里搞定RK3588交叉编译环境(基于gcc-linaro-7.5.0)
  • 快马平台十分钟速成:用自然语言驱动你的第一个任务管理Agent原型
  • LabVIEW发动机性能评估与故障诊断
  • 终极指南:如何通过stanford-tensorflow-tutorials可视化损失函数,轻松理解模型训练误差变化
  • 如何快速搭建专业开发者作品集:DevPortfolio项目结构深度解析
  • 利用快马平台快速原型:五分钟构建你的第一个multisim风格电路仿真器
  • 重构魔兽争霸III地图编辑:HiveWE的技术革新与性能突破
  • 语燕输入法YuyanIme:基于Rime引擎的终极隐私安全输入法解决方案
  • 颠覆窗口管理:PinWin如何革新你的多任务工作流
  • 别再傻傻用中断计数了!STM32F4主从定时器门控模式,精准输出指定数量PWM脉冲的保姆级教程