SPI总线协议概述

article/2025/8/26 22:00:20

一.概述

         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       , // 全局时钟50MHzinput               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)
beginif(!I_rst_n)beginR_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) // 发送使能信号打开的情况下beginO_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 : //整合奇数状态beginO_spi_sck   <=  1'b1                ;R_tx_state  <=  R_tx_state + 1'b1   ;O_tx_done   <=  1'b0                ;end4'd0:    // 发送第7位beginO_spi_mosi  <=  I_data_in[7]        ;O_spi_sck   <=  1'b0                ;R_tx_state  <=  R_tx_state + 1'b1   ;O_tx_done   <=  1'b0                ;end4'd2:    // 发送第6位beginO_spi_mosi  <=  I_data_in[6]        ;O_spi_sck   <=  1'b0                ;R_tx_state  <=  R_tx_state + 1'b1   ;O_tx_done   <=  1'b0                ;end4'd4:    // 发送第5位beginO_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位beginO_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位beginO_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位beginO_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位beginO_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位beginO_spi_mosi  <=  I_data_in[0]        ;O_spi_sck   <=  1'b0                ;R_tx_state  <=  R_tx_state + 1'b1   ;O_tx_done   <=  1'b1                ;enddefault:R_tx_state  <=  4'd0                ;   endcase endelse if(I_rx_en) // 接收使能信号打开的情况下beginO_spi_cs    <=  1'b0        ; // 拉低片选信号CScase(R_rx_state)4'd0, 4'd2 , 4'd4 , 4'd6  , 4'd8, 4'd10, 4'd12, 4'd14 : //整合偶数状态beginO_spi_sck      <=  1'b0                ;R_rx_state     <=  R_rx_state + 1'b1   ;O_rx_done      <=  1'b0                ;end4'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          ;   end4'd3:    // 接收第6位beginO_spi_sck       <=  1'b1                ;R_rx_state      <=  R_rx_state + 1'b1   ;O_rx_done       <=  1'b0                ;O_data_out[6]   <=  I_spi_miso          ; end4'd5:    // 接收第5位beginO_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位beginO_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位beginO_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位beginO_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位beginO_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位beginO_spi_sck       <=  1'b1                ;R_rx_state      <=  R_rx_state + 1'b1   ;O_rx_done       <=  1'b1                ;O_data_out[0]   <=  I_spi_miso          ; enddefault:R_rx_state  <=  4'd0                    ;   endcase end    elsebeginR_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      
endendmodule

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


http://chatgpt.dhexx.cn/article/7mUDReau.shtml

相关文章

SPI协议详解(总结)

关于SPI协议 1&#xff09;SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff1b; 2&#xff09;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff1b; 3&#xff09;在芯片的管脚上只占用四根线&#xff0c;节约了芯片的…

SPI通讯协议介绍

来到SPI通讯协议了。废话两句&#xff0c;“SPI很重要”&#xff0c;这是我在学校时候听那些单片机开发工程师说的。出来实习&#xff0c;到后来工作&#xff0c;确实如此&#xff0c;SPI的使用很常见&#xff0c;那么自然重要咯。 SPI(Serial peripheral interface)即串行外围…

spi协议及工作原理分析

说明.文章摘自&#xff1a;SPI协议及其工作原理浅析 http://bbs.chinaunix.net/thread-1916003-1-1.html 一、概述. SPI, Serial Perripheral Interface, 串行外围设备接口, 是 Motorola 公司推出的一种同步串行接口技术. SPI 总线在物理上是通过接在外围设备微控制器(PICmicro…

SPI总线协议介绍

SPI总线协议介绍 一、技术性能 SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI接…

SPI 协议的解析

目录 1、SPI 的硬件层 1.1、SPI接口的信号线 1.2、SPI通信常见的连接方式 2、SPI的通信协议 2.1、起始信号 2.2、停止信号 2.3、数据有效性 2.4、时钟的同步 3、SPI的四种工作模式 SPI协议是由摩托罗拉公司提出的一种串行外围设备通信接口&#xff0c;是一种高速全双工…

SPI协议详解

1.SPI协议简介 1.1.SPI协议概括 SPI&#xff0c;是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM&#xff0c;FLASH&#xff0c;实时时钟&#xff0c;AD转换器&a…

一文看懂SPI协议

文章目录 SPI协议简介4线还是3线&#xff1f;4种工作模式多种传输速率SPI协议的基本时序SPI协议的升级版FPGA实现SPI协议SPI和IIC的对比总结 SPI协议简介 板卡内不同芯片间通讯最常用的三种串行协议&#xff1a;UART、I2C、SPI&#xff0c;之前写过串口协议及其FPGA实现&#…

SPI协议简单介绍

