基于FPGA的SPI协议接口的verilog设计

Posted fpga&matlab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于FPGA的SPI协议接口的verilog设计相关的知识,希望对你有一定的参考价值。

1.简介与仿真结论

        SPI是一种三线同步接口,分别为同步时钟信号、数据输入信号和数据输出信号。另外每个扩展芯片还需要一个片选信号,主器件通过片选信号选通与其通信的从器件。它允许处理器与各种外围设备之间以串行方式(如8位数据同时、同步地被发送和接收)进行通信。

系统的功能仿真,MODELSIM。得到如下的结果:

SPI-MASTER仿真结果图

2.理论分析

        在SPI接口中,数据的传输需要1个时钟信号线和两条数据线,共由四根线组成,如下表。由此可见,SPI的结构简单,具有配置灵活等优点。

表1:SPI端口信号

信号

描述

SCK

串行时钟线,由SPI主设备产生

MOSI

主设备输出/从设备输入数据线

MISO

主设备输入/从设备输出数据线

SS

从设备选择线,低电平有效

其基本的管脚结构图如下:

1 SPI结构图

        SPI有主从两种工作模式。在主模式,每一位数据的发送接收需要1次时钟作用,并且只有主机才能初始化数据的传输。数据的传输开始于对主机数据寄存器的写操作。如果移位寄存器是空的,那么数据立即写入移位寄存器,然后8位的数据在串行时钟的作用下通过MOSI引脚依次送出,同时从机的数据通过MISO引脚依次送入主机。在数据传输过程中,SCK信号就是在主模式下由主机产生,并且此时SS信号作为主机的输出,用来传输对别的从机的选择和控制信号。当主机和从机开始通信时,SS信号为低电平;在空闲状态时,SS信号为高电平。

       在从模式下,每一位数据都是在接收到时钟信号之后才发送接收。此时,SCK信号由主机输入,数据在SCK信号作用下依次由MOSI引脚写入数据寄存器。SPI总线包括1根串行同步时钟信号线以及2根数据线。SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设间时钟相位和极性应该一致。

2 CPHA=0SPI总线数据传输时序

3 CPHA=1SPI总线数据传输时序

       SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致。一个典型的SPI系统,包括1个主机和1个或几个外围器件,其中传统的主机一般是微处理器,相应的接口是微机接口。主机通过SPI接口模块与外围器件相连。当该其以主机模式运行时,就可以与整个系统中的外部从机器件进行通信,而当它以从机模式工作时,就能与另外一个主机进行数据的通信。但同一时间内系统中只能有一个主机,否则系统就不能正常工作,本文设计时主要考虑的是工作在主模式的情况下。

SPI操作

       在对SPI核操作的控制寄存器进行设置之后即可启动数据传输。数据传输的启动是通过向数据寄存器SPDR中写入数据。对数据寄存器执行写操作实际上是往一个4项写队列中添加数据项。每次写操作即往队列中写入一个字节的数据。当SPI核被使能时,并且写缓冲区不为空,SPI核就是会自动往发送写队列中最旧的数据项。接收数据与发送数据同时进行;每当发送一个字节的数据,同时总会收到一个字节的数据。如果想要的接收一个字节的数据,需要往写缓冲区写入一个字节的冗余数据,这样做是为了让SPI核启动数据传输,在发送冗余数据的同时接收想要的数据,每完成接收一个字节的数据,这个字节会被移入读缓冲区。读缓冲区和写缓冲区是对立的,是一个独立的4项队列。对数据寄存器执行读操作,便会得到读队列中的数据项。

3.部分核心代码

module spi_master(
                 addr,        //地址
					  in_data,     //并行数据输入
					  out_data,    //并行数据输出
					  rd,          //读使能信号
					  wr,          //写使能信号
					  cs,          //片选信号
					  clk,         //系统时钟
					  miso,        //主设备输入,从设备输出
					  mosi,        //主设备输出,从设备输入
					  sclk         //SPI_clk
					  );
input [1:0]addr;//地址
input [7:0]in_data;//并行数据输入
output[7:0]out_data;//并行数据输出
input rd;//读使能信号
input wr;//写使能信号
input cs;//片选信号
input clk;//系统时钟
inout miso;//主设备输入,从设备输出
inout mosi;//主设备输出,从设备输入
inout sclk;//SPI_clk
//==================================
reg [7:0] out_data;
reg sclk_buffer     =1'b0;
reg mosi_buffer     =1'b0;
reg busy            =1'b0;
reg [7:0] in_buffer =8'd0;
reg [7:0] out_buffer=8'd0;
reg [7:0] clkcount  =8'd0;
reg [7:0] clkdiv    =8'd0;
reg [4:0] count     =5'd0;
always@(cs or rd or addr or out_buffer or busy or clkdiv)
begin
	   if(cs && rd)
    	begin
		case(addr)
	     2'b00:begin 
		        out_data = out_buffer;  //地址为00的时候,数据缓存给out_data信号
			     end
		  2'b01:begin 
		        out_data = 7'b0, busy;//地址为01,表示SPI处于BUSY状态
				  end
		  2'b10:begin 
		        out_data = clkdiv;      //地址为10:则表示输入数据赋给out_data  
				  end
				
		default:begin 
		        out_data = 8'bzzzz_zzzz;//默认状态为高阻态
				  end
		endcase
	   end
