天天看點

FPGA_Verilog_SPI主機

目錄

1 SPI總線協定

2 工作方式

本設計中:CPOL=1,CPHA = 1

 3 Verilog程式

 3.1 時鐘産生部分

3.2 資料接收部分

3.3 資料發送部分

4 全部程式

5 測試結果

6 部落客的閑聊群

1 SPI總線協定

SPI總線協定介紹SPI(Serion Perpheral Interface)[3]是一種高速的、全雙工、同步的通信總線,并且在晶片的管腳上隻占用4根線,節約了晶片的管腳,同時為PCB的布局節省空間,提供友善,正是出于這種簡單易用的特性,越來越多的晶片內建了這種通信協定。SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主裝置和一個或多個從裝置,需要至少4根線,事實上3根也可以(用于單向傳輸時,也就是半雙工方式)。也是所有基于SPI的裝置共有,分别是MISO(資料輸入),MOSI(資料輸出),SCK(時鐘),NSS(片選)。

2 工作方式

SPI子產品為了和外設進行資料交換,根據外設工作要求,其輸出串行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協定沒有重大的影響。如果 CPOL="0",串行同步時鐘的空閑狀态為低電平;如果CPOL=1,串行同步時鐘的空閑狀态為高電平。時鐘相位(CPHA)能夠配置用于選擇兩種不同的傳輸協定之一進行資料傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)資料被采樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)資料被采樣。SPI主子產品和與之通信的外設音時鐘相位和極性應該一緻

CPHA = 1,就表示資料的輸出是在一個時鐘周期的第一個沿上,至于這個沿是上升沿還是下降沿,這要看CPOL的值而定,CPOL=1那就是下降沿,反之就是上升沿,資料的采樣就是在第二個沿上,CPHA = 0,就表示資料的采樣是在一個時鐘周期的第一個沿上,那麼資料的輸出就在第二個沿上了。

本設計中:CPOL=1,CPHA = 1

 3 Verilog程式

 3.1 時鐘産生部分

 作為SPI通信中的主動方,SPI Master要提供給SPI Slaver一個同步通信時鐘SCK,通信的雙方都在SCK的上升沿和下降沿進行資料的交換,是以設計SPI Master的第一步應該是産生一個SCK,供雙方有一個統一的資料交換時鐘。

reg csr;
assign sck = cnt1[2];
assign cs = csr;