导言 SPI是串行扩展总线。 串行总线技术可以使系统的硬件设计大大简化、系统的体积减小、可靠性提高。同时系统的更改和扩充极为容易。 常用的串行扩展总线有&#xff1a;I2C&#xff08;Inter IC Bus)总线、单总线&#xff08;1-WIRE BUS)、SPI&#xff08;Serial Peripher…

SPI通信协议详解

SPI协议基本概念 SPI接口 &#xff08;1&#xff09;SPI&#xff08;Serial Peripheral interface&#xff09;是一种同步串行传输规范&#xff0c;也是单片机外设芯片串行外设扩展接口&#xff0c;该接口是一种高速&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;…

SPI 协议详解

SPI 协议详解 1、SPI 简介2、SPI四线3、SPI四种工作模式4、SPI时序图 1、SPI 简介 SPI 全称是 Serial Perripheral Interface&#xff0c;也就是串行外围设备接口。 SPI 是 Motorola 公司推出的一种同步串行接口技术&#xff0c;是一种高速、全双工的同步通信总线&#xff0c;…

SPI协议简介—学习笔记

SPI协议简介—学习笔记 1、SPI百度百科 SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&…

一文搞懂SPI通信协议

目录 1、简介 2、通信原理 3、通信特性 3.1、设备选择 3.2、设备时钟 3.2.1、时钟速率 3.2.2、时钟极性 3.2.3、时钟相位 3.3、四种模式 4、多从机模式 5、SPI优缺点 1、简介 SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xf…

SPI协议详解(图文并茂+超详细)

加入技术交流群 领取资料 文章目录 先说串口SPI通讯协议SPI特性时钟频率时钟极性 CKP/Clock Polarity时钟相位 CKE /Clock Phase (Edge)时钟配置总结 模式编号多从机模式优缺点SPI通讯的优势SPI的缺点 编程实现 先说串口 因为之前写过一篇UART&#xff0c;通用串行异步通讯协…

Xms Xmx Xss 释义

Xms 是指设定程序启动时占用内存大小。一般来讲&#xff0c;大点&#xff0c;程序会启动的快一点&#xff0c;但是也可能会导致机器暂时间变慢。 Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存&#xff0c;超出了这个设置值&#xff0c;就会…

JVM中的Xms和Xmx

JVM内存参数 -Xms和-Xmx 参考资料&#xff1a;(1) JVM常用内存参数配置 (2)深入理解Java虚拟机 -Xms和-Xmx &#xff08;1&#xff09;这两个参数老是搞混&#xff0c;特地记一下。-Xms 为JVM启动时申请的初始Heap值&#xff0c;默认为操作系统物理内存的1/64但小于1G。默认当…

通过两个小例子,更快了解-Xms -Xmx

我们偶尔就会遇到OutOfMemoryError&#xff0c;面试的时候总是能够被问道&#xff0c;可明明背好的面试题临时又不会了&#xff0c;答出来也很生硬&#xff0c;自己都想让自己快点“回家等通知”&#xff0c;那我们就通过下面的两个例子了解一下吧。 开发环境&#xff1a;ide…

程序员都需要会的JVM调优总结 -Xms -Xmx -Xmn -Xss,附idea配置实战(程序员必学)

目录 1.堆大小设置 2.JVM回收器和其他参数 3.JVM常见配置汇总 4.JVM在idea配置实战 5.JVM优化总结 1.堆大小设置 JVM 中最大堆大小有三方面限制&#xff1a;相关操作系统的数据模型&#xff08;32-bt还是64-bit&#xff09;限制&#xff1b;系统的可用虚拟内存限制&#x…

Xms Xmx size 调整java虚拟机堆大小

-Xms 设置堆空间&#xff08;年轻代&#xff09;的初始内存大小&#xff0c;等价于 -XX:InitialHeapSize -Xmx 设置堆空间&#xff08;老年代&#xff09;的最大内存大小&#xff0c;等价于 -XX:MaxHeapSize 一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时&#xff0c…

xms应用框架 - 基于.netcore

xms应用框架 - 基于.netcore 背景一、xms是什么二、能干什么三、目标四、框架介绍1、元数据管理2、组织架构3、授权体系4、高度可视化配置5、业务扩展6、流程7、二次开发 五、多图鉴赏六、源码地址 背景 鄙人经过多年开发&#xff0c;数百个项目“打磨(折磨)”&#xff0c;各种…

JVM -XMX与XMS是什么

XMS : JVM初始分配的堆内存XMX : JVM最大允许分配的堆内存&#xff0c;按需分配堆内存分配: 默认空余堆内存小于40%时&#xff0c;JVM就会增大堆直到-Xmx的最大限制&#xff1b; 空余堆内存大于70%时&#xff0c;JVM会减少堆直到-Xms的最小限制。 因此服务器一般设置-Xms、-…