20. 环境光传感器

AP3216C是敦南科技推出的三合一整合型光感测器,它能够对环境光强度和距离进行测量。因其封装小、功耗低、灵敏度高、控制简单而广泛应用于移动电话,笔记本,电容式触摸屏等领域。这方面最典型的应用是在手机上,使用者不想让他们的脸颊“按下按钮”或者在把电话凑近耳朵时碰断通话,如果电话能够在它接近耳朵时关闭 触摸屏的话就很方便,这正是接近传感器的作用。

本章节将为大家介绍如何使用FPGA开发板上的AP3216C器件实现对环境光照强度和距离的测量。

20.1. 理论学习

20.1.1. ap3216c简介

AP3216C是一个集成的ALS & PS模块,它包括一个数字环境光传感器(Ambilent Light Sensor,ALS)、一个接近传感器(Proximity Sensor,PS)和一个红外LED(Infrared Radiation LED,IR LED)。其中接近传感器有10位有效线性输出(0~1023),环境光传感器有16位有效线性输出(0~65535)。AP3216C采用的是i2c接口模式进行通信,内置温度补偿电路,可在−30℃至+80℃范围内工作。

AP3216C内部功能框图如图 49‑1所示。

AP3216002

图 49‑1 AP3216C功能框图

根据上图为大家描述一下AP3216C大致的工作模式,当有物体接近时,图中的红外发光二极管(IR_LED)发出的红外线碰撞到物体后反射到红外光电二极管(PS)上,红外光电二极管将接收到的光信号装换成电流信号,再通过其内部的模数转换器(ADC)将其转换成数字信号并存储在其内部的寄存器中。所以当物体靠的越 近时,反射的红外光就越强,转换后的数据就越大,即当物体靠近时,PS值增大。同理环境光检测也是如此,当可见光光电二极管接收到环境光强,将其转换成数字信号存储在其内部的寄存器中。

上面说到转换后的PS数据和ALS数据是存储在其内部的寄存器中的,那么我们就能通过读取寄存器的值来读出环境值了。

20.1.2. AP3216C寄存器简介

AP3216C内部有一些寄存器,每个寄存器的位宽为8bit,我们可通过只写寄存器设置AP3216C的工作模式、中断方式、数据参数等。这里仅为大家介绍本次实验所配置到的寄存器,其余寄存器,感兴趣的读者可以参考AP3216C数据手册进行进一步了解。

  • 系统配置寄存器

系统配置器用于启动/关闭设备,并选择设备的ALS/PS功能,其具体功能见表格 49‑1。

表格 49‑1 系统模式寄存器

地址 有效位 寄存器名称 功能 ==== ====== =========== ================= 0X0C 7:0 ALS数据低位 ALS低八位数据 0X0D 7:0 ALS数据高位 ALS高八位数据 0X0E 7 PS数据低位 0:物体在远离

1:物体在靠近 6 0:PS、IR数据有效

1:PS、IR数据无效 3:0 PS低四位数据 0X0F 7 PS数据高位 0:物体在远离

1:物体在靠近 6 0:PS、IR数据有效

1:PS、IR数据无效 5:0 PS高六位数据 ==== ====== =========== =================

根据以上的寄存器介绍,要使用AP3216C我们首先得对系统模式进行设置,激活ALS和PS+IR功能;再对数据寄存器里的数据进行读取。AP3216C是采用I2C总线协议与控制器进行通信。所以这里我们使用I2C协议对系统模式寄存器写入011来激活测量模式,激活后对ALS/PS数据寄存器进行读取即可。

AP3216C写寄存器时序如图 49‑3所示。

AP3216004

图 49‑3 AP3216C写寄存器时序

上图中,S:表示I2C起始信号;W:读写控制位(0表示写,1表示读);A:表示应答信号;P:表示I2C停止信号。

写时序过程为:先发送AP3216C的7为器件地址(7’b0011110)+写控制位(W=0);然后发送寄存器地址(0X00);最后发送写入寄存器的命令(0x03)。

AP3216C读寄存器时序如图 49‑4所示。

AP3216005

图 49‑4 AP3216C读寄存器时序

上图中,S:表示I2C起始信号;W、R:读写控制位(0表示写,1表示读);A:表示应答信号;N:表示不应答信号;P:表示I2C停止信号。

