19. 基于i2c协议的adda实验

在前面章节我们使用外载的AD/DA模块,进行了DDS信号发生器和简易电压表的设计与实现,在本章节我们使用板载的AD/DA芯片,通过AD/DA实验,学会PCF8591 AD/DA芯片的使用

19.1. 理论学习

PCF8591芯片的学习涉及到AD、DA以及I2C的相关知识,这三部分的理论知识,我们在前面章节已经做了详细讲解。读者若有遗忘可回顾相关章节,此处不再过多说明。对于PCF8591的使用方法,接下来我们要进行详细说明。

19.1.1. PCF8591简介

PCF8591是一款单片集成、单独供电、低功耗8位CMOS数据采集设备,具有四个模拟输入,一个模拟输出和一个串行I2C总线接口。 三个地址引脚A0,A1和A2用于硬件地址编码,最多可支持8个设备同时连接到I2C总线。芯片的输入输出地址、控制信号和数据信息通过两线双向I2C总线串行传输。

该器件的功能包括模拟输入多路复用、片上跟踪和保持功能,可实现8位模数转换和8位数模转换。 最大转换率取决于I2C总线最大传输速度。

19.1.2. 引脚信息

PCF8591引脚图、引脚信息,具体见图 48‑1、表格 48‑1。

I2CAD002

图 48‑1 PCF8591引脚图

表格 48‑1 PCF8591引脚描述

引脚

名称

描述

1

AIN0

模拟输入(模数转换)

2

AIN1

3

AIN2

4

AIN3

5

A0

硬件从地址

6

A1

7

A2

8

VSS

电源接地

9

SDA

I2C总线串行数据输入和输出

10

SCL

I2C总线串行时钟输入

11

OSC

振荡器输入/输出

12

EXT

振荡器输入的外部/内部开关

13

AGND

模拟地

14

VREF

参考电压输入

15

AOUT

模拟输出(数模转换)

16

VDD

电源电压

19.2. 功能描述

19.2.1. 地址信息

与其他I2C通讯设备相同,FPGA通过向设备发送有效地址,可以激活I2C总线系统中的每个PCF8591设备。 地址由固定部分和可编程部分组成,如图 48‑2所示, 必须根据地址引脚A0,A1和A2设置可编程部分。 在I2C总线协议中,地址始终作为起始条件之后的第一个字节发送,地址字节的最后一位是读/写位,它设置后续数据传输的方向。

I2CAD003

图 48‑2 地址字节

19.2.1.1. 控制字

发送到PCF8591设备的第二个字节存储在其控制寄存器中,是控制设备功能的必需字节。控制寄存器的高半字节用于启用模拟输出,并将模拟输入编程为单端或差分输入。下半字节选择上半字节定义的模拟输入通道之一,具体见图 48‑3。

如果设置了自动递增标志,则在每次A / D转换后通道号都会自动递增。如果在使用内部振荡器的应用中需要自动递增模式,则必须在控制字节(位6)中设置模拟输出使能标志。这样可以使内部振荡器连续运行,从而防止由于振荡器启动延迟而导致转换错误。可以在其他时间复位模拟输出使能标志,以减少静态功耗。

选择不存在的输入通道会自动分配最高的可用通道号。因此,如果设置了自动递增标志,则下一个选定的通道始终为通道0。两个半字节的最高有效位保留用于将来的功能,必须将其设置为逻辑0。上电复位(POR)条件后,控制寄存器的所有位均复位为逻辑0, 禁用D / A转换器和振荡器以节省功耗,模拟输出切换到高阻抗状态。

I2CAD004

图 48‑3 控制字的字节组成

19.2.1.2. 数模转换

发送到PCF8591器件的第三个字节存储在DAC数据寄存器中,并使用片上数模转换器转换为相应的模拟电压。数模转换器由电阻分压器链组成,该电阻分压器链通过256个抽头和选择开关连接到外部基准电压。抽头解码器将这些抽头之一切换到DAC输出线,具体见图 48‑4。

I2CAD005

图 48‑4 数模转换器电阻分压器

模拟输出电压由自动归零的单位增益放大器缓冲。 设置控制寄存器的模拟输出使能标志可打开或关闭此缓冲放大器。 在激活状态下,输出电压将保持到发送另一个数据字节为止。

模拟输出AOUT的输出电压的公式,如图 48‑5所示;数模转换序列波形,如图 48‑6所示。

I2CAD006

图 48‑5 模拟电压输出公式

I2CAD007

图 48‑6 数模转换序列波形

19.2.1.3. 模数转换

模数转换器使用逐次逼近转换技术。 在模数转换周期中,暂时使用片上数模转换器和高增益比较器。向PCF8591器件发送有效的读取模式地址后,始终执行模数转换周期。模数转换周期在应答时钟脉冲的后沿触发,并在传输前一转换结果的同时执行,具体见图 48‑7。

I2CAD008

图 48‑7 模数转换数据读取

触发转换周期后,所选通道的输入电压样本将存储在芯片上,并转换为相应的8位二进制代码,具体见图 48‑8。

I2CAD009

图 48‑8 数模转换

19.3. 实战演练

芯片涉及的相关理论知识已经讲解完毕,下面我们通过实验来教会读者使用PCF8591数模/模数转换芯片。

19.4. 实战演练一

19.4.1. 实验目标

设计并编写实验工程,使用板载AD/DA芯片PCF8591测量自电位器输入的模拟信号电压值。

19.4.1.1. 硬件资源

板载AD/DA芯片PCF8591实物图和原理图,具体见图 48‑9、图 48‑10。

I2CAD010

图 48‑9 PCF8591实物图

I2CAD011

图 48‑10 PCF8591原理图

19.4.1.2. 程序设计

整体说明

