FPGA_Verilog_SPI主机

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

always@(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 

always@(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;
always@(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;
always@(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

always@(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 

always@(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;
always@(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;
always@(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主机_第1张图片

从仿真图可以看出,通信双方都可以正常的收发。

你可能感兴趣的:(FPGA)