SPI通信协议

SPI通信协议

1. SPI介绍

  • SPI(Serial Peripheral Interface,串行外围设备接口)通信协议,同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输。
  • 应用:EEPROM、Flash、RTC、ADC、DSP等。
  • 优缺点:全双工通信,通讯方式较为简单,相对数据传输速率较快;没有应答机制确认数据是否接受,在数据可靠性上有一定缺陷(与I2C相比)。
  • 物理层:采用主从模式的控制方式,支持单Master,多Slave
    CLK(Serial Clock):时钟信号线,用于同步通讯数据
    MOSI(Master Output, Slave Input):主设备输出 / 从设备输入
    MISO(Master Input, Slave Output):主设备输入 / 从设备输出
    CS(Chip Select):片选信号线,低有效
    SPI通信协议
    SPI通信协议
  • 协议层:默认从高位开始交换数据,四个通信模式。
    CPOL(clock polarity):时钟极性。从设备空闲时,SCK的状态
    CPHA(clock phase):时钟相位。规定数据采样是在时钟信号的奇数边沿还是偶数边沿
    使用奇偶边沿是因为时钟极性有两种情况
    SPI通信协议

2. 实现模式0下的SPI协议

  • 介绍一下模式0,CPOL = 0, CPHA = 0,空闲时刻cslk为低电平,奇数边沿,也就是上升沿的时候master和slave进行数据采样,这也就要求在复位无效后,第一个上升沿到来前,master和slave要把对方带采样的数据寄存输出。
  • 各级模块调用关系
    SPI通信协议
    画图好慢,但是vivado生成的RTL图连线好乱
    SPI通信协议

2.1 top顶层模块

module top_spi(
    input   wire            clk,        // 50MHz
    input   wire            rst_n,      // 复位,低有效
    
    //
    input   wire    [7:0]   wr_data,    // 主机待发送数据
    input   wire            wr_en,      // 主机发送使能信号
    input   wire            rd_en,      // 主机接收使能信号
    output  wire    [7:0]   rd_data,    // 主机接收缓冲区
    output  wire            wr,         // 主机发送标志信号
    output  wire            rd          // 主机接收接收缓冲区数据标志信号
    );
    
    wire    [8:0]   clk_cnt;
    wire    [3:0]   cnt;
    wire            cs;
    wire            sclk;
    wire            mosi;
    wire            miso;
    
    
    // 时钟计数器,50MHz时钟进行500分频产生100KMz的sclk时钟
    // clk_cnt == clk_value / 4 * 4 - 1     上升沿
    // clk_cnt == clk_value / 4 * 1 - 1     高电平
    // clk_cnt == clk_value / 4 * 2 - 1     下降沿
    // clk_cnt == clk_value / 4 * 3 - 1     低电平
    parameter  clk_value   =   500;

    
    // 主机模块
    spi_master  # (
        .clk_value(clk_value)
        )
    spi_master0 (
        // 系统时钟和复位
        .clk(clk),
        .rst_n(rst_n), 
        
        // SPI接口
        .miso(miso),
        .mosi(mosi),
        .sclk(sclk),
        .cs(cs),
        
        // 主机控制信号
        .wr_data(wr_data),
        .wr_en(wr_en),
        .rd_en(rd_en),
        .rd_data(rd_data),
        .wr(wr),
        .rd(rd),
        
        .clk_cnt(clk_cnt),
        .cnt(cnt)
    );
        
     // 从机模块
     spi_slave  # (
        .clk_value(clk_value)
        )  
     spi_slave0 (
        .clk(clk),
        .rst_n(rst_n),
        
        .simo(mosi),
        .sclk(sclk),
        .cs(cs),
        .somi(miso),
        
        .rd_en(wr_en),
        .wr_en(rd_en),
        .clk_cnt(clk_cnt),
        .cnt(cnt),
        
        .slave_data(slave_data)
        );
            
endmodule

2.2 master模块

