返回列表 发新帖我要提问本帖赏金: 25.00元(功能说明)

[单片机芯片] CH32V203的USB1 HID库调试经验分享

[复制链接]
2174|2
 楼主| lilijin1995 发表于 2022-12-7 13:36 | 显示全部楼层 |阅读模式
本帖最后由 lilijin1995 于 2022-12-9 16:39 编辑

背景:      

今天要水的帖子是关于使用CH32V203替代旧STM32F103实现USB霍尔摇杆的项目。关于调试经验啥的,主要分享CH32V203的USB使用。这个项目最开始是使用迈来芯的方案,但是价格30RMB/PCS,也是芯片涨价时候涨上去的,后来替换了同厂不同方案,这个方案芯片对磁场灵敏度很高,高得离谱,迫不得已更换了磁铁,后来总算满足了,2021年时候发现了国产替代方案,矽睿科技的QMC磁力计方案,本来想推给老板更换的,然而老板觉得不成熟,时至今日,老板实在用不起国外的方案了,机会来了,我对WCH的CH32V RISC-V系列MCU比较熟悉,今天我们就开始国产化替代过程吧!


关于硬件
      由于需要兼容STM32的USB,所以这里使用了PA11(DM)和PA12(DP),对应CH32V203的USB1,我看USB Device 的实现跟STM32 的HAL库居然有点一致了,之前一致是在中断回调函数中枚举的。其实我更喜欢第二种方式,可能效率会更高。不过USB2好像还是原来的方式。
6884063905b29bfc57.png

软件设计

我是在懒得新建工程,移植USB Library了,干脆直接在官方工程上改,我用的是EXAM\USB\USBD\CompositeKM,USBD目录的工程用的USB1口对应PA11(DM)和PA12(DP),大家不要搞错了,我就搞错过,还寄了芯片给WCH的FAE调试,后来发现我自己用错IO了。

1.时钟初始化配置
初始化需要配置RCC_USBCLKConfig,以及RCC_APB1PeriphClockCmd,而在例程中是使用Set_USBConfig调用实现的,如下代码:
  1. void Set_USBConfig( )
  2. {
  3.         RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_Div3);
  4.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);                          
  5. }
大家都知道,USB 设备一般都是使用48MHz的时钟,而例程的PLLCLK是144Mhz,所以这里是3分频,另外需要使能一下APB1的外设时钟。

2.USB端点配置、描述符配置标准请求的实现等。

端点地址、大小,描述符,各类请求的实现是通过以下函数实现:
  1. void USB_Init(void)
  2. {
  3.   pInformation = &Device_Info;
  4.   pInformation->ControlState = 2;
  5.   pProperty = &Device_Property;
  6.   pUser_Standard_Requests = &User_Standard_Requests;
  7.   pProperty->Init();
  8. }
如果你定位到pInformation,pProperty,以及pUser_Standard_Requests的定义,可以发现,
pInformation的定义其实是DEVICE_INFO这样子的,这其实是一些USB请求事务信息。
  1. typedef struct _DEVICE_INFO
  2. {
  3.   uint8_t USBbmRequestType;       /* bmRequestType */
  4.   uint8_t USBbRequest;            /* bRequest */
  5.   uint16_t_uint8_t USBwValues;         /* wValue */
  6.   uint16_t_uint8_t USBwIndexs;         /* wIndex */
  7.   uint16_t_uint8_t USBwLengths;        /* wLength */

  8.   uint8_t ControlState;           /* of type CONTROL_STATE */
  9.   uint8_t Current_Feature;
  10.   uint8_t Current_Configuration;   /* Selected configuration */
  11.   uint8_t Current_Interface;       /* Selected interface of current configuration */
  12.   uint8_t Current_AlternateSetting;/* Selected Alternate Setting of current
  13.                                      interface*/

  14.   ENDPOINT_INFO Ctrl_Info;
  15. }DEVICE_INFO;
