SPI总线协议概述

2023-11-18

一.概述

         SPI(serial peripheral interface)是一种同步串行通信协议,由一个主设备和一个或多个从设备组成,主设备启动与从设备的同步通信,从而完成数据的交换。SPI是一种高速全双工同步通信总线,标准的SPI仅仅使用4个引脚,主要应用在 EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC), 数字信号处理器(DSP) 以及数字信号解码器之间。有迹象表明,SPI总线首次推出是在1979年,Motorola公司将SPI总线集成在他们第一支改自68000微处理器的微控制器芯片上。由于在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间。 正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术。

二.特点

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)传输数据

CPOL:clock polarity 时钟的极性;表示 SPI 在空闲时, 时钟信号是高电平还是低电平. 

CPHA:clock phase 时钟的相位;表示 SPI 设备是在 SCK 管脚上的时钟信号变为上升沿时触发数据采样, 还是在时钟信号变为下降沿时触发数据采样.

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

SPI总线传输的模式:

  SPI总线传输一共有4中模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。这四种模式的时序图如下图所示:

模式0:CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换

模式1:CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换

模式2:CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换

模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换

其中比较常用的模式是模式0和模式3。为了更清晰的描述SPI总线的时序,下面展现了模式0下的SPI时序图:

 

上图清晰的表明在模式0下,在空闲状态下,SCK串行时钟线为低电平,当SS被主机拉低以后,数据传输开始,数据线MOSI和MISO的数据切换(Toggling)发生在时钟的下降沿(上图的黑色虚线),而数据线MOSI和MISO的数据的采样(Sampling)发生在数据的正中间(上图中的灰色实线)。下图清晰的描述了其他三种模式数据线MOSI和MISO的数据切换(Toggling)位置和数据采样位置的关系图:

3. 数据交换(Data Exchanges)

       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.概述     

               

上图只是对 SPI 设备间通信的一个简单的描述, 下面就来解释一下图中所示的几个组件(Module):

       SSPBUF, Synchronous Serial Port Buffer, 泛指 SPI 设备里面的内部缓冲区, 一般在物理上是以 FIFO 的形式, 保存传输过程中的临时数据;

       SSPSR, Synchronous Serial Port Register, 泛指 SPI 设备里面的移位寄存器(Shift Regitser), 它的作用是根据设置好的数据位宽(bit-width) 把数据移入或者移出 SSPBUF;

       Controller, 泛指 SPI 设备里面的控制寄存器, 可以通过配置它们来设置 SPI 总线的传输模式.

        通常情况下, 我们只需要对上图所描述的四个管脚(pin) 进行编程即可控制整个 SPI 设备之间的数据通信:

        SCK, Serial Clock, 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;

        SS/CS, Slave Select/Chip Select, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;

        SDO/MOSI, Serial Data Output/Master Out Slave In, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;

        SDI/MISO, Serial Data Input/Master In Slave Out, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据;

        SPI 设备在进行通信的过程中, Master 设备和 Slave 设备之间会产生一个数据链路回环(Data Loop), 就像上图所画的那样, 通过 SDO 和 SDI 管脚, SSPSR 控制数据移入移出 SSPBUF, Controller 确定 SPI 总线的通信模式, SCK 传输时钟信号。

  • SDO     – 主设备数据输出,从设备数据输入
  • SDI      – 主设备数据输入,从设备数据输出
  • SCLK   – 时钟信号,由主设备产生
  • CS        – 从设备使能信号,由主设备控制

        CS: 其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效,这就允许在同一总线上连接多个SPI设备成为可能;

  SDI/SDO/SCLK: 通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变上沿和下沿为一次),就可以完成8位数据的传输;

       要注意的是,SCK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据,也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档;

      在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。

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

2. Timing.    

上图通过 Master 设备与 Slave 设备之间交换1 Byte 数据来说明 SPI 协议的工作机制.

上图里的 "Mode 1, 1" 说明了本例所使用的 SPI 数据传输模式被设置成 CPOL = 1, CPHA = 1. 这样, 在一个 Clock 周期内, 每个单独的 SPI 设备都能以全双工(Full-Duplex) 的方式, 同时发送和接收 1 bit 数据, 即相当于交换了 1 bit 大小的数据.对于主设备,数据输出在时钟下降沿,数据读入在时钟上升沿; 如果 SPI 总线的 Channel-Width 被设置成 Byte, 表示 SPI 总线上每次数据传输的最小单位为 Byte, 那么挂载在该 SPI 总线的设备每次数据传输的过程至少需要 8 个 Clock 周期(忽略设备的物理延迟). 因此, SPI 总线的频率越快, Clock 周期越短, 则 SPI 设备间数据交换的速率就越快.

 3. SSPSR

SSPSR 是 SPI 设备内部的移位寄存器(Shift Register). 它的主要作用是根据 SPI 时钟信号状态, 往 SSPBUF 里移入或者移出数据, 每次移动的数据大小由 Bus-Width 以及 Channel-Width 所决定.

        Bus-Width 的作用是指定地址总线到 Master 设备之间数据传输的单位.
        例如, 我们想要往 Master 设备里面的 SSPBUF 写入 16 Byte 大小的数据: 首先, 给 Master 设备的配置寄存器设置 Bus-Width 为 Byte; 然后往 Master 设备的 Tx-Data 移位寄存器在地址总线的入口写入数据, 每次写入 1 Byte 大小的数据(使用 writeb 函数); 写完 1 Byte 数据之后, Master 设备里面的 Tx-Data 移位寄存器会自动把从地址总线传来的1 Byte 数据移入 SSPBUF 里; 上述动作一共需要重复执行 16 次.

        Channel-Width 的作用是指定 Master 设备与 Slave 设备之间数据传输的单位. 与 Bus-Width 相似,  Master 设备内部的移位寄存器会依据 Channel-Width 自动地把数据从 Master-SSPBUF 里通过 Master-SDO 管脚搬运到 Slave 设备里的 Slave-SDI 引脚, Slave-SSPSR 再把每次接收的数据移入 Slave-SSPBUF里.

        通常情况下, Bus-Width 总是会大于或等于 Channel-Width, 这样能保证不会出现因 Master 与 Slave 之间数据交换的频率比地址总线与 Master 之间的数据交换频率要快, 导致 SSPBUF 里面存放的数据为无效数据这样的情况.
4. SSPBUF.

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

          例如, 在发送数据之前, 我们应该先往 Master 的 Tx-Data 寄存器写入将要发送出去的数据, 这些数据会被 Master-SSPSR 移位寄存器根据 Bus-Width 自动移入 Master-SSPBUF 里, 然后这些数据又会被 Master-SSPSR 根据 Channel-Width 从 Master-SSPBUF 中移出, 通过 Master-SDO  管脚传给 Slave-SDI 管脚,  Slave-SSPSR 则把从  Slave-SDI 接收到的数据移入 Slave-SSPBUF 里.  与此同时, Slave-SSPBUF 里面的数据根据每次接收数据的大小(Channel-Width), 通过 Slave-SDO 发往 Master-SDI, Master-SSPSR 再把从 Master-SDI 接收的数据移入 Master-SSPBUF.在单次数据传输完成之后, 用户程序可以通过从 Master 设备的 Rx-Data 寄存器读取 Master 设备数据交换得到的数据.

5. Controller.

Master 设备里面的 Controller 主要通过时钟信号(Clock Signal)以及片选信号(Slave Select Signal)来控制 Slave 设备. Slave 设备会一直等待, 直到接收到 Master 设备发过来的片选信号, 然后根据时钟信号来工作.

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

SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。

  • 如果 CPOL=0,串行同步时钟的空闲状态为低电平;
  • 如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。
  • 如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;
  • 如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设备时钟相位和极性应该一致

SPI主模块和与之通信的外设备时钟相位和极性应该一致。个人理解这句话有2层意思:其一,主设备SPI时钟和极性的配置应该由外设来决定;其二,二者的配置应该保持一致,即主设备的SDO同从设备的SDO配置一致,主设备的SDI同从设备的SDI配置一致。因为主从设备是在SCLK的控制下,同时发送和接收数据,并通过2个双向移位寄存器来交换数据。

例如:上升沿主机SDO发送数据1,同时从设备SDO发送数据0;紧接着在SCLK的下降沿的时候从设备的SDI接收到了主机发送过来的数据1,同时主机也接收到了从设备发送过来的数据0.也就是说主从设备在同一个沿发送数据,在同一个沿接收数据!

SPI接口时钟配置心得

       在主设备这边配置SPI接口时钟的时候一定要弄清楚从设备的时钟要求,因为主设备这边的时钟极性和相位都是以从设备为基准的。因此在时钟极性的配置上一定要搞清楚从设备是在时钟的上升沿还是下降沿接收数据,是在时钟的下降沿还是上升沿输出数据。但要注意的是,由于主设备的SDO连接从设备的SDI,从设备的SDO连接主设备的SDI,从设备SDI接收的数据是主设备的SDO发送过来的,主设备SDI接收的数据是从设备SDO发送过来的,所以主设备这边SPI时钟极性的配置(即SDO的配置)跟从设备的SDI接收数据的极性是相反的,跟从设备SDO发送数据的极性是相同的。下面这段话是Sychip Wlan8100 Module Spec上说的,充分说明了时钟极性是如何配置的:

The 81xx module will always input data bits at the rising edge of the clock, and the host will always output data bits on the falling edge of the clock.

意思是:主设备在时钟的下降沿发送数据,从设备在时钟的上升沿接收数据。因此主设备这边SPI时钟极性应该配置为下降沿有效。

又如,下面这段话是摘自LCD Driver IC SSD1289:

SDI is shifted into 8-bit shift register on every rising edge of SCK in the order of data bit 7, data bit 6 …… data bit 0.

意思是:从设备SSD1289在时钟的上升沿接收数据,而且是按照从高位到地位的顺序接收数据的。因此主设备的SPI时钟极性同样应该配置为下降沿有效。

时钟极性和相位配置正确后,数据才能够被准确的发送和接收, 因此应该对照从设备的SPI接口时序或者Spec文档说明来正确配置主设备的时钟;

四.传输时序

SPI接口有四种不同的数据传输时序,取决于时钟极性(CPOL)和时钟相位(CPHA)的组合。时钟相位设置读取数据和发送数据的时钟沿。主机和从机发送数据是同时完成的,接收数据也是同时完成的。
时钟极性CPOL:SPI在空闲时,时钟信号是高电平还是低电平,即SCLK发送8 bit数据之前和之后的状态。CPOL=0,空闲电平为低电平,CPOL=1,空闲电平为高电平。
时钟相位CPHA:数据采样(对于主机来说就是接收数据)在时钟的第几个边沿。CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。

Bit1为MSB,Bit8为LSB。假设CPOL=0,CPHA=0。在SCK的第一个时钟周期,在时钟的前沿采样数据(上升沿),在时钟的后沿输出数据。先看主器件,主器件的输出口(MOSI)输出数据bit1,在时钟的前沿被从器件采样,那主器件是何时输出bit1的呢?bit1的输出时刻实际上在SCK信号有效以前,比SCK的上升沿还要早半个时钟周期,bit1的输出时刻与SSEL信号没有关系。再来看从器件,主器件的输入口MISO同样是在时钟的前沿采样从器件输出的bit1的,那从器件又是在何时输出bit1的呢?从器件实在SSEL信号有效后,立即输出bit1,尽管此时SCK信号还没有生效。

五.设计思路与Verilog代码编写

下面将以模式0为例用Verilog编写SPI通信的代码;Verilog编写的SPI模块除了进行SPI通信的四根线以外还要包括一些时钟、复位、使能、并行的输入输出以及完成标志位。其框图如下所示

 

