2

用Verilog实现一个SPI从机

 5 months ago
source link: https://www.taterli.com/9824/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

用Verilog实现一个SPI从机

用Verilog实现一个SPI从机

网上很多教程都是怎么做SPI主机,怎么控制外部的东西,但是有时候FPGA是作为接受上游信息的存在的,有必要实现一个从机协议.我这里就一步一步的描述我是怎么思考然后最终实现一个从机的.

首先我这里确定一下我需要的信号.

module top(
    input wire clk_i,
    input wire rst_i,

    input wire sdi_csn_i,
    input wire sdi_clk_i,
    input wire sdi_dat_i,
    output wire sdi_dat_o,
    
    output wire [5:0] led
);

因为我的开发板只有6个LED,所以就暂时让收到的8B数据中的6B显示在LED,其余的都是SPI本身的信号,以及系统主时钟,也可以理解成采样时钟,并且采样时钟是快于SPI时钟的.

然后跨时钟域采集他们,并且要捕获SCK的上升沿.

reg [2:0] sync_sck_ff;
reg [1:0] sync_csn_ff;
reg [1:0] sync_sdi_ff; 

// SCK 边缘采集
assign sync_sck = sync_sck_ff[1] ^ sync_sck_ff[2];
assign sync_csn = sync_csn_ff[1];
assign sync_sdi = sync_sdi_ff[1];

// 跨时钟域处理
always @(posedge clk_i or negedge rstn_i) begin    
    if(!rstn_i) begin
        sync_sck_ff <= 1'b0;
        sync_csn_ff <= 1'b0;
        sync_sdi_ff <= 1'b0;
    end else begin
        sync_sck_ff <= {sync_sck_ff[1:0],sdi_clk_i};
        sync_csn_ff <= {sync_csn_ff[0],sdi_csn_i};
        sync_sdi_ff <= {sync_sdi_ff[0],sdi_dat_i};
    end
end

其中sync_sck指示目前时钟跳变了,sync_csn是传递到clk_i时钟域的CS,sync_sdi是传递到clk_i时钟域的DAT,sdi_dat_o为什么没在这里呢,因为我们clk_i是快时钟,我们只要保证快时钟有足够的周期数,慢时钟的sdi_clk_i就能采集,而因为我们一定会保持到下一个sdi_clk_i来到,所以肯定是足够的.

接下来到最难的状态机部分了,我这里定义了三个状态,用serial_state来记录.

  • serial_state = 00,属于IDLE状态,等CSN有效后转到10状态.
  • serial_state = 10,属于等待SCK状态,当SCK发生上升沿,转到11状态.
  • serial_state = 11,锁存数据,如果计算到8位数据了,说明传输完成.
  • 传输完成或者CSN中途拉高会使得serial_state = 00.
  • 传输完成会触发serial_done上升沿,传输开始会触发serial_start上升沿.
  • 数据应存在serial_sreg,当serial_done上升沿时,sreg有效,应立即取走.
  • sdi_dat_o数据也是在serial_sreg,当然这个可以另外实现.
reg [1:0] serial_state;
reg [3:0] serial_cnt;
reg [7:0] serial_sreg;
reg serial_sdi_ff;
reg serial_start;
reg serial_done;

assign sdi_dat_o = serial_sreg[7];

always @(posedge clk_i or negedge rstn_i) begin
    if(!rstn_i) begin
        serial_start  <= 1'b0;
        serial_done   <= 1'b0;
        serial_state  <= 3'b0;
        serial_cnt    <= 4'b0;
        serial_sreg   <= 8'b0;
        serial_sdi_ff <= 1'b0;
    end
    else begin
        serial_start <= 1'b0;
        serial_done <= 1'b0;

        case (serial_state) 
            2'b00:begin
                serial_cnt <= 4'b0;
                serial_sreg <= 8'h00;
                if(!sync_csn) begin
                    serial_start <= 1'b1;
                    serial_state[1:0] <= 2'b10;
                end 
            end
            2'b10:begin
                serial_sdi_ff <= sync_sdi;
                if(sync_csn) begin
                    serial_state[1:0] <= 2'b00;
                end
                else if (sync_sck) begin
                    serial_cnt <= serial_cnt + 1'b1;
                    serial_state[1:0] <= 2'b11;
                end
            end
            2'b11:begin
                if(sync_csn) begin
                    serial_state[1:0] <= 2'b00;
                end
                else if(sync_sck) begin
                    serial_sreg <= {serial_sreg[6:0],serial_sdi_ff};
                    if(serial_cnt[3] == 1'b1) begin
                        serial_done <= 1'b1;
                        serial_state[1:0] <= 2'b00;
                    end
                    else begin
                        serial_state[1:0] <= 2'b10;
                    end
                end
            end
            default begin
                serial_state[1:0] <= 2'b00;
            end
        endcase
    end
end

把数值附给LED.

reg [7:0] led_reg;
assign led = ~led_reg[5:0];

always @(posedge clk_i or negedge rstn_i) begin
    if(!rstn_i) begin
        led_reg[7:0] = 8'b0;
    end
    else if(serial_done)
    begin
        led_reg[7:0] = serial_sreg[7:0];
    end
end

但是在实测中serial_sreg仍有低概率是错误的,这时候可以再前置一个滤波采集,完整代码如下.

https://gist.github.com/nickfox-taterli/fe3713455b0ba55c73b63d45512f2bd9


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK