SAM9G45之USB學習筆記

無痕幽雨發表於2018-01-09

最近一直在學習SAM9G45的USB功能,資料大部分都是從網上找的,還有就是官方給的庫,只是把自己除錯過程中遇到的問題記錄下來,因為沒有除錯完,所以會持續更新。

問題一(其實也不是問題),SAM9G45只有7個端點(包括端點0),我開始除錯的時候以為不帶0,哎,鬱悶。

問題二(待求證),不過從我目前的除錯來看,結論是對的。SAM9G45的端點不是全雙工了,一個端點不能同時配置為IN和OUT,但是手冊又有自相矛盾的地方。


從表38-1來看它的每個端點都支援ctrl傳輸模式,根據USB協議規定,這個傳輸模式要求端點必須是全雙工的,因為ctrl的端點必須支援messsge pipe型別。


從表38-2也能看出,CTRL模式是雙向的;


從表38-3的注意1來看,它說control transfer必須是1bank,從38-1看,bank1只能是端點0;從這裡來看其它端點是不能支援ctrl模式的。和我的實驗結論一致,也可能是我不會設定端點地址,如果哪位網友正好知道,請給我留言,謝謝。


三、Bus Hound配置問題

這個東西用的不熟悉,抓包發現只能抓取32的位元組的IN指令包,以為是裝置問題,最後發現是busHound設定問題,鬱悶。


四、這個是目前遇到最鬱悶的問題

剛開始的Demo是hid_transfer,我在這個基礎上改的CCID,並且開始只是用一個裝置+一個interface除錯的,很順利,很快就識別出來了。應用需求,需要兩個interface,改過之後怎麼也識別不了第二個裝置。Bus Hound上面,也沒有第二個裝置的報文。鬱悶,以為是配置問題,結果把配置複製帶interface0上面,正常。鬱悶了,所有的操作函式都是一套,通過介面傳引數,來確定是操作那個裝置,難道個別的函式有錯誤,引數沒有改過來?於是把所有的函式全部對了一遍,沒有錯誤啊?這是怎麼回事。最後找到原因了,原來是windows系統的驅動問題。如果一個裝置的V_ID和P_ID不修改,之前已經插入過電腦,並且正確設別並安裝驅動的話,如果再次插入(即使配置付改變了)驅動不會從新安裝,這個時候裝置出來的還是原來的裝置。

解決方法:

1、修改P_ID或者V_ID的值;

2、或者在登錄檔裡面刪掉對應的配置資訊;

我修改了P_ID值,正確識別了兩個裝置:



《-------------------------------------------------------------------------------------------------------》

官方USB協議幀學習筆記:

先上傳幾張圖片,回頭再整理:

圖1   協議棧的結構


圖2  USB的狀態機


圖3  USB的協議棧回撥函式架構

首先要知道USB的驅動狀態:

USB device states
/// The device is currently suspended.
#define USBD_STATE_SUSPENDED            0
/// USB cable is plugged into the device.
#define USBD_STATE_ATTACHED             1
/// Host is providing +5V through the USB cable.
#define USBD_STATE_POWERED              2
/// Device has been reset.
#define USBD_STATE_DEFAULT              3
/// The device has been given an address on the bus.
#define USBD_STATE_ADDRESS              4
/// A valid configuration has been selected.
#define USBD_STATE_CONFIGURED           5

deviceState(狀態變數)  


其次USB的API返回值

USB device API return values
/// Indicates the operation was successful.
#define USBD_STATUS_SUCCESS             0
/// Endpoint/device is already busy.
#define USBD_STATUS_LOCKED              1
/// Operation has been aborted.
#define USBD_STATUS_ABORTED             2
/// Operation has been aborted because the device has been reset.
#define USBD_STATUS_RESET               3
/// Operation failed because parameter error
#define USBD_STATUS_INVALID_PARAMETER   4
/// Operation failed because HW not supported
#define USBD_STATUS_HW_NOT_SUPPORTED    5


端點的狀態:

Endpoint states
/// Endpoint states: Endpoint is disabled
#define UDP_ENDPOINT_DISABLED       0
/// Endpoint states: Endpoint is halted (i.e. STALLs every request)
#define UDP_ENDPOINT_HALTED         1
/// Endpoint states: Endpoint is idle (i.e. ready for transmission)
#define UDP_ENDPOINT_IDLE           2
/// Endpoint states: Endpoint is sending data
#define UDP_ENDPOINT_SENDING        3
/// Endpoint states: Endpoint is receiving data
#define UDP_ENDPOINT_RECEIVING      4


1、CCIDDriver_Initialize()

CCIDDriver_Initialize()->USBD_Init()->USBDCallbacks_Initialized()->IRQ_ConfigureIT(AT91C_ID_UDPHS, 0, UDPD_IrqHandler);//, UDPD_IrqHandler);

UDPD_IrqHandler()配置為USB的中斷函式;

UDPD_IrqHandler()呼叫UDPHS_EndpointHandler()和UDPHS_DmaHandler()

其中:

