FPGA-DA模块学习 I2C接口(附源码)

学习内容

D/A芯片DAC5571的使用,包括I2C的学习,并对代码进行详解。

实现功能

通过程序产生一个0-255循环递增的数据,通过I2C接口不断写入到DAC中,输出的模拟电压可以控制开发板上的某个LED的亮暗变化。结构如下
在这里插入图片描述

开发环境

xilinx spartan6开发板、ISE14.7、modelsim10.5、verilog

I2C相关知识

I2C 通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。I2C 通讯协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于 I2C 协议占用引脚特别少,两根线便可实现,硬件实现简单,可扩展型强,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

1、I2C物理层

在这里插入图片描述

图1 I2C通讯设备连接

物理层的特点
(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条**双向串行数据线(SDA)** ,一条**串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)每个连接到总线的设备都有一个
独立的地址**,主机可以利用这个地址进行不同设备之间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

2、I2C协议层

主要介绍I2C协议的整体时序图、读写时序以及I2C设备的器件地址、存储地址I2C整体时序图
在这里插入图片描述
由图可知,I2C 协议整体时序图分为 4 个部分,图中标注的①②③④表示 I2C 协议的 4个状态,分别为“总线空闲状态”、“起始信号”、“数据读/写状态”和“停止信号”。

  1. 图中的1表示“总线空闲状态”。再次状态下SCL和SDA均为高电平,此时无I2C设备工作。
  2. 图中的2表示“起始信号”。SCL依旧保持高电平,SDA出现下降沿,产生一个起始信号,此时与总线相连的所有I2C设备在检查到起始信号后,均跳出空闲状态,等待控制字节的输入。
  3. 图中的3表示“数据读写状态”,时序图如下在这里插入图片描述
    通讯模式是主从通讯,双方有主从之分。
    当主机向从机进行指令或者数据的写入时,串行数据线SDA上的数据在串行时钟SCL的高电平时写入从机设备,每次写入一位数据SDA中的数据在SCL为低电平时进行数据更新,保证数据的稳定。
    当一个完整字节的指令或数据传输完成,会通过拉低SDA为低电平来向主机设备发送单比特的应答信号,表示数据写入成功。若正确应答则可以结束或开始下一字节数据或指令的传输。
  4. 图中的4表示“停止信号”,完成数据读写后,串口时钟SCL保持高电平,当串口数据信号SDA产生一个上升沿时,产生一个停止信号,I2C总线跳回“总线空闲状态”。
3、I2C读写操作

对传入从机的控制命令最低位读写控制位写入不同数据值,主机可实现对从机的读/写操作,读写控制位为 0 时,表示主机要对从机进行数据写入操作;读写控制位为 1 时,表示主机要对从机进行数据读出操作。根据依次写入数据量的不同,I2C的写操作可以分为单字节写、页写、随机写操作。下面只介绍单字节写操作
I2C单字节写操作
在这里插入图片描述
单字节写操作流程如下

  1. 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低
    电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后。
  2. 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写
    入。按高位在前低位在后的顺序写入单字节存储地址。
  3. 地址写入完成,主机接收到从机回传的应答信号后,开始单字节数据的写入。
  4. 单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,单字节数据
    写入完成。

DA芯片相关知识

DAC5571的接口是I2C接口,关于I2C通信的基本接口时序在上边已经简单的总结。
FPGA为I2C总线的主机,若要控制D1C5571完成依次转换,则一共需要传输三个字节的数据。
首字节内容是从机地址(SLAVE ADDRESS)和读/写指示位(R/W)。
第二次字节的高4bit是控制数据,低4bit是有效数据的高4bit。
第三字节的高4bit是有效数据的低4bit,第三个字节的低4bit无效。
给发放大的的的

DA芯片通信协议

设备地址

分为固定部分和可编程部分,如下图中的A0为可编程部分。本设备地址设为10011000

在这里插入图片描述

控制字前四位为0000

代码详解

顶层模块sp.v如下
其中pll_controller为PLL IP核、dac_dbgene为产生0-255数据的子模块、dac_controller为I2C写控制模块

