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)

參考資料:https://www.cnblogs.com/liujinggang/p/9609739.html
模式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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM