【动手学STM32G4】(2)USB 虚拟串口通信

【动手学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) 类标准来实现。

  1. 物理层转换:
  • 传统串口: 设备 <--UART/RS232--> PC COM口
  • 虚拟串口: 设备 <--USB--> PC USB口 ↔ 虚拟COM口
  1. 实现机制
    设备通过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虚拟串口通信。

  1. 硬件连接
    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基本功能的同时,针对低功耗应用进行了特别优化。

  1. 软件实现

    使用STM32的固件库(如HAL库或LL库)以及USB CDC设备类(USB Device CDC Class)驱动程序,可以在STM32G431上配置和实现USB虚拟串口功能。

    STM32CubeMX是一个图形化配置工具,可以帮助用户快速配置USB功能,并生成初始化代码。

    通过编写程序,用户可以在STM32微控制器中实现数据的接收和发送,利用USB总线与PC进行通信。

  2. PC端驱动

    在PC上,STM32G431 通过 USB与计算机连接时,操作系统会自动为该设备安装虚拟串口驱动(如Windows的STMicroelectronics USB VCP驱动程序)。一旦驱动安装完成,开发板就会在PC上显示为一个标准的串口端口(例如COM3、COM4等)。

  3. 应用场景

  • 调试与开发:开发者可以通过PC上的串口调试软件(如Tera Term、PuTTY等)与Nucleo-G431RB进行通信,调试应用程序、查看输出信息,或者发送控制命令。
  • 数据采集与传输:Nucleo-G431RB可以用作数据采集设备,通过USB将采集的数据传输到PC端进行处理和显示。
  • 嵌入式通信:Nucleo-G431RB可以作为外设与其他嵌入式设备(如传感器、执行器等)进行通信,通过USB虚拟串口实现可靠的数据传输。
  1. 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 选择例程。

  1. 打开 STM32CubeMX,选择 New Project(或Ctrl-N快捷键)新建工程,进入 New Project 界面。
  • 选择MCU为 STM32G431RBT6(参考开发板的 MCU 型号选择)。
  • 选择开发板为 NUCLEO-G431RB 开发板。

本节不从 Example Selector 选择例程,而是在选择 MCU 和 开发板后直接点击 "Start Project" 建立项目。弹出项目选项窗口,勾选 "Generate demonstration code",如下图所示。

  1. 自动转入 CubeMX 的 Pinout Configuration 视图,如下图所示。

  2. 点击菜单栏 "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 选择固件包的安装路径。

  1. 在 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外部中断,可以发生中断响应或者事件响应,中断响应就是执行中断程序,事件响应就是操作外设而不触发中断。

  1. 在 Clock Configuration 视图进行时钟配置,如下图所示。

    本实验对于时钟时钟设置没有太多要求,只要设置正确即可。本例中使用外部 160MHz晶振,具体设置如下图所示,只供参考。

  2. 代码生成。

    点击右上角 "GENERATE CODE" ,将自动生成带 .ioc 的工程文件 "STM32G4_UART.ioc"

    加载完毕后,弹出代码生成提示窗口,点击" OPEN PROJECT",进入 STM32CubeIDE。

4. 使用 STM32CubeIDE 编写和调试串口通信程序

  1. 在 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 */
  }
  1. 程序编辑、编译与调试
  • 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
  • 点击工具栏中 "Build Debug" 按键对程序代码进行编译。
  • 点击工具栏中 "Debug" 按键,将程序下载烧录到目标板 NUCLEO-G431RB 。
  • 点击工具栏中 "Resume" 按键 或 F8 快捷键,运行程序。NUCLEO-G431RB 开发板上的 LD2 指示灯以 1000ms 开始闪烁。
  1. 打开串口调试助手,
    (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",按发送键发送,发送的字符串也显示在右上方的显示区。

  1. 关闭项目,关闭 STM32CubeIDE 。

  2. NUCLEO-G431RB 开发板重新上电。按下黑色按键 B2,MCU 重启(Reset),开发板上的 LD2 指示灯以 1000ms 开始闪烁。

5. 通过串口通信回显从 PC 发送的字符串

在以上串口通信程序基础上,我们进一步实现 回显从 PC 发送的字符串。

  1. 修改 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);
	}
  1. 程序编辑、编译与调试
  • 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
  • 将程序下载烧录到目标板 NUCLEO-G431RB。
  • 按下 NUCLEO-G431RB 开发板的黑色按键 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。。
  1. 打开串口调试助手,将配置参数设为与 "Connectivity -- LPUART1" 的配置一致。
  • 按下 NUCLEO-G431RB 开发板上的黑色按钮开关 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
  • 在串口调试助手的右下方输入框中输入字符串 "Hello!",按发送键发送。单片机接收到字符串 "Hello!",通过串口通信将其发送回 PC,显示在显示区。

