SPI学习笔记:DAC与ACD

news2024/9/22 15:49:00

一、SPI协议简介

SPI = Serial Peripheral Interface,是串行外围设备接口,是一种高速,全双工,同步的通信总线。常规只占用四根线,节约了芯片管脚,PCB的布局省空间。现在越来越多的芯片集成了这种通信协议,常见的有EEPROM、FLASH、AD转换器等。

二、遵循SPI协议控制DAC:tlv5618

   1.时序分析

    从tlv5618芯片的数据手册中可找到其时序特性:

没有Miso线时序,所以只需要设计三根线即可,因为此dac不需要主机发数据。由tsu(D)与th(D)均基于sclk的时钟下降沿描述,可以确定adc在时钟下降沿读取din数据,所以我们在sclk时钟上升沿改变数据即可。向由tw(不小于50ns)确定sclk时钟频率为20MHz;根据DIN时序可知在SCLK上升沿DIN改变,在SCLK下降沿读取DIN;tsu(D)建立时间,不小于10ns;th(D)保持时间,不小于10ns;故直接用20MHz时钟设计DIN长度即可满足;tsu用于限制CS的起止点,tsu(csh)应不小于50ns,tsu(cs-cl)应不小于10ns,tsu(c16-cs)应不小于10ns,在仿真时检验并调整使CS满足tsu即可。

2.模块框图

        根据时序图可知,用线性序列机即可(LSM)实现。定义的信号如下:

3.代码实现

//采用线性序列机(LSM)来实现
module dac_driver(
	input fpga_clk ,
	input rst_n ,
	input dac_pulse ,
	input [15:0]dac_data ,
	
	output reg cs_n ,//low level valid
	output reg sclk ,
	output reg mosi ,
	output reg dac_sig ,
	output reg dac_done 
);


//供电电压不同,参数最小值不同,这里按照兼容VDD = 5V 和 VDD = 3V 的参数设计 
// 			tsu(cs-ck) : 20ns														// 
//				tsu(c16-cs): 20ns															//
//				th(D): 10ns																	//	
//				th(csh):50ns																//
//           sclk : 20Mhz    															//
//
// D15 - D12 : 设置位  根据手册来设置                                     //
// D11 - D0  : 数据位	  需要转化的电压值												//



wire clk_40m ;
pll pll_inst(
	.inclk0(fpga_clk),
	.c0(clk_40m)
	);

//detect dac_pulse
reg dac_pulse_reg ;
always@(posedge fpga_clk or negedge rst_n)
if(!rst_n)
	dac_pulse_reg <= 0 ;
else 
	dac_pulse_reg <= dac_pulse ;

//dac_sig 
always@(posedge fpga_clk or negedge rst_n)
if(!rst_n)	
	dac_sig <= 0 ;
else if( (dac_pulse_reg == 0) & ( dac_pulse == 1) )
	dac_sig <= 1 ;
else if( dac_done )
	dac_sig <= 0 ;

// dac_cnt : 0 - 15
reg [5:0]dac_cnt ;
always@(posedge clk_40m or negedge rst_n)
if(!rst_n)	
	begin
		dac_cnt <= 0 ;
	end
else if(dac_sig)
	begin
		dac_cnt <= dac_cnt + 1 ;	
		if(dac_cnt == 34 )
			dac_cnt <= 0 ;
	end
else
	dac_cnt <= 0 ;
	
//dac_done:
always@(posedge clk_40m or negedge rst_n)
if(!rst_n)	
	dac_done <= 0 ;
else if( dac_cnt == 34 )
	dac_done <= 1 ;
else if( dac_done )
	dac_done <= 0 ;
	
// cs_n
always@(posedge clk_40m or negedge rst_n)
if(!rst_n)	
	cs_n <= 1 ;
else if(dac_sig)
	begin
		if(dac_cnt < 32 )
			cs_n <= 0 ;
		else
			cs_n <= 1 ;
	end
	
// sclk
always@(posedge clk_40m or negedge rst_n)
if(!rst_n)	
	sclk <= 0 ;
else if(dac_sig)
	begin
		sclk <= !sclk ; 
	end
else 
	sclk <= 0 ;

//mosi
always@(posedge clk_40m or negedge rst_n)
if(!rst_n)	
	mosi <= 0 ;
