基于STEP FPGA的PCF8591的DAC(I2C)功能驱动

 行业动态     |      2023-12-04 13:55:09    |      作者

硬件说明

PCF8591是集成了4路ADC和1路DAC的芯片,使用I2C总线通信。I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。这里不做过多的讲解,硬件连接如下:

本设计的硬件连接如下

本设计中FPGA作为I2C主设备,PCF8591作为I2C从设备,从设备的地址由固定地址和可编程地址组成,我们的外设底板已将可编程地址A0、A1、A2接地,所以7位地址为7'h48,加上最低位的读写控制,所以给PCF8591写数据时的寻址地址为8'h90,对PCF8591读数据时的寻址地址为8'h91。如下

PCF8591集成了很多功能,当需要不同的功能时要对PCF8591做相应的配置,配置数据存储在名为CONTROL BYTE的寄存器中,下图展示了寄存器中部分bit的功能,详细请参考PCF8591的datasheet,本设计中我们只使用DAC功能,配置数据为8'h40。

本设计中我们需要的通信过程具体为:开始–写寻址–读响应–写配置数据–读响应–[写DAC数据–读响应]循环–结束

通过上面的介绍大家应该对如何驱动PCF8591进行DAC采样有了整体的概念,还有一些细节就是I2C通信的时序明细,如下图


Verilog代码

