STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)

STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL )

STM32的USB功能模块可以配置为虚拟串口(VCOM: Visual Port Com)或人机交互设备(HID: Human Interface Device)两种主要的通讯接口。可以实现PC到STM32的双向通信。在应用方面的区别主要体现为:

  1. 虚拟串口的一帧字节长度可以自行定义,发送和接收的buffer可以开得比较大;
  2. HID的一帧字节长度有限制,如全速USB的HID一帧长度最大64字节;
  3. 虚拟串口需要用户在PC安装操作系统驱动,然后应用软件需要人工或自动扫描和指定串口端口号再进行通讯;
  4. HID采用操作系统自带的驱动,不需要用户再去安装驱动,应用软件可以扫描有效的VID和PID,从而识别对应USB设备有没有正常连接,一般采用自动扫描连接而不采用人工连接。

这里介绍STM32F401CCU6基于STM32CUBIDE开发环境的USB虚拟串口及USB CUSTOM HID的配置及Echo功能实现(HAL库)。

基本工程配置

首先建立工程并配置采用外部HSE晶振,采用默认参数。USB功能需要外部HSE晶振提供时钟。
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第1张图片
然后配置时钟树,关键是给USB提供48MHz时钟。
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第2张图片
对于虚拟串口,可以根据需要调整堆和栈的大小:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第3张图片
这样就完成了基本的时钟配置,然后使能USB Device功能,采用默认参数即可:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第4张图片
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第5张图片
这样就完成了USB Device功能的启用,后面根据实现虚拟串口还是HID进行不同的配置。

虚拟串口配置及Echo功能实现

首先将USB Device选择配置为虚拟串口并采用默认配置:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第6张图片
后面在PC上装STM32提供的虚拟串口驱动就可以进行通讯。这些默认配置信息可以修改,如果做了修改,则需要同步修改STM32提供的虚拟串口驱动里面的信息,实现信息匹配驱动才能对应生效。

注意根据需要配置发送和接收buffer的大小:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第7张图片
保存并生成初始工程代码:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第8张图片
Echo功能实现PC发到STM32的数据,STM32原样返回给PC端。首先找到STM32的接收函数:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第9张图片
然后在函数内增加发送代码,即可实现Echo功能:

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  CDC_Transmit_FS(Buf, *Len);
			
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

编译下载后,在PC上用串口工具就可以实现验证。需要先安装STM32虚拟串口驱动(https://www.st.com/zh/development-tools/stsw-stm32102.html):
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第10张图片
连接PC的USB口后,在Windows的设备管理器的端口里可以看到识别到的串口设备和端口号:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第11张图片
这里用PortHelper工具的效果,注意Echo设计在STM32里的转发速度比较快,所以只要PC端不是快速连续发送,波特率快慢可以任意调整:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第12张图片
在非Echo功能的实现上,如果有超长的数据发送,则可以分成多个段发送,前一个段发送完后再发下一个段,下面这个函数是每次发送完成之后的回调函数,可以在里面设置标志变化,从而在主程序里进行当前次发送已完成的判断。
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第13张图片

USB虚拟串口连接识别

如果USB虚拟串口没有建立连接就执行STM32向外发送数据代码,容易产生异常,一般是由主控端连接STM32嵌入式端, 向STM32发送指令后,STM32再进行串口数据外发。另外一种方式就是增加连接状态识别,各个发送代码段可以先调用状态识别函数,确认已经是连接态再发送串口数据。状态识别函数按如下编写:

  1. 在头文件中申明函数
    STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第14张图片
 uint8_t USB_CONN_STATUS(void);
  1. 编写状态识别函数
    STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第15张图片
uint8_t USB_CONN_STATUS(void)
{
	if(hUsbDeviceFS.dev_connection_status==0) return 1;
	else return 0;
}

这样,当调用USB_CONN_STATUS()时,返回值为1表示USB虚拟串口已连接,否则为未连接,如:

         if(USB_CONN_STATUS())
         {
			 CDC_Transmit_FS("\r\nUSB VCOM\r\n", strlen("\r\nUSB VCOM\r\n")) ;
         }

HID配置及Echo功能实现

首先将USB Device配置为HID模式:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第16张图片
然后配置如下参数:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第17张图片

Custom_HID_FS_BINTERVAL为响应主机发送数据的延时时间, 最小为1,越小响应度越好。
USBD_CUSTOM_HID_REPORT_DESC_SIZE是报告描述符的字节长度,其与后面将进一步描述的代码关系如下所示:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第18张图片
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE是输出的报文大小,这里设置为64个字节。对于低速设备每一笔事务最大是8字节;对于全速设备每一笔事务最大是64字节;对于高速设备每一笔事务最大是1024字节。
USBD_MAX_NUM_INTERFACES和USBD_MAX_NUM_CONFIGURATION是最大接口和配置的数量,这里只实现一个双向通信,所以是1个配置和1个接口。另外,在接口之下的端点则是3个,一个是默认0端点用于自举控制信息收发,另外两个端点一个用于发送一个用于接收。这些都通过描述符实现,STM32CUBEIDE会自动生成1个收发通讯对应的各级描述符,只有报告描述符需要手动设置。USB HID各级描述符的关系如下图:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第19张图片
USBD_MAX_STR_DESC_SIZ是字符串描述符的空间大小,采用默认即可。
USBD_SELF_POWERED是电源选项,设置为Enable。
USBD_DEBUG_LEVEL是调试选项,这里设置为不用即可。

然后配置描述符信息:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第20张图片
这里VID是厂家信息,正规厂家可以向USB协会申请厂家的代码,如下为ST的代码:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第21张图片
LANGID_STRING是语言识别符,设置为English即可。
MANUFACTURE_STRING是厂商名称,这里是ST公司名称。
PID是产品序列号,默认是22352。
PRODUCT_STRING是产品名称字符串设置。
CONFIGURATION_STRING是配置名称字符串设置。
INTERFACE_STRING是接口名称字符串设置。

实际上,以上的信息都是可以改动的,PC在USB HID自举时,更关键的信息是所采用的通讯协议的设定,从而决定采用的驱动。因此上述信息都是描述性信息,不会影响PC对USB HID的驱动安装,但是如果在PC上通过在线升级方式获取升级驱动,则需要这部分信息和在线驱动信息匹配。这里会将相关信息进行改动为如下所示:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第22张图片
保存后生成初始工程代码:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第23张图片
然后需要对报告描述符进行升级改写:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第24张图片
具体代码:

/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
  //0x00,
		0x05, 0x8c,
		0x09, 0x01,
		0xa1, 0x01,

		0x09, 0x03,
		0x15, 0x00,
		0x26, 0x00, 0xFF,
		0x75, 0x08,
		//0x95, CUSTOM_HID_EPIN_SIZE,
		0x95, 0x40,
		0x81, 0x02,

		0x09, 0x04,
		0x15, 0x00,
		0x26, 0x00, 0xFF,
		0x75, 0x08,
		//0x95, CUSTOM_HID_EPOUT_SIZE,
		0x95, 0x40,
		0x91, 0x02,
  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};

