SPI從機雙工通信實現-基於Zenq 7000

大門牙原創,歡迎隨意轉載,修改,吐槽

1. 準備工作

Zenq 7000系列SPI外設支持master和slave工作模式。其中,master模式應用比較好理解。但是作爲slave模式工作時,如何與master進行雙工通信,是一個問題。

Zenq 7000使用了兩塊FIFO進行時鐘域的同步(CPU時鐘和SPI時鐘)。當作爲slave模式工作時,當master的SCLK信號產生時,SPI控制器會自動將接收數據存入RxFIFO,同時將TxFIFO中的數據在MISO線上發送出去。

在這個邏輯流程中,RxFIFO和TxFIFO可以分別設置一個water flow值(水位值)。當數據滿足某種條件時,會觸發驅動提醒用戶程序FIFO的狀態變化。具體如下圖所示:

zenq7000-spi-dev

其中,RxFIFO可以設置一個Threshold寄存器,當RxFIFO中的數據數大於這個值時,產生“RxFIFO非空”中斷,通知程序處理接收數據。當接收到的數據數小於這個值時,認爲Rx爲空。

TxFIFO類似也有一個Threshold寄存器,當TxFIFO中的數據小於這個值時,產生一個“TxFIFO水位過低”中斷,通知程序TxFIFO中剩餘的數據不多,需要儘快添加數據到TxFIFO中。

2. 雙工通信方案

對於從機而言,無法在當前byte返回指定的信息,故設計通訊協議如下圖所示:

spi-comm

主機通過spi對從機進行兩種類型的通信:Set類和Get類命令。顧名思義,Set類是將某種數據設置到從機,Get類命令是向從機索取某種數據。
每一個通信幀都包含兩個byte block,每個byte block包括7個Bytes。其中,第一個byte block主機發送的數據爲DC AA AA BB BB BB BB,其中 ‘DC’表示標識字節,’AA’標識命令編號,’BB’命令的參數數據。對於Set類型的命令,其參數數據是有效負載;對於Get類型的數據,其參數是無效內容,可以隨意填。

第二個byte block是用於master接收slave的回發數據,其中’ED’是自定義的標識字節。對於Set命令,第二個byte block中的’AA AA BB BB BB BB’返回其第一個byte block中的對應內容作爲通信校驗;對於Get命令,其中’AA’爲對應的命令編號,’DD’爲具體返回的數據。

每一個通信幀完成一條命令,即主機無論是發送什麼類型的命令都需要2x7=14 bytes來完成。

3. 實現方法

底層使用spips v2_0驅動實現。對於FPGA,首先完成SPI外設的各種配置

然後註冊中斷函數(spips中斷服務函數)

extern XSpiPs SpiInstance;
    // SPI Interrupt
Status = XScuGic_Connect(IntcInstancePtr, ZYNQ_SPI1_INT_IRQ_ID,
                      (Xil_ExceptionHandler) XSpiPs_InterruptHandler,
                      (void *) (&SpiInstance));
if (Status != XST_SUCCESS) {
    return XST_FAILURE;
}
XScuGic_UserEnable(IntcInstancePtr, ZYNQ_SPI1_INT_IRQ_ID);

再註冊狀態服務函數(基於spips驅動)

XSpiPs_SetStatusHandler(&SpiInstance, &SpiInstance,
                 (XSpiPs_StatusHandler) SpiHandler);

再啓動設備

XSpiPs_Enable((&SpiInstance));

最後開啓一次7 bytes傳輸過程,(SPI_CMD_LEN == 7),開始傳輸

XSpiPs_Transfer(&SpiInstance, WriteBuffer, ReadBuffer, SPI_CMD_LEN);

spi狀態服務函數實現如下

void SpiHandler(void *CallBackRef, u32 StatusEvent, unsigned int ByteCount)
{
    int ret;
    static const unsigned char pError[SPI_CMD_LEN] =
                {0xED, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00};

    switch (StatusEvent){

        case XST_SPI_TRANSFER_DONE:

            ret = SpiCmdProc(ReadBuffer, WriteBuffer);

            if(ret)
                memcpy(WriteBuffer, pError, SPI_CMD_LEN);

            XSpiPs_Transfer(&SpiInstance, WriteBuffer, ReadBuffer, SPI_CMD_LEN);

            break;

        default:
            //printf("SPI Error! spi_int() Status = %d, ByteCount = %d\n", StatusEvent, ByteCount);
            memcpy(WriteBuffer, pError, SPI_CMD_LEN);
            XSpiPs_Transfer(&SpiInstance, WriteBuffer, ReadBuffer, SPI_CMD_LEN);
            break;

    }   //end of switch
}

命令處理函數如下

int SpiCmdProc(unsigned char *pInData, unsigned char *pOutData)
{
    int i,j;
    int ret;

    if(pInData[0] != 0xDC )
        return -1;

    u16 cmd = (pInData[1] << 8) | pInData[2];
    u32 data = (pInData[3] << 24) | (pInData[4] << 16) | (pInData[5] << 8) |pInData[6] ;

    memcpy(pOutData, pInData, SPI_CMD_LEN);
    pOutData[0] = 0xED;

    switch (cmd){

        case SPI_CMD_READDATA:
            // jie @ 2017-03-17 : not doing anything for transmit origin command back
        break;

        case SPI_GET_TEMP:
            ret = GetTemperature(&i, &j);
            if(ret)
                return -1;
            FillSpiData(pOutData, i);
        break;

        default:
            FillSpiData(pOutData, 0xFFFFFFFF);
        break;
    }
    return 0;
}

上位機程序比較簡單,不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章