天天看點

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

STM32的USB枚舉過程介紹

    之前的說明:

    文中大量引用網上資料,在文後已給出資料的引用說明。檔案涉及到的USB各種傳輸包各個位的含義以及USB标準裝置請求的含義都沒有做說明,推薦看《圈圈教你玩USB》裡面有詳細的說明

一、枚舉前的工作

    系統上電後,程式開始運作,簡單介紹一下USB的初始化

    根據STM32的USB庫做移植,介紹枚舉過程

    SetSystem函數是一些初始化化設定,因為我在之前已經做了初始化的操作,是以把這個函數就删了。

    首先系統執行USB中斷設定:USB_Interrupts_Config();//設定中斷向量表,設定優先級

    然後執行USB時鐘設定:Set_USBClock();//時鐘設定,USB使用48M時鐘,

    然後執行USB初始化設定:USB_Init();//這個函數比較重要

    在執行USB初始化設定主要進行結構體,與函數指針的配置

void USB_Init(void)
{
    pInformation = &Device_Info;//目前的連接配接狀态與資訊,關于Device_Info的資訊,自己go to 去檢視
    pInformation->ControlState = 2;//目前的控制狀态設定為IN_DATA
    pProperty = &Device_Property;//裝置本身支援的屬性和方法
    pUser_Standard_Requests =&User_Standard_Requests;//主機請求的實作方法
    /* Initialize devices one by one */
    pProperty->Init();//回調裝置的初始化例程
}
           

    這裡面還調用了pProperty->Init();函數進行初始化,它的函數主體是Joystick_init();函數程式執行到Joystick_init();函數,首先Get_SerialNum();擷取STM32内部唯一辨別碼。然後賦給Joystick_StringSerial,不過這個東西好像并沒有什麼用,因為後面我們在usb_desc.c檔案中給改寫了。。

    好,接下來執行PowerOn();函數,這個函數首先把D+的上拉電阻上電(我使用的是神舟IIIstm32開發闆,對應的D+上拉電阻控制位為PG11,我在之前的函數中已經進行了端口的初始化,你們不要忘記呀),這樣電腦就可以檢測到裝置了。(集線器報告裝置連接配接狀态,并收到主機指令後,會複位 USB總線,這需要一定的時間(這段時間内裝置應該準備好處理複位指令)。但是現在裝置初始化程式将繼續往下進行,因為它還沒有使能複位中斷)

    接着執行語句

<span style="font-size:10px;">  /*** CNTR_PWDN = 0 ***/
  wRegVal = CNTR_FRES;
  _SetCNTR(wRegVal);</span>
           

    這兩句話的含義實際上是使能了USB子產品電源,因為上電複位時,CNTR寄存器的斷電控制位PNWN位是1,子產品是斷電的,語句将強制複位USB子產品,直至

wInterrupt_Mask = 0;
  _SetCNTR(wInterrupt_Mask);
           

    語句清除複位信号,然後終止複位操作,這個過程中因為複位中斷允許位CNTR_RESETM沒有被使能,是以,不會觸發複位中斷,而是間接使PDWN=0,子產品開始工作。

wInterrupt_Mask =CNTR_RESETM | CNTR_SUSPM| CNTR_WKUPM;
  _SetCNTR(wInterrupt_Mask);
           

    執行語句後,複位中斷,挂起中斷,喚醒中斷被允許,而此時集線器多半已經開始複位端口了,或者說稍微有限延遲,裝置固件還能繼續初始化一些部件,但已經不會影響整個工作流程了。那麼實際上程式确實直接進入了複位中斷。

    程式直接進入了USB_LP_CAN1_RX0_IRQHandler的中斷口,執行 USB_Istr();,通過比對發現中斷源為複位中斷,程式運作到複位中斷部分,開始執行複位程式(這裡提一下,判斷發生複位中斷後,首先清中斷位。在其它幾個中斷源也是先清中斷位,但是CTR_LP();函數之前卻沒有清中斷位是為什麼呢?在STM32參考手冊中是這樣說的:端點在成功完成一次傳輸後, CTR位會被硬體置起,如果USB_CNTR上的相應位也被設定的話,就會産生中斷。與端點相關的中斷标志和USB_CNTR寄存器的CTRM位無關。這兩個中斷标志位将一直保持有效,直到應用程式清除了USB_EpnR寄存器中的相關中斷挂起位(CTR位是個隻讀位)。)

Device_Property.Reset();

這個函數指針,指向的函數是Joystick_Reset。我們跳過來繼續看。

