您的位置:首页 > 其它

SPI从机双工通信实现-基于Zenq 7000

2017-03-19 11:18 489 查看
大门牙原创,欢迎随意转载,修改,吐槽

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)

97e5
return -1;
FillSpiData(pOutData, i);
break;

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


上位机程序比较简单,不再赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  通信 zenq spi-slave