SPI总线协议

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

SPI总线的定义

SPI总线工作方式

SPI驱动Verilog实现

总结


SPI总线的定义

SPI(Serial Peripheral Interface)是一种同步串行通信协议,用于在微控制器、传感器、存储器等外设之间进行数据交换。SPI总线是一种全双工的通信方式,它由一个主设备(Master)和一个或多个从设备(Slave)组成。

spi总线的特点:

1、传输方式:SPI总线使用同步的时钟信号进行通信,主设备控制时钟信号的频率和极性。数据在时钟的边沿上升或下降时转移。SPI总线支持全双工通信,意味着主设备和从设备可以同时发送和接收数据。

2、线路结构:SPI总线具有四条线路:时钟线(SCLK),主设备输出的数据线(MOSI),主设备接收的数据线(MISO)和片选线(CS)。

        时钟线(SCLK):时钟信号线,由主机发出,不同的主机可能具有不同的时钟速率,也就是通信频率。

        主设备输出的数据线(MOSI):主机输出数据,从机接收数据。(M代表Master,S代表Slave)

        主设备接收的数据线(MISO):主机接收数据,从机输出数据。

        片选线(CS):由主机选择与某一从机进行通讯。常为低电平有效

一主一从模式:

SPI总线协议_第1张图片

一主多从模式:

(PS:借用一下大佬孤独的单刀的图,侵权请联系我,会立刻删除,原文章链接:FPGA实现的SPI协议(一)----SPI驱动_fpga spi_孤独的单刀的博客-CSDN博客)

SPI总线协议_第2张图片

SPI总线工作方式

SPI的工作模式通常由两个因素决定:一是时钟极性(CPOL,Clock Polarity),二是时钟相位(Clock Phase)。其中时钟极性决定了SCLK在空闲时是低电平还是高电平,时钟相位决定了在什么沿采集数据。

(PS:借用一下大佬孤独的单刀的图,侵权请联系我,会立刻删除,原文章链接::)

        SPI总线协议_第3张图片

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

通过象限可以将这四种模式进行简要概括:

SPI总线协议_第4张图片

SPI总线的优缺点:

优点:

        1、灵活:SPI可以使用不同的工作模式,以满足不同场景的需要。SPI主设备可以配置不同的时钟频率,灵活可变。

        2、高速:相比与串口(UART)、IIC接口,SPI具有很高的通信频率

        3、简单的线路结构,只有SCLK、MOSI、MISO、CS 4条线路

        4、不限于8位,一次可以传输任意大小的字

缺点:

        1、仅支持一个主设备,从设备被动响应,不支持从设备主动传输数据。

        2、虽然SPI总线线路相对简单,但每个外设都需要至少四条线路,因此在连接多个外设时,线路数量会增加。(每增加一个从机都会增加一个片选信号)

        3、主/从机没有应答信号

--------------------------------------------------------------------------------------

SPI驱动Verilog实现

说明:模式0,一主一从模式,使用一段式状态机实现。

(PS:不使用状态机的SPI驱动程序请阅读大佬孤独的单刀的文章:

FPGA实现的SPI协议(一)----SPI驱动_fpga spi_孤独的单刀的博客-CSDN博客)

主机代码:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/09/01 17:04:04
// Design Name: 
// Module Name: SPI_derive
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// SPI总线主机驱动程序
//


