基于fpga的spi通信设计_协议的概念

基于fpga的spi通信设计_协议的概念一、SPI协议1、SPI协议概括SPI(SerialPeripheralInterface)——串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟,AD转换器以及数字信号处理器和数字信号解码器之间。SPI是一种高速,全双工,同步的通信总线,在芯片上只占用四根线(CS、MOSI、MISO、SCK),极大的…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

一、SPI协议

1、SPI协议概括

SPI(Serial Peripheral Interface)——串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟,AD转换器以及数字信号处理器和数字信号解码器之间。SPI是一种高速,全双工,同步的通信总线,在芯片上只占用四根线(CS、MOSI、MISO、SCK),极大的节约了芯片的引脚。

 2、SPI物理层

SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或者多个从设备。图1是一个主设备一个从设备的物理连接示意图。图中SCK是由主设备发送给从的时钟,该时钟决定了主设备发送数据的速率;MOSI是主设备发送给从设备的数据;MISO是从设备发送给主设备的数据;CS是片选信号,即只有片选信号为预先规定的使能信号时(高电平或者低电平)对此芯片的操作才有效。

基于fpga的spi通信设计_协议的概念
图1  点对点通信

 

基于fpga的spi通信设计_协议的概念
图2 一主多从通信

 

3、SPI协议层

SPI通信是四线串行通信,也就是说数据是一位一位传输的。这也即是SCK存在的意义,SCK提供通信所需的时钟脉冲,MOSI和MISO则基于此时钟进行数据传输。数据输出通过MOSI线,数据在时钟的上升沿或下降沿时改变,在紧接着的下降沿或者上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,至少在8次时钟信号的改变(上升沿和下降沿为一次),就可以实现8位数据的传输。

需要注意的是,SCK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少要有一个主控设备。这样传输的特点:此传输方式有一个优点,与普通串行通信不同,普通的串行通信一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SCK时钟线的控制可以完成对通信的控制。SPI协议还可以实现数据的交换:因为SPI的数据输入和输出线独立所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要时改变和采集数据的时间不同,在时钟信号上升沿或下降沿采集有不同的定义。

SPI总线有四种工作方式(SPI0、SPI1、SPI2、SPI3),其中使用的最为广泛的是SPI0和SPI3方式。

SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升沿或下降沿)数据被采集;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升沿或下降沿)数据被采集。SPI主模块和与之通信的外设时钟相位和极性应该一致。

SPI时序图详解:SPI接口有四种不同的数据传输时序,取决于CPOL和CPHA的组合。图3中给出了这四种时序,时序与CPOL和CPHA的关系也可以从图中看出。

基于fpga的spi通信设计_协议的概念
图3 SPI四种时序

 图3中可以看出,CPOL是用来决定SCK时钟信号空闲时的电平。CPOL=0,SCK空闲时为低电平;CPOL=1,SCK空闲时为高电平。CPHA是用来决定采样输入数据MISO时刻,CPHA = 0,在第一个SCK时钟沿进行数据采样;CPHA=1,在第二个SCK时钟沿进行数据采集。(工作模式的确定:由SLAVE的工作模式确定MASTER的工作模式)。

二、SPI协议使用举例

这里通过使用SPI3来实现主机发送数据。

基于fpga的spi通信设计_协议的概念
图4  SPI3 工作模式的主机发送数据

在SPI3模式下,CPOL = 1,CPHA = 1。SCK在空闲时为高电平,在SCK的第二个时钟沿从机进行数据的采集(只考虑主机发送情况),在SCK的第一个时钟沿发送数据MOSI。

三、使用verilog实现SPI3工作模式的时序

1、SPI3模式下工作过程如下图所示,

基于fpga的spi通信设计_协议的概念
图5 SPI发送数据过程

