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

  • Post author:
  • Post category:其他



背景:

今天要水的帖子是关于使用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请求,这是获取报表描述符的。

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;

}

依葫芦画瓢,根据端点不同改一下即可实现不同的端点数据上报。

最后,希望大家样机调试成功!!!

———————

作者:lilijin1995

链接:https://bbs.21ic.com/icview-3271062-1-1.html

来源:21ic.com

此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。