基于FPGA的MCP4725驱动程序

本文介绍基于FPGA的MCP4725 DAC驱动程序设计,包括IIC驱动模块、MCP4725初始化模块及顶层模块的设计细节,并通过实验验证了驱动程序的正确性。
该文章已生成可运行项目,

基于FPGA的MCP4725驱动程序

  1. 芯片资料
          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)模式。
图2.MCP4725封装类型
   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
本文章已经生成可运行项目
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值