1. 程式人生 > >STM32 USB虛擬串列埠問題彙總

STM32 USB虛擬串列埠問題彙總

彙總1:STM32的USB例程修改步驟,來自http://blog.csdn.net/cy757/archive/2010/01/01/5117610.aspx 以下是筆者將ST的Custom_HID例程修改為“自定義USB裝置”例程時總結出來的,因為筆者也是剛剛學USB開發不久,某些方面理解錯誤在所難免,請各位大蝦指正。

一、usb_desc.c檔案 根據你程式使用的通訊方式修改。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傳輸): 一、 SETUP DATA_IN(0) DATA_IN(1) DATA_IN(0) ...... STATUS_OUT(1) 二、 SETUP DATA_OUT(0) DATA_OUT(1) DATA_OUT(0) ...... STATUS_IN(1) 三、 SETUP STATUS_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檢測緩衝區長度。