以上程序实现了 回显发送的字符串,但当发送的字符串超过接收的缓冲区长度(如 24 字节),多出来的数据就会因为溢出/错误被 UART 丢掉。对此我们可以继续改进。

6. 通过缓冲区储存收到的字符串

在以上串口通信程序基础上,我们用"中断 + 缓冲区" 的方式来做 LPUART1(USB 虚拟串口)回显,并解决长字符串被截断的问题。当字符串的长度超出256个字符时,会返回"more than 256"。。

  1. 修改 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 */
  1. 程序编辑、编译与调试
  • 用 USB连接线,连接 PC 与 NUCLEO-G431RB 开发板。
  • 将程序下载烧录到目标板 NUCLEO-G431RB。
  • 按下 NUCLEO-G431RB 开发板的黑色按键 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
  1. 打开串口调试助手,将配置参数设为与 "Connectivity -- LPUART1" 的配置一致。
  • 按下 NUCLEO-G431RB 开发板上的黑色按钮开关 B2,单片机重启后发送初始数据 "Device Ready (LPUART1)"。
  • 在串口调试助手的右下方输入框中输入字符串 "Hello!",按发送键发送。单片机接收到字符串 "Hello!",通过串口通信将其发送回 PC,显示在显示区。
  • 输入较长字符串,如果字符串长度小于 256 可以正常显示,如果字符串长度超过 256则显示提示信息 "more than 256"。

说明:

本例程中"中断 + 缓冲区" 方法的特点是:

  1. 不阻塞主循环:主循环 while(1) 处理其它任务,LPUART 中断在后台收发数据。
  2. 缓冲区长度可控:最多 256 字节,多了就提示"more than 256"。
  3. 逐行处理:直到收到 \r\n 才认为输入完成,一次性完整回显整行的字符串。

参考资料:

  1. UM2505 - STM32G4 Nucleo-64 boards (MB1367), STMicroelectronics/意法半导体, 2021

  2. 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

相关推荐
SystickInt3 小时前
32 DMA实现ROM与RAM通信
stm32·单片机·嵌入式硬件
俊昭喜喜里4 小时前
STM32开发板电源设计( DCDC 电路和 LDO 电路 )
stm32·单片机·嵌入式硬件
m0_555762904 小时前
方案再再对比
单片机
路弥行至5 小时前
FreeRTOS任务管理详解中: FreeRTOS任务创建与删除实战教程(动态方法)
c语言·开发语言·笔记·stm32·操作系统·freertos·入门教程
boneStudent5 小时前
Day20:串口基本配置与收发
stm32·单片机·嵌入式硬件
紫阡星影5 小时前
基于Arduino模拟烟雾监测系统
单片机·嵌入式硬件·arduino
HIZYUAN6 小时前
嵌入式开发踩坑记: AG32硬件设计指南(一)
stm32·单片机·嵌入式硬件·fpga开发·硬件设计·最小系统·agm ag32
三佛科技-134163842127 小时前
BP85958D输出12V400MA智能家居电源芯片(典型应用电路、替代型号HN32512)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
海涛高软7 小时前
axi-gpio
单片机·嵌入式硬件