module spi_master(
    input   wire            clk,        // 50MHz
    input   wire            rst_n,      // 复位,低有效
    
    // SPI接口信号
    input   wire            miso,       // 主收从发
    output  wire            mosi,       // 主发从收
    output  wire            sclk,       // SPI工作时钟
    output  wire            cs,         // 从设备片选,低有效
    
    //
    input   wire    [7:0]   wr_data,    // 主机待发送数据
    input   wire            wr_en,      // 主机发送使能信号
    input   wire            rd_en,      // 主机接收使能信号
    output  wire    [7:0]   rd_data,    // 主机接收缓冲区
    output  wire            wr,         // 主机发送标志信号
    output  wire            rd,         // 主机接收接收缓冲区数据标志信号
    
    output  wire    [8:0]   clk_cnt,
    output  wire    [3:0]   cnt
    );
    
    
    // 时钟计数器,50MHz时钟进行500分频产生100KMz的sclk时钟
    // clk_cnt == clk_value / 4 * 4 - 1     上升沿
    // clk_cnt == clk_value / 4 * 1 - 1     高电平
    // clk_cnt == clk_value / 4 * 2 - 1     下降沿
    // clk_cnt == clk_value / 4 * 3 - 1     低电平
    parameter  clk_value   =   500;

    
    // 用来产生sclk时钟信号和cs片选信号
    control #(
        .clk_value(clk_value)
            )
    control0 (
        .clk(clk),
        .rst_n(rst_n),
        .wr_en(wr_en),      // 主机写请求
        .rd_en(rd_en),      // 主机读请求
        
        .sclk(sclk),        // SCLK时钟信号
        .cs(cs),            // 从机片选信号
        .clk_cnt(clk_cnt),  // 时钟计数器
        .cnt(cnt)           // 数据计数器
        );
            
            
    // 发送
    spi_tx #(
        .clk_value(clk_value)
        )
    spi_tx0 (
        .clk(clk),
        .rst_n(rst_n),
        .wr_en(wr_en),
        .wr_data(wr_data),
        .clk_cnt(clk_cnt),
        .cnt(cnt),
        
        .wr(wr),
        .mosi(mosi)
         );
         
            
    // 接收
    spi_rx  #(
        .clk_value(clk_value)
        ) 
    spi_rx0  (
        .clk(clk),
        .rst_n(rst_n),
        .rd_en(rd_en),
        .clk_cnt(clk_cnt),
        .cnt(cnt),
        .miso(miso),
        
        .rd(rd),
        .rd_data(rd_data)
        );      

endmodule

2.2.1 control控制模块