voidJoystick_Reset(void)
{ 
  /* Set Joystick_DEVICE as not configured */
  pInformation->Current_Configuration = 0;
  pInformation->Current_Interface = 0;/*thedefault Interface*/
 
 
  /* Current Feature initialization */
  pInformation->Current_Feature =Joystick_ConfigDescriptor[7];//供電方式,總線供電,自供電
  SetBTABLE(BTABLE_ADDRESS);//設定包緩沖區位址
  /* Initialize Endpoint 0 */
  SetEPType(ENDP0, EP_CONTROL);//端點0為控制端點
  SetEPTxStatus(ENDP0, EP_TX_STALL);//端點狀态為發送無效,也就是主機IN令牌包來的時候,回送一個STALL
  SetEPRxAddr(ENDP0, ENDP0_RXADDR);//設定端點0的描述符表,包括接收緩沖區位址,最大允許接收的位元組數、發送緩沖區位址三個量。
  SetEPTxAddr(ENDP0, ENDP0_TXADDR);//發送緩沖區位址
  Clear_Status_Out(ENDP0);///清除EP_KIND的STATUS_OUT位,如果改位被設定, 在控制模式下隻對0位元組資料包相應。其它的都傳回STALL。主要用于控制傳輸的狀态過程
  SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);///接收緩沖區支援64個位元組
  SetEPRxValid(ENDP0);//使能端點0的接收,因為很快就要接收SETUP令牌包了
 
  /* Initialize Endpoint 1 */
  SetEPType(ENDP1, EP_INTERRUPT);//端點1設為中斷端點
  SetEPTxAddr(ENDP1, ENDP1_TXADDR);//設定發送緩沖區位址
  SetEPTxCount(ENDP1, 4);//每次發送4個位元組
  SetEPRxStatus(ENDP1, EP_RX_DIS);///接收禁止,隻發送 Mouse 資訊,而不從主機接收
  SetEPTxStatus(ENDP1, EP_TX_NAK);///現在發送端點還不允許發送資料
 
  /* Set this device to response on defaultaddress */
  SetDeviceAddress(0);//址預設為 0.
  bDeviceState = ATTACHED;//接狀态改為已經連接配接,預設位址狀态
}
           

    這個複位的過程我們用一句話總結就是端點初始化。至此RESET中斷處理完成,因為我們也沒有設定RESET_CALLBACK,是以也不會有RESET_Callback();,不過它本身也是個空函數。在執行完複位函數後PC指針又回到main函數中來,Joystick_init函數往下執行, USB_SIL_Init();函數中使能所有常用中斷前面說道,當開複位中斷後,程式進入中斷,那麼複位中斷執行完成以後,真正的枚舉的過程(擷取描述符)并沒有開始。而是有一段空餘的時間,這段時間内,程式執行了上面的提到的USB_SIL_Init();直至USB_Init();函數結束。那麼真正的USB枚舉過程都是在中斷中完成。

    通過我調試發現,在完成複位中斷後,主機好像并沒有發送擷取描述符的指令,在這個過程中,系統兩次進入挂起中斷,然後觸發喚醒中斷,然後複位中斷。我個人的了解是,這段時間因為stm32沒有收到sof,所有觸發挂起中斷,當裝置發送擷取裝置描述符指令時,觸發喚醒中斷,在喚醒中斷中,系統設定恢複正常工作。至于最後的複位請求,我表示我也不知道了。。。(這裡需要說一下,在移植的挂起中斷處理程式中,針對stm32部分也做了挂起處理,我覺得(實際上是我看以前的前輩們都覺得)這樣做沒必要。是以,隻保留了USB挂起,而對整個stm32的挂起被屏蔽掉了。)

(此時pInformation->ControlState=IN_DATA, bDeviceState=UNCONNECTED)

二、枚舉過程

    枚舉過程就是USB裝置與主機通訊擷取裝置的一系列描述符的過程。我們貼圖了解

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

    據說這是圈圈大神整理的枚舉過程圖,接下來我們來一步一步的看枚舉的過程。整個枚舉的過程以我實際調試的結果為準。

1、擷取裝置描述符

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

注:packet包是實際傳輸時的資料包。

    首先是建立連接配接階段,在經過漫長的等待以後,主機發出對位址0、端點0發出SETUP令牌包和資料包,然後裝置傳回ACK包。點0寄存器的CTR_RX被置位為1, ISTR的CTR置位為1, DIR=1, EP_ID=0,表示端點0接收到主機來的請求資料。此時裝置已經ACK主機,将觸發正确傳輸完成中斷。程式進入CTR_LP();中斷,我們來詳細看一下

void CTR_LP(void)
{
  __IO uint16_t wEPVal = 0;
       
  /* stay in loop while pending interrupts */
  while (((wIstr = _GetISTR()) & ISTR_CTR)!= 0)
  {
    /* extract highest priority endpoint number*/
   EPindex = (uint8_t)(wIstr & ISTR_EP_ID);//判斷讀取端點号
    if (EPindex == 0)
    {    
             SaveRState = _GetENDPOINT(ENDP0);//USB端點0寄存器  USB_EP0R中的資料
             SaveTState = SaveRState & EPTX_STAT;//擷取發送狀态碼
             SaveRState &=  EPRX_STAT; 
 
             _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);//将TXRX的狀态都改為NAK
 
      if ((wIstr & ISTR_DIR) == 0)//判斷是IN包
      {
 
        _ClearEP_CTR_TX(ENDP0);
        In0_Process();
           _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
                      return;
      }
     else//判斷是OUT包或SETUP包
      {
        wEPVal = _GetENDPOINT(ENDP0);
       
       if ((wEPVal &EP_SETUP) != 0)//判斷是SETUP包
        {
         Setup0_Process();
         _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
          return;
        }
        else if ((wEPVal & EP_CTR_RX) !=0)//判斷是OUT包
        {
          _ClearEP_CTR_RX(ENDP0);
          Out0_Process();  
 _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
          return;
        }
      }
    }/* if(EPindex == 0) */
    else//判斷是非0端點包
    {
    }/*if(EPindex == 0) else */
  }/* while(...) */
}
           

    其實以上這些判斷在實際的USB協定中,都在令牌包中都有展現,但是STM32的USB硬體都幫我們分析了,這些分析的結果以寄存器的形式顯示可以看到程式跳轉執行Setup0_Process();我們跟蹤過來繼續看它的跟蹤過程

