基于FPGA的MCP4725驅動程式
- 芯片資料
MCP4725是低功耗、高精度、單通道的12位緩沖電壓輸出數模轉換器(Digital-to-Analog Convertor,DAC),具有非易失性存盤器(EEPROM),用戶可以使用I2C介面命令將DAC輸入和配置資料燒寫到非易失性存盤器(EEPROM),非易失性存盤器功能使得DAC器件在斷電期間仍能保持DAC輸入代碼,且DAC輸出在上電后立即可用,

圖1.MCP4725功能框圖
MCP4725具有外部A0地址位選擇引腳,此A0引腳可連接用戶應用電路板的VDD或VSS,MCP4725具有2線型IIC兼容串行介面,可用于標準(100 kHz)、快速(400 kHz)或高速(3.4 MHz)模式,

Vout:模擬輸出電壓;
Vss:參考地;
VDD:電源電壓;3.7~5.5V
SDA:IIC串行資料;
SCL:IIC串行時鐘輸入
A0:地址位選擇引腳;該引腳可連接到VSS或VDD ,或由數字邏輯電平有效驅動,該引腳的邏輯狀態決定了I2 C地址位的A0位,
2. 輸出電壓計算

例如當我們輸入0x400,即十進制數1024,電源電壓接入為5V,那么輸出電壓Vout=5*1024/4096=1.25V,
3. 作業原理
當器件連接到I2C總線時,器件作為從器件作業,使用I2C介面命令,主器件可以讀/寫DAC輸入暫存器或EEPROM,MCP4725器件地址包含4個固定位(1100 =器件代碼)和3個地址位(A2、A1和A0),A2和A1位是在出廠前硬連線的,而A0位由A0引腳的邏輯狀態決定,A0引腳可連接到VDD或VSS,或由數字邏輯電平有效驅動,寫命令用于將配置位和DAC輸入代碼裝載到DAC暫存器,或寫入器件的EEPROM,通過使用3個寫命令型別位(C2、C1和C0)定義寫命令型別,

當C2=0,C1=0 時,為快速模式,此命令用于更改DAC暫存器,EEPROM不受影響;當C2=0,C1=1,C0=0 時,為寫DAC暫存器模式,即將配置位和資料代碼裝載到DAC暫存器;當C2=0,C1=1,C0=1 時,為寫DAC暫存器和更新EEPROM模式,將配置位和資料代碼裝載到DAC暫存器并且寫入EEPROM中,本次主要使用寫DAC暫存器模式和寫DAC暫存器和更新EEPROM模式,如下圖所示,

第一個位元組為器件尋址,A2和A1已經被廠家設定為0,A0由自己控制(默認為0,即接地),因此第一個位元組為0x60;第二個位元組為寫資料地址,PD0和PD1都為0時為正常模式,因此第二個位元組為0x60;第三個位元組和第四個位元組的高4位組成12位資料輸入,由我們自己定義輸入,
4. IIC串行通信
MCP4725器件使用2線IIC串行介面,該介面可在標準、快速或高速模式下作業,在總線上發送資料的器件定義為發送器,而接收資料的器件定義為接收器,總線必須由主器件控制,主器件產生串行時(SCL)信號、控制總線訪問權并產生啟動條件和停止條件,MCP4725器件作為從器件作業,主器件和從器件都可以作為發送器或接收器作業,但是由主器件決定激活哪種模式,通信由主器件(單片機)發起,它發送啟動位,隨后是從地址位元組,發送的第一個位元組始終為從地址位元組,它包含器件代碼、地址位和R/W位,MCP4725器件的器件代碼為1100,當器件接收到讀命令(R/W = 1)時,發送DAC輸入暫存器和EEPROM的內容,下圖給出了IIC通信時序要求,

