文章目录
1. PCF8591简介
PCF8591是一个单片集成、单电源供电、低功耗的8位CMOS数据采集转换(AD/DA)器件,具有4个模拟输入、1个模拟输出和1个串行IIC总线接口。
1.1 PCF8591框图
1.2 PCF8591器件地址
A0/A1/A2用于开发板上多快PCF8591的地址配置;读写控制位(0:写 -DA 1:读-AD)
1.3 PCF8591状态寄存器
bit[6]:模拟输出使能(1:使能 0:不使能)
bit[5:4]:模拟输入编程,00:4个AIN通道都是单端输入
bit[2]:自增标志 0:不自增 1:自增
bit[1:0]:00:AIN0通道 01:AIN1通道 10:AIN2通道 11:AIN3通道
1.4 PCF8591写数据(DA转换)
S:Start
A:应答
ADDRESS:器件地址7’b1001000=7’h48
CONTROL BYTE:控制字
DATA BYTE:DA转换数据
上图为写入数据与输出电压之间的关系及公式,在本次实验过程中,VAGND=0,VREF=3.3V,VAOUT=3.3/256*Data
1.5 PCF8591读数据(AD转换)
S:Start
A:应答
ADDRESS:器件地址7’b1001000=7’h48
DATA BYTE:AD转换后的数据
读出数据和模拟电压之间的转换关系:
Visb=3.3V/256=0.012890625V
AD_DATA=0
VIN=0Visb=0V
AD_DATA=255
VIN=255Visb=3.287109375V
2. 程序设计
使用FPGA开发板上的PCF8591模块实现数模、模数转换。FPGA输出从0~255变化的数字信号,经DAC转换后得到模拟信号。然后利用ADC采集该模拟信号,并将采集的电压值显示在数码管上。
即0-255——>DA——>AOUT——>AIN——>AD——>0~255
2.1 系统框图
2.2 源码
其中IIC设计源码参考EEPROM读写–IIC协议
module adda_top(
//system clock
input sys_clk , // 系统时钟
input sys_rst_n , // 系统复位
//PCF8591 interface
output i2c_ack , // I2C应答标志 0:应答 1:未应答
output scl , // i2c时钟线
inout sda , // i2c数据线
//user interface
output [5:0] sel , // 数码管位选
output [7:0] seg_led // 数码管段选
);
//parameter define
parameter SLAVE_ADDR = 7'h48 ; // 器件地址(SLAVE_ADDR)
parameter BIT_CTRL = 1'b0 ; // 字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; // I2C的SCL时钟频率
parameter POINT = 6'b00_1000 ; // 控制点亮数码管小数点的位置
//wire define
wire clk ; // I2C操作时钟
wire i2c_exec ; // i2c触发控制
wire [15:0] i2c_addr ; // i2c操作地址
wire [ 7:0] i2c_data_w; // i2c写入的数据
wire i2c_done ; // i2c操作结束标志
wire i2c_rh_wl ; // i2c读写控制
wire [ 7:0] i2c_data_r; // i2c读出的数据
wire [19:0] num ; // 数码管要显示的数据
//*****************************************************
//** main code
//*****************************************************
//例化AD/DA模块
pcf8591 u_pcf8591(
//global clock
.clk (clk ), // 时钟信号
.rst_n (sys_rst_n ), // 复位信号
//i2c interface
.i2c_exec (i2c_exec ), // I2C触发执行信号
.i2c_rh_wl (i2c_rh_wl ), // I2C读写控制信号
.i2c_addr (i2c_addr ), // I2C器件内地址
.i2c_data_w (i2c_data_w), // I2C要写的数据
.i2c_data_r (i2c_data_r), // I2C读出的数据
.i2c_done (i2c_done ), // I2C一次操作完成
//user interface
.num (num ) // 采集到的电压
);
//例化i2c_dri
i2c_dri #(
.SLAVE_ADDR (SLAVE_ADDR), // slave address从机地址,放此处方便参数传递
.CLK_FREQ (CLK_FREQ ), // i2c_dri模块的驱动时钟频率(CLK_FREQ)
.I2C_FREQ (I2C_FREQ ) // I2C的SCL时钟频率
) u_i2c_dri(
//global clock
.clk (sys_clk ), // i2c_dri模块的驱动时钟(CLK_FREQ)
.rst_n (sys_rst_n ), // 复位信号
//i2c interface
.i2c_exec (i2c_exec ), // I2C触发执行信号
.bit_ctrl (BIT_CTRL ), // 器件地址位控制(16b/8b)
.i2c_rh_wl (i2c_rh_wl ), // I2C读写控制信号
.i2c_addr (i2c_addr ), // I2C器件内地址
.i2c_data_w (i2c_data_w), // I2C要写的数据
.i2c_data_r (i2c_data_r), // I2C读出的数据
.i2c_done (i2c_done ), // I 2C一次操作完成
.i2c_ack (i2c_ack ), // I2C应答标志 0:应答 1:未应答
.scl (scl ), // I2C的SCL时钟信号
.sda (sda ), // I2C的SDA信号
//user interface
.dri_clk (clk ) // I2C操作时钟
);
//例化动态数码管显示模块
seg_led u_seg_led(
//module clock
.clk (sys_clk ), // 时钟信号
.rst_n (sys_rst_n), // 复位信号
//seg_led interface
.seg_sel (sel ), // 位选
.seg_led (seg_led ), // 段选
//user interface
.data (num ), // 显示的数值
.point (POINT ), // 小数点具体显示的位置,从高到低,高电平有效
.en (1'd1 ), // 数码管使能信号
.sign (1'b0 ) // 符号位(高电平显示“-”号)
);
endmodule
module pcf8591(
//clock and reset
input clk , // 时钟信号
input rst_n , // 复位信号
//i2c interface
output reg i2c_rh_wl , // I2C读写控制信号
output reg i2c_exec , // I2C触发执行信号
output reg [15:0] i2c_addr , // I2C器件内地址
output reg [ 7:0] i2c_data_w , // I2C要写的数据
input [ 7:0] i2c_data_r , // I2C读出的数据
input i2c_done , // I2C一次操作完成
//user interface
output reg [19:0] num // 数码管要显示的数据
);
//parameter
parameter CONTORL_BYTE = 8'b0100_0000; // PCF8591的控制字
parameter V_REF = 12'd3300 ; // 3.3V放大1000倍,避免用小数
//reg define
reg [7:0] da_data ; // DA数据
reg [7:0] ad_data ; // AD数据
reg [3:0] flow_cnt ; // 状态流控制
reg [18:0] wait_cnt ; // 计数等待
//wire define
wire [19:0] num_t ; // 临时寄存的数据
//*****************************************************
//** main code
//*****************************************************
assign num_t = V_REF * ad_data ;
//DA输出数据
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
da_data <= 8'd0;
end
else if(i2c_rh_wl == 1'b0 && i2c_done == 1'b1)begin
if(da_data == 8'd255)
da_data<= 8'd0;
else
da_data<= da_data + 1'b1;
end
end
//AD输入数据处理
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
num <= 20'd0;
end
else
num <= num_t >> 4'd8;
end
//AD、DA控制及采样
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
i2c_exec <= 1'b0;
i2c_rh_wl<= 1'b0;
i2c_addr <= 8'd0;
i2c_data_w <= 8'd0;
flow_cnt <= 4'd0;
wait_cnt <= 17'd0;
end
else begin
i2c_exec <= 1'b0;
case(flow_cnt)
'd0: begin
if(wait_cnt == 17'd100) begin
wait_cnt<= 17'd0;
flow_cnt<= flow_cnt + 1'b1;
end
else
wait_cnt<= wait_cnt + 1'b1;
end
//DA转换输出
'd1: begin
i2c_exec <= 1'b1;
i2c_addr <= CONTORL_BYTE;
i2c_rh_wl <= 1'b0;
i2c_data_w<= da_data;
flow_cnt <= flow_cnt + 1'b1;
end
'd2: begin
if(i2c_done == 1'b1) begin
flow_cnt<= flow_cnt + 1'b1;
end
end
'd3: begin
//每1秒变化0.1V,需33秒变化完,共256【0~255】次变化,故每次变化计数为:
//(33/256)*10^6 = 128906
if(wait_cnt == 17'd128906) begin
wait_cnt<= 17'd0;
flow_cnt<= flow_cnt + 1'b1;
end
else
wait_cnt<= wait_cnt + 1'b1;
end
//AD转换输入
'd4: begin
i2c_exec <= 1'b1;
i2c_addr <= CONTORL_BYTE;
i2c_rh_wl <= 1'b1;
flow_cnt <= flow_cnt + 1'b1;
end
'd5: begin
if(i2c_done == 1'b1) begin
ad_data <= i2c_data_r;
flow_cnt<= 4'd0;
end
end
default: flow_cnt <= 4'd0;
endcase
end
end
endmodule