[X]关闭
 专注集成电路FPGA应用生态推广
 微信公众号
米联客
米联客uisrc 首页 Xilinx课程 19版ZYNQ 2-ZYNQ入门 查看内容
0

S02-CH10 SPI通信测试实验

摘要: 软件版本:VIVADO2017.4 操作系统:WIN10 64bit 硬件平台:适用米联客 ZYNQ系列开发板 米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!! 10.1 概述 本课讲述ZYNQ PS自带的SPI控制器的 ...

软件版本: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);


}


  1. 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控制器,然后初始画发送的数据,之后发送数据,最后接收数据,并且把接收的数据,通过串口打印。


路过

雷人

握手

鲜花

鸡蛋

说点什么...

已有0条评论

最新评论...

本文作者
2019-9-6 19:21
  • 6
    粉丝
  • 5192
    阅读
  • 0
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜

关注我们:微信订阅号

官方微信公众号

官方微信公众号

客服服务热线:

0519-80699907

常州-总部:常州溧阳市中关村吴潭渡路雅创高科智造谷10-1幢楼

南京-分部:南京市栖霞区仙林大道181号5幢2220/2221室

邮编:213300 Email:tjy@uisrc.com

Copyright   ©2020-2026 内容版权归©UISRC.COM    ( 苏ICP备19046771号-2 )