详解SPI通信协议以及FPGA实现

文章目录

  • 一、SPI简介
  • 二、SPI通信结构
  • 三、多从机模式
  • 四、时钟极性(CPOL)和时钟相位(CPHA)
  • 五、SPI通信过程
  • 六、实现SPI主机发送程序
    • 6.1 波形图分析
    • 6.2 Verilog代码
    • 6.3 发送数据控制模块
    • 6.4 仿真代码编写以及仿真结果分析
  • 七、Verilog实现SPI从机接收程序
    • 7.1 波形图分析
    • 7.2 Verilog代码
    • 7.3 仿真代码以及仿真结果分析
  • 八、SPI回环测试
    • 8.1 顶层代码
    • 8.2 仿真代码以及仿真结果分析
    • 8.3 上板验证
  • 参考


一、SPI简介

  前面《详解UART通信协议以及FPGA实现》《UART自适应任意(典型)波特率原理以及FPGA实现》我们实现了UART通信以及自适应任意波特率UART的通信,常见的通信协议还有SPI和IIC。其中SPI 的接口速度可以最高到上百兆,采用 SPI 接口的设备一般兼顾低速通信和上百兆的高速通信。
  SPI(Serial Peripheral Interface)是一种同步串行全双工的通信接口,常用于在微控制器、传感器、存储器和其他外部设备之间进行数据交换。例如: EEPROM、RTC(实时时钟)、ADC(模数转换器)、DAC(数模转换器)、LCD、音频 IC、温度和压力等传感器,MMC 或 SD 卡等存储卡等等

二、SPI通信结构

  SPI是一个同步的数据总线,由上文《》可以知道,同步通信需要随路时钟,也就是说SPI是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步。通信结构如下图所示:
在这里插入图片描述

  • SCK: SPI通信的随路时钟,时钟由主机产生,接收时钟的为从机。SPI通信结构中有且只有一个主机,但是可以有多个从机。
  • MOSI:(Master output slave input )主机输出,从机输入(数据由主机发送给从机)
  • MISO:(Master input slave output)主机输入,从机输出(数据由从机发送给主机)
  • NSS:(Negative Slave Select)从机片选信号,低电平有效,(由主机发送给从机)

三、多从机模式

  1. 常规模式:主机需要为每个从机提供单独的片选信号。一旦主机(拉低)片选信号,MOSI或者MISO线上的时钟和数据便可用于所选的从机。如果拉低多个片选信号,则MISO线上的数据会被破坏,因为主机无法识别哪个从机正在传输数据。

在这里插入图片描述

  1. 菊花链模式:所有从机的片选信号连接在一起,数据从一个从机传播到下一个从机。所有从机同时接收同一SPI时钟。来自主机的数据直接送到第一个从机,该从机将数据提供给下一个从机,依此类推。
    在这里插入图片描述

四、时钟极性(CPOL)和时钟相位(CPHA)

  在SPI中,主机需要配置时钟极性和时钟相位。

  • 在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPOL = 0:时钟空闲时为低电平;CPHA = 1:时钟空闲时为高电平
  • CPHA位选择时钟相位。根据CPHA位的状态,使用时钟上升沿或下降沿来采样或者发送数据。CPHA = 0:在时钟信号 SCK的第一个跳变沿采样;CPHA = 1:在时钟信号 SCK的第二个跳变沿采样;
  • 主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。
SPI模块CPOLCPHA空闲状态时钟极性采样和发送数据的时钟相位
000低电平数据在第1个沿采样,在第2个沿发送
101低电平数据在第2个沿采样,在第1个沿发送
210高电平数据在第1个沿采样,在第2个沿发送
311高电平数据在第2个沿采样,在第1个沿发送
  四种传输模式如下所示,传输的开始和结束用绿色虚线表示,**采样沿用橙色**虚线表示,**发送沿用蓝色**虚线表示。
  1. SPI模式0,CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在在下降沿发送,并在上升沿采样。

在这里插入图片描述
2. SPI模式1,CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在上升沿发送,并在下降沿采样。
在这里插入图片描述

  1. SPI模式2,CPOL = 1,CPHA = 0:CLK空闲状态 = 高电平,数据在上升沿发送,并在下降沿采样。
    在这里插入图片描述
  2. SPI模式3,CPOL = 1,CPHA = 1:CLK空闲状态 = 高电平,数据在在下降沿发送,并在上升沿采样。
    在这里插入图片描述

