FPGA - SPI总线介绍以及通用接口模块设计

一,SPI总线

1,SPI总线概述

        SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口串行外设接口总线(SPI),是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

        SPI系统可直接与各个厂家生产的多种标准外围器件接口,它只需4条线:串行时钟线(SCK)、主机输入/从机输出数据线(MISO)、主机输出/从机输入数据线(MOSI)低电平有效从机选择线(NSS)。

(1)MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

(2)MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

(3)SCK:串口时钟,作为主设备的输出,从设备的输入。

(4)NSS:从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为片选引脚,让主设备可以单独地与特定从设备通信,避免数据线上的冲突。

SPI是一个环形总线结构,由NSS、SCK、MISO、MOSI构成,NSS引脚设置为输入,MOSI引脚相互连接,MISO引脚相互连接,数据在主和从之间串行地传输(MSB位在前)。

2,电路连接

下图表示基本的SPI设备连接示意图。片选信号NSS通常低电平有效。SPI数据传输原理是基于主从设备内部移位寄存器的数据交换。在主设备SCK的控制下,待传数据由各自设备的数据寄存器(Data Register)传输到移位寄存器(Shift Register),再通过MOSI和MISO信号线完成主从设备间的数据交换。

主从设备间数据交换逻辑示意图如下图所示:

3,硬件拓扑

(1)单主机单从机

(2)单主机多从机(片选方式)

        每个从设备都需要单独的片选信号,主设备每次只能选择其中一个从设备进行通信。因为所有从设备的SCK、MOSI、MISO都是连在一起的,未被选中从设备的MISO要表现为高阻状态(Hi-Z)以避免数据传输错误。由于每个设备都需要单独的片选信号,如果需要的片选信号过多,可以使用译码器产生所有的片选信号。

(3)菊花链方式

        数据信号经过主从设备所有的移位寄存器构成闭环。数据通过主设备发送绿色线)经过从设备返回蓝色线)到主设备。在这种方式下,片选和时钟同时接到所有从设备,通常用于移位寄存器和LED驱动器。注意,菊花链方式的主设备需要发送足够长的数据以确保数据送达到所有从设备。切记主设备所发送的第一个数据需(移位)到达菊花链中最后一个从设备。

        菊花链式连接常用于仅需主设备发送数据而不需要接收返回数据的场合,如LED驱动器。在这种应用下,主设备MISO可以不连。如果需要接收从设备的返回数据,则需要连接主设备的MISO形成闭环。同样地,切记要发送足够多的接收指令以确保数据(移位)送达主设备

4,SPI传输模式

通过设置控制寄存器SPICR1中的CPOL(时钟极性)CPHA(时钟相位,将SPI可以分成四种传输模式。

CPOL,即Clock Polarity,决定时钟空闲时的电平为高或低。对于SPI数据传输格式没有显著影响。
1 = 时钟低电平时有效,空闲时为高 
0 = 时钟高电平时有效,空闲时为低 

CPHA,即Clock Phase,定义SPI数据传输的两种基本模式。
1 = 数据采样发生在时钟(SCK)偶数(2,4,6,...,16)边沿(包括上下边沿)
0 = 数据采样发生在时钟(SCK)奇数(1,3,5,...,15)边沿(包括上下边沿)

四种模式如下图所示:

先看第一列两张图(CPHA = 0),采样发生在第一个时钟跳变沿,即数据采样发生在SCK奇数边沿

再看第二列CPHA =1),采样发生在第二个时钟跳变沿,即数据采样发生在SCK偶数边沿

第一行两张图(CPOL = 0),SCK空闲状态为低电平

第二行两张图(CPOL = 1),SCK空闲状态为高电平

主从设备进行SPI通讯时,要确保它们的传输模式设置相同。对于某些场合,可能需要调整CPOL/CPHA设置以满足设备特定要求。

5,SPI时序图 

CPHA = 0

  • 有些器件在片选后数据立即出现在MOSI/MISO管脚,数据锁存于第一个时钟边沿
  • 片选SS先于SCK半个时钟有效
  • 在SCK的第二个时钟边沿,上个时钟边沿锁存的数据写入移位寄存器(MSB或LSB)
  • 以此类推,数据在奇数边沿锁存,在偶数边沿写入移位寄存器
  • 经过16个时钟边沿后,串行传输的数据全部写入(并行的)移位寄存器,完成主从设备的数据交换

CPHA = 1

  • 有些设备要求数据输出在SCK第一个时钟边沿之后,数据锁存于第二个时钟边沿
  • 片选SS先于SCK半个时钟有效
  • 在SCK的第三个时钟边沿,上个时钟边沿锁存的数据写入移位寄存器(MSB或LSB)
  • 以此类推,数据在偶数边沿锁存,在奇数边沿写入移位寄存器
  • 经过16个时钟边沿后,串行传输的数据全部写入(并行的)移位寄存器,完成主从设备的数据交换

二,SPI 通用接口用户端模块-Verilog代码设计

1,SPI通用接口用户端设计框图

分析:
1,设计分频计数器(div_cnt)产生SCLK

2,设计比特计数器(bit_cnt)  sdo变化

3,设计字节计数器(byte_cnt) 

4,定义一个send_data  一直向右移位  把最低位send_data[0]给sdo

5,send_data向右移位,把最低位给sdo,在start信号为高的时候 ,把cmd赋值给send_data

6,CS在start信号为高的时候拉高 ,发完所有数据拉高

2,根据简单分析编写代码