//-------------------------------------------------------------------- //>>>>>>>>>>>>>>>>>>>>>>>>>COPYRIGHTNOTICE<<<<<<<<<<<<<<<<<<<<<<<<< //-------------------------------------------------------------------- //Module:DAC_I2C // //Author:Step // //Description:DAC_I2C //-------------------------------------------------------------------- //CodeRevisionHistory: //-------------------------------------------------------------------- //Version:|Mod.Date:|ChangesMade: //V1.1|2016/10/30|Initialver //-------------------------------------------------------------------- moduleDAC_I2C( input clk_in, //系统时钟 input rst_n_in, //系统复位,低有效 output reg dac_done, //DAC采样完成标志 input [7:0] dac_data, //DAC采样数据 output scl_out, //I2C总线SCL inout sda_out //I2C总线SDA ); parameter CNT_NUM = 15; localparam IDLE = 3'd0; localparam MAIN = 3'd1; localparam START = 3'd2; localparam WRITE = 3'd3; localparam STOP = 3'd4; //根据PCF8591的datasheet,I2C的频率最高为100KHz, //我们准备使用4个节拍完成1bit数据的传输,所以需要400KHz的时钟触发完成该设计 //使用计数器分频产生400KHz时钟信号clk_400khz reg clk_400khz; reg [9:0] cnt_400khz; always@(posedgeclk_inornegedgerst_n_in)begin if(!rst_n_in)begin cnt_400khz<=10'd0; clk_400khz<=1'b0; endelseif(cnt_400khz>=CNT_NUM-1)begin cnt_400khz<=10'd0; clk_400khz<=~clk_400khz; endelsebegin cnt_400khz<=cnt_400khz+1'b1; end end reg [7:0] adc_data_r; reg scl_out_r; reg sda_out_r; reg [2:0] cnt; reg [2:0] cnt_main; reg [7:0] data_wr; reg [2:0] cnt_start; reg [2:0] cnt_write; reg [2:0] cnt_stop; reg [2:0] state; always@(posedgeclk_400khzornegedgerst_n_in)begin if(!rst_n_in)begin //如果按键复位,将相关数据初始化 scl_out_r<=1'd1; sda_out_r<=1'd1; cnt<=1'b0; cnt_main<=1'b0; cnt_start<=1'b0; cnt_write<=3'd0; cnt_stop<=1'd0; dac_done<=1'b1; state<=IDLE; endelsebegin case(state) IDLE:begin //软件自复位,主要用于程序跑飞后的处理 scl_out_r<=1'd1; sda_out_r<=1'd1; cnt<=1'b0; cnt_main<=1'b0; cnt_start<=1'b0; cnt_write<=3'd0; cnt_stop<=1'd0; dac_done<=1'b1; state<=MAIN; end MAIN:begin if(cnt_main>=3'd3)cnt_main<=3'd3;//对MAIN中的子状态执行控制cnt_main elsecnt_main<=cnt_main+1'b1; case(cnt_main) 3'd0: beginstate<=START; end //I2C通信时序中的START 3'd1: begindata_wr<=8'h90; state<=WRITE;end //A0,A1,A2都接了GND,写地址为8'h90 3'd2: begindata_wr<=8'h40; state<=WRITE; end //controlbyte为8'h40,打开DAC功能 3'd3: begindata_wr<=dac_data; state<=WRITE; dac_done<=1'b0; end //需要进行DAC转换的数据 3'd4: beginstate<=STOP; end //I2C通信时序中的结束STOP default:state<=IDLE; //如果程序失控,进入IDLE自复位状态 endcase end START:begin //I2C通信时序中的起始START if(cnt_start>=3'd5)cnt_start<=1'b0; //对START中的子状态执行控制cnt_start elsecnt_start<=cnt_start+1'b1; case(cnt_start) 3'd0: beginsda_out_r<=1'b1; scl_out_r<=1'b1;end //将SCL和SDA拉高,保持4.7us以上 3'd1: beginsda_out_r<=1'b1; scl_out_r<=1'b1;end //clk_400khz每个周期2.5us,需要两个周期 3'd2: beginsda_out_r<=1'b0; end //SDA拉低到SCL拉低,保持4.0us以上 3'd3: beginsda_out_r<=1'b0; end //clk_400khz每个周期2.5us,需要两个周期 3'd4: beginscl_out_r<=1'b0; end //SCL拉低,保持4.7us以上 3'd5: beginscl_out_r<=1'b0; state<=MAIN; end //clk_400khz每个周期2.5us,需要两个周期,返回MAIN default:state<=IDLE; //如果程序失控,进入IDLE自复位状态 endcase end WRITE:begin //I2C通信时序中的写操作WRITE和相应判断操作ACK if(cnt<=3'd6)begin //共需要发送8bit的数据,这里控制循环的次数 if(cnt_write>=3'd3)begincnt_write<=1'b0; cnt<=cnt+1'b1; end elsebegincnt_write<=cnt_write+1'b1; cnt<=cnt; end endelsebegin if(cnt_write>=3'd7)begincnt_write<=1'b0; cnt<=1'b0; end //两个变量都恢复初值 elsebegincnt_write<=cnt_write+1'b1; cnt<=cnt; end end case(cnt_write) //按照I2C的时序传输数据 3'd0: beginscl_out_r<=1'b0; sda_out_r<=data_wr[7-cnt]; end //SCL拉低,并控制SDA输出对应的位 3'd1: beginscl_out_r<=1'b1; end //SCL拉高,保持4.0us以上 3'd2: beginscl_out_r<=1'b1; end //clk_400khz每个周期2.5us,需要两个周期 3'd3: beginscl_out_r<=1'b0; end //SCL拉低,准备发送下1bit的数据 //获取从设备的响应信号并判断 3'd4: beginsda_out_r<=1'bz;dac_done<=1'b1; end //释放SDA线,准备接收从设备的响应信号 3'd5: beginscl_out_r<=1'b1; end //SCL拉高,保持4.0us以上 3'd6: beginif(sda_out)state<=IDLE; elsestate<=state; end //获取从设备的响应信号并判断 3'd7: beginscl_out_r<=1'b0; state<=MAIN; end //SCL拉低,返回MAIN状态 default:state<=IDLE; //如果程序失控,进入IDLE自复位状态 endcase end STOP:begin //I2C通信时序中的结束STOP if(cnt_stop>=3'd5)cnt_stop<=1'b0; //对STOP中的子状态执行控制cnt_stop elsecnt_stop<=cnt_stop+1'b1; case(cnt_stop) 3'd0: beginsda_out_r<=1'b0; end //SDA拉低,准备STOP 3'd1: beginsda_out_r<=1'b0; end //SDA拉低,准备STOP 3'd2: beginscl_out_r<=1'b1; end //SCL提前SDA拉高4.0us 3'd3: beginscl_out_r<=1'b1; end //SCL提前SDA拉高4.0us 3'd4: beginsda_out_r<=1'b1; end //SDA拉高 3'd5: beginsda_out_r<=1'b1; state<=MAIN; end //完成STOP操作,返回MAIN状态 default:state<=IDLE; //如果程序失控,进入IDLE自复位状态 endcase end default:; endcase end end assign scl_out=scl_out_r; //对SCL端口赋值 assign sda_out=sda_out_r; //对SDA端口赋值endmodule


小结

本节主要为大家讲解了使用I2C驱动PCF8591的DAC功能的原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。