USB设备开发:STM32F105实现USB HID设备,虚拟串口通信实战

文章目录

    • 一、开发背景与环境准备
      • [1.1 硬件准备](#1.1 硬件准备)
      • [1.2 软件环境](#1.2 软件环境)
      • [1.3 核心原理说明](#1.3 核心原理说明)
    • 二、STM32CubeMX配置步骤
      • [2.1 新建工程](#2.1 新建工程)
      • [2.2 基础配置](#2.2 基础配置)
        • [2.2.1 时钟配置](#2.2.1 时钟配置)
        • [2.2.2 USB配置](#2.2.2 USB配置)
        • [2.2.3 HID配置](#2.2.3 HID配置)
        • [2.2.4 调试串口配置(可选,用于调试)](#2.2.4 调试串口配置(可选,用于调试))
      • [2.3 工程生成](#2.3 工程生成)
    • 三、代码编写与实现
      • [3.1 代码文件结构说明](#3.1 代码文件结构说明)
      • [3.2 关键代码编写](#3.2 关键代码编写)
        • [3.2.1 文件名:usbd_custom_hid_if.h](#3.2.1 文件名:usbd_custom_hid_if.h)
        • [3.2.2 文件名:usbd_custom_hid_if.c](#3.2.2 文件名:usbd_custom_hid_if.c)
        • [3.2.3 文件名:main.c](#3.2.3 文件名:main.c)
    • 四、编译与烧录
      • [4.1 编译工程](#4.1 编译工程)
      • [4.2 烧录程序](#4.2 烧录程序)
    • 五、硬件调试与通信验证
      • [5.1 驱动安装](#5.1 驱动安装)
      • [5.2 通信测试](#5.2 通信测试)
        • [5.2.1 工具准备](#5.2.1 工具准备)
        • [5.2.2 测试步骤](#5.2.2 测试步骤)
      • [5.3 常见问题排查](#5.3 常见问题排查)
    • 六、功能扩展与优化
      • [6.1 数据分包与校验](#6.1 数据分包与校验)
      • [6.2 低功耗优化](#6.2 低功耗优化)
    • 七、总结

一、开发背景与环境准备

你想要基于STM32F105芯片实现USB HID设备的开发,并完成虚拟串口通信的实战开发,本教程会从环境搭建到代码实现、硬件调试,全程详细讲解,确保零基础的你也能一步步完成开发落地。

1.1 硬件准备

  • 核心板:STM32F105RBT6/STM32F105VCT6开发板(需带USB OTG/FS引脚)
  • 外设:USB Type-A转Micro/Type-C数据线
  • 辅助工具:USB转串口模块(用于调试)、杜邦线、万用表(可选)
  • 烧录工具:ST-Link V2

1.2 软件环境

  • 开发IDE:STM32CubeIDE 1.15.0(兼容主流版本)
  • 固件库:STM32CubeF1 V1.8.5
  • 调试工具:串口助手(SecureCRT/串口调试助手)、USB设备查看器(Windows)
  • 驱动:STM32虚拟串口驱动(VCP Driver)

1.3 核心原理说明

STM32F105内置USB 2.0 FS控制器,支持HID(人机接口设备)类协议,虚拟串口本质是将HID协议封装成串口数据交互格式,实现PC与MCU之间的双向数据通信。整体开发流程如下:
成功
失败
环境搭建
STM32CubeMX配置
代码编写与移植
USB HID协议封装
虚拟串口数据交互
编译烧录
硬件调试
通信验证
功能扩展
问题排查

二、STM32CubeMX配置步骤

2.1 新建工程

  1. 打开STM32CubeMX,点击Access to MCU Selector
  2. 在搜索框输入STM32F105RBT6,选择对应芯片后点击Start Project
  3. 弹出Project Manager窗口,先点击Cancel,后续统一配置

2.2 基础配置

2.2.1 时钟配置
  1. 点击左侧Clock Configuration
  2. 选择HSECrystal/Ceramic Resonator(外部晶振)
  3. PLLMUL设置为x9AHB Prescaler/1APB1 Prescaler/2APB2 Prescaler/1
  4. 最终配置:System Clock MuxPLLCLKSYSCLK=72MHz,HCLK=72MHz,PCLK1=36MHz,PCLK2=72MHz
2.2.2 USB配置
  1. 点击左侧Pinout & Configuration
  2. Categories中选择Connectivity,找到USB_OTG_FS
  3. 模式选择Device Only(仅设备模式)
  4. 启用USB Global Settings中的VBUS Sense(根据硬件是否有VBUS引脚选择,无则关闭)
  5. 自动分配USB引脚:PA11(DM)、PA12(DP),确认引脚无冲突
2.2.3 HID配置
  1. Middleware中选择USB_DEVICE
  2. Class For FS IP选择HID Class
  3. 点击Parameter Settings,配置HID参数:
    • HID_IN_EP_SIZE:64(输入端点大小,最大64字节)
    • HID_OUT_EP_SIZE:64(输出端点大小)
    • HID_POLLING_INTERVAL:10ms(轮询间隔)
    • HID_REPORT_DESC_SIZE:64(报告描述符大小)
2.2.4 调试串口配置(可选,用于调试)
  1. Categories中选择Connectivity,找到USART1
  2. 模式选择Asynchronous(异步模式)
  3. 配置参数:波特率115200、8位数据位、1位停止位、无校验
  4. 分配引脚:PA9(TX)、PA10(RX)

2.3 工程生成

  1. 点击左侧Project Manager
  2. Project Name:输入STM32F105_USB_HID_VCOM
  3. Project Location:选择本地保存路径
  4. Toolchain/IDE:选择STM32CubeIDE
  5. 点击Code Generator,勾选Generate peripheral initialization as a pair of '.c/.h' files per peripheral
  6. 点击Generate Code,等待工程生成完成后,点击Open Project打开STM32CubeIDE

三、代码编写与实现

3.1 代码文件结构说明

生成的工程核心文件如下,后续主要修改/新增以下文件:

  • Core/Src/main.c:主函数,实现数据交互逻辑
  • Middlewares/ST/STM32_USB_Device_Library/Class/HID/Src/usbd_hid.c:HID类核心函数(少量修改)
  • Middlewares/ST/STM32_USB_Device_Library/Class/HID/Inc/usbd_hid.h:HID头文件(新增宏定义)
  • Core/Inc/usbd_custom_hid_if.h:HID接口头文件
  • Core/Src/usbd_custom_hid_if.c:HID接口实现(核心修改文件)

3.2 关键代码编写

3.2.1 文件名:usbd_custom_hid_if.h
c 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : usbd_custom_hid_if.h
  * @brief          : Header for usbd_custom_hid_if.c file.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USBD_CUSTOM_HID_IF_H
#define __USBD_CUSTOM_HID_IF_H

#ifdef __cplusplus
 extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "usbd_hid.h"

/* USER CODE BEGIN INCLUDE */
#include "stm32f1xx_hal.h"
/* USER CODE END INCLUDE */

/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
// 自定义HID报告描述符大小
#define CUSTOM_HID_REPORT_DESC_SIZE    64
// 虚拟串口数据缓冲区大小
#define VCOM_DATA_BUFFER_SIZE          64

// HID报告描述符(适配虚拟串口)
extern const uint8_t CUSTOM_HID_ReportDesc_FS[CUSTOM_HID_REPORT_DESC_SIZE];

/* Exported macro ------------------------------------------------------------*/
/* Exported functions prototypes ---------------------------------------------*/
extern USBD_HID_ItfTypeDef USBD_CustomHID_fops_FS;

/* USER CODE BEGIN EXPORTED_FUNCTIONS */
// 发送数据到PC
uint8_t USBD_CUSTOM_HID_SendData_FS(uint8_t *pData, uint16_t Length);
// 接收PC发送的数据(回调函数)
void USBD_CUSTOM_HID_ReceiveData_FS(uint8_t *pData, uint16_t Length);
/* USER CODE END EXPORTED_FUNCTIONS */

#ifdef __cplusplus
}
#endif

#endif /* __USBD_CUSTOM_HID_IF_H */
3.2.2 文件名:usbd_custom_hid_if.c
c 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : usbd_custom_hid_if.c
  * @brief          : USB Device Custom HID interface file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "usbd_custom_hid_if.h"

/* USER CODE BEGIN INCLUDE */
#include <string.h>
/* USER CODE END INCLUDE */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
USBD_HID_ItfTypeDef USBD_CustomHID_fops_FS =
{
  CUSTOM_HID_ReportDesc_FS,
  CUSTOM_HID_Init_FS,
  CUSTOM_HID_DeInit_FS,
  CUSTOM_HID_OutEvent_FS
};

// 接收数据缓冲区
static uint8_t HID_ReceiveBuffer[VCOM_DATA_BUFFER_SIZE] = {0};
// 发送数据缓冲区
static uint8_t HID_SendBuffer[VCOM_DATA_BUFFER_SIZE] = {0};

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
static int8_t CUSTOM_HID_Init_FS(void);
static int8_t CUSTOM_HID_DeInit_FS(void);
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state);

/**
  * @brief  CUSTOM_HID_ReportDesc_FS
  *         Custom HID report descriptor
  * @param  None
  * @retval pointer to report descriptor
  */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  // 虚拟串口HID报告描述符
  0x06, 0x00, 0xFF,       // USAGE_PAGE (Vendor Defined Page 1)
  0x09, 0x01,             // USAGE (Vendor Usage 1)
  0xA1, 0x01,             // COLLECTION (Application)
  0x09, 0x02,             //   USAGE (Vendor Usage 2)
  0x15, 0x00,             //   LOGICAL_MINIMUM (0)
  0x26, 0xFF, 0x00,       //   LOGICAL_MAXIMUM (255)
  0x75, 0x08,             //   REPORT_SIZE (8)
  0x95, 0x40,             //   REPORT_COUNT (64)
  0x81, 0x02,             //   INPUT (Data,Var,Abs)
  0x09, 0x03,             //   USAGE (Vendor Usage 3)
  0x15, 0x00,             //   LOGICAL_MINIMUM (0)
  0x26, 0xFF, 0x00,       //   LOGICAL_MAXIMUM (255)
  0x75, 0x08,             //   REPORT_SIZE (8)
  0x95, 0x40,             //   REPORT_COUNT (64)
  0x91, 0x02,             //   OUTPUT (Data,Var,Abs)
  0xC0                    // END_COLLECTION
};

/* Private functions ---------------------------------------------------------*/
/**
  * @brief  Initializes the CUSTOM HID media low layer
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CUSTOM_HID_Init_FS(void)
{
  /* USER CODE BEGIN 4 */
  // 初始化缓冲区
  memset(HID_ReceiveBuffer, 0, VCOM_DATA_BUFFER_SIZE);
  memset(HID_SendBuffer, 0, VCOM_DATA_BUFFER_SIZE);
  return (USBD_OK);
  /* USER CODE END 4 */
}

/**
  * @brief  DeInitializes the CUSTOM HID media low layer
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CUSTOM_HID_DeInit_FS(void)
{
  /* USER CODE BEGIN 5 */
  return (USBD_OK);
  /* USER CODE END 5 */
}

/**
  * @brief  Manage the CUSTOM HID class events
  * @param  event_idx: Event index
  * @param  state: Event state
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
  /* USER CODE BEGIN 6 */
  // 读取PC发送的数据到接收缓冲区
  USBD_HID_GetReport(&hUsbDeviceFS, HID_ReceiveBuffer, 0, VCOM_DATA_BUFFER_SIZE);
  
  // 调用接收回调函数
  USBD_CUSTOM_HID_ReceiveData_FS(HID_ReceiveBuffer, VCOM_DATA_BUFFER_SIZE);
  
  return (USBD_OK);
  /* USER CODE END 6 */
}

/* USER CODE BEGIN 7 */
/**
  * @brief  发送数据到PC端
  * @param  pData: 待发送数据指针
  * @param  Length: 数据长度
  * @retval 发送状态(0成功,其他失败)
  */
uint8_t USBD_CUSTOM_HID_SendData_FS(uint8_t *pData, uint16_t Length)
{
  uint8_t result = USBD_FAIL;
  
  if((pData != NULL) && (Length <= VCOM_DATA_BUFFER_SIZE))
  {
    // 拷贝数据到发送缓冲区
    memset(HID_SendBuffer, 0, VCOM_DATA_BUFFER_SIZE);
    memcpy(HID_SendBuffer, pData, Length);
    
    // 发送数据
    result = USBD_HID_SendReport(&hUsbDeviceFS, HID_SendBuffer, VCOM_DATA_BUFFER_SIZE);
  }
  
  return result;
}

/**
  * @brief  接收PC端数据的回调函数(可在main.c中重定义)
  * @param  pData: 接收数据指针
  * @param  Length: 数据长度
  * @retval None
  */
__attribute__((weak)) void USBD_CUSTOM_HID_ReceiveData_FS(uint8_t *pData, uint16_t Length)
{
  // 默认实现:回显数据(将接收的数据直接发送回去)
  if(Length > 0)
  {
    USBD_CUSTOM_HID_SendData_FS(pData, Length);
  }
}
/* USER CODE END 7 */
3.2.3 文件名:main.c
c 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usbd_custom_hid_if.h"
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// 串口打印缓冲区大小
#define UART_BUFFER_SIZE 128
// 测试数据发送间隔(ms)
#define SEND_TEST_DATA_INTERVAL 1000
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */
// 串口打印缓冲区
char UART_Buffer[UART_BUFFER_SIZE];
// 系统滴答计数
uint32_t SysTick_Count = 0;
// 测试发送数据
uint8_t Test_Send_Data[] = "STM32F105 USB HID VCOM Test: Hello PC!\r\n";
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
// 重定向printf到USART1
int fputc(int ch, FILE *f);
// 自定义HID数据接收回调函数(覆盖弱函数)
void USBD_CUSTOM_HID_ReceiveData_FS(uint8_t *pData, uint16_t Length);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// 重定向printf到USART1
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

// 自定义HID数据接收回调函数
void USBD_CUSTOM_HID_ReceiveData_FS(uint8_t *pData, uint16_t Length)
{
  if((pData != NULL) && (Length > 0))
  {
    // 1. 串口打印接收的数据(调试用)
    snprintf(UART_Buffer, UART_BUFFER_SIZE, "Receive PC Data: ");
    HAL_UART_Transmit(&huart1, (uint8_t *)UART_Buffer, strlen(UART_BUFFER_SIZE), HAL_MAX_DELAY);
    HAL_UART_Transmit(&huart1, pData, Length, HAL_MAX_DELAY);
    snprintf(UART_Buffer, UART_BUFFER_SIZE, "\r\n");
    HAL_UART_Transmit(&huart1, (uint8_t *)UART_Buffer, strlen(UART_BUFFER_SIZE), HAL_MAX_DELAY);
    
    // 2. 回显数据到PC(虚拟串口)
    USBD_CUSTOM_HID_SendData_FS(pData, Length);
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */
  
  // 初始化完成,串口打印提示信息
  printf("STM32F105 USB HID Virtual COM Port Init OK!\r\n");
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // 每隔1秒发送一次测试数据到PC
    if(HAL_GetTick() - SysTick_Count >= SEND_TEST_DATA_INTERVAL)
    {
      SysTick_Count = HAL_GetTick();
      
      // 发送测试数据
      USBD_CUSTOM_HID_SendData_FS(Test_Send_Data, strlen((char *)Test_Send_Data));
      
      // 串口打印发送状态
      printf("Send Test Data to PC: %s", Test_Send_Data);
    }
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief  System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB;
  PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief  USART1 Initialization Function
  * @param  None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{
  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */
}

/**
  * @brief  GPIO Initialization Function
  * @param  None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  /* USER CODE BEGIN MX_GPIO_Init_1 */

  /* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /* USER CODE BEGIN MX_GPIO_Init_2 */

  /* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
    // 错误处理:可添加LED闪烁等提示
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

四、编译与烧录

4.1 编译工程

  1. 在STM32CubeIDE中,点击顶部菜单栏Project -> Build Project
  2. 等待编译完成,控制台显示0 errors, 0 warnings即为编译成功
  3. 编译完成后,在工程目录Debug文件夹下会生成STM32F105_USB_HID_VCOM.elfSTM32F105_USB_HID_VCOM.bin文件

4.2 烧录程序

  1. 连接ST-Link V2到STM32开发板的SWD接口(SWDIO、SWCLK、GND、VCC)
  2. 连接开发板到电脑USB口,打开STM32CubeIDE的Run -> Debug Configurations
  3. 选择STM32 Cortex-M C/C++ Application,点击New创建新配置
  4. Project选择当前工程,C/C++ Application选择生成的.elf文件
  5. Debugger选择ST-Link Debugger,点击Apply -> Debug
  6. 程序会自动烧录到STM32芯片中,并进入调试模式,点击Resume运行程序

五、硬件调试与通信验证

5.1 驱动安装

  1. 将STM32开发板的USB接口(PA11/PA12对应的USB口)连接到电脑
  2. Windows会自动检测新设备,若未自动安装驱动,需手动安装STM32 HID驱动:
    • 下载STM32虚拟串口驱动(VCP Driver)
    • 设备管理器中找到"STM32 HID Device",右键更新驱动,选择下载的驱动目录

5.2 通信测试

5.2.1 工具准备
  • USB设备查看器:确认设备枚举成功,设备名称为"STM32 HID Virtual COM Port"
  • HID调试工具:如HID TerminalUSBlyzer
  • 串口助手:用于查看USART1的调试信息
5.2.2 测试步骤
  1. 打开串口助手,选择USART1对应的串口(如COM3),波特率115200,打开串口,可看到"STM32F105 USB HID Virtual COM Port Init OK!"提示
  2. 每隔1秒,串口助手会打印"Send Test Data to PC: STM32F105 USB HID VCOM Test: Hello PC!",说明MCU正在向PC发送数据
  3. 打开HID调试工具,选择对应的STM32 HID设备,发送任意字符(如"Test Data from PC")
  4. 串口助手会打印"Receive PC Data: Test Data from PC",同时HID调试工具会收到回显数据,说明双向通信成功

5.3 常见问题排查

  1. 设备枚举失败
    • 检查USB引脚连接(PA11/DM、PA12/DP)是否正确,有无虚焊
    • 确认时钟配置正确,USB时钟需为48MHz(72MHz/1.5)
    • 检查USB线缆是否正常,尝试更换线缆
  2. 数据发送失败
    • 检查HID报告描述符是否正确,端点大小是否匹配
    • 确认USBD_CUSTOM_HID_SendData_FS函数调用参数正确
    • 检查USB设备是否处于挂起状态,可添加唤醒逻辑
  3. 数据接收无回调
    • 确认CUSTOM_HID_OutEvent_FS函数中是否调用USBD_HID_GetReport
    • 检查HID输出端点配置是否正确

六、功能扩展与优化

6.1 数据分包与校验

对于超过64字节的数据,需实现分包发送,并添加CRC校验,避免数据丢失或错误:

c 复制代码
// 文件名:usbd_custom_hid_if.c(新增函数)
/**
  * @brief  分包发送数据
  * @param  pData: 待发送数据指针
  * @param  TotalLength: 数据总长度
  * @retval 发送状态
  */
uint8_t USBD_CUSTOM_HID_SendData_Pack_FS(uint8_t *pData, uint16_t TotalLength)
{
  uint8_t result = USBD_OK;
  uint16_t SendLen = 0;
  uint16_t RemainLen = TotalLength;
  uint8_t *pSendBuf = pData;
  
  while(RemainLen > 0)
  {
    SendLen = (RemainLen > VCOM_DATA_BUFFER_SIZE) ? VCOM_DATA_BUFFER_SIZE : RemainLen;
    
    result = USBD_CUSTOM_HID_SendData_FS(pSendBuf, SendLen);
    if(result != USBD_OK)
    {
      return result;
    }
    
    // 等待发送完成(可添加延时或等待标志位)
    HAL_Delay(1);
    
    pSendBuf += SendLen;
    RemainLen -= SendLen;
  }
  
  return result;
}

6.2 低功耗优化

添加USB挂起/唤醒处理,降低功耗:

c 复制代码
// 文件名:main.c(新增回调函数)
void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd)
{
  // USB挂起处理:关闭外设、进入低功耗模式
  printf("USB Suspend, Enter Low Power Mode!\r\n");
  HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}

void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd)
{
  // USB唤醒处理:恢复外设、退出低功耗模式
  printf("USB Resume, Exit Low Power Mode!\r\n");
  HAL_PWR_ExitSLEEPMode();
}

七、总结

关键点回顾

  1. STM32F105实现USB HID虚拟串口的核心是配置USB_DEVICE为HID类,并自定义符合虚拟串口的HID报告描述符;
  2. 数据交互的核心函数为USBD_CUSTOM_HID_SendData_FS(发送)和CUSTOM_HID_OutEvent_FS(接收回调),需保证缓冲区和端点大小匹配;
  3. 调试时需先确认USB设备枚举成功,再通过串口助手和HID调试工具验证双向通信,常见问题集中在引脚连接、时钟配置和报告描述符错误。

本教程从环境搭建到代码实现、调试优化,覆盖了STM32F105 USB HID虚拟串口开发的全流程,代码可复制使用,零基础用户按照步骤操作即可完成实战开发。

相关推荐
李boyang1 小时前
基于 STM32F103 + ESP8266 实现 DHT11 温湿度数据上传华为云 IoT 平台
stm32·物联网·华为云·esp8266
香水5只用六神2 小时前
【TIM】基本定时器定时实验(2)
c语言·开发语言·stm32·单片机·嵌入式硬件·mcu·学习
阿拉斯攀登2 小时前
【瑞芯微 RK 系列 + 安卓驱动全栈教程】博客系列
嵌入式硬件·安卓·瑞芯微·rk3576·嵌入式安卓·安卓驱动
时空自由民.3 小时前
IMU零漂温漂校准(chatgpt版本)
单片机
Saniffer_SH3 小时前
【高清视频】4小时带你了解Saniffer公司针对PCIe Gen6测试的最新白皮书15.X
网络·人工智能·驱动开发·嵌入式硬件·测试工具·计算机外设·压力测试
日更嵌入式的打工仔4 小时前
USART_IT_TC 与 IDLE 的区别
单片机·嵌入式硬件
香水5只用六神4 小时前
【RTOS快速入门】05_动态_静态创建任务(1)
c语言·开发语言·单片机·嵌入式硬件·freertos·rtos·嵌入式软件
foundbug9995 小时前
基于CAN总线的STM32F103 BootLoader实现方案
stm32·单片机·嵌入式硬件
qq_401700416 小时前
STM32使用SPI接口AD7175芯片ADC采集的驱动
stm32·单片机·嵌入式硬件