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功能的实现上,如果有超长的数据发送,则可以分成多个段发送,前一个段发送完后再发下一个段,下面这个函数是每次发送完成之后的回调函数,可以在里面设置标志变化,从而在主程序里进行当前次发送已完成的判断。
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里还没有设计输出数据,所以是个不动的鼠标。
范例下载
上述STM32F401 VCOM范例下载 上述STM32F401 CUSTOM HID范例下载
参考资料
Defined Class Codes Device Class Definition for Human Interface Devices (HID)
–End–
|