uint8_tSetup0_Process(void)
{
 
  union
  {
    uint8_t* b;
    uint16_t* w;
  } pBuf;
  uint16_t offset = 1;
   
  pBuf.b = PMAAddr + (uint8_t*)(_GetEPRxAddr(ENDP0) * 2); /* *2 for 32 bits addr 要讀取資料的位址*/
 
  if (pInformation->ControlState!= PAUSE)
  {
    pInformation->USBbmRequestType =*pBuf.b++; /* bmRequestType */
    pInformation->USBbRequest = *pBuf.b++;/* bRequest */
    pBuf.w += offset;  /* word not accessed because of 32 bitsaddressing */
    pInformation->USBwValue =ByteSwap(*pBuf.w++); /* wValue */
    pBuf.w += offset;  /* word not accessed because of 32 bitsaddressing */
    pInformation->USBwIndex  = ByteSwap(*pBuf.w++); /* wIndex */
    pBuf.w += offset;  /* word not accessed because of 32 bitsaddressing */
    pInformation->USBwLength = *pBuf.w; /*wLength */
  }//将USB标準裝置請求賦給對應的結構體
 
 
  pInformation->ControlState = SETTING_UP;
  if (pInformation->USBwLength == 0)
  {
    /* Setup with no data stage */
    NoData_Setup0();
  }
 <span style="color:#ff0000;">else</span>
  {
    /* Setup with data stage */
   Data_Setup0();
  }
  return Post0_Process();
}
           

    因為這個标準裝置請求是來要求裝置傳回裝置描述符的,需要傳回資料,是以程式會跳轉到Data_Setup0();執行在這個函數裡分析要傳回的資料。

voidData_Setup0(void)
{
  uint32_t Request_No =pInformation->USBbRequest;//擷取請求代碼
 
  /*GET DESCRIPTOR*/
  if (<spanstyle="color:#ff0000;">Request_No ==GET_DESCRIPTOR</span>)//判斷為擷取裝置描述符
  {
    if(<span style="color:#ff0000;">Type_Recipient ==(STANDARD_REQUEST | DEVICE_RECIPIENT)</span>)//标準請求
    {
      uint8_t wValue1 =pInformation->USBwValue1;
      if (<spanstyle="color:#ff0000;">wValue1 ==DEVICE_DESCRIPTOR</span>)//判斷是擷取裝置描述符
      {
        <spanstyle="color:#ff0000;">CopyRoutine =pProperty->GetDeviceDescriptor;//複制資料到CopyRoutine</span>
      }
    }
  }
 
  if (<spanstyle="color:#ff0000;">CopyRoutine</span>)//有資料将一些資訊寫入控制結構體
  {
    pInformation->Ctrl_Info.Usb_wOffset =wOffset;
    pInformation->Ctrl_Info.CopyData =CopyRoutine;
    /* sb in the original the cast to word wasdirectly */
    /* now the cast is made step by step */
    (*CopyRoutine)(0);//這個函數這裡調用的目的隻是設定了 pInformation中需要寫入的描述符的長度
 
    Result = USB_SUCCESS;
  }
 
  if (<spanstyle="color:#ff0000;">ValBit(pInformation->USBbmRequestType,7)</span>)//判斷是從裝置到主機
  {
    /* Device ==> Host */
    __IO uint32_t wLength =pInformation->USBwLength;
    
    /* Restrict the data length to be the onehost asks for */
    if (pInformation->Ctrl_Info.Usb_wLength> wLength)
    {//裝置描述符長度為18
      pInformation->Ctrl_Info.Usb_wLength =wLength;
    }  
    pInformation->Ctrl_Info.PacketSize =pProperty->MaxPacketSize;   
    <spanstyle="color:#ff0000;">DataStageIn();</span>//完成描述符的輸出準備
  }
  return;
}
           

    最後的重點落在 DataStageIn();函數上,我們來看一下

voidDataStageIn(void)
{
  ENDPOINT_INFO *pEPinfo =&pInformation->Ctrl_Info;
  uint32_t save_wLength =pEPinfo->Usb_wLength;//還需要發送多少位元組
  uint32_t ControlState =pInformation->ControlState;
  DataBuffer= (*pEPinfo->CopyData)(Length);//取得要發送的資料裝置描述符
  UserToPMABufferCopy(DataBuffer,GetEPTxAddr(ENDP0), Length);将裝置描述符複制到使用者的發送緩沖區
  SetEPTxCount(ENDP0, Length);//設定發送位元組數目
 
  pEPinfo->Usb_wLength -= Length;//等于0
  pEPinfo->Usb_wOffset += Length;//偏移到18
  vSetEPTxStatus(EP_TX_VALID);///使能端點發送,隻要主機的 IN令牌包一來,SIE就會将描述符傳回給主機
  USB_StatusOut();/* 這個實際上是使接收也有效,主機可取消 IN
Expect_Status_Out:
    pInformation->ControlState =ControlState;
}
           

    這一次的CTR中斷就執行完了,就等着,主機再發IN包把資料讀回去。

    接下來就是可選的資料傳輸階段。主機首先發一個 IN令牌包,由于端點0發送有效, SIE将資料傳回主機。主機方傳回一個 ACK後,主機發送資料的 CTR标志置位, DIR=0, EP_ID=0,表明主機正确收到了使用者發過去的描述符。固件程式由此進入IN中斷。是以接下來進入用 In0_Process()進行處理。但是此時裝置描述符傳回成功了。同樣的在用In0_Process()函數中,其目的隻是期待主機的0狀态位元組輸出了。

uint8_tIn0_Process(void)
{
  uint32_t ControlState =pInformation->ControlState;
   
  if ((ControlState == IN_DATA) || (<spanstyle="color:#ff0000;">ControlState == LAST_IN_DATA</span>))
  {
    <span style="color:#ff0000;">DataStageIn();//此次調用後,目前狀态變成WAIT_STATUS_OUT,表明裝置等待狀态過程,主機輸出0位元組</span>
    /* ControlState may be changed outside thefunction */
    ControlState =pInformation->ControlState;
  }
  else if (ControlState == WAIT_STATUS_IN)
  {
  } 
  else
  {
    ControlState = STALLED;
  }
  pInformation->ControlState = ControlState;
  return Post0_Process();
}
           

    我們進入DataStageIn();函數

