【动手学STM32G4】(1)导入和创建项目
【动手学STM32G4】(2)USB 虚拟串口通信
【动手学STM32G4】(3)上位机显示波形
【动手学STM32G4】(3)上位机实时显示波形
-
- [1. 通信协议](#1. 通信协议)
-
- [1.1 串口通信与 USB 虚拟串口通信](#1.1 串口通信与 USB 虚拟串口通信)
- [1.2 VOFA+ 支持的通信协议](#1.2 VOFA+ 支持的通信协议)
- [2. 软硬件准备](#2. 软硬件准备)
- [3. CubeMX工程创建](#3. CubeMX工程创建)
- [4. 使用 STM32CubeIDE 编写和调试串口通信程序](#4. 使用 STM32CubeIDE 编写和调试串口通信程序)
- [5. 使用 VOFA+ 上位机显示波形](#5. 使用 VOFA+ 上位机显示波形)
- [6. 基于 DMA 的实时波形发送](#6. 基于 DMA 的实时波形发送)
- [7. 显示多路实时波形](#7. 显示多路实时波形)
本节内容:
上位机实时显示波形,就是在PC端通过串口通信工具将嵌入式设备(如STM32)发送的数据实时绘制成直观的波形图、曲线图或仪表盘。
在前两篇博客中,我们已经完成了 STM32G431 的基础工程搭建 和 串口通信实验。本篇将让 STM32 生成波形数据,通过 USB 虚拟串口发送到电脑上,使用 VOFA+ 上位机进行实时波形显示。
实验条件:
① 硬件平台:NUCLEO-G431RB 开发板
② 软件平台:STM32CubeMX, STM32CubeIDE,VOFA+ 串口调试工具
1. 通信协议
1.1 串口通信与 USB 虚拟串口通信
串口通信(UART/RS-232)
传统串口通信基于UART(通用异步收发器)协议,通过简单的TX(发送)、RX(接收)和GND(地线)三根导线即可实现双向数据流动。发送方按照预设的波特率、数据位、停止位和校验位参数,将数据以电信号形式逐位发送;接收方则以完全相同的时序参数解读这些信号。
bash
硬件连接:
STM32G431 PC
TX (PA2) ----> RX (串口或USB转串口)
RX (PA3) <---- TX
GND ---- GND
物理特性:
- 电平标准:TTL(3.3V) 或 RS-232(±12V)
- 通信协议:简单的起始位+数据位+停止位
- 连接器:DB9、DB25或直接杜邦线
USB虚拟串口通信
USB虚拟串口通信不再依赖独立的UART硬件线路,而是让微控制器本身通过USB接口直接模拟一个串口设备。
当STM32G431被配置为USB CDC设备并连接到电脑,操作系统会将其识别为一个标准的串行端口,尽管底层的数据传输走的是USB总线。这意味着开发者在上位机软件中看到的和使用的是一个普通的COM口,编写应用程序时也采用标准的串口API,但实际的数据包却是通过USB的批量传输端点进行交换的。这种方式省去了额外的电平转换芯片(如MAX232),简化了硬件设计,并且能获得比传统串口高得多的带宽(USB全速模式可达12Mbps)。
在Nucleo-G431RB 开发板上,用户常用的USART2接口(PA2/TX、PA3/RX)通过板载的ST-LINK调试器间接转换为USB信号,使得开发者仅用一根USB线就能在电脑上获得一个虚拟的COM端口,从而与开发板进行通信。这种方式的本质仍然是传统的异步串行通信,只是物理层经过了ST-LINK的转换。
对于STM32开发者,虚拟串口提供了"开箱即用"的调试和通信方案,极大简化了开发流程。
1.2 VOFA+ 支持的通信协议
VOFA+ 主要支持文本格式协议(FireWater)和 二进制格式协议(JustFloat)。
文本格式协议(FireWater)
FireWater 是 VOFA+ 最常用、最易上手的协议格式,适合 STM32 通过 UART(阻塞或 DMA)直接输出。其本质就是一行文本对应一次采样,一行内包含若干通道的数值。每一行以换行符作为结束。
FireWater 协议格式规则:一行文本即一个采样周期。格式如下:
bash
"<any>:ch0,ch1,ch2,...,chN\n"
- any和冒号可以为空,但换行(\n)不可省略;
- any不可以为"image",这个前缀用于解析图片数据;
- 此处\n为换行,并非指字符斜杠+字符n;
- n也可以为\n\r,或\r\n。
FireWater 的优势在于无需解码,调试方便,缺点是传输效率较低,适合在通道数量不多、发送频率不高时使用。
二进制格式协议(JustFloat)
JustFloat协议是一种紧凑的二进制协议,核心思想是将多个浮点数(float类型)以原始二进制形式打包成一个数据帧进行发送。
一个典型的数据帧结构包含固定的帧头、紧随其后的浮点数数组、以及帧尾校验部分。数据以 float 格式发送,每个数据四个字节,数据帧尾以0x7f80000作为结束字节。小端模式其可以被表示为:
bash
{ 0x00, 0x00, 0x80, 0x7f }
JustFloat 协议的传输效率极高,但调试过程依赖于VOFA+ 的解析和显示,适合用在通道数量多、发送频率高时使用。
RawData
RawData 协议:发什么就显示什么,可以字符串显示或 Hex 显示。
RawData 协议适用于不需要解析数据,仅仅查看字节流的需求,可以直接当串口助手使用。
2. 软硬件准备
本项目使用:
- 开发板:Nucleo-G431RB
- 串口:LPUART1(PA2/PA3 → ST-LINK 虚拟串口)
- 上位机软件:VOFA+(免费版即可)
- 开发环境:STM32CubeMX + STM32CubeIDE
硬件连接:
-
Nucleo-G431RB 开发板通过其USB端口(Micro-USB)连接到PC。
安装 ST-LINK 驱动后,在设备管理器里能看到一个 "ST-LINK Virtual COM Port (COMx)"。
-
在 Nucleo-G431RB开发板上,默认 LPUART1(PA2/PA3)接到 ST-LINK 的虚拟串口。
LPUART1 是STM32微控制器中的低功耗通用异步收发器(Low-Power Universal Asynchronous Receiver Transmitter)。
这种方式不需要再接 USB-TTL,只要一根 USB 线就能做串口实验。
3. CubeMX工程创建
- 新建工程
启动 STM32CubeMX,点击 "Start New Project" (或Ctrl-N快捷键)新建工程,进入 New Project 界面。
- 选择MCU为 STM32G431RBT6(参考开发板的 MCU 型号选择)。
- 选择开发板为 NUCLEO-G431RB 开发板。
- 点击右上角 "Start Project" 创建项目。

- 引脚配置(Pinout & Configuration)
(1)选择 "System Core -- SYS" 设置调试器类型,将 Debug 模式设为 "Serial Wire"。
(2)选择 "System Core -- SYS" 设置基础时钟源(Timebase Source),可以选择默认设置 "SysTick",或设置为 "TIM2"。

(3)选择 "System Core -- RCC" 配置时钟模式,设置高速晶振为外部时钟,将 High Speed Clock (HSE) 设为 "Crystal/Ceramic Resonator"。

(4)选择 "Connectivity -- LPUART1" 配置 LPUART1,设置模式(Mode)为 "Asynchronous",
- 在右侧 "Pinout view" 视图中将自动分配引脚 PA2 为 "LPUART1_TX"、PA3 为 "LPUART1_RX"。
- PA2 → USART2_TX(发送到上位机)
- PA3 → USART2_RX(从上位机接收)
- 如下图所示,在 LPUART1 参数中设置:
- Baud Rate:115200 Bits/s
- Word Length:8 Bits
- Parity:None
- Stop Bits:1
- Data Direction:Receive and Transmit(收发都开)

(5)可选地,在 "Connectivity -- LPUART1" 中选择 NVIC Settings,将 "LPUART1 global interrupt" 设为 "Enable "。

(6)如果使用 DMA 方式,请选择 "System Core -- DMA" 配置 DMA 传输,添加:LPUART1_RX 和 LPUART1_TX。

(7) 可选地,在 Pinout Configuration 视图中,搜索 PA5 管脚(在 NUCLEO 开发板中 连接LD2 灯),将其设置为 GPIO_Output。搜索 PC13 管脚(在 NUCLEO 开发板中 连接蓝色用户按键 User Button),将其设置为 GPIO_EXTI13。
EXTI可以监测指定GPIO的电平信号,当电平变化时,EXTI向NVIC发出中断申请,支持上升沿、下降沿、双边沿、软件触发。如果发生EXTI外部中断,可以发生中断响应或者事件响应,中断响应就是执行中断程序,事件响应就是操作外设而不触发中断。

- 在 Clock Configuration 视图进行时钟配置,如下图所示。
LUSART的波特率与时钟频率密切相关。LPUART 波特率计算方式不同于普通 UART,它使用 20 位过采样分频器:
B a u d R a t e = C l o c k D i v i d e r BaudRate = \frac{Clock}{Divider} BaudRate=DividerClock
其中 Divider 的范围大约必须满足: 16 ≤ D i v i d e r ≤ 131072 16≤Divider≤131072 16≤Divider≤131072。
在RCC 中设置 USART1 and LPUART1 Clock Selection = PCLK1,并配置 LPUART1 时钟为 48MHz.

- 点击菜单栏 "Project Manager" 进入工程配置界面,如下图所示。
(1)在 Project Name 输入项目名称 "STM32G431_LPUART1"。
(2)在 Toolchain/IDE 选择 IDE 工具为 "STM32CubeIDE"(也可以根据需要选择其它 IDE 工具 )。

(3)在 "Project Manager" 继续向下拉,"在 MCU and Firmware Package" 栏中,取消选中 "Use latest available version",根据所安装的 G4 固件版本,选择 "STM32Cube FW_G4 V1.5.0";
(4)如果固件包不是安装在默认路径,则要取消选中 "Use Default Firmware Location",通过 Browse 选择固件包的安装路径。

- 代码生成。
点击右上角 "GENERATE CODE" ,将自动生成带 .ioc 的工程文件 "STM32G431_LPUART1.ioc"
加载完毕后,弹出代码生成提示窗口,点击" OPEN PROJECT",进入 STM32CubeIDE。

4. 使用 STM32CubeIDE 编写和调试串口通信程序
- 在 STM32CubeIDE 打开代码文件 main.c。
代码生成后,已经自动进入 STM32CubeIDE,并打开创建的 STM32G431_UART1 项目。
从 Src 目录打开程序文件 main.c,如下图所示。

在 main.c 里会自动生成 MX_LPUART1_UART_Init(),并把 huart1 或类似句柄定义好(具体名字看工程,常见写法是 huart1 对应 USART1,hlpuart1 对应 LPUART1,你看一下 IDE 生成的变量名即可)。
c
static void MX_LPUART1_UART_Init(void)
{
hlpuart1.Instance = LPUART1;
hlpuart1.Init.BaudRate = 115200;
hlpuart1.Init.WordLength = UART_WORDLENGTH_8B;
hlpuart1.Init.StopBits = UART_STOPBITS_1;
hlpuart1.Init.Parity = UART_PARITY_NONE;
hlpuart1.Init.Mode = UART_MODE_TX_RX;
hlpuart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
hlpuart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
hlpuart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
hlpuart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
...
}
- 修改 main.c 代码。
(1)在程序开头添加用户指定的库。
c
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
/* USER CODE END Includes */
(2)在 main.c 顶部的 "用户变量区" 添加用户变量。
c
/* USER CODE BEGIN PV */
float temp[1] = {0.0f};
static uint8_t tempData[8] = {0,0,0,0,0,0,0x80,0x7F};
/* USER CODE END PV */
(3)轮询程序 while(1) 的代码如下,生成锯齿波,并使用 LPUART1 重定向 printf 打印数据。
c
/* Infinite loop */
while (1)
{
temp[0] += 0.01f;
if(temp[0] > 6.28f)
{
temp[0] = 0;
}
printf("%f\r\n",temp[0]);
HAL_Delay(1);
}
(4)实现 _write,将 printf 输出重定向至LPUART1(USB虚拟串口)。
c
/* USER CODE BEGIN 4 */
/* printf Redirection to LPUART1 (USB Virtual COM Port) */
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&hlpuart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
/* USER CODE END 4 */
-
启用 printf 的浮点输出--非常重要!
在 STM32CubeIDE 中:
(1)在菜单点击 "Project → Properties",弹出 Properties 窗口。
(2)选择 "C/C++ Build → Settings → Tool Settings → MCU Settings"
(3)勾选:Use float with printf from newlib-nano (-u _printf_float)
(4)点击右下角 "Apply and Close" 确认设置。

-
程序编辑、编译与调试
- 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
- 点击工具栏中 "Build Debug" 按键对程序代码进行编译。
- 点击工具栏中 "Debug" 按键,将程序下载烧录到目标板 NUCLEO-G431RB 。
- 点击工具栏中 "Resume" 按键 或 F8 快捷键,运行程序。
5. 使用 VOFA+ 上位机显示波形
- 打开 VOFA+ 软件:
(1)点击 "数据引擎" 下拉菜单,选择 "FireWater" 协议。
(2)点击 "数据接口" 下拉菜单,选择 "串口"。
(3)点击 "端口号" 下拉菜单,选中 STM STLink Virtual COM Port。例如,本例中为 COM3,选中后在端口号下方显示一行端口描述:
STMicroelectrionics STLink Virtual COM Port
(4)将配置参数设为与 STM32CubeMX 中 "Connectivity -- LPUART1" 的配置一致:
波特率:115200
数据位:8 Bits
校验位:None
数据流控:None
停止位:1
-
点击 VOFA+ 左上角的 "连接" 按钮,在数据显示区就将显示 HEX 格式的数据,并在右侧 "IO" 显示转换的十进制浮点数值。

-
鼠标移到上方波形图区域,点击鼠标右键唤出设置菜单。
(1)点击 "X轴",选择"使用时间轴";
(2)点击 "Y轴",选择"All" 绘制所有曲线的波形;

于是在图形区域就显示接收的数据波形,如下图所示。

6. 基于 DMA 的实时波形发送
如前所述,FireWater 的传输效率较低,而JustFloat 协议的传输效率高,适合用在通道数量多、发送频率高时使用。与之相适应地,DMA 可以在不占用 CPU 的情况下,自动把内存中的数据搬运到外设(如 LPUART1 TX 寄存器)。
只要你调用:
c
HAL_UART_Transmit_DMA(&hlpuart1, buffer, length);
DMA 就会自动完成整个发送过程:
- 自动从 buffer 读取数据
- 自动写入 UART TX
- 自动发送完全部字节
- 发送完自动触发中断通知 CPU
对于采样 + 波形显示系统,MA 不占用 CPU,CPU 完全可以用于实时控制。
- 修改 main.c 如下:
(1)在程序开头添加用户指定的库。
c
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
(2)在 main.c 顶部的 "用户变量区" 添加用户变量。
c
/* USER CODE BEGIN PV */
float temp[1] = {0.0f};
static uint8_t tempData[8] = {0,0,0,0,0,0,0x80,0x7F};
/* USER CODE END PV */
(3)轮询程序 while(1) 的代码如下,生成锯齿波,并使用 DMA 发送数据,适合 VOFA+ 实时波形显示。
c
/* Infinite loop */
while (1)
{
temp[0] += 0.01f;
if(temp[0] > 6.28f)
{
temp[0] = 0;
}
// printf("%f\r\n", temp[0]);
memcpy(tempData, (uint8_t *)&temp, sizeof(temp));
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)tempData, 8);
HAL_Delay(1);
}
-
启用 printf 的浮点输出--非常重要!
-
程序编辑、编译与调试
- 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
- 将程序下载烧录到目标板 NUCLEO-G431RB。
- 按下 NUCLEO-G431RB 开发板的黑色按键 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。。
- 打开 VOFA+ 软件:
(1)点击 "数据引擎" 下拉菜单,选择 "JustFloat" 协议。
(2)点击 "数据接口" 下拉菜单,选择 "串口"。
(3)点击 "端口号" 下拉菜单,选中 STM STLink Virtual COM Port。
(4)将配置参数设为与 STM32CubeMX 中 "Connectivity -- LPUART1" 的配置一致:
波特率:115200
数据位:8 Bits
校验位:None
数据流控:None
停止位:1
- 点击 VOFA+ 左上角的 "连接" 按钮,在数据显示区就将显示 HEX 格式的数据,并在上方图形区域显示 锯齿波。

7. 显示多路实时波形
我们进一步尝试让 STM32 同时向上位机输出多路数据,实现更贴近实际工程应用的"三相波形"可视化场景。
借助 VOFA+ 的多通道显示能力,我们可以将 MCU 内部生成的多路浮点波形打包后以二进制协议发送,再由上位机在毫秒级刷新频率下连续绘制出来,从而构建一个轻量、稳定、易用的实时波形监测平台。本节基于 LPUART1 + DMA,通过 JustFloat 协议打包三路正弦波数据,实现多通道数据的实时可视化。
STM32CubeMX 的配置不变,可以另存为 STM32G431_LPUART_ex3.ioc。生成代码并在 STM32CubeIDE中打开 main.c 程序。
- 修改 main.c 如下:
(1)在程序开头添加用户指定的库。
c
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
(2)在 main.c 顶部的 "用户变量区" 添加用户变量。
c
/* USER CODE BEGIN PV */
// Three-Phase Waveform Parameters
#define PI_F 3.1415926f
#define FREQ_HZ 50.0f // Frequency: 50 Hz
#define TS 0.001f // sampling period: 1 ms -> fs = 1000Hz
// DMA Transmission Buffer: 3 Channels of Floating-Point Data, 12 Bytes in Total
typedef union {
float f;
uint8_t b[4];
} FloatBytes;
// One frame of data: 3 channels of float + 4-byte frame tail = 4 × 4 = 16 bytes
FloatBytes frame[4]; // frame[0]~[2] = 3 waveform channels; frame[3] = frame tail
volatile uint8_t txReady = 1; // 1 = New DMA transmission can be initiated; 0 = Transmission in progress
/* USER CODE END PV */
(3)在 main() 函数 生成三相正弦 + DMA 发送。
c
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_LPUART1_UART_Init();
/* USER CODE BEGIN 2 */
const char *init_msg = "Device Ready (LPUART1 DMA, 3-phase)\r\n";
HAL_UART_Transmit(&hlpuart1, (uint8_t *)init_msg, strlen(init_msg), 100);
float theta = 0.0f; // Current Phase
const float dtheta = 2.0f * PI_F * FREQ_HZ * TS; // Phase Increment
memcpy(frame[3].b, (uint8_t[]){0x00, 0x00, 0x80, 0x7F}, 4); // Frame Tail: frame[3].f = NAN
/* Infinite loop */
while (1)
{
// Update the phase
theta += dtheta;
if (theta > 2.0f * PI_F)
{
theta -= 2.0f * PI_F;
}
float I1 = 8.0f * sinf(theta); // 10A, 0°
float I2 = 9.0f * sinf(theta - 2.0f * PI_F / 3.0f); // 10A, -120°
float I3 = 10.0f * sinf(theta - 4.0f * PI_F / 3.0f); // 8A, -240°
// New DMA transmission can only be initiated after the previous one is completed
if (txReady)
{
txReady = 0;
frame[0].f = I1; // Channel 1
frame[1].f = I2; // Channel 2
frame[2].f = I3; // Channel 3
// The frame tail at frame[3] is a fixed value
// A total of 4 floats (3 channels of data + 1 frame tail), 16 bytes in total.
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)frame, 4 * sizeof(float));
}
HAL_Delay(1); // 1ms
}
}
(4)DMA 发送完成回调(放在 USER CODE 4 区)。
c
/* USER CODE BEGIN 4 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == LPUART1)
{
// One frame transmission completed; next frame transmission allowed
txReady = 1;
}
}
/* USER CODE END 4 */
-
启用 printf 的浮点输出--非常重要!
-
程序编辑、编译与调试
- 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
- 将程序下载烧录到目标板 NUCLEO-G431RB。
- 按下 NUCLEO-G431RB 开发板的黑色按键 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。。
- 打开 VOFA+ 软件:
(1)点击 "数据引擎" 下拉菜单,选择 "JustFloat" 协议。
(2)点击 "数据接口" 下拉菜单,选择 "串口"。
(3)点击 "端口号" 下拉菜单,选中 STM STLink Virtual COM Port。
(4)将配置参数设为与 STM32CubeMX 中 "Connectivity -- LPUART1" 的配置一致:
波特率:115200
数据位:8 Bits
校验位:None
数据流控:None
停止位:1
- 点击 VOFA+ 左上角的 "连接" 按钮,在数据显示区就将显示 HEX 格式的数据,并在上方图形区域显示波形。
鼠标移到上方波形区域,点击鼠标右键唤出设置菜单。
(1)点击 "X轴",选择"使用时间轴";
(2)点击 "Y轴",选择"All" 绘制所有曲线的波形;
于是在图形区域就显示接收的多路波形,如下图所示。

借助 VOFA+,我们能快速对控制策略或算法效果进行可视化验证,大幅提升调试与开发效率。下一节我们将进一步探索如何将真实采集的 ADC 信号接入波形链路,实现 MCU → 上位机 的闭环实时数据监测。
版权声明:
【动手学电机驱动】是 youcans@qq 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/155752008)
Copyright@youcans 2025
Crated:2025-12