大門牙原創,歡迎隨意轉載,修改,吐槽
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的狀態變化。具體如下圖所示:
其中,RxFIFO可以設置一個Threshold寄存器,當RxFIFO中的數據數大於這個值時,產生“RxFIFO非空”中斷,通知程序處理接收數據。當接收到的數據數小於這個值時,認爲Rx爲空。
TxFIFO類似也有一個Threshold寄存器,當TxFIFO中的數據小於這個值時,產生一個“TxFIFO水位過低”中斷,通知程序TxFIFO中剩餘的數據不多,需要儘快添加數據到TxFIFO中。
2. 雙工通信方案
對於從機而言,無法在當前byte返回指定的信息,故設計通訊協議如下圖所示:
主機通過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;
}
上位機程序比較簡單,不再贅述。