Verilog实现SPI协议

元气小坏坏 提交于 2020-11-29 04:30:11

关于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



作者:安公子_
链接:https://www.jianshu.com/p/9f011f119198
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!