基于FPGA的SPI协议及设计实现

江枫思渺然 提交于 2020-01-01 05:19:06

基于FPGA的SPI协议及设计实现

博主微信:flm13724054952,不懂的有疑惑的也可以加微信咨询,欢迎大家前来投稿,谢谢!

引言介绍

在电子通信领域里采用的通信协议有IIC,SPI,UART,FSMC等协议。本文将基于FPGA来介绍并设计标准的SPI总线协议,实现FPGA与MCU的数据通信。SPI是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口。SPI是一种高速的、全双工、同步通信总线,标准的SPI也仅仅使用4个引脚,常用于FPGA和 EEPROM、FLASH、数字信号处理器等器件的数据通信。

SPI的原理介绍

SPI的通信方式是主从方式通信。这种模式通常只有一个主机和一个从机或者一个主机和多个从机;一般来说,标准的SPI协议是由4根线组成,分别是SSEL(从机片选使能信号,也写作 SCS,CSB)、SCLK(串行时钟,也写作SCK)、MOSI(主机输出从机输入Master Output/Slave Input)和MISO(主机输入从机输出Master Input/Slave Output)。有的SPI接口芯片带有中断信号INT,也有的SPI接口芯片只作为从机使用故只有MISO口,不过这里本文将基于FPGA来介绍并设计标准的SPI总线协议。

SPI的标准接口

CSB:从设备片选使能信号。注意的是,如果从设备是低电平使能信号的有效话,当拉低这个引脚后,从设备就会被选中,主机和这个被选中的从机进行通信。如果从设备是高电平使能信号的有效话,当拉高这个引脚后,从设备就会被选中,主机和这个被选中的从机进行通信
(高低电平的使能信号对数据的采集捕捉有不同的影响,后面再做阐述介绍)。

SCLK:串行时钟信号,由主机产生,和I2C通信的SCL类似。

MOSI:主机输出从机输入线,主机给从机发送指令或者数据的通道。

MISO:主机输入从机输出线,主机读取从机的状态或者数据的通道。

SPI的工作模式

SPI通信的工作模式根据CPOL,CPHA的状态分为四种通信工作模式。为什么根据CPOL,CPHA状态可以有不同的通信模式呢?这里就得介绍CPOL,CPHA的“含义”了。CPHA全称为Clock Polarity,就是时钟的极性。时钟的极性又是什么?就是在SPI通信的整个过程中,分为空闲时刻(在数据通信之前和之后的无工作状态)和通信时刻,如果CPOL=1时,空闲状态的SCLK就是高电平,如果 CPOL=0时,空闲状态的SCLK就是低电平,那么就是 CPOL=0。
在这里插入图片描述
CPHA的全称为Clock Phase,就是时钟的相位。CPHA的状态决定了主机输出数据跟从机采集数据的“捕捉沿的上或下”,同步通信的一个特点就是所有数据的变化和采样都是伴随着时钟沿进行的,也就是说数据总是在时钟的边沿附近变化或被采样。而在一个时钟周期里面有下降沿跟上升沿,只是这两个沿的先后并无规定。
那么主机和从机要交换数据的时候,就会牵涉到一个问题:主机在什么时刻输出数据到MOSI上而从机在什么时刻采样这个数据?如果主机在上升沿输出数据到MOSI上,从机就只能在下降沿去采样这个数据了。反之如果一方在下降沿输出数据,那么另一方就必须在上升沿采样这个数据。
CPHA=1:表示数据的输出是在一个时钟周期的第一个沿上,而数据的采集是在第二个沿上。
至于这个沿是上升沿还是下降沿,这要视CPOL的值而定,CPOL=1 那就是数据输出在下降沿,数据采集在第二个沿(上升沿);反之CPOL=0时那就是数据输出在上升沿,数据采集在下降沿(第二个沿)。
CPHA=0:表示数据的采样是在一个时钟周期的第一个沿上,而数据的输出是在第二个沿上。
同样它是什么沿由 CPOL 决定。CPOL=1的时候那就是数据采集在下降沿,数据输出在上升沿(第二个沿);反之CPOL=0的时候那就是数据采集在上升沿,数据输出在下降沿(第二个沿)。
仔细想一下,这里会有一个问题:就是当一帧数据开始传输第一个 bit 时,在第一个时钟沿上就采样该数据了,那么它是在什么时候输出来的呢?有两种情况:一是 SSEL 使能的边沿,二是上一帧数据的最后一个时钟沿,或者两种都有可能。

SPI的工作时序