五、SPI通信过程

  1. 主机在访问从机前,先将NSS信号拉低,选择对应的从机;当主机要结束本次通信时,再拉高NSS信号。
  2. 主机发送SCLK时钟信号
  3. 主机按照配置的时钟极性和时钟相位,将数据一位一位通过MOSI发送给从机
  4. 从机根据主机配置的时钟极性和时钟相位,在对应位置一位一位的接收数据
    在这里插入图片描述

六、实现SPI主机发送程序

6.1 波形图分析

在这里插入图片描述

  1. 当前没有发送数据时,spi_en为低,当上有请求发送数据时,则拉高spi_en信号以及缓存待发送数据
  2. 然后拉低cs信号,开始时钟计数
  3. 每次计数一半周期时,拉高第一时钟沿信号,计数一个周期时,拉高第二时钟沿信号。
  4. 如果CPOL=0,则时钟空闲时为低电平,第一时钟沿为上升沿。如果CPOL=1,则相反
  5. 每次第一时钟沿来临时,发送的bit计数器累加
  6. 如果CPHA=0时,数据采样在第一时钟沿(上升沿),采样数据如上面红线所示。例如发送的数据为10010110,则采样的数据也为10010110。
  7. 如果CPHA=1时,数据采样在第二个时钟沿(下降沿),采样数据如下面红线所示,例如发送的数据为10010110,则采样的数据也为10010110。

6.2 Verilog代码

`timescale 1ns/1ns
module spi_master_tx#
(
    parameter SYS_CLK_FREQ = 50000000,                  //输入时钟频率50M
    parameter SPI_CLK_FREQ = 500000,                    //输出spi时钟500K
    parameter CPOL = 1'b0,                              //时钟极性设置为0
    parameter CPHA = 1'b0                               //时钟相位设置为0
)   
(
    input                                               sys_clk ,       //输入系统时钟
    input                                               rst_n   ,       //系统复位
    input                                               spi_tx_req  ,   //待发送数据请求
    input           [7:0]                               spi_tx_data ,   //待发送数据
    output  reg                                         spi_cs  ,       //cs片选信号
    output                                              spi_clk ,       //主机发送的spi时钟
    output                                              spi_busy,       //spi繁忙信号
    output                                              spi_mosi        //mosi
  
);

    localparam      [9:0]                               spi_clk_cnt_max = SYS_CLK_FREQ / SPI_CLK_FREQ;  //一个spi时钟需要计数的最大值
    localparam      [9:0]                               spi_clk_cnt_max_div2    = spi_clk_cnt_max/2;    //半个时钟周期需要计数的最大值

    reg             [9:0]                               clk_div_cnt ;             //spi时钟计数器
    reg                                                 spi_en  ;                 //spi发送使能
    reg             [7:0]                               spi_tx_data_reg ;         //缓存待发送的数据
    reg                                                 clk1_en ;                 //第一时钟边沿
    reg                                                 clk2_en ;                 //第二时钟边沿
    reg                                                 spi_clk_temp    ;         //spi时钟
    reg             [3:0]                               tx_cnt  ;                 //发送的bit计数器
    reg                                                 spi_strobe_en   ;         //spi发送数据使能有效范围
    wire                                                strobe  ;                 //spi发送数据使能信号

//如果时钟极性=1,则取反时钟信号
assign spi_clk = (CPOL == 1'b1)?  ~spi_clk_temp : spi_clk_temp;
//先发送高位数据
assign spi_mosi = spi_tx_data_reg[7];
//如果时钟相位=1,则采样数据才第二时钟沿,发送数据在第一时钟沿
assign strobe = (CPHA == 1'b1)? clk1_en&spi_strobe_en : clk2_en&spi_strobe_en;
//spi繁忙信号就=spi发送使能信号
assign spi_busy = spi_en;

//等待上游给出发送请求和发送数据。发送完成8个bit,则本次发送完成。
always @(posedge sys_clk or negedge rst_n) begin
    if((rst_n == 1'b0) || (clk1_en == 1'b1 && tx_cnt == 4'd8)) begin
        spi_en <= 1'b0;
        spi_tx_data_reg <= 'd0;
    end
    else if((spi_tx_req == 1'b1) && (spi_en == 1'b0))begin
        spi_en <= 1'b1;
        spi_tx_data_reg <= spi_tx_data;
    end
    else if(spi_en == 1'b1 && strobe)
        spi_tx_data_reg <= {spi_tx_data_reg[6:0],1'b0};
    else
        spi_tx_data_reg <= spi_tx_data_reg;
end

//发送开始前,拉低cs信号
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_cs <= 1'b1;
    else if(spi_en == 1'b1)
        spi_cs <= 1'b0;
    else
        spi_cs <= 1'b1;
end

//当时钟计数器计数到一半时,拉高第一时钟沿信号,计数完成一个时钟周期时,拉高第二时钟信号
always @(posedge sys_clk or negedge rst_n) begin
    if((rst_n == 1'b0) || (spi_cs == 1'b1))begin
        clk_div_cnt <= 0;
        clk1_en <= 1'b0;
        clk2_en <= 1'b0;
    end
    else if(clk_div_cnt == spi_clk_cnt_max - 1)begin
        clk_div_cnt <= 0;
        clk2_en <= 1'b1;
    end
    else if(clk_div_cnt == spi_clk_cnt_max_div2 - 1)begin
        clk1_en <= 1'b1;
        clk_div_cnt <= clk_div_cnt + 1'b1;
    end
    else begin   
        clk_div_cnt <= clk_div_cnt + 1'b1;
        clk1_en <= 1'b0;
        clk2_en <= 1'b0;
    end
end

//每次第一时钟沿上来,则表示发送完一个数
always @(posedge sys_clk or negedge rst_n) begin
    if((rst_n == 1'b0)||(spi_en == 1'b0))
        tx_cnt <= 'd0;
    else if(clk1_en == 1'b1)
        tx_cnt <= tx_cnt + 1'b1;
    else
        tx_cnt <= tx_cnt;
end

        
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_strobe_en  <= 1'b0;
    else if(tx_cnt < 4'd8)
        if(clk1_en == 1'b1 )
            spi_strobe_en  <= 1'b1;
        else
            spi_strobe_en  <= spi_strobe_en;
    else
        spi_strobe_en  <= 1'b0;
end
    

always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_clk_temp <= 1'b0;
    else if(clk2_en == 1'b1)
        spi_clk_temp <= 1'b0;
    else if((clk1_en == 1'b1)&&(tx_cnt <4'd8))
        spi_clk_temp <= 1'b1;
    else
        spi_clk_temp <= spi_clk_temp;
end

endmodule

6.3 发送数据控制模块

  简单设置发送数据控制模块,发送数据从1开始累加,代码如下:

`timescale 1ns / 1ps