接下来分析图5所示SPI发送数据的过程,首先在复位信号到来时,进入s0状态,在s0状态计数器和分频器模块加载初始值,如果发送数据开始信号spi_start有效进入s1状态,s1状态加载待发送的数据,同时计数器计数计数,分频器开始工作,如果i=1,进入s2状态,s2状态主要用来发送数据,如果i为偶数,进入s3状态,该状态是用来采集数据,由于只考虑发送,因此此模块不进行数据采集工作,如果i=15,进入s4状态,否则如果i为奇数,则进入s2状态。;在s4状态,发送最后一位数据,如果i=16,进入s5状态,此时整个SPI时序模拟完成。

2、数据路径

由图5可知,构成SPI发送时序的基本电路块包括计数器,移位寄存器和触发器模块。

基于fpga的spi通信设计_协议的概念
图6 数据路径

图6中,左移寄存器将8位的待发送的数据spi_data转换为串行的数据mosi一位一位的发送出去,计数器用来计数发送数据的个数,触发器用来产生分频后的sck时钟信号。 

 3、控制信号

 

基于fpga的spi通信设计_协议的概念
图7 控制信号

图7中给出了各个状态哪些控制信号应该有效,参照图5图6图7可以理清spi整个发送数据的过程。

 四、 verilog描述

接下来使用verilog来描述图6所示的电路,控制信号可根据图7进行描述。

spi发送模块(该模块主要描述控制信号):

module SPI_SEND(input clk_50m,
					 input rst_n,
					 input spi_start,
					 input[7:0] spi_data,
					 output reg spi_done,
					 output sck,
					 output reg cs,
					 output mosi
    );
reg load_c;
reg en_c;
reg load_a;
reg en_a;
reg load_b;
reg en_b;
wire [4:0]i;
parameter [4:0] s0 = 'b000001;
parameter [4:0] s1 = 'b000010;
parameter [4:0] s2 = 'b000100;
parameter [4:0] s3 = 'b001000;
parameter [4:0] s4 = 'b010000;
parameter [4:0] s5 = 'b100000;
reg [5:0]current_state = 'd0;
reg [5:0]next_state = 'd0;
always @(posedge clk_50m or negedge rst_n)
	if(!rst_n)
		current_state <= s0;
	else
		current_state <= next_state;