其中:

  I_clk是系统时钟;

  I_rst_n是系统复位;

  I_tx_en是主机给从机发送数据的使能信号,当I_tx_en为1时主机才能给从机发送数据;

  I_rx _en是主机从从机接收数据的使能信号,当I_rx_en为1时主机才能从从机接收数据;

  I_data_in是主机要发送的并行数据;

  O_data_out是把从机接收回来的串行数据并行化以后的并行数据;

  O_tx_done是主机给从机发送数据完成的标志位,发送完成后会产生一个高脉冲;

  O_rx_done是主机从从机接收数据完成的标志位,接收完成后会产生一个高脉冲;

  I_spi_miso、O_spi_cs、O_spi_sck和O_spi_mosi是标准SPI总线协议规定的四根线;

要想实现上文模式0的时序,最简单的办法还是设计一个状态机。为了方便说明,这里把模式0的时序再在下面贴一遍:

由于是要用FPGA去控制或读写QSPI Flash,所以FPGA是SPI主机,QSPI是SPI从机。

  发送:当FPGA通过SPI总线往QSPI Flash中发送一个字节(8-bit)的数据时,首先FPGA把CS/SS片选信号设置为0,表示准备开始发送数据,整个发送数据过程其实可以分为16个状态:

    状态0:SCK为0,MOSI为要发送的数据的最高位,即I_data_in[7]

    状态1:SCK为1,MOSI保持不变

    状态2:SCK为0,MOSI为要发送的数据的次高位,即I_data_in[6]

    状态3:SCK为1,MOSI保持不变

    状态4:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[5]

    状态5:SCK为1,MOSI保持不变

    状态6:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[4]

    状态7:SCK为1,MOSI保持不变

    状态8:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[3]

    状态9:SCK为1,MOSI保持不变

    状态10:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[2]

    状态11:SCK为1,MOSI保持不变

    状态12:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[1]

    状态13:SCK为1,MOSI保持不变

    状态14:SCK为0,MOSI为要发送的数据的最低位,即I_data_in[0]

    状态15:SCK为1,MOSI保持不变

一个字节数据发送完毕以后,产生一个发送完成标志位O_tx_done并把CS/SS信号拉高完成一次发送。通过观察上面的状态可以发现状态编号为奇数的状态要做的操作实际上是一模一样的,所以写代码的时候为了精简代码,可以把状态号为奇数的状态全部整合到一起。

接收:当FPGA通过SPI总线从QSPI Flash中接收一个字节(8-bit)的数据时,首先FPGA把CS/SS片选信号设置为0,表示准备开始接收数据,整个接收数据过程其实也可以分为16个状态,但是与发送过程不同的是,为了保证接收到的数据准确,必须在数据的正中间采样,也就是说模式0时序图中灰色实线的地方才是代码中锁存数据的地方,所以接收过程的每个状态执行的操作为:

状态0:SCK为0,不锁存MISO上的数据

    状态1:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[7]

    状态2:SCK为0,不锁存MISO上的数据

    状态3:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[6]

    状态4:SCK为0,不锁存MISO上的数据

    状态5:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[5]

    状态6:SCK为0,不锁存MISO上的数据

    状态7:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[4]

    状态8:SCK为0,不锁存MISO上的数据

    状态9:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[3]

    状态10:SCK为0,不锁存MISO上的数据

    状态11:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[2]

    状态12:SCK为0,不锁存MISO上的数据

    状态13:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[1]

    状态14:SCK为0,不锁存MISO上的数据

    状态15:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[0]

  一个字节数据接收完毕以后,产生一个接收完成标志位O_rx_done并把CS/SS信号拉高完成一次数据的接收。通过观察上面的状态可以发现状态编号为偶数的状态要做的操作实际上是一模一样的,所以写代码的时候为了精简代码,可以把状态号为偶数的状态全部整合到一起。而这一点刚好与发送过程的状态刚好相反。

  思路理清楚以后就可以直接编写Verilog代码了,spi_module模块的代码如下:

module spi_module
(
    input               I_clk       , // 全局时钟50MHz
    input               I_rst_n     , // 复位信号,低电平有效
    input               I_rx_en     , // 读使能信号
    input               I_tx_en     , // 发送使能信号
    input        [7:0]  I_data_in   , // 要发送的数据
    output  reg  [7:0]  O_data_out  , // 接收到的数据
    output  reg         O_tx_done   , // 发送一个字节完毕标志位
    output  reg         O_rx_done   , // 接收一个字节完毕标志位
    
    // 四线标准SPI信号定义
    input               I_spi_miso  , // SPI串行输入,用来接收从机的数据
    output  reg         O_spi_sck   , // SPI时钟
    output  reg         O_spi_cs    , // SPI片选信号
    output  reg         O_spi_mosi    // SPI输出,用来给从机发送数据          
);

reg [3:0]   R_tx_state      ; 
reg [3:0]   R_rx_state      ;

always @(posedge I_clk or negedge I_rst_n)
begin
    if(!I_rst_n)
        begin
            R_tx_state  <=  4'd0    ;
            R_rx_state  <=  4'd0    ;
            O_spi_cs    <=  1'b1    ;
            O_spi_sck   <=  1'b0    ;
            O_spi_mosi  <=  1'b0    ;
            O_tx_done   <=  1'b0    ;
            O_rx_done   <=  1'b0    ;
            O_data_out  <=  8'd0    ;
        end 
    else if(I_tx_en) // 发送使能信号打开的情况下
        begin
            O_spi_cs    <=  1'b0    ; // 把片选CS拉低
            case(R_tx_state)
                4'd1, 4'd3 , 4'd5 , 4'd7  , 
                4'd9, 4'd11, 4'd13, 4'd15 : //整合奇数状态
                    begin
                        O_spi_sck   <=  1'b1                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end
                4'd0:    // 发送第7位
                    begin
                        O_spi_mosi  <=  I_data_in[7]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end
                4'd2:    // 发送第6位
                    begin
                        O_spi_mosi  <=  I_data_in[6]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end
                4'd4:    // 发送第5位
                    begin
                        O_spi_mosi  <=  I_data_in[5]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd6:    // 发送第4位
                    begin
                        O_spi_mosi  <=  I_data_in[4]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd8:    // 发送第3位
                    begin
                        O_spi_mosi  <=  I_data_in[3]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end                            
                4'd10:    // 发送第2位
                    begin
                        O_spi_mosi  <=  I_data_in[2]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd12:    // 发送第1位
                    begin
                        O_spi_mosi  <=  I_data_in[1]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd14:    // 发送第0位
                    begin
                        O_spi_mosi  <=  I_data_in[0]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b1                ;
                    end
                default:R_tx_state  <=  4'd0                ;   
            endcase 
        end
    else if(I_rx_en) // 接收使能信号打开的情况下
        begin
            O_spi_cs    <=  1'b0        ; // 拉低片选信号CS
            case(R_rx_state)
                4'd0, 4'd2 , 4'd4 , 4'd6  , 
                4'd8, 4'd10, 4'd12, 4'd14 : //整合偶数状态
                    begin
                        O_spi_sck      <=  1'b0                ;
                        R_rx_state     <=  R_rx_state + 1'b1   ;
                        O_rx_done      <=  1'b0                ;
                    end
                4'd1:    // 接收第7位
                    begin                       
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[7]   <=  I_spi_miso          ;   
                    end
                4'd3:    // 接收第6位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[6]   <=  I_spi_miso          ; 
                    end
                4'd5:    // 接收第5位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[5]   <=  I_spi_miso          ; 
                    end 
                4'd7:    // 接收第4位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[4]   <=  I_spi_miso          ; 
                    end 
                4'd9:    // 接收第3位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[3]   <=  I_spi_miso          ; 
                    end                            
                4'd11:    // 接收第2位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[2]   <=  I_spi_miso          ; 
                    end 
                4'd13:    // 接收第1位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[1]   <=  I_spi_miso          ; 
                    end 
                4'd15:    // 接收第0位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b1                ;
                        O_data_out[0]   <=  I_spi_miso          ; 
                    end
                default:R_rx_state  <=  4'd0                    ;   
            endcase 
        end    
    else
        begin
            R_tx_state  <=  4'd0    ;
            R_rx_state  <=  4'd0    ;
            O_tx_done   <=  1'b0    ;
            O_rx_done   <=  1'b0    ;
            O_spi_cs    <=  1'b1    ;
            O_spi_sck   <=  1'b0    ;
            O_spi_mosi  <=  1'b0    ;
            O_data_out  <=  8'd0    ;
        end      
