学习内容
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 通讯总线中,可连接多个 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表示“总线空闲状态”。再次状态下SCL和SDA均为高电平,此时无I2C设备工作。
- 图中的2表示“起始信号”。SCL依旧保持高电平,SDA出现下降沿,产生一个起始信号,此时与总线相连的所有I2C设备在检查到起始信号后,均跳出空闲状态,等待控制字节的输入。
- 图中的3表示“数据读写状态”,时序图如下
通讯模式是主从通讯,双方有主从之分。
当主机向从机进行指令或者数据的写入时,串行数据线SDA上的数据在串行时钟SCL的高电平时写入从机设备,每次写入一位数据;SDA中的数据在SCL为低电平时进行数据更新,保证数据的稳定。
当一个完整字节的指令或数据传输完成,会通过拉低SDA为低电平来向主机设备发送单比特的应答信号,表示数据写入成功。若正确应答则可以结束或开始下一字节数据或指令的传输。 - 图中的4表示“停止信号”,完成数据读写后,串口时钟SCL保持高电平,当串口数据信号SDA产生一个上升沿时,产生一个停止信号,I2C总线跳回“总线空闲状态”。
3、I2C读写操作
对传入从机的控制命令最低位读写控制位写入不同数据值,主机可实现对从机的读/写操作,读写控制位为 0 时,表示主机要对从机进行数据写入操作;读写控制位为 1 时,表示主机要对从机进行数据读出操作。根据依次写入数据量的不同,I2C的写操作可以分为单字节写、页写、随机写操作。下面只介绍单字节写操作
I2C单字节写操作
单字节写操作流程如下
- 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低
电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后。 - 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写
入。按高位在前低位在后的顺序写入单字节存储地址。 - 地址写入完成,主机接收到从机回传的应答信号后,开始单字节数据的写入。
- 单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,单字节数据
写入完成。
DA芯片相关知识
DAC5571的接口是I2C接口,关于I2C通信的基本接口时序在上边已经简单的总结。
FPGA为I2C总线的主机,若要控制D1C5571完成依次转换,则一共需要传输三个字节的数据。
首字节内容是从机地址(SLAVE ADDRESS)和读/写指示位(R/W)。
第二次字节的高4bit是控制数据,低4bit是有效数据的高4bit。
第三字节的高4bit是有效数据的低4bit,第三个字节的低4bit无效。
设备地址
分为固定部分和可编程部分,如下图中的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