读时序过程为:先发送AP3216C的7位器件地址(7’b0011110)+写控制位(W=0);然后发送寄存器地址;再发送器件地址+读控制位(R=1);最后读取寄存器里的数据。

具体的I2C读写时序,详细可见前面《基于I2C协议的 EEPROM驱动控制》章节讲解。

20.2. 实战演练

20.2.1. 实验目标

实验目标:使用AP3216C传感器测量环境光强度和物体距离,并显示在数码管上,同时可使用按键切换显示光强度和距离值。

20.2.2. 硬件资源

本次实验我们需使用到开发板上的按键、数码管及板载环境光传感器,其中按键和数码管硬件资源在前面章节已有讲解,这里就不再过多描述。开发板上的环境光传感器如图 49‑6所示。

AP3216006

图 49‑5 环境光传感器

其原理图如图 49‑6所示,从该图中可以看到该传感器是通过I2C总线与FPGA芯片进行通信的。

AP3216007

图 49‑6 环境光传感器原理图

20.3. 程序设计

20.3.1. 整体说明

根据实验目标可知,要实现这个功能,首先数码管显示模块是必不可少的;同时要实现按键切换显示,需要一个按键消抖以及按键控制数据切换模块;因为AP3216C传感器是通过i2c总线与FPGA进行通信的,所以需要一个i2c驱动模块;最后我们还需产生一个AP3216C的控制模块才能读出数据。该工程的整体框图如图 49‑7所示。

AP3216008

图 49‑7 环境光传感实验整体框图

通过上图可以看到,该工程共分六个模块。各模块简介见表格 49‑2。

表格 49‑2 ap3216c工程模块简介

模块名称

功能描述

ap3216c_ctrl

ap3216c控制模块

i2c_ctrl

iic驱动模块

key_filter

按键消抖模块

key_ctrl

按键控制数据切换模块

seg_595_dynamic

数码管动态显示模块

ap3216c

工程顶层模块

通过模块框图为大家讲述该工程的大概工作流程:通过ap3216c_ctrl模块和i2c_ctrl模块控制AP3216C传感器进行测量环境光强及物体距离值并读取数据传到key_ctrl控制模块,同时按键消抖模块输入按键有效信号到key_ctrl模块去控制ALS数据以及PS数据的输出,再将输出传到seg_ 595_dynamic模块控制数码管将数据显示出来。

下面分模块为大家讲解。

20.3.1.1. 按键消抖模块

在前面按键消抖章节已经对按键消抖模块进行了详细的讲解,在此就不再进行说明,直接调用这个模块即可。

20.3.1.2. 数码管动态显示模块

在“数码管的动态显示”章节中我们已经对数码管动态显示模块做了详细的讲解,在此就不再讲解了,在这里我们直接调用这个模块即可。需要注意的是该模块下还有子模块我们没有在整体框图中画出,该模块框图如图 49‑8所示。

AP3216009

图 49‑8 数码管显示模块

20.3.1.3. i2c驱动模块

在《基于I2C协议的 EEPROM驱动控制》章节已经详细地讲解了i2c驱动方法,在此就不再叙述,直接调用这个模块即可。

20.3.1.4. ap3216c控制模块

该模块通过i2c总线对AP3216C进行功能设置以及数据读取。在理论部分说到通过对寄存器0x00来设置检测模式,因为我们需要测量环境光强以及物体距离,所以我们需向寄存器写入011来激活ALS和PS+IR功能。同时AP3216C转换环境数据是需要时间的,如图 49‑9所示。

AP3216010

图 49‑9 ALS和PS+IR模式转换时间

由上图可以看到PS和ALS的转换时间,而在实际中环境变化是比较灵敏的,如果转换过快,我们在数码管上显示的话就会因为数值变化过快而不便于观察。所以在这里我们稍微等待的时间长一点让显示的数据相对较稳定一些,在此我们将时间设置为PS(50ms),ALS(200ms)。

模块框图

AP3216011

图 49‑10 ap3216c控制模块

这个模块的作用就是配置AP3216C的功能模式,同时读取环境光强值与物体距离值。模块各个信号的简介,如表格 49‑3所示。

表格 49‑3 ap3216c控制模块输入输出信号描述

信号

位宽

类型

功能描述

i2c_clk

1bit

input

i2c驱动时钟,1MHz

sys_rst_n

1bit

input

复位信号,低有效

i2c_end

1bit

input

i2c一次读/写操作完成

rd_data

8bit

input

i2c设备读取的数据

wr_en

1bit

output

写数据使能信号

rd_en

1bit

output

读数据使能信号

i2c_start

1bit

output

i2c触发信号

byte_addr

16bit

output

输入i2c字节地址

wr_data

8bit

output

输入i2c设备数据

ps_data

10bit

output

输出距离

als_data

16bit

output

输出光感

根据上面的图 49‑7 环境光传感实验整体框图可以看到,该控制模块是与i2c驱动模块一起使用的,即该模块通过i2c配置来对AP3216C器件进行设置与读取,所以该模块的大部分输入输出信号与i2c驱动模块的输出输入是相连接的。如果大家对i2c驱动模块已经弄清楚了,那么对于该模块的输入输出信号就一目了然了。

通过对AP3216C的理论学习我们可以知道,该模块适合用状态机去控制AP3216C的设置及数据读取。

状态机

通过该模块的状态跳转图来进一步了解AP3216C的工作过程,如图 49‑11所示。

AP3216012

图 49‑11 AP3216C控制模块状态跳转图

表格 49‑4 ap3216c控制模块状态机状态描述

状态名称

状态描述

S_WAIT_1MS

上电等待1ms状态

S_CFG

配置检测模式状态

S_WAIT_PS

等待PS数据转换完成状态

S_RD_PS_L4

读取PS低四位数据状态

S_RD_PS_H6

读取PS高六位数据状态

S_WAIT_ALS

等待ALS数据转换完成状态

S_RD_ALS_L8

读取ALS低八位数据状态

S_RD_ALS_H8

读取ALS高八位数据状态

结合状态跳转图,我们讲述一下该模块的工作过程。

上电后先等待1ms,越过不稳定状态后,开始配置AP3216C的检测模式。我们是通过i2c总线对其配置的。当配置完成后,i2c驱动模块会输出一个结束信号,当检测到这个结束信号后,表示配置完成,跳到S_WAIT_PS状态。在该状态下等待50ms,PS数据稳定转换完成后,跳转到S_RD_PS_L4状态,在 该状态下通过i2c总线对PS数据的低四位进行读取,读取完之后i2c驱动模块会输出一个结束信号,当检测到这个信号后跳转到S_RD_PS_H6状态读取PS数据的高六位,同理读取完之后i2c驱动模块会输出一个结束信号,检测到该信号后跳转到等待ALS数据转换完成状态。后面状态跳转与PS数据读取过程几乎是一样 的,唯一不同的是读取ALS数据之前的等待时间为200ms。

通过状态机的描述我们知道,该模块就是对AP3216C要转换什么环境数据进行设置,设置完成之后,AP3216C会通过我们设置的检测模式,转换成数据寄存在寄存器内,我们通过i2c总线进行读取即可,过程较为简单。下面再通过波形图来看看具体如何去实现对模式的设置以及数据的读取。

波形图绘制

AP3216013

图 49‑12 ap3216c控制模块波形图(一)

由于整个控制时序过长,所以在这里将分开为大家讲解。如图 49‑12所示,不管是上电等待,还是PS,ALS数据转换都需要等待一段时间,所以我们需要用到一个计数器来产生等待完成信号。

cnt_wait:不止一个状态需要利用等待完成信号来作为跳转条件,所以状态机每跳到一个新的状态,我们就让计数器归0,其他时候就让计数器一直自加,每来一个i2c_clk时钟加一。

当计数器计到1ms数值时,状态机跳转到配置检测模式状态。由理论部分的讲解我们知道,我们是通过对AP3216C的00h寄存器写入03h来激活ALS+PS+IR功能的。所以在配置检测功能模式状态下,我们需要产生写使能信号,i2c触发信号以及写入的地址和数据。

wr_en:只有在S_CFG状态需要写入,所以我们在S_CFG状态拉高wr_en信号,其他状态拉低即可。只有在写使能为高时才能写入。

iic_start:i2c触发信号,由i2c驱动模块可知,无论是写还是读,都是先检测到i2c触发信号后才开始写入和读取的。所以每次写入和读取我们都需要产生一个时钟的触发信号,因为计数器在每个状态都会从0开始自加计数,所以在S_CFG状态下,cnt_wait为0时,我们产生一个触发信号即可。

wr_data与byte_addr:在相应的状态下给其相应的地址和数据即可。

