STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL )
STM32的USB功能模块可以配置为虚拟串口(VCOM: Visual Port Com)或人机交互设备(HID: Human Interface Device)两种主要的通讯接口。可以实现PC到STM32的双向通信。在应用方面的区别主要体现为:
- 虚拟串口的一帧字节长度可以自行定义,发送和接收的buffer可以开得比较大;
- HID的一帧字节长度有限制,如全速USB的HID一帧长度最大64字节;
- 虚拟串口需要用户在PC安装操作系统驱动,然后应用软件需要人工或自动扫描和指定串口端口号再进行通讯;
- HID采用操作系统自带的驱动,不需要用户再去安装驱动,应用软件可以扫描有效的VID和PID,从而识别对应USB设备有没有正常连接,一般采用自动扫描连接而不采用人工连接。
这里介绍STM32F401CCU6基于STM32CUBIDE开发环境的USB虚拟串口及USB CUSTOM HID的配置及Echo功能实现(HAL库)。
基本工程配置
首先建立工程并配置采用外部HSE晶振,采用默认参数。USB功能需要外部HSE晶振提供时钟。

讯享网
然后配置时钟树,关键是给USB提供48MHz时钟。

对于虚拟串口,可以根据需要调整堆和栈的大小:

这样就完成了基本的时钟配置,然后使能USB Device功能,采用默认参数即可:


这样就完成了USB Device功能的启用,后面根据实现虚拟串口还是HID进行不同的配置。
虚拟串口配置及Echo功能实现
首先将USB Device选择配置为虚拟串口并采用默认配置:

后面在PC上装STM32提供的虚拟串口驱动就可以进行通讯。这些默认配置信息可以修改,如果做了修改,则需要同步修改STM32提供的虚拟串口驱动里面的信息,实现信息匹配驱动才能对应生效。
注意根据需要配置发送和接收buffer的大小:

保存并生成初始工程代码:

Echo功能实现PC发到STM32的数据,STM32原样返回给PC端。首先找到STM32的接收函数:

然后在函数内增加发送代码,即可实现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):

连接PC的USB口后,在Windows的设备管理器的端口里可以看到识别到的串口设备和端口号:

这里用PortHelper工具的效果,注意Echo设计在STM32里的转发速度比较快,所以只要PC端不是快速连续发送,波特率快慢可以任意调整:

在非Echo功能的实现上,如果有超长的数据发送,则可以分成多个段发送,前一个段发送完后再发下一个段,下面这个函数是每次发送完成之后的回调函数,可以在里面设置标志变化,从而在主程序里进行当前次发送已完成的判断。

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

讯享网 uint8_t USB_CONN_STATUS(void);
- 编写状态识别函数

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模式:

然后配置如下参数:

Custom_HID_FS_BINTERVAL为响应主机发送数据的延时时间, 最小为1,越小响应度越好。
USBD_CUSTOM_HID_REPORT_DESC_SIZE是报告描述符的字节长度,其与后面将进一步描述的代码关系如下所示:

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各级描述符的关系如下图:

USBD_MAX_STR_DESC_SIZ是字符串描述符的空间大小,采用默认即可。
USBD_SELF_POWERED是电源选项,设置为Enable。
USBD_DEBUG_LEVEL是调试选项,这里设置为不用即可。

然后配置描述符信息:

这里VID是厂家信息,正规厂家可以向USB协会申请厂家的代码,如下为ST的代码:

LANGID_STRING是语言识别符,设置为English即可。
MANUFACTURE_STRING是厂商名称,这里是ST公司名称。
PID是产品序列号,默认是22352。
PRODUCT_STRING是产品名称字符串设置。
CONFIGURATION_STRING是配置名称字符串设置。
INTERFACE_STRING是接口名称字符串设置。
实际上,以上的信息都是可以改动的,PC在USB HID自举时,更关键的信息是所采用的通讯协议的设定,从而决定采用的驱动。因此上述信息都是描述性信息,不会影响PC对USB HID的驱动安装,但是如果在PC上通过在线升级方式获取升级驱动,则需要这部分信息和在线驱动信息匹配。这里会将相关信息进行改动为如下所示:

保存后生成初始工程代码:

然后需要对报告描述符进行升级改写:

具体代码:
/ 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接收中断回调函数进行改写,从而对接收到的数据进行存储。

具体代码:
讯享网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 HID Demonstrator作为测试工具。这里还是继续采用PortHelper工具来进行USB HID测试,还是比较方便的。

点击“打开USB"后:

其中自举握手信息显示的厂商号06C7就是十进制的1735,产品号3039就是十进制的12345, 也就是在STM32的USB HID配置界面设置的数字。
PortHelper已经基于获得的64个传输字节大小的信息,已经放置了64个字节在下面的发送区。在界面的辅助位置点上Hex发送和Hex显示,再点击端点2/HID发送,则STM32会将收到的数据回传过来。

需要注意的是,因为设置了64个字节的传输包大小,所以STM32侧发送数据必须以64个字节为一组发送,否则数量不足包不能发送。而在PC侧的软件方面,会自动检测输入的字节数是不是64个字节,如果不是64个字节,就会自动补0到64个字节发送。所以这里将发送区的发送数据改为1个55进行发送,实际上发出的就是1个55和63个00。如下所示:

按照上述设计,就实现了STM32 USB HID的Echo功能。
STM32两种HID区别
STM32可以将USB配置为HID或CUSTOM HID,上述的介绍实际上是针对CUSTOM HID的实现。HID和CUSTOM HID的配置区别为下面的部分:


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


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/70225.html