FPGA——SPI从机通信实现与仿真


一、设计思路

发送数据计数器
接收数据计数器
从机的时钟SCK是由主机支持的,所以不是一个时钟域,接收时钟SCK需要防止亚稳态接两级触发器
因为边沿检测接两级触发器延后一拍,所以接收的数据要再接一级触发器,与接收数据的边沿对齐

二、参数化设计

从机代码参数说明
DATA_W:为接收、发送数据的个数
工作方式设置:

  • 模式0:spi_sync复位时为0,接收计数器加一条件为上升沿(pedge),发送计数器加一条件为下降沿(nedge)
  • 模式1:spi_sync复位时为0,接收计数器加一条件为下降沿(nedge),发送计数器加一条件为上升沿(pedge)
  • 模式2:spi_sync复位时为1,接收计数器加一条件为下降沿(nedge),发送计数器加一条件为上升沿(pedge)
  • 模式3:spi_sync复位时为0,接收计数器加一条件为上升沿(pedge),发送计数器加一条件为下降沿(nedge)

参考资料:
模式0:CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
模式1:CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
模式2:CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换

三、SPI从机代码(工作模式3)

module spi_slave(
   clk      ,  //50MHz时钟
   rst_n    ,  //复位
   data_in  ,  //要发送的数据
   data_out ,  //接收到的数据
   spi_sck  ,  //主机时钟
   spi_miso ,  //主收从发(从机)
   spi_mosi ,  //主发从收(从机)
   spi_cs   ,  //主机片选,低有效(从机)
   tx_en    ,  //发送使能
   tx_done  ,  //发送完成标志位
   rx_done     //接收完成标志位
);
//改DATA_W的参数即可实现任意字节的发送和接收,现在是两字节发送和接收
parameter DATA_W  =  16;

parameter SYNC_W  =  2;

//计数器参数
parameter CNT_W   =  4;
parameter CNT_N   =  DATA_W;

input                   clk;
input                   rst_n;
input    [DATA_W-1:0]   data_in;
input                   spi_sck;
input                   spi_mosi;
input                   spi_cs;
input                   tx_en;

output   [DATA_W-1:0]   data_out;
output                  spi_miso;
output                  tx_done;
output                  rx_done;

reg      [DATA_W-1:0]   data_out;
reg                     spi_miso;
reg                     tx_done;
reg                     rx_done;

//中间变量
reg      [SYNC_W-1:0]   spi_sync;
wire                    nedge;
wire                    pedge;
reg                     spi_mosi_reg;

//计数器变量
reg      [CNT_W-1:0]    cnt_rxbit;
wire                    add_cnt_rxbit;
wire                    end_cnt_rxbit;

reg      [CNT_W-1:0]    cnt_txbit;
wire                    add_cnt_txbit;
wire                    end_cnt_txbit;
reg                     tx_flag;

//边沿检测
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      //SCK时钟空闲状态位高电平,工作模式3
      spi_sync <= 2'b11;
   else
      spi_sync <= {spi_sync[0],spi_sck};
end
assign nedge = spi_sync[1:0] == 2'b10;
assign pedge = spi_sync[1:0] == 2'b01;

//上升沿接收,工作模式三
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_rxbit <= 0;
   else if(add_cnt_rxbit)begin
      if(end_cnt_rxbit)
         cnt_rxbit <= 0;
      else
         cnt_rxbit <= cnt_rxbit + 1'b1;
   end
end
assign add_cnt_rxbit = pedge;
assign end_cnt_rxbit = add_cnt_rxbit && cnt_rxbit == CNT_N - 1;

//下降沿发送,工作模式三
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_txbit <= 0;
   else if(add_cnt_txbit)begin
      if(end_cnt_txbit)
         cnt_txbit <= 0;
      else
         cnt_txbit <= cnt_txbit + 1'b1;
   end
end
assign add_cnt_txbit = nedge && tx_flag;
assign end_cnt_txbit = add_cnt_txbit && cnt_txbit == CNT_N - 1;

//因为异步信号同步化的原因,为了与延后的下降沿对齐,多打一拍
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      spi_mosi_reg <= 0;
   else
      spi_mosi_reg <= spi_mosi;
end

//下降沿接收
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)  
      data_out <= 0;
   else if(!spi_cs)
      data_out[CNT_N - 1 - cnt_rxbit ] <= spi_mosi_reg;
end

