STM32CubeIDE新建项目过程记录备忘(六)USART接收不定长数据

        上一篇,实验了中断方式的USART接收,其过程是接收缓冲区接收到的的数据长度达到设定字节后调用回调函数进行处理,并开启下一次接收。在实际的工程中很多通信数据的帧长度并不确定,本篇就是要解决这个问题。


USART接收不定长数据的方法主要有两种:

方法1:每次接收一个字节,根据特定字符判断是否接收完成

核心思路
  1. 单次接收 1 字节:通过HAL_UART_Receive_IT()每次只接收 1 个字节,接收完成后进入中断回调函数。
  2. 循环触发中断:在中断回调中处理当前字节后,重新开启接收中断,实现连续接收。
  3. 结束标志判断:通过约定的结束符(如\n\r或特定帧尾)判断数据是否接收完成。
  4. 缓冲区管理:使用数组缓存接收数据,通过索引跟踪接收位置,防止溢出。

关于方法1的文章很多,不再重复。


方法2:“空闲中断”(IDLE Line Interrupt)配合DMA的接收中断机制

1. 空闲中断(IDLE Line Interrupt)的作用

        idle:空闲的。

        当串口接收数据时,若连续一段时间(通常为一个字符传输时间的 1.5 倍以上)没有新数据到来,硬件会触发 “空闲中断”。其核心功能是判断一段完整数据帧的结束—— 例如,上位机发送一帧数据后暂停,空闲状态的出现意味着当前帧传输完成。

2. DMA 的接收机制

        DMA(直接存储器访问)可在不占用 CPU 的情况下,将串口接收寄存器的数据直接搬运到内存缓冲区。此时,DMA 的作用是自动完成数据的连续接收和存储,避免 CPU 频繁中断来读取单字节数据,极大提升效率。

3. 两者配合的工作流程
  • DMA 持续接收:串口启动后,DMA 被配置为循环或单次模式,持续将接收数据写入缓冲区,并记录接收长度(可通过 DMA 计数器或自定义变量实现)。
  • 空闲中断触发帧结束:当数据帧传输完毕,串口进入空闲状态,触发空闲中断。此时 CPU 响应中断,通过读取 DMA 记录的接收长度,即可获取当前完整帧的数据量。
  • 处理与复位:CPU 在中断服务程序中处理缓冲区中的完整数据帧,处理完成后复位 DMA 和空闲中断标志,准备接收下一帧数据。
4. 优势
  • 高效性:DMA 负责数据搬运,CPU 仅在帧结束时被中断,减少 CPU 占用。
  • 完整性:空闲中断确保能准确捕获一帧数据的边界,避免数据拆分或拼接错误。

这种机制广泛用于串口通信(如 RS232/485)中,尤其适合不定长数据帧的接收场景。


下面是方法2的实现步骤:

  • 设定GPIO和时钟、通信等
  • USART和时钟

        在之前的SYM32模板里面,已经设定了USART的管脚、波特率等,时钟也进行了设定,这里不再重复。

  • 打开DMA
  1. 切到 DMA Settings 标签页

  2. 点击 Add 按钮

  3. 在弹出的列表里选

    • USART1_RX
    • Direction = Peripheral To Memory
    • Priority = Low(或 Medium,随意)
    • Mode = Circular(推荐,防止溢出)
    • Data Width = Byte
    • Increment = Memory(√)
    • 其余默认即可

点 OK 后,会看到列表里出现一行 USART1_RX DMA1 Channel5(不同芯片通道号不同)。

        4 .同样的方法,add一个USART1_TX的DMA channel:

  • 打开 USART1 全局中断 

确保UART1的全局中断优先级高于DMA。

黄齿轮生成代码。 


以下为编程阶段。

  • 新建关于串口通信的相关文件

由于之前在创建项目的时候选择了为每个外设生成.c和.h文件:

        配置了usart功能后,系统会自动生成相关的文件,在usart.s和usart.h中有关于usart外设的配置,这一点还是比较方便的。

        上面的usart.h和usart.c是底层驱动代码,是由STM32CubeIDE托管的文件,一般不用来存放应用层的代码。所以,新建uart_app.c和uart_app.h,所有与uart通信相关的应用层的各种定义和功能函数都放在这两个文件内。

  •  在uart_app.h中添加代码:

#ifndef __UART_APP_H
#define __UART_APP_H

#include "stm32f1xx_hal.h"   // 根据芯片换成对应 hal 头

#define UART_RX_BUF_LEN   256     // 最大一次接收字节数

extern UART_HandleTypeDef huart1;   // 在 usart.h 中声明

void UART_APP_Init(void);           // 供 main.c 调用
void UART_APP_IdleCallback(void);   // 在 USART1_IRQHandler 里调用

#endif
  •  在uart_app.c中添加代码:

#include "uart_app.h"
#include <string.h>

/* 接收缓冲区 -------------------------------------------------------------*/
static uint8_t  RxBuf[UART_RX_BUF_LEN];
static uint16_t RxLen = 0;      // 当前帧长度

/* 初始化:打开 DMA 接收 + 空闲中断 --------------------------------------*/
void UART_APP_Init(void)
{
    /* 使能空闲中断 */
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);

    /* 启动 DMA 循环接收 */
    HAL_UART_Receive_DMA(&huart1, RxBuf, UART_RX_BUF_LEN);
}

/* USART1_IRQHandler 里调用的空闲中断处理 --------------------------------*/
void UART_APP_IdleCallback(void)
{
    /* 1. 清 IDLE 标志 */
    __HAL_UART_CLEAR_IDLEFLAG(&huart1);

    /* 2. 关闭 DMA,计算本次实际接收字节数 */
    HAL_DMA_Abort(huart1.hdmarx);               // 立即停止 DMA
    RxLen = UART_RX_BUF_LEN - __HAL_DMA_GET_COUNTER(huart1.hdmarx);

    /* 3. 把收到的数据原样发回去 */
    //这里可以是其他任意功能,比如说数据存储、处理、操作GPIO等等,也可以在这里调用自定义的应用层功能函数
    if (RxLen > 0)
    {
        HAL_UART_Transmit_DMA(&huart1, RxBuf, RxLen);
    }

    /* 4. 重新打开 DMA 接收,继续监听下一帧 */
    HAL_UART_Receive_DMA(&huart1, RxBuf, UART_RX_BUF_LEN);
}
  • 在stm32f1xx_it.c中添加代码:
  • 添加用户include

#include "uart_app.h"
  • 修改stm32f1xx_it.c中的USART1_IRQHandler()函数

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  /* 如果产生了 IDLE 中断,就调用在uart_app.c中定义的的回调函数 */
      if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
      {
          UART_APP_IdleCallback();
      }

  /* USER CODE END USART1_IRQn 1 */
}
  • 在main.c中添加代码:
  • 添加用户include

#include "uart_app.h"
  • 在main()函数内添加uart_app的初始化:

  UART_APP_Init();          // 打开接收
  • 总结一下整体思路:
  1. uart_app.h内声明了uart初始化函数:UART_APP_Init()和空闲中断回调函数UART_APP_IdleCallback()原型。
  2. 在uart_app.c中定义了上面的初始化函数和回调函数。
  3. main.c中运行初始化函数:UART_APP_Init()。
  4. 在stm32f1xx_it.c中,当检测到了USART1中断,在USART1_IRQHandler()函数(USART1中断的回调函数)中判断当前是否空闲中断(IDLE Line Interrupt),如果是,就调用回调函数UART_APP_IdleCallback()。
  5. 在回调函数UART_APP_IdleCallback()定义应用函数和实现功能。
  • 生成代码和调试,并用仿真软件进行定时发送: 

### 实现STM32CubeIDE中通过串口接收不定长数据并打印 #### 使用串口中断和DMA技术 为了实现STM32CubeIDE中的串口接收不定长度的数据并在调试控制台或终端上打印,可以采用串口中断配合DMA的方式。这种方式能够高效地处理接收到的数据,并且不会阻塞主程序运行。 配置过程涉及初始化UART接口以及设置相应的中断和服务函数来管理数据流。具体来说,在`main.c`文件里定义全局变量用于存储接收到的信息缓冲区及其长度标志位: ```c uint8_t rx1_buffer[BUFFER_SIZE]; // 接收缓存数组 volatile uint16_t rx1_len = 0; // 记录实际接收到的有效字节数 volatile uint8_t rx1_cplt_flag = 0; // 数据接收完成标记 ``` 接着是在`MX_USART1_UART_Init()`之后启用IDLE线状态检测以便触发中断事件,同时启动DMA传输模式来进行批量读取操作: ```c __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); HAL_UART_Receive_DMA(&huart1, rx1_buffer, BUFFER_SIZE); ``` 当发生空闲线路条件时(即发送方停止发送),会进入对应的回调函数内执行特定逻辑判断是否存在有效载荷等待处理;如果确实有新消息到达,则计算其确切大小并通过调用API清除相关硬件寄存器的状态防止误判。最后重启DMA通道继续监听后续输入直到再次遇到静默期为止[^3]。 ```c void USART1_IRQHandler(void){ /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); HAL_UART_DMAStop(&huart1); rx1_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); rx1_cplt_flag = 1; } /* USER CODE END USART1_IRQn 1 */ } // 在主循环或其他适当位置检查rx1_cplt_flag并清零后重置DMA if (rx1_cplt_flag == 1){ for(int i=0;i<rx1_len;i++){ printf("%c",rx1_buffer[i]); } rx1_cplt_flag = 0; HAL_UART_Receive_DMA(&huart1, rx1_buffer, BUFFER_SIZE); } ``` 上述方法不仅简化了代码结构还提高了系统的响应速度与稳定性。值得注意的是,这里假设已经完成了必要的外设初始化工作并且正确设置了波特率等参数以匹配通信双方的要求[^1]。 对于想要进一步优化此功能的应用开发者而言,还可以考虑引入环形队列机制来增强多包连续接收的能力,或是利用RTOS特性实现更复杂的任务调度方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深蓝海拓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值