1 SPI通信协议
- SPI是串行外设接口(Serial Peripheral Interface)的缩写,是 Motorola 首先提出的全双工三线同步串行外围接口。
- 采用主从模式(Master Slave)架构,支持多 slave模式应用,一般仅支持单 Master。时钟由 Master 控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSBfirst)。
1.1 接口定义
- SPI 接口共有 4 根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。为全双工通信,目前应用中的数据速率可达几 Mbps 的水平。
(1)MOSI:主器件数据输出,从器件数据输入
(2)MISO:主器件数据输入,从器件数据输出
(3)SCLK:时钟信号,由主器件产生
(4)SS:从器件使能信号,由主器件控制
1.2 时钟极性和时钟相位
- SPI 数据的传输是在串行同步时钟信号SCK的控制下进行的。主机的时钟发生器一方面控制主机的移位寄存器,另一方面通过从机的 SCK 信号线来控制从机的移位寄存器,从而保证主机与从机的数据交换是同步进行的。
- SPI 串行同步时钟可以设置为不同的极性(Clock Polarity ,CPOL)与相位(Clock Phase ,CPHA)。
- 时钟的极性CPOL用来决定在总线空闲时,同步时钟SCK信号线上的电位是高电平还是低电平。 当时钟极性为 0 时(CPOL=0),SCK 信号线在空闲时为低电平;当时钟极性为 1 时(CPOL=1),SCK 信号线在空闲时为高电平。
- 时钟的相位CPHA用来决定何时进行信号采样。
- CPHA=1时,在 SCK 信号线的第二个跳变沿进行采样;当时钟极性为 0 时,取下降沿;当时钟极性为1时,取上升沿。
- CPHA=0时,在 SCK 信号线的第一个跳变沿进行采样。跳变沿同样与时钟极性有关:当时钟极性为 0 时,取上升沿;当时钟极性为 1时,取下降沿。
1.3 数据传输
- 在一个 SPI 时钟周期内,会完成如下操作:
1)主机通过 MOSI 线发送 1 位数据,从机通过该线读取这 1 位数据;
2)从机通过 MISO 线发送 1 位数据,主机通过该线读取这 1 位数据。 - 这是通过移位寄存器来实现的。 如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换
2 ZYNQ-7000 SPI控制器介绍
- 详情见ug-585 TRM
- ZYNQ 的 ARM 内集成了 2 路 SPI 控制器,分别为 SPI0 和 SPI1。
- zynq-7000核通过APB总线与SPI控制器进行互联,SPI控制器通过MIO/EMIO路由选择器,连接至zynq的MIO端口,或通过EMIO连接至PL。这里要注意,若通过EMIO连接至PL,在PL端要进行管脚约束至外部SPI设备。若通过MIO,则是直接通过zynq-7000的管脚连接至外部设备!!!
- SPI 控制器具有以下特性:
1)128 字节读取和 128 字节写入 FIFO
2)可编程时钟相位和极性(CPHA,CPOL)
3)当 I / O 是 MIO 引脚时最高时钟为 50 MHz SCLK,当 I / O 是 EMIO 引脚时最高时钟为 25 MHz SCLK
4)Master I / O 模式:手动和自动开始传输数据,手动和自动从动选择(SS)模式,从机选择信号可以直接连接到从机设备或外部扩展,可编程 SS 和 MOSI 延迟。
5)Slave I / O 模式:可编程启动检测模式
3 裸机下ZYNQ-7000 SPI控制器的使用
3.1 PL逻辑设计
-
目标:在PS通过SPI控制器下发数据,在FPGA的外接管脚对SPI0_MOSI和SPI0_MISO进行短接,并在PS读取SPI控制器读到的数据,并进行对比。即回环测试。
-
构建zynq-7000最小系统,选中SPI控制器把SPI 接口的IO以EMIO的方式引出,然后在开发板上把SPI0_MOSI和 SPI0_MISO 环路短接,这样就可以回环测试了。
-
FCLK_CLK0 是PS输出的时钟,可以给PL部分使用。用来提供给 ILA 在线逻辑分析仪使用,我们会用在线逻辑分析仪查看 SPI 的时序波形。
-
EMIO是PS的拓展MIO,连接到了PL端,记得要对SPI0_MOSI和SPI0_MISO进行xdc约束。
-
生成bitstream并导出到SDK后,启动SDK,并进行调试。在开发板上对这两个端口用杜邦线进行短接后,可以看到串口打印输出。
3.2 PS程序设计
//spips.h 对Xilinx的spi裸机驱动进行封装
#ifndef SRC_SPIPS_H_
#define SRC_SPIPS_H_#include "xparameters.h" /* SDK generated parameters */
#include "xspips.h" /* SPI device driver */
#include "xil_printf.h"void SpiPs_Read (u8 *ReadBuffer, int ByteCount);
void SpiPs_Send (u8 *SendBuffer, int ByteCount);
int SpiPs_Init (u16 SpiDeviceId);#define SPI_DEVICE_ID XPAR_XSPIPS_0_DEVICE_ID
#define MAX_DATA 100
#define SpiPs_RecvByte(BaseAddress) (u8)XSpiPs_In32((BaseAddress) + XSPIPS_RXD_OFFSET)
#define SpiPs_SendByte(BaseAddress, Data) XSpiPs_Out32((BaseAddress) + XSPIPS_TXD_OFFSET, (Data))
XSpiPs SpiInstance;#endif /* SRC_SPIPS_H_ */
//spips.c
#include "spips.h"
int SpiPs_Init(u16 SpiDeviceId)
{int Status;u8 *BufferPtr;XSpiPs_Config *SpiConfig;/** Initialize the SPI driver so that it's ready to use*/SpiConfig = XSpiPs_LookupConfig(SpiDeviceId);if (NULL == SpiConfig) {return XST_FAILURE;}Status = XSpiPs_CfgInitialize((&SpiInstance), SpiConfig,SpiConfig->BaseAddress);if (Status != XST_SUCCESS) {return XST_FAILURE;}/** The SPI device is a slave by default and the clock phase* have to be set according to its master. In this example, CPOL is set* to quiescent high and CPHA is set to 1.*/Status = XSpiPs_SetOptions((&SpiInstance), XSPIPS_MASTER_OPTION);if (Status != XST_SUCCESS) {return XST_FAILURE;}Status = XSpiPs_SetClkPrescaler(&SpiInstance, XSPIPS_CLK_PRESCALE_64);/** Enable the device.*/XSpiPs_Enable((&SpiInstance));return XST_SUCCESS;
}void SpiPs_Read(u8 *ReadBuffer,int ByteCount)
{int Count;u32 StatusReg;do{StatusReg = XSpiPs_ReadReg(SpiInstance.Config.BaseAddress,XSPIPS_SR_OFFSET);}while(!(StatusReg & XSPIPS_IXR_RXNEMPTY_MASK));/** Reading the Rx Buffer*/for(Count = 0; Count < ByteCount; Count++){ReadBuffer[Count] = SpiPs_RecvByte(SpiInstance.Config.BaseAddress);}
}void SpiPs_Send(u8 *SendBuffer, int ByteCount)
{u32 StatusReg;int TransCount = 0;/** Fill the TXFIFO with as many bytes as it will take (or as* many as we have to send).*/while ((ByteCount > 0) &&(TransCount < XSPIPS_FIFO_DEPTH)) {SpiPs_SendByte(SpiInstance.Config.BaseAddress,*SendBuffer);SendBuffer++;++TransCount;ByteCount--;}/** Wait for the transfer to finish by polling Tx fifo status.*/do {StatusReg = XSpiPs_ReadReg(SpiInstance.Config.BaseAddress,XSPIPS_SR_OFFSET);} while ((StatusReg & XSPIPS_IXR_TXOW_MASK) == 0);
}
#include "spips.h"
u8 ReadBuf[MAX_DATA];
u8 SendBuf[MAX_DATA];int main(void)
{int i =0;SpiPs_Init(SPI_DEVICE_ID);for(i=0;i<10;i++)SendBuf[i]=i;SpiPs_Send(SendBuf,10);SpiPs_Read(ReadBuf,10);for(i=0;i<10;i++)xil_printf("%d,",ReadBuf[i]);return 0;
}
3.2.1 代码分析
SpiPs_Init
- SpiPs_Init用于对SPI控制器进行初始化。XSpiPs_LookupConfig在xparameters.h中查找和SPI有关的参数定义。xparameters.h在该裸机工程的bsp中,bsp根据Vivado导出的硬件平台hw_platform生成。
- XSpiPs_CfgInitialize初始化用户定义的XSpiPs类型,用户操作的每一个SPI设备都需要定义一个相应的XSpiPs变量。
- XSpiPs_Reset对SPI控制器进行复位,使其保持在初始状态。我们看到在最后对写偏移量为XSPIPS_CR_OFFSET的地址的寄存器,即写SPI0的配置寄存器。
- 在ug585中看到SPI相关的控制寄存器如下所示。
- XSpiPs_SetOptions用于设置SPI的工作模式(slave/master、CPOL、CPHA),SPI 默认是 Slave 模式。在其中主要对XSPIPS_CR_OFFSET和XSPIPS_ER_OFFSET寄存器进行了设置。
SpiPs_Read
- SpiPs_Read用于读取指定字节的数据到buff中。
- 先读取XSPIPS_SR_OFFSET寄存器,判断接收RX FIFO是否为空。
- RX FIFO不空时读取其中的数据。注意下面的XSPIPS_RXD_OFFSET寄存器其中只有一个Byte的数据有效。
SpiPs_Send
- SpiPs_Send用于将buff中指定的数据发送。
- 当TX FIFO不满时,尽可能地填充FIFO。下面的米联逻辑感觉应有问题,不应该用FIFO的大小进行判断,应读取寄存器XSPIPS_SR_OFFSET的值,当TX FIFO不满时就尽可能地写。不过这里是用一小段数据进行回环测试,这里就不再细究了。
- 下面读取XSPIPS_SR_OFFSET的值,等待发送结束。