else if(dac_sig)
	begin
		case(dac_cnt)
			0 :  mosi <= dac_data[15] ;
			2 :  mosi <= dac_data[14] ;
			4 :  mosi <= dac_data[13] ;
			6 :  mosi <= dac_data[12] ;
			8 :  mosi <= dac_data[11] ;
			10 : mosi <= dac_data[10] ;
			12 : mosi <= dac_data[9] ;
			14 : mosi <= dac_data[8] ;
			16 : mosi <= dac_data[7] ;
			18 : mosi <= dac_data[6] ;
			20 : mosi <= dac_data[5] ;
			22 : mosi <= dac_data[4] ;
			24 : mosi <= dac_data[3] ;
			26 : mosi <= dac_data[2] ;
			28 : mosi <= dac_data[1] ;	
			30 : mosi <= dac_data[0] ;	
			32 : mosi <= 0 ;	
			default:;
		endcase
	end
else 
	mosi <= 0 ;
	
endmodule
`timescale 1ns/1ns
module dac_driver_tb();

reg fpga_clk ;
reg rst_n  ;
reg dac_pulse ;
reg [15:0]dac_data ;
wire cs_n ;
wire sclk ;
wire mosi ;
wire dac_done ;

dac_driver dac_driver_inst(
	.fpga_clk(fpga_clk) ,
	.rst_n(rst_n) ,
	.dac_pulse(dac_pulse) ,
	.dac_data(dac_data) ,
	
	.cs_n(cs_n) ,//low level valid
	.sclk(sclk) ,
	.mosi(mosi) ,
	.dac_done(dac_done)
);

initial fpga_clk = 0 ;
always #10 fpga_clk = ! fpga_clk ;

initial
begin
	rst_n = 0 ;
	dac_pulse = 0 ;
	dac_data = 0 ;
	#100 ;
	rst_n = 1 ;
	#100 ;
	dac_data = 16'h5a5a ;
	#100 ;
	dac_pulse = 1 ;
	#40 ;
	dac_pulse = 0 ;
	#100;
	wait(dac_done);
	#3000 ;
	dac_data = 16'ha5a5 ;
	#100 ;
	dac_pulse = 1 ;
	#40 ;
	dac_pulse = 0 ;
	#100;
	wait(dac_done);
	#3000 ;
	$stop;
	
end

endmodule

4.仿真验证

由图可知,tsu为25ns,大于tsu(c16-cs)(10ns),th(csh)为50ns,不小于50ns,满足要求。

三、遵循SPI协议控制ADC:ADC128S022

1.时序分析

        ADC128S022的spi接口时序图如下:

根据时序图及对应参数数值作以下分析:

1.fSCLK = 3.2 MHz to 8 MHz ,这里我们选取fsclk = 4Mhz进行设计,可以满足要求。

2.mosi:通过sdstdh来判断,它是基于sclk的上升沿描述的,所以从机大概率是在时钟上升沿读取数据,因而可以选择在sclk的下降沿改变数据,从而到上升沿时数据已经保持稳定。

3.miso:根据tdacctdhld以及时序图判断,它基于下降沿描述的,即从机在下降沿改变数据,故主机在上升沿进行读取。(读和写的变化沿大部分都是一致的)

根据时序图分析画出如下波形:

2.模块框图

        本模块通过一个adc_en脉冲开启一次adc转换读取;adc_channel用于选择八个ad通道中的一个;每次转换完成发出一个adc_done脉冲,并输出转换完成的数据adc_data;

3.代码实现


//****************************//
//fsclk要求:3.2MHz - 8MHz 取:4MHz   //
//    

module adc_driver(

	input fpga_clk ,
	input rst_n ,
	input [2:0]adc_channel ,
	input adc_en ,
	
	//SPI
	input  miso ,
	output reg mosi ,
	output reg cs_n ,
	output reg sclk ,
	
	output reg adc_done,
	output reg adc_state ,
	output reg [11:0]adc_data
);


wire clk_8m ;
pll pll_inst(
	.inclk0(fpga_clk),
	.c0(clk_8m)
	);
	
reg adc_en_reg ;
always@(posedge fpga_clk or negedge rst_n)
if(!rst_n)
	adc_en_reg <= 0 ;
else 
	adc_en_reg <= adc_en ;

always@(posedge fpga_clk or negedge rst_n)
if(!rst_n)
	adc_state <= 0 ;
else if(adc_en == 1 & adc_en_reg == 0)
	adc_state <= 1 ;
else if(adc_done)
	adc_state <= 0 ;
	
reg [6:0]adc_cnt ;
always@(posedge clk_8m or negedge rst_n)
if(!rst_n)
	adc_cnt <= 0 ;
else if ( adc_state == 1 )
	adc_cnt <= adc_cnt + 1 ;
else if(adc_done)
	adc_cnt <= 0 ;

//cs_n
always@(posedge fpga_clk or negedge rst_n)
if(!rst_n)	
	cs_n <= 1 ;
else if( adc_en )
	cs_n <= 0 ;
else if( adc_done )
	cs_n <= 1 ;
	
//sclk
always@(posedge clk_8m or negedge rst_n)
if(!rst_n)	
	sclk <= 1 ;
else if(adc_state)
	sclk <= ~sclk ;
else 
	sclk <= 1 ;

//mosi miso
reg [11:0]adc_data_reg ;	
always@(posedge clk_8m or negedge rst_n)
if(!rst_n)
	begin
		mosi <= 0 ;
		adc_done <= 0 ;
		adc_data <= 0 ;
		adc_data_reg <= 0 ;
	end
else if (adc_state)
	begin
		case(adc_cnt)
			4  :  mosi <= adc_channel[2] ;
			6  :  mosi <= adc_channel[1] ;
			8  :  begin mosi <= adc_channel[0] ;  end
			9  : adc_data_reg[11] <= miso ;
			11 : adc_data_reg[10] <= miso ;
			13 : adc_data_reg[9] <= miso ;
			15 : adc_data_reg[8] <= miso ;
			17 : adc_data_reg[7] <= miso ;
			19 : adc_data_reg[6] <= miso ;
			21 : adc_data_reg[5] <= miso ;
			23 : adc_data_reg[4] <= miso ;
			25 : adc_data_reg[3] <= miso ;
			27 : adc_data_reg[2] <= miso ;
			29 : adc_data_reg[1] <= miso ;
			31 : adc_data_reg[0] <= miso ;
			32 :  begin adc_done <= 1 ; adc_data <= adc_data_reg ; end
			
			default : ;
		endcase
	end
else
	begin
		adc_done <= 0 ;
		adc_data_reg <= 0 ;
		mosi <= 0 ;
	end
	
endmodule
`timescale 1ns/1ns

