1. 程式人生 > >STM32 USB學習筆記6

STM32 USB學習筆記6

主機環境:Windows 7 SP1

開發環境:MDK5.14

目標板:STM32F103C8T6

開發庫:STM32F1Cube庫和STM32_USB_Device_Library

現在來分析哈USB器件庫程式碼,先來看usbd_core檔案,其標頭檔案只有一些函式宣告,沒啥可說的,只有一點,之前分析usbd_conf.c檔案時裡面USB中斷回撥函式中呼叫的底層介面都是在usbd_core.h檔案中宣告的,同樣由使用者實現的底層介面也是在該檔案中宣告的,在usbd_core.c檔案中實現,該檔案是很重要的一個檔案,因為所有上層操作最終都會呼叫該檔案中的API來實現。在器件庫文件中提到了核心庫的作用,如下:

第一個分析的函式是USB棧的初始化以及重新初始化,如下:

/**
* @brief  USBD_Init
*         Initializes the device stack and load the class driver
* @param  pdev: device instance
* @param  pdesc: Descriptor structure address
* @param  id: Low level core index
* @retval None
*/
USBD_StatusTypeDef USBD_Init(USBD_HandleTypeDef *pdev, USBD_DescriptorsTypeDef *pdesc, uint8_t id)
{
  /* Check whether the USB Host handle is valid */
  if(pdev == NULL)
  {
    USBD_ErrLog("Invalid Device handle");
    return USBD_FAIL; 
  }
  
  /* Unlink previous class*/
  if(pdev->pClass != NULL)
  {
    pdev->pClass = NULL;
  }
  
  /* Assign USBD Descriptors */
  if(pdesc != NULL)
  {
    pdev->pDesc = pdesc;
  }
  
  /* Set Device initial State */
  pdev->dev_state  = USBD_STATE_DEFAULT;
  pdev->id = id;
  /* Initialize low level driver */
  USBD_LL_Init(pdev);
  
  return USBD_OK; 
}

/**
* @brief  USBD_DeInit 
*         Re-Initialize th device library
* @param  pdev: device instance
* @retval status: status
*/
USBD_StatusTypeDef USBD_DeInit(USBD_HandleTypeDef *pdev)
{
  /* Set Default State */
  pdev->dev_state  = USBD_STATE_DEFAULT;
  
  /* Free Class Resources */
  pdev->pClass->DeInit(pdev, pdev->dev_config);  
  
    /* Stop the low level driver  */
  USBD_LL_Stop(pdev); 
  
  /* Initialize low level driver */
  USBD_LL_DeInit(pdev);
  
  return USBD_OK;
}
USB初始化函式很簡單,將USB控制代碼的裝置類指標置NULL,同時將USB的描述符載入上去,將USB裝置狀態置為預設狀態,該函式裡面的id目前不清楚是作何用,最後呼叫USB_LL_Init()函式來初始化底層驅動。USB器件庫中USB裝置有四種狀態,定義在usbd_def.h檔案中,如下:
/*  Device Status */
#define USBD_STATE_DEFAULT                                1
#define USBD_STATE_ADDRESSED                              2
#define USBD_STATE_CONFIGURED                             3
#define USBD_STATE_SUSPENDED                              4
預設狀態、地址狀態、配置狀態、掛起狀態。在USB2.0協議文件的第9章節中規定了USB裝置的6種狀態:連線狀態、上電狀態、預設狀態、地址狀態、配置狀態、掛起狀態,六者之間的關係圖如下所示:

在USB庫中是省略了連線和上電兩個狀態,剩下四種狀態的說明可以在USB2.0協議的第九章節找到,由此可以看出USB2.0協議中第九章節有多重要了。在USB分配地址之前其使用預設地址,處在預設狀態下的USB裝置不能響應正常的請求,當USB裝置分配了唯一的地址後即進入地址狀態,響應正常請求,USB裝置配置完成後進入配置狀態,USB裝置在指定時間長度內沒有檢測到匯流排通訊時會進入掛起狀態,但會保持任何內部狀態,包括地址和配置。在USB重新初始化函式中,需要釋放類資源,且停止USB底層驅動,重新初始化底層驅動。接著是註冊類函式:

/**
  * @brief  USBD_RegisterClass 
  *         Link class driver to Device Core.
  * @param  pDevice : Device Handle
  * @param  pclass: Class handle
  * @retval USBD Status
  */
