软件版本:VIVADO2017.4 操作系统:WIN10 64bit 硬件平台:适用米联客 ZYNQ系列开发板 米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!! 10.1 概述 本课讲述ZYNQ PS自带的SPI控制器的使用,本课中使用到了EMIO,测试通过回环测试的方式,演示SPI控制器的使用。 10.2 SPI总线协议技术性能: SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(MasterSlave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSBfirst);SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。总线结构如下图所示。 接口定义: SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。 (1)MOSI:主器件数据输出,从器件数据输入 (2)MISO:主器件数据输入,从器件数据输出 (3)SCLK:时钟信号,由主器件产生 (4)/SS:从器件使能信号,由主器件控制
时钟极性和时钟相位: SPI数据的传输是在串行同步时钟信号(Serial Clock,SCK)的控制下进行的。主机的时钟发生器一方面控制主机的移位寄存器,另一方面通过从机的SCK信号线来控制从机的移位寄存器,从而保证主机与从机的数据交换是同步进行的。 SPI串行同步时钟可以设置为不同的极性(Clock Polarity ,CPOL)与相位(Clock Phase ,CPHA)。 时钟的极性(CPOL)用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。当时钟极性为0时(CPOL=0),SCK信号线在空闲时为低电平;当时钟极性为1时(CPOL=1),SCK信号线在空闲时为高电平; 时钟的相位(CPHA)用来决定何时进行信号采样。 当时钟相位为1时(CPHA=1),在SCK信号线的第二个跳变沿进行采样;这里的跳变沿究竟是上升沿还是下降沿?取决于时钟的极性。当时钟极性为0时,取下降沿;当时钟极性为1时,取上升沿;如下图: CPHA=1 的SPI时序 当时钟相位为0时(CPHA=0),在SCK信号线的第一个跳变沿进行采样。跳变沿同样与时钟极性有关:当时钟极性为0时,取上升沿;当时钟极性为1时,取下降沿;如下图: CPHA=0 的SPI时序
数据传输 在一个SPI时钟周期内,会完成如下操作: 1)主机通过MOSI线发送1位数据,从机通过该线读取这1位数据; 2)从机通过MISO线发送1位数据,主机通过该线读取这1位数据。 这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。 10.2 搭建FPGA BD工程Step1:新建一个名为为Miz_sys的工程。 Step2:创建一个BD文件,并命名为system,添加并且配置好ZYNQ IP。读者需要根据自己的硬件类型配置好输入时钟频率、内存型号、串口,连接时钟等。新手不清楚这些内容个,请参考“CH01 HelloWold/DDR/网口测试及固化”这一节课。 做这个实验必须勾选支持SPI控制器,并且把SPI接口的IO以EMIO的方式引出,然后在开发板上把SPI0_MOSI和SPI0_MISO环路短接,这样就可以回环测试了。 此外由于用不到GP0接口,可以取消GP0的设置 FCLK_CLK0用来提供给ILA在线逻辑分析仪使用,我们会用在线逻辑分析仪查看SPI的通信波形 Step3:修改顶层文件,增加ILA IP CORE, 添加需要查看的信号 10.3 添加PIN添加PIN约束SPI0_MISO和SPI0_MOSI对应的管脚分配到EMIO。 Step1:选中PROJECT MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。 Step2:打开提供例程,复制约束文件中的管脚约束到XDC文件,或者查看原理图,自行添加管脚约束,并保存。 10.6 硬件连线短接开发板或者FEP转NEP转接板上的IO1和IO2 MZ7XA、MZ7XB自带此IO 对于没有这组IO的开发板通过FEP转NEP转接卡实现,此转接卡需要单独购买 10.7 实验结果10.8 程序分析10.8.1 SPI 控制器SPI系统模块图 ZYNQ的ARM具有2路SPI控制器,分别为SPI0和SPI1,我们这里用到了SPI0, 本课程实验的SPI接口接到了EMIO上,SPI控制器具有以下特性: 用于Rx / Tx FIFO的存储器映射读/写数据端口(字节宽度) -128字节读取和128字节写入FIFO -可编程FIFO阈值状态和中断 Master I / O模式 -手动和自动开始传输数据 -手动和自动从动选择(SS)模式 -从机选择信号可以直接连接到从机设备或外部扩展 -可编程SS和MOSI延迟 Slave I / O模式 -可编程启动检测模式 Muilti-master I / O功能 -如果未启用控制器,则将I / O缓冲区驱动为3状态 -检测到另一个主站时生成模式故障中断 SCLK时钟 -当I / O是 MIO引脚时最高时钟为50 MHz SCLK -当I / O是EMIO引脚时最高时钟为25 MHz SCLK 可编程时钟相位和极性(CPHA,CPOL) 可编程中断驱动设备或轮询状态 SPI接口模块图 10.8.2 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);
} |
- SpiPs_Init 函数
XSpiPs_LookupConfig(SpiDeviceId)函数在前面的课程中已经提到过很多次了,就是从parameters.h里面查看是否有SPI外设的定义。 XSpiPs_CfgInitialize((&SpiInstance), SpiConfig,SpiConfig->BaseAddress);这个函数在前面的课程也介绍过,对于XILINX的SDK库函数,初始化过程基本一样。 上面的函数中,在最后部分有一个XSpiPs_Reset(InstancePtr);因为注释上也写了是为了让SPI控制器为初始状态 我们可以追踪下这个寄存XSPIPS_CR_OFFSET 上面说明了0x00020000是复位后SPI寄存器,下面是寄存器对应的表,但是似乎没说明哪一位是起复位作用的, XSpiPs_SetOptions((&SpiInstance), XSPIPS_MASTER_OPTION)是用来设置SPI模式的,SPI默认是Slave模式,所以我们必须设置的是Master模式,我们来进入这个函数追踪下 s32 XSpiPs_SetOptions(XSpiPs *InstancePtr, u32 Options) { u32 ConfigReg; u32 Index; u32 CurrentConfigReg; s32 Status;
Xil_AssertNonvoid(InstancePtr != NULL); Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/* * Do not allow the slave select to change while a transfer is in * progress. Not thread-safe. */ if (InstancePtr->IsBusy == TRUE) { Status = (s32)XST_DEVICE_BUSY; } else {
ConfigReg = XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_CR_OFFSET);
CurrentConfigReg = ConfigReg;
/* * Loop through the options table, turning the option on or off * depending on whether the bit is set in the incoming options flag. */ for (Index = 0U; Index < XSPIPS_NUM_OPTIONS; Index++) { if ((Options & OptionsTable[Index].Option) != (u32)0U) { /* Turn it on */ ConfigReg |= OptionsTable[Index].Mask; } else { /* Turn it off */ ConfigReg &= ~(OptionsTable[Index].Mask); } }
/* * If CPOL-CPHA bits are toggled from previous state, * disable before writing the configuration register and then enable. */ if( ((CurrentConfigReg & XSPIPS_CR_CPOL_MASK) != (ConfigReg & XSPIPS_CR_CPOL_MASK)) || ((CurrentConfigReg & XSPIPS_CR_CPHA_MASK) != (ConfigReg & XSPIPS_CR_CPHA_MASK)) ) { XSpiPs_Disable(InstancePtr); }
/* * Now write the Config register. Leave it to the upper layers * to restart the device. */ XSpiPs_WriteReg(InstancePtr->Config.BaseAddress, XSPIPS_CR_OFFSET, ConfigReg);
/* * Enable */ if( ((CurrentConfigReg & XSPIPS_CR_CPOL_MASK) != (ConfigReg & XSPIPS_CR_CPOL_MASK)) || ((CurrentConfigReg & XSPIPS_CR_CPHA_MASK) != (ConfigReg & XSPIPS_CR_CPHA_MASK)) ) { XSpiPs_Enable(InstancePtr); }
Status = (s32)XST_SUCCESS; } return Status; } |
从上面的函数可以看出来,XSpiPs_SetOptions函数里面主要是对XSPIPS_CR_OFFSET寄存器配置,而XSPIPS_CR_OFFSET寄存器在前面已经介绍过。 XSpiPs_Enable((&SpiInstance))函数使能并且启动SPI控制器,追踪下这个函数 可以看到这个函数值对寄存器XSPIPS_ER_OFFSET做了操作 2、SpiPs_Read(u8 *ReadBuffer,int ByteCount) 这个函数是读取SPI 接收FIFO里面的数据 do{ StatusReg = XSpiPs_ReadReg(SpiInstance.Config.BaseAddress, XSPIPS_SR_OFFSET); }while(!(StatusReg & XSPIPS_IXR_RXNEMPTY_MASK));
|
这以上代码中不断判断SPI FIFO是否有数据 如果有数据,在下面代码中接收数据 for(Count = 0; Count < ByteCount; Count++){ ReadBuffer[Count] = SpiPs_RecvByte( SpiInstance.Config.BaseAddress); } |
SpiPs_Send(u8 *SendBuffer, int ByteCount) 这个函数负责发送数据,如下代码 while ((ByteCount > 0) && (TransCount < XSPIPS_FIFO_DEPTH)) { SpiPs_SendByte(SpiInstance.Config.BaseAddress, *SendBuffer); SendBuffer++; ++TransCount; ByteCount--; } |
最后等待发送结束 do { StatusReg = XSpiPs_ReadReg( SpiInstance.Config.BaseAddress, XSPIPS_SR_OFFSET); } while ((StatusReg & XSPIPS_IXR_TXOW_MASK) == 0); |
10.8.3 spi_test.c #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; } |
在main 函数中,首先初始化SPI控制器,然后初始画发送的数据,之后发送数据,最后接收数据,并且把接收的数据,通过串口打印。 |