发新帖本帖赏金 25.00元(功能说明)我要提问
返回列表
打印
[单片机芯片]

CH32V203的USB1 HID库调试经验分享

[复制链接]
1205|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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好像还是原来的方式。


软件设计

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

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

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

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

  uint8_t ControlState;           /* of type CONTROL_STATE */
  uint8_t Current_Feature;
  uint8_t Current_Configuration;   /* Selected configuration */
  uint8_t Current_Interface;       /* Selected interface of current configuration */
  uint8_t Current_AlternateSetting;/* Selected Alternate Setting of current
                                     interface*/

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

  RESULT (*Class_Data_Setup)(uint8_t RequestNo);

  RESULT (*Class_NoData_Setup)(uint8_t RequestNo);

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

  uint8_t* (*GetDeviceDescriptor)(uint16_t Length);
  uint8_t* (*GetConfigDescriptor)(uint16_t Length);
  uint8_t* (*GetStringDescriptor)(uint16_t Length);

  void* RxEP_buffer;
   
  uint8_t MaxPacketSize;

}DEVICE_PROP;

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

设备描述符:以下是我雷蛇鼠标的:
配置描述符集合包含了配置描述符,接口描述符,HID描述符(如果是HID),端点描述符:

上图只是接口0的,共有三个接口,其实都是同理可得。
HID每个接口几乎都有自己的报表描述符。

在代码中如何实现的呢?
Device_Property中的USBD_Data_Setup里面有个USBD_GetReportDescriptor请求,这是获取报表描述符的。
uint8_t *USBD_GetReportDescriptor(uint16_t Length)
{
  uint8_t wIndex0 = pInformation->USBwIndexs.bw.bb0;
  if (wIndex0 > 2)
  {
    return NULL;
  }
  else
  {
    return Standard_GetDescriptorData(Length, &Report_Descriptor[wIndex0]);
  }
}
ONE_DESCRIPTOR Report_Descriptor[3] =
{
        {(uint8_t*)USBD_JoystickRepDesc, USBD_SIZE_REPORT_DESC_JS},
        {(uint8_t*)USBD_MouseRepDesc, USBD_SIZE_REPORT_DESC_MS},
        {(uint8_t*)USBD_VendorRepDesc, USBD_SIZE_REPORT_DESC_VD},
};
如果设备描述符、配置描述符、接口描述符、HID描述符、端点描述符、报表描述符没有问题的话,是不是就一定可以枚举成功了呢,是不是就可以高枕无忧了呢,然而事实并非如此。还需要注意一下端点地址和大小的配置。
端点地址和缓存大小其实也是在Device_Property中的USBD_Reset复位中的,
void USBD_Reset(void)
{
  pInformation->Current_Configuration = 0;
  pInformation->Current_Feature = USBD_ConfigDescriptor[7];
  pInformation->Current_Interface = 0;

  SetBTABLE(BTABLE_ADDRESS);

    SetEPType(ENDP0, EP_CONTROL);
    SetEPTxStatus(ENDP0, EP_TX_STALL);
    SetEPRxAddr(ENDP0, ENDP0_RXADDR);
    SetEPTxAddr(ENDP0, ENDP0_TXADDR);
    Clear_Status_Out(ENDP0);
    SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
    SetEPRxValid(ENDP0);
    _ClearDTOG_RX(ENDP0);
    _ClearDTOG_TX(ENDP0);

      SetEPType(ENDP1, EP_INTERRUPT);
      SetEPRxAddr(ENDP1, ENDP1_RXADDR);
      SetEPTxAddr(ENDP1, ENDP1_TXADDR);
      SetEPTxStatus(ENDP1, EP_TX_NAK);
      Clear_Status_Out(ENDP1);
      SetEPRxCount(ENDP1, Device_Property.MaxPacketSize);
      SetEPRxValid(ENDP1);
      _ClearDTOG_TX(ENDP1);
      _ClearDTOG_RX(ENDP1);

      SetEPType(ENDP2, EP_INTERRUPT);
      SetEPTxAddr(ENDP2, ENDP2_TXADDR);
      SetEPTxStatus(ENDP2, EP_TX_NAK);
      _ClearDTOG_TX(ENDP2);
      _ClearDTOG_RX(ENDP2);

      SetEPType(ENDP3, EP_INTERRUPT);
      SetEPTxAddr(ENDP3, ENDP3_TXADDR);
      SetEPTxStatus(ENDP3, EP_TX_NAK);
      _ClearDTOG_TX(ENDP3);
      _ClearDTOG_RX(ENDP3);


    SetDeviceAddress(0);

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

如果你的设备成功枚举,这时候HID还不能动的,需要实现端点数据的上报,如下:
uint8_t USBD_ENDPx_DataUp( uint8_t endp, uint8_t *pbuf, uint16_t len )
{
        if( endp == ENDP1 )
        {
                if (USBD_Endp1_Busy)
                {
                        return USB_ERROR;
                }
                USB_SIL_Write( EP1_IN, pbuf, len );
                USBD_Endp1_Busy = 1;
                SetEPTxStatus( ENDP1, EP_TX_VALID );
               
        }
    else if( endp == ENDP2 )
        {
                if (USBD_Endp2_Busy)
                {
                        return USB_ERROR;
                }
                USB_SIL_Write( EP2_IN, pbuf, len );
                USBD_Endp2_Busy = 1;
                SetEPTxStatus( ENDP2, EP_TX_VALID );
        }
    else if( endp == ENDP3 )
    {
        if (USBD_Endp3_Busy)
        {
            return USB_ERROR;
        }
        USB_SIL_Write( EP3_IN, pbuf, len );
        USBD_Endp3_Busy = 1;
        SetEPTxStatus( ENDP3, EP_TX_VALID );
    }
        else
        {
                return USB_ERROR;
        }
        return USB_SUCCESS;
}
依葫芦画瓢,根据端点不同改一下即可实现不同的端点数据上报。
最后,希望大家样机调试成功!!



使用特权

评论回复

打赏榜单

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

沙发
yangxiaor520| | 2022-12-11 19:33 | 只看该作者
希望下次能中个开发板

使用特权

评论回复
板凳
瞎折腾| | 2022-12-12 14:20 | 只看该作者
学习了,谢谢

使用特权

评论回复
发新帖 本帖赏金 25.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

54

主题

162

帖子

7

粉丝