在本小节,我们先要对整个实验工程有一个整体认识,首先来看一下AD实验工程整体框图,具体见图 48‑11。

I2CAD012

图 48‑11 AD实验工程整体框图

由上图可知,本实验工程包括4个模块,各模块简介,具体见表格 48‑2。

表格 48‑2 模块功能简介

模块名称

功能描述

pcf8591_ad

AD转换模块,实现模数转换

i2c_ctrl

I2C驱动模块

seg_595_dynamic

数码管显示模块

ad

AD实验工程顶层模块

结合图表,简述一下本实验工程的具体流程。

系统上电后,电位器将待检测的模拟电压传入PCF8591,AD/DA芯片将传入的模拟电压的电压值通过ADC转换为数字量;FPGA通过I2C驱动模块(i2c_ctrl)读取转换后的数字量;读取的数字量在经过AD转换模块(pcf8591_ad)处理后传输给数码管显示模块(seg7_dynamic)完成测试 电压显示。

经过本小节的讲解,相信读者对本实验工程的整体框架有了简单了解,接下来我们对实验工程的各子功能模块分别进行详细讲解,帮助更加深入理解实验工程。

I2C驱动模块

I2C驱动模块的相关内容在前面章节已经做了详细介绍,读者若有遗忘可回顾查阅,此处不再过多叙述。

AD转换模块

模块框图

AD转换模块的主要功能是实现待测电压幅值的模数转换。AD转换模块框图及模块输入输出端口简介,具体见图 48‑12、表格 48‑3。

I2CAD013

图 48‑12 AD转换模块框图

表格 48‑3 AD转换模块输入输出信号简介

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,I2C驱动模块产生并传入

sys_rst_n

1Bit

Input

复位信号,低有效

i2c_end

1Bit

Input

i2c设备一次读/写操作完成

rd_data

8Bit

Input

自ADC芯片读取数据

rd_en

1Bit

Output

读使能信号

i2c_start

1Bit

Output

单字节数据读/写开始信号

byte_addr

16Bit

Output

读/写数据存储地址

po_data

8Bit

Output

数码管待显示数据

由图表可知,AD/DA转换模块包括8路输入输出信号,其中输入信号4路、输出信号4路。

输入信号中,有1路时钟信号和1路复位信号,sys_clk为系统时钟信号,由I2C驱动模块生成并传入,是存储地址、读/写数据以及使能信号的同步时钟,因为I2C驱动模块的工作时钟为i2c_clk时钟信号,两模块工作时钟相同,不会出现时钟不同引起时序问题;复位信号sys_rst_n,低电平有效,不必多说; i2c_end为单字节数据读/写接数信号,由I2C驱动模块产生并传入,告知数据生成模块单字节数据读/写操作完成。若连续读/写多字节数据,此信号可作为存储地址、写数据的更新标志;rd_data为I2C驱动模块传入的数据信号,表示由转换芯片读出的字节数据。

输出信号中, rd_en为读使能信号,生成后传入I2C驱动模块,作为I2C驱动模块读操作的判断标志;i2c_start是单字节数据读开始信号,作为I2C驱动模块单字节写操作开始的标志信号;byte_addr为读写数据存储地址;po_data为自转换芯片读出的字节数据换算后的模拟电压值,要发送到数码换 显示模块在数码管显示出来。

波形图绘制

在模块框图小节,对模块框图以及各输入输出信号做了简单介绍,接下来我们通过波形图的绘制,对模块各信号波形的设计与实现做详细说明。使用已知输入信号实现模块功能,并输出有效信号。AD转换模块整体波形图,具体见图 48‑13。

I2CAD014

图 48‑13 AD转换模块整体波形图

接下来,我们对各信号的设计与实现进行详细讲解,对于模块的的输入信号,在模块框图小节已经做了详细说明,此处不再赘述。

要实现模拟量到数字量转换,使用状态机较为方便。结合实验目标和数据量的转换顺序,我们声明三个状态变量:初始状态(IDLE)、数字量开始读取状态(AD_START)、数字量读入状态(AD_CMD)。

初始状态(IDLE):系统上电或数字量读取完成后,状态机处于初始状态,状态机在初始状态要等待一段时间,才能进行状态跳转。等待时间的长短由声明的计数器cnt_wait控制,cnt_wait计数初值为0,在初始状态下进行0 – MAX的循环计数,每个时钟周期自加1,其他状态保持初值0;当计数到最大值MAX,计数器归0,状态机由初始状态(IDLE)向数模转换开始状态(DA_START)跳转。要注意的是,计数器控制的等待时间不宜过短或过长,它影响着模拟电压值的保持时间。初始状态涉及的相关信号波形如下。

I2CAD015

图 48‑14 初始状态相关信号波形

数字量开始读取状态(AD_START):在本本状态中,开始信号i2c_start再次拉高一个时钟周期,准备读取模数转换后的数字量。状态机在此状态保持一个时钟周期,随即跳转到下一状态。

数字量读入状态(AD_CMD):在本状态中,读使能信号rd_en拉高,读使能信号与地址byte_addr传入I2C驱动模块,进行数据数据读取;数据读取完成后,I2C驱动模块回传结束信号i2c_end,状态机跳回初始初始状态,开始下一轮数据转换;读取的数据为模数转换后的数字量,赋值给变量ad_data ,通过对ad_data计算得到模拟电压值data_reg,将模拟电压值data_reg赋值给输出数据端口po_data,传入数码管显示模块用于显示。数字量读入状态各信号波形如下。

I2CAD016

图 48‑15 数字量读入状态相关信号波形

代码编写

AD转换模块各信号波形介绍完毕,波形图绘制完成,参照波形图开始参考代码的编写。模块参考代码,具体见代码清单 48‑1。

