Zedboard中的SPI通信记录文档(已实现)

SPI记录文档备份

CSDN博客的bug就是传图片太麻烦,所以我把文档上传了,点击下载地址,即可下载。

以下只是文字版的,没有图片,看起来有点乱,建议去下载。

概念

SPI是串行外设接口(SerialPeripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。SPI是利用4根信号线进行通信的串行接口协议,4个接口信号为:串行数据输入(MISO,主设备输入、从设备输出)、串行数据输出(MOSI,主设备输出、从设备输入)、移位时钟(SCK)低电平有效的从设备使能信号(cs)

zedboard有两个独立的SPI,可以工作在主/从模式或者多主机模式下,

主模式:

数据传输和片选 都可以手工或者自动(需要配置)

发送数据、接收数据、片选从设备(直接连接上即可)

 

相关寄存器

 

1.       Config_reg0(0xE0006000): SPI配置寄存器

2.       Intr_status_reg0(0xE0006004:中断状态寄存器,wtc。

3.       Intrpt_en_reg0(0xE0006008):中断使能寄存器,这里面有多个中断

4.       Intrpt_dis_reg0(0xE000600C):中断不使能寄存器,只写

5.       Intrpt_mask_reg0(0xE0006010):中断屏蔽寄存器,只读

6.       En_reg0(0xE0006014):SPI使能寄存器

7.       Delay_reg0(0xE0006018):延时寄存器

8.       Tx_data_reg0(0xE000601C):发送数据寄存器,只写。Data toTX FIFO. Valid data bits are [7:0].

9.       Rx_data_reg0(0xE0006020):接收数据寄存器,只读。Datafrom RX FIFO. Valid data bits are [7:0]

10.   Slave_Idle_count_reg0(0xE0006024):从空闲计数寄存器

11.   TX_thres_reg0(0xE0006028):发送阈值寄存器。TX_FIFOThreshold Register

Defines the level at which the TX FIFOnot full interrupt is generated

12.   RX_thres_reg0(0xE000602C):接收阈值寄存器。RX FIFOThreshold Register

Defines the level at which the RX FIFO not emptyinterruptis generated

13.   Mod_id_reg0(0xE00060FC):模块ID寄存器。Module ID register

 

 

 

中断号:SPI1是81号中断,SPI0是58号中断

中断寄存器的几个相关值

0x14: rx fifo not empty  tx fifo not full

0x10: rx fifo not empty

 

特点

1. 采用主-从模式(Master-Slave) 的控制方式

SPI 规定了两个 SPI 设备之间通信必须由主设备(Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作.

 

2. 采用同步方式(Synchronous)传输数据

Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) ,时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.

 

3. 数据交换(DataExchanges)

SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.

 

一个 Slave 设备要想能够接收到Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access). 所以, Master设备必须首先通过 SS/CS pin Slave 设备进行片选, 把想要访问的 Slave 设备选上.

 

在数据传输的过程中,  每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃,  导致 SPI 物理模块最终失效. 因此, 在程序中一般都会在 SPI传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的.

 

注意

1.       Master 设备的片选操作必须由程序所实现. 例如: 由程序把 SS/CS 管脚的时钟信号拉低电平, 完成 SPI 设备数据通信的前期工作; 当程序想让 SPI 设备结束数据通信时, 再把 SS/CS 管脚上的时钟信号拉高电平.

 

2.       在每个时钟周期内,Master 与 Slave 之间交换的数据其实都是 SPI 内部移位寄存器从 SSPBUF 里面拷贝的. 我们可以通过往 SSPBUF 对应的寄存器 (Tx-Data / Rx-Data register) 里读写数据, 间接地操控 SPI 设备内部的 SSPBUF.

 

3.       时钟信号通过时钟极性(CPOL) 和时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.

重点!!!:

4.       主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机(如图2所示)。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。

如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

当主机发送一个连续的数据流时,有些外设能够进行多字节传输。多数具有SPI接口的存储芯片就以这种方式工作。在这种传输方式下,从机的片选端必须在整个传输过程中保持低电平。此时,一次传输可能会涉及到成千上万字节的信息,而不必在每个字节的数据发送的前后都去检测其起始位和结束位,这正是同步传输方式优于异步传输方式的原因所在。

  虽然SPI有以上优点,然而在图像传输中却很少用到,原因主要是其抗干扰能力差。SPI采用的是单端非平衡的传输方式,即传输的数据位的电压电平是以公共地作为参考的。在这种传输方式中,对于已进入信号中的干扰是无法消除和减弱的。而信号在传输过程中总会受到干扰,而且距离越长干扰越严重,以致于信号传输产生错误。在这种条件下,信号传输就变得毫无意义了。另外,由于单端非平衡传输方式以公共地作为参考点,地线作为信号回流线,因此也存在信号电流。当传输线两端的系统之间存在交流电位差时,这个电位差将直接窜到信号中,形成噪声干扰。所以,为了解决抗干扰问题,通常采用平衡传输(balancedtransmission)方式,这里采用比较常见的RS-422

 

最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。

SPI是一个同步协议接口,所有的传输都参照一个共同的时钟,这个同步时钟信号由主机(处理器)产生,接收数据的外设(从设备)使用时钟来对串行比特流的接收进行同步化。可能会有许多芯片连到主机的同一个SPI接口上,这时主机通过触发从设备的片选输入引脚来选择接收数据的从设备,没有被选中的外设将不会参与SPI传输。SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。

 

 

发送数据的流程三步

0.  片选从设备

1.       /*将发送数据写入Tx FIFO*/

         for(i = 0; i < 2; i++)

         {

                   Write_Reg(0xE000601C,TxBuffer[i]);

         }

 

2.       /*开始发送*/

         Reg = Read_Reg(0xE0006000);

         Reg |= 0x00010000;

         Write_Reg(0xE0006000, Reg);

 

3.       /*等待发送完成*/

         while((Read_Reg(0xE0006004) &0x00000004) == 0);

 

 

vivado创建工程

1.      zynq包含两个SPI控制器,可以工作在主/从模式或者多主机模式下。zedboard的扩展外PMOD在PL端有4个,在PS部分有1个。可编程逻辑端通过SPI协议实现硬件设备间的通信,通过Pmod口实现硬件设备的物理连接。其中SPI协议传输只用到Pmod的4个引脚,SPI的4个信号线SS、MOSI、MISO、SCK分别连接到Pmod的JA1、JA2、JA3、JA4(PL部分),也可以通过PS部分的JE1(PS部分)。

2.       搭建SPI有两种方式:

1.       通过PL端的PMOD JA/JB/JC/JD,可选SPI0、SPI1。配置相应的PMOD引脚

2.       通过PS端的PMOD JE,选择SPI1因为引脚正好匹配,可省略。

3.引脚连接图

 

 

 

 

 

 

 

 

Spi0 by EMIO pmod JA 

SPI1 by MIO PMODJE

 

硬件都是统一的,即在vivado上设计是一样的,主从的分配是寄存器的设置,是在sdk arm端配置选择主从模式,来确定哪个是主,哪个是从。

 

要想用uart打印,需要在vivado上选择uart1!

中断的程序是好的,可以拿来当做测试用

两个板子通SPI数据传输初步方案

zynq支持两个SPI控制器,选用SPI0,路由到EMIO,通过PMOD JA1互连

  选用两个板子,一个作主,一个作从。

  分别编写主从的程序,先实现数据发送成功,通过示波器观察时钟及数据的波形。

  在此基础上再添加TDMA及MAC的接入

时钟:

Ps的IO口是160mhz,8分频得到20MHZ

 

SPI的时钟获得:

I/O PLLàSPI_Ref_ClkàSCLK

160MHZ—》

Resets

//重置控制SPI 1

Write_Reg(SPI_RST_CTRL, 0x0A);

usleep(100);

Write_Reg(SPI_RST_CTRL, 0x00);

 

//重置控制SPI 0

Write_Reg(SPI_RST_CTRL, 0x05);

usleep(100);

Write_Reg(SPI_RST_CTRL, 0x00);

//SPI0

SPI_Ref_Clk 来自IO PLL, 是160MHZ,1分频得到

Write_Reg(SPI_CLK_CTRL , 0x0101);

 

SCLK由SPI_Ref_Clk分频得到

 

//SPI1

SPI_Ref_Clk 来自IO PLL 是160MHZ,1分频得到

Write_Reg(SPI_CLK_CTRL , 0x0102);

 

SCLK由SPI_Ref_Clk分频得到

 


 

需要测试的情况

1.   简单的一次发-收:主设备发,从设备收(√)

2.   中断改为发送共同,中断用一套程序(√)

3.   主设备发:间隔发送两次, 检查从设备收是否可以(√)

4.   作为仿真:想发的时候就发,有数据就接收。

一般情况下是配置收,需要发送时配置发送即可,发送完毕,关闭发送的配置,再次回到配置收的状态。(这个想法是可以的,但是实施起来并不简单,收发的切换操作在这个地方不合适)

更简单易操作的方式:两组SPI,即相当于全双工的,能发能收,发和收采用不同的SPI。

5.   用按键产生中断来操作发送!!(√)

6.   发送不用中断(查询状态位即可),接收用中断!!!!(√)

遇到的问题:

参考UG585

1.       如何确定主从设备,即谁是主,谁是从?

如果是两个zedboard板子,软件设置不一样!区分主从,

2.       主设备的CLK由ps提供?

Theserial clock is derived from the PS clock subsystem。

3.       SPIREF clock,相关时钟, SCLK串行时钟,前者至少为后者的2倍

4. SPI是不是全双工的,不用收发切换?(是全双工的,收发切换不太好操作)

 

调试出现的异常情况:

在vivado上重新综合实现,产生bit stream,再开启SDK

 

 

用vivado重新打开SDK即可。

 

 


 

调试记录SPI1

主设备发送:

 

从设备接收:

主设备发送,其RX_FIFO也是有数据的,来自从设备的0?(如下两图),

 

所以要在程序中的发送时,等待发送完毕,

或者发送时,不开启接收的有关中断,等待发送完毕后再开启接收中断。

 

 

一开始发送,就会产生两个中断://tx_no_full   rx_not_empty

 

发送数据时,自己的rx_fifo也是有中断产生的:

 

 

发送完毕后,会产生tx_no_full的中断:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Sleep(3)。多次发送,接收ok

 

 

 

如下,等待发送完成,就不会产生发送时收到的rx_not_empty中断

调试记录SPI0

除了连接的时候接线方式与SPI1不一样,其他均相同。具体连接方式见后面的调试记录

 

调试记录SPI1和SPI0

双方向传输:

1.测试记录

节点1  

节点2 

测试结果

SPI_0_1收发一体(发)

SPI_0_1收发一体(收)

Ok

SPI_0_1收发一体(收)

SPI_0_1收发一体(发)

Ok

 

 

 

 

2.连线方式

SPI1路由到PMOD-JE上,SPI0路由到PMOD-JA上,

节点1的PMOD-JE连接节点2的PMOD-JA,

节点2的PMOD-JE连接节点1的PMOD-JA,引脚分配如下:

SPI1发送

 

 

 

SPI0接收

JE4

MIO12

SCLK

Y10

JA3

JE3

MIO11

MISO

AA11

JA2

JE2

MIO10

MOSI

Y11

JA1

JE1

MIO13

SS[0]

AB10

JA8

 

 

3.串口调试显示

节点1的显示:

 

节点2的显示:

4.发送操作

按键BTN8是发送的操作按键,即按键BTN8产生中断,启动SPI的发送函数。

按下节点1的BTN8,即SPI1发送数据到节点2(节点2的SPI0产生中断接收)。

按下节点2的BTN8,即SPI1发送数据到节点1(节点1的SPI0产生中断接收)。

网络答疑

如果用主从机切换的方法的话,切换的时机不好掌握,这个问题在主机和从机都想往对方送信的情况下尤其明显,比较方便的方法是主机定周期发送数据(也可以是空数据包),同时收取从机数据包,一般情况下都可以满足通信要求

 

使用SPI接口读取从机的数据时,主机为什么要发送数据?

因为读数据也要主机发出时钟,一般的SPI口主机,发送一个数据的时候才会启动时钟,所以要发数据

 

SPI通信方式从机不能主动给主机发送数据。因为SPI是全双工的,主机在发送数据的同时也在接收数据。所以,主机可以通过查询(发送数据给从机,查看接收到的数据)方式来判断从机是否有数据发送给主机,如果有,则主机继续发送数据给从机来获取从机想要发送给主机的数据。

 

 

 

中断外处理接收数据的做法思路

1.       两个板子当做两个节点,比如节点1和节点2.

2.       发送数据,两种做法

A. 节点间发送数据,依靠按键中断触发发送。按键按下,即表示要发送数据。(测试ok)

B. 在main函数的while循环中,间隔5ms循环发送数据。

3.       接收数据

A. 在中断处理函数中接收数据,这种方式不太好,处理时不应该占用中断,会影响下一次或者其他中断。

B. 在中断处理函数中设置标志位,在main函数中检测该标志位,置位时调用接收函数去处理数据,并将标志位清零。

 

 

中断做法测试记录

1.       5ms间隔发送,在中断中处理接收数据,会出现问题。

发送的数据太快,rxfifo 发生覆盖。

 

2.       主机间隔1s发送,在中断中处理收数据,ok

3.       中断rx notempty 后,关闭SPI0总中断且设置标志位rx_flag,在while中处理收数据,程序一直进入到rx not empty中断?,没有进入到收数据函数中。。。。

 

经过多次试验,确定关闭SPI0的总中断不可以,关闭rxnot empty中断后可以:收到rx not empty中断后,关闭该中断,并rx_flag=1,while循环中检测到rx_flag=1,置零后进入接收函数处理接收到的数据,然后再次打开rx not empty中断,等待下次的中断。

 

4.       主机间隔1s,发送20字节数据,在中断外处理接收数据,ok

5.       主机间隔1s,发送100字节数据,在中断外处理接收数据,ok

 

 

 

 

 

 

 

 


 

 

6.       主机间隔5ms,发送20字节数据,在中断外处理接收数据。

数据发生了较多的积压,发送速度大于读取数据的速度。

 

 

 


 

7.       主机间隔10ms,发送20字节数据,在中断外处理接收数据

数据发生了部分积压,发送速度大于读取数据的速度

 


 

8.       主机间隔15ms,发送20字节数据,在中断外处理接收数据,

数据没有积压现象,但根据打印结果分析,间隔15ms发送速度还是较快。

 


 

9.       节点1和节点2分别中断发送和接收,ok

 


 

10.   节点2间隔1s发送数据给节点1,节点1收到后回复节点2,ok


 

11.   节点2间隔5ms发送数据给节点1,节点1收到后回复节点2,

数据出现大量积压状况,发送速度太快,读取不过来(确切是打印,打印费时)


 

12.   节点2间隔20ms、25ms发送数据给节点1,节点1收到后回复节点2,

数据没有出现大量积压状况,但是根据打印结果分析,发送的速度还是略快


 

Debug调式和开辟buff二维数组

1.      程序要在debug模式下跑,添加统计变量,尽量不要使用串口,串口是打印操作,费时,调试也不直观,使用全局变量统计,方便调试。

2.      找PS端或者PL端的IO口连接示波器,查看波形,计算TX和RX的时间。

3.      GPIO口,只能用PL端的,要在vivado上分配引脚,配置EMIO口。

4.      发送80字节,通过示波器查看TX和RX的时间,这里按照完成的一包的时间进行TX和RX,测试发送和接收的时间,方便后面的时隙安排。

单独测试:

主  à从

 

主  ß从

 

SPI0àSPI0

Ok

SPI0ßSPI0

Ok

SPI1àSPI1

Ok

SPI1ßSPI1

Ok

SPI1àSPI0

Fail

SPI0ßSPI1

Fail

 


 

库函数

s32 XSpiPs_Transfer(XSpiPs *InstancePtr,u8 *SendBufPtr,u8 *RecvBufPtr,u32 ByteCount);

SPI总线上传输指定的数据。如果将SPI设备配置为master,该函数将启动总线通信,并将数据发送到所选的SPI奴隶。如果SPI设备被配置为一个奴隶,那么这个函数将准备在主选中时发送/接收的缓冲区。对于发送的每个字节,接收一个字节。该函数应该用于执行基于中断的传输。

 

调用者可以选择为发送和接收提供两个不同的缓冲区,或者为发送和接收提供一个缓冲区,或者不为接收提供缓冲区。接收缓冲区必须至少与发送缓冲区一样大,以防止不必要的内存写入。这意味着,作为参数传递的字节数,如果它们的大小不同,则必须是两个缓冲区中较小的一个。下面是一些示例用法:

 

XSpiPs_Transfer(InstancePtrSendBufRecvBuf ByteCount)

调用者希望发送和接收,并为发送和接收提供两个不同的缓冲区。

XSpiPs_Transfer(InstancePtr,SendBuf,NULL,ByteCount)

调用者只希望发送并且不关心接收到的数据。在本例中,驱动程序忽略了接收到的数据。

XSpiPs_Transfer(InstancePtr,SendBuf,SendBuf,ByteCount)

调用者希望发送和接收,但是提供相同的缓冲区。驱动程序发送数据并覆盖发送

缓冲区在传输数据时接收到的数据。

XSpiPs_Transfer(InstancePtr,RecvBuf,RecvBuf,ByteCount)

调用者希望只接收并且不关心发送数据。在这种情况下,调用者仍然必须提供一个发送缓冲区,但是如果调用者不关心它发送的内容,则它可能与接收缓冲区相同。如果要接收N字节的数据,设备必须发送N字节的数据。

 

虽然这个函数以整个缓冲区为参数,但是驱动程序一次只能传输有限的字节数,受FIFO的大小限制。对这个函数的调用只启动传输,然后数据的后续传输由中断服务例程执行,直到整个缓冲区被传输为止。当整个缓冲区被发送/接收时,将调用状态回调函数。

这个函数是阻塞。作为一个主,必须在此函数之前调用SetSlaveSelect函数。

 

 

 

void XSpiPs_SetStatusHandler(XSpiPs *InstancePtr,void *CallBackRef,XSpiPs_StatusHandler FunctionPtr)

设置状态回调函数,状态处理程序,当它遇到应该报告到上层软件的条件时,驱动程序调用它。处理程序在中断上下文中执行,因此必须减少执行的处理量。下面的状态事件之一传递给状态处理程序。


 

SPI1发送和接收测试记录

查看示波器发送数据的波形

统计变量:正确包计数和错误包计数

1.      间隔1s 发送200包数据,ok

2.      间隔5ms 发送5000包数据,ok

 

SPI0和SPI1整合测试记录

经过多次调试和查看UG585,完成了SPI之间的通信。注意程序中的usleep的使用。

1 直线连接

 

2 交叉连接

3.      节点1à节点2单向发送5000包,每包80字节,测试ok。

节点2à节点1单向发送5000包,每包80字节,测试ok。

节点1<==>节点2,双向互发5000包,每包80字节,测试ok

测试结果分析,发送80字节,需要40us,读取接收到的80字节数据需要100us

如上图:单节点发送80字节,需要40us

如上图:节点1发送80字节给节点2,节点2接收并读取完毕后,发送80字节给节点1,从图中看出发送80字节大约40us,接收并读取80字节大约100us。


 

SPI模拟物理底层传输方案SDL

节点1间隔10ms给一次tx_ind,发送数据5000次,ok

节点1间隔5ms给一次tx_ind,发送数据5000次,ok

有上图得出,节点1发送一次数据到节点2接收完,即一次完整的传输大约需要500us。

 

 

你可能感兴趣的:(ARM,ZedBoard)