//上升沿发送数据
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      spi_miso <= 0;
   else if(!spi_cs && tx_flag)
      spi_miso <= data_in[CNT_N - 1 - cnt_txbit];
   else
      spi_miso <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      rx_done <= 0;
   else if(end_cnt_rxbit)
      rx_done <= 1;
   else
      rx_done <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      tx_done <= 0;
   else if(end_cnt_txbit)
      tx_done <= 1;
   else
      tx_done <= 0;
end


always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      tx_flag <= 0;
   else if(tx_en)
      tx_flag <= 1;
   else if(end_cnt_txbit)
      tx_flag <= 0;
end

endmodule

四、SPI从机 2字节仿真验证代码

`timescale 1ns / 1ns

module spi_slave_tb();

parameter DATA_W     =  16;
parameter CYCLE      =  20;
parameter CYCLE_SPI  =  40;  

reg                   clk;
reg                   rst_n;
reg    [DATA_W-1:0]   data_in;
reg                   spi_sck;
reg                   spi_mosi;
reg                   spi_cs;

wire   [DATA_W-1:0]   data_out;
wire                  spi_miso;
wire                  rx_done;
wire                  tx_done;

reg    [DATA_W-1:0]   data;
reg                   tx_en;

spi_slave spi_slave(
   .clk      (clk),  //50MHz时钟
   .rst_n    (rst_n),  //复位
   .data_in  (data_in),  //要发送的数据
   .data_out (data_out),  //接收到的数据
   .spi_sck  (spi_sck),  //主机时钟
   .spi_miso (spi_miso),  //主收从发(从机)
   .spi_mosi (spi_mosi),  //主发从收(从机)
   .spi_cs   (1'b0),  //主机片选,低有效(从机)
   .tx_done  (tx_done),  //发送完成标志位
   .tx_en    (tx_en),
   .rx_done  (rx_done)   //接收完成标志位
);
reg [3:0]state;

initial begin                                                  
	rst_n = 1;
	#3;
	rst_n = 0;
   #(3*CYCLE)
   rst_n = 1;
end


initial clk = 1;
always #(CYCLE/2) clk = ~clk;  

//SCK空闲时为1
initial spi_sck = 1;
always #(CYCLE_SPI/2) spi_sck = ~spi_sck;


initial begin
   data <= 16'hAAA1;
   repeat(5)begin
      @ (posedge rx_done)
      data <= data + 1'b1;
   end
   #1000;
   $stop;
end

initial begin
   tx_en = 0;
   data_in = 0;
   @ (posedge rst_n)
   #(10*CYCLE)
   data_in = 16'hAAA9;
   tx_en = 1;
   #(CYCLE)
   tx_en = 0;
end

//下降沿发送数据
always @(negedge spi_sck,negedge rst_n)
begin 
	if(!rst_n)begin
      spi_mosi <= 0;
		state <= 4'b0000;
   end
	else begin
      case(state)
         4'd0:
         begin
            state <= state + 1;
            spi_mosi <= data[15];
         end
         4'd1:
         begin
            state <= state + 1;
            spi_mosi <= data[14];
         end
         4'd2:
         begin
            state <= state + 1;
            spi_mosi <= data[13];
         end
         4'd3:
         begin
            state <= state + 1;
            spi_mosi <= data[12];
         end
         4'd4:
         begin
            state <= state + 1;
            spi_mosi <= data[11];
         end
         4'd5:
         begin
            state <= state + 1;
            spi_mosi <= data[10];
         end
         4'd6:
         begin
            state <= state + 1;
            spi_mosi <= data[9];
         end
         4'd7:
         begin
            state <= state + 1;
            spi_mosi <= data[8];
         end
         4'd8:
         begin
            state <= state + 1;
            spi_mosi <= data[7];
         end
         4'd9:
         begin
            state <= state + 1;
            spi_mosi <= data[6];
         end
         4'd10:
         begin
            state <= state + 1;
            spi_mosi <= data[5];
         end
         4'd11:
         begin
            state <= state + 1;
            spi_mosi <= data[4];
         end
         4'd12:
         begin
            state <= state + 1;
            spi_mosi <= data[3];
         end
         4'd13:
         begin
            state <= state + 1;
            spi_mosi <= data[2];
         end
         4'd14:
         begin
            state <= state + 1;
            spi_mosi <= data[1];
         end
         4'd15:
         begin
            state <= state + 1;
            spi_mosi <= data[0];
         end
         default:state <= 4'b0000;
      endcase
   end
end

endmodule

相关