SPI简介及FPGA通用MOSI模块实现

news2024/12/23 8:24:00

简介

SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术。是一种高速、全双工、同步通信总线。在芯片中只占用四根管脚用来控制及数据传输。

优缺点:

SPI通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;

缺点是没有指定的流控制,没有应答机制确认数据是否接收,与IIC总线通讯协议相比,在数据可靠性上有一定缺陷。

物理层

对于SPI协议的物理层,需要讲解的就是SPI通讯设备的连接方式和设备引脚的功能描述。

SPI通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分,根据从机设备的个数,SPI通讯设备之间的连接方式可分为一主一从和一主多从,具体见下图1、2。

SPIFla002

图 1 一主一从SPI通讯设备连接图

SPIFla003

图 2 一主多从SPI通讯设备连接图

SPI通讯协议包含1条时钟信号线、2条数据总线和1条片选信号线, 时钟信号线为SCK,2条数据总线分别为MOSI(主输出从输入)、MISO(主输入从输出),片选信号线为,它们的作用介绍如下:

  1. SCK (Serial Clock):时钟信号线,用于同步通讯数据。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不同,两个设备之间通讯时,通讯速率受限于低速设备。

  2. MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。

  3. MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。

  4. (Chip Select):片选信号线,也称为CS_N,以下用CS_N表示。当有多个SPI从设备与SPI主机相连时,设备的其它信号线SCK、MOSI及MISO同时并联到相同的SPI总线上,即无论有多少个从设备,都共同使用这3条总线;而每个从设备都有独立的这一条CS_N信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。

I2C协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI协议中没有设备地址,它使用CS_N信号线来寻址,当主机要选择从设备时,把该从设备的CS_N信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI通讯。所以SPI通讯以CS_N线置低电平为开始信号,以CS_N线被拉高作为结束信号。

四种通讯模式

SPI通讯协议一共有四种通讯模式,模式0、模式1、模式2以及模式3,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义。

CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中)时SCK时钟信号的电平状态;

CPHA规定了数据采样是在SCK时钟的奇数边沿还是偶数边沿。

极性和相位

SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (时钟)相位
(3) SCK=SCLK=SPI的时钟
(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)

模式判别

CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:
CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low;

CPHA=0,表示第一个边沿
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;

CPHA=1,表示第二个边沿
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

SPI通讯协议的4种模式如下,通讯模式时序图,具体见下图 :

模式0:CPOL= 0,CPHA=0。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的上升沿;数据更新在SCK时钟的下降沿。

模式1:CPOL= 0,CPHA=1。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的下降沿;数据更新在SCK时钟的上升沿。

模式2:CPOL= 1,CPHA=0。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的下降沿;数据更新在SCK时钟的上升沿。

模式3:CPOL= 1,CPHA=1。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的上升沿;数据更新在SCK时钟的下降沿。

在这里插入图片描述

模式的判断:

如果起始的SCLK的电平是0,那么CPOL=0,如果是1,那么CPOL=1,

然后看数据采样时刻,对应到上面SCLK时钟的位置,对应着是第一个边沿或是第二个边沿,即CPHA是0或1

时序需求

tSLCH:cs_n拉低到sck高的时间

tCHSH:sck高到cs_n拉高的时间

SCK上升沿MOSI的建立时间保持时间需求

在这里插入图片描述

SPI通用模块

实现功能

用于将任意宽度向量型数据转换为SPI串行输出,模式0:CPOL= 0,CPHA=0;
默认串行数据mosi数据的建立时间和保持时间均为2个clk(0.5*DIV_FREQUENCY)周期,即sck上升沿的前后2个(0.5*DIV_FREQUENCY)clk数据稳定,串行时钟sck周期为4*clk(DIV_FREQUENCY)的周期;
默认tSLCH(cs_n拉低到sck高的时间为6*clk(1.5*DIV_FREQUENCY)周期),tCHSH(sck高到cs_n拉高的时间为2*clk(0.5*DIV_FREQUENCY)周期);
若时序满足此模块可以不做修改。若需要修改在外部例化时修改DIV_FREQUENCY(只能偶分频)和PERIOD_WIDTH_MAX即可。

使用方法

输入data的位宽和计数器位宽在外部进行例化时修改参数的值即可,不必修改SPI模块
DATA_WIDTH_MAX修改为输入数据的位宽,如[31:0]的数据则DATA_WIDTH_MAX=32
CNT_DATA_WIDTH_MAX修改为输入数据的位宽计数器需要的位宽上限,即DATA_WIDTH_MAX=32对应的二进制位宽32=6’b10_0000,所以CNT_DATA_WIDTH_MAX=6

输入输出端口说明:

//input
input 		wire 						clk 			, //系统时钟,spi串行时钟的分频基准 			
input 		wire						clr_n 			, //spi信号标志信号,允许发送时一直拉高,重新发送时拉低复位再拉高
input 	  	wire  [DATA_WIDTH_MAX-1:0] 	data 			, //需要data与clr_n一同进入			
	                                                    
//output	                                            
output		reg							cs_n 			, //片选信号
output 	  	reg			 				sck 			, //串行时钟
output		reg							mosi 			, //主输出从输入数据
output		reg							flag 			  //spi发送完成标志位,完成则一直拉高;clr_n置0时拉低

例化模板:

spi #(
.DATA_WIDTH_MAX 	(8	 	),
.CNT_DATA_WIDTH_MAX	(4		) 
)
u_spi(
	.clk 	(clk			),
	.clr_n  (clr_n			),
	.data   (data_in		),
		
	.cs_n   (cs_n			),
	.sck    (sck 			),
	.mosi   (mosi			),
	.flag   (flag			)
); 

SPI模块

//========================================================================
// 	module_name.v	:spi.v
// 	Author			:YprgDay
// 	Description		:用于将任意宽度向量型数据转换为SPI串行输出,模式0:CPOL= 0,CPHA=0。
//========================================================================
module spi
 #(
	//=========================< Parameter >==============================
	parameter 				DATA_WIDTH_MAX		=	32				,//例化时修改为输入数据的位宽,如[31:0]的数据则DATA_WIDTH_MAX	=32
	parameter 				CNT_DATA_WIDTH_MAX	=	6				,//例化时修改为输入数据的位宽计数器需要的位宽上限,即DATA_WIDTH_MAX=32对应的二进制位宽32=6'b10_0000,所以CNT_DATA_WIDTH_MAX=6

	parameter 				DIV_FREQUENCY		=	4				,//分频数(只允许偶分频),串行时钟sck周期为DIV_FREQUENCY*clk的周期
	parameter 				PERIOD_WIDTH_MAX	=	2				,//(DIV_FREQUENCY-1)对应的二进制位宽即为PERIOD_WIDTH_MAX
	parameter 				CNT_PERIOD_MAX		=	DIV_FREQUENCY-1	,
	parameter 				CNT_HALF_PERIOD_MAX	=	CNT_PERIOD_MAX >> 1	//计数分频中值
) 
(
	//=========================< Port Name >==============================
	//input
	input 		wire 						clk 			, //系统时钟,spi串行时钟的分频基准 			
	input 		wire						clr_n 			, //spi信号标志信号,允许发送时一直拉高,重新发送时拉低复位再拉高		
	input 	  	wire  [DATA_WIDTH_MAX-1:0] 	data 			, //需要data与clr_n一同进入			
		                                                    
	//output	                                            
	output		reg							cs_n 			, //片选信号
	output 	  	reg			 				sck 			, //串行时钟
	output		reg							mosi 			, //主输出从输入数据
	output		reg							flag 			  //spi发送完成标志位,完成则一直拉高;clr_n置0时拉低
	
);

	//=========================< Always block >===========================
	reg [CNT_DATA_WIDTH_MAX-1:0] 	cnt_data_width			;
	reg [PERIOD_WIDTH_MAX-1:0] 		cnt_spi_period			;
	reg [DATA_WIDTH_MAX-1:0] 		data_reg	  			;
	
	//输出的cs_n片选信号
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			cs_n <= 1'b1;
		end
		else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
            cs_n <= 1'b1;
		end
		else begin
			cs_n <= 1'b0;
		end
	end	
	
	//输出的sck串行时钟信号,
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			sck <= 0;
		end
		else if(cnt_data_width > 0 && cnt_spi_period == CNT_PERIOD_MAX)begin
            sck <= 0;
		end
		else if(cnt_data_width > 0 && cnt_spi_period == CNT_HALF_PERIOD_MAX)begin
			sck <= 1;
		end
		else begin
			sck <= sck;
		end
	end
	
	//mosi的串行输出
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			mosi <= 0;
		end
		else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
            mosi <= 0;
		end
		else if(cnt_spi_period == CNT_PERIOD_MAX)begin
            mosi <= data_reg[DATA_WIDTH_MAX-1-cnt_data_width];
		end
		else begin
			mosi <= mosi;
		end
	end 
	
	
	//输出的串行数据发送完成标志信号,发送完成即拉高
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			flag <= 0;
		end
		else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
            flag <= 1;
		end
		else begin
			flag <= 0;
		end
	end
	
	//输入数据寄存,保证data在一串SPI数据发完之间不发生变化
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			data_reg <= 0;
		end
		else if(cnt_data_width == 0 && cnt_spi_period == 1)begin
            data_reg <= data;
		end
		else begin
			data_reg <= data_reg;
		end
	end
	
	//时钟四分频计数
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			cnt_spi_period <= 0;
		end
		else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
			cnt_spi_period <= cnt_spi_period;
		end
		else if(cnt_spi_period == CNT_PERIOD_MAX)begin
			cnt_spi_period <= 0;
		end
		else if(cs_n == 0)begin
			cnt_spi_period <= cnt_spi_period + 1'b1;
		end
		else;
	end	
	
	//计数表示此时输出到[DATA_WIDTH_MAX:0] data的第几位位置
	always @(posedge clk or negedge clr_n)begin
		if(clr_n == 1'b0)begin
			cnt_data_width <= 0;
		end
		else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
			cnt_data_width <= cnt_data_width;
		end
		else if(cnt_spi_period == CNT_PERIOD_MAX)begin
            cnt_data_width <= cnt_data_width + 1'b1;
		end
		else;
	end
	
endmodule

仿真模块

`timescale 1ns / 1ps
//
// Module Name: tb_spi
// Dependencies: spi模块仿真
//
module tb_spi();

	//=========================< Parameter >==============================
	parameter 				SPI_CLK_PERIOD		=	2			    ;//设置spi时钟信号周期
	parameter 				HALF_SPI_CLK_PERIOD	=	SPI_CLK_PERIOD/2;//生成spi时钟信号半周期

	//=========================< Port Name >==============================
	//input
	reg						clk								;
	reg    					clr_n							;
	reg 		[7:0]		data_in							;
	//output
	wire					mosi							;
	wire					cs_n							;
	wire					sck								;
	wire					flag							;
	
	//==========================< Clock block >============================
	always 	  	#HALF_SPI_CLK_PERIOD		clk = ~clk	;
	
	//==========================< Reset block >============================
	 initial begin
		clk 		= 	1'b1	;
		clr_n  		<= 	1'b0	;
		data_in     <=	0		;
		#HALF_SPI_CLK_PERIOD
		clr_n  		<= 	1'b1	;
		data_in     <=	8'h83	;
		#30
		data_in     <=	8'h54	;
		#300
		clr_n  		<= 	1'b0	;
		data_in     <=	8'hc7	;
		#10
		clr_n  		<= 	1'b1	;
	end
	
	//==========================< Module Instance >============================
	spi #(
	.DATA_WIDTH_MAX 	(8	 	),
	.CNT_DATA_WIDTH_MAX	(4		)				
	)
	u_spi(
		.clk 	(clk			),
		.clr_n  (clr_n			),
		.data   (data_in		),
			
		.cs_n   (cs_n			),
		.sck    (sck 			),
		.mosi   (mosi			),
		.flag   (flag			)
	); 

endmodule

仿真时序图

以8位输入数据[7:0]data的SPI时序图为例

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VM17虚拟机设置网络,本地使用工具连接虚拟机

VM17虚拟机设置网络&#xff0c;本地使用工具连接虚拟机 下载及安装虚拟机不再说明&#xff0c;网络一堆教程。此处只对VM17设置网路及本地使用工具连接虚拟机操作&#xff0c;进行说明。 我下载的是VM17&#xff0c;网上有说VM16是较稳定的版本。想尝尝鲜&#xff0c;结果耗…

linux系统源码安装php5.6手把手教程

linux系统源码安装php5.6实用教程 1、下载php5.6安装包2、开始安装3、安装成功 1、下载php5.6安装包 wget http://mirrors.sohu.com/php/php-5.6.2.tar.gz在安装之前&#xff0c;我们需要安装php5.6编译时所依赖的软件包。如下&#xff1a; yum -y install gcc gcc-c lib2、开…

docker下的nginx代理转发到tomcat

多次尝试失败原因&#xff0c;修改nginx配置文件以后&#xff0c;需要./nginx.sh -s reload 下&#xff0c;之前一直不转发&#xff0c;好像完全没有跳转的意思&#xff0c;后来查了多篇文档&#xff0c;最简单的方法如下 docker 安装 nginx 和tomcat就不多说了&#xff0c;可…

一文6个步骤带你实现接口测试入门!

一、接口测试概述 1 什么是接口测试&#xff1a; 接口测试是测试系统组件间交互的一种测试。接口测试主要用于检测外部系统与系统之间&#xff0c;内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑…

CLion配置libtorch找不到xxx.dll

项目场景&#xff1a; 使用CLion配置libtorch时遇到该问题 问题描述 使用CLion配置libtorch时&#xff0c;CMakeLists.txt文件写完后&#xff0c;cmake也能成功&#xff0c;但是一旦运行代码就会报错找不到xxx.dll&#xff0c;比如找不到torch_cuda.dll或找不到c10.dll 原因分…

jsp基本表格和简单算法表格

基本表格&#xff1b; <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd…

java项目之果蔬经营平台系统(ssm框架)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的果蔬经营平台系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 管理员&#xff1a;首页、个人…

Android 12 S 系统开机流程分析-FirstStageMain(一)

开机有好几种方式启动&#xff0c;本文主要讲的是按Power键开机流程。 本文参考AOSP 12原生代码&#xff0c;链接为&#xff1a;AOSP 12 Searchhttp://aospxref.com/android-12.0.0_r3/ 目录 1. BootLoader加载 2. kernel启动 3. init进程启动 3.1 FirstStageMain 3.1.1…

Windows安装Docker(无网)

Windows安装Docker&#xff08;无网&#xff09; window无网安装Docker 1. 开启虚拟化功能 1. 开启window的虚拟化功能 方式一&#xff1a;直接在window的搜索框搜索 “启用或关闭windows功能”&#xff0c;就可以快捷进入【启用或关闭windows功能】页面 方式二&#xff1…

Gui基础使用之项目部署

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.gui图形化界面的使用 1.1 前期准备 新建仓库&#xff0c;具体操作如下&#xff1a; 初始化readme文件&…

如何使用fiddler实现手机抓包,Filters过滤器!

一、Fiddler与其他抓包工具的区别 1、Firebug虽然可以抓包&#xff0c;但是对于分析http请求的详细信息&#xff0c;不够强大。模拟http请求的功能也不够&#xff0c;且firebug常常是需要“无刷新修改”&#xff0c;如果刷新了页面&#xff0c;所有的修改都不会保存&#xff1b…

ChatRule:基于知识图推理的大语言模型逻辑规则挖掘11.10

ChatRule&#xff1a;基于知识图推理的大语言模型逻辑规则挖掘 摘要引言相关工作初始化和问题定义方法实验 摘要 逻辑规则对于揭示关系之间的逻辑联系至关重要&#xff0c;这可以提高推理性能并在知识图谱&#xff08;KG&#xff09;上提供可解释的结果。虽然已经有许多努力&a…

Java类和对象详解

文章目录 面向对象概述类和对象类定义和使用定义使用 对象引用对象的初始化和构造构造方法默认初始化就地初始化 面向对象概述 面向对象是一种现在主流的程序设计方法&#xff0c;现如今的大部分语言都支持面向对象&#xff0c;Java的面向对象是由C的面向对象衍生而来&#xf…

AMD发布大小核 CPU,6核心直接砍成单核了

2022年 Intel 第12代酷睿发布&#xff0c;PE 大小核设计被正式带到了 PC 上。 P-Core 也就是传统的大核有着高性能、高功耗&#xff0c;而 E-Core 小核则是更讲究能效比以更低频率运行。 虽说小蝾也曾有对 Windows 调度方面的怀疑&#xff0c;但多线程性能确实实打实证明了其优…

node插件MongoDB(四)—— 库mongoose 操作文档使用(新增、删除、更新、查看文档)(二)

文章目录 前言&#xff08;1&#xff09;问题&#xff1a;安装的mongoose 库版本不应该过高导致的问题&#xff08;2&#xff09;重新安装低版本 一、插入文档1. 代码2. node终端效果3. 使用mongo.exe查询数据库的内容 二、删除文档1. 删除一条2. 批量删除3. 代码 三、修改文档…

react类式组件的生命周期和useEffect实现函数组件生命周期

概念 生命周期是一个组件丛创建,渲染,更新,卸载的过程,无论是vue还是react都具有这个设计概念,也是开发者必须熟练运用的,特别是业务开发,不同的生命周期做不同的事是很重要的. ....多说两句心得,本人是先接触vue的,无论是vue2还是vue3的生命周期,在理解和学习上都会比react更…

echarts饼图label显示不全原因?

echarts饼图label显示不全原因&#xff1f; 标签数量过多&#xff1a;当饼图的扇形数量较多时&#xff0c;为了保证图形的清晰性&#xff0c;ECharts 可能不会显示所有的标签&#xff0c;而是选择显示部分标签或者不显示标签。标签过长&#xff1a;如果标签的文字过长&#xf…

软件测试常用的测试方法详解

软件测试是软件开发过程中重要组成部分&#xff0c;是用来确认一个程序的质量或者性能是否符合开发之前提出的一些要求。软件测试的目的有两方面&#xff0c;一方面是确认软件的质量&#xff0c;另一方面是提供信息&#xff0c;例如&#xff0c;给开发人员或者程序经理反馈意见…

基于springboot实现家具商城管理系统项目【项目源码】计算机毕业设计

基于springboot实现家具商城管理系统演示 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的…

vue+nodejs商城实战项目【登录 + 购物车 + 支付】

从零开始一个前端项目并将其完成需要经历一系列步骤。以下是一个常见的开发流程&#xff0c;可以帮助规划和管理项目&#xff1a; 需求分析和规划&#xff1a; 确定项目的目标和范围。定义用户需求和功能要求。制定项目计划和时间表。 技术选型&#xff1a; 选择适当的前端技术…