/*注意,由于使用联合仿真的时候,modelsim的默认目录是当前Quartus工
程下的simulation目录下的modelsim文件夹,所以,需要在执行仿真前手
动将sin_12bit.txt文件拷贝到simulation/modelsim下。修改了
sin_12bit.txt内容后也请记得重新覆盖modelsim下的sin_12bit.txt文件
*/
`define sin_data_file "./sin_12bit.txt"

module adc128s022_tb;

	reg Clk;
	reg Rst_n;
	reg [2:0]Channel;
	wire [11:0]Data;
	
	reg En_Conv;
	wire Conv_Done;
	wire ADC_State;
	wire [7:0]DIV_PARAM;
	
	wire ADC_SCLK;
	wire ADC_CS_N;
	reg  ADC_DOUT;
	wire ADC_DIN;
	
	assign DIV_PARAM = 13;
	
	reg[11:0]  memory[4095:0];//测试波形数据存储空间
	
	reg[11:0] address;//存储器地址 
	
adc_driver adc_driver_inst(

	.fpga_clk(Clk) ,
	.rst_n(Rst_n) ,
	.adc_channel(Channel) ,
	.adc_en(En_Conv) ,
	
	//SPI
	.miso(ADC_DOUT) ,
	.mosi(ADC_DIN) ,
	.cs_n(ADC_CS_N) ,
	.sclk(ADC_SCLK) ,

	.adc_done(Conv_Done),
	.adc_state(ADC_State) ,
   .adc_data(Data)
);

	

	initial Clk = 1'b1;
	always #10 Clk = ~Clk;
	
	//将原始波形数据从文件读取到定义的存储器中
	initial $readmemh(`sin_data_file,memory);//读取原始波形数据读到memory中

	integer i;
	
	initial begin
		Rst_n = 0;
		Channel = 0;
		En_Conv = 0;
		ADC_DOUT = 0;
		address = 0;
		#101;
		Rst_n = 1;
		#100;
		Channel = 5;
		for(i=0;i<3;i=i+1)begin
			for(address=0;address<4095;address=address+1)begin
				En_Conv = 1;
				#20;
				En_Conv = 0;
				gene_DOUT(memory[address]);	//依次将存储器中存储的波形读出,按照ADC的转换结果输出方式送到DOUT信号线上
				@(posedge Conv_Done);	//等待转换完成信号
				#200;
			end
		end
		#20000;
		$stop;
	end	
	
	//将并行数据按照ADC的数据输出格式,送到DOUT信号线上,供控制模块采集读取
	task gene_DOUT;
		input [15:0]vdata;
		reg [4:0]cnt;
		begin
			cnt = 0;
			wait(!ADC_CS_N);
			while(cnt<16)begin
				@(negedge ADC_SCLK) ADC_DOUT = vdata[15-cnt];
				cnt = cnt + 1'b1;
			end
		end
	endtask
	
endmodule

4.仿真验证

仿真结果满足时序要求,可行。

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

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

相关文章

python【多线程、单线程、异步编程】三个版本--在爬虫中的应用

并发编程在爬虫中的应用 之前的课程&#xff0c;我们已经为大家介绍了 Python 中的多线程、多进程和异步编程&#xff0c;通过这三种手段&#xff0c;我们可以实现并发或并行编程&#xff0c;这一方面可以加速代码的执行&#xff0c;另一方面也可以带来更好的用户体验。爬虫程…

解读Stata输出的OLS回归结果

Stata是一个广泛用于统计分析和数据管理的软件,以下是一些Stata的基础命令: 输入数据:use + 数据文件路径 显示数据:browse 或 list 或 describe 选择数据:keep 或 drop 或 select 或 exclude 建立变量和赋值:generate 或 replace 汇总数据:summarize 或 tabulate 画图:…

自学SLAM(4)《第二讲:三维物体刚体运动》作业

前言 小编研究生的研究方向是视觉SLAM&#xff0c;目前在自学&#xff0c;本篇文章为初学高翔老师课的第二次作业。 文章目录 前言1.熟悉 Eigen 矩阵运算2.几何运算练习3.旋转的表达4.罗德里格斯公式的证明5.四元数运算性质的验证6.熟悉 C11 1.熟悉 Eigen 矩阵运算 设线性⽅程 …

cordens

1 coredns的用途 CoreDNS 是一个灵活可扩展的 DNS 服务器&#xff0c;可以作为 Kubernetes 集群 DNS&#xff0c;在Kubernetes1.12版本之后成为了默认的DNS服务。 与 Kubernetes 一样&#xff0c;CoreDNS 项目由 CNCF 托管。 coredns在K8S中的用途,主要是用作服务发现&#x…

企业知识库管理软件介绍,打造企业最强大脑!

企业知识经验既是企业稳定可控的基础&#xff0c;也是企业继续长足发展的基石&#xff0c;如何实现组织内外部知识沉淀&#xff0c;让知识在组织内外传播与应用&#xff0c;就成为我们广大企业负责人应该思考的问题了。 企业知识库管理 随着与人工智能技术的融合&#xff0c;知…

arcgis js api 4.x通过TileLayer类加载arcgis server10.2发布的切片服务跨域问题的解决办法

1.错误复现 2.解决办法 2.1去https://github.com/Esri/resource-proxy 网站下载代理配置文件&#xff0c;我下载的是最新的1.1.2版本&#xff0c;这里根据后台服务器配置情况不同有三种配置文件&#xff0c;此次我用到的是DotNet和Java. 2.2 DotNet配置 2.2.1 对proxy文件增加…

第 368 场 LeetCode 周赛题解

A 元素和最小的山形三元组 I 前后缀操作&#xff1a;求出前后缀上的最小值数组&#xff0c;然后枚举 j j j class Solution { public:int minimumSum(vector<int> &nums) {int n nums.size();vector<int> l(n), r(n);//l[i]min{nums[0],...,nums[i]}, r[i]mi…

AirPods Pro的降噪功能让你体验更好,那么如何打开这个功能

本文介绍了如何在AirPods Pro上使用降噪功能&#xff0c;如何关闭它&#xff0c;以及该功能的工作原理。 AirPods Pro和AirPods Max支持降噪。你的设备必须运行iOS 13.2或iPadOS 13.2或更高版本才能使用降噪。 如何在AirPods Pro上打开降噪功能 AirPods Pro凭借其噪音控制功…

