GD32实现USB HID自定义复合设备

本文详细介绍了如何在GD32微控制器上实现一个USB HID复合设备,通过两个不同的接口提供多种功能。首先介绍了复合设备的概念,然后讲解了开发准备、代码移植的步骤,包括修改USB控制引脚、添加串口调试、更新设备和配置描述符、以及修改相关处理函数。最终,成功编译并下载后,设备在主机上表现为两个独立的USB设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

复合设备是啥,通俗讲就是一个USB物理设备可以实现多个功能,在主机端可以看到多个设备。通常实现HID复合设备有两种方式。第一种是使用同一个接口,修改报告描述符,增加一个功能集合,同时需要使用报告ID来区分哪一个设备,这样主机端和设备端需要增加报告ID处理,但只需要两个端点来实现功能,对于端口资源较少的MCU可以使用;第二种是使用两个接口,每个接口对应不同的报告描述符,不需要特意使用报告ID来区分,但不同接口使用独立的端点。
本文章使用不同接口来实现,基于GD32例程开发。

  1. 开发准备
    (1)下载官方GD32F1x0_Firmware_Library_v3.1.0,使用GD32F1x0_Firmware_Library_v3.1.0\Projects\USBD\HID_custom工程
    (2)keil5添加GD系列
    (3)连接GDlink和串口工具

  2. 移植代码
    2.1 修改控制控制USBDP上拉电阻引脚,(内部无法直接控制上下拉,因此增加此电路,否则主机不会枚举设备)
    DP控制电路
    #define USB_PULLUP GPIOC //PORT
    #define USB_PULLUP_PIN GPIO_PIN_13 //PIN
    #define RCC_AHBPeriph_GPIO_PULLUP RCU_GPIOC //时钟

2.2 增加串口1输出调试信息,main函数里添加:
/* uart configuration */
gd_eval_com_init(EVAL_COM1);
添加如下代码实现printf功能

    int fputc(int ch, FILE *f)
{
    usart_data_transmit(EVAL_COM1, (uint8_t)ch);
    while(RESET == usart_flag_get(EVAL_COM1, USART_FLAG_TBE));
    return ch;
}
int main(void)
{
   
   
	
	
    /* system clocks configuration */
    rcu_config();

    /* keys configuration */
    key_config();

    /* leds configuration */
    led_config();
		//gd_eval_led_on(LED1);
    /* GPIO configuration */
    gpio_config();
	
	    /* uart configuration */
    gd_eval_com_init(EVAL_COM1);

    /* USB device configuration */
    usbd_core_init(&usb_device_dev);

    /* NVIC configuration */
    nvic_config();

    /* enabled USB pull-up */
    gpio_bit_reset(USB_PULLUP, USB_PULLUP_PIN);//使能上拉

    /* now the usb device is connected */
    usb_device_dev.status = USBD_CONNECTED;
		printf("a usart transmit test example!");
		
    while (1)
		{
   
   
		};
}

2.3 修改USB相关描述符
2.3.1 设备描述符:不需要修改

const usb_descriptor_device_struct device_descripter =
{
   
   
    .Header = 
     {
   
   
         .bLength = USB_DEVICE_DESC_SIZE, 
         .bDescriptorType = USB_DESCTYPE_DEVICE
     },
    .bcdUSB = 0x0200,
    .bDeviceClass = 0x00,
    .bDeviceSubClass = 0x00,
    .bDeviceProtocol = 0x00,
    .bMaxPacketSize0 = USBD_EP0_MAX_SIZE,
    .idVendor = USBD_VID,
    .idProduct = USBD_PID,
    .bcdDevice = 0x0200,
    .iManufacturer = USBD_MFC_STR_IDX,
    .iProduct = USBD_PRODUCT_STR_IDX,
    .iSerialNumber = USBD_SERIAL_STR_IDX,
    .bNumberConfigurations = USBD_CFG_MAX_NUM
};

2.3.2 配置描述符:需要增加第二个接口的接口描述符,端点描述符

const usb_descriptor_configuration_set_struct configuration_descriptor = 
{
   
   
    .Config = 
    {
   
   
        .Header = 
         {
   
   
             .bLength = sizeof(usb_descriptor_configuration_struct), 
             .bDescriptorType = USB_DESCTYPE_CONFIGURATION 
         },
        .wTotalLength = CUSTOMHID_CONFIG_DESC_SIZE,
        .bNumInterfaces = 0x02,      //==接口数修改为2==
        .bConfigurationValue = 0x01,
        .iConfiguration = 0x00,
        .bmAttributes = 0xc0,
        .bMaxPower = 0x32
    },

    .HID_Interface = 
    {
   
   
        .Header = 
         {
   
   
             .bLength = sizeof(usb_descriptor_interface_struct), 
             .bDescriptorType = USB_DESCTYPE_INTERFACE 
         },
        .bInterfaceNumber = 0x00, //==接口序号为0,表示第一个接口==
        .bAlternateSetting = 0x00,
        .bNumEndpoints = 0x02, //==使用两个接口==
        .bInterfaceClass = 0x03,
        .bInterfaceSubClass = 0x00,
        .bInterfaceProtocol = 0x00,
        .iInterface = 0x00
    },

    .HID_VendorHID = 
    
### 如何在 GD32F103 上正确使用 `printf` 函数 为了能够在基于 GD32F103 的项目中成功调用 C 语言标准库中的 `printf` 函数,需完成几个必要的配置工作。 #### 配置 MicroLib 支持 当目标设备资源有限时,可以考虑启用编译器提供的微库支持来优化程序体积和性能。具体操作是在 IDE 中找到 Target 设置页面并勾选选项“Use MicroLib”。这一步骤简化了后续串口调试过程中对于输入输出功能的支持[^1]。 #### 初始化 USART 接口 要使 `printf` 输出的信息能够通过硬件 UART 发送出去,则必须先初始化相应的外设模块。通常情况下会定义波特率常量用于设定通信速度,并实现一个简单的初始化函数: ```c #define BAUDRATE 115200U void bsp_usart_init(void); ``` 此部分代码片段展示了如何声明全局宏定义以及对外设进行初步设置的函数原型[^2]。 #### 实现重定向方法 由于嵌入式环境中默认的标准流并不指向任何实际物理端口,因此还需要提供自定义版本的 `_write()` 或者直接覆盖 `fputc()` 来指定数据最终流向何处。下面是一个典型的例子说明怎样把字符写入到 USART0 设备: ```c int fputc(int ch, FILE *f){ while((USART0->SR & USART_SR_TXE)==0); /* Wait until transmit data register empty */ USART_SendData(USART0,ch); return(ch); } ``` 这段代码实现了单字节发送的功能,在每次调用 `printf` 后都会自动触发该回调机制从而实现实时日志记录的目的。 综上所述,只要按照上述指导完成了所有准备工作之后就可以像平常那样自由地运用 `printf` 进行开发测试啦!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值