end

endmodule

参考链接:https://blog.csdn.net/ivy_reny/article/details/78189058

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SPI总线协议概述 的相关文章

  • 基于RS485通信的Modbus通信协议

    通信可以分为两个方面 xff1a 硬件层 xff1a RS485解决的是数据传输问题 xff0c 也就是说如何将一个 0 或 1 传输到另外一端 xff08 保证了数据可以转移到另一端 xff09 软件层 xff1a modbus是在硬件基
  • wifi和服务器之间通信协议,安卓和wifi通信协议

    安卓和wifi通信协议 内容精选 换一换 MindX DL API的调用使用REST Representational State Transfer 风格API xff0c Volcano使用k8s apiserver的url地址 xff0
  • 二、传感器 Modbus-RTU 通信协议

    水文传感器通信协议 传感器宜采用 RS 485 422 RS 232C SDI 12 等通用接口标准 xff1b 通信协议宜采用 Modbus RTU协议和 SDI 12 通信协议 通信速率和字节帧结构 通信波特率宜采用1200bps xf
  • 详解 Modbus 通信协议(清晰易懂)

    文章目录 已剪辑自 https mp weixin qq com s dvo1l1GgJ2DtIHnPK5E1tA 本文总结关于 Modbus 相关的知识 xff0c 浅显易懂 xff0c 旨在对 Modbus 有一个很直观的了解 如有错误
  • 【通信协议】IIC通信协议详解

    IIC的基本介绍 IIC总线的发展 xff1a 芯片间总线 xff08 Inter Interface Circuit xff0c IIC xff09 xff0c 是应用广泛的芯片间串行扩展总线 目前世界上采用的IIC总线一共有两个规范 x
  • SOAP传输协议

    一 HTTP传输协议 超文本传输协议 xff08 HyperText Transfer Protocol xff0c 缩写 xff1a HTTP xff09 xff0c 它是基于请求 响应的模式协议 xff0c 客户端发出请求 xff0c
  • UART通信协议

    UART通信协议 一 UART是什么 xff1f 1 同步串口通信 vs 异步串口通信2 串行通信 二 通信协议三 工作原理四 特点 一 UART是什么 xff1f 通用异步收发传输器 xff08 Universal Asynchronou
  • Modbus通信协议

    一 Modbus 协议简介 Modbus 协议是应用于电子控制器上的一种通用语言 通过此协议 xff0c 控制器相互之间 控制器经由网络 xff08 例如以太网 xff09 和其它设备之间可以通信 它已经成为一通用工业标准 有了它 xff0
  • 详解LVDS通信协议

    目录 LVDS概述LVDS接口电路的组成LVDS输出接口电路类型单路6位LVDS输出接口双路6位LVDS输出接口单路8位1TL输出接口双路8位1TL输出位接口 典型LVDS发送芯片介绍四通道LVDS发送芯片五通道LVDS发送芯片十通道LVD
  • 【通信协议】单总线协议详解——以DHT11为例

    单总线概述 1 单总线的介绍 1 单总线也称为1 Wire bus 它是由美国DALLAS 达尔斯 公司推出的外围串行扩展总线 单总线系统中配置的各种器件 由DALLAS公司提供的专用芯片实现 2 每个芯片都有64位ROM 厂家对每一芯片都
  • I2C总结(单主机和多主机)

    I2C在使用过程中单个主机是不论是硬件I2C还是硬件I2C都不太难 理解好时序很容易实现 还有就是很多人认为硬件I2C有很多缺点 其实这是谬论吧 硬件I2C在稳定性上胜过软件I2C 而且不占用MCU时间 可以实现I2C中断 如果系统有硬件I
  • 车辆总线-MVB通讯

    概述 MVB Multifunction vehicle bus 为多功能车辆总线 它是列车通信网 TCN Train Communication Network 的一部分 TCN 网络由 WTB Wire Train Bus MVB 构成
  • SPI通信原理和协议

    1 SPI原理超详细讲解 值得一看 https blog csdn net as480133937 article details 105764119 2 STM32 HAL库 STM32CubeMX教程十四 SPI https blog
  • 通信 / 网络地址转换(NAT)过程

    一 英文全称 Network Address Translation 二 诞生原因 解决因为可用 IP 过少导致有些设备无法连入网络的问题 该技术的核心思想是多个私有网络 ip 通过一个公共 ip 连入网络 三 过程说明 为了实现上述思想
  • DNS 解析顺序是怎样的?

    1 DNS 解析过程 当客户端对域名发起访问时 会将解析请求发送给递归解析服务器 递归服务器会代替客户端进行全球递归查询 首先递归服务器会请求根域名服务器 根域名服务器根据域名后缀 告知对应的顶级域名服务器 递归服务器再向顶级服务器发起请求
  • HTTPS 原理详解

    转自 https baijiahao baidu com s id 1570143475599137 wfr spider for pc 前言 HTTPS 全称 HyperText Transfer Protocol over Secure
  • MAC 认证和 MAC 旁路认证

    一 MAC 认证原理 1 MAC认证是什么 MAC 认证 是指终端网络接入控制设备自动获取终端的 MAC 地址 作为接入网络的凭证发到RADIUS 服务器进行校验 MAC 认证是一种基于接口和 MAC 地址对用户的网络访问权限进行控制的认证
  • SPI总线协议概述

    一 概述 SPI serial peripheral interface 是一种同步串行通信协议 由一个主设备和一个或多个从设备组成 主设备启动与从设备的同步通信 从而完成数据的交换 SPI是一种高速全双工同步通信总线 标准的SPI仅仅使用
  • 通信子网在计算机网络中的地位和作用

    一 通信子网是计算机网络的核心组成部分 通信子网是计算机网络的核心组成部分 它负责为计算机网络中的各种设备提供通信支持 无论是主机之间的数据传输 还是主机与终端之间的数据通信 都需要通过通信子网来实现 通信子网是连接各个设备的关键基础设施
  • 计算机网络中的通信子网:架构、协议与技术简介

    在计算机网络中 通信子网是负责实现主机之间以及主机与终端之间数据传输的核心部分 它由一系列硬件设备和通信协议组成 为上层应用提供可靠 高效和透明的数据传输服务 本文将详细介绍通信子网的架构 协议与技术 一 通信子网的架构 星型拓扑 星型拓扑