为了实现Echo功能,对HID接收中断回调函数进行改写,从而对接收到的数据进行存储。
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第25张图片
具体代码:

static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
  /* USER CODE BEGIN 6 */
	extern uint8_t usb_rx_status;
	extern uint8_t reportdata[64];

	USBD_CUSTOM_HID_HandleTypeDef   *hhid;
    hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;//Get receiving data address

    usb_rx_status = 1;

	for(uint8_t i=0;i<64;i++)
	{
	   reportdata[i]=hhid->Report_buf[i]; //note: event_idx==hhid->Report_buf[0] ; state==hhid->Report_buf[1]
	}


  UNUSED(event_idx);
  UNUSED(state);

  /* Start next USB packet transfer once data processing is completed */
  if (USBD_CUSTOM_HID_ReceivePacket(&hUsbDeviceFS) != (uint8_t)USBD_OK)
  {
    return -1;
  }

  return (USBD_OK);
  /* USER CODE END 6 */
}

然后在main.c文件实现变量定义和Echo功能的发送控制,代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 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 */

/* USER CODE END Includes */

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

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

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

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t usb_rx_status = 0;
uint8_t reportdata[64]={0};
extern USBD_HandleTypeDef hUsbDeviceFS;

/* 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_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  if(usb_rx_status==1)
	  {
		  usb_rx_status = 0;
		  USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, reportdata, 64);
	  }

    /* 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};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  /** 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.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  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();
  }
}

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

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

}

/* 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)
  {
  }
  /* 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 */

编译下载到STM32后,用USB线连接到PC,则可以看到USB HID设备已识别成功:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第26张图片
可以用工具进行通讯测试,STM32提供USB HID Demonstrator作为测试工具。这里还是继续采用PortHelper工具来进行USB HID测试,还是比较方便的。
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第27张图片
点击“打开USB"后:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第28张图片
其中自举握手信息显示的厂商号06C7就是十进制的1735,产品号3039就是十进制的12345, 也就是在STM32的USB HID配置界面设置的数字。
PortHelper已经基于获得的64个传输字节大小的信息,已经放置了64个字节在下面的发送区。在界面的辅助位置点上Hex发送和Hex显示,再点击端点2/HID发送,则STM32会将收到的数据回传过来。
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第29张图片
需要注意的是,因为设置了64个字节的传输包大小,所以STM32侧发送数据必须以64个字节为一组发送,否则数量不足包不能发送。而在PC侧的软件方面,会自动检测输入的字节数是不是64个字节,如果不是64个字节,就会自动补0到64个字节发送。所以这里将发送区的发送数据改为1个55进行发送,实际上发出的就是1个55和63个00。如下所示:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第30张图片
按照上述设计,就实现了STM32 USB HID的Echo功能。

STM32两种HID区别

STM32可以将USB配置为HID或CUSTOM HID,上述的介绍实际上是针对CUSTOM HID的实现。HID和CUSTOM HID的配置区别为下面的部分:
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第31张图片
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)_第32张图片

也即HID相比CUSTOM HID少了报文描述符大小和传输报文大小的设置。实际上HID配置生成工程代码后,直接编译下载,用USB线连接STM32和PC后,PC识别为一个鼠标输入设备并直接使用,当然此时因为STM32里还没有设计输出数据,所以是个不动的鼠标。

范例下载

上述STM32F401 VCOM范例下载
上述STM32F401 CUSTOM HID范例下载

参考资料

Defined Class Codes
Device Class Definition for Human Interface Devices (HID)

–End–

你可能感兴趣的:(STM32,STM32,HAL,VCOM,虚拟串口,HID,CUSTOM,HID)