module SPI_derive #(
    parameter   integer DW = 8
)
(
   input                    sys_clk     ,           //系统时钟50M
   input                    sys_rst     ,           //复位信号低电平有效
   
   input[DW-1:0]            din         ,           //要发送的数据
   input                    spi_start   ,           //SPI驱动程序启动信号,一个高电平
   input                    spi_end     ,           //SPI驱动程序结束信号
   output reg [DW-1:0]      dout        ,           //接收的数据
   output reg               rec_over    ,           //单个字节接收完毕信号
   output reg               send_over   ,           //单个字节发送结束信号
   
   output reg               sclk        ,           //SPI程序的SCLK 本程序是对系统时钟的2分频
   output reg               cs_n        ,           //SPI片选信号
   output reg               mosi        ,           //SPI输出,给从机发送数据
   input                    miso                    //SPI输入,从机发送过来的数据
          
    );
    
    localparam          idle        = 3'b001  ;
    localparam          send_recv   = 3'b010  ;
    localparam          over        = 3'b100  ;
    reg[2:0]            state                 ;
     
    reg[DW-1:0]         recv_data   = 'd0     ;
    reg                 clk_2d      = 'd0     ;
    reg[2:0]            send_cnt    = 'd0     ;
    reg[2:0]            recv_cnt    = 'd0     ;
    reg[1:0]            cs_n_cnt    = 'd0     ;
    
    
    
    always@(posedge sys_clk,negedge sys_rst)begin
        if(!sys_rst == 1)begin
            state <= idle;
            cs_n  <= 1'b1;
            sclk  <= 1'b0;
            mosi  <= 1'b1;
            send_cnt<= 'd0 ;
            recv_cnt<= 'd0 ;
            dout    <= 'd0 ;
            send_over <= 1'b0;
            rec_over <= 1'b0;
            cs_n_cnt <= 2'b0;
        end
        else begin
            case(state)
                idle:
                    begin
                        if(spi_start == 1)begin
                            state <= send_recv;
                        end
                        else begin
                            state <= state;
                        end
                    end
           send_recv:                                           //send and receive
                    begin
                          if(spi_end == 1)begin 
                            state <= over;  
                          end                
                          else begin         
                            state  <= state; 
                          end  
                          
                         if(cs_n_cnt == 2 || sclk == 1)begin                    //发送 相对于CS的下降沿,发送延迟1个SCLK周期
                             mosi <= din[DW-1-send_cnt];
                             if(send_cnt < 3'b111)begin
                                send_cnt  <= send_cnt + 1 ;
                             end
                             else begin 
                                send_cnt  <= 3'b0;
                             end
                         end
                         else begin
                             mosi <= mosi;                   
                             send_cnt <= send_cnt;
                         end
                         
                         if(send_cnt == 3'b111 && sclk == 1)begin
                            send_over <= 1'b1;
                         end
                         else begin
                            send_over <= 1'b0; 
                         end
///                         
                        if(cs_n_cnt > 2 && sclk == 0)begin                    //接收
                             recv_data[DW-1-recv_cnt] <= miso;
                             if(recv_cnt < 3'b111)begin
                                recv_cnt <= recv_cnt + 1;
                                rec_over <= 1'b0;
                             end
                             else begin
                                rec_over <= 1'b1;
                                recv_cnt <= 3'b0;
                             end   
                         end
                         else begin
                             recv_data<= recv_data;
                             recv_cnt <= recv_cnt;
                             rec_over <= rec_over;
                         end
                         
                         if(recv_cnt == 3'b000 && sclk == 1)begin
                            rec_over <= 1'b1;
                            dout     <= recv_data;
                         end
                         else begin
                            rec_over <= 1'b0;
                            dout     <= 'd0; 
                         end
                         
                         
                         
                         cs_n  <= 1'b0;
                         if(cs_n_cnt < 3)begin
                            cs_n_cnt <= cs_n_cnt + 1;
                         end
                         else begin
                            cs_n_cnt <= cs_n_cnt;
                         end
                            
                            
                         if(cs_n_cnt < 3)begin          //空余出1个的SCLK周期,使得主从机收发的数据对其
                            sclk  <= sclk;
                         end
                         else begin
                            sclk  <= !sclk;
                         end              
                    end
                over:
                    begin
                        state <= idle;
                        cs_n  <= 1'b1;   
                        sclk  <= 1'b0;   
                        mosi  <= 1'b1;   
                        send_cnt <= 'd0 ; 
                        recv_cnt<= 'd0 ;
                        dout  <= 'd0 ; 
                        send_over <= 1'b0;
                        rec_over <= 1'b0;
                        cs_n_cnt <= 2'b0;    
                    end
            endcase
        end
    end
    
    
    
    
endmodule

从机代码:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/09/01 17:04:04
// Design Name: 
// Module Name: SPI_derive
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// SPI总线从机驱动程序
//


module spi_slave #(
    parameter   integer DW = 8
)
(
   input                    sys_clk     ,       //系统时钟50M  
   input                    sys_rst     ,       //复位信号低电平有效
   
   input[DW-1:0]            din         ,       //要发送的数据               
   output reg               spi_start   ,       //SPI驱动程序启动信号,通知后级SPI程序已经启动  
   output reg               spi_end     ,       //SPI驱动程序结束信号,通知后级SPI程序已经结束          
   output reg [DW-1:0]      dout        ,       //接收的数据                
   output reg               rec_over    ,       //单个字节接收完毕信号           
   output reg               send_over   ,       //单个字节发送结束信号           
   
   input                    sclk        ,       //SPI程序的SCLK 本程序是对系统时钟的2分频    
   input                    cs_n        ,       //SPI片选信号                     
   input                    mosi        ,       //SPI输入,主机发送过来的数据               
   output reg               miso                //SPI输出,从机发送的数据             
          
    );
    
    localparam          idle        = 3'b001  ;
    localparam          send_recv   = 3'b010  ;
    localparam          over        = 3'b100  ;
    reg[2:0]            state                 ;
       
    reg[DW-1:0]         recv_data   = 'd0     ;
    reg                 clk_2d      = 'd0     ;
    reg[2:0]            send_cnt    = 'd0     ;
    reg[2:0]            recv_cnt    = 'd0     ;
    reg                 sd_rc_flag  = 'd0     ;
    
    
    

    
    always@(posedge sys_clk,negedge sys_rst)begin
        if(!sys_rst == 1)begin
            state <= idle;
            send_cnt<= 'd0 ;
            recv_cnt<= 'd0 ;
            dout    <= 'd0 ;
            send_over <= 1'b0;
            rec_over <= 1'b0;
            sd_rc_flag <= 1'b0;
            spi_start<= 1'b0;
            spi_end  <= 1'b0;
        end
        else begin
            case(state)
                idle:
                    begin
                        send_cnt<= 'd0 ; 
                        recv_cnt<= 'd0 ; 
                        dout    <= 'd0 ; 
                        send_over <= 1'b0;
                        rec_over <= 1'b0;
                        sd_rc_flag <= 1'b0;
                        spi_end  <= 1'b0;       
                        if(cs_n == 0)begin
                            state <= send_recv;
                            spi_start<= 1'b1;
                        end
                        else begin
                            state <= state;
                            spi_start<= spi_start;
                        end
                    end
           send_recv:                                           //send and receive
                    begin
                          if(cs_n  == 1)begin 
                            state <= over;  
                          end                
                          else begin         
                            state  <= state; 
                          end  
                          

                         if(sd_rc_flag == 0 || sclk == 1)begin                    //发送
                             miso <= din[DW-1-send_cnt];
                             if(send_cnt < 3'b111)begin
                                send_cnt  <= send_cnt + 1 ;
                             end
                             else begin 
                                send_cnt  <= 3'b000;
                             end
                         end
                         else begin         
                             send_cnt <= send_cnt;
                         end
                         
                         if(send_cnt == 3'b111 && sclk == 1)begin
                            send_over <= 1'b1;
                         end
                         else begin
                            send_over <= 1'b0; 
                         end
///                         
                        if(sd_rc_flag == 1 && sclk == 0)begin                    //接收
                             recv_data[DW-1-recv_cnt] <= mosi;
                             if(recv_cnt < 3'b111)begin
                                recv_cnt <= recv_cnt + 1;
                                rec_over <= 1'b0;
                             end
                             else begin
                                rec_over <= 1'b1;
                                recv_cnt <= 3'b000;
                             end   
                         end
                         else begin
                             recv_data<= recv_data;
                             recv_cnt <= recv_cnt;
                             rec_over <= rec_over;
                         end
                         
                         if(recv_cnt == 3'b000 && sclk == 1)begin
                            rec_over <= 1'b1;
                            dout <= recv_data;
                         end
                         else begin
                            rec_over <= 1'b0;
                            dout <= 'd0; 
                         end
                         
                      spi_start<= 1'b0;
                      spi_end  <= 1'b0;
                      sd_rc_flag <= 1'b1;
                  end       
                over:
                    begin
                        state <= idle;  
                        send_cnt <= 'd0 ; 
                        recv_cnt<= 'd0 ;
                        dout  <= 'd0 ; 
                        send_over <= 1'b0;
                        rec_over <= 1'b0;
                        sd_rc_flag <= 1'b0;
                        spi_start<= 1'b0;
                        spi_end  <= 1'b1;
                            
                    end
            endcase
        end
    end
    
    
    
    
endmodule

 主机仿真波形

SPI总线协议_第5张图片

从机仿真波形

SPI总线协议_第6张图片

SPI总线协议_第7张图片

Testbench源代码:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/09/02 10:38:32
// Design Name: 
// Module Name: tb_spi_derive
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module tb_spi_derive();

reg          sys_clk    ;
reg          sys_rst    ;
reg[7:0]     din        ;
reg          dval_i     ;
reg          spi_start  ;
reg          spi_end    ;

wire[7:0]    dout       ;
wire         dval_o     ;
wire         rec_over   ;
wire         send_over  ;

wire         sclk       ;
wire         cs_n       ;
wire         mosi       ;
wire         miso       ;


reg[7:0]     din_s      ;
wire         spi_start_s  ;
wire         spi_end_s    ;

wire[7:0]    dout_s       ;
wire         rec_over_s   ;
wire         send_over_s  ;






reg[3:0]     send_cnt     ;
reg[3:0]     send_cnt_s   ;





SPI_derive #(
    .DW     (8  )
)
u_SPI_derive(
    .sys_clk            (sys_clk ) ,  
    .sys_rst            (sys_rst ) ,
    .din                (din      ),
    .spi_start          (spi_start),
    .spi_end            (spi_end  ),
    .dout               (dout     ),
    .rec_over           (rec_over ),
    .send_over          (send_over),
    .sclk               (sclk     ),
    .cs_n               (cs_n     ),
    .mosi               (mosi     ),
    .miso               (miso     )  
);


spi_slave #(
    .DW     (8  )
)
u_spi_slave(
    .sys_clk            (sys_clk    ) ,  
    .sys_rst            (sys_rst    ) ,
    .din                (din_s      ),
    .spi_start          (spi_start_s),
    .spi_end            (spi_end_s  ),
    .dout               (dout_s     ),
    .rec_over           (rec_over_s ),
    .send_over          (send_over_s),
    .sclk               (sclk       ),
    .cs_n               (cs_n       ),
    .mosi               (mosi       ),
    .miso               (miso       )  
);



initial begin
    sys_clk     = 1;
    sys_rst     = 0;
    spi_start   = 1'b0;
    din         = 8'd0;
    dval_i      = 1'b0;
    spi_end     = 1'b0;
    
    
    #80
    sys_rst     = 1;
    din_s       = 8'h51;
    #30
    spi_start = 1'b1; din = 8'b00110011;
    #20
    spi_start = 1'b0;
    
end

always@(posedge sys_clk,negedge sys_rst)
begin
    if(!sys_rst)begin
        din  <= 8'd0;
        spi_end <= 1'b0;
        send_cnt <= 4'b0;
    end
    else if(send_over == 1) begin
        din <= din + 8'b01;
        if(send_cnt < 10)begin
            send_cnt <= send_cnt + 1;
            spi_end <= 1'b0;
        end
        else begin
            send_cnt <= 4'b0;
            spi_end <= 1'b1;         
        end
    end
    else begin
       spi_end <= 1'b0; 
       send_cnt <= send_cnt;
       din <= din; 
    end
end

always@(posedge sys_clk,negedge sys_rst)
begin
    if(!sys_rst)begin
        din_s  <= 8'b0;
        send_cnt_s<= 4'b0;
    end
    else if(spi_start_s == 1|| send_over_s ==1) begin
        din_s <= din_s + 8'b01;
        if(send_cnt_s < 10)begin
            send_cnt_s <= send_cnt_s + 1;
        end
        else begin
            send_cnt_s <= 4'b0;       
        end
    end
    else begin
       send_cnt_s <= send_cnt_s;
       din_s <= din_s; 
    end
end




initial begin
    forever #10 sys_clk = !sys_clk;
end




endmodule


总结

本文只是对SPI总线的模式0做初步实现,如有错误欢迎指正。

你可能感兴趣的:(接口,fpga开发)