美文网首页
Verilog实现SPI协议

Verilog实现SPI协议

作者: 安公子_ | 来源:发表于2017-07-31 13:02 被阅读0次

    关于SPI的教程有很多,这里写下自己学习SPI协议后的总结。

    什么是SPI?

    SPI是Serial Peripheral Interface Bus的缩写,意为:串行外围接口。它是一种用于短距通信的同步串行通信接口标准,主要用于嵌入式系统。这个接口是Motorola在1980年末开发的,之后变成一种约定俗成的通信标准。SPI协议使用单个Master的主-从(Master-Slave)结构,以全双工的方式工作。主设备控制读写,多个从设备通过片选信号(SS)连接。

    Interface

    SPI结构

    采用SPI协议通信的设备通常只需要四条线就可以完成数据的传输,因此,这种占用端口资源少的优点也被称为SPI协议的一个亮点。

    • SCLK:串行时钟,由Master输出,从机接受SCLK信号。它控制着数据传输的节拍,进而影响数据交换的快慢。

    • MOSI:(Master output Slave input)从字面意思就可以知道,这条线为主出从入,也就是主机的数据输出端口,从机的数据输入端口。(实际上,个人认为将MOSI拆为MO和SI理解更好)

    • MISO:(Master input Slave output)主入从出,即主机输入,从机输出。

    • SS:(Slave Select)片选信号。只有该Slave上的SS信号有效时,该Slave才被选中。

    典型的主-从结构

    工作过程

    SPI通信过程本质上来讲,就是数据的交换。在数据交换的过程中完成数据的发送和接收。
    主机控制SS信号和SCLK信号的产生,在SS信号有效时,相应的从机被选中。在SCLK的节拍下完成数据的交换。

    SPI数据交换

    SPI因为SCLK的不同形式可以分为四种工作模式,四种工作模式受控于CPOL和CPHA。也就是串行时钟SCLK的极性和相位。

    SPI模式 时钟极性(CPOL) 时钟相位(CPHA)
    0 0 0
    1 0 1
    2 1 0
    3 1 1

    为了讨论方便,给出一种模式来说明SPI如何工作。

    以下就是SPI协议完成数据交换的时序图。

    Timing

    在SS有效的情况下,主机在SCLK的前沿通过MOSI输出数据(write),而在SCLK的后沿通过MISO采样数据(read)。对于从机而言,同理,SCLK的前沿通过MISO进行数据输出。SCLK的后沿通过MOSI完成数据的采样。

    这样一来,一个SCLK时钟周期可以完成1bit的数据输出和1bit的数据读入,高效的利用了时钟资源。

    实际上,根据时序图即可完成Verilog代码的编写,经过一番折腾,完成了master的数据发送。同时,通过test bench的测试,完成SPI协议的模拟。部分代码给出了一定的说明。

    spi_master.v

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date:    10:41:00 07/29/2017 
    // Design Name: 
    // Module Name:    spi_master 
    // Project Name: 
    // Target Devices: 
    // Tool versions: 
    // Description: 
    //
    // Dependencies: 
    //
    // Revision: 
    // Revision 0.01 - File Created
    // Additional Comments: 
    //
    //////////////////////////////////////////////////////////////////////////////////
    module spi_master(
        input wire[7:0] in_data,
        input wire clk,
        input wire[1:0] addr, // commonds
        input wire wr,
        input wire rd,
        input wire cs,
        output reg[7:0] out_data,
        inout mosi,
        input miso,
        inout sclk
        );
        
        // --------define internal register and buffer--------
        // output buffer stage
        reg sclk_buf = 0;
        reg mosi_buf = 0;
        // idle flag , busy = 0 if no data to receive or send , or else set busy = 1
        reg busy = 0;
        // shift register
        reg[7:0] in_buf = 0;
        reg[7:0] out_buf = 0;
    
        reg[7:0] clk_cnt = 0;
        // division of clk , clk_div=0 means that clk is not be divide , and modify it could implement corresponding sck for device
        reg[7:0] clk_div = 0;
        
        reg[4:0] cnt = 0;
        // --------------------------------------------------
    
        // the port of module links internal buffer
        assign sclk = sclk_buf;
        assign mosi = mosi_buf;
    
        //sclk positive edge read data into out-shift register from miso , implement read operation
        always @(posedge sclk_buf) begin
            out_buf[0] <= miso;
            out_buf <= out_buf << 1;
        end 
    
        // read data (combinatorial logic that level sensitive , detect all input)
        always @(cs or wr or rd or addr or out_buf or busy or clk_div) begin
            out_data = 8'bx;
            if (cs && rd) begin
                case(addr)
                    2'b00 : out_data = out_buf;
                    2'b01 : out_data = {7'b0 , busy}; // when send data encounter spi is busy , return busy singal 
                    2'b10 : out_data = clk_div;
                    default : out_data = out_data;
                endcase
            end
        end
        
        // sclk negitive edge write data to mosi
        always @(posedge clk) begin
            if (!busy) begin // idle state load data into send buffer
                if(cs && wr) begin
                    case(addr) // commonds
                        2'b00 : begin
                            in_buf <= in_data;
                            busy <= 1;
                            cnt <= 0;
                        end
                        2'b10 : begin
                            in_buf <= clk_div; // load number of division to slave for implement sync of sclk
                        end
                        default : in_buf <= in_buf; 
                    endcase
                end
                else if(cs && rd) begin
                    busy <= 1;
                    cnt <= 0;
                end
            end
            else begin // when 8-bits data write into buffer ,  begin send with bit by bit
                clk_cnt <= clk_cnt + 1;
                if (clk_cnt >= clk_div) begin // divide clk
                    clk_cnt <= 0;
    
                    if (cnt % 2 == 0) begin // when csk_buf is negitive , shift data into mosi buffer
                        mosi_buf <= in_buf[7];
                        in_buf <= in_buf << 1;
                    end 
                    else begin
                        mosi_buf <= mosi_buf;
                    end
    
                    if (cnt > 0 && cnt < 17) begin
                        sclk_buf <= ~sclk_buf;
                    end
    
                    // 8-bits had sent over , spi regain idle
                    if (cnt >= 17) begin 
                        cnt <= 0;
                        busy <= 0;
                    end
                    else begin
                        cnt <= cnt;
                        busy <= busy;
                    end
    
                    cnt <= cnt + 1;
                end
            end
        end
    
    
        
    
    
    endmodule
    
    

    testbench.v

    `timescale 1ns / 1ps
    
    ////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer:
    //
    // Create Date:   13:12:38 07/29/2017
    // Design Name:   spi_master
    // Module Name:   E:/ISEProjece/SPI/spi_master_tb.v
    // Project Name:  SPI
    // Target Device:  
    // Tool versions:  
    // Description: 
    //
    // Verilog Test Fixture created by ISE for module: spi_master
    //
    // Dependencies:
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    ////////////////////////////////////////////////////////////////////////////////
    
    module spi_master_tb;
    
        // Inputs
        reg [7:0] in_data;
        reg clk;
        reg [1:0] addr;
        reg wr;
        reg rd;
        reg cs;
        reg miso;
    
        // Outputs
        wire [7:0] out_data;
    
        // Bidirs
        wire mosi;
        wire sclk;
    
        // Instantiate the Unit Under Test (UUT)
        spi_master uut (
            .in_data(in_data), 
            .clk(clk), 
            .addr(addr), 
            .wr(wr), 
            .rd(rd), 
            .cs(cs), 
            .out_data(out_data), 
            .mosi(mosi), 
            .miso(miso), 
            .sclk(sclk)
        );
    
        initial begin
            // Initialize Inputs
            in_data = 0;
            clk = 0;
            addr = 0;
            wr = 0;
            rd = 0;
            cs = 0;
            miso = 0;
    
            // set clk_div , and out by out_data
            #40;
            addr = 0;
            in_data = 8'haa;
            wr = 1;
            cs = 1;
            
            // write data 
            #20 ;
            wr = 0;
            cs = 0;
    
            #360 ;
            wr = 1;
            cs = 1;
            in_data = 8'h91;
    
            #20 ;
            wr = 0;
            cs = 0;
        end
    
        // define clock
        initial begin
            clk = 0;
            forever #10 clk = ~clk;
        end
    endmodule
    

    SPI详细资料参见SPI协议
    代码托管于https://github.com/caxElva/SPI

    相关文章

      网友评论

          本文标题:Verilog实现SPI协议

          本文链接:https://www.haomeiwen.com/subject/rzollxtx.html