task clk_states;
		     
             if(cnt1==3'b000) begin cnt1 <= 3'b111;cnt <= cnt+1'b1;end
             else cnt1 <= cnt1 - 1'b1;
			 
endtask

[email protected](posedge clk or negedge rst)
begin
     if(!rst)
	  begin
	  txd_flag <= 1'b0;
	  txd_start<=3'd1;
	  cnt <=4'd0;
	  cnt1<=3'b111;
	  csr <= 1'b1;
	  end
	 else
	  begin
	   if(txd_start==txd_start_outside)
	     begin
		  csr <= 1'b0;
		  txd_flag <=1'b1;//txd_flag 信号與cs信号相反,電路當處于通信時為高電平,表示忙碌狀态
	      case(cnt)       //為低電平表示空閑狀态
		  0:clk_states(); 
		  1:clk_states();
		  2:clk_states();
		  3:clk_states();
		  4:clk_states();
		  5:clk_states();
		  6:clk_states();
		  7:
		     begin
			   if(cnt1==3'b000) begin
                               cnt1 <= 3'b111;
                               cnt <= 4'd8; 
                                end
		        else cnt1 <= cnt1 - 1'b1;
                  end
                  8://多加一個狀态延長cs的低電平時間,給從機足夠的時間接受資料
                   begin
                    txd_start <= txd_start+1'b1;
                    cnt <= 4'd0; 
                   end		   
          endcase
        end
       else begin csr <= 1'b1; txd_flag <= 1'b0; end
		 
   end
end 

[email protected](posedge txd_signal or negedge rst) //外部觸發發送脈沖,上升沿觸發發送一次
begin                                      //每次觸發産生8個周期的SCK脈沖,完成一次8bit資料                                           
   if(!rst)                                //的通信
     txd_start_outside <= 1'b0;
   else
     begin
       if(!txd_flag) txd_start_outside <= txd_start_outside+1'b1; //空閑狀态才允許觸發通信
       else;
     end 
end
           

3.2 資料接收部分

     有了固定的SCK,接下來的接收和發送就簡單了,隻需要在SCK的上升沿或下降沿,從MISO引腳一位一位讀取資料,将要發送的資料一位一位的寫入到MOSI引腳。

reg[7:0] rxd_outr;
reg [2:0] rec_cnt;
[email protected](posedge sck or negedge rst) //sck上升沿采樣資料
begin
   if(!rst)
     begin
	  rxd_out <= 8'h00;
	  rec_cnt <= 3'd0;
	 end
   else
	begin
		case(rec_cnt)
		0:begin rxd_outr[7] <= miso;rec_cnt<=3'd1;end
		1:begin rxd_outr[6] <= miso;rec_cnt<=3'd2;end
		2:begin rxd_outr[5] <= miso;rec_cnt<=3'd3;end
		3:begin rxd_outr[4] <= miso;rec_cnt<=3'd4;end
		4:begin rxd_outr[3] <= miso;rec_cnt<=3'd5;end
		5:begin rxd_outr[2] <= miso;rec_cnt<=3'd6;end
		6:begin rxd_outr[1] <= miso;rec_cnt<=3'd7;end
		7:begin 
			rxd_outr[0] <= miso;
			rxd_out <= {rxd_outr[7:1],miso};
			rec_cnt<=3'd0;
			end
		default:;
		endcase
    end
end
           

3.3 資料發送部分

reg [2:0] send_cnt;
[email protected](negedge sck or negedge rst)//sck下降沿沿發送資料
begin
   if(!rst)
     begin
	  send_cnt <= 3'd0;
	 end
   else
   case(send_cnt)
   0:begin mosi <= txd_in[7];send_cnt<=3'd1;end
   1:begin mosi <= txd_in[6];send_cnt<=3'd2;end
   2:begin mosi <= txd_in[5];send_cnt<=3'd3;end
   3:begin mosi <= txd_in[4];send_cnt<=3'd4;end
   4:begin mosi <= txd_in[3];send_cnt<=3'd5;end
   5:begin mosi <= txd_in[2];send_cnt<=3'd6;end
   6:begin mosi <= txd_in[1];send_cnt<=3'd7;end
   7:begin mosi <= txd_in[0];send_cnt<=3'd0;end
   default:;
   endcase
end
           

4 全部程式

module SPI_M(
             clk,
             rst,
             txd_flag, //高電平表示為發送完成處于忙碌狀态,低電平表示處于空閑狀态
             sck,
             cs,
             mosi,
             miso,
             txd_signal,
             rxd_out,
             txd_in
             );
input clk,rst,miso,txd_signal;
output reg mosi,txd_flag;
output wire sck,cs;
output reg[7:0] rxd_out;
input [7:0] txd_in;
reg[2:0] txd_start;
reg[2:0] txd_start_outside;
reg[3:0] cnt;
reg[2:0] cnt1;

reg csr;
assign sck = cnt1[2];
assign cs = csr;

task clk_states;
		     
			   if(cnt1==3'b000) begin cnt1 <= 3'b111;cnt <= cnt+1'b1;end
		        else cnt1 <= cnt1 - 1'b1;
			 
endtask

[email protected](posedge clk or negedge rst)
begin
     if(!rst)
	  begin
	  txd_flag <= 1'b0;
	  txd_start<=3'd1;
	  cnt <=4'd0;
	  cnt1<=3'b111;
	  csr <= 1'b1;
	  end
	 else
	  begin
	   if(txd_start==txd_start_outside)
	     begin
		  csr <= 1'b0;
		  txd_flag <=1'b1;//txd_flag 信号與cs信号相反,電路當處于通信時為高電平,表示忙碌狀态
	      case(cnt)       //為低電平表示空閑狀态
		  0:clk_states(); 
		  1:clk_states();
		  2:clk_states();
		  3:clk_states();
		  4:clk_states();
		  5:clk_states();
		  6:clk_states();
		  7:
		     begin
			   if(cnt1==3'b000) begin
                               cnt1 <= 3'b111;
                               cnt <= 4'd8; 
                                end
		        else cnt1 <= cnt1 - 1'b1;
                   end
                  8://多加一個狀态延長cs的低電平時間,給從機足夠的時間接受資料
                   begin
                    txd_start <= txd_start+1'b1;
                    cnt <= 4'd0; 
                   end		   
          endcase
        end
       else begin csr <= 1'b1; txd_flag <= 1'b0; end
		 
   end
end 

[email protected](posedge txd_signal or negedge rst) //外部觸發發送脈沖,上升沿觸發發送一次
begin
   if(!rst)
     txd_start_outside <= 1'b0;
   else 
   begin
    if(!txd_flag) txd_start_outside <= txd_start_outside+1'b1; 
	else;
   end
  
end

reg[7:0] rxd_outr;
reg [2:0] rec_cnt;
[email protected](posedge sck or negedge rst) //sck上升沿采樣資料
begin
   if(!rst)
     begin
	  rxd_out <= 8'h00;
	  rec_cnt <= 3'd0;
	 end
   else
	begin
		case(rec_cnt)
		0:begin rxd_outr[7] <= miso;rec_cnt<=3'd1;end
		1:begin rxd_outr[6] <= miso;rec_cnt<=3'd2;end
		2:begin rxd_outr[5] <= miso;rec_cnt<=3'd3;end
		3:begin rxd_outr[4] <= miso;rec_cnt<=3'd4;end
		4:begin rxd_outr[3] <= miso;rec_cnt<=3'd5;end
		5:begin rxd_outr[2] <= miso;rec_cnt<=3'd6;end
		6:begin rxd_outr[1] <= miso;rec_cnt<=3'd7;end
		7:begin 
			rxd_outr[0] <= miso;
			rxd_out <= {rxd_outr[7:1],miso};
			rec_cnt<=3'd0;
			end
		default:;
		endcase
    end
end

reg [2:0] send_cnt;
[email protected](negedge sck or negedge rst)//sck下降沿沿發送資料
begin
   if(!rst)
     begin
	  send_cnt <= 3'd0;
	 end
   else
   case(send_cnt)
   0:begin mosi <= txd_in[7];send_cnt<=3'd1;end
   1:begin mosi <= txd_in[6];send_cnt<=3'd2;end
   2:begin mosi <= txd_in[5];send_cnt<=3'd3;end
   3:begin mosi <= txd_in[4];send_cnt<=3'd4;end
   4:begin mosi <= txd_in[3];send_cnt<=3'd5;end
   5:begin mosi <= txd_in[2];send_cnt<=3'd6;end
   6:begin mosi <= txd_in[1];send_cnt<=3'd7;end
   7:begin mosi <= txd_in[0];send_cnt<=3'd0;end
   default:;
   endcase
end
endmodule
           

5 測試結果

    測試用的從機在https://blog.csdn.net/qq_40893012/article/details/103995154

SPI子產品:将SPI主機和SPI從機連接配接起來

module SPI(
          clk,
          rst,
          M_txd_signal,
          M_rxd_out,
          M_txd_in,
          S_rxd_out,
          M_txd_flag,
          S_rxd_flag,
          sck
          );
input clk,rst,M_txd_signal;
output  M_txd_flag,S_rxd_flag,sck;
output [7:0] S_rxd_out,M_rxd_out;
input [7:0] M_txd_in;
wire sck,cs,mosi,miso;
SPI_M SPIM(
           .clk(clk),
           .rst(rst),
           .txd_flag(M_txd_flag),
           .sck(sck),
           .cs(cs),
           .mosi(mosi),
           .miso(miso),
           .txd_signal(M_txd_signal),
           .rxd_out(M_rxd_out),
           .txd_in(M_txd_in)
            );
spi_slaver_ctrl spi_slaver_ctrl1(
                               .clk(clk),
                               .rst(rst),
                               .cs(cs),
                               .sck(sck),
                               .MOSI(mosi),
                               .MISO(miso),
                               .rxd_out(S_rxd_out),
                               .rxd_flag(S_rxd_flag)
                                );
endmodule
					  
           

測試腳本程式

`timescale 1 ns/ 1 ps
module SPI_vlg_tst();
reg clk;
reg rst;
reg M_txd_signal;
reg [7:0] M_txd_in;
// wires                                               
wire [7:0] M_rxd_out;
wire [7:0] S_rxd_out;
wire  S_rxd_flag;
wire sck;
// assign statements (if any)                          
SPI i1 (
  
      .clk(clk),
      .rst(rst),
      .M_txd_signal(M_txd_signal),
      .M_rxd_out(M_rxd_out),
      .M_txd_in(M_txd_in),
      .S_rxd_out(S_rxd_out),
      .M_txd_flag(M_txd_flag),
      .S_rxd_flag(S_rxd_flag),
     .sck(sck)
);
initial                                                
begin                                                     
          M_txd_signal = 0;
		  clk = 0;
   		  rst = 1;
		  #100;
		  rst = 0;
		  #50;
		  rst = 1;  //複位完成
		  #50;
		  M_txd_in = 8'ha5;
		  M_txd_signal = 1; //啟動發送;
		  #50;
		  M_txd_signal = 0;
		  #1400;
		  
		  M_txd_in = 8'h33;  //正常應該接受到8'ha5+1
		  M_txd_signal = 1; //啟動發送;
		  #50;
		  M_txd_signal = 0;
		  #2000;
		  
		  M_txd_in = 8'h66;  //正常應該接受到8'h33+1
		  M_txd_signal = 1; //啟動發送;
		  #50;
		  M_txd_signal = 0;
		  #2000;
		  $stop;
                                                       
// --> end                                             
$display("Running testbench");                       
end                                                    
always                                                                 
begin                                                                        
             #10 clk = ~clk; //50M時鐘                                          
                                             
end                                                    
endmodule

           

測試結果

FPGA_Verilog_SPI主機

從仿真圖可以看出,通信雙方都可以正常的收發。

6 部落客的閑聊群

QQ:701359719

FPGA_Verilog_SPI主機