Verilog实现SPI协议

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容