voidDataStageIn(void)
{
  ENDPOINT_INFO *pEPinfo =&pInformation->Ctrl_Info;
  uint32_t save_wLength = pEPinfo->Usb_wLength;
  uint32_t ControlState =pInformation->ControlState;
 
  uint8_t *DataBuffer;
  uint32_t Length;
    
  if ((save_wLength == 0) && (<spanstyle="color:#ff0000;">ControlState == LAST_IN_DATA</span>))
  {
    if(Data_Mul_MaxPacketSize == TRUE)
    {
      /* No more data to send and empty packet*/
      Send0LengthData();
      ControlState = LAST_IN_DATA;
      Data_Mul_MaxPacketSize = FALSE;
    }
   <spanstyle="color:#ff0000;"> else </span>
    {
      /* No more data to send so STALL the TXStatus*/
      ControlState = WAIT_STATUS_OUT;
      vSetEPTxStatus(EP_TX_STALL);
    }   
    <spanstyle="color:#ff0000;">goto Expect_Status_Out;</span>
  }
  Length = pEPinfo->PacketSize;
  ControlState = (save_wLength <= Length) ?LAST_IN_DATA : IN_DATA;
 
  if (Length > save_wLength)
  {
    Length = save_wLength;
  }
 
  DataBuffer = (*pEPinfo->CopyData)(Length);

  UserToPMABufferCopy(DataBuffer,GetEPTxAddr(ENDP0), Length);
 
  SetEPTxCount(ENDP0, Length);
  pEPinfo->Usb_wLength -= Length;
  pEPinfo->Usb_wOffset += Length;
  vSetEPTxStatus(EP_TX_VALID);
  USB_StatusOut();/* Expect the host to abortthe data IN stage */
 
Expect_Status_Out:
    pInformation->ControlState =ControlState;  
}
           

    主機收到18個位元組的描述符後,進入狀态事務過程,此過程的主句發送令牌包為OUT,和0位元組的資料包,裝置需要回一個ACK。中斷處理程式會進入Out0_Process()。由于此時狀态為WAIT_STATUS_OUT,是以執行以下這段

<spanstyle="font-size:10px;">uint8_t Out0_Process(void)
{
  uint32_t ControlState =pInformation->ControlState;
 
  if ((ControlState == IN_DATA) ||(ControlState == LAST_IN_DATA))
  {
  }
  else if ((ControlState == OUT_DATA) ||(ControlState == LAST_OUT_DATA))
  {
  }
 
  else if (<spanstyle="color:#ff0000;">ControlState ==WAIT_STATUS_OUT</span>)
  {
    (*pProperty->Process_Status_OUT)();//空函數什麼也沒幹
   <spanstyle="color:#ff0000;"> ControlState = STALLED;</span>
  }
  /* Unexpect state, STALL the endpoint */
  else
  {
    ControlState = STALLED;
  }
  pInformation->ControlState = ControlState;
 
  return Post0_Process();
}</span>
           

2、主機發送複位指令

    擷取裝置描述符後,主機再一次複位裝置。裝置又進入初始狀态

3、設定位址

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

    1)重新從複位狀态開始

    在第一次擷取裝置描述符後,程式使端點 0 的發送和接收都無效,狀态也設定為STALLED,是以主機先發一個複位,使得端點 0接收有效。 雖然說在 NAK和STALL狀态下,端點仍然可以響應和接收 SETUP包。

    2)設定位址的建立階段:

    主機先發一個 SETUP令牌包,裝置端 EP0的 SETUP标志置位。然後主機發了一個OUT 包,共 8個位元組,裡面包含設定位址的要求(USB标準裝置請求)。裝置在檢驗資料後,發一個 ACK握手包。同時 CTR_RX 置位,CTR置位。資料已經儲存到 RxADDR 所指向的緩沖區。此時 USB産生資料接收中斷。由于 CTR_RX和 SETUP同時置位, 終端處理程式調用 Setup0_Process(),所做的工作仍然是先填充 pInformation結構, 擷取請求特征碼、 請求代碼和資料長度。由于設定位址不會攜帶資料,是以接下來調用 NoData_Setup0()。執行以下代碼:

void NoData_Setup0(void)
{
 RESULT Result = USB_UNSUPPORT;
 uint32_t RequestNo = pInformation->USBbRequest;
 uint32_t ControlState;
   
 if (Type_Recipient == (STANDARD_REQUEST |DEVICE_RECIPIENT))
 {
    if (RequestNo == SET_CONFIGURATION)
    {
    }
 
    /*SET ADDRESS*/
    else if (RequestNo== SET_ADDRESS)
    {
      if ((pInformation->USBwValue0 > 127)|| (pInformation->USBwValue1 != 0)
          || (pInformation->USBwIndex != 0)
          ||(pInformation->Current_Configuration != 0))
        /* Device Address should be 127 orless*/
      {
        ControlState = STALLED;
        goto exit_NoData_Setup0;
      }
     else
      {
       Result =USB_SUCCESS;//說明設定位址沒有做任何工作
      }
    }
    /*SET FEATURE for Device*/
    else if (RequestNo == SET_FEATURE)
    {
    }
    /*Clear FEATURE for Device */
    else if (RequestNo == CLEAR_FEATURE)
    {
    }
 }
 
 /* Interface Request*/
 else if (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
 {
 }
 
 /* EndPoint Request*/
 else if (Type_Recipient == (STANDARD_REQUEST | ENDPOINT_RECIPIENT))
 {
 }
 else
 {
    Result = USB_UNSUPPORT;
 }
 if (Result != USB_SUCCESS)
 { 
 }
 
 if (Result != USB_SUCCESS)
 {
 }
 ControlState = WAIT_STATUS_IN;/* After nodata stage SETUP */
 USB_StatusIn();
exit_NoData_Setup0:
  pInformation->ControlState = ControlState;
  return;
}
           

    USB_StatusIn(); //這句話是一個關鍵,它是一個宏,實際是準備好發送 0 位元組的狀态資料包。 因為位址設定沒有資料過程, 建立階段後直接進入狀态階段,主機發 IN令牌包,裝置傳回 0 位元組資料包,主機再 ACK。

    它對應的宏是這樣的:

#define USB_StatusIn() Send0LengthData() //準備發送 0 位元組資料

#define Send0LengthData() { _SetEPTxCount(ENDP0, 0); \

vSetEPTxStatus(EP_TX_VALID); \ //設定發送有效,發送位元組數為 0}

    3)設定位址的狀态階段:

    而前面把狀态設定為 WAIT_STATUS_IN是給 IN令牌包的處理提供訓示。因為建立階段結束以後, 主機接着發一個 IN 令牌包, 裝置傳回 0 位元組資料包,主機傳回ACK包,進入中斷。本次中斷由 IN0_Process()函數來處理,追蹤進入,它執行以下代碼:

uint8_t In0_Process(void)
{
 uint32_t ControlState = pInformation->ControlState;
   
 if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))
 {
 }
 
 else if (ControlState == WAIT_STATUS_IN)
 {
    if ((pInformation->USBbRequest== SET_ADDRESS) &&
        (Type_Recipient == (STANDARD_REQUEST |DEVICE_RECIPIENT)))
    {     
     SetDeviceAddress(pInformation->USBwValue0);
     pUser_Standard_Requests->User_SetDeviceAddress();//這個函數就一個指派語句, bDeviceState = ADDRESSED。
    }
    (*pProperty->Process_Status_IN)();
    ControlState = STALLED;
 }
 
 else
 {
    ControlState = STALLED;
 }
 
  pInformation->ControlState = ControlState;
  return Post0_Process();
}
           

    執行設定位址操作、 采用新位址後, 把裝置的狀态改為 STALLED。 而在處理的出口中調用 Post0_Process()函數,這個所做的工作是:

SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);//将端點 0 的緩沖區大小設定為 64位元組
if (pInformation->ControlState== STALLED)
{
 vSetEPRxStatus(EP_RX_STALL);
 vSetEPTxStatus(EP_TX_STALL);
}//将端點 0 的發送和接收都設定為: STALL,這種狀态下隻接受 SETUP 令牌包
           

4、從新位址擷取裝置描述符

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

因為過程類似,這裡就簡單說一下

    1)上一階段末尾的狀态

    端點 0的發送和接收都設定為: STALL,隻接收 SETUP令牌包。

    2)建立階段:主機發令牌包、資料包、裝置回應ACK

    産生資料接收中斷,且端點 0 的SETUP 置位,調用 Setup0_Process() 函數進行處理。在Setup0_Process()中,因為主機發送了請求資料 8 個位元組。由調用Data_Setup0()函數進行處理。 首先是擷取裝置描述符的長度,描述符的起始位址,傳送的最大位元組數,根據這些參數确定本次能夠傳輸的位元組數,然後調用DataStageIn()函數進行實際的資料傳輸操作,裝置描述符必須在本次中斷中就寫入發送緩沖區,因為很快就要進入資料階段了。在函數處理的最後:

vSetEPTxStatus(EP_TX_VALID);
USB_StatusOut();/* 本來期待 IN令牌包,但使用者可以取消資料階段,一般不會用到 */
           

    3)資料階段:主機發IN包,裝置傳回資料,主機發ACK

    本次操作會産生資料發送完成中斷, 由 In0_Process(void)來進行中斷, 它也調用DataStageIn()函數來進行處理。如果資料已經發送完:

ControlState = WAIT_STATUS_OUT;
vSetEPTxStatus(EP_TX_STALL);//轉入狀态階段。
           

有可能的話:

Send0LengthData();
ControlState = LAST_IN_DATA;
Data_Mul_MaxPacketSize = FALSE;//這一次發送 0 個位元組,狀态轉為最後輸入階段。
           

否則,繼續準備資料,調整剩餘位元組數、發送指針位置,等待主機的下一個 IN令牌包。

    4)狀态階段:主機發OUT包、 0位元組包,裝置發送ACK

    資料發送完成中斷, 調用 Out0_Process(void)函數進行處理,由于在資料階段的末尾已經設定裝置狀态為: WAIT_STATUS_OUT, 是以處理函數基本上沒有做什麼事,就退出了。并将狀态設為 STALLED

5、擷取配置描述符

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

    以後的過程大概類似就不做詳細說明了。隻做幾點說明。按照網上的說法這次其實隻是擷取9位元組的配置描述符,然後通過擷取配置描述符的第二個位元組(從0開始算)得知整個描述符集合(配置描述符、接口描述符、HID描述符(最後兩個位元組為下級描述符的長度(報告描述符))、端點描述符)共有多少個位元組。然後擷取整個描述符的集合。但是我在自己調試的時候,發現USB直接擷取了整個描述符集合的資料。不知到為啥?

6、擷取字元串描述符。

    通過實際調試發現,擷取三次字元串描述符,分别是,主機擷取字元串序号描述符、主機擷取字元串ID描述符、主機擷取字元串産品描述符。

7、主機再次擷取裝置描述符和配置描述符集合

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

    在這一步驟,主機進行了三次USB标準裝置請求,分别為擷取裝置描述符、擷取配置描述符、擷取配置描述符集合。

8、主機設定配置

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

    建立階段:主機發 SETUP包、發請求資料包( DATA0 包)、使用者發 ACK。

    進入 CTR中斷,使用者調用 Setup0_Process()函數進行處理,取得請求資料後,由于沒有資料傳輸階段,該函數調用NoData_Setup0()函數進行處理。判斷為設定配置後, 調用Standard_SetInterface()函數将裝置狀态結構體的目前配置改為主機資料中的配置參數。同時調用使用者的設定配置函數,将裝置狀态改為“ configured” 。退出時,将控制傳輸狀态改為:ControlState= WAIT_STATUS_IN,進入狀态階段。

    裝置期待主機的IN令牌包,傳回狀态資料。狀态階段:主機發IN令牌、 裝置傳回0Setup0_Process()函數進行處理,取得請求資料後,由于沒有資料傳輸階段,該函數調用 NoData_Setup0()函數進行處理。設定空閑時一個類特殊請求, 其特征碼為0x21, 2表示類請求而不是标準請求,1表示接收對象是接口而不是裝置。USB的底層并不支援類特殊請求,它将調用上層函數提供的函數:

if (Result !=USB_SUCCESS)
{
  Result =(*pProperty->Class_NoData_Setup)(RequestNo); //這裡就是調用使用者提供的類特殊請求的處理函數。 結果發現使用者       提供的類特殊請求(針對無資料情況)隻支援SET_PROTOCOL。針對有資料情況隻支援: GET_PROTOCOL。
  if ((Type_Recipient==(CLASS_REQUEST |INTERFACE_RECIPIENT))&& (RequestNo == SET_PROTOCOL)
  {
    return Joystick_SetProtocol();
  }
}
           

9、主機擷取報告描述符

    建立階段:主機發SETUP包、發請求資料包( DATA0包)、使用者ACK。進入CTR中斷,擷取描述符是一個标準請求,但是報告描述符并不是需要通用實作的,是以在底層函數中沒有實作。跟蹤Setup0_Process(void)——進入Data_Setup(void)函數,它是這麼處理的:

void Data_Setup0(void)
{
 uint8_t *(*CopyRoutine)(uint16_t);
 RESULT Result;
 uint32_t Request_No = pInformation->USBbRequest;
 
 uint32_t Related_Endpoint, Reserved;
 uint32_t wOffset, Status;
 
 CopyRoutine = NULL;
 wOffset = 0;
 
 /*GET DESCRIPTOR*/
 if (Request_No == GET_DESCRIPTOR)
 {
    if (Type_Recipient== (STANDARD_REQUEST | DEVICE_RECIPIENT))
    {//在這裡面并沒有獲得有用的資訊因為它并不是一個标準裝置請求
      uint8_t wValue1 =pInformation->USBwValue1;
      if (wValue1 == DEVICE_DESCRIPTOR)
      {
        CopyRoutine =pProperty->GetDeviceDescriptor;
      }
     else if (wValue1 ==CONFIG_DESCRIPTOR)
      {
        CopyRoutine =pProperty->GetConfigDescriptor;
      }
      else if (wValue1 == STRING_DESCRIPTOR)
      {
        CopyRoutine =pProperty->GetStringDescriptor;
      } /* End of GET_DESCRIPTOR */
    }
 }
 
 /*GET STATUS*/
 else if ((Request_No == GET_STATUS) &&(pInformation->USBwValue == 0)
           &&(pInformation->USBwLength == 0x0002)
           &&(pInformation->USBwIndex1 == 0))
 {
    /* GET STATUS for Device*/
    if ((Type_Recipient == (STANDARD_REQUEST |DEVICE_RECIPIENT))
        && (pInformation->USBwIndex== 0))
    {
      CopyRoutine = Standard_GetStatus;
    }
 
    /* GET STATUS for Interface*/
    else if (Type_Recipient ==(STANDARD_REQUEST | INTERFACE_RECIPIENT))
    {
      if(((*pProperty->Class_Get_Interface_Setting)(pInformation->USBwIndex0, 0)== USB_SUCCESS)
          &&(pInformation->Current_Configuration != 0))
      {
        CopyRoutine = Standard_GetStatus;
      }
    }
 
    /* GET STATUS for EndPoint*/
    else if (Type_Recipient ==(STANDARD_REQUEST | ENDPOINT_RECIPIENT))
    {
      Related_Endpoint =(pInformation->USBwIndex0 & 0x0f);
      Reserved = pInformation->USBwIndex0& 0x70;
 
      if (ValBit(pInformation->USBwIndex0,7))
      {
        /*Get Status of endpoint & stallthe request if the related_ENdpoint
        is Disabled*/
        Status =_GetEPTxStatus(Related_Endpoint);
      }
      else
      {
        Status =_GetEPRxStatus(Related_Endpoint);
      }
 
      if ((Related_Endpoint <Device_Table.Total_Endpoint) && (Reserved == 0)
          && (Status != 0))
      {
        CopyRoutine = Standard_GetStatus;
      }
    }
 
 }
 
 /*GET CONFIGURATION*/
 else if (Request_No == GET_CONFIGURATION)
 {
    if (Type_Recipient == (STANDARD_REQUEST |DEVICE_RECIPIENT))
    {
      CopyRoutine = Standard_GetConfiguration;
    }
 }
 /*GET INTERFACE*/
 else if (Request_No == GET_INTERFACE)
 {
    if ((Type_Recipient == (STANDARD_REQUEST |INTERFACE_RECIPIENT))
        && (pInformation->Current_Configuration!= 0) && (pInformation->USBwValue == 0)
        && (pInformation->USBwIndex1== 0) && (pInformation->USBwLength == 0x0001)
        &&((*pProperty->Class_Get_Interface_Setting)(pInformation->USBwIndex0, 0)== USB_SUCCESS))
    {      
      CopyRoutine = Standard_GetInterface;
    }
 
 }
 
 if (CopyRoutine)
 {
    pInformation->Ctrl_Info.Usb_wOffset =wOffset;
    pInformation->Ctrl_Info.CopyData =CopyRoutine;
    /* sb in the original the cast to word wasdirectly */
    /* now the cast is made step by step */
    (*CopyRoutine)(0);///這個函數這裡調用的目的隻是設定了 pInformation中需要寫入的描述符的長度
    Result = USB_SUCCESS;
 }
 else//最終通過調用用火的類特殊實作來擷取報告描述符
 {
   Result =(*pProperty->Class_Data_Setup)(pInformation->USBbRequest);
    if (Result == USB_NOT_READY)
    {
      pInformation->ControlState = PAUSE;
      return;
    }
 }
 
 if (pInformation->Ctrl_Info.Usb_wLength == 0xFFFF)
 {
    /* Data is not ready, wait it */
    pInformation->ControlState = PAUSE;
    return;
 }
 if ((Result == USB_UNSUPPORT) || (pInformation->Ctrl_Info.Usb_wLength== 0))
 {
    /* Unsupported request */
    pInformation->ControlState = STALLED;
    return;
 }
 
 
 if (ValBit(pInformation->USBbmRequestType, 7))//裝置到主機
 {
    /* Device ==> Host */
    __IO uint32_t wLength =pInformation->USBwLength;
    
    /* Restrict the data length to be the onehost asks for */
    if (pInformation->Ctrl_Info.Usb_wLength> wLength)
    {
      pInformation->Ctrl_Info.Usb_wLength =wLength;
    }
   
    else if(pInformation->Ctrl_Info.Usb_wLength < pInformation->USBwLength)
    {
      if(pInformation->Ctrl_Info.Usb_wLength < pProperty->MaxPacketSize)
      {
        Data_Mul_MaxPacketSize = FALSE;
      }
      else if((pInformation->Ctrl_Info.Usb_wLength % pProperty->MaxPacketSize) == 0)
      {
        Data_Mul_MaxPacketSize = TRUE;
      }
    }  
 
    pInformation->Ctrl_Info.PacketSize =pProperty->MaxPacketSize;
   
    DataStageIn();
 }
 else
 {
    pInformation->ControlState = OUT_DATA;
   vSetEPRxStatus(EP_RX_VALID); /*enable for next data reception */
 }
 
 return;
}
           

    可見核心函數隻支援裝置描述符、配置描述符以及字元串描述符。 最終該函數将調用:

Result= (*pProperty->Class_Data_Setup)(pInformation->USBbRequest);

    調用使用者的類特殊實作來擷取報告描述符,它的函數本體是Joystick_Data_Setup函數,同時 HID 類描述符也是通過這種方式取得的,我們來看一下

RESULTJoystick_Data_Setup(uint8_t RequestNo)
{
 uint8_t *(*CopyRoutine)(uint16_t);
 
 CopyRoutine = NULL;
 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;
   }
 
 } /* End of GET_DESCRIPTOR */
 
 /*** GET_PROTOCOL ***/
 else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
           && RequestNo ==GET_PROTOCOL)
 {
    CopyRoutine = Joystick_GetProtocolValue;
 }
 if (CopyRoutine == NULL)
 {
    return USB_UNSUPPORT;
 }
  pInformation->Ctrl_Info.CopyData =CopyRoutine;
  pInformation->Ctrl_Info.Usb_wOffset = 0;
 (*CopyRoutine)(0);
 return USB_SUCCESS;
}
           

最後通過DataStageIn做發送準備。主機發送IN令牌包時,資料就被發送出去了

下面貼一下我自己調試時,發送的一些序列槽的資料。