在本次設計中,SCL時鐘輸入頻率選擇為250KHz,FPGA作業時鐘為50MHz,上電等待20ms后開始IIC資料寫入,采用模塊化設計,分為IIC驅動設計,MCP4725初始化設計,頂層模塊,
5. 代碼模塊
5.1 IIC驅動模塊
module i2c_dri
#(// slave address(器件地址)
parameter SLAVE_ADDR = 7'b1100000 ,
parameter CLK_FREQ = 26'd50_000_000, // 時鐘頻率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 // I2C的SCL時鐘頻率
)(
//global clock
input clk , // 時鐘
input rst_n , // 復位信號
//i2c interface
input i2c_exec , // I2C觸發執行信號
input bit_ctrl , // 字地址位控制(16b/8b)
input i2c_rh_wl , // I2C讀寫控制信號
input [15:0] i2c_addr , // I2C器件內地址
input [15:0] i2c_data_w , // I2C要寫的資料
output reg [ 7:0] i2c_data_r , // I2C讀出的資料
output reg i2c_done , // I2C一次操作完成
output reg scl , // I2C的SCL時鐘信號
inout sda , // I2C的SDA信號
//user interface
output reg dri_clk // 驅動I2C操作的驅動時鐘
);
//localparam define
localparam st_idle = 8'b0000_0001; // 空閑狀態
localparam st_sladdr = 8'b0000_0010; // 發送器件地址(slave address)
localparam st_addr16 = 8'b0000_0100; // 發送16位字地址
localparam st_addr8 = 8'b0000_1000; // 發送8位字地址
localparam st_data_wr = 8'b0001_0000; // 寫資料(8 bit)
localparam st_addr_rd = 8'b0010_0000; // 發送器件地址讀
localparam st_data_rd = 8'b0100_0000; // 讀資料(8 bit)
localparam st_stop = 8'b1000_0000; // 結束I2C操作
//reg define
reg sda_dir ; // I2C資料(SDA)方向控制
reg sda_out ; // SDA輸出信號
reg st_done ; // 狀態結束
reg wr_flag ; // 寫標志
reg [ 6:0] cnt ; // 計數
reg [ 7:0] cur_state ; // 狀態機當前狀態
reg [ 7:0] next_state ; // 狀態機下一狀態
reg [15:0] addr_t ; // 地址
reg [ 7:0] data_r ; // 讀取的資料
reg [15:0] data_wr_t ; // I2C需寫的資料的臨時寄存
reg [ 9:0] clk_cnt ; // 分頻時鐘計數
//wire define
wire sda_in ; // SDA輸入信號
wire [8:0] clk_divide ; // 模塊驅動時鐘的分頻系數
//SDA控制
assign sda = sda_dir ? sda_out : 1'bz; // SDA資料輸出或高阻
assign sda_in = sda ; // SDA資料輸入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 3; // 模塊驅動時鐘的分頻系數
//生成I2C的SCL的四倍頻率的驅動時鐘用于驅動i2c的操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b1;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式狀態機)同步時序描述狀態轉移
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//組合邏輯判斷狀態轉移條件
always @( * ) begin
// next_state = st_idle;
case(cur_state)
st_idle: begin // 空閑狀態
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl) // 判斷是16位還是8位字地址
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin // 寫16位字地址
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin // 8位字地址
if(st_done) begin
if(wr_flag==1'b0) // 讀寫判斷
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin // 寫資料(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin // 寫地址以進行讀資料
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin // 讀取資料(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin // 結束I2C操作
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
//時序電路描述狀態輸出
always @(posedge dri_clk or negedge rst_n) begin
//復位初始化
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r <= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin // 空閑狀態
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
end
end
st_sladdr: begin // 寫地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; // 開始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 傳送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; // 0:寫
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0; // 從機應答
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; // 傳送字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; // 從機應答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7]; // 字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; // 從機應答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin // 寫資料(12 bit)
case(cnt)
7'd0: begin
sda_out <= data_wr_t[15]; // I2C寫2次8位資料
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; // 從機應答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: sda_out <= data_wr_t[7];
7'd37: scl <= 1'b1;
7'd39: scl <= 1'b0;
7'd40: sda_out <= data_wr_t[6];
7'd41: scl <= 1'b1;
7'd43: scl <= 1'b0;
7'd44: sda_out <= data_wr_t[5];
7'd45: scl <= 1'b1;
7'd47: scl <= 1'b0;
7'd48: sda_out <= data_wr_t[4];
7'd49: scl <= 1'b1;
7'd51: scl <= 1'b0;
7'd52: sda_out <= data_wr_t[3];
7'd53: scl <= 1'b1;
7'd55: scl <= 1'b0;
7'd56: sda_out <= data_wr_t[2];
7'd57: scl <= 1'b1;
7'd59: scl <= 1'b0;
7'd60: sda_out <= data_wr_t[1];
7'd61: scl <= 1'b1;
7'd63: scl <= 1'b0;
7'd64: sda_out <= data_wr_t[0];
7'd65: scl <= 1'b1;
7'd67: scl <= 1'b0;
7'd68: begin
sda_dir <= 1'b0; // 從機應答
sda_out <= 1'b1;
end
7'd69: scl <= 1'b1;
7'd70: st_done <= 1'b1;
7'd71: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin // 寫地址以進行讀資料
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; // 重新開始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 傳送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; // 1:讀
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0; // 從機應答
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin // 讀取資料(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1; // 非應答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin // 結束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; // 結束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; // 向上層模塊傳遞I2C結束信號
end
default : ;
endcase
end
endcase
end
end
endmodule
5.2 MCP4725初始化模塊
module MCP4725_init(
input clk , //時鐘信號
input rst_n , //復位信號,低電平有效
input i2c_done , //I2C暫存器配置完成信號
output reg i2c_exec , //I2C觸發執行信號
output reg [23:0] i2c_data //I2C要配置的地址與資料(高16位地址,低8位資料)
);
//reg define
reg [14:0] start_init_cnt; //等待延時計數器
//scl配置成250khz,輸入的clk為1Mhz,周期為1us,20000*1us = 20ms
//上電到開始配置IIC至少等待20ms
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
start_init_cnt <= 15'd0;
else if(start_init_cnt < 15'd20000)
start_init_cnt <= start_init_cnt + 1'b1;
end
//i2c觸發執行信號
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_exec <= 1'b0;
else if(start_init_cnt == 15'd19999)
i2c_exec <= 1'b1;
else
i2c_exec <= 1'b0;
end
//配置暫存器地址與資料
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_data <= 16'd0;
else
i2c_data <= {8'h60,8'h40,8'h00} ;//第一個位元組為模式控制(60:同步更新到EEPROM;40:只更新DAC暫存器),后面兩個位元組為輸入資料,取高12位
end
endmodule
5.3 頂層模塊
module MCP4725_CTRL(
input clk,
input rst_n,
output scl,
inout sda
);
wire i2c_exec ; //I2C觸發執行信號
wire [23:0] i2c_data ; //I2C要配置的地址與資料(高8位地址,低16位資料)
wire i2c_done ; //I2C暫存器配置完成信號
wire i2c_dri_clk ; //I2C操作時鐘
parameter SLAVE_ADDR = 7'h60 ; //MCP4725的器件地址7'h60
parameter BIT_CTRL = 1'b0 ; //位元組地址為8位 0:8位 1:16位
parameter CLK_FREQ = 26'd50_000_000; //時鐘頻率 50MHz
parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL時鐘頻率,250KHz
i2c_dri
#(
.SLAVE_ADDR (SLAVE_ADDR), //引數傳遞
.CLK_FREQ (CLK_FREQ ),
.I2C_FREQ (I2C_FREQ )
)
u_i2c_dri(
.clk (clk ),
.rst_n (rst_n ),
.i2c_exec (i2c_exec ),
.bit_ctrl (BIT_CTRL ),
.i2c_rh_wl (1'b0), //固定為0,只用到了IIC驅動的寫操作
.i2c_addr (i2c_data[23:16]),
.i2c_data_w (i2c_data[15:0]),
.i2c_data_r (),
.i2c_done (i2c_done ),
.scl (scl ),
.sda (sda ),
.dri_clk (i2c_dri_clk) //I2C操作時鐘
);
MCP4725_init u_MCP4725_init(
.clk (i2c_dri_clk), //時鐘信號
.rst_n (rst_n), //復位信號,低電平有效
.i2c_done(i2c_done), //I2C暫存器配置完成信號
.i2c_exec(i2c_exec), //I2C觸發執行信號
.i2c_data(i2c_data) //I2C要配置的地址與資料(高8位地址,低16位資料)
);
endmodule
PS:IIC驅動程式是我在以前寫的IIC單位元組讀寫程式的基礎上修改而來,從而實作兩個位元組的寫入(這里可以再增加實作多個位元組的寫入,但要注意cnt的位寬),MCP4725初始化程式中最后的 i2c_data <= {8’h60,8’h40,8’h00} ; 這一行大家可以根據自己的實際需求做更改,詳細更改的內容自行參考芯片的資料手冊,
6.驗證
將工程編譯好后的sof檔案下載到EP4CE6F17C8器件中,連接好MCP4725各個引腳,電源接5V,輸入的資料為1024,為4096的四分之一,因此輸出電壓應該為1.25V,利用萬用表測量資料為1.25V,將模塊斷電再重新上電,再次測量也為1.25V,證明資料成功寫入EEPROM中,驗證成功,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/208454.html
標籤:其他