而pProperty的定义更复杂以下,DEVICE_PROP定义如下,如果说DEVICE_INFO是数据结构的话,我觉得DEVICE_PROP更像是实现,因为里面都是一些函数指针,和一些必要的指针和变量,囊括了USB 设备的枚举过程:端点的配置,各种请求类,相关描述符的获取。
  1. typedef struct _DEVICE_PROP
  2. {
  3.   void (*Init)(void);        /* Initialize the device */
  4.   void (*Reset)(void);       /* Reset routine of this device */
  5.   void (*Process_Status_IN)(void);
  6.   void (*Process_Status_OUT)(void);

  7.   RESULT (*Class_Data_Setup)(uint8_t RequestNo);

  8.   RESULT (*Class_NoData_Setup)(uint8_t RequestNo);

  9.   RESULT  (*Class_Get_Interface_Setting)(uint8_t Interface, uint8_t AlternateSetting);

  10.   uint8_t* (*GetDeviceDescriptor)(uint16_t Length);
  11.   uint8_t* (*GetConfigDescriptor)(uint16_t Length);
  12.   uint8_t* (*GetStringDescriptor)(uint16_t Length);

  13.   void* RxEP_buffer;
  14.    
  15.   uint8_t MaxPacketSize;

  16. }DEVICE_PROP;

在这里我们先来改描述符的获取吧,
  1.   uint8_t* (*GetDeviceDescriptor)(uint16_t Length);
  2.   uint8_t* (*GetConfigDescriptor)(uint16_t Length);
  3.   uint8_t* (*GetStringDescriptor)(uint16_t Length);
这三个指针函数是获取设备描述符,配置描述符集合,字符串描述符。

设备描述符:以下是我雷蛇鼠标的: 450976392f04da87d6.png
配置描述符集合包含了配置描述符,接口描述符,HID描述符(如果是HID),端点描述符:
374246392f0c3d2918.png
上图只是接口0的,共有三个接口,其实都是同理可得。
HID每个接口几乎都有自己的报表描述符。
514796392f17413ecf.png
在代码中如何实现的呢?
Device_Property中的USBD_Data_Setup里面有个USBD_GetReportDescriptor请求,这是获取报表描述符的。
  1. uint8_t *USBD_GetReportDescriptor(uint16_t Length)
  2. {
  3.   uint8_t wIndex0 = pInformation->USBwIndexs.bw.bb0;
  4.   if (wIndex0 > 2)
  5.   {
  6.     return NULL;
  7.   }
  8.   else
  9.   {
  10.     return Standard_GetDescriptorData(Length, &Report_Descriptor[wIndex0]);
  11.   }
  12. }
  1. ONE_DESCRIPTOR Report_Descriptor[3] =
  2. {
  3.         {(uint8_t*)USBD_JoystickRepDesc, USBD_SIZE_REPORT_DESC_JS},
  4.         {(uint8_t*)USBD_MouseRepDesc, USBD_SIZE_REPORT_DESC_MS},
  5.         {(uint8_t*)USBD_VendorRepDesc, USBD_SIZE_REPORT_DESC_VD},
  6. };
如果设备描述符、配置描述符、接口描述符、HID描述符、端点描述符、报表描述符没有问题的话,是不是就一定可以枚举成功了呢,是不是就可以高枕无忧了呢,然而事实并非如此。还需要注意一下端点地址和大小的配置。
端点地址和缓存大小其实也是在Device_Property中的USBD_Reset复位中的,
  1. void USBD_Reset(void)
  2. {
  3.   pInformation->Current_Configuration = 0;
  4.   pInformation->Current_Feature = USBD_ConfigDescriptor[7];
  5.   pInformation->Current_Interface = 0;

  6.   SetBTABLE(BTABLE_ADDRESS);

  7.     SetEPType(ENDP0, EP_CONTROL);
  8.     SetEPTxStatus(ENDP0, EP_TX_STALL);
  9.     SetEPRxAddr(ENDP0, ENDP0_RXADDR);
  10.     SetEPTxAddr(ENDP0, ENDP0_TXADDR);
  11.     Clear_Status_Out(ENDP0);
  12.     SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
  13.     SetEPRxValid(ENDP0);
  14.     _ClearDTOG_RX(ENDP0);
  15.     _ClearDTOG_TX(ENDP0);

  16.       SetEPType(ENDP1, EP_INTERRUPT);
  17.       SetEPRxAddr(ENDP1, ENDP1_RXADDR);
  18.       SetEPTxAddr(ENDP1, ENDP1_TXADDR);
  19.       SetEPTxStatus(ENDP1, EP_TX_NAK);
  20.       Clear_Status_Out(ENDP1);
  21.       SetEPRxCount(ENDP1, Device_Property.MaxPacketSize);
  22.       SetEPRxValid(ENDP1);
  23.       _ClearDTOG_TX(ENDP1);
  24.       _ClearDTOG_RX(ENDP1);

  25.       SetEPType(ENDP2, EP_INTERRUPT);
  26.       SetEPTxAddr(ENDP2, ENDP2_TXADDR);
  27.       SetEPTxStatus(ENDP2, EP_TX_NAK);
  28.       _ClearDTOG_TX(ENDP2);
  29.       _ClearDTOG_RX(ENDP2);

  30.       SetEPType(ENDP3, EP_INTERRUPT);
  31.       SetEPTxAddr(ENDP3, ENDP3_TXADDR);
  32.       SetEPTxStatus(ENDP3, EP_TX_NAK);
  33.       _ClearDTOG_TX(ENDP3);
  34.       _ClearDTOG_RX(ENDP3);


  35.     SetDeviceAddress(0);

  36.     bDeviceState = ATTACHED;
  37. }
地址是偏移地址,需要根据端点描述符中的最大Size来配置并偏移。

如果你的设备成功枚举,这时候HID还不能动的,需要实现端点数据的上报,如下:
  1. uint8_t USBD_ENDPx_DataUp( uint8_t endp, uint8_t *pbuf, uint16_t len )
  2. {
  3.         if( endp == ENDP1 )
  4.         {
  5.                 if (USBD_Endp1_Busy)
  6.                 {
  7.                         return USB_ERROR;
  8.                 }
  9.                 USB_SIL_Write( EP1_IN, pbuf, len );
  10.                 USBD_Endp1_Busy = 1;
  11.                 SetEPTxStatus( ENDP1, EP_TX_VALID );
  12.                
  13.         }
  14.     else if( endp == ENDP2 )
  15.         {
  16.                 if (USBD_Endp2_Busy)
  17.                 {
  18.                         return USB_ERROR;
  19.                 }
  20.                 USB_SIL_Write( EP2_IN, pbuf, len );
  21.                 USBD_Endp2_Busy = 1;
  22.                 SetEPTxStatus( ENDP2, EP_TX_VALID );
  23.         }
  24.     else if( endp == ENDP3 )
  25.     {
  26.         if (USBD_Endp3_Busy)
  27.         {
  28.             return USB_ERROR;
  29.         }
  30.         USB_SIL_Write( EP3_IN, pbuf, len );
  31.         USBD_Endp3_Busy = 1;
  32.         SetEPTxStatus( ENDP3, EP_TX_VALID );
  33.     }
  34.         else
  35.         {
  36.                 return USB_ERROR;
  37.         }
  38.         return USB_SUCCESS;
  39. }
依葫芦画瓢,根据端点不同改一下即可实现不同的端点数据上报。
最后,希望大家样机调试成功!!



打赏榜单

21ic小管家 打赏了 25.00 元 2022-12-20
理由:签约作者奖励

yangxiaor520 发表于 2022-12-11 19:33 来自手机 | 显示全部楼层
希望下次能中个开发板
瞎折腾 发表于 2022-12-12 14:20 | 显示全部楼层
学习了,谢谢
您需要登录后才可以回帖 登录 | 注册

本版积分规则

56

主题

165

帖子

8

粉丝
快速回复 在线客服 返回列表 返回顶部