ALS+PS+IR功能激活后需要等待50ms时间让AP3216C对PS进行采集转换。等待时序与上电等待时序是一致的。等待完之后跳转到下一状态读取PS低四位数据。

当状态跳转到读取PS低四位数据状态时,需要产生读使能信号,i2c触发信号以及读取的地址。i2c触发信号的产生方法与配置检测模式时一致,写使能信号与读使能信号也是一样的,在需要写入的状态一直拉高,其他状态拉低即可,同时在相应的状态给需要写入的寄存器地址赋值。

在PS低四位数据读取完(i2c_end=1)之后,我们需要将读取的数据寄存起来,不然当下一次读取数据时会将上一次读取的数据覆盖掉。所以在该状态下当i2c_end=1时,将读取的PS低四位数据寄存在PS数据寄存器(ps_data_reg)的低四位中。读取完之后跳转到下一个状态读取PS高六位数据。

在S_RD_PS_H6状态下,i2c触发信号我们不能像之前一样在cnt_wait=0处产生,因为从AP3216C的数据手册可以知道,在一次读写完成之后至少要等待1.3us后才能开始下一次读写。所以如果我们在cnt=0处产生触发信号的话,与结束信号(i2c_end=1)只相隔了1个时钟信号,由i2c配 置模块我们知道一个i2c_clk时钟信号的持续时间是1us,这并不能满足等待时间。所以连续读写时,在读写完成之后需等待一段时间再开始下一次的读写,在这里我们等待10us后再开始下一次的读写,即cnt_wait = 9时产生触发信号。同读取PS低四位数据状态一样,当PS高六位数据读取完之后将读取的数值寄存在PS数据寄存器的高六位同时产生一个时钟的PS数据读取完成信号(ps_done)。当检测到ps_done有效时(ps_done=1)将PS数据寄存器里的值赋给ps_data。

PS数据读取完之后是读取ALS数据,波形图如图 49‑13所示。

AP3216014

图 49‑13 ap3216c控制模块波形图(二)

该波形图绘制的等待ALS数据转换完成状态,以及ALS数据读取状态时序与PS数据的等待,读取时序的产生方法是一样的;同样ALS数据的采集时序的产生与PS数据的采集时序的产生方法也是一致的,在此就不再一遍叙述。这里需要注意的是:当ALS高八位数据读取完之后,表示我们对一个时间状态下的PS与ALS数据读取 完成,如果我们要实时检测不同环境的PS与ALS值,我们就需要让状态机跳回S_WAIT_PS状态开始下一个环境数据的读取,让状态机一直这样循环读取,就能读得每一个环境的PS,ALS值了。

代码编写

参照绘制波形图,编写模块代码。模块参考代码,具体见代码清单 49‑1。