end
always@(posedge clk)
begin
//系统处于BUSY状态,
//则在地址00处,将输入信号给予in_buffer,busy取反,
//在地址10处,将输入信号给予clkdiv
	  if(!busy)
	  begin
		    if(cs && wr)
		    begin
			      case(addr)
			      2'b00:  begin 
					        in_buffer = in_data; 
							  busy      = 1'b1; 
							  end
			      2'b10:  begin 
					        clkdiv = in_data; 
							  end
			      endcase
		    end
	  end
	  //系统处于工作状态
else begin
	   clkcount = clkcount + 1;
		if(clkcount >= clkdiv)
		begin
			  clkcount = 0;
			  if((count % 2) == 0)
			  begin
			    //将并行信号转变为SPI串行信号
			    mosi_buffer = in_buffer[7];
			    in_buffer = in_buffer << 1;
			  end
			  if(count > 0 && count < 17)
			  begin
			  //根据数据并串转变的时序产生SPI时钟
			    sclk_buffer = ~sclk_buffer;
			  end
			  count = count + 1;
           //完成一个SPI时序工作周期
			  if(count > 17)
			  begin
			    count = 0;
			    busy = 1'b0;
			  end
		end
	  end
end
//利用SPI时钟,发送串行信号
always@(posedge sclk_buffer)
begin
	out_buffer = out_buffer << 1;
	out_buffer[0] = miso;
end
assign sclk = sclk_buffer;
assign mosi = mosi_buffer;
endmodule
`timescale 1ns/1ns
module spi_master_tb();
reg [1:0] addr;     //地址
reg [7:0] in_data;  //并行数据输入
wire[7:0] out_data; //并行数据输出
reg       rd;       //读使能信号
reg       wr;       //写使能信号
reg       cs;       //片选信号
reg       clk;      //系统时钟
wire      miso;     //主设备输入,从设备输出
wire      mosi;     //主设备输出,从设备输入
wire      sclk;     //SPI_clk
integer counter = 0;
initial
begin
//初始化各个输入信号的值
    addr    = 2'b00;
    in_data = 8'b0000_0000;
    rd      = 1'b0;
    wr      = 1'b0;
    cs      = 1'b0;
    clk     = 1'b0;
	 //===========================
	 //20个时间单位延迟后的值
    #20
    addr    = 2'b10;
    in_data = 8'b0000_0000;
    wr      = 1'b1;
    cs      = 1'b1;
	 //============================
	 //20个时间单位延迟后的值
    #20
    addr = 2'b00;
    in_data = 8'b0000_0000;
    wr = 1'b0;
    cs = 1'b0;
    //=============================
    #20
    for(counter = 0; counter < 256; counter = counter + 1)
    begin    
         addr    = 2'b00;
         in_data = counter;
         cs      = 1'b1; 
         #20
         addr    = 2'b00;
         in_data = 8'b0000_0000;
         wr      = 1'b0;
         cs      = 1'b0;    
         #20
         addr    = 2'b01;
         cs      = 1'b1;
         rd      = 1'b1;
         #20
         while(out_data[0] == 1'b1)
         begin
              #20;
         end
         cs = 1'b0;
         rd = 1'b0;
         #20;
    end
$stop;
end
always  #10 clk = ~clk; //产生测试时钟
spi_master uut(
            .addr     (addr), 
			   .in_data  (in_data), 
			   .out_data (out_data), 
			   .rd       (rd), 
			   .wr       (wr), 
			   .cs       (cs), 
			   .clk      (clk ), 
			   .miso     (miso), 
			   .mosi     (mosi), 
			   .sclk     (sclk)
			   );
endmodule

A38-03

以上是关于基于FPGA的SPI协议接口的verilog设计的主要内容,如果未能解决你的问题,请参考以下文章

基于 FPGA verilog 的 Ethercat 主站工程代码

FPGA的学习:基于SPI协议的Flash驱动写操作控制

FPGA的学习:基于SPI协议的Flash驱动写操作控制

FPGA的学习:基于SPI协议的Flash驱动写操作控制

FPGA的学习:基于SPI协议的Flash驱动写操作控制

ARM与FPGA通过spi通信设计1.spi基础知识