你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【STM32C5教程】(10)LPUART DMA 通信实验

[复制链接]
HuangXF 发布时间:2026-10-22 10:45

1. 项目简介

1.1 实验目标

本实验在 NUCLEO-C542RC 开发板上,使用 LPUART1 通过板载虚拟串口,以 DMA 方式连续发送 JustFloat 格式的锯齿波数据,并在 VOFA+ 中显示波形。数据格式以 vofa_justfloat.md 为准。

功能需求:

  • 使用 LPUART1,115200 波特率、8 数据位、1 停止位、无校验,通过板载 ST-LINK 虚拟串口发送数据;
  • 使用 DMA 发送,每帧包含 1 个浮点采样值和固定帧尾,共 8 字节;
  • 锯齿波数据每 10 ms 提交一帧,周期约 1 秒;
  • 串口数据流只包含 JustFloat 数据帧,不插入文本、换行或图片数据;
  • 在发送过程中不得改写 DMA 正在读取的缓冲区。

开发板和 MCU 型号: NUCLEO-C542RC 开发板,搭载 STM32C542RCT6 MCU。

<br>

1.2 实验条件

硬件:

  • NUCLEO-C542RC 开发板
  • 支持数据传输的 USB Type-C 线(用于连接 ST-LINK 接口,同时提供下载、调试和虚拟串口)

软件:

  • STM32CubeMX2(用于引脚、时钟、LPUART1 和 DMA 配置与代码生成)
  • VS Code + STM32Cube 插件(用于编译、烧录、调试)
  • VOFA+(用于串口数据接收与波形显示)

<br>

1.3 VOFA+ JustFloat 协议说明

VOFA+ 支持多种数据引擎,JustFloat 是面向波形显示的二进制协议。其帧结构为:N 个 32 位小端浮点数 + 4 字节固定帧尾 0x00 0x00 0x80 0x7F。VOFA+ 依靠帧尾识别一帧结束,按帧长解析后绘制各通道波形。

JustFloat 的数据帧由两部分组成:

struct Frame {
    float ch_data[CH_COUNT];   // 浮点通道数据(小端)
    unsigned char tail[4];     // 固定帧尾
};
  • ch_data 为浮点型通道数据,采用小端字节序。STM32 默认即为小端,可直接通过内存拷贝写入。
  • CH_COUNT 为每帧的通道数量。
  • tail 为固定帧尾,内容为 0x00 0x00 0x80 0x7F,对应 +Infinity 的小端表示。VOFA+ 依靠帧尾识别一帧的结束,因此帧尾必须完整、不能缺失。

本实验每帧发送 1 路锯齿波数据,CH_COUNT = 1,每帧共 8 字节:前 4 字节为当前采样值的 float 小端表示,后 4 字节为固定帧尾。串口数据流中只包含 JustFloat 数据帧,不插入文本、换行或调试打印。VOFA+ 侧串口参数需与 MCU 一致(115200、8N1、无流控),数据引擎选择 JustFloat。

<br>

2. 硬件说明

2.1 NUCLEO-C542RC 开发板

NUCLEO-C542RC 是 ST 基于 STM32C542RCT6 推出的 Nucleo-64 开发板。主要特性:

  • MCU:STM32C542RCT6,Arm Cortex-M33 内核,最高主频 144 MHz,256 KB Flash,64 KB RAM
  • 调试器:板载 STLINK-V3EC,支持大容量存储、虚拟串口和调试端口三种功能
  • 用户 LED:LD1,连接 PA5
  • 用户按键:B1,连接 PC13
  • 扩展接口:Arduino Uno V3 和 ST morpho 连接器

本实验通过板载 ST-LINK 的虚拟串口(VCP)将 LPUART1 的数据传输到 PC,无需额外外接 USB 转串口模块。一根 USB 线同时完成供电、下载、调试和串口通信。

<br>

2.2 引脚说明

引脚 复用功能 Swap 后实际功能 说明
PA2 LPUART1_RX(AF3) LPUART1_TX 连接 ST-LINK VCP 的 RX
PA3 LPUART1_TX(AF3) LPUART1_RX 连接 ST-LINK VCP 的 TX

PA2、PA3:配置为 LPUART1 复用功能(AF3),并启用 Tx/Rx Pins swapping。引脚复用配置本身不变,通过外设内部交换完成信号路由。

STM32C542RCT6 的 LPUART1 默认复用功能(AF3)映射为 PA2=RX、PA3=TX。而 NUCLEO-C542RC 板载 ST-LINK 的虚拟串口要求 MCU 侧的 TX 引脚连接到 PA2。默认映射与板载 VCP 连线不匹配,因此必须启用 LPUART1 的 Tx and Rx Pins swapping 功能。交换后,PA2 实际输出 LPUART1_TX,PA3 实际接收 LPUART1_RX,与板载 VCP 正确对接。生成代码中的引脚注释仍显示原始复用名称,这是正常现象。

<br>

2.3 DMA 资源分配

资源 配置 说明
LPDMA1_CH0 LPUART1_TX DMA 内存到外设,源地址递增,目标地址固定,字节宽度
LPDMA1_CH1 LPUART1_RX DMA 已配置,本实验未启动接收

<br>

3. STM32CubeMX2 工程配置

3.1 新建工程

本实验采用 MCU 方式创建工程,即以芯片型号 STM32C542RC 为起点,而非开发板方式。

  1. 进入 MCU 选择界面 启动 STM32CubeMX2,在首页点击 MCU,进入芯片型号选择列表。
  2. 搜索并选中芯片 在 MCU name 输入框中输入 STM32C542RC(或完整型号 STM32C542RCT6),从筛选出的列表中选择对应型号。
  3. 填写项目信息 输入项目名称 LPUART_DMA 和项目保存路径。项目名称避免使用特殊字符,路径建议使用纯英文目录。

image.png

<br>

3.2 基础配置

工程创建后,软件自动进入 Pinout 视图。

时钟配置:本工程系统时钟配置为 144 MHz,LPUART1 时钟源为 APB3,频率 144 MHz,串口预分频系数为 1。

image.png

<br>

3.3 LPUART1 配置

(1)启用 LPUART1

在左侧外设列表中找到 Connectivity → LPUART1 ,激活(Active)LPUART1。

在 Main features 中,将 Mode 设置为 Asynchronous(异步模式)。设置完成后,Pinout 视图中 PA2 自动分配为 LPUART1_RX,PA3 自动分配为 LPUART1_TX(均为 AF3 复用功能)。

image.png

image.png

(2)LPUART1 参数设置

在 Main features 中设置:

参数 值
Baud Rate 115200 Bits/s
Word Length 8 Bits (including Parity)
Parity None
Stop Bits 1
Data Direction Receive and Transmit
Hardware Flow Control None
Over Sampling 16

<br>

image.png

<br>

(3)启用 TX/RX 引脚交换

在 Advanced Features 中,将 Tx and Rx Pins swapping 设置为 Enabled。

STM32C542RCT6 的 LPUART1 默认 AF3 映射为 PA2=RX、PA3=TX,而 NUCLEO-C542RC 板载 ST-LINK 的虚拟串口要求 MCU 侧的 TX 连接到 PA2。启用 Swap 后,外设内部交换 TX/RX 信号路由,使 PA2 实际输出 TX、PA3 实际接收 RX,与板载 VCP 正确对接。引脚复用配置(AF3)本身不变。

image.png

<br>

(4)配置 DMA Settings

在 DMA Settings 中,点击 DMA Tx 后的 “Enable” 开关 激活 TX DMA 请求,CubeMX2 会自动将该请求分配到 LPDMA1_CH0。

参数 值
Request Tx LPUART1_CH0
Mode Direct transfer
Peripheral Request LPUART1_Tx_dma
Direction Memory To Peripheral
Source Data Width Byte
Destination Data Width Byte
Priority Low

如需接收功能,可选择 LPUART1_Rx,方向设为 Peripheral To Memory。

image.png

<br>

(5)使能中断

设置如下中断使能:

  • LPUART1 global interrupt
  • LPDMA1_CH0 interrupt(TX DMA 完成中断)

TX DMA 完成中断是 HAL_UART_TxCpltCallback 被调用的前提。

<br>

3.4 项目配置

  1. 点击 Project Settings,进入工程设置页面。
  2. 在 IDE Format 下拉选项中选择 CMake。
  3. 在 CMake Toolchain 下拉选项中选择 GCC。
  4. 确认配置无误后,点击 Generate Code。CubeMX2 会执行配置检查,如果没有错误,开始在指定路径下生成 CMake 工程文件。

image.png

<br>

4. 编程实现

4.1 实现思路

采用 “主循环定时调度 + DMA 单次发送 + 静态帧缓冲区” 的方案:

mx_system_init()              // CubeMX2 生成:时钟、GPIO、LPUART1、DMA 初始化
    │
    ▼
主循环 wave_process()
    │
    ├─ 检查是否有发送错误待恢复
    │      └─ 是 → HAL_UART_AbortTransmit(),等待下一个周期
    │
    ├─ 距上次提交 < 10 ms?或 UART TX 状态非空闲?
    │      └─ 是 → 直接返回
    │
    ├─ 将当前采样值写入帧缓冲区前 4 字节(memcpy,小端)
    │
    ├─ HAL_UART_Transmit_DMA() 提交一帧(8 字节)
    │      ├─ HAL_OK   → 采样序号加一,超过 99 归零
    │      ├─ HAL_BUSY → 本次跳过,不计错误
    │      └─ 其他     → 记录错误,置恢复标志
    │
    └─ 更新上次提交时刻