UDPHS_EndpointHandler()->USBDCallbacks_RequestReceived()->CCID_RequestHandler()->CCID

處理和USBDDriver_RequestHandler()

UDPHS_EndpointHandler()和UDPHS_DmaHandler()都會呼叫UDPHS_EndOfTransfer()

來看下UDPHS_EndOfTransfer()

//------------------------------------------------------------------------------
/// Handles a completed transfer on the given endpoint, invoking the
/// configured callback if any.
/// \param bEndpoint Number of the endpoint for which the transfer has completed.
/// \param bStatus   Status code returned by the transfer operation
//------------------------------------------------------------------------------
static void UDPHS_EndOfTransfer( unsigned char bEndpoint, char bStatus )
{
    Endpoint *pEndpoint = &(endpoints[bEndpoint]);
    Transfer *pTransfer = &(pEndpoint->transfer);

    // Check that endpoint was sending or receiving data
    if( (pEndpoint->state == UDP_ENDPOINT_RECEIVING)
     || (pEndpoint->state == UDP_ENDPOINT_IDLE)
     || (pEndpoint->state == UDP_ENDPOINT_SENDING) ) {

        TRACE_DEBUG_WP("UDPHS_EndOfTransfer ");
        if(pEndpoint->state == UDP_ENDPOINT_SENDING) {
            pEndpoint->sendZLP = 0;
        }
        // Endpoint returns in Idle state
        //if(pEndpoint->state == UDP_ENDPOINT_SENDING){
            pEndpoint->state = UDP_ENDPOINT_IDLE;
        //}

        // Invoke callback is present
        if (pTransfer->fCallback != 0) {

            ((TransferCallback) pTransfer->fCallback)
                (pTransfer->pArgument,
                 bStatus,
                 pTransfer->transferred,
                 pTransfer->remaining + pTransfer->buffered);
        }
        else {
            TRACE_DEBUG_WP("No callBack\n\r");
        }
    }
}

當接收完成或者傳送完成,就會呼叫一個callback,這個callback是哪裡來的呢?繼續看

void CCID_SmartCardRequest_V1( uint8_t   chCcidNum )
{
    unsigned char bStatus;
    if(chCcidNum >= CCID_INTERFACE_NUM){
        TRACE_ERROR("CCID_SmartCardRequest  chCcidNum >= CCID_INTERFACE_NUM\n\r");
        return;
    }
    //do{

        bStatus = CCID_Read_V1((void*)&ccidDriver.tCcidStruct[chCcidNum].sCcidCommand,
                             sizeof(S_ccid_bulk_out_header),
                             (TransferCallback)&CCIDCommandDispatcher_V1,
                             (void*)chCcidNum );//直接把值變為地址傳輸
    //}while (bStatus != USBD_STATUS_SUCCESS);

}
這個是API層的CCID呼叫函式,
//------------------------------------------------------------------------------
/// Reads data from the Data OUT endpoint
/// \param pBuffer   Buffer to store the received data
/// \param dLength   data buffer length
/// \param fCallback Optional callback function
/// \param pArgument Optional parameter for the callback function
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
/*
unsigned char CCID_Read(void *pBuffer,
                        unsigned int dLength,
                        TransferCallback fCallback,
                        void *pArgument)
{
    return USBD_Read(CCID_EPT_DATA_OUT, pBuffer, dLength, fCallback, pArgument);
}*/


unsigned char CCID_Read_V1( void *pBuffer,
                            unsigned int dLength,
                            TransferCallback fCallback,
                            void *pArgument)
{
    uint8_t chEndpointOut = 0;

    if(((uint8_t)pArgument) >= CCID_INTERFACE_NUM){
        TRACE_ERROR("CCID_Read  chCcidNum >= CCID_INTERFACE_NUM\n\r");
        return USBD_STATUS_INVALID_PARAMETER;
    }

    switch((uint8_t)pArgument){
#if (CCID_INTERFACE_NUM>0)
    case 0:
        chEndpointOut = CCID_INTERFACE0_EPT_DATA_OUT;
        break;
#endif
#if (CCID_INTERFACE_NUM>1)
    case 1:
        chEndpointOut = CCID_INTERFACE1_EPT_DATA_OUT;
        break;
#endif
#if (CCID_INTERFACE_NUM>2)
    case 2:
        chEndpointOut = CCID_INTERFACE2_EPT_DATA_OUT;
        break;
#endif
#if (CCID_INTERFACE_NUM>3)
    case 3:
        chEndpointOut = CCID_INTERFACE3_EPT_DATA_OUT;
        break;
#endif
    }

    return USBD_Read(chEndpointOut, pBuffer, dLength, fCallback, pArgument);
}

繼續看:

//------------------------------------------------------------------------------
/// Reads incoming data on an USB endpoint (OUT)
/// \param bEndpoint Index of endpoint
/// \param *pData  Data to be readen
/// \param dLength Data length to be receive
/// \param fCallback  Callback to be call after the success command
/// \param *pArgument Callback argument
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
char USBD_Read( unsigned char    bEndpoint,
                void             *pData,
                unsigned int     dLength,
                TransferCallback fCallback,
                void             *pArgument )
{
    Endpoint *pEndpoint = &(endpoints[bEndpoint]);
    Transfer *pTransfer = &(pEndpoint->transfer);
  
    // Return if the endpoint is not in IDLE state
    if (pEndpoint->state != UDP_ENDPOINT_IDLE) {

        return USBD_STATUS_LOCKED;
    }

    TRACE_DEBUG_WP("Read%d(%d) ", bEndpoint, dLength);

    // Endpoint enters Receiving state
    pEndpoint->state = UDP_ENDPOINT_RECEIVING;

    // Set the transfer descriptor
    pTransfer->pData = pData;
    pTransfer->remaining = dLength;
    pTransfer->buffered = 0;
    pTransfer->transferred = 0;
    pTransfer->fCallback = fCallback;
    pTransfer->pArgument = pArgument;

#ifdef DMA
    // Test if endpoint type control
    if(AT91C_UDPHS_EPT_TYPE_CTL_EPT == (AT91C_UDPHS_EPT_TYPE&(AT91C_BASE_UDPHS->UDPHS_EPT[bEndpoint].UDPHS_EPTCFG))) {
#endif
        // Control endpoint
        // Enable endpoint IT
        AT91C_BASE_UDPHS->UDPHS_IEN |= (1 << SHIFT_INTERUPT << bEndpoint);
        AT91C_BASE_UDPHS->UDPHS_EPT[bEndpoint].UDPHS_EPTCTLENB = AT91C_UDPHS_RX_BK_RDY;
#ifdef DMA
    }
    else {

        TRACE_DEBUG_WP("Read%d(%d) ", bEndpoint, dLength);

        // Others endpoints (not control)
        if( pTransfer->remaining > DMA_MAX_FIFO_SIZE ) {

            // Transfer the max
            pTransfer->buffered = DMA_MAX_FIFO_SIZE;    
        }
        else {
            // Transfer the good size
            pTransfer->buffered = pTransfer->remaining;
        }

        AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMAADDRESS = (unsigned int)(pTransfer->pData);

        // Clear unwanted interrupts
        AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMASTATUS;

        // Enable DMA endpoint interrupt
        AT91C_BASE_UDPHS->UDPHS_IEN |= (1 << SHIFT_DMA << bEndpoint);

        TRACE_DEBUG_WP("\n\r_RR:%d ", pTransfer->remaining );
        TRACE_DEBUG_WP("B:%d ", pTransfer->buffered );
        TRACE_DEBUG_WP("T:%d ", pTransfer->transferred );

        // DMA config
        AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMACONTROL = 0; // raz
        AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMACONTROL =
                                 ( ((pTransfer->buffered << 16) & AT91C_UDPHS_BUFF_COUNT)
                                   | AT91C_UDPHS_END_TR_EN
                                   | AT91C_UDPHS_END_TR_IT
                                   | AT91C_UDPHS_END_B_EN
                                   | AT91C_UDPHS_END_BUFFIT
                                   | AT91C_UDPHS_CHANN_ENB );
    }
#endif

    return USBD_STATUS_SUCCESS;
}
到這看明白了吧,是上層的CCID_SmartCardRequest_V1()通過usb_read()設定的

看下端點的結構體:

//------------------------------------------------------------------------------
/// Describes the state of an endpoint of the UDP controller.
//------------------------------------------------------------------------------
typedef struct
{
    /// Current endpoint state.
    volatile unsigned char  state;
    /// Current reception bank (0 or 1).
    unsigned char  bank;
    /// Maximum packet size for the endpoint.
    unsigned short size;
    /// Describes an ongoing transfer (if current state is either
    ///  <UDP_ENDPOINT_SENDING> or <UDP_ENDPOINT_RECEIVING>)
    Transfer       transfer;
    /// Special case for send a ZLP
    unsigned char  sendZLP;
} Endpoint;

/// Describes an ongoing transfer on a UDP endpoint.
typedef struct
{
    /// Pointer to a data buffer used for emission/reception.
    char             *pData;
    /// Number of bytes which have been written into the UDP internal FIFO
    /// buffers.
    volatile int     buffered;
    /// Number of bytes which have been sent/received.
    volatile int     transferred;
    /// Number of bytes which have not been buffered/transferred yet.
    volatile int     remaining;
    /// Optional callback to invoke when the transfer completes.
    volatile TransferCallback fCallback;
    /// Optional argument to the callback function.
    void             *pArgument;
} Transfer;
至此,我們把自己每個端點的處理函式CCIDCommandDispatcher_V1()繫結到了相應的端點上。

問題:

1、到目前為止,有一點我不是很理解,為什麼每次收到後,需要對相應的DMA重新初始化,相應的端點也同樣做了初始化,我檢視了HID、CDC歷程,都是這麼處理的。因此在CCIDCommandDispatcher_V1()裡面我們需要呼叫usb_read()做相應初始化,或者利用查詢方式,在應用層初始化。









相關文章