USBD_StatusTypeDef  USBD_RegisterClass(USBD_HandleTypeDef *pdev, USBD_ClassTypeDef *pclass)
{
  USBD_StatusTypeDef   status = USBD_OK;
  if(pclass != 0)
  {
    /* link the class to the USB Device handle */
    pdev->pClass = pclass;
    status = USBD_OK;
  }
  else
  {
    USBD_ErrLog("Invalid Class handle");
    status = USBD_FAIL; 
  }
  
  return status;
}

註冊裝置類函式也很簡單,把裝置類指標傳遞給USB裝置控制代碼即可,通過指標USB控制代碼包含了我們所用的所有資源,接著來看USB的一些基本操作,如下:

/**
  * @brief  USBD_Start 
  *         Start the USB Device Core.
  * @param  pdev: Device Handle
  * @retval USBD Status
  */
USBD_StatusTypeDef  USBD_Start  (USBD_HandleTypeDef *pdev)
{
  
  /* Start the low level driver  */
  USBD_LL_Start(pdev); 
  
  return USBD_OK;  
}

/**
  * @brief  USBD_Stop 
  *         Stop the USB Device Core.
  * @param  pdev: Device Handle
  * @retval USBD Status
  */
USBD_StatusTypeDef  USBD_Stop   (USBD_HandleTypeDef *pdev)
{
  /* Free Class Resources */
  pdev->pClass->DeInit(pdev, pdev->dev_config);  

  /* Stop the low level driver  */
  USBD_LL_Stop(pdev); 
  
  return USBD_OK;  
}

/**
* @brief  USBD_RunTestMode 
*         Launch test mode process
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef  USBD_RunTestMode (USBD_HandleTypeDef  *pdev) 
{
  return USBD_OK;
}


/**
* @brief  USBD_SetClassConfig 
*        Configure device and start the interface
* @param  pdev: device instance
* @param  cfgidx: configuration index
* @retval status
*/

USBD_StatusTypeDef USBD_SetClassConfig(USBD_HandleTypeDef  *pdev, uint8_t cfgidx)
{
  USBD_StatusTypeDef   ret = USBD_FAIL;
  
  if(pdev->pClass != NULL)
  {
    /* Set configuration  and Start the Class*/
    if(pdev->pClass->Init(pdev, cfgidx) == 0)
    {
      ret = USBD_OK;
    }
  }
  return ret; 
}

/**
* @brief  USBD_ClrClassConfig 
*         Clear current configuration
* @param  pdev: device instance
* @param  cfgidx: configuration index
* @retval status: USBD_StatusTypeDef
*/
USBD_StatusTypeDef USBD_ClrClassConfig(USBD_HandleTypeDef  *pdev, uint8_t cfgidx)
{
  /* Clear configuration  and De-initialize the Class process*/
  pdev->pClass->DeInit(pdev, cfgidx);  
  return USBD_OK;
}

/**
* @brief  USBD_LL_Reset 
*         Handle Reset event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SetSpeed(USBD_HandleTypeDef  *pdev, USBD_SpeedTypeDef speed)
{
  pdev->dev_speed = speed;
  return USBD_OK;
}
其中USBD_Start和USBD_Stop跟USBD的初始化類似都是呼叫usbd_conf中的底層基本操作,USBD_RunTestMode()函式為空,表明不支援測試模式,測試模式在USB2.0協議文件有提及,既然這裡不支援就麼有去細研究該功能。另外兩個函式USBD_SetClassConfig()、USBD_ClrClassConfig()函式則是跟USB裝置類相關,這裡我們還沒有分析到USB裝置類中,所以也略過,知道其功能即可。最後有一個USBD_LL_SetSpeed()函式,USB通訊有三種通訊速度:低速、全速、高速,STM32F103C8T6支援全速模式,USB速度的定義如下:
/* Following USB Device Speed */
typedef enum 
{
  USBD_SPEED_HIGH  = 0,
  USBD_SPEED_FULL  = 1,
  USBD_SPEED_LOW   = 2,  
}USBD_SpeedTypeDef;
usbd_core.c中剩下的一些函式體則是在usbd_conf.cUSB中斷回撥函式中呼叫的USB通訊處理的真正實現者,如下:
/**
* @brief  USBD_SetupStage 
*         Handle the setup stage
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SetupStage(USBD_HandleTypeDef *pdev, uint8_t *psetup)
{

  USBD_ParseSetupRequest(&pdev->request, psetup);
  
  pdev->ep0_state = USBD_EP0_SETUP;
  pdev->ep0_data_len = pdev->request.wLength;
  
  switch (pdev->request.bmRequest & 0x1F) 
  {
  case USB_REQ_RECIPIENT_DEVICE:   
    USBD_StdDevReq (pdev, &pdev->request);
    break;
    
  case USB_REQ_RECIPIENT_INTERFACE:     
    USBD_StdItfReq(pdev, &pdev->request);
    break;
    
  case USB_REQ_RECIPIENT_ENDPOINT:        
    USBD_StdEPReq(pdev, &pdev->request);   
    break;
    
  default:           
    USBD_LL_StallEP(pdev , pdev->request.bmRequest & 0x80);
    break;
  }  
  return USBD_OK;  
}

/**
* @brief  USBD_DataOutStage 
*         Handle data OUT stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev , uint8_t epnum, uint8_t *pdata)
{
  USBD_EndpointTypeDef    *pep;
  
  if(epnum == 0) 
  {
    pep = &pdev->ep_out[0];
    
    if ( pdev->ep0_state == USBD_EP0_DATA_OUT)
    {
      if(pep->rem_length > pep->maxpacket)
      {
        pep->rem_length -=  pep->maxpacket;
       
        USBD_CtlContinueRx (pdev, 
                            pdata,
                            MIN(pep->rem_length ,pep->maxpacket));
      }
      else
      {
        if((pdev->pClass->EP0_RxReady != NULL)&&
           (pdev->dev_state == USBD_STATE_CONFIGURED))
        {
          pdev->pClass->EP0_RxReady(pdev); 
        }
        USBD_CtlSendStatus(pdev);
      }
    }
  }
  else if((pdev->pClass->DataOut != NULL)&&
          (pdev->dev_state == USBD_STATE_CONFIGURED))
  {
    pdev->pClass->DataOut(pdev, epnum); 
  }  
  return USBD_OK;
}

/**
* @brief  USBD_DataInStage 
*         Handle data in stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DataInStage(USBD_HandleTypeDef *pdev ,uint8_t epnum, 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;
        
        USBD_CtlContinueSendData (pdev, 
                                  pdata, 
                                  pep->rem_length);
        
        /* Prepare endpoint for premature end of transfer */
        USBD_LL_PrepareReceive (pdev,
                                0,
                                NULL,
                                0);  
      }
      else
      { /* last packet is MPS multiple, so send ZLP packet */
        if((pep->total_length % pep->maxpacket == 0) &&
           (pep->total_length >= pep->maxpacket) &&
             (pep->total_length < pdev->ep0_data_len ))
        {
          
          USBD_CtlContinueSendData(pdev , NULL, 0);
          pdev->ep0_data_len = 0;
          
        /* Prepare endpoint for premature end of transfer */
        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;
    }
  }
  else if((pdev->pClass->DataIn != NULL)&& 
          (pdev->dev_state == USBD_STATE_CONFIGURED))
  {
    pdev->pClass->DataIn(pdev, epnum); 
  }  
  return USBD_OK;
}

/**
* @brief  USBD_LL_Reset 
*         Handle Reset event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_Reset(USBD_HandleTypeDef  *pdev)
{
  /* Open EP0 OUT */
  USBD_LL_OpenEP(pdev,
              0x00,
              USBD_EP_TYPE_CTRL,
              USB_MAX_EP0_SIZE);
  
  pdev->ep_out[0].maxpacket = USB_MAX_EP0_SIZE;
  
  /* Open EP0 IN */
  USBD_LL_OpenEP(pdev,
              0x80,
              USBD_EP_TYPE_CTRL,
              USB_MAX_EP0_SIZE);
  
  pdev->ep_in[0].maxpacket = USB_MAX_EP0_SIZE;
  /* Upon Reset call user call back */
  pdev->dev_state = USBD_STATE_DEFAULT;
  
  if (pdev->pClassData) 
    pdev->pClass->DeInit(pdev, pdev->dev_config);  
 
  
  return USBD_OK;
}