usb觸摸滑鼠。
RESET中斷。
SUSP中斷。
SUSP中斷。
WKUP中斷。
RESET中斷。
标準請求:
0x80 0x6 0x0 0x1 0x0 0x0 0x00x40//主機擷取裝置描述符
發送資料:
0x12 0x1 0x0 0x2 0x0 0x0 0x0 0x400x88 0x88 0x3 0x0 0x0 0x2 0x1 0x2 0x3 0x1//裝置發送裝置描述符
RESET中斷。
标準請求:
0x0 0x5 0x2 0x0 0x0 0x0 0x0 0x0//主機設定位址
标準請求:
0x80 0x6 0x0 0x1 0x0 0x0 0x00x12//主機擷取裝置描述符
發送資料:
0x12 0x1 0x0 0x2 0x0 0x0 0x0 0x400x88 0x88 0x3 0x0 0x0 0x2 0x1 0x2 0x3 0x1//裝置發送裝置描述符
标準請求:
0x80 0x6 0x0 0x2 0x0 0x0 0x00xff//主機擷取配置描述符
發送資料:
0x9 0x2 0x22 0x0 0x1 0x1 0x0 0xa00x32 0x9 0x4 0x0 0x0 0x1 0x3 0x1 0x2 0x0 0x9 0x21 0x10 0x1 0x0 0x1 0x22 0x4a0x0 0x7 0x5 0x81 0x3 0x4 0x0 0x20//裝置發送配置描述符
标準請求:
0x80 0x6 0x3 0x3 0x9 0x4 0x00xff//主機擷取字元串序号描述符
發送資料:
0x1a 0x3 0x34 0x0 0xff 0x0 0xd40x0 0x5 0x0 0x33 0x0 0x46 0x0 0x34 0x0 0x30 0x0 0x35 0x0 0x83 0x0 0x12 0x0 0x430x0//裝置發送字元串序号描述符
标準請求:
0x80 0x6 0x0 0x3 0x0 0x0 0x00xff//主機擷取字元串ID描述符
發送資料:
0x4 0x3 0x9 0x4 //裝置發送字元串ID描述符
标準請求:
0x80 0x6 0x2 0x3 0x9 0x4 0x00xff//主機擷取字元串産品描述符
發送資料:
0x1e 0x3 0x53 0x0 0x54 0x0 0x4d0x0 0x33 0x0 0x32 0x0 0x20 0x0 0x4a 0x0 0x6f 0x0 0x79 0x0 0x73 0x0 0x74 0x00x69 0x0 0x63 0x0 0x6b 0x0//裝置發送字元串産品描述符
标準請求:
0x80 0x6 0x0 0x1 0x0 0x0 0x00x12//主機擷取裝置描述符
發送資料:
0x12 0x1 0x0 0x2 0x0 0x0 0x0 0x400x88 0x88 0x3 0x0 0x0 0x2 0x1 0x2 0x3 0x1//裝置發送裝置描述符
标準請求:
0x80 0x6 0x0 0x2 0x0 0x0 0x00x9//主機擷取配置描述符
發送資料:
0x9 0x2 0x22 0x0 0x1 0x1 0x0 0xa00x32//裝置發送配置描述符
标準請求:
0x80 0x6 0x0 0x2 0x0 0x0 0x00x22//主機擷取配置描述符
發送資料:
0x9 0x2 0x22 0x0 0x1 0x1 0x0 0xa00x32 0x9 0x4 0x0 0x0 0x1 0x3 0x1 0x2 0x0 0x9 0x21 0x10 0x1 0x0 0x1 0x22 0x4a0x0 0x7 0x5 0x81 0x3 0x4 0x0 0x20 //裝置發送配置描述符
标準請求:
0x0 0x9 0x1 0x0 0x0 0x0 0x00x0///主機設定配置
标準請求:
0x21 0xa 0x0 0x0 0x0 0x0 0x00x0//HID設定SET IDLE
标準請求:
0x81 0x6 0x0 0x22 0x0 0x0 0x00x8a//主機擷取報告描述符
發送資料:
0x5 0x1 0x9 0x2 0xa1 0x1 0x9 0x10xa1 0x0 0x5 0x9 0x19 0x1 0x29 0x3 0x15 0x0 0x25 0x1 0x95 0x3 0x75 0x1 0x81 0x20x95
 
0x1 0x75 0x5 0x81 0x1 0x5 0x1 0x90x30 0x9 0x31 0x9 0x38 0x15 0x81 0x25 0x7f 0x75 0x8 0x95 0x3 0x81 0x6 0xc0 0x90x3c
 
0x5 0xff 0x9 0x1 0x15 0x0 0x250x1 0x75 0x1 0x95//裝置發送報告描述符
發送資料:
0x2 0xb1 0x22 0x75 0x6 0x95 0x10xb1 0x1 0xc0//裝置發送報告描述符 
           

然後又用Bus Hound抓了一下USB的包資料,如下所示

STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹
STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹
STM32USB的枚舉過程簡介STM32的USB枚舉過程介紹

看捕捉的資料,也是發現有不了解的地方,我們看USB主機與28通信的資料(42-47),是對USB設定進行枚舉的過程,但是在與28通信的過程,并沒有對于字元串描述符的擷取,後來仔細檢視,原來字元串描述符是在7端口擷取(36-40),而且還在28端口進行資料通信之前。這個是為什麼呢?

最後說明:文中大量引用網絡資料,自己隻是做了少量修改,引用資料包括:

1網絡文檔:USB基本知識(主要是http://www.usr.cc/thread-51423-1-1.html)

2網絡文檔:基于STM32的USBUSB程式開發筆記

3書籍《圈圈教你玩USB》

繼續閱讀