之前介绍了Zynq中的SPI控制器。本文再系统总结下对SPI协议的理解,加强对其认识。最后再说明Zynq中如果配置和使用SPI控制器。
SPI协议概述
SPI是串行外设接口(Serial Peripheral Interface)的缩写。标准四根线只使用4根信号线进行通信:MISO(主输入-从输出)、MOSI(主输出-从输入)、时钟SCLK、从机选择信号SS(有时也称为片选信号CS)。 SPI协议有如下特点:
主-从模式:两个SPI设备间必须由主机(master)控制从机(slave)。一个主机设备通过提供SCLK信号、选择SS信号(低电平有效)来控制多个从机设备。因此从机设备是无法主动向主机设备发送数据的,因为SPI是一种“时钟驱动”的协议,没有SCLK时无法正常工作。
同步传输:主机设备在要交换数据时输出时钟信号,相位CLK_PH和极性CLK_POL的不同配置组成了通常所说的4种SPI模式(具体见第28篇)。只要主机和从机选择同样的配置,即可完成同步传输。
数据交换:这里放出28篇中一种模式下的时序图,可以看到每个时钟周期内,MOSI和MISO两根信号上都有数据,即SPI设备会同时“发送”和“接收”1 bit数据,完成数据交换。
SPI协议读写操作
主机设备在访问从机设备前,必须将SS信号拉低,使能从机设备;当主机要结束本次通信时,再把SS信号拉高即可。
程序中我们都是通过寄存器操纵Tx/Rx FIFO,数据串行化工作由SPI硬件完成(分为手动和自动两种设置)。由于SPI协议“时钟驱动”和“数据交换”的特点,当主机向TxFIFO写数据,进行数据传输时,同时也会把接收到的数据存储到RxFIFO中。下一次传输之前我们应该将RxFIFO中的全部数据都读取出来。
SPI的读、写是同时进行的。如果我们只希望进行写操作,则忽略接收到的字节即可;如果要读取从机中的数据,由于从机无法主动发送数据,因此需要主机向从机发送空字节,来引导从机的传输。
SPI协议的优缺点
SPI的最大优点是速度快(是UART的几十倍),这也是EEPROM、Flash等芯片采用SPI接口的原因。比如我们将比较大的FPGA bin文件烧写到Flash中,用的便是SPI协议。其支持连续传输模式,无需不断检测帧的起始与结束,同步传输的效率比异步传输也要高。
SPI的缺点主要是抗干扰能力比RS422/485要差,不适合远距离传输。另外没有与IIC协议类似的应答机制,以告诉用户是否收到了数据。编程实现上比UART要困难一些,因为我们平时使用的EEPROM、Flash等都提供了现成的命令格式,按照时序图设计即可。当我们自定义主机和从机间的通信时,还要根据设计目的定义一些命令。
硬件环境搭建
Vivado中配置Zynq IP核,SPI控制器可以路由到MIO或EMIO。选择路由到MIO时SS[1]和SS[2]是可选的。配置DDR、UART等其它选项。
由于我使用的MZ7X和Redpitaya开发板在MIO部分没有留出SPI使用的管脚,因此选择将SPI 0路由到EMIO接口(不使用SPI 1)。直接将SPI_0管脚“右键->Make External”引出到PL管脚。
对引出的SPI管脚做约束,选择开发板上的扩展接口:
其中spi_0_io1_io为SPI的MISO信号,spi_0_io0_io为MOSI信号。将硬件环境导出到SDK中。
SDK程序设计
本文设计一个简单的SPI设备的自检程序,学会Zynq中SPI控制器的初始化。user_spi.h文件的代码如下:
#ifndef SRC_USER_SPI_H_ #define SRC_USER_SPI_H_ #include "xparameters.h" #include "xspips.h" #include "xil_printf.h" #define SPI_DEVICE_ID XPAR_XSPIPS_0_DEVICE_ID int SpiPs_Init(XSpiPs *Spi, u16 DeviceId); #endif /* SRC_USER_SPI_H_ */
user_spi.c文件的代码如下:
#include "user_spi.h" //-------------------------------------------------------------------- // SPI设备初始化函数 //-------------------------------------------------------------------- int SpiPs_Init(XSpiPs* Spi, u16 DeviceId) { int Status; XSpiPs_Config *SpiConfig; // 初始化SPI设备 SpiConfig = XSpiPs_LookupConfig(DeviceId); if (NULL == SpiConfig) { return XST_FAILURE; } Status = XSpiPs_CfgInitialize(Spi, SpiConfig, SpiConfig->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } // 进行硬件自检 Status = XSpiPs_SelfTest(Spi); if (Status != XST_SUCCESS) { return XST_FAILURE; } return XST_SUCCESS; }
main.c文件的代码如下:
#include "user_spi.h" XSpiPs Spi; //SPI设备 int main(void) { int Status; xil_printf("SPI Selftest Example \r\n"); /* SPI初始化 */ Status = SpiPs_Init(&Spi, SPI_DEVICE_ID); if (Status != XST_SUCCESS) { xil_printf("SPI Selftest Example Failed\r\n"); return XST_FAILURE; } xil_printf("Successfully ran SPI Selftest Example\r\n"); return XST_SUCCESS; }
SPI设备初始化的程序相信大家都很熟悉了,与本系列前面那些定时器设备、中断设备等等的初始化过程都相同。最后使用XSpiPs_SelfTest函数对SPI设备进行自检。这个函数会执行一次所有SPI控制器寄存器的读取和写入,相当于复位SPI设备。自检成功返回XST_SUCCESS;读取或写入某个寄存器失败时返回XST_REGISTER_ERROR。
运行结果如上,表明SPI设备自检成功,可以正常使用。
总结
本文总结了一些对SPI协议的理解,与第28篇结合希望我们可以对Zynq中SPI的具体情况有所认识。最后简单介绍了下Zynq中SPI的环境搭建以及自检程序设计。下一篇将着重讲述如何实现两块Zynq间SPI的主、从机通信,将不会再介绍这些基础内容。