// -----------------------------------------------------------------------------
// Author : RLG
// File   : spi_master.v
// Create : 2024-03-17 14:35:00
// Revise : 2024-03-17 16:20:40
// -----------------------------------------------------------------------------
`timescale 1ns / 1ps
module spi_master #(
	parameter   SYS_CLK_FRWQ     = 50000000,
	parameter   SPI_CLK_FREQ     = 12500000,
	parameter   ADDR_WIDTH       = 24
	)
	(
	input                    		clk         ,
	input			      	 		reset       ,
      	
	//SPI的物理接口      	
	output reg              		spi_sck     ,
	output reg              		spi_cs      ,
	output                   		spi_sdo     ,
	input                   		spi_sdi     ,
	
	//SPI的用户接口	
	input                    	    spi_start   ,
	input      [7:0]            	spi_cmd     ,
	input      [ADDR_WIDTH-1:0] 	spi_addr    ,
	input      [11:0]           	spi_length  ,
	output reg              	    spi_busy    ,
	output reg              	    spi_wr_req  ,
	input      [7:0]               	spi_wr_data ,
	output reg              	    spi_rd_vld  ,
	output reg [7:0]        	    spi_rd_data
	
    );
	localparam DIV_CNT_MAX      = SYS_CLK_FRWQ / SPI_CLK_FREQ - 1;  //只需计算一次 复位之前已经计算好了
	localparam DIV_CNT_MAX_HALE = DIV_CNT_MAX / 2;

	//定义3个计数器
	reg [$clog2(DIV_CNT_MAX) - 1 :0] div_cnt  ; //$clog2函数自动计算最小位宽
	reg [7:0]                        bit_cnt  ; 
	reg [12:0]                       byte_cnt ;
	reg [11:0]                       spi_length_d0;
	reg [ADDR_WIDTH-1:0] 	         spi_addr_d0    ;
	reg [7:0]                        send_data;
	//锁存spi_length
	always @(posedge clk ) 
		if(spi_start) begin
			spi_length_d0 <= spi_length;
		end


	//分频计数器
	always @(posedge clk ) begin 
		if(reset) 
			div_cnt <= 0;
		else if(spi_cs)
			div_cnt <= 0;
		else if(div_cnt == DIV_CNT_MAX) 
			div_cnt <= 0;
		else if(~spi_cs)
			div_cnt <= div_cnt + 1;
	end
	//比特计数器
	always @(posedge clk ) begin
		if(reset) 
			bit_cnt <= 0;
		else if(spi_cs)
			bit_cnt <= 0;
		else if(bit_cnt == 7 &&div_cnt == DIV_CNT_MAX )
			bit_cnt <= 0;
		else if (div_cnt == DIV_CNT_MAX)
			bit_cnt <= bit_cnt + 1;
	end
	//字节计数器
	always @(posedge clk ) begin
		if(reset) 
			byte_cnt <= 0;
		else if(spi_cs)
			byte_cnt <= 0;
		else if(byte_cnt == (spi_length_d0 + ADDR_WIDTH/8) && div_cnt == DIV_CNT_MAX && bit_cnt == 7)
			byte_cnt <= 0;
		else if (div_cnt == DIV_CNT_MAX && bit_cnt == 7)
			byte_cnt <= byte_cnt + 1;
	end
	//片选信号
	always @(posedge clk) 
		if(reset) 
			spi_cs <= 1'b1;
		else if(byte_cnt ==(spi_length_d0 + ADDR_WIDTH/8) && div_cnt == DIV_CNT_MAX && bit_cnt == 7)
			spi_cs <= 1'b1;
		else  if(spi_start)
			spi_cs <= 1'b0;

	//SCK时钟信号
	always @(posedge clk ) begin 
		if(reset) 
			spi_sck <= 0;
		else if(div_cnt == DIV_CNT_MAX)
			spi_sck <= 0;
		else if(div_cnt == DIV_CNT_MAX_HALE )
			spi_sck <= 1;
	end
	//spi_addr_d0
	always @(posedge clk) begin
		if (spi_start) begin
			spi_addr_d0	<= spi_addr;
		end
		else if (byte_cnt <= ADDR_WIDTH/8 && div_cnt == DIV_CNT_MAX && bit_cnt == 7) begin
			spi_addr_d0 <= spi_addr_d0 >> 8;
		end
		else begin
			spi_addr_d0 <= spi_addr_d0;
		end
	end
	//send_data
	always @(posedge clk) begin
		if (reset) begin
			send_data <= 0;
		end
		else if (spi_start) begin
			send_data <= spi_cmd;
		end
		else if(byte_cnt <= ADDR_WIDTH/8 -1 && div_cnt == DIV_CNT_MAX && bit_cnt == 7) begin
			send_data <= spi_addr_d0[7:0];  //发送地址
		end
		else if(byte_cnt >= ADDR_WIDTH/8 && div_cnt == DIV_CNT_MAX && bit_cnt == 7) begin
			send_data <= spi_wr_data;
		end
		else begin
			send_data <= send_data >> 1;
		end
	end

	assign spi_sdo = send_data[0];

	always @(posedge clk ) begin
		if (reset) 
			spi_wr_req <= 0;
		else if (~spi_cs && byte_cnt >= ADDR_WIDTH/8 && div_cnt == DIV_CNT_MAX - 2 && bit_cnt == 7) begin
			spi_wr_req <= 1;
		end
		else begin
			spi_wr_req <= 0;
		end
	end

	always @(posedge clk) begin
		if (reset) 
			spi_rd_data <= 0;
		else if (byte_cnt >= ADDR_WIDTH/8 + 1 && div_cnt == DIV_CNT_MAX ) 
			spi_rd_data <= {spi_sdi,spi_rd_data[7:1]};
		else 
			spi_rd_data <= spi_rd_data;
	end
 	
 	always @(posedge clk) begin
 		if (byte_cnt >= ADDR_WIDTH/8 + 1 && div_cnt == DIV_CNT_MAX - 2 && bit_cnt == 7) begin
 			spi_rd_vld <= 1;
 		end
 		else begin
 			spi_rd_vld <= 0;
 		end
 	end

 	always @(posedge clk ) begin
 		spi_busy  <= ~spi_cs;
 	end

endmodule

3,编写测试文件:

// -----------------------------------------------------------------------------
// Author : RLG
// File   : tb_spi_master.v
// Create : 2024-03-17 15:47:19
// Revise : 2024-03-17 15:57:44
// -----------------------------------------------------------------------------
`timescale 1ns / 1ps
module tb_spi_master();
	parameter      SYS_CLK_FRWQ = 50000000;
	parameter      SPI_CLK_FREQ = 12500000;
	parameter        ADDR_WIDTH = 24;

	reg                   clk                      ;
	reg                   reset                    ;
             
	wire                   spi_sck                 ;
	wire                   spi_cs                  ;
	wire                   spi_sdo                 ;
	wire                   spi_sdi     = 1         ;

	reg                    spi_start               ;
	wire             [7:0] spi_cmd     = 8'h0a     ; 
	wire  [ADDR_WIDTH-1:0] spi_addr    = 24'haabbcc;
	wire            [11:0] spi_length  = 5         ;
	wire                   spi_busy                ;
	wire                   spi_wr_req              ;
	wire            [7:0]  spi_wr_data = 8'haa     ;     
	wire                   spi_rd_vld              ;
	wire            [7:0]  spi_rd_data             ;

	spi_master #(
			.SYS_CLK_FRWQ(SYS_CLK_FRWQ),
			.SPI_CLK_FREQ(SPI_CLK_FREQ),
			.ADDR_WIDTH(ADDR_WIDTH)
		) inst_spi_master (
			.clk         (clk),
			.reset       (reset),
			.spi_sck     (spi_sck),
			.spi_cs      (spi_cs),
			.spi_sdo     (spi_sdo),
			.spi_sdi     (spi_sdi),
			.spi_start   (spi_start),
			.spi_cmd     (spi_cmd),
			.spi_addr    (spi_addr),
			.spi_length  (spi_length),
			.spi_busy    (spi_busy),
			.spi_wr_req  (spi_wr_req),
			.spi_wr_data (spi_wr_data),
			.spi_rd_vld  (spi_rd_vld),
			.spi_rd_data (spi_rd_data)
		);
	// initial clk = 0;
	// always #10 clk = ~clk;

	initial begin
		clk = 0;
		forever #(10)
		clk = ~clk;
	end

	initial begin
		reset = 1;
		#200;
		reset = 0;
	end

	initial begin
		spi_start <= 0;
		#290;
		spi_start <= 1;
		#20;
		spi_start <= 0;
		#8000;
		$stop;
	end
