背景:? ?? ?
今天要水的帖子是關于使用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 currentinterface*/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所有,任何人未經允許禁止轉載。
?
總結
以上是生活随笔為你收集整理的[单片机芯片]CH32V203的USB1 HID库调试经验分享的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。