DMA 与阻塞发送的对比:HAL_UART_Transmit() 会逐字节等待发送寄存器空,115200 波特率下一帧 8 字节约需 0.7 ms,期间 CPU 无法执行其他任务。DMA 由硬件搬运数据,CPU 只需调用一次启动函数即可返回。

单帧提交与 DMA 循环模式:DMA 设为 Direct transfer(即 Normal 模式),每次传输完成后停止。主循环按 10 ms 周期重复提交,每帧的采样值都可以更新。循环模式下 DMA 会反复发送同一个缓冲区,波形无法变化。

提交前检查 TX 状态:HAL_UART_Transmit_DMA() 在上一帧尚未发完时调用会返回 HAL_BUSY。通过 HAL_UART_GetTxState() 判断为空闲再提交,避免无意义的忙状态返回,调度逻辑更清晰。

静态帧缓冲区:DMA 在后台读取内存。缓冲区如果定义为栈上的局部变量,函数返回后内存可能被覆盖,导致发送数据错乱。静态存储区在整个程序生命周期内有效,DMA 读取期间不会被回收。

时间调度使用 HAL_GetTick() 差值:(now - last) >= 10 的写法在毫秒计数器回绕时仍然正确,且是非阻塞的,主循环不会被延时卡住。

关键 HAL 函数:

步骤 HAL 函数 作用
获取 LPUART1 句柄 mx_lpuart1_uart_gethandle() 返回 CubeMX2 生成的 LPUART1 句柄
查询 TX 状态 HAL_UART_GetTxState() 判断上一帧是否发送完成
启动 DMA 发送 HAL_UART_Transmit_DMA() 提交一帧数据,立即返回
中止发送 HAL_UART_AbortTransmit() 出错时停止 DMA 并清理状态
获取毫秒时基 HAL_GetTick() 10 ms 周期调度

<br>

4.2 关键代码

在 VS Code 中打开生成的 CMake 工程,找到 Core/Src/main.c,添加以下代码。

(1)文件头部添加头文件与宏定义

#include "main.h"
#include "mx_lpuart1.h"
#include <string.h>

/* Private define ------------------------------------------------------------*/
#define WAVE_SAMPLE_INTERVAL_MS 10U
#define WAVE_SAMPLES_PER_PERIOD 100U
#define JUSTFLOAT_FRAME_SIZE 8U

_Static_assert(sizeof(float) == 4U, "JustFloat requires a 32-bit float");

(2)静态帧缓冲区与统计变量

/* Private variables ---------------------------------------------------------*/
static uint8_t wave_tx_frame[JUSTFLOAT_FRAME_SIZE] =
{
  0U, 0U, 0U, 0U, 0x00U, 0x00U, 0x80U, 0x7FU
};
static volatile uint32_t wave_tx_error_pending;
static volatile uint32_t wave_tx_completed_count;
static volatile uint32_t wave_tx_error_count;

/* Private functions prototype -----------------------------------------------*/
static void wave_process(void);

(3)主函数

int main(void)
{
  if (mx_system_init() != SYSTEM_OK)
  {
    return (-1);
  }
  else
  {
    while (1)
    {
      wave_process();
    }
  }
}

(4)波形发送调度函数

static void wave_process(void)
{
  static uint32_t last_sample_tick;
  static uint32_t sample_index;
  hal_uart_handle_t *huart = mx_lpuart1_uart_gethandle();
  const uint32_t now = HAL_GetTick();

  if (wave_tx_error_pending != 0U)
  {
    /* 出错后在中断外恢复:先停止 DMA,再等待下一个周期 */
    wave_tx_error_pending = 0U;
    if (HAL_UART_AbortTransmit(huart) != HAL_OK)
    {
      wave_tx_error_pending = 1U;
    }
    last_sample_tick = now;
    return;
  }

  if (((uint32_t)(now - last_sample_tick) < WAVE_SAMPLE_INTERVAL_MS) ||
      (HAL_UART_GetTxState(huart) != HAL_UART_TX_STATE_IDLE))
  {
    return;
  }

  /* STM32 为小端,float 直接拷贝到前 4 字节,后 4 字节帧尾保持不变 */
  const float sample = (float)sample_index;
  memcpy(wave_tx_frame, &sample, sizeof(sample));

  const hal_status_t status = HAL_UART_Transmit_DMA(huart, wave_tx_frame,
                                                  sizeof(wave_tx_frame));
  last_sample_tick = now;
  if (status == HAL_OK)
  {
    sample_index++;
    if (sample_index >= WAVE_SAMPLES_PER_PERIOD)
    {
      sample_index = 0U;
    }
  }
  else if (status != HAL_BUSY)
  {
    wave_tx_error_count++;
    wave_tx_error_pending = 1U;
  }
}

