软件版本:VIVADO2017.4 操作系统:WIN10 64bit 硬件平台:适用米联客 ZYNQ系列开发板 米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!! 10.1 概述 本课讲述ZYNQ PS自带的SPI控制器的使用,本课中使用到了EMIO,测试通过回环测试的方式,演示SPI控制器的使用。
10.2 SPI总线协议技术性能:
接口定义: (1)MOSI:主器件数据输出,从器件数据输入 时钟极性和时钟相位: 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时钟周期内,会完成如下操作:
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文件,或者查看原理图,自行添加管脚约束,并保存。
|
#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); } |
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控制器,然后初始画发送的数据,之后发送数据,最后接收数据,并且把接收的数据,通过串口打印。
网站内容版权所有归 米联客品牌所有,如果网站内容有侵权行为请联系客服热线0519-80699907,本站点会第一时间核对排查并处理 GMT+8, 2025-10-29 08:37