1)实验平台:正点原子开拓者
FPGA 开发板
2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html
第三十一章 AD/DA实验
PCF8591是具有I2C总线接口的8位AD/DA转换芯片。因其功耗低、控制简单、封装小而广泛
应用于远程数据采集的低功耗转换器、
电源监控等领域。本章我们将使用FPGA开发板上的
PCF8591器件实现AD/DA的转换。
本章包括以下几个部分:
31.1 PCF8591简介
31.2 实验任务
31.3 硬件设计
31.4 程序设计
31.5 下载验证
PCF8591简介
PCF8591是一个单片集成、单电源供电、低功耗的8位CMOS数据采集转换(AD/DA)器件,
具有4个模拟输入、1个模拟输出和1个串行I2C总线接口。PCF8591的功能包括多路复用模拟输
入、片上跟踪和保持、8位AD(模数)转换和8位DA(数模)转换,其最大转换速率取决于I2C
总线的最高速率。
PCF8591内部功能模块的框图如图 31.1.1所示:
图 31.1.1 PCF8591功能框图
当使用I2C总线配置完状态寄存器(STATUS REGISTER)后,传送数模转换的数据到DAC数
据寄存器(DAC DATA REGISTER)。经片内数模转换器(DAC)转换成对应的模拟电压。片内DAC
由连接至外部参考电压的具有256个接头的电阻分压
电路和选择开关组成。接头译码器切换一
个接头至DAC输出线经AOUT接口输出,如下图 31.1.2所示。
图 31.1.2 DAC电阻电路
片内DAC也可用于逐次逼近式ADC。为了在AD转换进行时能够保持DA转换的电压输出,器件
内配备了采样和保持电路(SAMPLE AND HOLD)。
AIN0至AIN3为多路复用模拟输入接口(ANALOGUE MUL
tiPLEXER)。外部的模拟信号通过多
路复用模拟输入接口后被采样和保持(SAMPLE AND HOLD),并通过比较器(COMPARATOR)采
用逐次逼近转换技术转换成数字信号。
PCF8591采用I2C总线协议与控制器(FPGA)进行
通信,有关I2C总线协议详细的介绍请大
家参考“EEPROM读写实验”。PCF8591的器件地址(7位)由固定部分和可编程部分组成。可编
程部分根据地址引脚A0、A1、A2来设置,第8位为读写控制位R/W,格式如下图所示
图 31.1.3 器件地址格式图
发送完器件地址和读写控制位后就发送控制字。PCF8591将控制字存储在控制寄存器,用
于控制器件功能。控制字格式如下图所示:
图 31.1.4 控制字格式图
控制字的第6位用于使能模拟输出(高有效),第5和第4位用于模拟输入编程。当第5位和
第4位为00时,AIN0至AIN3为四个单端输入;为01时AIN0至AIN3为三个差分输入;为10时AIN0
至AIN3为两单端一差分输入;为11时AIN0至AIN3为两差分输入。我们这次实验使用的是四个单
端输入(00)。第2位为自增标志位,高有效。如果自增标志位置1,每次AD转换后通道号将自
动增加。如果选择一个不存在的输入通道将导致分配到最高可用的通道号,此时如果自增标志
有效,那么下一个被选择的通道将总是通道0。第1位和第0位用于选择通道号:00为通道0;01
为通道1;10为通道2;11为通道3。第7位和第3位是预留未来使用的,必须设置为0。上电复位
后,控制寄存器所有位为0。
如果是进行DA转换,在发送完控制字后发送需要转换的8位数据,这8位数据被PCF8591存
储在DAC数据寄存器,并使用片上DA转换器转换成对应的模拟电压输出。转换电压如图 31.1.5
所示:
图 31.1.5 DA转换电压
图 31.1.6 DA转换时序图
从DA转换时序图(图 31.1.6)中,我们可以看到在发送第一个字节的转换数据(DATA BYTE1)
时,DAC输出的还是先前的转换的值,在PCF8591发送应答信号(A)后DAC才开始输出相应的模
拟电压。
在开始进行AD转换时,一个AD转换周期总是开始于发送一个有效读模式命令给PCF8591。
AD转换周期在每次应答时钟脉冲的后沿被触发,并在传输当前AD数据寄存器的数据时执行转换
操作,如下图 31.1.7所示。
图 31.1.7 AD转换时序图
图 31.1.7中,DATA BYTE0是ADC寄存器(ADC DATA REGISTER)当前存储的值,也就是先
前转换的值。PCF8591在传送DATA BYTE0时采样所选模拟输入通道的输入电压,并转换为对应
的8位二进制码即DATA BYTE1的数据。转换结果被保存在ADC数据寄存器。上电复位之后ADC寄
存器中的数据默认为0x80,故读取的第一个ADC数据为0x80。
本次实验采用的是单端输入,其转换特性如下图所示:
图 31.1.8 单端输入的AD转换特性
可见转换精度Vl***与参考电压VREF和模拟地AGND有关,而ADC转换的数据(ADC DATA)与转
换精度有关。AGND引脚因为必须连接到系统模拟地,所以一般为0。
实验任务
本节实验任务是使用开拓者FPGA开发板上的PCF8591模块实现数模、模数转换。FPGA输出
从0~255变化的数字信号,经DAC转换后得到模拟信号。然后利用ADC采集该模拟信号,并将采
集的电压值显示在数码管上。
硬件设计
开拓者开发板上PCF8591接口部分的原理图如图 31.3.1所示。
图 31.3.1 PCF8591接口原理图
由上图可见器件地址的可编程引脚(A0/A1/A2)全部接地,所以PCF8591的器件地址为
7’h48。参考电压为3.3V,AGND接地,所以AD转换精度Vl*** = 3
.3−0
256
≈ 0.01289,转换的数值为
VAIN
= 256×
3.
3
。
由实验任务可知,我们需要把DA转换输出引脚AOUT连接至任一AD输入引脚,这里我们选择
AIN0,相应的控制字设置为8’h40。
本实验中,各端口信号的管脚分配如下表所示:
表 31.3.1 ADC模数DAC数模转换(PCF8591)实验管脚分配
程序设计
根据实验任务,我们可以大致规划出系统的控制流程:FPGA首先通过I2C总线向PCF8591写
入DA转换的数据,然后再从PCF8591中读取AD转换的值,将读取的到值转换为实际的模拟电压,
并用数码管显示出来。由此画出系统的功能框图如下所示:
图 31.4.1 ADC模数DAC数模转换(PCF8591)系统框图
程序中各模块端口及信号连接如图 31.4.2所示:
图 31.4.2 顶层模块原理图
顶层模块(adda_top):例化了IIC驱动模块(i2c_dri)、PCF8591 AD和DA转换模块(pcf8591)
以及数码管驱动模块(seg_led)三个模块。并实现各模块控制及数据信号的交互。PCF8591 AD
和DA转换模块调用IIC驱动模块与PCF8591进行通信,并将处理得到的AD值送入数码管显示。
PCF8591 DA和AD转换模块模块(pcf8591):通过调用IIC驱动模块来实现DA和AD的转换,
并将读到的AD数值转换成模拟电压(num)传递给数码管驱动模块(segled_dri_x)显示。
IIC驱动模块(i2c_dri):由于PCF8591采用IIC协议,所以使用IIC驱动模块实现FPGA与
PCF8591间的通信。
数码管驱动模块(seg_led):数码管驱动模块显示AD转换得到的电压值。
顶层模块的代码如下:
1
module adda_top
(
2 //system clock
3
input sys_clk
, // 系统时钟
4
input sys_rst_n
, // 系统复位
5
6 //PCF8591 interface
7
output scl
, // i2c时钟线
8
inout sda
, // i2c数据线
9
10 //user interface
11
output [5
:0
] sel
, // 数码管位选
12
output [7
:0
] seg_led // 数码管段选
13
);
14
15 //parameter define
16
parameter SLAVE_ADDR
= 7'h48
; // 器件地址(SLAVE_ADDR)
17
parameter BIT_CTRL
= 1'b0
; // 字地址位控制参数(16b/8b)
18
parameter CLK_FREQ
= 26'd50_000_000
; // i2c_dri模块的驱动时钟频率(CLK_FREQ)
19
parameter I2C_FREQ
= 18'd250_000
; // I2C的SCL时钟频率
20
parameter POINT
= 6'b00_1000
; // 控制点亮数码管小数点的位置
21
22 //wire define
23
wire clk
; // I2C操作时钟
24
wire i2c_exec
; // i2c触发控制
25
wire [15
:0
] i2c_addr
; // i2c操作地址
26
wire [ 7
:0
] i2c_data_w
; // i2c写入的数据
27
wire i2c_done
; // i2c操作结束标志
28
wire i2c_rh_wl
; // i2c读写控制
29
wire [ 7
:0
] i2c_data_r
; // i2c读出的数据
30
wire [19
:0
] num
; // 数码管要显示的数据
31
32 //*****************************************************
33 //** main code
34 //*****************************************************
35
36 //例化AD/DA模块
37 pcf8591 u_pcf8591
(
38 //global clock
39
.clk
(clk
), // 时钟信号
40
.rst_n
(sys_rst_n
), // 复位信号
41 //i2c interface
42
.i2c_exec
(i2c_exec
), // I2C触发执行信号
43
.i2c_rh_wl
(i2c_rh_wl
), // I2C读写控制信号
44
.i2c_addr
(i2c_addr
), // I2C器件内地址
45
.i2c_data_w
(i2c_data_w
), // I2C要写的数据
46
.i2c_data_r
(i2c_data_r
), // I2C读出的数据
47
.i2c_done
(i2c_done
), // I2C一次操作完成
48 //user interface
49
.num
(num
) // 采集到的电压
50
);
51
52 //例化i2c_dri
53 i2c_dri
#(
54
.SLAVE_ADDR
(SLAVE_ADDR
), // slave address从机地址,放此处方便参数传递
55
.CLK_FREQ
(CLK_FREQ
), // i2c_dri模块的驱动时钟频率(CLK_FREQ)
56
.I2C_FREQ
(I2C_FREQ
) // I2C的SCL时钟频率
57
) u_i2c_dri
(
58 //global clock
59
.clk
(sys_clk
), // i2c_dri模块的驱动时钟(CLK_FREQ)
60
.rst_n
(sys_rst_n
), // 复位信号
61 //i2c interface
62
.i2c_exec
(i2c_exec
), // I2C触发执行信号
63
.bit_ctrl
(BIT_CTRL
), // 器件地址位控制(16b/8b)
64
.i2c_rh_wl
(i2c_rh_wl
), // I2C读写控制信号
65
.i2c_addr
(i2c_addr
), // I2C器件内地址
66
.i2c_data_w
(i2c_data_w
), // I2C要写的数据
67
.i2c_data_r
(i2c_data_r
), // I2C读出的数据
68
.i2c_done
(i2c_done
), // I 2C一次操作完成
69
.scl
(scl
), // I2C的SCL时钟信号
70
.sda
(sda
), // I2C的SDA信号
71 //user interface
72
.dri_clk
(clk
) // I2C操作时钟
73
);
74
75 //例化动态数码管显示模块
76 seg_led u_seg_led
(
77 //module clock
78
.clk
(sys_clk
), // 时钟信号
79
.rst_n
(sys_rst_n
), // 复位信号
80 //seg_led interface
81
.sel
(sel
), // 位选
82
.seg_led
(seg_led
), // 段选
83 //user interface
84
.data
(num
), // 显示的数值
85
.point
(POINT
), // 小数点具体显示的位置,从高到低,高电平有效
86
.en
(1'd1
), // 数码管使能信号
87
.sign
(1'b0
) // 符号位(高电平显示“-”号)
88
);
89
90
endmodule
顶层模块中主要完成对其余模块的例化,其中I2C驱动模块(i2c_dri)程序与“EEPROM读
写实验”章节中的IIC驱动模块(i2c_dri)程序完全相同。有关IIC驱动模块的详细介绍请大
家参考“EEPROM读写实验”。
PCF8591 AD和DA转换模块模块的代码如下所示:
1
module pcf8591
(
2 //clock and reset
3
input clk
, // 时钟信号
4
input rst_n
, // 复位信号
5
6 //i2c interface
7
output reg i2c_rh_wl
, // I2C读写控制信号
8
output reg i2c_exec
, // I2C触发执行信号
9
output reg [15
:0
] i2c_addr
, // I2C器件内地址
10
output reg [ 7
:0
] i2c_data_w
, // I2C要写的数据
11
input [ 7
:0
] i2c_data_r
, // I2C读出的数据
12
input i2c_done
, // I2C一次操作完成
13
14 //user interface
15
output reg [19
:0
] num // 数码管要显示的数据
16
);
17
18 //parameter
19
parameter CONTORL_BYTE
= 8'b0100_0000
; // PCF8591的控制字
20
parameter V_REF
= 12'd3300
; // 3.3V放大1000倍,避免用小数
21
22 //reg define
23
reg [7
:0
] da_data
; // DA数据
24
reg [7
:0
] ad_data
; // AD数据
25
reg [3
:0
] flow_cnt
; // 状态流控制
26
reg [18
:0
] wait_cnt
; // 计数等待
27
28 //wire define
29
wire [19
:0
] num_t
; // 临时寄存的数据
30
31 //*****************************************************
32 //** main code
33 //*****************************************************
34
35
assign num_t
= V_REF
* ad_data
;
36
37 //DA输出数据
38
always @(posedge clk
or negedge rst_n
) begin
39
if(rst_n
== 1'b0
) begin
40 da_data
<= 8'd0
;
41
end
42
else if(i2c_rh_wl
== 1'b0
& i2c_done
== 1'b1
)begin
43
if(da_data
== 8'd255
)
44 da_data
<= 8'd0
;
45
else
46 da_data
<= da_data
+ 1'b1
;
47
end
48
end
49
50 //AD输入数据处理
51
always @(posedge clk
or negedge rst_n
) begin
52
if(rst_n
== 1'b0
) begin
53 num
<= 20'd0
;
54
end
55
else
56 num
<= num_t
>> 4'd8
;
57
end
58
59 //AD、DA控制及采样
60
always @(posedge clk
or negedge rst_n
) begin
61
if(rst_n
== 1'b0
) begin
62 i2c_exec
<= 1'b0
;
63 i2c_rh_wl
<= 1'b0
;
64 i2c_addr
<= 8'd0
;
65 i2c_data_w
<= 8'd0
;
66 flow_cnt
<= 4'd0
;
67 wait_cnt
<= 17'd0
;
68
end
69
else begin
70 i2c_exec
<= 1'b0
;
71
case(flow_cnt
)
72 'd0
: begin
73
if(wait_cnt
== 17'd100
) begin
74 wait_cnt
<= 17'd0
;
75 flow_cnt
<= flow_cnt
+ 1'b1
;
76
end
77
else
78 wait_cnt
<= wait_cnt
+ 1'b1
;
79
end
80 //DA转换输出
81 'd1
: begin
82 i2c_exec
<= 1'b1
;
83 i2c_addr
<= CONTORL_BYTE
;
84 i2c_rh_wl
<= 1'b0
;
85 i2c_data_w
<= da_data
;
86 flow_cnt
<= flow_cnt
+ 1'b1
;
87
end
88 'd2
: begin
89
if(i2c_done
== 1'b1
) begin
90 flow_cnt
<= flow_cnt
+ 1'b1
;
91
end
92
end
93 'd3
: begin
94 //每1秒变化0.1V,需33秒变化完,共256【0~255】次变化,故每次变化计数为:
95 //(33/256)*10^6 = 128906
96
if(wait_cnt
== 17'd128906
) begin
97 wait_cnt
<= 17'd0
;
98 flow_cnt
<= flow_cnt
+ 1'b1
;
99
end
100
else
101 wait_cnt
<= wait_cnt
+ 1'b1
;
102
end
103 //AD转换输入
104 'd4
: begin
105 i2c_exec
<= 1'b1
;
106 i2c_addr
<= CONTORL_BYTE
;
107 i2c_rh_wl
<= 1'b1
;
108 flow_cnt
<= flow_cnt
+ 1'b1
;
109
end
110 'd5
: begin
111
if(i2c_done
== 1'b1
) begin
112 ad_data
<= i2c_data_r
;
113 flow_cnt
<= 4'd0
;
114
end
115
end
116
default: flow_cnt
<= 4'd0
;
117
endcase
118
end
119
end
120
121
endmodule
因 为 AD 采集到的 数 据 ad_data = VAIN
= 256×
3.
3
,所以输入的 模拟电压 VAIN =
_×3.3×1000
256×1000
。程序中第35行我们对采集到的AD数据进行了处理,使其乘以V_REF(3300),
也就是对参考电压放大1000倍,然后在第58行以右移8位的方式除以256。结合顶层模块的第20
行可知我们使数码管的小数点显示在第4位数码管,相当于对输入给数码管显示的数值缩小
1000倍,这样我们送给数码管显示的值就是实际的模拟输入值。
图 31.4.3为读取过程中SignalTap抓取的波形图。可以看到寄存器地址为40h,也就是控
制字为0x40,选择的是通道0(AIN0),并使能模拟输出。当前读到的AD值为02h,写的DA值为
03h,可见传送的上次AD转换的值,传递给数码管显示的数值为25,表明当前输入的模拟电压
的值为0.025V,
图 31.4.3 SignalTap波形图
下载验证
首先我们打开PCF8591模数/数模转换实验工程,在工程所在的路径下打开adda_top/par文
件夹,在里面找到“adda_top.qpf”并双击打开。注意工程所在的路径名只能由字母、数字以
及下划线组成,不能出现中文、空格以及特殊字符等。工程打开后如图 31.5.1所示。
图 31.5.1 ADC模数DAC数模转换(PCF8591)工程
然后将下载器一端连电脑,另一端与开发板上对应端口连接,并将P4的AOUT引脚与AINO用
跳帽或者杜邦线连接起来,最后连接电源线并打开电源开关。
接下来我们下载程序,验证PCF859的ADC模数DAC数模转换功能。
工程打开后通过点击工具栏中的“Programmer”图标打开下载界面,通过“Add File”按
钮选择adda_top/par/output_files目录下的“adda_top.sof”文件。开发板电源打开后,在
程序下载界面点击“Hardware Setup”,在弹出的对话框中选择当前的硬件连接为“USB-
Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如
图 31.5.2所示。
图 31.5.2 程序下载界面
下载完成后观察到开发板上数码管显示的值从0增加到3.3V,说明ADC模数DAC数模转换
(PCF8591)实验程序下载验证成功。
图 31.5.3 数码管显示