SPI

概述

全双工与半双工

全双工是指收发可以在同一时刻进行,而半双工是指在同一时刻只能进行一项操作

SPI

SPI是serial peripheral interface(串行外设接口)的缩写,是一种同步串行通信协议。于1979年,由Motorola公司推出,用于节省自家芯片的PCB空间布局。

特点

主从控制

主机可以输出时钟信号和片选信号来控制从设备,从设备只能从主设备处获取时钟。、

同步传输

主设备根据将要交换的数据来产生相应的时钟脉冲(clock pulse),时钟脉冲组成时钟信号(clock signal),时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制主从设备之间何时交换数据、何时对数据进行采样,来保证数据传输同步进行。

缺点

没有应答机制确认是否接收到数据,所以跟IIC比降低了可靠性

管脚

img

  • MOSI:主设备数据输出,从设备数据输入
  • MISO:主设备数据输入,从设备数据输出
  • SCLK:主设备产生,发送给从设备
  • CS:从设备使能信号,由主设备控制。

传输模式

img

SPI总线共有四种传输模式,主要是{CPOL,CPHA}的组合。CPOL代表SPI总线空闲时刻的电平,CPHA规定数据在第几个边沿采样,在第几个边沿切换。

比如第一种模式,CPOL=0,CPHA=0,表示空闲时刻为低电平,数据在上升沿采样,数据在时钟下降沿切换。

常用的为模式0和模式3,下面为模式0时序图

img

在空闲时刻,片选信号为高电平,时钟信号为低电平。在需要传送数据时,片选信号拉低,MOSI于MISO在时钟上升沿进行采样,在时钟下降沿数据切换。可以看到时钟上升沿正对数据中间。

img

SPI设备可以理解为一个大的移位寄存器,它在通信过程中不能单纯充当发送或接受,而应该是在每个CLK周期内进行1bit数据交换。

代码实现

spi-master

module spi_master
#(
    parameter   CLK_FRE = 50    ,  // system clk frequence
    parameter   SPI_FRE = 5     ,  // SCLK frequence
    parameter   DATA_WIDTH = 8  ,  // serial bit number
    parameter   CPOL    = 0     ,  // clock polarity 
    parameter   CPHA    = 0        // clock phase
)(
    input       clk             ,
    input       rst_n           ,
    input   [DATA_WIDTH-1:0]    data_tx , //the serial data from master to slave
    input       start           ,
    input       miso            ,
    output      o_sclk            ,
    output  reg mosi            ,
    output  reg cs_n            ,
    output  reg [DATA_WIDTH-1:0] data_rx ,  //the serial data from slave to master
    output  reg finish          
);
    localparam  CYCLE = CLK_FRE / SPI_FRE - 1   ;
    localparam  SHIFT_CNT = clogb2(DATA_WIDTH)  ;
    localparam  SCLK_CNT  = clogb2(CYCLE)       ;

    function integer clogb2 ( input integer bit_depth) ;
        begin
            for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
                bit_depth = bit_depth >> 1;
        end
    endfunction

    localparam  IDLE      = 3'b000   ;
    localparam  LOAD_DATA = 3'b001   ;
    localparam  SEND_DATA = 3'b010   ;
    localparam  DONE      = 3'b100   ;
    reg [2:0]   state,next_state     ;
    reg         sclk                 ;
    reg         sclk_valid           ;
    reg [SCLK_CNT-1:0] sclk_cnt      ;
    reg [SHIFT_CNT-1:0]shift_cnt     ;
    reg [DATA_WIDTH-1:0] data_tx_temp;
    reg         sclk_delay           ;
    wire        sclk_posedge         ;
    wire        sclk_negedge         ;
    reg        data_sample          ;
    reg        data_switch          ;

    //  generate sclk
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk_cnt <= 'd0;
            else if(sclk_valid)
                begin
                    if(sclk_cnt == CYCLE)
                        sclk_cnt <= 'd0 ;
                    else
                        sclk_cnt <= sclk_cnt + 1 ;
                end
            else
                sclk_cnt <= 'd0;
        end

    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk <= CPOL ;
            else if(sclk_valid)
                begin
                    if(sclk_cnt == CYCLE)
                        sclk <= ~sclk ;
                    else
                        sclk <= sclk ;
                end
            else
                sclk <= CPOL ;
        end
    // detect the sclk edge
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk_delay <= CPOL ;
            else if (sclk_valid)
                sclk_delay <= sclk ;
            else
                sclk_delay <= CPOL ;
        end
    assign sclk_posedge = sclk & (!sclk_delay) ;
    assign sclk_negedge = (!sclk) & sclk_delay ;
    
    always @(*)
        begin
           case ({CPOL,CPHA})
                 2'b00   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 2'b01   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b10   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b11   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 default   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
            endcase
        end
    //FSM
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                state <= IDLE ;
            else
                state <= next_state ;
        end
    always @(*)
        begin
            case(state)
                IDLE : next_state = start ? LOAD_DATA : IDLE ;
                LOAD_DATA : next_state = SEND_DATA  ;
                SEND_DATA : next_state = (shift_cnt == DATA_WIDTH) ? DONE : SEND_DATA;
                DONE : next_state = IDLE ;
            endcase
        end
    //the control signal
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                begin
                    sclk_valid  <= 1'b0;
                    data_tx_temp<= 'd0;
                    cs_n        <= 1'b1;
                    shift_cnt   <= 'd0 ;
                    finish      <= 1'b0;
                end
            else
                begin
                    case(state)
                        IDLE : begin
                            sclk_valid   <= 1'b0    ;
                            data_tx_temp <= 'd0     ;
                            cs_n         <= 1'b1    ;
                            shift_cnt    <= 'd0     ;
                            finish       <= 1'b0    ;
                        end
                        LOAD_DATA : begin
                            sclk_valid   <=  1'b0    ;
                            data_tx_temp <= data_tx ;
                            cs_n         <=  1'b0   ;
                            shift_cnt    <=  'd0    ;
                            finish       <= 1'b0    ;
                        end
                        SEND_DATA : begin
                            sclk_valid   <= 1'b1    ;
                            if(data_switch)
                                begin
                                    shift_cnt <= shift_cnt + 1;
                                    data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
                                end
                            else
                                begin
                                    shift_cnt <= shift_cnt ;
                                    data_tx_temp <= data_tx_temp ;
                                end
                        end
                        DONE : begin
                            sclk_valid <= 1'b0;
                            data_tx_temp <= 'd0;
                            cs_n <= 1'b1 ;
                            finish <= 1'b1;
                            shift_cnt <= 'd0 ;
                        end
                    endcase
                end
        end
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                data_rx <= 'd0;
            else if(data_sample)
                data_rx <= {data_rx[DATA_WIDTH-2:0],miso} ;
            else
                data_rx <= data_rx ;
        end

    assign mosi = data_tx_temp[DATA_WIDTH-1];
    assign o_sclk = sclk_delay;