/**
* @brief  USBD_Suspend 
*         Handle Suspend event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_Suspend(USBD_HandleTypeDef  *pdev)
{
  pdev->dev_old_state =  pdev->dev_state;
  pdev->dev_state  = USBD_STATE_SUSPENDED;
  return USBD_OK;
}

/**
* @brief  USBD_Resume 
*         Handle Resume event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_Resume(USBD_HandleTypeDef  *pdev)
{
  pdev->dev_state = pdev->dev_old_state;  
  return USBD_OK;
}

/**
* @brief  USBD_SOF 
*         Handle SOF event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef  *pdev)
{
  if(pdev->dev_state == USBD_STATE_CONFIGURED)
  {
    if(pdev->pClass->SOF != NULL)
    {
      pdev->pClass->SOF(pdev);
    }
  }
  return USBD_OK;
}

/**
* @brief  USBD_IsoINIncomplete 
*         Handle iso in incomplete event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_IsoINIncomplete(USBD_HandleTypeDef  *pdev, uint8_t epnum)
{
  return USBD_OK;
}

/**
* @brief  USBD_IsoOUTIncomplete 
*         Handle iso out incomplete event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_IsoOUTIncomplete(USBD_HandleTypeDef  *pdev, uint8_t epnum)
{
  return USBD_OK;
}

/**
* @brief  USBD_DevConnected 
*         Handle device connection event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DevConnected(USBD_HandleTypeDef  *pdev)
{
  return USBD_OK;
}

/**
* @brief  USBD_DevDisconnected 
*         Handle device disconnection event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DevDisconnected(USBD_HandleTypeDef  *pdev)
{
  /* Free Class Resources */
  pdev->dev_state = USBD_STATE_DEFAULT;
  pdev->pClass->DeInit(pdev, pdev->dev_config);  
   
  return USBD_OK;
}
雖然函式有些多,但看具體函式的程式碼量就可以知道哪些函式是重要的,第一個函式是USBD_LL_SetupStage(),USB請求分為三個階段:Setup階段、可選的資料階段、狀態階段。該函式用於處理Setup階段,解析USB主機發來的請求,呼叫USBD_ParseSetupRequest()函式來獲取setup請求,並賦給pdev->request變數,該函式實現如下:
/**
* @brief  USBD_ParseSetupRequest 
*         Copy buffer into setup structure
* @param  pdev: device instance
* @param  req: usb request
* @retval None
*/

void USBD_ParseSetupRequest(USBD_SetupReqTypedef *req, uint8_t *pdata)
{
  req->bmRequest     = *(uint8_t *)  (pdata);
  req->bRequest      = *(uint8_t *)  (pdata +  1);
  req->wValue        = SWAPBYTE      (pdata +  2);
  req->wIndex        = SWAPBYTE      (pdata +  4);
  req->wLength       = SWAPBYTE      (pdata +  6);

}

#define  SWAPBYTE(addr)        (((uint16_t)(*((uint8_t *)(addr)))) + \
                               (((uint16_t)(*(((uint8_t *)(addr)) + 1))) << 8))
函式實現很簡單即獲取Setup的8個成員變數值,至於Setup包中8個數據來源在usbd_conf.c檔案中傳遞的是hpcd->Setup,其資料來源是在Cube庫中USB的實現的,因此在這裡並沒有關心。在獲取完Setup包資料後將端點0狀態置為USBD_EP0_SETUP,端點0比較重要是因為其是USB預設的控制端點用於接收USBSetup請求資料,在USB器件庫中規定了端點0的幾種狀態,如下:
/*  EP0 State */    
#define USBD_EP0_IDLE                                     0
#define USBD_EP0_SETUP                                    1
#define USBD_EP0_DATA_IN                                  2
#define USBD_EP0_DATA_OUT                                 3
#define USBD_EP0_STATUS_IN                                4
#define USBD_EP0_STATUS_OUT                               5
#define USBD_EP0_STALL                                    6 

可以看出端點0的狀態跟USB請求息息相關。接著使用ep0_data_len來儲存該Setup請求的資料長度,並根據bmRequest資料值來檢測該請求的接收者,在USB2.0協議中規定了請求的接收者有三個:裝置、介面、端點。根據接收者的不同調用不同的函式實現體,這些實現體在另一個檔案usbd_ctlreq.c中實現,所以這裡不細說,等分析usbd_ctlreq.c檔案時再細說,如果接收者不是以上三種則呼叫USBD_LL_StallEP()來將端點設定一個停止條件。USBD_LL_DataOutStage()和USBD_LL_DataInStage()是最重要的兩個函式且程式碼行數較多,放到最後分析,先分析另外幾個比較簡單的函式,USBD_LL_Reset()函式是重新初始化,這裡是重新設定了端點0的屬性,並把裝置狀態置為預設狀態,並呼叫相應類的DeInit()函式來重新初始化裝置類。USB的掛起和喚醒函式更簡單,只是設定裝置狀態即可。USBD_LL_SOF()函式是發出起始幀訊號,SOF是一個數據包,而EOF是一種電平狀態。後面有一個裝置斷開連線的實現,跟復位有些類似,將裝置狀態置為預設狀態,並重新初始化裝置類。現在來分析兩個重要的函式,首先是USBD_LL_DataOutStage(),需要注意的是Setup階段是隻跟端點0相關,而資料階段是可以跟每一個端點相關的,因為任何端點都可以傳輸資料,所以該函式的引數中有epnum來傳遞傳輸資料的端點號,在函式實現中可以看到如果傳遞的epnum不是0,則表明是裝置類中的端點傳遞資料,如果裝置處於配置狀態且裝置類的DataOut指標非空則執行裝置類中的DataOut函式,如果epnum為0則是端點0上的資料,DataOutStage上的資料是USB模組接收USB主機發來的,要明白其資料傳輸的方向。每個端點都有設定其最大包大小即maxpacket,端點接收的資料大小一定是小於等於maxpacket的,當需要接收遠多於maxpacket的資料時是需要分包傳送/接收的,一個很形象的例子如下:


該圖是STM32論壇中培訓資料中得到的,根據該圖可以方便於我們分析DataOutStage以及DataInStage,在DataOutStage的處理中是獲取該0號端點,且該端點處於DATA_OUT狀態,端點的rem_length變數儲存的是當次接收的資料總長度即在Setup函式中request.wLength,而maxpacket的值是在開啟該端點時傳遞進來的,當我們接收到的資料長度大於自身的最大包大小時,表明我們還有資料需要繼續接收,這裡要注意的是該函式實際是USB中斷函式中的DataStage的回撥函式,即該函式執行時已經接收到了一包資料,因此這裡才呼叫USBD_CtlContinueRx()即繼續接收,這個跟串列埠的接收中斷較類似,也由此,呼叫USBD_CtlContinueRx()中的引數是rem_length和maxpacket中的小值,當最後資料接收完畢時rem_length的值是小於等於maxpacket,就看傳輸的資料量是否是maxpacket的整數倍了。資料接收完成時這裡呼叫了裝置類的EP0_RxReady回撥函式即交由對應的裝置類對所收到的資料進行處理,對於本例的VCP類說即交由CDC類的介面檔案中的CDC_Itf_Control()函式來處理(設定裝置串列埠屬性),最後呼叫USBD_CtlSendStatus()函式來執行請求的第三階段:狀態階段。該函式我們在後面另分析。

有了分析DataOutStage的基礎,DataInStage就容易分析多了,兩者是相反的過程,該函式是傳送資料到USB主機,首先當傳輸資料的端點是非0端點時呼叫相應裝置類的DataIn函式進行處理,當傳輸資料的端點是0端點時且端點0處於DATA_IN狀態,同理此處的rem_length同樣和Setup階段的request.wLength相等,且該函式執行時已經有一包資料傳送出去,因此這裡更新完rem_length後繼續傳送資料且使用USBD_LL_PrepareReceive()接收USB主機發來的應答資訊。當rem_length小於等於maxpacket時表明資料已經發送完畢,如果所需要傳送的資料量是maxpacket的整數倍這裡需要傳送一個0位元組資料包來通知USB主機資料傳送完畢。這裡一直不理解的是下面這條語句

(pep->total_length < pdev->ep0_data_len )
因為按個人理解這兩個值應該是相等的,通過檢索可知ep0_data_len只有一處賦值即Setup階段的request.wLength,而total_length的值在最開始的傳送時與rem_length值相等也即為本次傳送的資料長度值,無論怎麼想二者都應該是相等才對。ep0_data_len的值只有在這裡清零,下次進入該函式時total_length值即大於ep0_data_len(因此個人覺得這裡應該是ep0_data_len和0的判斷而不是和total_length判斷),資料傳送完畢後,如果EP0_TxSent回撥函式不為空則執行該回調,最後接收USB主機發來的狀態資訊。當端點0的狀態不為DATA_IN時這裡有個測試模式的呼叫,由於沒有使能測試模式,所以這裡不關心,如果有興趣的話可以研究哈。至此,usbd_core檔案分析完畢,USB器件庫的核心檔案還剩下兩個usbd_ioreq和usbd_ctlreq。