STM32 USB 問題匯總

來源:互聯網
上載者:User

匯總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檢測緩衝區長度。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.