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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iPhone 的健康数据采用的是 FHIR 传输格式

虽然感觉 FHIR 的数据传输格式还是有点繁琐的&#xff0c;但貌似现在也是唯一的事实上的标准。 通过 iPhone 健康上面查看的数据来看&#xff0c;有关健康的数据还是使用 FHIR 的数据传输格式。 不管怎么样&#xff0c;针对老旧的数据传输格式来看&#xff0c;FHIR 至少目前还是…

智慧公厕是什么?让公共厕所的“生命体征”有了“监测大脑”

智慧公厕是指将公共厕所进行信息化、数字化、智慧化的升级改造&#xff0c;针对公共厕所使用、运行、管理、养护等全方位业务流程进行优化。它不仅仅是传统公共厕所的升级版&#xff0c;更是公共厕所管理的一种全新方式。智慧公厕的独特之处在于&#xff0c;把公共厕所作为一个…

LabVIEW飞机液压基础试验台测试系统

LabVIEW飞机液压基础试验台测试系统 为解决飞机液压基础实验台人工控制操作复杂、测试时间长、测试流程易出错等问题&#xff0c;开发了一套基于LabVIEW的飞机液压基础试验台测试系统。该系统通过计算机控制&#xff0c;实现了高度自动化的测试流程&#xff0c;有效提高了测试…

深度学习-基于机器学习的语音情感识别系统的设计

概要 语音识别在现实中有着极为重要的应用&#xff0c;现在语音内容的识别技术已日趋成熟。当前语音情感识别是研究热点之一&#xff0c;它可以帮助AI和人更好地互动、可以帮助心理医生临床诊断、帮助随时随地高效测谎等。本文采用了中科院自动化所的CASIA语料库作为样本&#…

gitlab cicd问题整理

1、docker设置数据目录&#xff1a; 原数据目录磁盘空间不足&#xff0c;需要更换目录&#xff1a; /etc/docker/daemon.json //写入/etc/docker/daemon.json {"data-root": "/data/docker" } 2、Dockerfile中ADD指令不生效 因为要ADD的文件被.docker…

IP复习实验(gre)

拓扑图(r6相当于公网设备) 要求r1,r2,r3之间是hub-spoke架构 。r1,r4,r5是全连接&#xff0c;最后做OSPF跑通 第一步把所以接口配置了 第二步配置缺省0.0.0.0 0 n6.1.1.6 &#xff08;n就是具体的路由器r1 n就是1&#xff09; 测试一下先保证公网通畅 就先配置hub架构的 hu…

基于粒子群算法的分布式电源配电网重构优化matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1基本PSO算法原理 4.2配电网重构的目标函数 5.完整工程文件 1.课题概述 基于粒子群算法的分布式电源配电网重构优化。通过Matlab仿真&#xff0c;对比优化前后 1.节点的电压值 2.线路的损耗,这里计…

【人工智能】英文学习材料01(每日一句)

&#x1f33b;个人主页&#xff1a;相洋同学 &#x1f947;学习在于行动、总结和坚持&#xff0c;共勉&#xff01; 目录 1.Natural Language Processing&#xff0c;NLP&#xff08;自然语言处理&#xff09; 2.Machine Learing&#xff0c;ML&#xff08;机器学习&#xf…

【开源鸿蒙】模拟运行OpenHarmony轻量系统QEMU RISC-V版

文章目录 一、准备工作1.1 编译输出目录简介 二、QEMU安装2.1 安装依赖2.2 获取源码2.3 编译安装2.4 问题解决 三、用QEMU运行OpenHarmony轻量系统3.1 qemu-run脚本简介3.2 qemu-run脚本参数3.3 qemu-run运行效果3.4 退出QEMU交互模式 四、问题解决五、参考链接 开源鸿蒙坚果派…

【GPT-SOVITS-06】特征工程-HuBert原理

说明&#xff1a;该系列文章从本人知乎账号迁入&#xff0c;主要原因是知乎图片附件过于模糊。 知乎专栏地址&#xff1a; 语音生成专栏 系列文章地址&#xff1a; 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…

最小化战斗力差距——算法思路

题目链接&#xff1a;1.最小化战斗力差距 - 蓝桥云课 (lanqiao.cn) 可分析&#xff0c;把一个数组分成两组&#xff0c;求一组的最大值与另一组的最小值的差值的绝对值最小&#xff0c;可以转换为求任意两个相邻数字之间的最小插值的绝对值。 可看图示&#xff1a; package lan…

微信小程序Skyline模式自定义tab组件胶囊与原生胶囊平齐,安卓和ios均自适应

进入下面小程序可以体验效果&#xff1a; 至于原理的话&#xff0c;解释起来毕竟麻烦&#xff0c;各位可以看源码自己分析。其实很简单&#xff0c;就算计算布局。很多网上公布的布局&#xff0c;都不能正常自适应。在下这个是完美可以的 1、WXML <view class"weui…

时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解

时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解 目录 时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.CEEMDAN方法的分解效果取决于白噪声幅值权重(Nstd)和噪声添…

【tls招新web部分题解】

emowebshell (php7.4.21版本漏洞) 非预期 题目提示webshell&#xff0c;就直接尝试一下常见的后门命名的规则 如 shell.php这里运气比较好&#xff0c;可以直接shell.php就出来 要是不想这样尝试的话&#xff0c;也可以直接dirsearch进行目录爆破 然后在phpinfo中直接搜素c…

【Leetcode-73.矩阵置零】

题目&#xff1a; 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]示例 2&…

Redis学习笔记(基础篇)

Redis基础 1 Redis是什么&#xff1f;1.1 键值型1.2 NoSQL1.2.1 NoSQL与SQL的区别是什么1.2.2 总结 1.3 Redis的特点是什么&#xff1f; 2 Redis怎么用&#xff1f;2.1 Redis的基本命令2.2 Key的层级结构2.3 Redis的基本数据类型有哪些&#xff1f;2.1.1 String类型2.1.2 Hash类…

JavaWeb:vue、AJax、ELement、maven、SpringBoot、、Http、Tomcat、请求响应、分层解耦

1 Vue 1.1 Vue介绍 VUE是前端框架&#xff0c;基于MVVM&#xff0c;实现数据双向绑定 框架是半基础软件&#xff0c;可重用的代码模型 1.2 Vue指令 <script src"js/vue.js"></script></head> <body><div id"id"><!--…

【鸿蒙HarmonyOS开发笔记】常用组件介绍篇 —— 弹窗组件

简介 弹窗是移动应用中常见的一种用户界面元素&#xff0c;常用于显示一些重要的信息、提示用户进行操作或收集用户输入。ArkTS提供了多种内置的弹窗供开发者使用&#xff0c;除此之外还支持自定义弹窗&#xff0c;来满足各种不同的需求。 下面是所有涉及到的弹窗组件官方文档…

边缘计算+WEB端应用融合:AI行为识别智能监控系统搭建指南 -- 云端系统数据库设计(五)

专栏目录 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 – 整体介绍&#xff08;一&#xff09; 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 – 边缘设备图像识别及部署&#xff08;二&#xff09; 边缘计算WEB端应用融合&#xf…

研究生总结

Note:本博客更多是关于自己的感悟&#xff0c;没有翻阅文件详细查证&#xff0c;如果存在错过&#xff0c;也请提出指正。 1. 半监督回归 相比于半监督分类&#xff0c;半监督回归相对冷门。回归和分类之间有着难以逾越的天谴&#xff0c;预测精度。分类中的类别是可数的&…