学会Zynq(29)SPI协议的理解与初步使用

时间:2024-04-12 08:03:16

本上介绍了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数据,完成数据交换。

学会Zynq(29)SPI协议的理解与初步使用


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等其它选项。
学会Zynq(29)SPI协议的理解与初步使用
由于我使用的MZ7X和Redpitaya开发板在MIO部分没有留出SPI使用的管脚,因此选择将SPI 0路由到EMIO接口(不使用SPI 1)。直接将SPI_0管脚“右键->Make External”引出到PL管脚。
学会Zynq(29)SPI协议的理解与初步使用
对引出的SPI管脚做约束,选择开发板上的扩展接口:
学会Zynq(29)SPI协议的理解与初步使用
其中spi_0_io0为SPI的MISO信号,spi_0_io1_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
学会Zynq(29)SPI协议的理解与初步使用
运行结果如上,表明SPI设备自检成功,可以正常使用。


总结

本文总结了一些对SPI协议的理解,与第28篇结合希望我们可以对Zynq中SPI的具体情况有所认识。最后简单介绍了下Zynq中SPI的环境搭建以及自检程序设计。下一篇将着重讲述如何实现两块Zynq间SPI的主、从机通信,将不会再介绍这些基础内容。