module sp6(
			input ext_clk_25m,	//外部输入25MHz时钟信号
			input ext_rst_n,	//外部输入复位信号,低电平有效
			output dac_iic_sck,		//DAC5571的IIC接口SCL
			inout dac_iic_sda		//DAC5571的IIC接口SDA
		);													

//-------------------------------------
//PLL例化
wire clk_12m5;	//PLL输出12.5MHz时钟
wire clk_25m;	//PLL输出25MHz时钟
wire clk_50m;	//PLL输出50MHz时钟
wire clk_100m;	//PLL输出100MHz时钟
wire sys_rst_n;	//PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作

  pll_controller uut_pll_controller
   (// Clock in ports
    .CLK_IN1(ext_clk_25m),      // IN
    // Clock out ports
    .CLK_OUT1(clk_12m5),     // OUT
    .CLK_OUT2(clk_25m),     // OUT
    .CLK_OUT3(clk_50m),     // OUT
    .CLK_OUT4(clk_100m),     // OUT
    // Status and control signals
    .RESET(~ext_rst_n),// IN
    .LOCKED(sys_rst_n));      // OUT		
	
//-------------------------------------		
//产生递增的DAC转换数据
wire[7:0] dac_data;	//DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz

dac_dbgene		uut_dac_dbgene(
				.clk(clk_25m),		//时钟信号
				.rst_n(sys_rst_n),	//复位信号,低电平有效
				.dac_data(dac_data)	//DAC转换数据	
			);
		
//-------------------------------------
//DAC5571的IIC写DA转换数据模块

dac_controller		uut_dac_controller(
					.clk(clk_25m),		//时钟信号
					.rst_n(sys_rst_n),	//复位信号,低电平有效
					.dac_data(dac_data),	//DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
					.scl(dac_iic_sck),		//DAC5571的IIC接口SCL
					.sda(dac_iic_sda)		//DAC5571的IIC接口SDA	
				);
		
endmodule

子模块dac_dbgene如下

module dac_dbgene(
			input clk,		//时钟信号,25MHz
			input rst_n,	//复位信号,低电平有效
			output reg[7:0] dac_data	//DAC转换数据	
		);

//-------------------------------------------------
//10ms定时计数
reg[17:0] cnt;	//10ms计数器