代码清单 48‑1 AD转换模块参考代码(pcf8591_ad.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
 module pcf8591_ad
 (
 input wire sys_clk , //输入系统时钟,50MHz
 input wire sys_rst_n , //输入复位信号,低电平有效
 input wire i2c_end , //i2c设备一次读/写操作完成
 input wire [7:0] rd_data , //输出i2c设备读取数据

 output reg rd_en , //输入i2c设备读使能信号
 output reg i2c_start , //输入i2c设备触发信号
 output reg [15:0] byte_addr , //输入i2c设备字节地址
 output wire [19:0] po_data //数码管待显示数据

 );

 ////
 //\* Parameter and Internal Signal \//
 ////
 //parameter define
 parameter CTRL_DATA = 8'b0100_0000; //AD/DA控制字
 parameter CNT_WAIT_MAX= 18'd6_9999 ; //采样间隔计数最大值
 parameter IDLE = 3'b001,
 AD_START = 3'b010,
 AD_CMD = 3'b100;

 //wire define
 wire [31:0] data_reg/\* synthesis keep \*/; //数码管待显示数据缓存

 //reg define
 reg [17:0] cnt_wait; //采样间隔计数器
 reg [4:0] state ; //状态机状态变量
 reg [7:0] ad_data ; //AD数据

 ////
 //\* Main Code \//
 ////
 //cnt_wait:采样间隔计数器
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt_wait <= 18'd0;
 else if(state == IDLE)
 if(cnt_wait == CNT_WAIT_MAX)
 cnt_wait <= 18'd0;
 else
 cnt_wait <= cnt_wait + 18'd1;
 else
 cnt_wait <= 18'd0;

 //state:状态机状态变量
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 state <= IDLE;
 else
 case(state)
 IDLE:
 if(cnt_wait == CNT_WAIT_MAX)
 state <= AD_START;
 else
 state <= IDLE;
 AD_START:
 state <= AD_CMD;
 AD_CMD:
 if(i2c_end == 1'b1)
 state <= IDLE;
 else
 state <= AD_CMD;
 default:state <= IDLE;
 endcase

 //i2c_start:输入i2c设备触发信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 i2c_start <= 1'b0;
 else if(state == AD_START)
 i2c_start <= 1'b1;
 else
 i2c_start <= 1'b0;

 //rd_en:输入i2c设备读使能信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 rd_en <= 1'b0;
 else if(state == AD_CMD)
 rd_en <= 1'b1;
 else
 rd_en <= 1'b0;

 //byte_addr:输入i2c设备字节地址
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 byte_addr <= 16'b0;
 else
 byte_addr <= CTRL_DATA;

 //ad_data:AD数据
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 ad_data <= 8'b0;
 else if(i2c_end == 1'b1) //(state == AD_CMD) && (i2c_end == 1'b1))
 ad_data <= rd_data;

 //data_reg:数码管待显示数据缓存
 assign data_reg = ((ad_data \* 3300) >> 4'd8);

 //po_data:数码管待显示数据
 assign po_data = data_reg[19:0];

 endmodule

数码管显示模块

数码管显示模块的相关内容在前面章节已经做了详细介绍,读者若有遗忘可回顾查阅,此处不再过多叙述。

顶层模块

顶层模块内部实例化实验工程的各子功能模块,连接个模块对应信号;对外接收外部传入的时钟、复位信号,发送串行时钟scl和串行数据sda给PCF8591转换芯片,发送给数码管,顶层模块框图如下。

I2CAD017

图 48‑16 顶层模块框图

顶层模块内容并不复杂,就是个子功能模块实例化,无需过多解释,无需波形图绘制,直接编写参考代码。顶层模块参考代码如下。

代码清单 48‑2 顶层模块参考代码(ad.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
 module ad
 (
 input wire sys_clk , //输入系统时钟,50MHz
 input wire sys_rst_n , //输入复位信号,低电平有效

 output wire i2c_scl , //i2c设备的串行时钟信号scl
 inout wire i2c_sda , //i2c设备的串行数据信号sda
 output wire stcp , //输出数据存储寄时钟
 output wire shcp , //移位寄存器的时钟输入
 output wire ds , //串行数据输入
 output wire oe //使能信号
 );

 ////
 //\* Parameter and Internal Signal \//
 ////
 //parameter define
 parameter DEVICE_ADDR = 7'h48 ; //i2c设备地址
 parameter SYS_CLK_FREQ = 26'd50_000_000 ; //输入系统时钟频率
 parameter SCL_FREQ = 18'd250_000 ; //i2c设备scl时钟频率

 //wire define
 wire i2c_clk ; //i2c驱动时钟
 wire i2c_start ; //i2c触发信号
 wire [15:0] byte_addr ; //i2c字节地址
 wire i2c_end ; //i2c一次读/写操作完成
 wire [ 7:0] rd_data ; //i2c设备读取数据
 wire [19:0] data ; //数码管待显示数据
 wire rd_en ; //读使能信号

 ////
 //\* Instantiate \//
 ////
 //------------- pcf8591_adda_inst -------------
 pcf8591_ad pcf8591_ad_inst
 (
 .sys_clk (i2c_clk ), //输入系统时钟,50MHz
 .sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
 .i2c_end (i2c_end ), //i2c设备一次读/写操作完成
 .rd_data (rd_data ), //输出i2c设备读取数据

 .rd_en (rd_en ), //输入i2c设备读使能信号
 .i2c_start (i2c_start ), //输入i2c设备触发信号
 .byte_addr (byte_addr ), //输入i2c设备字节地址
 .po_data (data ) //数码管待显示数据
 );

 //------------- i2c_ctrl_inst -------------
 i2c_ctrl
 #(
 .DEVICE_ADDR (DEVICE_ADDR ), //i2c设备器件地址
 .SYS_CLK_FREQ (SYS_CLK_FREQ ), //i2c_ctrl模块系统时钟频率
 .SCL_FREQ (SCL_FREQ ) //i2c的SCL时钟频率
 )
 i2c_ctrl_inst
 (
 .sys_clk (sys_clk ), //输入系统时钟,50MHz
 .sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
 .wr_en ( ), //输入写使能信号
 .rd_en (rd_en ), //输入读使能信号
 .i2c_start (i2c_start ), //输入i2c触发信号
 .addr_num (1'b0 ), //输入i2c字节地址字节数
 .byte_addr (byte_addr ), //输入i2c字节地址
 .wr_data ( ), //输入i2c设备数据

 .rd_data (rd_data ), //输出i2c设备读取数据
 .i2c_end (i2c_end ), //i2c一次读/写操作完成
 .i2c_clk (i2c_clk ), //i2c驱动时钟
 .i2c_scl (i2c_scl ), //输出至i2c设备的串行时钟信号scl
 .i2c_sda (i2c_sda ) //输出至i2c设备的串行数据信号sda
 );

 //------------- seg_595_dynamic_inst -------------
 seg_595_dynamic seg_595_dynamic_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低有效
 .data (data ), //数码管要显示的值
 .point (6'b001000 ), //小数点显示,高电平有效
 .seg_en (1'b1 ), //数码管使能信号,高电平有效
 .sign (1'b0 ), //符号位,高电平显示负号

 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ), //串行数据输入
 .oe (oe ) //使能信号
 );

 endmodule

RTL视图

至此实验工程基本完成,在Quartus中对代码进行编译,编译若有错误,请读者根据错误提示信息作出更改,直至编译通过,编译通过后查看RTL视图,与顶层模块框图对比,两者一致,各信号连接正确。RTL视图,具体见图 48‑17。

I2CAD018

图 48‑17 RTL视图

SignalTap信号抓取

我们直接对AD转换模块的部分信号使用SignalTap进行信号抓取,各信号抓取波形图如图 48‑18、图 48‑19所示。抓取的各信号波形与绘制波形图相同,验证通过。

I2CAD019

图 48‑18 AD转换模块SignalTap波形图(一)

I2CAD020

图 48‑19 AD转换模块SignalTap波形图(二)

19.4.1.3. 上板验证

引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 47‑4所示。

表格 48‑4 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

输入系统时钟

sys_rst_n

Input

M15

复位信号

i2c_scl

Output

P15

i2c串行时钟

i2c_sda

Inout

N14

i2c数据信号

shcp

Output

B1

移位寄存器时钟

stcp

Output

K9

数据存储器时钟

ds

Output

R1

串行数据输入

oe

Output

L11

使能信号

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下所示。

I2CAD021

图 48‑20 管脚分配

结果验证

如图 48‑21所示,开发板连接12V直流电源和USB-Blaster下载器JTAG端口。线路正确连接后,打开开关为板卡上电。

I2CAD022

图 48‑21 程序下载连线图

如图 48‑22所示,使用“Programmer”为开发板下载程序。

I2CAD023

图 48‑22 程序下载图

如图 48‑23、图 48‑24、图 48‑25所示,程序下载完毕后,调节电位器控制输入板载AD/DA芯片的电压幅值,数码管会显示出当前输入AD/DA芯片电压值。

I2CAD024

图 48‑23 板载AD上板验证(一)

I2CAD025

图 48‑24 板载AD上板验证(二)

I2CAD026

图 48‑25 板载AD上板验证(三)

19.5. 实战演练二

19.5.1. 实验目标-1

设计并编写实验工程,控制AD/DA芯片PCF8591作为输出电源,可通过按键控制输出电压幅值。

19.5.1.1. 硬件资源

参见本章节“实战演练一”中“硬件资源”小节。

19.5.1.2. 程序设计

整体说明

在本小节,我们先要对整个实验工程有一个整体认识,首先来看一下DA实验工程整体框图,具体见。

I2CAD027

图 48‑26 DA实验工程整体框图

由上图可知,本实验工程包括4个模块,各模块简介,具体见表格 48‑2。

表格 48‑5 模块功能简介

模块名称

功能描述

pcf8591_da

DA转换模块,实现数模转换

i2c_ctrl

I2C驱动模块

seg_595_dynamic

数码管显示模块

da

DA实验工程顶层模块

系统上电后,FPGA通过I2C驱动模块(i2c_ctrl)向PCF8951写入欲输出电压幅值的数字量;数字量的产生与变化受AD转换模块(pcf8591_ad)和外部输入按键信号控制;模块cross_clk作用是跨时钟域处理;PCF8591芯片将数字量转换为模拟电压信号输出;输出电压幅值通过数码管显示 。

按键消抖模块

按键消抖模块的相关内容在前面章节已经做了详细介绍,读者若有遗忘可回顾查阅,此处不再过多叙述。

I2C驱动模块

I2C驱动模块的相关内容在前面章节已经做了详细介绍,读者若有遗忘可回顾查阅,此处不再过多叙述。

DA转换模块

模块框图

我们开始DA转换模块的讲解。DA转换模块的主要功能是实现数字量与模拟量之间的转换。/DA转换模块框图及模块输入输出端口简介,具体见、。

I2CAD028

图 48‑27 DA转换模块框图

表格 48‑6 AD/DA转换模块输入输出信号简介

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,I2C驱动模块产生并传入

sys_rst_n

1Bit

Input

复位信号,低有效

i2c_end

1Bit

Input

i2c设备一次读/写操作完成

add

1Bit

Input

加电压按键

sub

1Bit

Input

减电压按键

v_2_5

1Bit

Input

输出2.5V电压按键

v_3_3

1Bit

Input

输出3.3V电压按键

wr_en

1Bit

Output

写使能信号

i2c_start

1Bit

Output

单字节数据读/写开始信号

byte_addr

16Bit

Output

读/写数据存储地址

wr_data

8Bit

Output

待写入转换芯片数据

po_data

8Bit

Output

数码管待显示数据

由图表可知,AD/DA转换模块包括12路输入输出信号,其中输入信号7路、输出信号5路。

输入信号中,有1路时钟信号和1路复位信号,sys_clk为系统时钟信号,由I2C驱动模块生成并传入,是存储地址、读/写数据以及使能信号的同步时钟,因为I2C驱动模块的工作时钟为i2c_clk时钟信号,两模块工作时钟相同,不会出现时钟不同引起时序问题;复位信号sys_rst_n,低电平有效,不必多说; i2c_end为单字节数据读/写接数信号,由I2C驱动模块产生并传入,告知数据生成模块单字节数据读/写操作完成。若连续读/写多字节数据,此信号可作为存储地址、写数据的更新标志;输入的4路按键信号可控制输出电压幅值增加、减少以及固定2.5V、3.3V电压输出。

输出信号中, wr_en为写使能信号,生成后传入I2C驱动模块,作为I2C驱动模块写操作的判断标志;i2c_start是单字节数据写开始信号,作为I2C驱动模块单字节写操作开始的标志信号;byte_addr为写数据存储地址;wr_data为待写入EEPROM的字节数据;po_data为自转换芯片读出 的字节数据换算后的模拟电压值,要发送到数码换显示模块在数码管显示出来。

波形图绘制

在模块框图小节,对模块框图以及各输入输出信号做了简单介绍,接下来我们通过波形图的绘制,对模块各信号波形的设计与实现做详细说明。使用已知输入信号实现模块功能,并输出有效信号。DA转换模块整体波形图,具体见。

I2CAD029

图 48‑28 DA转换模块波形图

接下来,我们对各信号的设计与实现进行详细讲解,对于模块的的输入信号,在模块框图小节已经做了详细说明,此处不再赘述。

由实验目标可知,本实验要实现数字量到模拟量转换,输入DA转换芯片的数字量是由模块内部自己产生,受外部输入按键的控制,模拟产生的数字量da_data信号波形如下图所示。

I2CAD030

图 48‑29 da_data信号波形图

按键add控制输出数字量增加、按键sub控制输出数字量减少,增加或减少幅度可通过修改代码中相关参数进行调节;按键v_2_5可直接输出2.5V电压数字量、按键v_3_3可直接输出3.3V电压数字量;输出电压幅值范围为0-3.3V,对应数字量0-255.

要实现模拟量到数字量转换,使用状态机较为方便。结合实验目标和数据量的转换顺序,我们声明三个状态变量:初始状态(IDLE)、数模转换开始状态(DA_START)、数字量写入状态(DA_CMD)。

初始状态(IDLE):系统上电或数字量读取完成后,状态机处于初始状态,状态机在初始状态要等待一段时间,才能进行状态跳转。等待时间的长短由声明的计数器cnt_wait控制,cnt_wait计数初值为0,在初始状态下进行0 – MAX的循环计数,每个时钟周期自加1,其他状态保持初值0;当计数到最大值MAX,计数器归0,状态机由初始状态(IDLE)向数模转换开始状态(DA_START)跳转。要注意的是,计数器控制的等待时间不宜过短或过长,它影响着模拟电压值的保持时间。初始状态涉及的相关信号波形如下。

I2CAD031

图 48‑30 初始状态相关信号波形

数模转换开始状态(DA_START):在这个状态下,我们要将数字量da_data赋值给写数据端口wr_data;并拉高i2c_start,准备将待转换数字量写入转换芯片。数模转换开始状态相关信号波形如下。

I2CAD032

图 48‑31 数模转换开始状态相关信号波形

数字量写入状态(DA_CMD):状态机在数模转换开始状态保持一个时钟周期跳转到数字量写入状态。在本状态中,写使能信号wr_en拉高,写使能信号与写地址byte_addr、写数据wr_data传入I2C驱动模块;通过对da_data计算得到模拟电压值data_reg,将模拟电压值data_reg赋值给 输出数据端口po_data,传入数码管显示模块用于显示;数据写入完成后,I2C驱动模块回传结束信号i2c_end,状态机跳转到初始状态。数字量写入状态各信号波形如下。

I2CAD033

图 48‑32 数字量写入状态相关信号波形

代码编写

DA转换模块各信号波形介绍完毕,波形图绘制完成,参照波形图开始参考代码的编写。模块参考代码,具体见。

代码清单 48‑3 DA转换模块参考代码(pcf8591_da.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
module pcf8591_da
(
input wire sys_clk , //输入系统时钟,50MHz
input wire sys_rst_n , //输入复位信号,低电平有效
input wire add , //电压加按键
input wire sub , //电压减按键
input wire v_2_5 , //输出电压2.5V按键
input wire v_3_3 , //输出电压3.3V按键
input wire i2c_end , //i2c设备一次读/写操作完成

output reg wr_en , //输入i2c设备写使能信号
output reg i2c_start , //输入i2c设备触发信号
output reg [15:0] byte_addr , //输入i2c设备字节地址
output reg [7:0] wr_data , //输入i2c设备数据
output wire [19:0] po_data //数码管待显示数据
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter CTRL_DATA = 8'b0100_0000 ; //AD/DA控制字
parameter CNT_WAIT_MAX= 18'd6_9999 ; //采样间隔计数最大值
parameter CNT_DATA_MAX= 24'd15_000_00 ; //DA数据切换间隔计数最大值
parameter IDLE = 3'b001 ,
DA_START = 3'b010 ,
DA_CMD = 3'b100 ;

//wire define
wire [31:0] data_reg; //数码管待显示数据缓存

//reg define
reg [8:0] da_data ; //DA数据
reg [17:0] cnt_wait; //采样间隔计数器
reg [2:0] state ; //状态机状态变量

////
//\* Main Code \//
////
//da_data:DA数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
da_data <= 9'd0;
else if(da_data > 8'd255)
da_data <= 9'd0;
else if(add == 1'b1)
da_data <= da_data + 8'd5;
else if(sub == 1'b1)
da_data <= da_data - 8'd5;
else if(v_2_5 == 1'b1)
da_data <= 9'd194;
else if(v_3_3 == 1'b1)
da_data <= 9'd255;
else
da_data <= da_data;

//cnt_wait:采样间隔计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 18'd0;
else if(state == IDLE)
if(cnt_wait == CNT_WAIT_MAX)
cnt_wait <= 18'd0;
else
cnt_wait <= cnt_wait + 18'd1;
else
cnt_wait <= 18'd0;

//state:状态机状态变量
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE:
if(cnt_wait == CNT_WAIT_MAX)
state <= DA_START;
else
state <= IDLE;
DA_START:
state <= DA_CMD;
DA_CMD:
if(i2c_end == 1'b1)
state <= IDLE;
else
state <= DA_CMD;
default:state <= IDLE;
endcase

//i2c_start:输入i2c设备触发信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i2c_start <= 1'b0;
else if(state == DA_START)
i2c_start <= 1'b1;
else
i2c_start <= 1'b0;

//wr_en:输入i2c设备写使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en <= 1'b0;
else if(state == DA_CMD)
wr_en <= 1'b1;
else
wr_en <= 1'b0;

//byte_addr:输入i2c设备字节地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
byte_addr <= 16'b0;
else
byte_addr <= CTRL_DATA;

//wr_data:输入i2c设备数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_data <= 8'b0;
else if(state == DA_START)
wr_data <= da_data;
else
wr_data <= wr_data;

//data_reg:数码管待显示数据缓存
assign data_reg = ((da_data \* 3300) >> 4'd8);

//po_data:数码管待显示数据
assign po_data = data_reg[19:0];

endmodule

跨时钟域模块

模块框图

按键消抖模块使用的是50MHz系统时钟sys_clk,而传入DA转换模块的工资时钟为i2c_clk,两者时钟频率不同,为保证DA转换模块能正常接收到消抖后的按键信号,我们编写了跨时钟域模块,对输入的按键信号进行跨时钟域处理。跨时钟域处理模块框图设计和模块输入输出信号的功能描述,具体见。

I2CAD034

图 48‑33 跨时钟域处理模块框图

表格 48‑7 输入输出信号功能描述

信号

位宽

类型

功能描述

clk_a

1Bit

Input

时钟A

clk_b

1Bit

Input

时钟B

sys_rst_n

1Bit

Input

复位信号,低电平有效

en_a

1bit

Input

时钟A同步下的使能信号

en_b

1Bit

Output

跨时钟域处理后的时钟B同步下的使能信号

波形图绘制

在模块框图小节,我们已经对以太网数据发送模块的模块功能和输入输出端口做了详细介绍。接下来我们通过波形图的绘制,为读者说明各信号的设计与实现方法,以及模块的功能实现。跨时钟域处理模块整体波形图,具体见。

I2CAD035

图 48‑34 跨时钟域处理模块整体波形图

跨时钟域处理模块功能实现的设计思路是,在时钟信号clk_a同步下,延长使能信号en_a的保持时间,然后在时钟信号clk_b同步下对en_a的延长信号进行打拍处理,使其同步到eth_tx_clk时钟信号下,采集打拍处理后信号的下降沿作为使能信号en_b。

按照设计思路,声明信号en_a_valid和计数器cnt;在数据接收时钟信号clk_a同步下,对en_a信号进行延时处理,延长时钟周期个数由计数器cnt控制;当采集到en_a信号有效时,拉高信号en_a_valid;en_a_valid为高有效时,cnt来时计数,当计数器cnt计数到设定值MAX时, 信号en_a_valid拉低,MAX的设定值范围为N < MAX < 2N(N = fclk_a/fclk_b)。

延长后的使能信号en_a_valid可以被时钟信号clk_b正确采集,声明信号en_a_valid_r对en_a_valid进行打拍,在clk_b时钟信号下,当en_a_valid为低电平、en_a_valid_r为高电平时,输出使能信号en_b保持一个时钟周期高电平,其他时刻为低电平。

代码编写

以上述绘制波形图为参照,编写模块参考代码。模块参考代码具体见。

代码清单 48‑4 跨时钟域处理模块参考代码(cross_clk.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
module cross_clk
(
input wire clk_a , //时钟a
input wire clk_b , //时钟b
input wire sys_rst_n , //复位信号
input wire en_a , //a使能信号

output reg en_b //b使能信号
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter CNT_MAX = 16'd50; //计数器最大值

//reg define
reg [15:0] cnt ; //计数器
reg en_a_valid ; //a使能有效信号
reg en_a_valid_r; //a使能有效信号寄存

////
//\* Main Code \//
////
//en_a_valid:a使能有效信号
always@(posedge clk_a or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
en_a_valid <= 1'b0;
else if(cnt == CNT_MAX)
en_a_valid <= 1'b0;
else if(en_a == 1'b1)
en_a_valid <= 1'b1;
else
en_a_valid <= en_a_valid;

//cnt:计数器
always@(posedge clk_a or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 16'b0;
else if(cnt == CNT_MAX)
cnt <= 16'b0;
else if(en_a_valid == 1'b1)
cnt <= cnt + 1'b1;

//en_a_valid_r:a使能有效信号寄存
always@(posedge clk_b or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
en_a_valid_r <= 1'b0;
else
en_a_valid_r <= en_a_valid;

//en_b:b使能信号
always@(posedge clk_b or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
en_b <= 1'b0;
else if((en_a_valid == 1'b0) && (en_a_valid_r == 1'b1))
en_b <= 1'b1;
else
en_b <= 1'b0;

endmodule

数码管显示模块

数码管显示模块的相关内容在前面章节已经做了详细介绍,读者若有遗忘可回顾查阅,此处不再过多叙述。

顶层模块

顶层模块内部实例化实验工程的各子功能模块,连接个模块对应信号;对外接收外部传入的时钟、复位信号,发送串行时钟scl和串行数据sda给PCF8591转换芯片,发送片选和段选给数码管。顶层模块框图如下。

I2CAD036

图 48‑35 顶层模块框图

顶层模块内容并不复杂,就是个子功能模块实例化,无需过多解释,无需波形图绘制,直接编写参考代码。顶层模块参考代码如下。

代码清单 48‑5 顶层模块参考代码(da.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
 module da
 (
 input wire sys_clk , //输入系统时钟,50MHz
 input wire sys_rst_n , //输入复位信号,低电平有效
 input wire key_add , //电压加按键
 input wire key_sub , //电压减按键
 input wire key_2_5 , //输出电压2.5V按键
 input wire key_3_3 , //输出电压3.3V按键

 output wire i2c_scl , //i2c设备的串行时钟信号scl
 inout wire i2c_sda , //i2c设备的串行数据信号sda
 output wire stcp , //输出数据存储寄时钟
 output wire shcp , //移位寄存器的时钟输入
 output wire ds , //串行数据输入
 output wire oe //使能信号
 );

 ////
 //\* Parameter and Internal Signal \//
 ////
 //parameter define
 parameter DEVICE_ADDR = 7'h48 ; //i2c设备地址
 parameter SYS_CLK_FREQ = 26'd50_000_000 ; //输入系统时钟频率
 parameter SCL_FREQ = 18'd250_000 ; //i2c设备scl时钟频率
 parameter CNT_MAX = 20'd999_999 ; //计数器计数最大值

 //wire define
 wire i2c_clk ; //i2c驱动时钟
 wire i2c_start ; //i2c触发信号
 wire [15:0] byte_addr ; //i2c字节地址
 wire [ 7:0] wr_data ; //i2c设备数据
 wire i2c_end ; //i2c一次读/写操作完成
 wire [19:0] data ; //数码管待显示数据
 wire wr_en ; //写使能信号
 wire add ; //电压加按键
 wire add_valid ; //电压加按键有效
 wire sub ; //电压减按键
 wire sub_valid ; //电压减按键有效
 wire v_2_5 ; //电压2.5V按键
 wire v_2_5_valid ; //电压2.5V按键有效
 wire v_3_3 ; //电压3.3V按键
 wire v_3_3_valid ; //电压3.3V按键有效

 ////
 //\* Instantiation \//
 ////
 //------------- key_fifter_inst --------------
 key_filter
 #(
 .CNT_MAX (CNT_MAX ) //计数器计数最大值
 )
 key_add_inst
 (
 .sys_clk (sys_clk ) , //系统时钟50MHz
 .sys_rst_n (sys_rst_n) , //全局复位
 .key_in (key_add ) , //按键输入信号

 .key_flag (add ) //按键消抖后标志信号
 );

 //------------- cross_clk_add_inst -------------
 cross_clk cross_clk_add_inst
 (
 .clk_a (sys_clk ), //时钟a
 .clk_b (i2c_clk ), //时钟b
 .sys_rst_n (sys_rst_n ), //复位信号
 .en_a (add ), //a使能信号

 .en_b (add_valid ) //b使能信号
 );

 //------------- key_sub_inst -------------
 key_filter
 #(
 .CNT_MAX (CNT_MAX ) //计数器计数最大值
 )
 key_sub_inst
 (
 .sys_clk (sys_clk ), //系统时钟50MHz
 .sys_rst_n (sys_rst_n ), //全局复位
 .key_in (key_sub ), //按键输入信号

 .key_flag (sub ) //key_flag为1时表示按键有效,0表示按键无效
 );

 //------------- cross_clk_sub_inst -------------
 cross_clk cross_clk_sub_inst
 (
 .clk_a (sys_clk ), //时钟a
 .clk_b (i2c_clk ), //时钟b
 .sys_rst_n (sys_rst_n ), //复位信号
 .en_a (sub ), //a使能信号

 .en_b (sub_valid ) //b使能信号
 );

 //------------- key_2_5_inst -------------
 key_filter
 #(
 .CNT_MAX (CNT_MAX ) //计数器计数最大值
 )
 key_2_5_inst
 (
 .sys_clk (sys_clk ), //系统时钟50MHz
 .sys_rst_n (sys_rst_n ), //全局复位
 .key_in (key_2_5 ), //按键输入信号

 .key_flag (v_2_5 ) //key_flag为1时表示按键有效,0表示按键无效
 );

 //------------- cross_clk_2_5_inst -------------
 cross_clk cross_clk_2_5_inst
 (
 .clk_a (sys_clk ), //时钟a
 .clk_b (i2c_clk ), //时钟b
 .sys_rst_n (sys_rst_n ), //复位信号
 .en_a (v_2_5 ), //a使能信号

 .en_b (v_2_5_valid) //b使能信号
 );

 //------------- key_3_3_inst -------------
 key_filter
 #(
 .CNT_MAX (CNT_MAX ) //计数器计数最大值
 )
 key_3_3_inst
 (
 .sys_clk (sys_clk ), //系统时钟50MHz
 .sys_rst_n (sys_rst_n ), //全局复位
 .key_in (key_3_3 ), //按键输入信号

 .key_flag (v_3_3 ) //key_flag为1时表示按键有效,0表示按键无效
 );

 //------------- cross_clk_3_3_inst -------------
 cross_clk cross_clk_3_3_inst
 (
 .clk_a (sys_clk ), //时钟a
 .clk_b (i2c_clk ), //时钟b
 .sys_rst_n (sys_rst_n ), //复位信号
 .en_a (v_3_3 ), //a使能信号

 .en_b (v_3_3_valid) //b使能信号
 );

 //------------- pcf8591_adda_inst -------------
 pcf8591_da pcf8591_da_inst
 (
 .sys_clk (i2c_clk ), //输入系统时钟
 .sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
 .add (add_valid ), //电压加按键有效
 .sub (sub_valid ), //电压减按键有效
 .v_2_5 (v_2_5_valid ), //电压2.5V按键有效
 .v_3_3 (v_3_3_valid ), //电压3.3V按键有效
 .i2c_end (i2c_end ), //i2c设备一次读/写操作完成

 .wr_en (wr_en ), //输入i2c设备写使能信号
 .i2c_start (i2c_start ), //输入i2c设备触发信号
 .byte_addr (byte_addr ), //输入i2c设备字节地址
 .wr_data (wr_data ), //输入i2c设备数据
 .po_data (data ) //数码管待显示数据
 );

 //------------- i2c_ctrl_inst -------------
 i2c_ctrl
 #(
 .DEVICE_ADDR (DEVICE_ADDR ), //i2c设备器件地址
 .SYS_CLK_FREQ (SYS_CLK_FREQ ), //i2c_ctrl模块系统时钟频率
 .SCL_FREQ (SCL_FREQ ) //i2c的SCL时钟频率
 )
 i2c_ctrl_inst
 (
 .sys_clk (sys_clk ), //输入系统时钟,50MHz
 .sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
 .wr_en (wr_en ), //输入写使能信号
 .rd_en ( ), //输入读使能信号
 .i2c_start (i2c_start ), //输入i2c触发信号
 .addr_num (1'b0 ), //输入i2c字节地址字节数
 .byte_addr (byte_addr ), //输入i2c字节地址
 .wr_data (wr_data ), //输入i2c设备数据

 .rd_data ( ), //输出i2c设备读取数据
 .i2c_end (i2c_end ), //i2c一次读/写操作完成
 .i2c_clk (i2c_clk ), //i2c驱动时钟
 .i2c_scl (i2c_scl ), //输出至i2c设备的串行时钟信号scl
 .i2c_sda (i2c_sda ) //输出至i2c设备的串行数据信号sda
 );

 //------------- seg_595_dynamic_inst -------------
 seg_595_dynamic seg_595_dynamic_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低有效
 .data (data ), //数码管要显示的值
 .point (6'b001000 ), //小数点显示,高电平有效
 .seg_en (1'b1 ), //数码管使能信号,高电平有效
 .sign (1'b0 ), //符号位,高电平显示负号

 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ), //串行数据输入
 .oe (oe ) //使能信号
 );

 endmodule

RTL视图

至此实验工程基本完成,在Quartus中对代码进行编译,编译若有错误,请读者根据错误提示信息作出更改,直至编译通过,编译通过后查看RTL视图,与顶层模块框图对比,两者一致,各信号连接正确。RTL视图,具体见。

I2CAD037

图 48‑36 RTL视图

SignalTap信号抓取

我们直接对DA转换模块和跨时钟域处理模块的部分信号使用SignalTap进行信号抓取,各信号抓取波形图如、所示。抓取的各信号波形与绘制波形图相同,验证通过。

I2CAD038

图 48‑37 DA转换模块SignalTap波形图

I2CAD039

图 48‑38 跨时钟域处理模块SignalTap波形图

19.5.1.3. 上板验证

引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如所示。

表格 48‑8 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

输入系统时钟

sys_rst_n

Input

M15

复位信号

key_add

Input

M2

加电压按键

key_sub

Input

M1

减电压按键

key_2_5

Input

E15

2.5V电压输出按键

key_3_3

Input

E16

3.3V电压输出按键

i2c_scl

Output

P15

i2c串行时钟

i2c_sda

Inout

N14

i2c数据信号

shcp

Output

B1

移位寄存器时钟

stcp

Output

K9

数据存储器时钟

ds

Output

R1

串行数据输入

oe

Output

L11

使能信号

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下所示。

I2CAD040

图 48‑39 管脚分配

结果验证

如图 48‑40所示,开发板连接12V直流电源和USB-Blaster下载器JTAG端口。线路正确连接后,打开开关为板卡上电。

I2CAD022

图 48‑40 程序下载连线图

如所示,使用“Programmer”为开发板下载程序。

I2CAD041

图 48‑41 程序下载图

程序下载完成后,DA芯片初始输出电压为0V,按下KEY3或KEY4可直接输出2.5V或3.3V电压;KEY1、KEY2控制输出点幅值的增加或减少;输出电压幅值通过数码管实时显示,如图 48‑42、图 48‑43、图 48‑44所示。

I2CAD042

图 48‑42 板载DA上板验证(一)

I2CAD043

图 48‑43 板载DA上板验证(二)

I2CAD044

图 48‑44 板载DA上板验证(三)

19.6. 章末总结

本章节我们为读者详细介绍了板载AD/DA芯片PCF8591的使用方法,进一步巩固了I2C接口的相关知识,读者务必理解掌握,融会贯通。

19.7. 拓展训练

修改工程,使用其他单通道或多通道进行输入电压的测量。