随机推荐

  • CVAT标注工具的部署步骤详解

    简介 CVAT Computer Vision Annotation Tool 此标注工具是用于机器视觉数据标注的在线标注工具 以网页形式标注 能够生成多种数据标注格式基本涵盖了市面上百分之九十以上格式 此工具也有自己的标注格式 此工具的优
  • Canvas 详解

    HTML 5 Canvas 参考手册
  • ES6语法知识点

    目录 let const 常用 暂时性死区 const 建议 箭头函数 常用 建议 iterator迭代器 解构赋值 常用 建议 剩余 扩展运算符 常用 扩展运算符 剩余运算符 在对象中使用扩展运算符 建议 对象属性 方法简写 常用 对象属
  • centos7 搭建Hadoop3.0.3完全分布式

    第一步 服务器规划 IP地址 主机名称 nameNode dataNode 192 168 60 201 master 是 否 192 168 60 200 node1 否 是 第二步 基于依赖环境准备 1 centos7 搭建JDK8 参
  • Java后台面试题

    Java后台面试题 一 Java内存 私有内存区 伴随线程的产生而产生 一旦线程终止 私有内存区也会自动消除 程序计数器 指示当前程序执行到了哪一行 执行Java方法时记录正在执行的虚拟机字节码指令地址 执行本地方法时 计数器值为null
  • Linq语法详细

    1 简单的linq语法 1 var ss from r in db Am recProScheme select r 2 var ss1 db Am recProScheme 3 string sssql select from Am re
  • 不加电透明屏:在场景化应用中,有哪些特点和优点?

    不加电透明屏是一种新型的显示技术 它可以在不需要电源的情况下显示图像和文字 这种屏幕的原理是利用光的折射和反射来实现显示效果 而不需要通过电流来激发像素点 不加电透明屏的最大优点是节能环保 传统的显示屏需要消耗大量的电能来显示图像 而不加电
  • 环境搭建04-Ubuntu16.04更改conda,pip的镜像源

    我常用的pipy国内镜像源 https pypi tuna tsinghua edu cn simple 清华 http mirrors aliyun com pypi simple 阿里云 https pypi mirrors ustc
  • Java-基于SSM的药品销售管理系统

    项目背景 本论文主要论述了如何使用JAVA语言开发一个药品销售系统 本系统将严格按照软件开发流程进行各个阶段的工作 采用B S架构 面向对象编程思想进行项目开发 在引言中 作者将论述药品销售系统的当前背景以及系统开发的目的 后续章节将严格按
  • 【论文翻译】基于层次结构的动态异构图嵌入

    基于层次结构的动态异构图嵌入 Dynamic Heterogeneous Graph Embedding Using Hierarchical Attentions 百度学术 摘要 图嵌入已经引起了许多研究兴趣 现有的研究主要集中在静态同质
  • 2018.08.31 WorkSummary——05

    最近在做一个SMH spring springmvc hibernate 的项目 比较有意思 主要是在前端做大数据展示 后台业务较少 但是表特别多 一个图对应一个表 一共上百个图 hibernate是特点是操作对象等于操作数据库 每个表对应
  • React Native

    小手动一动 点赞转发加关注 微信搜索 大前端杂货铺 公众号关注大前端老司机带您遨游大前端知识的海洋 关注 Github https github com big frontend 还有大前端代码实践哦 java 与 javascript 互
  • C++基础——简单而强大的bitset

    basis bitset 的构造 bitset的操作 一些高级用法 将Bitsets视为一组标志 一些简单的原子操作 往往能组合出复杂而强大的功能 位操作的深远意义不在于表示一种数值 而是可能的情况数 我虽然暂时不知道bitset能组合出如
  • Python 之列表添加元素的3种方法

    一 追加单个值 append 方法 追加单个元素 gt gt gt list crazyit 20 2 gt gt gt list append fkit gt gt gt print list crazyit 20 2 fkit 二 追加
  • Configure and build Mesa3D

    1 环境 Mesa3D 21 1 4 Mesa3D demos Ubuntu 20 04 2 配置环境 sudo apt install gcc sudo apt install g sudo apt install vim sudo ap
  • React-Native ERROR: JAVA_HOME is not set and no ‘java’ command could be found in your PATH.

    ERROR JAVA HOME is not set and no java command could be found in your PATH Please set the JAVA HOME variable in your env
  • React重点知识拓展,含Hooks、路由懒加载等

    第7章 React扩展 一 setState 1 setState更新状态的2种写法 setState stateChange callback 对象式的setState stateChange为状态改变的对象 该对象可以体现出状态的更改
  • 华为云原生之数据仓库服务GaussDB(DWS)的深度使用与应用实践

    一 GaussDB DWS 简介 什么是 GaussDB DWS 数据仓库服务 GaussDB DWS 是一种基于华为云基础架构和平台的在线数据处理数据库 提供即开即用 可扩展且完全托管的分析型数据库服务 GaussDB DWS 是基于华为
  • java内存工具VisualVM的简单使用以及与Idea集成

    一 idea集成 1 打开设置 windows File gt Setting MacOS Intelij Idea gt Preferences 1 2 打开插件仓库 Plugins gt Browers Repositrories 在这
  • SPI总线协议概述

    一 概述 SPI serial peripheral interface 是一种同步串行通信协议 由一个主设备和一个或多个从设备组成 主设备启动与从设备的同步通信 从而完成数据的交换 SPI是一种高速全双工同步通信总线 标准的SPI仅仅使用