代码清单 49‑1 ap3216c控制模块参考代码(ap3216c_ctrl.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
module ap3216c_ctrl
(
input wire i2c_clk , //i2c驱动时钟,1MHz
input wire sys_rst_n , //复位信号,低有效
input wire i2c_end , //i2c一次读/写操作完成
input wire [7:0] rd_data , //i2c设备读取数据

output reg wr_en , //写数据使能信号
output reg rd_en , //读数据使能信号
output reg i2c_start , //i2c触发信号
output reg [15:0] byte_addr , //输入i2c字节地址
output reg [7:0] wr_data , //输入i2c设备数据
output reg [9:0] ps_data , //输出距离
output reg [15:0] als_data //输出光感
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter S_WAIT_1MS = 4'd1 , //上电等待1ms状态
S_CFG = 4'd2 , //系统配置状态
S_WAIT_PS = 4'd3 , //等待50ms
S_RD_PS_L4 = 4'd4 , //读取PS低四位数据
S_RD_PS_H6 = 4'd5 , //读取PS高六位数据
S_WAIT_ALS = 4'd6 , //等待200ms
S_RD_ALS_L8 = 4'd7 , //读取als低八位数据
S_RD_ALS_H8 = 4'd8 ; //读取als高八位数据

parameter CNT_WAIT_ALS = 200000 ; //200ms时间计数值
parameter CNT_WAIT_PS = 50000 ; //50ms时间计数值
parameter CNT_WAIT_1MS = 1000 ; //1ms时间计数值

//reg define
reg [17:0] cnt_wait ; //寄存器配置上电等待计数器
reg ps_done ; //ps数据采集完成信号
reg als_done ; //als数据采集完成信号
reg [9:0] ps_data_reg ; //PS数据寄存器
reg [15:0] als_data_reg ; //als数据寄存器
reg [3:0] state ; //状态机状态

////
//\* Main Code \//
////

//cnt_wait:当跳转到一个新的状态时计数器归0,其余时候让其一直加
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 18'd0;
else if((state==S_WAIT_1MS && cnt_wait==CNT_WAIT_1MS) \|\|
(state==S_CFG && i2c_end==1'b1) \|\| (state==S_WAIT_PS
&& cnt_wait==CNT_WAIT_PS) \|\| (state==S_RD_PS_L4 &&
i2c_end==1'b1) \|\| (state==S_RD_PS_H6 && i2c_end==1'b1) \|\|
(state==S_WAIT_ALS && cnt_wait==CNT_WAIT_ALS) \|\|
(state==S_RD_ALS_L8 && i2c_end==1'b1) \|\| (state==S_RD_ALS_H8
&& i2c_end==1'b1))
cnt_wait <= 18'd0;
else
cnt_wait <= cnt_wait + 1'b1;

//状态机状态跳转
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= S_WAIT_1MS;
//当跳转到下一个状态时计数器归0,在当前状态每来一个时钟信号,计数器加一
else case(state)
//上电等待1ms后跳转到系统配置状态
S_WAIT_1MS:
if(cnt_wait == CNT_WAIT_1MS)
state <= S_CFG;
else
state <= S_WAIT_1MS ;
//系统配置完成(i2c_end == 1)后,跳转到下一状态
S_CFG:
if(i2c_end == 1'b1)
state <= S_WAIT_PS;
else
state <= S_CFG ;
//等待50ms后跳转
S_WAIT_PS:
if(cnt_wait == CNT_WAIT_PS)
state <= S_RD_PS_L4;
else
state <= S_WAIT_PS ;
//读取完ps低四位数据之后跳转
S_RD_PS_L4:
if(i2c_end == 1'b1)
state <= S_RD_PS_H6;
else
state <= S_RD_PS_L4 ;
//读取完PS高六位数据后跳转
S_RD_PS_H6:
if(i2c_end == 1'b1)
state <= S_WAIT_ALS;
else
state <= S_RD_PS_H6 ;
//等待200ms后跳转
S_WAIT_ALS:
 if(cnt_wait == CNT_WAIT_ALS)
 state <= S_RD_ALS_L8;
 else
 state <= S_WAIT_ALS ;
 //读取完als低八位数据后跳转
 S_RD_ALS_L8:
 if(i2c_end == 1'b1)
 state <= S_RD_ALS_H8;
 else
 state <= S_RD_ALS_L8 ;
 //读取完als高八位数据后跳转到S_WAIT_PS状态开始下一轮数据的读取
 S_RD_ALS_H8:
 if(i2c_end == 1'b1)
 state <= S_WAIT_PS;
 else
 state <= S_RD_ALS_H8 ;
 default:
 state <= S_WAIT_1MS;
 endcase

 //各状态下的信号赋值
 always@(posedge i2c_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b0 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h0 ;
 end
 else case(state)
 S_WAIT_1MS: //上电等待状态
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b0 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h00 ;
 end
 S_CFG: //系统配置状态,产生一个时钟的开始信号,拉高写使能
 if(cnt_wait == 18'd0)
 begin
 wr_en <= 1'b1 ;
 i2c_start <= 1'b1 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h03 ;
 end
 else
 begin
 wr_en <= 1'b1 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h03 ;
 end
 S_WAIT_PS: //等待ps数据转换完成状态
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b0 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h0 ;
 end
 S_RD_PS_L4://读取ps数据低四位,产生一个时钟的开始信号,拉高读使能
 if(cnt_wait == 18'd0)
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b1 ;
 byte_addr <= 16'h0E ;
 wr_data <= 8'h00 ;
 end
 else
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0E ;
 wr_data <= 8'h00 ;
 end
 S_RD_PS_H6://读取ps数据高六位,产生一个时钟的开始信号,拉高读使能
 //ap3216c i2c配置结束信号与开始信号之间最小需等待1.3us
 //这里等待10us后产生开始信号
 if(cnt_wait == 18'd9)
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b1 ;
 byte_addr <= 16'h0F ;
 wr_data <= 8'h00 ;
 end
 else
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0F ;
 wr_data <= 8'h00 ;
 end
 S_WAIT_ALS: //等待als数据转换完成状态
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b0 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h0 ;
 end
 S_RD_ALS_L8://读取als数据低八位,产生一个时钟的开始信号,拉高读使能
 if(cnt_wait == 18'd0)
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b1 ;
 byte_addr <= 16'h0C ;
 wr_data <= 8'h00 ;
 end
 else
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0C ;
 wr_data <= 8'h00 ;
 end
 S_RD_ALS_H8 ://读取als数据低八位,产生一个时钟的开始信号,拉高读使能
 //ap3216c i2c配置结束信号与开始信号直接最小需等待1.3us
 //这里等待10us后产生开始信号
 if(cnt_wait == 18'd9)
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b1 ;
 byte_addr <= 16'h0D ;
 wr_data <= 8'h00 ;
 end
 else
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b1 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0D ;
 wr_data <= 8'h00 ;
 end
 default:
 begin
 wr_en <= 1'b0 ;
 rd_en <= 1'b0 ;
 i2c_start <= 1'b0 ;
 byte_addr <= 16'h0 ;
 wr_data <= 8'h0 ;
 end
 endcase

 //读取的ps数据寄存到ps_data_reg中,读取完产生一个时钟的完成信号
 always@(posedge i2c_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 begin
 ps_data_reg <= 10'b0;
 ps_done <= 1'b0;
 end
 else if(state == S_RD_PS_L4 && i2c_end == 1'b1)
 begin
 ps_data_reg[3:0] <= rd_data[3:0];
 ps_done <= 1'b0;
 end
 else if(state == S_RD_PS_H6 && i2c_end == 1'b1)
 begin
 ps_data_reg[9:4] <= rd_data[5:0];
 ps_done <= 1'b1;
 end
 else
 begin
 ps_data_reg <= ps_data_reg;
 ps_done <= 1'b0;
 end

 //读取的als数据寄存到als_data_reg中,读取完产生一个时钟的完成信号
 always@(posedge i2c_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 begin
 als_data_reg <= 16'b0;
 als_done <= 1'b0;
 end
 else if(state == S_RD_ALS_L8 && i2c_end == 1'b1)
 begin
 als_data_reg[7:0] <= rd_data;
 als_done <= 1'b0;
 end
 else if(state == S_RD_ALS_H8 && i2c_end == 1'b1)
 begin
 als_data_reg[15:8] <= rd_data;
 als_done <= 1'b1;
 end
 else
 begin
 als_data_reg <= als_data_reg;
 als_done <= 1'b0;
 end

 //ps数据读取完后,将值赋给ps_data
 always@(posedge i2c_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 ps_data <= 10'd0;
 else if(ps_done == 1'b1)
 ps_data <= ps_data_reg;
 else
 ps_data <= ps_data;

 //als数据读取完后,将值赋给als_data
 always@(posedge i2c_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 als_data <= 16'd0;
 else if(als_done == 1'b1)
 als_data <= als_data_reg \* 6'd35 / 7'd100;
 else
 als_data <= als_data;

 endmodule

代码第312行是对环境光值的计算,从数据手册上可知,环境光的计算方式为:

环境光(lux) = 16位ALS ADC数据 * 分辨率

其中分辨率可由寄存器0x10的bit[5:4]位进行设置,bit[5:4]默认值为00,分辨率为0.35,由于我们我们并没有对寄存0x10进行设置,所以此时为默认值00,分辨率为0.35 。所以最后的环境光(als_data)的值需要乘以0.35,代码上表现为*6’d35 / 7’d100 。

20.3.1.5. 按键控制数据切换模块

模块框图

AP3216015

图 49‑14 按键控制数据切换模块框图

该模块实现对PS数据以及ALS数据切换显示功能(开始时数码管显示ALS数据,按一次按键后显示PS数据,再按一次后又显示ALS数据)。模块各个信号的简介,如表格 49‑5所示。

表格 49‑5 按键控制数据切换模块输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1bit

input

时钟,50MHz

sys_rst_n

1bit

input

复位信号,低有效

ps_data

10bit

input

物体距离值

als_data

16bit

input

环境光感值

key_flag

1bit

input

按键消抖后有效信号

data_out

20bit

output

输出数码管显示数据

为匹配数码管显示模块的输入数据位宽(20bit),我们将输出数码管显示数据也设置为20bit,ps_data与als_data是检测的距离和光感值。下面通过波形图去了解该模块的具体时序。

波形图绘制

AP3216016

图 49‑15 按键控制数据切换模块波形图

如图 49‑15所示:通过前面对按键消抖的学习我们知道,消抖后会产生一个时钟的高电平标志信号,如果我们只用这一个高电平的标志信号去控制数据的切换,并不能实现我们实验想要的功能。所以这里我们需要生成一个data_flag信号去控制数据的切换,当按键信号到来时对data_flag取反即可。当data_f lag为低电平时显示光感值,高电平时显示距离值,这样就实现了按键对光感和距离值的切换显示了。

代码编写

参照绘制波形图,编写模块代码,模块参考代码,具体见代码清单 49‑2。

代码清单 49‑2 按键控制数据切换模块参考代码(key_ctrl.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
module key_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //系统复位,低电平有效
input wire key_flag , //按键消抖标志信号
input wire [9:0] ps_data , //ps数据
input wire [15:0] als_data , //als数据

output reg [19:0] data_out //输出数据给数码管显示

 );

 ////
 //\* Parameter and Internal Signal \//
 ////

 //reg define
 reg data_flag ; //数据切换标志信号

 ////
 //\* Main Code \//
 ////

 //data_flag:数据切换标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data_flag <= 1'd0;
 else if(key_flag == 1'b1)
 data_flag <= ~data_flag;

 //data_out:输出数码管显示数据
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data_out <= 20'd0;
 else if(data_flag == 1'b0)
 data_out <= als_data;
 else
 data_out <= ps_data;

 endmodule

模块参考代码是参照绘制波形图进行编写的,在波形图绘制小节已经对模块各信号有了详细的说明,本小节不再过多叙述。

20.3.1.6. 顶层模块

ap3216c顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接,模块框图如图 49‑16所示。

模块框图

AP3216017

图 49‑16 ap3216c顶层模块框图

模块各输入输出信号描述如表格 49‑6所示。

表格 49‑6 ap3216c顶层模块输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1bit

input

系统时钟,50MHz

sys_rst_n

1bit

input

复位信号,低电平有效

key_in

1bit

input

按键输入信号

scl

1bit

output

输出至ap3216c的串行时钟信号scl

sda

1bit

inout

输出至ap3216c的串行数据信号sda

stcp

1bit

output

存储寄存器时钟

shcp

1bit

output

移位寄存器时钟

ds

1bit

output

串行数据

oe

1bit

output

输出使能,低有效

代码编写

顶层代码编写较为容易,无需波形图的绘制。顶层参考代码,具体见代码清单 49‑3。

代码清单 49‑3 ap3216c顶层模块参考代码(ap3216c.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
module ap3216c
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire key_in , //按键信号

inout wire sda , //输出至ap3216c的串行数据信号sda

output wire scl , //输出至ap3216c的串行时钟信号scl
 output wire stcp , //输出数据存储寄时钟
 output wire shcp , //移位寄存器的时钟输入
 output wire ds , //串行数据输入
 output wire oe

 );

 ////
 //\* Parameter And Internal Signal \//
 ////

 //wire define
 wire [19:0] data_out ; // 数码管显示数据
 wire key_flag ; // 按键消抖后信号
 wire i2c_clk ; // i2c驱动时钟
 wire i2c_end ; // i2c一次读/写操作完成
 wire [7:0] rd_data ; // i2c设备读取数据
 wire wr_en ; // 写数据使能信号
 wire rd_en ; // 读数据使能信号
 wire i2c_start ; // iic触发信号
 wire [15:0] byte_addr ; // 输入i2c字节地址
 wire [7:0] wr_data ; // 输入i2c设备数据
 wire [9:0] ps_data ; // ps数据
 wire [15:0] als_data ; // als数据

 ////
 //\* Instantiation \//
 ////

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

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

 );

 //-------------ap3216c_ctrl_inst--------------
 ap3216c_ctrl ap3216c_ctrl_inst
 (
 .i2c_clk (i2c_clk ), //i2c驱动时钟,1MHz
 .sys_rst_n (sys_rst_n), //复位信号,低有效
 .i2c_end (i2c_end ), //i2c一次读/写操作完成
 .rd_data (rd_data ), //i2c设备读取数据

 .wr_en (wr_en ), //写数据使能信号
 .rd_en (rd_en ), //读数据使能信号
 .i2c_start (i2c_start), //i2c触发信号
 .byte_addr (byte_addr), //输入i2c字节地址
 .wr_data (wr_data ), //输入i2c设备数据
 .ps_data (ps_data ), //输出距离
 .als_data (als_data ) //输出光感

 );

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

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

 );

 //-------------key_fifter_inst--------------
 key_filter
 #(
   .CNT_MAX (24'd999_999)
 )
 key_filter_inst(
 .sys_clk (sys_clk ), //系统时钟50MHz
 .sys_rst_n (sys_rst_n ), //全局复位
 .key_in (key_in ), //按键输入信号

 .key_flag (key_flag ) //按键消抖后输出信号

 );

 //-------------key_ctrl_inst--------------
 key_ctrl key_ctrl_inst
 (
 .sys_clk (sys_clk ) , //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ) , //系统复位,低电平有效
 .key_flag (key_flag ) , //按键消抖标志信号
 .ps_data (ps_data ) , //ps数据
 .als_data (als_data ) , //als数据

 .data_out (data_out ) //输出数据给数码管显示

 );

 endmodule

代码第77行是对设备地址进行赋值,输入AP3216C的器件地址即可。代码第87行是对写入字节数赋值,由于AP3216C地址数为一个字节,所以直接给其赋0即可。代码45、46、47是对数码管小数点,数码管使能信号,数码管符号进行赋值,根据实际工程需求赋值即可。

20.3.1.7. RTL视图

编译完成后,我们查看一下RTL视图,仔细看RTL视图展示信息与顶层模块框图是一致,具体见图 49‑17。

AP3216018

图 49‑17 实验工程RTL视图

20.3.1.8. SignalTap波形抓取

由于该工程的仿真文件不易产生,所以我们可以利用quartus软件自带的SignalTap工具对波形进行抓取。

AP3216019

图 49‑18 SignalTap抓取波形图(一)

如图 49‑18所示为抓取的PS数据波形图,从图中可以看到ps_data是有数据的,这说明我们的工程是可以驱动AP3216C工作的。ps_data是在ps_done(PS数据读取完成标志信号)为高时数据变化的,这与我们所绘制的波形图是一致的,同时可以看到状态机的跳转也是与我们波形图绘制的一样。

AP3216020

图 49‑19 SignalTap抓取波形图(二)

如图 49‑19所示为抓取的ALS数据,可以看到它与PS数据一样,与我们设计的波形图时序是一样的。

AP3216021

图 49‑20 SignalTap抓取波形图(三)

如图 49‑20所示为按键切换数据波形图,可以看到该波形图与我们设计的波形图是一致的。

通过SignalTap抓取的波形图可以看到,我们编写的代码与我们所绘制的波形图时序是相对应的,说明我们的代码能满足实验要求。

20.4. 上板调试

20.4.1. 引脚约束

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

表格 49‑7 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

input

E1

时钟

sys_rst_n

input

M15

复位

key_in

input

M2

按键

scl

input

P15

I2C时钟线

sda

inout

N14

I2C数据线

stcp

output

K9

存储寄存器时钟

shcp

output

B1

移位寄存器时钟

ds

output

R1

串行数据

oe

output

L11

输出使能,低有效

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

AP3216022

图 49‑21 管脚分配

20.4.1.1. 结果验证

管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将电源与下载线与开发板连接好,连接好后上电,如图 49‑22所示。

AP3216023

图 49‑22 下载连线图

打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下次,随后界面会显示下载成功,如图 49‑23所示。

AP3216024

图 49‑23 下载成功界面

下载成功后即可以开始验证了。刚开始数码管上显示的是环境光传感器采集的环境光强度值,显示的数值会随环境光强度的变化而变化。当按下按键KEY1时,数码管上显示的值会切换为距离值,物体越靠近环境光传感器显示的数值越大,最近时显示的值为1023(该值只是一个衡量物体远近的值,并不是实际的距离值)。

20.5. 章末总结

本章节使用的通信总线为i2c总线,这是一个较为常用的通信接口。若i2c模块大家能够完全理解,那么本实验完成起来就较为简单了,所以学习本章节之前望大家务必先学习好《基于I2C协议的 EEPROM驱动控制》章节的i2c协议。

20.6. 拓展训练

更改代码,使用前面章节的红外遥控来对ALS、PS数据切换显示,也就是用红外遥控来代替按键功能。