(5)UART 发送完成与错误回调

void HAL_UART_TxCpltCallback(hal_uart_handle_t *huart)
{
  if (huart == mx_lpuart1_uart_gethandle())
  {
    wave_tx_completed_count++;
  }
}

void HAL_UART_ErrorCallback(hal_uart_handle_t *huart)
{
  if (huart == mx_lpuart1_uart_gethandle())
  {
    wave_tx_error_count++;
    wave_tx_error_pending = 1U;
  }
}

代码说明:

  • wave_tx_frame 为静态数组,DMA 读取期间始终有效。前 4 字节每次发送前更新,后 4 字节 0x00 0x00 0x80 0x7F 为 JustFloat 固定帧尾。
  • memcpy 将 float 写入字节数组,避免指针强制转换的别名问题,也避免结构体填充影响帧长。
  • HAL_UART_GetTxState() 返回 HAL_UART_TX_STATE_IDLE 表示上一帧已发送完成,可以提交下一帧。
  • HAL_UART_Transmit_DMA() 返回 HAL_OK 只表示启动成功,不表示数据已发完;真正完成由 TX DMA 中断触发 HAL_UART_TxCpltCallback。
  • HAL_UART_ErrorCallback 在串口出错时被调用,置位 wave_tx_error_pending,主循环在下一轮调用 HAL_UART_AbortTransmit() 恢复。
  • 采样序号从 0 递增到 99 后归零,配合 10 ms 周期,锯齿波周期约为 1 秒。

<br>

5. 编译烧录与运行测试

5.1 编译与烧录

  1. 打开工程:用 VS Code 打开 CubeMX2 生成的 CMake 工程文件夹(如 LPUART_DMA_cmake)。
  2. 编译:点击 VS Code 底部状态栏的 Build 按钮,或使用命令面板执行 CMake 构建。编译完成后,终端应输出 Build finished with exit code 0,无 error。
  3. 连接开发板:用支持数据传输的 USB Type-C 线连接 NUCLEO-C542RC 的 ST-LINK USB 口(CN1)。
  4. 确认设备识别:打开设备管理器,在“端口 (COM 和 LPT)”下应能看到 STLink COMx。
  5. 烧录:点击 VS Code 中的 Flash 按钮,将固件下载到开发板。

image.png

<br>

5.2 运行与测试

烧录完成后,点击 VS Code 中的 Run 按钮,程序开始运行。

(1)VOFA+ 连接设置

打开 VOFA+,在串口连接区域选择与开发板对应的 STLink COMx。 串口参数设置为:115200、8 数据位、1 停止位、无校验、无流控;数据引擎选择 JustFloat。

(2)波形显示 烧录完成后,点击 VS Code 中的 Run 按钮,程序开始运行。

打开 VOFA+,选择对应的 STLink COM 端口,串口参数设为 115200、8 数据位、1 停止位、无校验、无流控,数据引擎选择 JustFloat,打开串口连接并添加 1 个波形通道。观察波形,应显示连续、无断裂的锯齿波,数值从 0 递增到 99 后回落,周期约 1 秒。

编译、烧录、运行及 VOFA+ 波形显示均正常,符合预期。

image.png

<br>

6. 小结

本实验通过 LPUART1 和 DMA 实现了 JustFloat 格式锯齿波数据的连续发送,并在 VOFA+ 中成功显示波形,掌握了 STM32C5 系列 LPUART1 的 DMA 发送配置与使用方法。

核心逻辑可以概括为:“主循环定时调度 → 封装 JustFloat 帧 → DMA 单次发送 → 中断通知完成”。

需要注意的是:

  • TX/RX 引脚交换。 STM32C542RCT6 的 LPUART1 默认 AF3 映射为 PA2=RX、PA3=TX,而板载 ST-LINK 虚拟串口要求 MCU 侧的 TX 连接到 PA2。必须在 CubeMX2 中启用 Tx and Rx Pins swapping,否则数据无法通过虚拟串口发送到 PC。
  • DMA 模式与缓冲区。 DMA 设为 Direct transfer(Normal 模式),每帧发送完成后停止,由主循环按 10 ms 周期重复提交。帧缓冲区使用静态存储,保证 DMA 读取期间始终有效。

<br>

版权声明:

Copyright by [email]ai4mcu@qq.com[/email] 2026 Crated:2026-10

赞 收藏 评论0 发布时间:2026-10-22 10:45

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版