module spi_top
(
    input                                               sys_clk ,   //输入时钟
    input                                               rst_n   ,   //系统复位
    output                                              spi_clk ,   //SPI发送时钟
    output                                              spi_mosi    //SPI发送数据
    );

    wire                                                spi_cs  ;
    wire                                                spi_busy    ;                                  //SPI忙信号
    reg                                                 spi_tx_req  ;                                  //SPI发送req信号,有发送需求时拉高
    reg             [7:0]                               spi_tx_data ;                                  //待发送数据存储
    reg             [1:0]                               state ;                                        //状态机

//spi send state machine
always @(posedge sys_clk) begin
    if(!rst_n) begin                                  
        spi_tx_req  <= 1'b0;
        spi_tx_data <= 8'd0;
        state <= 2'd0;
    end
    else begin
        case(state)
        0:if(!spi_busy)begin                            //总线不忙启动传输
           spi_tx_req  <= 1'b1;                         //req信号拉高,开始传输
           spi_tx_data <= spi_tx_data + 1'b1;          //测试数据
           state <= 2'd1;
        end
        1:if(spi_busy)begin                             //如果spi总线忙,清除spi_tx_req
           spi_tx_req  <= 1'b0;
           state <= 2'd0;
        end
        default:state <= 2'd0;
        endcase
    end
end   

//例化SPI Master发送驱动器