always @(posedge clk or negedge rst_n)
	if(!rst_n) cnt <= 18'd0;
	else if(cnt < 18'd249_999) cnt <= cnt+1'b1;
	else cnt <= 18'd0;
	
//-------------------------------------------------
//DA转换数据递增

always @(posedge clk or negedge rst_n)
	if(!rst_n) dac_data <= 18'd0;
	else if(cnt == 18'd249_999) dac_data <= dac_data+1'b1;


endmodule

子模块dac_controller如下,其中包含一个10段状态机,主要结合上边的知识设置的,设置bnt来写入7——0的数据。

module dac_controller(
			input clk,		//时钟信号,25MHz
			input rst_n,	//复位信号,低电平有效
			input[7:0] dac_data,	//DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
			output scl,		//DAC5571的IIC接口SCL
			inout sda		//DAC5571的IIC接口SDA	
		);

//-------------------------------------------------	
//判断DAC输出数据是否变化,若变化则发起一次IIC数据写入操作					
reg[7:0] dac_datar;		//dac_data缓存寄存器
reg dac_en;		//DAC转换使能信号,高电平有效															

always @(posedge clk or negedge rst_n)										
	if(!rst_n) dac_datar <= 8'd0;
	else dac_datar <= dac_data;

always @(posedge clk or negedge rst_n)										
	if(!rst_n) dac_en <= 1'b0;
	else if(dac_datar != dac_data) dac_en <= 1'b1;
	else dac_en <= 1'b0;

//-------------------------------------------------						
reg[8:0] cnti;		//计数器,25MHz时钟频率下,产生5KHz的IIC时钟															

always @(posedge clk or negedge rst_n)										
	if(!rst_n) cnti <= 9'd0;
	else if(cnti < 9'd499 && cstate != IDLE) cnti <= cnti + 1'b1;
	else cnti <= 9'd0;

wire scl_low = (cnti == 9'd374);											
wire scl_high = (cnti == 9'd124);											

assign scl = ~cnti[8];														

//-------------------------------------------------
//IIC写操作状态机
parameter IDLE		= 4'd0;
parameter START		= 4'd1;
parameter ADDR		= 4'd2;
parameter ACK1		= 4'd3;
parameter CMSB		= 4'd4;
parameter ACK2		= 4'd5;
parameter LSBI		= 4'd6;
parameter ACK3		= 4'd7;
parameter ACK4		= 4'd8;
parameter STOP		= 4'd9;

parameter DEVICE_ADDR	= 8'b1001_1000;										
wire[7:0] dac_mdata = {4'b0000,dac_data[7:4]};	
wire[7:0] dac_ldata = {dac_data[3:0],4'b0000};	

reg[3:0] cstate,nstate;	
reg sdar;	
reg[2:0] bcnt;																
reg sdlink;																	

always @(posedge clk or negedge rst_n)
	if(!rst_n) cstate <= IDLE;
	else cstate <= nstate;
	
always @(cstate or dac_en or scl_high or scl_low or bcnt) begin				
	case(cstate)
		IDLE: 	if(dac_en) nstate <= START;
				else nstate <= IDLE;
		START:  if(scl_high) nstate <= ADDR;
				else nstate <= START;
		ADDR: 	if(scl_low && bcnt == 3'd0) nstate <= ACK1;
				else nstate <= ADDR;
		ACK1: 	if(scl_low) nstate <= CMSB;
				else nstate <= ACK1;
		CMSB: 	if(scl_low && bcnt == 3'd0) nstate <= ACK2;
				else nstate <= CMSB;
		ACK2: 	if(scl_low) nstate <= LSBI;
				else nstate <= ACK2;
		LSBI:	if(scl_low && bcnt == 3'd0) nstate <= ACK3;
				else nstate <= LSBI;
		ACK3: 	if(scl_low) nstate <= ACK4;
				else nstate <= ACK3;
		ACK4: 	if(scl_low) nstate <= STOP;
				else nstate <= ACK4;				
		STOP: 	if(scl_high) nstate <= IDLE;
				else nstate <= STOP;
		default: nstate <= IDLE;
		endcase
end

always @(posedge clk or negedge rst_n)										
	if(!rst_n) begin
			sdar <= 1'b1;
			sdlink <= 1'b1;
		end
	else begin
		case(cstate)
			IDLE: begin
					sdar <= 1'b1;
					sdlink <= 1'b1;	
				end
			START: 	if(scl_high) begin
					sdar <= 1'b0;
					sdlink <= 1'b1;	
				end			
			ADDR: if(scl_low) begin
					sdar <= DEVICE_ADDR[bcnt];	
					sdlink <= 1'b1;	
				end					
			CMSB: if(scl_low) begin 
					sdar <= dac_mdata[bcnt];	
					sdlink <= 1'b1;	
				end
			LSBI: if(scl_low) begin
					sdar <= dac_ldata[bcnt];	
					sdlink <= 1'b1;	
				end
			ACK1,ACK2,ACK3: if(scl_low) begin
					sdar <= 1'b0;
					sdlink <= 1'b0;	
				end
			ACK4: if(scl_low) begin
					sdar <= 1'b0;
					sdlink <= 1'b1;	
				end
			STOP: if(scl_high) begin 
					sdar <= 1'b1;
					sdlink <= 1'b1;	
				end
			default: ;
			endcase
		end
	
assign sda = sdlink ? sdar : 1'bz;											

always @(posedge clk or negedge rst_n)										
	if(!rst_n) bcnt <= 3'd0;
	else begin
		case(cstate)
			ADDR,CMSB,LSBI:	begin
					if(scl_low) bcnt <= bcnt-1'b1;
					else ;
				end
			default: bcnt <= 3'd7;
			endcase
		end

endmodule

参考资料

特权同学
野火FPGA
DAC5571datesheet

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kelinnn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值