module control(
    input   wire            clk,    // 50MHz
    input   wire            rst_n,  
    input   wire            wr_en,  // 主机写使能 
    input   wire            rd_en,  // 主机读使能
    
    output  reg             sclk,
    output  reg             cs,
    output  reg     [8:0]   clk_cnt,
    output  reg     [3:0]   cnt
    );
    
    // parameter  clk_value   =   9'd500;
    parameter   clk_value   =   9'b111110100;
    
    // 状态声明
    parameter   IDLE    =   2'b00;  // 空闲状态
    parameter   WORK    =   2'b01;  // 工作状态
    parameter   STOP    =   2'b10;  // 停止状态
    
    // 状态寄存器
    reg [1:0]   curr_state;
    reg [1:0]   next_state;
    
    
    // 时钟计数器
    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            clk_cnt <=  9'd0;
        end else if ( clk_cnt == (clk_value - 1) )  begin
            clk_cnt <=  9'd0;
        end else    begin
            clk_cnt <=  clk_cnt + 1'd1;
        end
    end
    
    // 状态转移
    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            curr_state  <=  IDLE;
        end else if (clk_cnt == (clk_value / 4 * 2 - 1))    begin   // 状态再sclk下降沿改变
            curr_state  <=  next_state;
        end else    begin
            curr_state  <=  curr_state;
        end
    end
    
    // 次态产生组合逻辑
    always @ (wr_en or rd_en or cnt)    begin
        case (curr_state)
            IDLE:    begin
                if (wr_en || rd_en) next_state  <=  WORK;   // 写/读使能,开始工作
                else                next_state  <=  IDLE;
            end
            WORK:    begin
                if (cnt == 4'd8)    next_state  <=  STOP;   // 8bit数据已完成写/读
                else                next_state  <=  WORK;
            end
            STOP:    begin
                if (wr_en || rd_en) next_state  <=  WORK;   // 写/读使能,继续工作
                else                next_state  <=  IDLE;
            end
            default:    begin
                                    next_state  <=  IDLE;
            end
        endcase
    end
    
    // 三段式状态机输出
    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sclk    <=  1'b0;
            cs      <=  1'b1;
            cnt     <=  4'd0;
        end else    begin
            case (curr_state)
                IDLE:   begin
                    sclk    <=  1'b0;
                    cs      <=  1'b1;
                    cnt     <=  4'd0;
                end
                WORK:  begin
                    cs      <=  1'b0;
                    if (clk_cnt == clk_value / 4 * 4 - 1)   begin           // 上升沿位置
                        sclk    <=  1'b1;
                        cnt     <=  cnt + 1'd1;     // 模式0工作
                    end else if (clk_cnt == clk_value / 4 * 2 - 1)  begin   // 下降沿位置
                        sclk    <=  1'b0;
                        cnt     <=  cnt;
                    end else    begin
                        sclk    <=  sclk;
                        cnt     <=  cnt;
                    end
                end
                STOP:   begin
                    sclk    <=  1'b0;
                    cs      <=  1'b1;
                    cnt     <=  4'd0;
                end
                default:    begin
                    sclk    <=  1'b0;
                    cs      <=  1'b1;
                    cnt     <=  4'd0;
                end
            endcase
        end
    end
    
endmodule

2.2.2 tx发送模块

// 发送
// 模式0下,sclk下降沿输出
module spi_tx(
    input   wire            clk,
    input   wire            rst_n,
    input   wire            wr_en,      // 写/发送使能信号
    input   wire    [7:0]   wr_data,    // 写/发送数据
    input   wire    [8:0]   clk_cnt,    // 时钟计数器
    input   wire    [3:0]   cnt,        // 数据计数器
    
    output  reg             wr,         // 写/发送标志信号,低有效
    output  reg             mosi        // 主发从收
    );
 
    parameter  clk_value   =   500;
    
    // 状态声明
    parameter   IDLE    =   2'b00;  // 空闲状态
    parameter   WORK    =   2'b01;  // 工作状态
    parameter   STOP    =   2'b10;  // 停止状态
    
    // 状态寄存器
    reg [1:0]   curr_state;
    reg [1:0]   next_state;
    
    // 状态转移
    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            curr_state  <=  IDLE;
        end else if (clk_cnt == clk_value / 4 * 2 - 1)  begin
            curr_state  <=  next_state;
        end
    end
    
    // 次态产生组合逻辑
    always @ (wr_en or cnt) begin
        case (curr_state)
            IDLE:   begin
                if (wr_en)          next_state  <=  WORK;
                else                next_state  <=  IDLE;
            end
            WORK:   begin
                if (cnt == 4'd8)    next_state  <=  STOP;
                else                next_state  <=  WORK;
            end
            STOP:   begin
                if (wr_en)          next_state  <=  WORK;
                else                next_state  <=  IDLE;
            end
            default:    begin
                                    next_state  <=  IDLE;
            end
        endcase
    end
    
    // 三段式状态机输出
    always @ (*) begin
        if (!rst_n) begin
            mosi    <=  1'b0;
            wr      <=  1'b1;
        end else    begin
            case (curr_state)
                IDLE:   begin
                    mosi    <=  1'b0;
                    wr      <=  1'b1;
                end
                WORK:   begin
                    if (clk_cnt == clk_value / 4 * 2 - 1)   begin
                        case (cnt)
                            4'd0:   mosi    <=  wr_data[7];
                            4'd1:   mosi    <=  wr_data[6];
                            4'd2:   mosi    <=  wr_data[5];
                            4'd3:   mosi    <=  wr_data[4];
                            4'd4:   mosi    <=  wr_data[3];
                            4'd5:   mosi    <=  wr_data[2];
                            4'd6:   mosi    <=  wr_data[1];
                            4'd7:   mosi    <=  wr_data[0];
                            default:    mosi    <=  1'b0;
                        endcase
                    end
                    wr  <=  1'b0;
                end
                STOP:   begin
                    mosi    <=  1'b0;
                    wr      <=  1'b1;
                end
                default:    begin
                    mosi    <=  1'b0;
                    wr      <=  1'b1;
                end
            endcase
        end
    end

endmodule

2.2.3 rx接收模块

// 接收
// 模式0下,下降沿输出,上升沿进行数据采样
module spi_rx(
    input   wire            clk,
    input   wire            rst_n,
    input   wire            rd_en,      // 读/接收使能信号
    input   wire    [8:0]   clk_cnt,    // 时钟计数器
    input   wire    [3:0]   cnt,        // 数据计数器
    input   wire            miso,       // 主收从发
    
    output  reg             rd,         // 读/接收数据标志信号,低有效
    output  reg      [7:0]  rd_data     // 读/接收数据
    );

    parameter   clk_value   =   500;
    
    // 状态声明
    parameter   IDLE    =   2'b00;  // 空闲状态
    parameter   WORK    =   2'b01;  // 工作状态
    parameter   STOP    =   2'b10;  // 停止状态
    
    // 状态寄存器
    reg [1:0]   curr_state;
    reg [1:0]   next_state;
    
    // 状态转移   
    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            curr_state  <=  IDLE;
        end else if (clk_cnt == clk_value / 4 * 2 - 1)  begin
            curr_state  <=  next_state;
        end
    end 
    
    // 次态产生逻辑
    always @ (rd_en or cnt) begin
        case (curr_state)
            IDLE:   begin
                if (rd_en)          next_state  <=  WORK;
                else                next_state  <=  IDLE;
            end
            WORK:   begin
                if (cnt == 4'd8)    next_state  <=  STOP;
                else                next_state  <=  WORK;
            end
            STOP:   begin
                if (rd_en)          next_state  <=  WORK;
                else                next_state  <=  IDLE;
            end
            default:   begin
                                    next_state  <=  IDLE;
            end
        endcase
    end
    
    // 三段式状态机输出
    always @ (*) begin
        if (!rst_n) begin
            rd      <=  1'b1;
            rd_data <=  8'b0; 
        end else    begin
            case (curr_state)
                IDLE:   begin
                    rd      <=  1'b1;
                    rd_data <=  8'b0;
                end
                WORK:   begin
                    
                    if (clk_cnt == clk_value / 4 * 4 - 1)   begin
                        case (cnt)
                            4'd0:   rd_data[7]  <=  miso;
                            4'd1:   rd_data[6]  <=  miso;
                            4'd2:   rd_data[5]  <=  miso;
                            4'd3:   rd_data[4]  <=  miso;
                            4'd4:   rd_data[3]  <=  miso;
                            4'd5:   rd_data[2]  <=  miso;
                            4'd6:   rd_data[1]  <=  miso;
                            4'd7:   rd_data[0]  <=  miso;
                            default:    rd_data <=  8'b0;
                        endcase
                    end
                    rd      <=  1'b0;
                end
                STOP:   begin
                    rd      <=  1'b1;
                end
                default:    begin
                    rd      <=  1'b1;
                    rd_data <=  8'b0;
                end
            endcase
        end
    end

endmodule

2.3 slave模块

// 从机模块
/*
只需要一个module实现,接发都在这一个模块中
用reg寄存发送最高位的数据,把内部寄存器用成移位的,接收来的寄存器放在最低位,用数据计数器cnt数8次交换

*/
module spi_slave(
    input   wire            clk,
    input   wire            rst_n,
    
    // SPI从机接口信号
    input   wire            simo,       // 从收主发
    input   wire            sclk,       // SPI工作时钟
    input   wire            cs,         // 从机片选,低有效
    output  reg             somi,       // 从发主收
    
    // 控制信号
    input   wire            rd_en,
    input   wire            wr_en,
    input   wire    [8:0]   clk_cnt,    // 时钟计数器
    input   wire    [3:0]   cnt,        // 数据计数器
    
    // 寄存器信息
    output  reg     [7:0]   slave_data
        
    );
    
    // clk_cnt == clk_value / 4 * 4 - 1     上升沿
    // clk_cnt == clk_value / 4 * 1 - 1     高电平
    // clk_cnt == clk_value / 4 * 2 - 1     下降沿
    // clk_cnt == clk_value / 4 * 3 - 1     低电平
    parameter  clk_value   =   9'd500;
  
    // 状态声明
    parameter   IDLE    =   2'b00;  // 空闲状态
    parameter   WORK    =   2'b01;  // 工作状态
    parameter   STOP    =   2'b10;  // 停止状态
    
    // 状态寄存器
    reg [1:0]   curr_state;
    reg [1:0]   next_state;
    
    reg         flag;
    
    
    // 状态转移   
    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            curr_state  <=  IDLE;
        end else if (clk_cnt == clk_value / 4 * 2 - 1)  begin
            curr_state  <=  next_state;
        end
    end     
    
    // 次态产生逻辑
    // 默认发送和接收是同时进行的
    always @ (rd_en or wr_en or cnt) begin
        case (curr_state)
            IDLE:   begin
                if (rd_en)          next_state  <=  WORK;
                else                next_state  <=  IDLE;
            end
            WORK:   begin
                if (cnt == 4'd8)    next_state  <=  STOP;
                else                next_state  <=  WORK;
            end
            STOP:   begin
                if (rd_en)          next_state  <=  WORK;
                else                next_state  <=  IDLE;
            end
            default:   begin
                                    next_state  <=  IDLE;
            end
        endcase
    end
    
    // 三段式状态机的输出情况
    // 当进入交换状态WORK时,先发送1bit数据
    always @ (cs or cnt)    begin
        if (!rst_n) begin
            somi        <=  1'b0;           // 发送1'b0
            slave_data  <=  8'b1111_0101;   // 初始化从机寄存器
            flag        <=  1'b0;
        end else    begin
            case (curr_state)
                IDLE:   begin
                    somi        <=  1'b0;
                    slave_data  <=  8'b1111_0101;  
                end
                WORK:   begin
                    somi        <=  slave_data[7];
                    slave_data[7:0]  <=  {slave_data[6:0], simo};
                    flag        <=  simo;
                end
                STOP:   begin
                    somi        <=  1'b0;
                    slave_data  <=  8'b1111_0101;  
                end
                default:    begin
                    somi        <=  1'b0;
                    slave_data  <=  8'b1111_0101;  
                end
            endcase
        end
    end

endmodule

2.4 测试

module sim_spi();
    reg             clk;
    reg             rst_n;
    
    
    reg     [7:0]   wr_data;        // 主机待发送
    reg             wr_en;          // 主机发送使能信号
    reg             rd_en;          // 主机接收使能信号
    wire    [7:0]   rd_data;        // 主机接收缓冲区
    wire            wr;             // 主机发送标志信号
    wire            rd;             // 主机接收标志信号
    
    
    top_spi top_spi0 (
        .clk(clk),
        .rst_n(rst_n),
        
        .wr_data(wr_data),
        .wr_en(wr_en),
        .rd_en(rd_en),
        .rd_data(rd_data),
        .wr(wr),
        .rd(rd)
        );
    
     // 50MHz产生
     parameter  clk_period_50M  =   20;
     always #(clk_period_50M / 2)   clk = ~clk;
     
     
     // 信号初始化
     initial    begin
        clk     =   0;
        rst_n   =   0;      // 复位使能,低有效

        wr_data =   8'b0;   // 主机待发送数据
                            // 这是主机内部寄存器的已有数据
        wr_en   =   1'b0;   // 主机发送使能,高有效
        rd_en   =   1'b0;   // 主机接收使能,高有效
     end
     
     
     initial    begin
        
     end
     initial    begin
        wr_data =   8'b1010_1010;   // 这是主机待发送的数据
        #200    rst_n   =   1'b1;   // 复位无效
        #200    wr_en   =   1'b1;   // 发送写使能
                rd_en   =   1'b1;   // 接收读使能
        
        #5000       //miso    =   1'b1;
        #10000      //miso    =   1'b1;
        #10000      //miso    =   1'b0;
        #10000      //miso    =   1'b1;
        #10000      //miso    =   1'b0;
        #10000      //miso    =   1'b0;
        #10000      //miso    =   1'b1;
        #10000      //miso    =   1'b1;
        
        #5000       wr_en   =   1'b0;
                    rd_en   =   1'b0;
     
     end
     
endmodule

SPI通信协议

3. 问题分析与总结

  • 上面的波形放大后,局部显示如下,为什么master和slave的发送没有同步在一个时钟?
    原因:master的发送是组合逻辑,当curr_state发生变化就输出一次,slave的输出组合逻辑由cs触发,在control模块,cs的变化是时序逻辑,所以晚了一拍
    SPI通信协议

4. 参考

基于FPGA的SPI主机数据接收发送控制器

上一篇:***链分析标准化


下一篇:Paths and Trees(最短路)