endmodule

仿真结果

思路

作为spi master端,可以先产生sclk,通过检测sclk的上升沿和下降沿来进行数据采样和数据切换进行数据传输。

在实际应用中spi最为接口信号,可以约定指令格式,分为指令位、读写位、数据位,通过spi master端向slave端发送指令。

spi-slave

module spi_slave
#(
    parameter   CLK_FRE = 50        ,
    parameter   SCLK_FRE = 5        ,
    parameter   DATA_WIDTH = 8      ,
    parameter   CPOL = 0            ,
    parameter   CPHA = 0            
)(
    input           clk             ,
    input           rst_n           ,
    input   [DATA_WIDTH-1:0] data_tx,
    input           data_tx_valid   ,
    input           sclk            ,
    input           cs_n            ,
    input           mosi            ,
    output          miso            ,
    output          finish          ,
    output  reg [DATA_WIDTH-1:0] data_rx
);
    localparam  SAMPLE_CNT = clogb2(DATA_WIDTH);
    reg  [DATA_WIDTH-1:0]   data_tx_temp ;
    reg             sclk_delay      ;
    reg             cs_n_delay      ;
    wire            sclk_posedge    ;
    wire            sclk_negedge    ;
    wire            cs_negedge      ;
    reg            data_sample     ;
    reg            data_switch     ;
    reg [SAMPLE_CNT-1:0] sample_cnt ;
    //capture the edge of sclk
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk_delay <= CPOL ;
            else if(!cs_n)
                sclk_delay <= sclk ;
            else
                sclk_delay <= CPOL ;
        end
    assign sclk_posedge = sclk & (!sclk_delay) ;
    assign sclk_negedge = (!sclk) & sclk_delay ;
    //capture the edge of cs_n
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                cs_n_delay <= 1'b1 ;
            else
                cs_n_delay <= cs_n ;
        end
    assign cs_negedge = (!cs_n) & cs_n_delay ;

    always @(*)
        begin
           case ({CPOL,CPHA})
                 2'b00   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 2'b01   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b10   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b11   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 default   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
            endcase
        end
//trans data from slave to master
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                data_tx_temp <= 'd0;
            else if(data_tx_valid)
                data_tx_temp <= data_tx ;
            else if(!cs_n && data_switch)
                data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
            else
                data_tx_temp <= data_tx_temp ;
        end
assign miso = !cs_n ? data_tx_temp[DATA_WIDTH-1] : 1'b0 ;

//sample data from master to slave
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                data_rx <= 'd0;
            else if(!cs_n && data_sample)
                data_rx <= {data_rx[DATA_WIDTH-2:0],mosi} ;
            else 
                data_rx <= data_rx ;
        end
//finish signal
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sample_cnt <= 'd0;
            else if(!cs_n && data_sample)
                sample_cnt = sample_cnt + 1 ;
            else if(cs_n)
                sample_cnt <= 'd0;
            else
                sample_cnt <= sample_cnt ;
        end
    assign finish = (sample_cnt == DATA_WIDTH) ;

    function integer clogb2 ( input integer bit_depth);
        begin
            for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
                bit_depth = bit_depth >> 1;
        end
    endfunction
endmodule

loop

posted @   骑猪上树的少年  阅读(493)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
回到顶部
点击右上角即可分享
微信分享提示

目录导航