leetCode 30.串联所有单词的子串

给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff0c; 那么 "abcdef&…

如何利用示波器解析I2C数据

前言 &#xff08;1&#xff09;如果有嵌入式企业需要招聘校园大使&#xff0c;湖南区域的日常实习&#xff0c;任何区域的暑假Linux驱动实习岗位&#xff0c;可C站直接私聊&#xff0c;或者邮件&#xff1a;zhangyixu02gmail.com&#xff0c;此消息至2025年1月1日前均有效 &am…

【5G PHY】5G SS/PBCH块介绍(一)

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

短视频矩阵打造攻略:玩转短视频

短视频矩阵系统是一个智能的短视频创作、发布、管理和分析的平台。该方法能够帮助用户迅速搭建起自身的短视频矩阵&#xff0c;并在多个平台上进行传播与展示&#xff0c;进而提升短视频的营销效率。该短视频矩阵系统具有如下基本功能&#xff1a; 1.短视频制作&#xff1a;为用…

队列的实现方式—Python数据结构(三)

队列 1. 定义 队列是一种常见的数据结构&#xff0c;用于按照先进先出&#xff08;FIFO&#xff09;的原则管理数据项。在Python中&#xff0c;有多种方法可以实现队列&#xff0c;其中最常见的包括使用列表&#xff08;list&#xff09;和使用标准库中的 queue 模块。队列通…

docker运行nginx镜像

今天在这里讲如何在docker上运行nignx镜像&#xff0c;并将配置文件和目录挂载到宿主机上&#xff0c;以实现方便统一的管理配置信息。 首先第一步需要拉取镜像&#xff0c;我们还是拉取最新的镜像&#xff0c;不需要添加tag版本号&#xff0c; docker pull nginx 拉取结束后用…

反射的作用( 越过泛型检查 和 可以使用反射保存所有对象的具体信息 )

1、绕过 编译阶段 为集合添加数据 反射是作用在运行时的技术&#xff0c;此时集合的泛型将不能产生约束了&#xff0c;此时是可以 为集合存入其他任意类型的元素的 。泛型只是在编译阶段可以约束集合只能操作某种数据类型&#xff0c;在 编译成Class文件进入 运行阶段 的时候&a…

Zebec Protocol 薪酬支付工具 WageLink 上线,掀新一轮薪酬支付浪潮

Zebec Protocol 正在从多个方面推动流支付的应用&#xff0c;除了作为一种全新的支付手段来对支付领域进行重塑外&#xff0c;其也在以流支付体系为基础&#xff0c;不断地向薪酬发放领域深度的拓展。 在今年早些时候&#xff0c;Zebec Protocol 通过美国投资机构 Payroll Grow…

【类和对象+this引用】

文章目录 面向对象与面向过程面向对象关注的是对象&#xff0c;用类描述这个对象如何定义类如何更改类名 类的实例化this引用总结 面向对象与面向过程 面向对象就是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。 面向过程好比传统的洗衣服方式&#x…

【RNA world】RNA的多功能性与早期生命进化

文章目录 RNARNA plays core functions in Central Dogma of BiologyrRNAsnRNA RNA worldReplication催化作用感知环境变化并作出响应 来自Manolis Kellis教授&#xff08;MIT计算生物学主任&#xff09;的课 油管链接&#xff1a;6.047/6.878 Lecture 7 - RNA folding, RNA wo…

[尚硅谷React笔记]——第5章 React 路由

目录&#xff1a; 对SPA应用的理解对路由的理解前端路由原理路由的基本使用路由组件与一般组件NavLink的使用封装NavLink组件Switch的使用解决样式丢失问题路由的模糊匹配与严格匹配Redirect的使用嵌套路由向路由组件传递params参数向路由组件传递search参数.向路由组件传递st…

【Docker从入门到入土 3】Docker镜像的创建方法

Part3 一、Docker镜像1.1 镜像的概念1.2 镜像结构的分层 二、Docker镜像的创建2.1 基于现有镜像创建2.1.1 创建思路2.1.2 举个例子 2.2 基于本地模板创建2.3 基于Dockerfile 创建 三、Dockerfile 详解3.1 Dockerfile 操作指令3.1.1 常用的操作指令3.1.2 CMD和ENTRYPOINT的区别…