从上面的分析介绍可以得到,SPI通信中根据CPOL,CPHA的状态有四种工作时序。下面来介绍SPI的四种工作时序的不同情况,来深入体会SPI协议的设计原理。例如下图为CPOL=0,CPHA=0情况下的SPI工作时序,从下图可以看得出,在空闲状态下(数据的发送前跟数据的接收后)SCK的时钟为低电平,因此CPOL为0。数据流在上升沿(第一个沿)被先采集,再在下降沿被输出(第二个沿),因此CPHA=0;这种工作时序的好处就是数据先采集再输出,就可以保证在一个读写周期里面可以先读写完一个完整的数据流,而不用被延迟到下一个读写周期里面。保证了信号的完整性。
在这里插入图片描述
下图为CPOL=0,CPHA=1情况下的SPI工作时序,从下图可以看得出,在空闲状态下(数据的发送前跟数据的接收后)SCK的时钟为低电平,因此CPOL为0。数据流在上升沿(第一个沿)先输出,再在下降沿被采集(第二个沿),因此CPHA=1;这种工作时序就是数据先输出再采集,从下图就可以就可以看得出,在一个读写周期里面,一个数据流也可以被完整采集到,但是由于先输出(在一个周期里面)会导致输出的第一个数据为上一个数据流的。这样子有一个隐患会造成数据流的不完整。
在这里插入图片描述
剩下的两种工作时序如下图所示,这里不再做详细介绍。
在这里插入图片描述

MCU_SPI的设计实现

本文的设计是基于CPOL=0,CPHA=0情况下的SPI工作时序,时序图如下所示:
在这里插入图片描述
本文的设计架构如下所示,主机通过SPI协议的sdata_in写入地址跟数据,对ram的寄存器进行写数据控制,通过SPI协议的sdata_out读取了从机mcu_ram的相应地址的寄存器数据。所以本文的设计主要有两部分一个是SPI传输协议,一个是MCU_RAM存储器。这里重点介绍SPI协议的设计实现。ram的设计只是为了更好的验证SPI协议的传输功能。
在这里插入图片描述

SPI的设计实现

SPI的端口设计如下所示,主机通过SPI的sdata_in将数据写入到中间寄存器reg_data_shift,再通过产生的wr_flag写入使能信号将相应地址reg_addr_shift的寄存器数据写入到mcu_ram里面。从机想将数据传输到主机时,可读取ram的数据,通过sdata_out传输到主机上。从而构成了一个主从机写入读取数据的一个完整流程。
在这里插入图片描述

SPI的状态机设计

正如上面所说的,对于一个主从机写入读取数据的一个完整流程,主要细分着其实有五个流程的
S0:判断csb的状态,是否启动从机进行读写数据;
S1:判断写入还是读取的流程;
S2:读取主机传输的寄存器地址;
S3:将主机sdata_in传输过来的数据写入到中间寄存器;
S4:将存储在寄存器的数据读取到sdata_out给主机。
对于一个主从机写入读取数据的一个完整流程,有一些人特意对写数据跟读数据设计成两个模块,进行数据交互,这里本文采用状态机的设计思路,不仅完成了读取数据的整个流程,而且使整个流程设计更为严谨简单。
本文对SPI的状态机设计如下所示:
在这里插入图片描述
在这里插入图片描述

SPI的读写设计

SPI的读写设计,可以来说就是按照状态机的相应状态从sdata_in写入数据,读取数据到sdata_out中。这里注意的是我们从机对主机的数据采集都是上升沿采集数据,从下图的设计也可以看的出来。
在这里插入图片描述
S0:判断csb的状态,是否启动从机进行读写数据;此时各寄存器都为复位到初始状态。
S1:判断是写入还是读取的流程;rd_flag=1是读操作rd_flag=0是写操作。在这里插入图片描述
S2:读取主机传输的寄存器地址;将sdtaa_in写入到地址寄存器reg_addr_shift。记住了要操作的地址寄存器。
S3:将主机sdata_in传输过来的数据写入到中间寄存器reg_data_shift。
S4:将存储在寄存器的数据读取到sdata_out_po(即为sdata_out)给主机。
在这里插入图片描述

MCU_RAM的设计实现

这里是简单设计一个存储器mcu_ram进行数据的读写储存,不做详细介绍。
在这里插入图片描述

SPI的仿真验证

这里对MCU_SPI进行功能仿真验证,设计两个任务spi_wr,spi_rd。模仿testbench环境主机对从机mcu_spi进行读写数据。
如下面的功能仿真波形图所示,先写入地址reg_adr=3,reg_dat=56。可以看到当csb=0从机mcu_spi开始工作。在状态1的时候先判断读写,rd_flag=0,即为写操作。在状态2的时候将地址reg_adr=3写入到地址寄存器reg_addr_reg。完成写地址后,进入状态3将reg_dat=56写入到中间寄存器reg_data_shift。完成了一个从机写操作的流程。
当主机想读取从机数据的时候,csb=0从机mcu_spi开始工作,状态1的时候先判断读写,rd_flag=1,即为读操作。在状态2的时候将地址reg_adr=3写入到地址寄存器reg_addr_reg。完成写地址后,进入状态4将存储器的相应地址数据的ram_rd读取到sdata_out。完成了一个读取从机的流程。
在这里插入图片描述
如下面的功能仿真波形图所示,先写入地址reg_adr=3,reg_dat=56。可以看到当csb=0从机mcu_spi开始工作。在状态1的时候先判断读写,rd_flag=0,即为写操作。在状态2的时候将地址reg_adr=3写入到地址寄存器reg_addr_reg。完成写地址后,进入状态3将reg_dat=56写入到中间寄存器reg_data_shift。完成了一个从机写操作的流程的时候,会根据相应的地址将中间寄存器的数据写入到存储器mcu_ram的相应寄存器上去,等待读取的时候被读取。
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!