spi_master_tx#(
    .SYS_CLK_FREQ ( 50000000 ),
    .SPI_CLK_FREQ ( 500000 ),
    .CPOL         ( 1'b0 ),
    .CPHA         ( 1'b0 )
)u_spi_master_tx(
    .sys_clk      ( sys_clk      ),
    .rst_n        ( rst_n        ),
    .spi_tx_req   ( spi_tx_req   ),
    .spi_tx_data  ( spi_tx_data  ),
    .spi_cs       ( spi_cs       ),
    .spi_clk      ( spi_clk      ),
    .spi_busy     ( spi_busy     ),
    .spi_mosi     ( spi_mosi     )
);

endmodule

6.4 仿真代码编写以及仿真结果分析

  tb代码如下:


`timescale 1ns / 1ps
module master_spi_tb;

reg             sys_clk;               //系统时钟
reg     rst_n;  
wire spi_clk;
wire spi_mosi;

spi_top u_spi_top(
.sys_clk(sys_clk),
.rst_n(rst_n),
.spi_clk(spi_clk),
.spi_mosi(spi_mosi)
);

initial begin
    sys_clk  = 1'b0;                              //设置时钟基础值
    rst_n = 1'b0;                              //低电平复位
    #100;
    rst_n = 1'b1;                             //复位释放

 #2000000 $finish;
end
 
always #10 sys_clk = ~sys_clk;     //产生主时钟

endmodule

  设置CPOL=0,CPHA=0观看仿真结果,我们随便选取一个数据,例如发送8‘d37,结果如下:
在这里插入图片描述
  8’d37的二进制为00100101,所以采集数据在时钟上升沿,采集到的数据也为00100101,结果正确。

  现在修改CPOL=0,CPHA=1观看同样的结果,如下:
在这里插入图片描述
  采集数据在时钟下升沿,采集到的数据也为00100101,结果正确。

七、Verilog实现SPI从机接收程序

7.1 波形图分析

在这里插入图片描述

  1. 接收端首先检测cs线是否拉低,如果拉低了则进入接收状态,拉高rx_en。
  2. 然后根据CPOL和CPHA的数值来找到采集信号的沿。
  3. 每次采集沿到来时,采集数据并进行串并转换。

7.2 Verilog代码

`timescale 1ns / 1ns
module spi_slave_rx#
(
    parameter                                           BITS_LEN    = 8,    //设置接收bit长度
    parameter                                           CPOL    = 1'b0,     //时钟极性
    parameter                                           CPHA    = 1'b1      //时钟相位
)
(
    input                                               sys_clk ,
    input                                               rst_n   ,
    input                                               spi_cs  ,           //spi片选信号
    input                                               spi_clk ,           //spi_clk
    input                                               spi_mosi    ,       //spi_mosi
    output  reg                                         rx_data_valid   ,   //接收到的数据有效信号
    output  reg     [BITS_LEN - 1:0]                    rx_data             //接收到的信号
);

    reg             [3:0]                               spi_cs_reg  ;       //打四拍
    reg             [3:0]                               spi_clk_reg ;       //打四拍
    reg             [3:0]                               spi_mosi_reg    ;   //打四拍
    reg                                                 cap ;               //采集时刻信号
    reg                                                 spi_clk_pos ;       //上升沿
    reg                                                 spi_clk_neg ;       //下降沿
    wire                                                rx_en   ;           //接收使能信号
    reg             [4:0]                               rx_bit_cnt  ;       //接收bit计数器

assign rx_en  = (~spi_cs_reg[3]);   

//将 cs,spi_clk,mosi信号都打三拍消除亚稳态
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_cs_reg <= 3'd0;
    else
        spi_cs_reg <= {spi_cs_reg[2:0],spi_cs};
end

always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_clk_reg <= 3'd0;
    else
        spi_clk_reg <= {spi_clk_reg[2:0],spi_clk};
end
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_mosi_reg <= 3'd0;
    else
        spi_mosi_reg <= {spi_mosi_reg[2:0],spi_mosi};
end

//spi_clk上升沿
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_clk_pos <= 1'b0;
    else if (spi_clk_reg[2] == 1'b0 && spi_clk_reg[1] == 1'b1)
        spi_clk_pos <= 1'b1;
    else
        spi_clk_pos <= 1'b0;
end

//spi_clk下降沿
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        spi_clk_neg <= 1'b0;
    else if (spi_clk_reg[2] == 1'b1 && spi_clk_reg[1] == 1'b0)
        spi_clk_neg <= 1'b1;
    else
        spi_clk_neg <= 1'b0;
end

//根据CPOL CPHA来确定采集信号位置
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cap <= 1'b0;
    else if(CPOL == 1'b0 && CPHA == 1'b0)
        cap <= spi_clk_pos;
    else if(CPOL == 1'b0 && CPHA == 1'b1)
        cap <= spi_clk_neg;
    else if(CPOL == 1'b1 && CPHA == 1'b0)
        cap <= spi_clk_neg;
    else if(CPOL == 1'b1 && CPHA == 1'b1)
        cap <= spi_clk_pos;
    else
        cap <= 1'b0;
end

//每次采集一次,接收bit计数器累加一次
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)begin
        rx_bit_cnt <= 'd0;  
        rx_data_valid <= 1'b0;
    end
    else if((rx_en == 1'b1) && (cap == 1'b1) && (rx_bit_cnt < BITS_LEN))begin
        rx_bit_cnt <= rx_bit_cnt +1'b1;
        rx_data_valid <= 1'b0;
    end
    else if((rx_en == 1'b0) || (rx_bit_cnt == BITS_LEN))begin
        rx_bit_cnt <= 'd0;
        rx_data_valid <= 1'b1;
    end
    else begin
        rx_bit_cnt <= rx_bit_cnt;
        rx_data_valid <= 1'b0;
    end
end

//每次采集时刻到来,进行串并转换
always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        rx_data <= 'd0;
    else if(rx_en == 1'b1 && cap == 1'b1)
        rx_data <= {rx_data[BITS_LEN -2 : 0],spi_mosi_reg[3]};
    else if(rx_en == 1'b0)
        rx_data <= 'd0;
    else
        rx_data <= rx_data;
end

endmodule

7.3 仿真代码以及仿真结果分析

  编写tb文件如下:


`timescale 1ns / 1ps

module tb_spi_slave_rtx();

localparam  BYTES = 8;
localparam  TCNT  = BYTES*8*2-1;

localparam  CPOL = 1;
localparam  CPHA = 1;

reg I_clk; //系统时钟
reg [7:0] i;//计数器,用于产生SPI时钟数量
reg I_rstn; //系统复位
reg I_spi_clk;//SPI时钟
reg I_spi_ss; //SPI的Slave选通信号
reg [3:0]bit_cnt; //bit计数器
reg [7:0]spi_tx_buf; //发送缓存(移位寄存器)
reg [7:0]spi_tx_buf_r; //发送化缓存,用于产生测试数据
reg first_data_flag; //是否一个时钟改变数据

wire O_spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据
wire [7:0]O_spi_rdata; //SPI读数据
wire I_spi_rx;//SPI数据总线

//tb模拟的SPI测试数据接到I_spi_rx
assign I_spi_rx = spi_tx_buf[7];

//例化SPI 接收模块

spi_slave_rx#(
    .BITS_LEN       ( 8     ),
    .CPOL           ( CPOL  ),
    .CPHA           ( CPHA  )
)u_spi_slave_rx(
    .sys_clk        ( I_clk         ),
    .rst_n          ( I_rstn        ),
    .spi_cs         ( I_spi_ss      ),
    .spi_clk        ( I_spi_clk     ),
    .spi_mosi       ( I_spi_rx      ),
    .rx_data_valid  ( O_spi_rvalid  ),
    .rx_data        ( O_spi_rdata   )
);


initial begin
    I_clk  = 1'b0;
    I_rstn = 1'b0;
    #100;
    I_rstn = 1'b1;
end

always #10   I_clk  = ~I_clk;   //时钟信号翻转,产生系统时钟

initial begin
    #100;
    i = 0;
    
    forever begin
        I_spi_clk = CPOL; //设置时钟极性
        I_spi_ss  = 1; // 设置SPI的SS控制信号
        #2000;
        I_spi_ss  = 0;
        for(i=0;i<TCNT;i=i+1) #1000 I_spi_clk = ~ I_spi_clk; //产生SPI时钟
        #2000;
        I_spi_ss  = 1;

    end
end

initial begin
        #100;
        bit_cnt = 0;
        first_data_flag =0;
        spi_tx_buf[7:0] = 8'ha0;
        spi_tx_buf_r[7:0] = 8'ha0;
    forever begin
//spi ss 控件用于启用传输
        wait(I_spi_ss);//spi ss 
        bit_cnt = 0;
        spi_tx_buf[7:0] = 8'ha0;
        spi_tx_buf_r[7:0] = 8'ha0;

        if((CPHA == 1 && CPOL ==0)||(CPHA == 1 && CPOL ==1))//第一个时钟沿改变数据的情况
            first_data_flag = 1; //设置first_data_flag=1 下面的发送时序对应情况跳过第一个沿

//ss低时开始数据传输          
        wait(!I_spi_ss);

        while(!I_spi_ss)begin


//COPL=0 CPHA=0默认SCLK为低电平,对于发送方,在对于第1个bit数据提前放到总线
            if(CPHA == 0 && CPOL ==0)begin 
             @(negedge I_spi_clk)  begin //每个时钟的下降沿更新需要发送的BIT
                if(bit_cnt == 7)begin//连续发送过程中,8bits 发送完毕后更新数据
                    bit_cnt = 0;
                    spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
                    spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
                end
                else begin 
                    spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
                    bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
                end
             end
            end

//CPHA=0 COPL=1 默认SCLK为高电平,对于发送方,在对于第1个bit数据提前放到总线
            if(CPHA == 0 && CPOL ==1)begin 
             @(posedge I_spi_clk)  begin //每个时钟的上升沿更新需要发送的BIT
                if(bit_cnt == 7)begin //连续发送过程中,8bits 发送完毕后更新数据
                    bit_cnt = 0;
                    spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
                    spi_tx_buf = spi_tx_buf_r; //重新跟新发送寄存器
                end
                else begin
                    spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
                    bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
                end
             end
            end

//CPHA=1 COPL=0 默认SCLK为低电平,对于发送方,在第1个SCLK的跳变沿更新
            if(CPHA == 1 && CPOL ==0)begin 
             @(posedge I_spi_clk)  begin
                if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿
                    first_data_flag = 0;
                    //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据
                end
                else begin
                    if(bit_cnt == 7)begin
                        bit_cnt = 0;
                        spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
                        spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
                    end
                    else begin 
                        spi_tx_buf = {spi_tx_buf[6:0],1'b0}; //数据移位,更新数据
                        bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
                    end
                end
             end
            end

//CPHA=1 COPL=1 默认SCLK为高电平,对于发送方,在第1个SCLK的跳变沿更新
            if(CPHA == 1 && CPOL ==1)begin 
             @(negedge I_spi_clk)  begin
                if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿
                    first_data_flag = 0;
                    //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据
                end
                else begin
                    if(bit_cnt == 7)begin
                        bit_cnt = 0;
                        spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
                        spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
                    end
                    else begin 
                        spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
                        bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
                    end
                end
             end
            end

        end
    end
end

endmodule

  测试结果如下图所示,上游发送数据a0,接收模块接收到的也是a0 。

在这里插入图片描述

八、SPI回环测试

8.1 顶层代码

  将spi发送端和spi接收端接起来,通过顶层模拟发送数据。
顶层代码:

`timescale 1ns / 1ps

module spi_top
(
    input                                               sys_clk ,   //输入时钟
    input                                               rst_n   ,   //系统复位
    input                                               spi_clk_in  ,
    input                                               spi_miso    ,
    output                                              spi_clk ,   //SPI发送时钟
    output                                              spi_mosi    //SPI发送数据

    );

    reg                                                 spi_cs  ;
    wire                                                spi_busy    ;                                  //SPI忙信号
    reg                                                 spi_tx_req  ;                                  //SPI发送req信号,有发送需求时拉高
    reg             [7:0]                               spi_tx_data ;                                  //待发送数据存储
    reg             [1:0]                               state ;     //状态机
    reg             [10:0]                              delay_cnt   ;
    wire                                                rx_data_valid   ;
    wire            [7:0]                               rx_data ;

always @(posedge sys_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        delay_cnt <= 'd0;
    else if(delay_cnt[10] == 1'b1)
        delay_cnt <= 'd0;
    else
        delay_cnt <= delay_cnt +1'b1;   
end

//spi send state machine
always @(posedge sys_clk) begin
    if(!rst_n) begin                                  
        spi_tx_req  <= 1'b0;
        spi_tx_data <= 8'd0;
        state <= 2'd0;
         spi_cs <= 1'b1;
    end
    else begin
        case(state)
        0:if((delay_cnt[10]==1'b1) &&(spi_busy == 1'b0))begin
            spi_cs <= 1'b1;
            state <= 2'd1;
        end

        1:if((delay_cnt[10]==1'b1) &&(spi_busy == 1'b0))begin
            spi_cs <= 1'b0;
            state <= 2'd2;
        end
        2:if((delay_cnt[10]==1'b1) &&(spi_busy == 1'b0))begin                            //总线不忙启动传输
           spi_tx_req  <= 1'b1;                         //req信号拉高,开始传输
           spi_tx_data <= spi_tx_data + 1'b1;          //测试数据
           state <= 2'd3;
        end
        3:if(spi_busy)begin                             //如果spi总线忙,清除spi_tx_req
           spi_tx_req  <= 1'b0;
           state <= 2'd0;
        end
        default:state <= 2'd0;
        endcase
    end
end   

//例化SPI Master发送驱动器

spi_master_tx#(
    .SYS_CLK_FREQ ( 50000000 ),
    .SPI_CLK_FREQ ( 500000 ),
    .CPOL         ( 1'b0 ),
    .CPHA         ( 1'b1 )
)u_spi_master_tx(
    .sys_clk      ( sys_clk      ),
    .rst_n        ( rst_n        ),
    .spi_tx_req   ( spi_tx_req   ),
    .spi_tx_data  ( spi_tx_data  ),
    .spi_cs       (                ),
    .spi_clk      ( spi_clk      ),
    .spi_busy     ( spi_busy     ),
    .spi_mosi     ( spi_mosi     )
);

spi_slave_rx#(
    .BITS_LEN       ( 8 ),
    .CPOL           ( 1'b0 ),
    .CPHA           ( 1'b1 )
)u_spi_slave_rx(
    .sys_clk        ( sys_clk        ),
    .rst_n          ( rst_n          ),
    .spi_cs         ( spi_cs         ),
    .spi_clk        ( spi_clk_in     ),
    .spi_mosi       ( spi_miso       ),
    .rx_data_valid  ( rx_data_valid  ),
    .rx_data        ( rx_data        )
);

ila_0 u_ila (
	.clk(sys_clk), // input wire clk
	.probe0(rx_data_valid), // input wire [0:0]  probe0  
	.probe1(rx_data) // input wire [7:0]  probe1
);

endmodule

8.2 仿真代码以及仿真结果分析


`timescale 1ns / 1ps
module master_spi_tb;

reg             sys_clk;               //系统时钟
reg     rst_n;  
wire spi_clk;
wire spi_mosi;

spi_top u_spi_top(
.sys_clk(sys_clk),
.rst_n(rst_n),
.spi_clk(spi_clk),
.spi_mosi(spi_mosi),
.spi_clk_in(spi_clk),
.spi_miso(spi_mosi)
);

initial begin
    sys_clk  = 1'b0;                          //设置时钟基础值
    rst_n = 1'b0;                             //低电平复位
    #100;
    rst_n = 1'b1;                             //复位释放

 #2000000 $finish;
end
 
always #10 sys_clk = ~sys_clk;     //产生主时钟

endmodule

  仿真结果如下:
在这里插入图片描述
  顶层模拟发送累加数字给发送端,发送端再连接到接收端,数据接收一致。

8.3 上板验证

  我们例化一个ILA,抓取spi_rx模块里的rx_data和rx_data_valid信号,并且打开捕获模式。具体捕获模式原理以及怎么使用,请看Vivado ILA Capture Control 模式与 Advanced Trigger的功能使用以及TSM(触发状态机)的编写,配置图如下:
在这里插入图片描述
在这里插入图片描述
  下载后,我们捕获模式设置BASIC,触发条件选择rx_data_valid。观察波形,发现是连续累加的数据,表面SPI回环成功。

参考

《introduction-to-spi-interface》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/554497.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes Pod的配置管理 ConfigMap和Secret

目录 前言 一、为什么需要配置管理 二、使用ConfigMap管理Pod的配置信息 2.1 创建ConfigMap&#xff08;4种方式&#xff09; 2.1.1 指定ConfigMap的参数创建 2.1.2 指定配置文件创建ConfigMap 2.1.3 通过一个文件内的多个键值对创建ConfigMap 2.1.4 yaml文件创建Config…

Android 性能优化(七):APK安装包体积优化

包体积优化重要性 移动 App 特别关注投放转化率指标&#xff0c;而 App 包体积是影响用户新增的重要因素&#xff0c;而 App 的包体积又是影响投放转化率的重要因素。 Google 2016 年公布的研究报告显示&#xff0c;包体积每上升 6MB 就会带来下载转化率降低 1%&#xff0c; …

114 接口中幂等性的保证

前言 同样是 面试问题 如何确保接口的 幂等性 幂等是一个 较为抽象的概念, 多次重复访问, 不会导致业务逻辑的异常 这里从增删改查, 几个方面列一下 一般来说, 我们核心需要关注的就是 新增 和 更新 对于 增加元素, 首先针对唯一约束进行校验, 然后再处理新增的相关业…

二刷大数据(三)- Flink1.17

目录 Flink概念与SparkStreaming区别分层API 工作流程部署模式**Local Mode****Standalone Mode****YARN Mode****Kubernetes Mode****Application Mode** 运行架构stand alone 核心概念算子链任务槽 窗口窗口**窗口的目的与作用****时间窗口&#xff08;Time Windows&#xff…

vue3中web前端JS动画案例(一)

上述案例主要使用定时器&#xff0c;和绝对定位产生动画 <script setup> import { ref, onMounted, watch } from vue // ----------------------- 01 js 动画介绍--------------------- // 1、匀速运动 // 2、缓动运动&#xff08;常见&#xff09; // 3、透明度运动 //…

抖店底层逻辑,关于5个“为什么”的答案~

我是王路飞。 很多人对抖店的底层逻辑有一种抗拒心态&#xff0c;不想学习和了解。 认为做抖店不就是开店卖货嘛&#xff0c;什么火、什么有热度卖什么就好了&#xff0c;了解那么多“理论知识”有什么用呢&#xff1f; 但往往正是这些基础理论&#xff0c;底层逻辑&#xf…

Linux系统一键安装DataEase结合内网穿透实现公网访问本地WebUI界面

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

PMM2 MySQL监控管理工具

目录 1. PMM介绍 2. 安装PMM服务端 2.1 安装docker 2.1.1 下载docker 2.1.2 上传docker包 2.1.3 启动守护进程 2.1.4 查看docker状态 2.2 安装PMM 2.2.1 下载镜像 2.2.2 load镜像 2.2.3 查看镜像 2.2.4 创建容器 2.2.5 运行镜像 2.2.6 验证PMM服务器 2.2.7 删除…

MySQL行格式(row format)

MySQL行格式&#xff08;row format&#xff09; 表的行格式决定了其行的物理存储方式&#xff0c;这反过来又会影响查询和 DML 操作的性能。随着单个磁盘页面容纳更多行&#xff0c;查询和索引查找可以更快地工作&#xff0c;缓冲池中需要的高速缓存内存更少&#xff0c;写出…

【进阶六】Python实现SDVRPTW常见求解算法——离散粒子群算法(DPSO)

基于python语言&#xff0c;采用经典离散粒子群算法&#xff08;DPSO&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRPTW&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4. …

.netcore+vue新生分班系统的设计与实现

.netcore vue新生分班系统的设计与实现说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于.net core架构和mysql数据库vue 东北石油大学新生分班系统的设计与实现 功能模块&#xff1a; 登录 注册学生 忘记密码 系统首顶 个…

fatal error C1001: An internal error has occurred in the compiler

VS2008驱动项目A&#xff0c;集成一个Wzarid生成的驱动LIB项目B&#xff0c;在编译64位驱动时,出现以下错误&#xff1a; 1>------ Build started: Project: xxxx, Configuration: Release x64 ------ 1>Linking... 1>fatal error C1001: An internal error has occu…

AI时代下,普通人能怎么做?

AI玩法千千万&#xff0c;你已经知道了多少呢&#xff1f;是否已经实践起来&#xff1f; 大家好&#xff0c;我是程序员影子 | 全网同名 一名致力于帮助更多朋友快速入门编程的程序猿 今天&#xff0c;我将以小白入门的视角带着大家了解一下目前火热的AI实践玩法&#xff1b…

SQL --索引

索引 INDEX 伪列 伪装起来的列&#xff0c;不容易被看见&#xff0c;要特意查询才能看见 ROWNUM&#xff1a; 是对查询结果自动生成的一组连续的自然数序号。 SELECT emp.*,ROWNUM FROM emp例题&#xff1a;查询emp表中&#xff0c;前三个员工 SELECT * FROM * from emp w…

工业控制(ICS)---MMS

MMS 工控领域的TCP协议&#xff0c;有时wireshark会将response包解析为tcp协议&#xff0c;影响做题&#xff0c;如果筛选mms时出现连续request包&#xff0c;考虑wireshark解析错误&#xff0c;将筛选条件删除手动看一下 initiate&#xff08;可以理解为握手&#xff09; i…

QQ浏览器模仿Arc,推垂直工作栏引领新体验潮流|TodayAI

之前在硅谷大受欢迎的AI浏览器Arc一经推出就立即引起了广泛关注&#xff0c;Arc自称不仅仅是一款浏览器&#xff0c;而且还是一个“与互联网同规模的平台”。如今&#xff0c;中国的QQ浏览器也想借Arc的热潮之势&#xff0c;同样采用了类似Arc的垂直工作栏&#xff0c;声称能够…

【总结】jdk安装配置后,执行报错java: error while loading shared libraries: libjli.so

目录 问题现象排查分析解决方法 问题现象 安装jdk后&#xff0c;配置好了环境变量&#xff0c;但执行java -vesion命令时&#xff0c;报错。 java: error while loading shared libraries: libjli.so: cannot open shared object file: No such file or directory 看起来这个…

就业分析丨云计算数通双认证学员,入职海外年薪50W

扫码联系誉天猎头部&#xff0c;可享高薪岗位内推、职业规划岗前测评等服务 誉天&#xff0c;我的职业启航之地 大家好&#xff0c;我是誉天学员万*,回想起那段为梦想而奋斗的日子&#xff0c;我的心中充满了感慨。誉天在行业内一直深受好评&#xff0c;在我心中播下了技能的种…

14.多态(多态的构成条件、虚函数的重写、抽象类也就是纯虚函数的类、虚函数表、单继承和多继承的虚函数表)

1.多态的概念 ​ 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 举个例子&#xff1a;比如买票这个行为&#xff0c;当普通人买票时&#xff0c;是全价买票&#xff1b;…

java动态代理--JDK代理

1.概述 JDK动态代理&#xff1a;只能代理实现了接口的类&#xff0c;代理对象是实现了目标对象所有接口的代理类 使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来创建代理对象&#xff0c;工作通过反射机制完成。 2.实现接口InvocationHandler …