D/A芯片DAC5571的使用,包括I2C的学习,并对代码进行详解。
通过程序产生一个0-255循环递增的数据,通过I2C接口不断写入到DAC中,输出的模拟电压可以控制开发板上的某个LED的亮暗变化。结构如下
xilinx spartan6开发板、ISE14.7、modelsim10.5、verilog
I2C 通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。I2C 通讯协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于 I2C 协议占用引脚特别少,两根线便可实现,硬件实现简单,可扩展型强,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
主要介绍I2C协议的整体时序图、读写时序以及I2C设备的器件地址、存储地址I2C整体时序图
由图可知,I2C 协议整体时序图分为 4 个部分,图中标注的①②③④表示 I2C 协议的 4个状态,分别为“总线空闲状态”、“起始信号”、“数据读/写状态”和“停止信号”。
对传入从机的控制命令最低位读写控制位写入不同数据值,主机可实现对从机的读/写操作,读写控制位为 0 时,表示主机要对从机进行数据写入操作;读写控制位为 1 时,表示主机要对从机进行数据读出操作。根据依次写入数据量的不同,I2C的写操作可以分为单字节写、页写、随机写操作。下面只介绍单字节写操作
I2C单字节写操作
单字节写操作流程如下
DAC5571的接口是I2C接口,关于I2C通信的基本接口时序在上边已经简单的总结。
FPGA为I2C总线的主机,若要控制D1C5571完成依次转换,则一共需要传输三个字节的数据。
首字节内容是从机地址(SLAVE ADDRESS)和读/写指示位(R/W)。
第二次字节的高4bit是控制数据,低4bit是有效数据的高4bit。
第三字节的高4bit是有效数据的低4bit,第三个字节的低4bit无效。
分为固定部分和可编程部分,如下图中的A0为可编程部分。本设备地址设为10011000
顶层模块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