在進(jìn)行USB CDC類開發(fā)時(shí),無(wú)法發(fā)送64整數(shù)倍的數(shù)據(jù)
掃描二維碼
隨時(shí)隨地手機(jī)看文章
1 前言
本文將基于STM32F4DISCOVERY板,介紹如何使用USB的CDC類進(jìn)行開發(fā),以及在開發(fā)過(guò)程中碰到發(fā)送64整數(shù)倍數(shù)據(jù)時(shí)會(huì)失敗的問(wèn)題分析及解決方案。
2 硬件介紹在創(chuàng)建工程之前,我們首先即將使用的硬件進(jìn)行必要的介紹。
如上圖所示,USB電路使用PA11,PA12,全速USB OTG,當(dāng)然,這里只做device,英雌只需要看上圖的下面部分。
如上圖,本例中將使用到1個(gè)用戶按鍵,PA0,按下時(shí)為1電平。
另外,晶振使用的是外部HSE 8M晶振。
打開CubeMX(V4.17.0),創(chuàng)建一個(gè)以STM32F407VGTx的工程,使用FS-USB,并使用PA0外部輸入EXIT,如下圖所示:
使能外部HSE,使用外部8M HSE,其時(shí)鐘樹如下設(shè)置:
接下來(lái)是配置參數(shù),這里只修改USB中斷優(yōu)先級(jí)為1,而PA0的外部中斷優(yōu)先級(jí)設(shè)置為4,如下:
然后再中間件將USB class設(shè)置為Communicaiton Device Class,如下:
最后將工程的堆設(shè)為0.5K,棧設(shè)為1.5K :
最后生成一個(gè) F407_CDC_Test的工程。
我們對(duì)生成的工程不做任何修改,直接編譯后燒進(jìn)開發(fā)板后是可以被PC識(shí)別為虛擬串口的,如下圖所示:
當(dāng)然,這里的前提是必須在PC機(jī)上安裝了ST發(fā)布的虛擬串口驅(qū)動(dòng)(STSW-STM32102下載地址 :http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-utilities/stsw-stm32102.html)。
(注意:這里所說(shuō)的虛擬串口主要是指其可以被當(dāng)做串口來(lái)用,但其速度跟串口所設(shè)置的波特率完全沒有關(guān)系,用戶不要被名字所迷惑,雖然使用起來(lái)跟串口沒有區(qū)別,但其本質(zhì)還是USB,在初始化設(shè)置波特率不會(huì)對(duì)USB的通訊速率產(chǎn)生任何影響,本文檔所描述的是全速USB,因此,其最大速率就固定為12M/S,這個(gè)是由全速USB外設(shè)標(biāo)準(zhǔn)48M輸入時(shí)鐘所決定的)
此時(shí)是沒有任何具體功能的,為了更好的看到通訊的數(shù)據(jù),我們將使用串口通訊工具來(lái)進(jìn)行測(cè)試,這里我們使用的串口工具是: sscom32.
4.1 驗(yàn)證接收功能我們將使用PC串口工具SSCOM32通過(guò)USB向MCU發(fā)送數(shù)據(jù),為了能在PC端能看到MCU是否能接收到數(shù)據(jù),我們?cè)贛CU端修改代碼,讓MCU一旦接收到來(lái)自PC端的數(shù)據(jù)后,立馬返回一模一樣的數(shù)據(jù),因此需要在生成的源碼文件usbd_cdc_if.c文件中找到到函數(shù):CDC_Receive_FS(),添加處理函數(shù),如下:
staticint8_tCDC_Receive_FS(uint8_t*Buf,uint32_t*Len){/*USERCODEBEGIN6*/HanldeReceiveData(Buf,*Len);USBD_CDC_SetRxBuffer(&hUsbDeviceFS,&Buf[0]);USBD_CDC_ReceivePacket(&hUsbDeviceFS);return(USBD_OK);/*USERCODEEND6*/}/*USERCODEBEGINPRIVATE_FUNCTIONS_IMPLEMENTATION*/staticvoidHanldeReceiveData(uint8_t*Buf,uint32_tLen){CDC_Transmit_FS(Buf,Len);}/*USERCODEENDPRIVATE_FUNCTIONS_IMPLEMENTATION*/12345678910111213141516171234567891011121314151617
編譯后燒進(jìn)板子進(jìn)行驗(yàn)證:
如上如所示,串口工具能夠收到來(lái)自MCU的返回?cái)?shù)據(jù),與發(fā)送數(shù)據(jù)完全一樣,這說(shuō)明,MCU已經(jīng)接收到了PC端發(fā)送的數(shù)據(jù),另外,PC端也能接收到MCU端發(fā)送的數(shù)據(jù)。
接下來(lái)我們來(lái)通過(guò)按鍵響應(yīng)來(lái)主動(dòng)向PC端發(fā)送數(shù)據(jù),我們?cè)诎存I回調(diào)函數(shù)內(nèi)添加代碼如下:
/*USERCODEBEGIN0*/staticuint8_tSendData[256]={0};voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){uint16_ti;for(i=0;i即用戶按下按鍵,MCU則向PC端發(fā)送一次數(shù)據(jù),這里發(fā)送的是63個(gè)字節(jié),內(nèi)容為0~62,測(cè)試后PC端的串口工具完全能收到MCU端發(fā)送的63個(gè)字節(jié),如下圖所示:
圖10 PC端收到63個(gè)字節(jié)
但是當(dāng)我們將代碼修改為發(fā)送64個(gè)字節(jié)后 :
/*USERCODEBEGIN0*/staticuint8_tSendData[256]={0};voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){uint16_ti;for(i=0;i修改后進(jìn)行驗(yàn)證,發(fā)現(xiàn)PC端串口工具不能接收到數(shù)據(jù),,這里代碼基本完全沒有變化,只是發(fā)送長(zhǎng)度之前為63,這里為64,結(jié)果卻不相同,很明顯,USB CDC協(xié)議棧哪里出了問(wèn)題。
我們先使用USB分析儀對(duì)發(fā)送64個(gè)字節(jié)時(shí)進(jìn)行USB總線監(jiān)控:
圖11 USB數(shù)據(jù)監(jiān)控
如上圖,我們發(fā)現(xiàn),當(dāng)發(fā)送64個(gè)字節(jié)時(shí),由于正好是最大包長(zhǎng)(64),按USB標(biāo)準(zhǔn)來(lái)看,應(yīng)該多發(fā)一次空的transaction,但是這里,僅僅只發(fā)了一次transaction,這也就是為什么串口沒有接收到數(shù)據(jù)的原因(MCU端的USB實(shí)際上是已經(jīng)發(fā)送了64個(gè)字節(jié),但由于缺少一個(gè)transaction,因此PC端的驅(qū)動(dòng)會(huì)認(rèn)為數(shù)據(jù)格式不完整,而放棄所有已經(jīng)接收到的數(shù)據(jù),從而使上層看起來(lái)沒有接收到任何內(nèi)容)。
圖12 USB2.0標(biāo)準(zhǔn)對(duì)于最大包長(zhǎng)整數(shù)倍的要求
因此,接下來(lái)的工作就是找到USB協(xié)議棧中的相應(yīng)處理環(huán)節(jié),然后將缺少的那個(gè)空的transaction補(bǔ)上即可。4.3 USB CDC協(xié)議棧修改4.3.1 USB數(shù)據(jù)發(fā)送流程分析
在對(duì)USB CDC協(xié)議棧進(jìn)行修改之前,我們先來(lái)梳理下USB發(fā)送的流程。
發(fā)送USB數(shù)據(jù)大概過(guò)程如下:
1> 填寫DIEPTSIZ寄存器的發(fā)送包數(shù)(pakage count)和傳輸大小(transfer size)。
2> 使能發(fā)送斷點(diǎn)的發(fā)送空中斷(DIEPEMPMSK,利用發(fā)送空中斷TXFE來(lái)將發(fā)送數(shù)據(jù)填充到DFIFO)。
3> 使能中斷。
4> 后續(xù)就是中斷的事了。
后續(xù)將會(huì)有3次中斷:
1> USB_OTG_DIEPINT_TXFE中斷:在此中斷處理中,程序?qū)l(fā)送緩沖的數(shù)據(jù)分包填充到DFIFO(不能超過(guò)最大包長(zhǎng),只有最后一包數(shù)據(jù)才有可能小于最大包長(zhǎng))。
2> USB_OTG_DIEPINT_TXFE中斷: 還是TXFE中斷,上次TXFE填充的發(fā)送數(shù)據(jù)全部發(fā)送完了后,最終還是會(huì)繼續(xù)觸發(fā)TXFE中斷,也就是這次中斷,在這次FXFE中斷中禁止FXFE。也就是說(shuō),后續(xù)不會(huì)再有TXFE中斷,除非再次使能。
3> USB_OTG_DIEPINT_XFRC中斷:傳輸完成中斷,表示到這次中斷為止,傳輸完成。在這個(gè)中斷中將回調(diào)HAL_PCD_DataInStageCallback()函數(shù),就相當(dāng)于發(fā)送中斷一樣。
這就是USB數(shù)據(jù)發(fā)送的流程,這里需要注意地是,對(duì)于端點(diǎn)0和非端點(diǎn)0來(lái)說(shuō),在具體流程實(shí)現(xiàn)上還是稍微有所差異的。究其原因,主要是端點(diǎn)0和非端點(diǎn)0的DIEPTSIZ寄存器的包大小和傳輸大小位寬是不一樣的。如下圖:
圖13 端點(diǎn)0的DIEPTSIZ寄存器
圖14 端點(diǎn)1~3的DIEPTSIZ寄存器
對(duì)比上圖,端點(diǎn)0的DIEPTSIZ寄存器的XFRSIZ位寬為7,最大值為127,也就是說(shuō)最多一次只能傳輸127個(gè)字節(jié),按最大包長(zhǎng)64字節(jié)來(lái)算,就是是最多兩包數(shù)據(jù)。如果需要發(fā)送超過(guò)127個(gè)字節(jié)時(shí),又該如何做呢?查看USB協(xié)議棧內(nèi)核代碼,發(fā)現(xiàn)每次端點(diǎn)0發(fā)送數(shù)據(jù)時(shí),在發(fā)送代碼中固定每次最多可以傳輸64字節(jié),然后在傳輸完成中斷處理時(shí),再將剩下的數(shù)據(jù)接著傳輸(usb core),當(dāng)然,每次傳輸最多也是64個(gè)字節(jié),就這樣,直到發(fā)送完所有數(shù)據(jù)為止。為什么每次傳輸最大設(shè)置為64?不是XFRSIZ位寬為7,理論上可以為127嗎?我的理解是,這樣也是可以的,只要包長(zhǎng)控制在64個(gè)字節(jié)內(nèi)就可以了,至于每次傳輸多少字節(jié),只要XFRSIZ位寬夠用,你可以設(shè)置127個(gè)字節(jié)范圍內(nèi)任何數(shù)據(jù)均可。代碼中設(shè)置為64,主要為了圖方便。
但是,對(duì)于非端點(diǎn)0,XFRSIZ位寬為19位,524288個(gè)字節(jié),足夠傳輸所有實(shí)際數(shù)據(jù)了,因此,在發(fā)送代碼中,并沒有限定傳輸數(shù)據(jù)的長(zhǎng)度,在TXFE中斷中也能將所有待發(fā)送的字節(jié)填入DFIFO。但是,當(dāng)發(fā)送的數(shù)據(jù)剛好是64的整數(shù)倍時(shí),按USB標(biāo)準(zhǔn),應(yīng)該繼續(xù)發(fā)送一次空字節(jié),以表示數(shù)據(jù)全部發(fā)送完畢。
4.3.2 代碼修改對(duì)比端點(diǎn)0的處理,發(fā)現(xiàn)端點(diǎn)0在傳輸完成中斷(XFRC)中,有對(duì)這種情況的判斷,一旦檢測(cè)到這種情況,則會(huì)發(fā)送一次空傳輸。如下:
usb_core.c文件中的USBD_LL_DataInStage()函數(shù) :USBD_StatusTypeDefUSBD_LL_DataInStage(USBD_HandleTypeDef*pdev,uint8_tepnum,uint8_t*pdata){USBD_EndpointTypeDef*pep;if(epnum==0){pep=&pdev->ep_in[0];if(pdev->ep0_state==USBD_EP0_DATA_IN){if(pep->rem_length>pep->maxpacket){pep->rem_length-=pep->maxpacket;//繼續(xù)發(fā)送剩余數(shù)據(jù)USBD_CtlContinueSendData(pdev,pdata,pep->rem_length);/*Prepareendpointforprematureendoftransfer*/USBD_LL_PrepareReceive(pdev,0,NULL,0);}else{/*lastpacketisMPSmultiple,sosendZLPpacket*/if((pep->total_length%pep->maxpacket==0)&&(pep->total_length>=pep->maxpacket)&&(pep->total_lengthep0_data_len)){//再多發(fā)送一次空數(shù)據(jù)USBD_CtlContinueSendData(pdev,NULL,0);pdev->ep0_data_len=0;/*Prepareendpointforprematureendoftransfer*/USBD_LL_PrepareReceive(pdev,0,NULL,0);}else{if((pdev->pClass->EP0_TxSent!=NULL)&&(pdev->dev_state==USBD_STATE_CONFIGURED)){pdev->pClass->EP0_TxSent(pdev);}USBD_CtlReceiveStatus(pdev);}}}if(pdev->dev_test_mode==1){USBD_RunTestMode(pdev);pdev->dev_test_mode=0;}}elseif((pdev->pClass->DataIn!=NULL)&&(pdev->dev_state==USBD_STATE_CONFIGURED)){pdev->pClass->DataIn(pdev,epnum);//非0端點(diǎn)回調(diào)CDC類的DataIn()函數(shù)處理}returnUSBD_OK;}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364651234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465