新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > STM32 USB 問題匯總

        STM32 USB 問題匯總

        作者: 時間:2016-11-10 來源:網絡 收藏
        以下是筆者將ST的Custom_HID例程修改為“自定義USB設備”例程時總結出來的,因為筆者也是剛剛學USB開發不久,某些方面理解錯誤在所難免,請各位大蝦指正。

        一、usb_desc.c文件

        本文引用地址:http://www.104case.com/article/201611/317102.htm

        根據你程序使用的通信方式修改。usb_desc.h文件中定義要根據usb_desc.c文件中的數組的大小;ConfigDescriptor[SIZ_CONFIG_DESC]下添加需要處理的端點;根據需要添加或刪除報告描述符(主要用于HID)和CDC接口描述符(主要用于實現USB轉串口)等。具體方法可以下載個“電腦圈圈”使用D12編寫的例子。

        二、Usb_conf.h文件:

        1、修改需要處理那些中斷

        CNTR_CTRM 處理數據正確傳輸后控制,比如說響應主機

        CNTR_DOVRM /* DMA OVeR/underrun Mask */

        CNTR_ERRM /* ERRor Mask */

        CNTR_WKUPM 0 /* WaKe UP Mask */

        CNTR_SUSPM /* SUSPend Mask */

        CNTR_RESETM 主要處理USB復位后進行一些初始化任務

        CNTR_SOFM /* Start Of Frame Mask */

        CNTR_ESOFM /* Expected Start Of Frame Mask */

        如:

        usb_conf.h中的#define IMR_MSK (CNTR_CTRM | CNTR_SOFM | CNTR_RESETM )是決定USB_CNTR寄存器中的那個USB相關中斷啟動還是屏蔽。

        2、根據需要增加端點緩存地址,要根據緩存區的地址修改,防止數據重疊

        如下為根據每個緩沖區的大小為64字節修改:

        #define ENDP1_TXADDR (0xC0)

        #define ENDP1_RXADDR (0xD0)

        #define ENDP2_TXADDR (0x100)

        #define ENDP2_RXADDR (0x140)

        #define ENDP3_TXADDR (0x180)

        #define ENDP3_RXADDR (0x1C0)

        3、修改/* CTR service routines */下的EPX_IN_Callback和EPX_OUT_Callback。注釋掉需要處理的函數。NOP_Process表示不處理。

        三usb_prop.c文件

        1、修改void XX_Reset(void)(如:void Joystick_Reset(void))

        一般/* Initialize Endpoint 0 */的不用修改,如下為舉例說明端點1的初始化,其他端口原理一樣。

        SetEPType(ENDP1, EP_INTERRUPT);//設置端點1類型

        /*EP_BULK 批量端點

        EP_CONTROL 控制端點

        EP_ISOCHRNOUS 同步端點

        EP_INTERRUPT 中斷端點*/

        SetEPTxAddr(ENDP1, ENDP1_TXADDR); //設置端點1緩沖區基地址

        SetEPTxCount(ENDP1, 64);// 配置Tx 緩沖計數器

        SetEPRxStatus(ENDP1, EP_RX_DIS);// //設置端點接收關閉

        SetEPTxStatus(ENDP1, EP_TX_NAK);// //設置端點1發送不應答

        /*

        #define EP_RX_DIS (0x0000) // EndPoint RX DISabled 端點接收關閉

        #define EP_RX_STALL (0x1000) // EndPoint RX STALLed 端點接收延遲

        #define EP_RX_NAK (0x2000) // EndPoint RX NAKed 端點接收不應答

        #define EP_RX_VALID (0x3000) // EndPoint RX VALID端點接收有效

        #define EP_TX_DIS (0x0000) //EndPoint TX DISabled

        #define EP_TX_STALL (0x0010) // EndPoint TX STALLed

        #define EP_TX_NAK (0x0020) // EndPoint TX NAKed

        #define EP_TX_VALID (0x0030) // EndPoint TX VALID */

        2、刪除不相干的描述符等。

        如自定義的USB設備就不需要以下結構體初始化:

        ONE_DESCRIPTOR Joystick_Report_Descriptor

        ONE_DESCRIPTOR Mouse_Hid_Descriptor

        3、修改RESULT XX_Data_Setup(u8 RequestNo)的數據類請求處理。

        如Custom_HID例程修改為“自定義USB設備”例程時可以將以下代碼刪除

        if ((RequestNo == GET_DESCRIPTOR)

        && (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))

        && (pInformation->USBwIndex0 == 0))

        {

        if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)

        {

        CopyRoutine = Joystick_GetReportDescriptor;

        }

        else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)

        {

        CopyRoutine = Joystick_GetHIDDescriptor;

        }

        }

        4、刪除不相干的獲得描述符返回函數

        如自定義的USB設備就不需要以下函數:

        Joystick_GetReportDescriptor

        Joystick_GetHIDDescriptor

        四、usb_endp.c文件

        1、增加之前定義的中斷數據處理函數

        如:

        void EP1_OUT_Callback(void)

        {

        這些寫接收代碼

        }

        五、數據發送和接收,舉例說明

        1、數據接收

        u8 DataLen;

        DataLen = GetEPRxCount(ENDP1);

        PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);

        SetEPRxValid(ENDP1);

        USART1_Send(DataLen);

        count_out = 1;

        2、數據發送

        UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);

        SetEPTxCount(ENDP1, 64);

        SetEPTxValid(ENDP1);

        ===========================================================================

        匯總2:STM32 USB 程序將BULK EP改成雙緩沖機制后,一直狂飚到了1MB/S!來自:http://www.powermcu.com/bbs/viewthread.php?tid=693

        前天測試自己編寫的USB驅動程序時候發現從主機到STM32的OUT傳輸(主機到設備)速率竟然只有最高33KB/S,實在是暈死了。經過研究后發現是驅動程序中設置的PIPE MaxTransferSize參數的關系,原先設置64只能33KB/S,后參考其他USB設備驅動程序的值,設置成了65535,再測試USB OUT的速度,達到了500KB/S,終于解決了驅動程序的瓶頸。不過算下USB 2.0全速的通訊速率是12Mb/S,排除掉CRC、令牌、SOF等等開銷怎么也應該不止最大500KB/S啊。到網上看了看,基本上應該能達到600KB/S~700KB/S以上,我現在的速度應該還有很大的提升才是。
        看看程序,發現
        void EP3_OUT_Callback(void)//EP3 OUT的回調函數,當EP3接收到數據時候中斷調用該函數
        {
        count_out = GetEPRxCount(ENDP3);//獲得接收到的數據長度
        PMAToUserBufferCopy(buffer_out, ENDP3_RXADDR, count_out);//將數據從USB EP3 RX的緩沖區拷貝到用戶指定的數組中
        SetEPRxValid(ENDP3); //完成拷貝后置有效狀態,從而EP3發送ACK主機可以進行下一個數據包的發送
        }
        試著將PMAToUserBufferCopy這句注釋掉(這樣STM32就不處理接收到的數據了)后再測試速度,驚奇地發現速度竟然達到了997KB/S!晚上仔細想了想,數據肯定是要使用的,這個數據拷貝的過程的時間消費總是少不了的;由于通常情況下USB設備BULK數據接收的步驟就是:接收到數據,置NAK->將緩沖區數據拷貝到用戶區(用戶處理過程)->發ACK通知主機完成了完整的接收可以發送下一個->主機發送下一個,按照以上的步驟USB接收一步步的進行,只要STM32不完成數據處理,狀態就一直是NAK,主機就會不停地發送該數據包,浪費了帶寬,因此就會導致我上面最大速度500KB/S難以再增加的情況!不甘心啊~~
        昨天晚上又仔細研究了STM32的技術參考手冊的USB章節內容,里面提到BULK可以采用雙緩沖機制(PING-PONG)進行處理,正好可以解決上面的情況。雙緩沖機制的原理就是分配2塊接收緩沖,STM32的用戶處理和USB接口可以分別交替占用2個緩沖區,當USB端點接收數據寫其中一個緩沖區的時候,用戶的應用程序可以同時處理另一個緩沖區,這樣緩沖區依次交換占有者,只要用戶處理程序在USB端點接收的時間片段內完成處理,就能夠完全不影響USB的通訊速度!
        程序部分修改
        一、EP3_OUT的設置修改,
        //ZYP:修改EP3為BULK雙緩沖方式-------------------------
        SetEPType(ENDP3, EP_BULK);
        SetEPDoubleBuff(ENDP3);
        SetEPDblBuffAddr(ENDP3, ENDP3_BUF0Addr, ENDP3_BUF1Addr);
        SetEPDblBuffCount(ENDP3, EP_DBUF_OUT, VIRTUAL_COM_PORT_DATA_SIZE);
        ClearDTOG_RX(ENDP3);
        ClearDTOG_TX(ENDP3);
        ToggleDTOG_TX(ENDP3);
        SetEPRxStatus(ENDP3, EP_RX_VALID);
        SetEPTxStatus(ENDP3, EP_TX_DIS);
        //------------------------------------------------------
        二、EP3_OUT回調函數的修改
        void EP3_OUT_Callback(void)
        {
        //ZYP:以下是修改成EP3雙緩沖OUT后的處理函數
        if (GetENDPOINT(ENDP3) & EP_DTOG_TX)//先判斷本次接收到的數據是放在哪塊緩沖區的
        {
        FreeUserBuffer(ENDP3, EP_DBUF_OUT); //先釋放用戶對緩沖區的占有,這樣的話USB的下一個接收過程可以立刻進行,同時用戶并行進行下面處理
        count_out = GetEPDblBuf0Count(ENDP3);//讀取接收到的字節數
        PMAToUserBufferCopy(buffer_out, ENDP3_BUF0Addr, count_out);
        }
        else
        {
        FreeUserBuffer(ENDP3, EP_DBUF_OUT);
        count_out = GetEPDblBuf1Count(ENDP3);
        PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out);
        }
        }
        經過上面的修改,終于解決了STM32在處理接收數據時導致主機等待的情況,用BUS HOUND軟件測試了下

        哈哈,這下終于爽了。
        PS:上面的FreeUserBuffer(ENDP3, EP_DBUF_OUT); 這句話的上下位置是關鍵,如果放到函數的后面,則仍舊會有主機等待STM32處理數據的情況,速度仍然是500KB/S!
        把這句話放在拷貝函數的前面的話就真正把雙緩沖PING-PONG機制用起來了。大致算了下PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out);這句話當count_out為最大值64的時候STM32執行需要302個周期,72MHZ情況下約4.2微秒執行時間,而USB傳輸按照12Mb/s的線速度傳輸64字節的數據至少也得40微秒,因此只要PMAToUserBufferCopy的時間不超過40微秒,就不會導致緩沖區競爭的情況。

        ===============================================================================

        匯總3:STM32的USB中斷說明,來自:http://bbs.ednchina.com/BLOG_ARTICLE_238817.HTM

        STM32的USB模塊可以產生三種中斷:USB喚醒中斷、USB高優先級中斷和USB低優先級中斷,在STM32的參考手冊中沒有詳細說明這三種中斷對應哪些事件,現說明如下:


        1)USB喚醒中斷:在中斷向量表中的位置是42。這個中斷在USB設備從暫停模式喚醒時產生,喚醒事件由USB_ISTR寄存器的WKUP位標識。


        2)USB高優先級中斷:在中斷向量表中的位置是19。這個中斷僅由USB同步(Isochronous)模式傳輸或雙緩沖塊(Bulk)傳輸模式下的正確傳輸事件產生,正確傳輸事件由USB_ISTR寄存器的CTR位標識。


        3)USB低優先級中斷:在中斷向量表中的位置是20。這個中斷由所有其它的USB事件產生,例如正確傳輸(不包括同步模式和雙緩沖塊模式)、USB復位等,事件標志位在USB_ISTR寄存器中。


        在STM32的USB開發包的例子中包含了上述中斷的處理,例如在USB揚聲器的例子中,CTR_HP函數處理USB高優先級中斷;在所有例子中都有USB_Istr()函數處理USB低優先級中斷

        ===============================================================================

        匯總4:如何使用STM32的USB庫支持控制端點0,來自:http://bbs.ednchina.com/BLOG_ARTICLE_242276.HTM

        首先我們先回顧一下控制端點的傳輸方式:

        控制端點的傳輸有三個階段,SETUP階段、數據階段和狀態階段;數據階段又分為數據入(DATA IN)和數據出(DATA OUT),控制端點傳輸可以沒有數據階段;狀態階段有狀態入(STATUS IN)和狀態出(STATUS OUT)。

        總結起來,控制端點有如下三種可能的傳輸過程(以下括號中的0或1表示DATA0或DATA1傳輸):
        一、 SETUPDATA_IN(0)DATA_IN(1)DATA_IN(0)......STATUS_OUT(1)
        二、 SETUPDATA_OUT(0)DATA_OUT(1)DATA_OUT(0)...... STATUS_IN(1)
        三、 SETUPSTATUS_IN(1)

        這里做一個約定,把上述過程一定義為“數據入過程”,過程二定義為“數據出過程”,過程三定義為“無數據過程”。所有的USB控制端點的數據傳輸都可以而且只用這三種傳輸過程表示。HID的SET_REPORT是數據出過程,HID的GET_REPORT是數據入過程,USB的GET DEVICE DESCRIPTOR是數據入過程,USB的SET CONFIGURATION是無數據過程,等等。



        接下來,我們看看STM32的USB庫是如何處理控制端點0的傳輸。

        根據USB協議,每個SETUP包都由8個字節構成,用戶程序可以通過結構體Device_Info(類型DEVICE_INFO)訪問SETUP包的數據,因為在整個的USB處理中都要用到結構體Device_Info的內容,庫中定義了一個全局的指針pInformation指向這個結構體,用戶可以通過這個指針訪問結構體的內容。

        對應SETUP包的8個字節,用戶可以用下述方式訪問:
        pInformation->USBbmRequestType (字節類型)
        pInformation->USBbRequest(字節類型)
        pInformation->USBwValue(雙字節類型)
        pInformation->USBwIndex(雙字節類型)
        pInformation->USBwLength (雙字節類型)
        使用pInformation->USBwValue0訪問wValue的低字節,pInformation->USBwValue1訪問wValue的高字節。
        使用pInformation->USBwIndex0訪問USBwIndex的低字節,pInformation->USBwIndex1訪問USBwIndex的高字節。
        使用pInformation->USBwLength0訪問USBwLength的低字節,pInformation->USBwLength1訪問USBwLength的高字節。

        通過分析SETUP包的8個字節,可以判斷出一個SETUP的傳輸過程是屬于數據入過程、數據出過程還是無數據過程。STM32的USB庫中處理了所有的USB協議文本中定義的標準SETUP命令,對于USB協議文本中未定義的命令,USB庫按照數據入過程、數據出過程或無數據過程通過回調函數交給用戶程序處理。

        全局變量Device_Property(DEVICE_PROP類型)封裝了所有的回調函數,DEVICE_PROP定義如下:
        typedef struct _DEVICE_PROP
        {
        void (*Init)(void);// 設備初始化回調函數
        void (*Reset)(void);// USB復位回調函數

        void (*Process_Status_IN)(void);// STATUS_IN階段處理回調函數
        void (*Process_Status_OUT)(void); // STATUS_OUT階段處理回調函數

        RESULT (*Class_Data_Setup)(u8 RequestNo);//數據入/出過程處理回調函數
        RESULT (*Class_NoData_Setup)(u8 RequestNo); //無數據過程處理回調函數

        RESULT(*Class_Get_Interface_Setting)(u8 Interface, u8 AlternateSetting); // GET_INTERFACE 回調函數

        u8* (*GetDeviceDescriptor)(u16 Length); // GET_DEVICE_DESCRIPTION回調函數
        u8* (*GetConfigDescriptor)(u16 Length); // GET_CONFIGURATION_DESCRIPTION回調函數
        u8* (*GetStringDescriptor)(u16 Length); // GET_STRING_DESCRIPTION回調函數
        u8 MaxPacketSize; // 最大包長度
        } DEVICE_PROP;

        結合SETUP的三種傳輸過程,用戶通過實現不同的回調函數即可完成對各種USB類命令的處理,下面以HID的SET REPORT為例說明。




        在介紹具體實現之前,先介紹一下另一個回調函數CopyRoutine的概念,這個函數的原型是:
        u8 *CopyRoutine(u16 length);// 返回一個緩沖區指針

        USB庫通過這個函數獲得用戶的數據緩沖區地址,從而可以在數據出過程中把收到的數據拷貝到用戶緩沖區,或在數據入過程中把用戶緩沖區的數據拷貝到USB發送緩沖區。每個數據出過程可能有若干次DATA_OUT傳輸,USB庫每完成一次這樣的傳輸都會調用一次回調函數CopyRoutine,參數length是本次傳輸所收到的數據字節數目,CopyRoutine必須返回一個緩沖區指針,這個緩沖區必須能夠容納length字節的數據,CopyRoutine返回到USB庫之后,USB庫將把收到的數據拷貝到用戶指定的緩沖區。同樣每個數據入過程也可能有若干次DATA_IN傳輸,每次需要向主機傳輸數據時,USB庫都會調用一次回調函數CopyRoutine,參數length是本次傳輸所要發送的數據字節數目,CopyRoutine必須返回一個緩沖區指針,這個緩沖區中必須包含要求的數據字節,USB庫將把用戶緩沖區的數據拷貝到USB緩沖區并擇機發送出去。

        當以length=0調用CopyRoutine時,CopyRoutine需要返回用戶緩沖區的長度,因為CopyRoutine的返回類型是一個指針,所以需要通過類型的強制轉換返回緩沖區長度。這個功能是為了處理用戶緩沖區的長度與主機SETUP數據請求長度不符的情況,而不至于造成用戶緩沖區的溢出。




        介紹完上述若干概念和回調函數,再看SET_REPORT的實現就很容易了。

        SET_REPORT是一個數據出過程,因此需要實現一個Class_Data_Setup回調函數,示例如下:

        RESULT HID_Data_Setup(u8 RequestNo)
        {
        u8 *(*CopyRoutine)(u16 length);
        CopyRoutine = NULL;
        if (pInformation->USBbmRequestType == CLASS_REQUEST|INTERFACE_RECIPIENT
        && RequestNo == SET_REPORT)
        CopyRoutine = My_Data_Request;

        if (CopyRoutine == NULL)
        return USB_UNSUPPORT;

        pInformation->Ctrl_Info.CopyData = CopyRoutine;
        pInformation->Ctrl_Info.Usb_wOffset = 0;
        pInformation->Usb_wLength = (*CopyRoutine)(0);

        return USB_SUCCESS;
        } // End of HID_Data_Setup()

        u8 My_Buffer[10];
        u8 *My_Data_Request(u16 length)
        {
        if (length == 0)
        return (u8*)10;// 假定你的REPORT長度和Buffer長度為10

        return My_Buffer;
        }



        上面介紹的CopyRoutine用于把多次傳輸的數據包合并到一個完整的緩沖區中,因此只有到STATUS階段才能夠指導一次SETUP傳輸是否結束,所以用戶程序需要在回調函數Process_Status_IN中處理從SET_REPORT接收到的數據。因為所有的回調函數都是USB中斷處理的一部分,所以更好的辦法是在Process_Status_IN中設置一個標記,然后在用戶主程序中判斷這個標記并做處理。



        注意,STM32的USB庫設計成以回調函數處理用戶命令請求,包含類命令請求,是為了能夠清晰地區分庫程序和用戶程序,使這兩者不會混在一起,這樣的好處是非常明顯的,當USB庫需要更新升級時,只需替換掉相應的程序模塊,而不必修改用戶已經完成的程序。

        以上的介紹都可以在STM32 USB庫的說明手冊中找到。




        上述示意代碼是以My_Buffer長度為10字節為例,而USB庫的默認包長度為16字節,因此My_Data_Request并沒有多包的處理。

        關于多包的緩沖區處理的示意代碼可以是這樣的:

        u8 *My_Data_Request(u16 length)
        {
        if (length == 0)
        return (u8*)100;// 假定你的REPORT長度和Buffer長度為100

        return &My_Buffer[pInformation->Ctrl_Info.Usb_wOffset];
        }

        這里有一個庫中使用的變量pInformation->Ctrl_Info.Usb_wOffset,這個變量回在傳輸每個數據包時候由庫中的程序按數據包長度增加,如最大包長為16字節時,第一次調用My_Data_Request時Usb_wOffset=0,第二次調用My_Data_Request時Usb_wOffset=16,第三次調用My_Data_Request時Usb_wOffset=32,依此類推。這樣就可以使用Usb_wOffset作為My_Buffer的下標從My_Data_Request返回。




        對于提問“如何傳遞length?在上面沒有看到這個參數的傳遞過程”的回答:


        參數length是用于檢測緩沖區長度是否足夠,如果你有足夠長的緩沖區,可以不必檢測,上述示例中使用了一個固定的緩沖區,所以不必使用參數length檢測緩沖區長度。


        關鍵詞: STM32USB問題匯

        評論


        技術專區

        關閉
        主站蜘蛛池模板: 德昌县| 林周县| 莱州市| 乌鲁木齐县| 石首市| 临桂县| 犍为县| 无为县| 新田县| 柳林县| 信丰县| 汾阳市| 长岭县| 安国市| 衡山县| 普宁市| 深水埗区| 东山县| 扎鲁特旗| 呼伦贝尔市| 丰城市| 讷河市| 阿尔山市| 麻阳| 吉木萨尔县| 宜州市| 象山县| 南华县| 乌拉特中旗| 潮安县| 太仆寺旗| 连平县| 且末县| 榆树市| 松滋市| 阳西县| 湘潭县| 天峨县| 松潘县| 健康| 新和县|