endmodule

4,仿真波形

spi_addr   = 24'haabbcc(给从机的地址) 移位传输 ,分别在

byte_cnt == 1时 传输8'hcc

byte_cnt == 2时 传输8'hbb

byte_cnt == 3时 传输8'hcc

  • 41
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于FPGASPI通信接口设计主要包括硬件和软件两个方面。首先,硬件设计部分要考虑到FPGASPI设备之间的物理连接。一般使用四根信号线来实现SPI通信,包括时钟线、数据输入线、数据输出线和片选线。时钟线用于同步数据的传输,数据输入线负责将数据从外设传输到FPGA,数据输出线则将FPGA的数据发送给外设,片选线用于选择特定的外设。 其次,软件设计部分要实现SPI协议的逻辑控制和数据传输。首先,需要配置FPGA的时钟频率,使其与SPI设备的时钟信号保持同步。接着,通过FPGA的输入输出端口,读取和发送数据。在数据传输过程中,需要注意时序的控制,确保数据的稳定传输。 此外,SPI通信接口设计还需要考虑数据的校验和错误处理。例如,可以通过奇偶校验、CRC校验等方式来验证数据是否正确。若发现错误,可以进行重传或者纠错处理,以确保数据传输的可靠性和完整性。 最后,基于FPGASPI通信接口设计还需要考虑功耗和资源的利用率。可通过设定FPGA的工作频率和电源管理机制来控制功耗,同时利用FPGA的资源来实现SPI通信的高效率。 总而言之,基于FPGASPI通信接口设计需要考虑硬件和软件两个方面,包括物理连接、协议控制、数据传输、校验和错误处理、功耗和资源管理等。这样设计SPI接口可以实现FPGA与外设(如传感器、存储器等)之间的高速、稳定、可靠的数据传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值