【动手学STM32G4】(1)导入和创建项目
【动手学STM32G4】(2)USB 虚拟串口通信
【动手学STM32G4】(3)上位机显示波形
【动手学STM32G4】(2)USB 虚拟串口通信
-
- [1. USB虚拟串口通信的工作原理](#1. USB虚拟串口通信的工作原理)
- [2. Nucleo-G431RB 和 USB虚拟串口通信](#2. Nucleo-G431RB 和 USB虚拟串口通信)
- [3. 使用 CubeMX 创建 USB 虚拟串口通信项目](#3. 使用 CubeMX 创建 USB 虚拟串口通信项目)
- [4. 使用 STM32CubeIDE 编写和调试串口通信程序](#4. 使用 STM32CubeIDE 编写和调试串口通信程序)
- [5. 通过串口通信回显从 PC 发送的字符串](#5. 通过串口通信回显从 PC 发送的字符串)
- [6. 通过缓冲区储存收到的字符串](#6. 通过缓冲区储存收到的字符串)
本节内容:
USB虚拟串口通信(USB Virtual COM Port, VCP)是一种将USB接口模拟为传统串行通信端口(如RS232串口)的技术。通过这种方式,嵌入式设备(如基于STM32微控制器的开发板)可以通过USB与PC进行串行通信,而无需实际的串口硬件。它常用于需要数据传输的应用中,例如嵌入式系统的调试、通信、数据记录等。
本节通过 NUCLEO-G431RB 开发板, 以 USB虚拟串口通信实验为例,介绍 STM32G4 的编程和调试。
实验条件:
① 硬件平台:NUCLEO-G431RB 开发板
② 软件平台:STM32CubeMX, STM32CubeIDE
1. USB虚拟串口通信的工作原理
USB虚拟串口通信基于USB通信协议,并通过 USB CDC(Communications Device Class) 类标准来实现。
- 物理层转换:
- 传统串口: 设备 <--UART/RS232--> PC COM口
- 虚拟串口: 设备 <--USB--> PC USB口 ↔ 虚拟COM口
- 实现机制
设备通过CDC类模拟一个串口,PC上的操作系统(如Windows、Linux)会将其识别为一个COM端口,就像是一个标准的串口接口。这意味着应用程序(如终端工具、数据采集软件等)可以像访问传统串口一样,通过USB与设备进行数据交换。
具体工作过程如下:
- 设备端:嵌入式设备通过USB接口与PC连接时,会通过USB的CDC类协议被PC识别为虚拟串口。
- PC端:操作系统会将该设备映射为一个虚拟COM端口,类似于传统的串口设备(如COM1、COM2等)。
- 数据通信:数据从PC传送到设备,或者从设备传送到PC时,通信过程就像标准的串口通信一样,数据通过USB总线进行传输。
USB虚拟串口是传统串口的现代化替代方案,它结合了USB的便利性和串口的简单编程模型。对于STM32开发者,特别是使用Nucleo/G431RB等开发板时,虚拟串口提供了"开箱即用"的调试和通信方案,极大简化了开发流程。
2. Nucleo-G431RB 和 USB虚拟串口通信
Nucleo-G431RB 是基于STM32G431RB微控制器的开发板,支持USB功能,包括作为USB虚拟串口通信的能力。STM32系列微控制器支持通过USB实现各种功能,包括设备端的CDC协议。因此,可以使用Nucleo-G431RB来实现USB虚拟串口通信。
- 硬件连接
Nucleo-G431RB开发板通过其USB端口(Micro-USB)连接到PC。
在 Nucleo-G431RB开发板上,默认 LPUART1(PA2/PA3)接到 ST-LINK 的虚拟串口:
- LPUART1:PA2 / PA3
- 通过 ST-LINK VCP 映射到 PC 上的一个 COM 口(Windows 设备管理器里能看到)
LPUART1 是STM32微控制器中的低功耗通用异步收发器(Low-Power Universal Asynchronous Receiver Transmitter)。它在保持UART基本功能的同时,针对低功耗应用进行了特别优化。
-
软件实现
使用STM32的固件库(如HAL库或LL库)以及USB CDC设备类(USB Device CDC Class)驱动程序,可以在STM32G431上配置和实现USB虚拟串口功能。
STM32CubeMX是一个图形化配置工具,可以帮助用户快速配置USB功能,并生成初始化代码。
通过编写程序,用户可以在STM32微控制器中实现数据的接收和发送,利用USB总线与PC进行通信。
-
PC端驱动
在PC上,STM32G431 通过 USB与计算机连接时,操作系统会自动为该设备安装虚拟串口驱动(如Windows的STMicroelectronics USB VCP驱动程序)。一旦驱动安装完成,开发板就会在PC上显示为一个标准的串口端口(例如COM3、COM4等)。
-
应用场景
- 调试与开发:开发者可以通过PC上的串口调试软件(如Tera Term、PuTTY等)与Nucleo-G431RB进行通信,调试应用程序、查看输出信息,或者发送控制命令。
- 数据采集与传输:Nucleo-G431RB可以用作数据采集设备,通过USB将采集的数据传输到PC端进行处理和显示。
- 嵌入式通信:Nucleo-G431RB可以作为外设与其他嵌入式设备(如传感器、执行器等)进行通信,通过USB虚拟串口实现可靠的数据传输。
- Nucleo-G431RB 和 PC通信
用 USB 数据线把 CN1(ST-LINK USB 口) 接到电脑。装好 ST-LINK 驱动(Windows 10 通常自动装好),在设备管理器里能看到一个 "ST-LINK Virtual COM Port (COMx)"。
以后 PC 串口助手(PuTTY、Tera Term、SSCOM 等)就选这个 COM 口。
这种方式不需要再接 USB-TTL,只要一根 USB 线就能做串口实验。
3. 使用 CubeMX 创建 USB 虚拟串口通信项目
使用 CubeMX 创建 USB 虚拟串口通信项目,在新建工程时选择 MCU 型号或开发板型号,不从 Example Selector 选择例程。
- 打开 STM32CubeMX,选择 New Project(或Ctrl-N快捷键)新建工程,进入 New Project 界面。
- 选择MCU为 STM32G431RBT6(参考开发板的 MCU 型号选择)。
- 选择开发板为 NUCLEO-G431RB 开发板。
本节不从 Example Selector 选择例程,而是在选择 MCU 和 开发板后直接点击 "Start Project" 建立项目。弹出项目选项窗口,勾选 "Generate demonstration code",如下图所示。

-
自动转入 CubeMX 的 Pinout Configuration 视图,如下图所示。
-
点击菜单栏 "Project Manager" 进入工程配置界面,如下图所示。
(1)在 Project Name 输入项目名称 "STM32G431_UART1"。
(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 选择固件包的安装路径。

- 在 Pinout Configuration 视图进行系统配置。
(1)选择 "System Core -- SYS" 设置调试器类型,将 Debug 模式设为 "Serial Wire"。
(2)选择 "System Core -- SYS" 设置基础时钟源,将 Timebase Source 设为 "TIM2"------非常重要!
注意:Timebase Source 默认为 "SysTick",但在 STM32支持包中 "SysTick" 已经用作框架的基础时间功能,因此必须修改为 非"SysTick" 的其它定时器。否则在编译项目时会报错------非常重要!。

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

(4)选择 "Connectivity -- LPUART1" 配置 LPUART1,设置模式(Mode)为 "Asynchronous",
在 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)可选地,选择 "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 视图进行时钟配置,如下图所示。
本实验对于时钟时钟设置没有太多要求,只要设置正确即可。本例中使用外部 160MHz晶振,具体设置如下图所示,只供参考。

-
代码生成。
点击右上角 "GENERATE CODE" ,将自动生成带 .ioc 的工程文件 "STM32G4_UART.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;
...
}

(1)在程序开头添加用户指定的库。
c
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
(2)在初始化完成后发送一行字符串 "Device Ready (LPUART1)\r\n"。
c
/* USER CODE BEGIN 2 */
// Initial Transmit
const char *init_msg = "Device Ready (LPUART1)\r\n";
HAL_UART_Transmit(&hlpuart1, (uint8_t *)init_msg, strlen(init_msg), 100);
/* USER CODE END 2 */
(3)轮询程序 while(1) 的代码如下,通过延时 1000ms 翻转 LD2_PIN,并向 UASB 串口发送一行字符串 "Hello, Nucleo-G431RB UART!\r\n"。
c
/* Infinite loop */
/* USER CODE BEGIN WHILE */
const char *msg = "Hello, Nucleo-G431RB UART!\r\n";
while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit(&hlpuart1, (uint8_t *)msg, strlen(msg), HAL_MAX_DELAY);
HAL_GPIO_TogglePin(LD2_GPIO_PORT, LD2_PIN);
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
- 程序编辑、编译与调试
- 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
- 点击工具栏中 "Build Debug" 按键对程序代码进行编译。
- 点击工具栏中 "Debug" 按键,将程序下载烧录到目标板 NUCLEO-G431RB 。
- 点击工具栏中 "Resume" 按键 或 F8 快捷键,运行程序。NUCLEO-G431RB 开发板上的 LD2 指示灯以 1000ms 开始闪烁。

- 打开串口调试助手,
(1)点击 "端口名" 下拉菜单,选中 STM STLink Virtual COM Port。例如,本例中为 COM3。

(2)将配置参数设为与 "Connectivity -- LPUART1" 的配置一致:
波特率:115200
数据位:8 Bits
校验位:None
停止位:1
(3) 点击 "打开"按钮打开串口(打开后显示为绿色"关闭",再次点击则关闭串口)。
- 连接成功后,接收区开始显示接受的数据,每 1000ms 更新一行:
bash
Device Ready (LPUART1)
Hello, Nucleo-G431RB UART!
Hello, Nucleo-G431RB UART!
Hello, Nucleo-G431RB UART!
...
-
按下 NUCLEO-G431RB 开发板上的黑色按钮开关 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
-
在串口调试助手的右下方输入框中输入字符串 "123",按发送键发送,发送的字符串也显示在右上方的显示区。

-
关闭项目,关闭 STM32CubeIDE 。
-
NUCLEO-G431RB 开发板重新上电。按下黑色按键 B2,MCU 重启(Reset),开发板上的 LD2 指示灯以 1000ms 开始闪烁。
5. 通过串口通信回显从 PC 发送的字符串
在以上串口通信程序基础上,我们进一步实现 回显从 PC 发送的字符串。
- 修改 main.c 如下:
(1)在程序开头添加用户指定的库。
c
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
(2)可选地,在初始化完成后发送一行字符串 "Device Ready (LPUART1)\r\n"。
c
/* USER CODE BEGIN 2 */
// Initial Transmit
const char *init_msg = "Device Ready (LPUART1)\r\n";
HAL_UART_Transmit(&hlpuart1, (uint8_t *)init_msg, strlen(init_msg), 100);
/* USER CODE END 2 */
(3)轮询程序 while(1) 的代码如下,阻塞等待接收一个字节,并向 UASB 串口发送接收的字符串,即收到什么就回显什么。
c
/* Infinite loop */
uint8_t rx_byte;
while (1)
{
// HAL_UART_Transmit(&hlpuart1, (uint8_t *)msg, strlen(msg), HAL_MAX_DELAY);
// Blocking wait to receive one byte
if (HAL_UART_Receive(&hlpuart1, &rx_byte, 1, HAL_MAX_DELAY) == HAL_OK)
{
// Echo back whatever is received
HAL_UART_Transmit(&hlpuart1, &rx_byte, 1, HAL_MAX_DELAY);
}
- 程序编辑、编译与调试
- 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
- 将程序下载烧录到目标板 NUCLEO-G431RB。
- 按下 NUCLEO-G431RB 开发板的黑色按键 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。。
- 打开串口调试助手,将配置参数设为与 "Connectivity -- LPUART1" 的配置一致。
- 按下 NUCLEO-G431RB 开发板上的黑色按钮开关 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
- 在串口调试助手的右下方输入框中输入字符串 "Hello!",按发送键发送。单片机接收到字符串 "Hello!",通过串口通信将其发送回 PC,显示在显示区。

以上程序实现了 回显发送的字符串,但当发送的字符串超过接收的缓冲区长度(如 24 字节),多出来的数据就会因为溢出/错误被 UART 丢掉。对此我们可以继续改进。
6. 通过缓冲区储存收到的字符串
在以上串口通信程序基础上,我们用"中断 + 缓冲区" 的方式来做 LPUART1(USB 虚拟串口)回显,并解决长字符串被截断的问题。当字符串的长度超出256个字符时,会返回"more than 256"。。
- 修改 main.c 如下:
(1)在 main.c 开头添加用户指定的库。
c
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
(2)在 main.c 顶部的"用户变量区"添加(USER CODE BEGIN PV)。
c
/* USER CODE BEGIN PV */
uint8_t LPUART1_RxByte; // Single-byte Receive Buffer (one byte received per interrupt)
uint8_t LPUART1_RxBuff[256]; // circular/linear buffer (max 256 bytes capacity)
uint8_t LPUART1_RxCnt = 0; // bytes currently stored
uint8_t LPUART1_AlmStr[] = "more than 256\r\n"; // Warning string when exceeding the maximum length
/* USER CODE END PV */
(3)在 main() 里初始化好 LPUART1,并开启中断接收。
c
int main(void)
{
/* 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_LPUART1_UART_Init();
// Initial Transmit
const char *init_msg = "Device Ready (LPUART1)\r\n";
HAL_UART_Transmit(&hlpuart1, (uint8_t *)init_msg, strlen(init_msg), 100);
// Enable interrupt-driven reception for LPUART1, one byte per interrupt
HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&LPUART1_RxByte, 1);
/* Infinite loop */
//The main loop can do nothing temporarily
while (1)
{
}
}
(4)实现 HAL_UART_RxCpltCallback(放在 USER CODE BEGIN 4 区)。
c
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
// 1. 溢出判断
if (LPUART1_RxCnt >= 255)
{
LPUART1_RxCnt = 0;
memset(LPUART1_RxBuff, 0x00, sizeof(LPUART1_RxBuff));
// 发送溢出提示信息
HAL_UART_Transmit(&hlpuart1, (uint8_t *)LPUART1_AlmStr, sizeof(LPUART1_AlmStr) - 1, 0xFFFF);
}
else
{
// 2. 接收数据转存
LPUART1_RxBuff[LPUART1_RxCnt++] = LPUART1_RxByte;
// 3. 判断是否以 "\r\n" 结尾
if (LPUART1_RxBuff[LPUART1_RxCnt-1]==0x0A && LPUART1_RxBuff[LPUART1_RxCnt-2]==0x0D)
{
// 将收到的信息发送出去
HAL_UART_Transmit(&hlpuart1, (uint8_t *)LPUART1_RxBuff, LPUART1_RxCnt, 0xFFFF);
// 清空计数和缓冲区,准备下一帧
LPUART1_RxCnt = 0;
memset(LPUART1_RxBuff, 0x00, sizeof(LPUART1_RxBuff)); //清空数组
}
}
// 4. 重新启动下一次中断接收
HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&LPUART1_RxByte, 1);
}
/* USER CODE END 4 */
- 程序编辑、编译与调试
- 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
- 将程序下载烧录到目标板 NUCLEO-G431RB。
- 按下 NUCLEO-G431RB 开发板的黑色按键 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
- 打开串口调试助手,将配置参数设为与 "Connectivity -- LPUART1" 的配置一致。
- 按下 NUCLEO-G431RB 开发板上的黑色按钮开关 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
- 在串口调试助手的右下方输入框中输入字符串 "Hello!",按发送键发送。单片机接收到字符串 "Hello!",通过串口通信将其发送回 PC,显示在显示区。
- 输入较长字符串,如果字符串长度小于 256 可以正常显示,如果字符串长度超过 256则显示提示信息 "more than 256"。

说明:
本例程中"中断 + 缓冲区" 方法的特点是:
- 不阻塞主循环:主循环 while(1) 处理其它任务,LPUART 中断在后台收发数据。
- 缓冲区长度可控:最多 256 字节,多了就提示"more than 256"。
- 逐行处理:直到收到 \r\n 才认为输入完成,一次性完整回显整行的字符串。
参考资料:
-
UM2505 - STM32G4 Nucleo-64 boards (MB1367), STMicroelectronics/意法半导体, 2021
-
UM2538 - STM32 motor-control pack using the FOC algorithm for three-phase, low-voltage, and low‑current motor evaluationl, STMicroelectronics/意法半导体, 2023
版权声明:
【动手学电机驱动】是 youcans@qq 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/155714743)
Copyright@youcans 2025
Crated:2025-11