always @(*)
	case(current_state)
		s0:	begin
			if(spi_start)
				next_state = s1;
			else
				next_state = s0;
		end
		s1:	begin/该状态加载待发送的数据
			if(i == 'd1)
				next_state = s2;
			else
				next_state = s1;
		end
		s2:	begin1,3,5,7,9,11,13,15
			if(i[0] == 1'b0)//
				next_state = s3;
			else
				next_state = s2;
		end
		s3:	begin2,4,6,8,10,12,14,16
			if(i == 'd15)
				next_state = s4;
			else if(i[0] == 'd1)
				next_state = s2;
			else
				next_state = s3;
		end
		s4:	begin
			if(i == 'd16)
				next_state = s5;
			else
				next_state = s4;
		end
		s5:	begin
			if(i == 'd0)
				next_state = s0;
			else
				next_state = s5;
		end
		default:	next_state = s0;
	endcase
always @(*)
	case(current_state)
		s0:	begin///空闲状态
			load_c = 'd1;
			en_c = 'd0;
			load_a = 'd0;
			en_a = 'd0;
			load_b = 'd1;
			en_b = 'd0;
			spi_done = 'd0;
			cs = 'd1;
		end
		s1:	begin加载待发送数据状态
			load_c = 'd0;
			en_c = 'd1;
			load_a = 'd1;
			en_a = 'd0;
			load_b = 'd0;
			en_b = 'd1;
			spi_done = 'd0;
			cs = 'd0;			
		end
		s2:	begin	第一个时钟沿发送数据
			load_c = 'd0;
			en_c = 'd1;
			load_a = 'd0;
			en_a = 'd1;
			load_b = 'd0;
			en_b = 'd1;
			spi_done = 'd0;
			cs = 'd0;	
		end
		s3:	begin第二个时钟沿采样数据
			load_c = 'd0;
			en_c = 'd1;
			load_a = 'd0;
			en_a = 'd0;
			load_b = 'd0;
			en_b = 'd1;
			spi_done = 'd0;
			cs = 'd0;
		end
		s4:	begin数据发送完毕
			load_c = 'd0;
			en_c = 'd1;
			load_a = 'd0;
			en_a = 'd0;
			load_b = 'd0;
			en_b = 'd0;
			spi_done = 'd0;
			cs = 'd0;			
		end
		s5:	begin
			load_c = 'd0;
			en_c = 'd0;
			load_a = 'd0;
			en_a = 'd0;
			load_b = 'd0;
			en_b = 'd0;
			spi_done = 'd1;
			cs = 'd1;	
		end
		default:	begin
			load_c = 'd1;
			en_c = 'd0;
			load_a = 'd0;
			en_a = 'd0;
			load_b = 'd1;
			en_b = 'd0;
			spi_done = 'd0;
			cs = 'd1;			
		end
	endcase
// Instantiate the module
count_num count_num (
    .clk_50m(clk_50m), 
    .load_c(load_c), 
    .en_c(en_c), 
    .count(i)
    );
// Instantiate the module
left_shifter left_shifter (
    .clk_50m(clk_50m), 
    .load_a(load_a), 
    .en_a(en_a), 
    .spi_data_in(spi_data), 
    .mosi(mosi)
    );
// Instantiate the module
sck_generate sck_generate (
    .clk_50m(clk_50m), 
    .load_b(load_b), 
    .en_b(en_b), 
    .sck(sck)
    );
endmodule

 计数器电路描述:

module count_num(input clk_50m,
					  input load_c,
					  input en_c,
					  output reg[4:0]count
    );
always @(posedge clk_50m)	 
	if(load_c)
		count <= 'd0; 
	else if(en_c)	begin
		if(count == 'd16)
			count <= 'd0;
		else
			count <= count + 'd1;
	end
	else
		count <= count;

endmodule

 移位寄存器电路描述:

module left_shifter(input clk_50m,
						  input load_a,
						  input en_a,
						  input [7:0]spi_data_in,
						  output mosi
    );
reg [7:0]data_reg;
always @(posedge clk_50m)
	if(load_a)
		data_reg <= spi_data_in;
	else if(en_a)
		data_reg <= {data_reg[6:0],1'b0};
	else
		data_reg <= data_reg;
assign mosi = data_reg[7];
endmodule

 触发器电路描述:

//SPI3模式下工作,SCK空闲时为高电平
//
module sck_generate(input clk_50m,
						  input load_b,
						  input en_b,
						  output reg sck
    );
always @(posedge clk_50m)
	if(load_b)
		sck <= 'd1;
	else if(en_b)
		sck <= ~sck;
	else
		sck <= 'd1;

endmodule

 仿真激励文件:

module test;

	// Inputs
	reg clk_50m;
	reg rst_n;
	reg spi_start;
	reg [7:0]spi_data;
	// Outputs
	wire spi_done;
	wire sck;
	wire cs;
	wire mosi;

	// Instantiate the Unit Under Test (UUT)
	SPI_SEND uut (
		.clk_50m(clk_50m), 
		.rst_n(rst_n), 
		.spi_start(spi_start), 
		.spi_done(spi_done), 
		.sck(sck), 
		.cs(cs), 
		.spi_data(spi_data),
		.mosi(mosi)
	);

	initial begin
		// Initialize Inputs
		clk_50m = 0;
		rst_n = 0;
		spi_start = 0;
		spi_data = 'd0;
		// Wait 100 ns for global reset to finish
		#100;
        
		// Add stimulus here

	end
    always #5 clk_50m = ~clk_50m;
reg [4:0] count = 'd0;
always @(posedge clk_50m)	
	if(count == 'd20)
		count <= 'd20;
	else
		count <= count + 'd1;
always @(posedge clk_50m)
	if(count <= 'd10)
		rst_n <= 'd0;
	else
		rst_n <= 'd1;
reg [9:0]cnt = 'd0; 
always @(posedge clk_50m)
	if(spi_done)	
		cnt <= 'd0;
	else if(cnt == 'd500)
		cnt <= 'd500;
	else
		cnt <= cnt + 'd1;
always @(posedge clk_50m)
 if(cnt=='d499)	begin
		spi_start <= 'd1;
		spi_data <= 'b10101010;
	end
	else	begin
		spi_start <= 'd0;
		spi_data <= spi_data;
	end
		
endmodule

 使用ISIM仿真结果:

基于fpga的spi通信设计_协议的概念
图8 仿真结果

图8中待发送的数据spi_data[7:0]=10101010,由于使用的是SPI3模式(CPOL=1,CPHA=1),此模式下SCK空闲时为1,在SCK第一个时钟沿进行数据发送(即图中SCK下降沿进行数据发送),从图中波形可以看出 ,在cs为低时,mosi被一位一位的送出(高位先输出)。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/181785.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Windows下nmap命令及Zenmap工具的使用方法「建议收藏」

    Windows下nmap命令及Zenmap工具的使用方法「建议收藏」#Nmap简介nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统(这是亦称fingerprinting)。它是网络管理员必用的软件之一,以及用以评估网络系统安全。正如大多数被用于网络安全的工具,nmap也是不少黑客爱用的工具。系统管理员可以利用nmap来探测工作环境中未经批准使用的服务器,但是黑客会利用nmap来搜集目标电脑的网络设定,从而计划攻击的方法。Nmap常被跟评估系统漏洞软件Nessus混为一谈。Nmap

    2022年5月26日
    203
  • Django(74)drf-spectacular自动生成接口文档「建议收藏」

    Django(74)drf-spectacular自动生成接口文档「建议收藏」介绍drf-spectacular是为DjangoRESTFramework生成合理灵活的OpenAPI3.0模式。它可以自动帮我们提取接口中的信息,从而形成接口文档,而且内容十分详细,再也不

    2022年8月7日
    5
  • 解决win10状态栏的搜索框无法搜索本地应用或无反应

    解决win10状态栏的搜索框无法搜索本地应用或无反应今天突然出现的问题,在状态栏左下角的搜索框搜索OneNote没有任何反应,对,就是这个地方。最后在另一篇博客上找到了答案,那篇博客也是在知乎找到的答案,虽然是用被人的方法解决了问题,但我还是打算记下来;1、首先,打开管理员命令窗口,win+x,可以看到弹出一个窗口,打开windowsPowershell(管理员)如图2,输入下面这行英文startpowershell然…

    2022年6月4日
    37
  • HTML5 语义元素

    返回目录 http://hovertree.com/h/bjaf/html5zixueji.htm一个语义元素能够清楚的描述其意义给浏览器和开发者。无语义元素实例:<div&gt

    2021年12月23日
    38
  • mysql导入excel文件_将Excel数据导入MySQL「建议收藏」

    mysql导入excel文件_将Excel数据导入MySQL「建议收藏」去年的投资统计月报数据量庞大,原始表格是xls格式(还是EXECL2003的),单个sheet最大只能放几万行,但数据总量有10万行以上,于是只能存成两个sheet。EXECL2010格式倒是单个sheet可以放得下,可是居然不能将数据完整的从一个sheet复制粘贴到另一个sheet(可能是因为行数太多)。正好想学习一下execl数据导入MySQL数据库的方法,于是开始尝试。一开始使用的是MySQ…

    2025年11月19日
    9
  • 正则表达式不包含某些字符_js匹配正则表达式的方法

    正则表达式不包含某些字符_js匹配正则表达式的方法问题:去除字符串中的标签,但不包括

    Nooneshouldbealoneintheiroldage.

    ‘.replace(/<((?!br).)*?>/g,”)//结果”Nooneshouldbealoneintheiroldage.

    2022年9月19日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号