差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

后一修订版
前一修订版
智能接近系统设计 [2018/10/22 09:55]
anran 创建
智能接近系统设计 [2020/01/18 21:56] (当前版本)
gongyu
行 1: 行 1:
-=====矩阵键盘键入系统设计===== +### 基于接近式传感器的智能接近系统设计
------ +
-====实验任务====+
  
 +---
  
-====实验目的====+#### 实验任务
  
 +  * 任务:智能手机通话,手机靠近耳朵后关闭屏显,基于 STEP-MAX10M08核心板 和 STEP BaseBoard V3.0底板 完成智能接近系统设计并观察调试结果
 +  * 要求:驱动底板上的接近式传感器APDS-9901获得接近数据,控制核心板上LED按能量条方式点亮
 +  * 解析:通过FPGA编程驱动接近式传感器APDS-9901,获取接近距离信息,然后根据距离信息编码控制8个LED灯按能量条方式点亮。
  
-====设计框图====+#### 实验目的
  
 +本节实验主要学习I2C总线工作原理、协议及相关知识,掌握FPGA驱动I2C设备的原理及方法,了解输入输出型端口的模型及控制实现,最终实现智能接近系统的总体设计。
 +  * 熟悉I2C总线工作原理及通信协议
 +  * 完成I2C接口接近式传感器的FPGA驱动
 +  * 完成智能接近系统设计实现
  
-====实验原理==== +#### 设计框图
-===I2C总线介绍=== +
-===APDS-9901模块连接=== +
-===双向端口设计=== +
-===APDS-9901驱动设计=== +
-===系统总体实现===+
  
 +根据前面的实验解析我们可以得知,该设计可以拆分成两个功能模块实现,
 +  * APDS_9901_Driver:接近式传感器APDS-9901芯片I2C总线通信驱动模块。
 +  * Decoder:将距离信息转换成能量条数据。
  
-====实验步骤====+{{:​9-Top-Down层次设计.png?​500|Top-Down层次设计}} {{:​9-模块结构设计.png?​500|模块结构设计}} 
 + 
 +#### 实验原理 
 + 
 +##### I2C总线介绍 
 + 
 +I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。 
 + 
 +{{:​9-I2C总线连接.png?​500|I2C总线连接}} 
 + 
 +主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。 
 + 
 +{{:​9-I2C通信时序.png?​600|I2C通信时序}} 
 + 
 +**<wrap hi>​字节格式</​wrap>​** 
 + 
 +发送到SDA 线上的每个字节必须为8 位,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位。首先传输的是数据的最高位(MSB),如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线SCL 保持低电平,迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线SCL 后数据传输继续。 
 + 
 +{{:​9-I2C字节格式.png?​450|I2C字节格式}} 
 + 
 +**<wrap hi>​启动和停止</​wrap>​** 
 + 
 +在时钟线SCL保持高电平期间,数据线SDA上的电平被拉低(即负跳变),定义为I2C总线总线的启动信号,它标志着一次数据传输的开始。启动信号是一种电平跳变时序信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前I2C总线必须处于空闲状态。  
 + 
 +在时钟线SCL保持高电平期间,数据线SDA被释放,使得SDA返回高电平(即正跳变),称为I2C总线的停止信号,它标志着一次数据传输的终止。停止信号也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信号之后,I2C总线将返回空闲状态。 
 + 
 +{{:​9-启动和停止格式.png?​500|启动和停止格式}} 
 + 
 +**<wrap hi>​应答响应</​wrap>​** 
 + 
 +数据传输必须带响应,相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间,发送器释放SDA 线(上拉电阻拉高),接收器必须将SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平,这种情况下是应答,如果在这个时钟脉冲的高电平期间SDA线没有被拉低则表示没有应答。通常被寻址的接收器在接收到的每个字节后,必须产生一个应答。当从机接收器不应答时,主机产生一个停止或重复起始条件。 
 + 
 +{{:​9-应答响应格式.png?​500|应答响应格式}} 
 + 
 +**<wrap hi>​通信速率</​wrap>​** 
 + 
 +常见的I²C总线依传输速率的不同而有不同的模式:标准模式(100 Kbit/​s)、低速模式(10 Kbit/​s),但时钟频率可被允许下降至零,这代表可以暂停通信。而新一代的I2C总线可以和更多的节点(支持10比特长度的地址空间)以更快的速率通信:快速模式(400 Kbit/​s)、高速模式(3.4 Mbit/​s)。 
 + 
 +##### APDS-9901模块连接 
 + 
 +STEP BaseBoard V3.0底板上的接近光传感器APDS-9901模块电路图如下(上拉电阻未显示): 
 + 
 +{{:​9-APDS-9901模块电路.png?​600|APDS-9901模块电路}} 
 + 
 +上图为接近光传感器APDS-9901模块电路,与FPGA硬件接口有I2C总线(SCL、SDA)和中断信号INT,APDS-9901是博通公司的集成环境光ALS、红外光IR和接近距离传感器,具有体积小、低功耗等优点,被大量应用于手机、笔记本、相机、液晶显示器等电子产品上,环境光ALS可以根据外部环境调节设备屏幕显示亮度,接近距离传感器可以根据应用场景实现产品对应应用,例如接听电话时控制手机关闭显示等,接口采用I2C总线能够支持400KHz的I2C快速模式。 
 + 
 +{{:​9-APDS-9901内部框图.png?​600|APDS-9901内部框图}} 
 + 
 +##### 双向端口设计 
 + 
 +可综合Verilog模块设计中必须有端口存在,端口有输入input,输出output,双向inout,对于输入和输出型端口我们很好理解,我们来了解一下双向端口信号的处理。 
 + 
 +在芯片中为了管脚复用,很多管脚都是双向的,既可以输入也可以输出。在Verilog中即为inout型端口。Inout端口的实现是使用三态门,三态门的第三个状态是高阻态Z。在实际电路中高阻态意味着响应的管脚悬空、断开。 
 + 
 +{{:​9-FPGA引脚输入输出模型.png?​600|FPGA引脚输入输出模型}} 
 + 
 +当inout用作输出时,就像平常一样。当inout用作输入时,需要设为高阻态,这样其电平就可以由外部输入信号决定了(这是高阻态的特性)。 
 + 
 +双向端口应用案例: 
 + 
 +<code verilog>​ 
 +module bid 
 +
 +input           ​out_en,​ 
 +input           a, 
 +inout           b, 
 +output ​         c 
 +);  
 + 
 +assign ​ b out_en? a : 1'​bz;​ 
 +assign ​ c b; 
 + 
 +endmodule 
 +</​code>​ 
 + 
 +##### APDS-9901驱动设计 
 + 
 +通过前面的了解,我们对于整个I2C总线的驱动原理有了一定的了解,接下来我们根据APDS-9901的芯片手册了解其驱动方法及参数要点。 
 + 
 +{{:​9-APDS-9901时序.png?​700|APDS-9901时序}} 
 + 
 +{{:​9-APDS-9901时序参数.png?​600|APDS-9901时序参数}} 
 + 
 +通过APDS-9901时序参数了解,APDS-9901支持I2C通信400KHz快速模式同时兼容100KHz的标准模式,还有两种模式下时序中的各种时间参数,本例中我们就采用标准模式完成驱动设计。 
 + 
 +首先我们分频得到400KHz的时钟,整个设计都基于该时钟完成,程序实现如下: 
 + 
 +<code verilog>​ 
 +//​使用计数器分频产生400KHz时钟信号clk_400khz 
 +reg                 ​clk_400khz;​ 
 +reg     ​[9:​0] ​      ​cnt_400khz;​ 
 +always@(posedge clk or negedge rst_n) begin 
 +    if(!rst_n) begin 
 +        cnt_400khz <10'd0; clk_400khz <1'​b0;​ 
 +    end else if(cnt_400khz >= CNT_NUM-1) begin 
 +        cnt_400khz <= 10'd0; clk_400khz <= ~clk_400khz;​ 
 +    end else begin 
 +        cnt_400khz <= cnt_400khz + 1'​b1;​ 
 +    end 
 +end 
 +</​code>​ 
 + 
 +I2C时序可以分解成基本单元(启动、停止、发送、接收、发应答、读应答),整个I2C通信都是由这些单元按照不同的顺序组合,我们设计一个状态机,将这些基本单元做成状态,控制状态机的跳转就能实现I2C通信时序。主机每次发送数据都要接收判断从机的响应,每次接收数据也要向从机发送响应,所以发送单元和读应答单元可以合并,接收单元和写应答单元可以合并。 
 + 
 +启动时序状态设计程序实现如下: 
 + 
 +<code verilog>​ 
 +START:begin //​I2C通信时序中的起始START 
 +        if(cnt_start >= 3'd5) cnt_start <= 1'​b0; ​   //​对START中的子状态执行控制cnt_start 
 +        else cnt_start <= cnt_start + 1'​b1;​ 
 +        case(cnt_start) 
 +            3'​d0: ​  begin sda <= 1'b1; scl <= 1'b1; end //​将SCL和SDA拉高,保持4.7us以上 
 +            3'​d1: ​  begin sda <= 1'b1; scl <= 1'b1; end //​每个周期2.5us,需要两个周期 
 +            3'​d2: ​  begin sda <= 1'b0; end  //​SDA拉低到SCL拉低,保持4.0us以上 
 +            3'​d3: ​  begin sda <= 1'b0; end  //​clk_400khz每个周期2.5us,需要两个周期 
 +            3'​d4: ​  begin scl <= 1'b0; end  //​SCL拉低,保持4.7us以上 
 +            3'​d5: ​  begin scl <= 1'b0; state <= state_back; end //​每个周期2.5us,两个周期 
 +            default: state <= IDLE; //​如果程序失控,进入IDLE自复位状态 
 +        endcase 
 +    end 
 +</​code>​ 
 + 
 +发送单元和读应答单元合并,时序状态设计程序实现如下: 
 + 
 +<code verilog>​ 
 +WRITE:begin //​I2C通信时序中的写操作WRITE和相应判断操作ACK 
 +        if(cnt <= 3'd6) begin   //​共需要发送8bit的数据,这里控制循环的次数 
 +            if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end 
 +            else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end 
 +        end else begin 
 +            if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end //​复位变量 
 +            else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end 
 +        end 
 +        case(cnt_write) 
 +            //​按照I2C的时序传输数据 
 +            3'​d0: ​  begin scl <= 1'b0; sda <= data_wr[7-cnt];​ end   //​SCL拉低,SDA输出 
 +            3'​d1: ​  begin scl <= 1'b1; end  //​SCL拉高,保持4.0us以上 
 +            3'​d2: ​  begin scl <= 1'b1; end  //​clk_400khz每个周期2.5us,需要两个周期 
 +            3'​d3: ​  begin scl <= 1'b0; end  //​SCL拉低,准备发送下1bit的数据 
 +            //​获取从设备的响应信号并判断 
 +            3'​d4: ​  begin sda <= 1'bz; end  //​释放SDA线,准备接收从设备的响应信号 
 +            3'​d5: ​  begin scl <= 1'b1; end  //​SCL拉高,保持4.0us以上 
 +            3'​d6: ​  begin ack_flag <= i2c_sda; end  //​获取从设备的响应信号 
 +            3'​d7: ​  begin scl <= 1'b0; if(ack_flag)state <= state; else state <= state_back; end //​SCL拉低,如果不应答循环写 
 +            default: state <= IDLE; //​如果程序失控,进入IDLE自复位状态 
 +        endcase 
 +    end 
 +</​code>​ 
 + 
 +接收单元和写应答单元合并,时序状态设计程序实现如下: 
 + 
 +<code verilog>​ 
 +READ:​begin ​ //​I2C通信时序中的读操作READ和返回ACK的操作 
 +        if(cnt <= 3'd6) begin   //​共需要接收8bit的数据,这里控制循环的次数 
 +            if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end 
 +            else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end 
 +        end else begin 
 +            if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; cnt <= 1'b0; end   //​复位变量值 
 +            else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end 
 +        end 
 +        case(cnt_read) 
 +            //​按照I2C的时序接收数据 
 +            3'​d0: ​  begin scl <= 1'b0; sda <= 1'bz; end //​SCL拉低,释放SDA线 
 +            3'​d1: ​  begin scl <= 1'b1; end  //​SCL拉高,保持4.0us以上 
 +            3'​d2: ​  begin data_r[7-cnt] <= i2c_sda; end //​读取从设备返回的数据 
 +            3'​d3: ​  begin scl <= 1'b0; end  //​SCL拉低,准备接收下1bit的数据 
 +            //​向从设备发送响应信号 
 +            3'​d4: ​  begin sda <= ack; end   //​发送响应信号,将前面接收的数据锁存 
 +            3'​d5: ​  begin scl <= 1'b1; end  //​SCL拉高,保持4.0us以上 
 +            3'​d6: ​  begin scl <= 1'b1; end  //​SCL拉高,保持4.0us以上 
 +            3'​d7: ​  begin scl <= 1'b0; state <= state_back; end //SCL拉低  
 +            default: state <= IDLE; //​如果程序失控,进入IDLE自复位状态 
 +        endcase 
 +    end 
 +</​code>​ 
 + 
 +停止时序状态设计程序实现如下: 
 + 
 +<code verilog>​ 
 +STOP:​begin ​ //​I2C通信时序中的结束STOP 
 +        if(cnt_stop >= 3'd5) cnt_stop <= 1'​b0; ​ //​对STOP中的子状态执行控制cnt_stop 
 +        else cnt_stop <= cnt_stop + 1'​b1;​ 
 +        case(cnt_stop) 
 +            3'​d0: ​  begin sda <= 1'b0; end  //​SDA拉低,准备STOP 
 +            3'​d1: ​  begin sda <= 1'b0; end  //​SDA拉低,准备STOP 
 +            3'​d2: ​  begin scl <= 1'b1; end  //​SCL提前SDA拉高4.0us 
 +            3'​d3: ​  begin scl <= 1'b1; end  //​SCL提前SDA拉高4.0us 
 +            3'​d4: ​  begin sda <= 1'b1; end  //​SDA拉高 
 +            3'​d5: ​  begin sda <= 1'b1; state <= state_back; end //​完成STOP操作 
 +            default: state <= IDLE; //​如果程序失控,进入IDLE自复位状态 
 +        endcase 
 +    end 
 +</​code>​ 
 + 
 +基本单元都有了,接下来我们需要了解APDS-9901驱动的流程,手册上看到APDS-9901芯片有很多寄存器,有的配置工作模式,有的配置功能使能,有的返回结果数据,这个需要大家自己查看芯片手册,这里不作讲解。 
 + 
 +{{:​9-APDS-9901寄存器.png?​600|APDS-9901寄存器}} 
 + 
 +手册给用户提供了基本功能的C实例代码,流程如下: 
 +<code c> 
 +WriteRegData (0, 0); //Disable and Powerdown  
 +WriteRegData (1, 0xff); // 2.7 ms – minimum ALS integration time 
 +WriteRegData (2, 0xff); // 2.7 ms – minimum Prox integration time 
 +WriteRegData (3, 0xff); // 2.7 ms – minimum Wait time 
 +WriteRegData (0xe, 1);  // Minimum prox pulse count 
 +WriteRegData (0xf, 0x20); // CH1 Diode 
 +WriteRegData (0,0x0f); //Enable WEN PEN AEN PON 
 +Wait(12); ​ //Wait for 12 ms  
 +CH0_data = Read_Word(0x14);​  
 +CH1_data = Read_Word(0x16);​  
 +Prox_data = Read_Word(0x18);​  
 + 
 +WriteRegData(uint8 reg, uint8 data)  
 +{  
 +    m_I2CBus.WriteI2C(0x39,​ 0x80 | reg, 1, &data);  
 +}  
 +uint16 Read_Word(uint8 reg) 
 +{  
 +    uint8 barr[2];  
 +    m_I2CBus.ReadI2C(0x39,​ 0xA0 | reg, 2, ref barr);  
 +    return (uint16)(barr[0] + 256 * barr[1]);  
 +
 +</​code>​ 
 + 
 +根据手册提供的软件操作流程,我们首先有7次向寄存器写入数据的操作,按照时序 
 + 
 +{{:​9-I2C写操作.png?​600|I2C写操作}} 
 + 
 +向reg_addr地址寄存器中写入数据reg_data,程序实现如下 
 + 
 +<code verilog>​ 
 +4'​d0: ​  begin state <= START; end   //​I2C通信时序中的START 
 +4'​d1: ​  begin data_wr <= dev_addr<<​1;​ state <= WRITE; end  //​设备地址 
 +4'​d2: ​  begin data_wr <= reg_addr; state <= WRITE; end  //​寄存器地址 
 +4'​d3: ​  begin data_wr <= reg_data; state <= WRITE; end  //​写入数据 
 +4'​d4: ​  begin state <= STOP; end    //​I2C通信时序中的STOP 
 +</​code>​ 
 + 
 +7次向寄存器写入数据的操作需要7段上面的代码,罗列起来程序不易读,干脆我们将1次写操作做成状态机的一个状态,这样7次向寄存器写入数据的操作只需要在这个状态上循环执行7次就好了,单词写操作状态程序实现如下: 
 + 
 +<code verilog>​ 
 +MODE1:begin //​单次写操作 
 +        if(cnt_mode1 >= 4'd5) cnt_mode1 <= 1'​b0; ​   //​对START中的子状态执行控制cnt_start 
 +        else cnt_mode1 <= cnt_mode1 + 1'​b1;​ 
 +        state_back <= MODE1; 
 +        case(cnt_mode1) 
 +            4'​d0: ​  begin state <= START; end   //​I2C通信时序中的START 
 +            4'​d1: ​  begin data_wr <= dev_addr<<​1;​ state <= WRITE; end   //​设备地址 
 +            4'​d2: ​  begin data_wr <= reg_addr; state <= WRITE; end  //​寄存器地址 
 +            4'​d3: ​  begin data_wr <= reg_data; state <= WRITE; end  //​写入数据 
 +            4'​d4: ​  begin state <= STOP; end    //​I2C通信时序中的STOP 
 +            4'​d5: ​  begin state <= MAIN; end    //​返回MAIN 
 +            default: state <= IDLE; //​如果程序失控,进入IDLE自复位状态 
 +        endcase 
 +    end 
 +</​code>​ 
 + 
 +同理两字节数据连读的操作也做成一个状态 
 + 
 +{{:​9-I2C读操作.png?​600|I2C读字操作}} 
 + 
 +程序实现如下: 
 + 
 +<code verilog>​ 
 +MODE2:begin //​两次读操作 
 +        if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'​b0; ​  //​对START中的子状态执行控制cnt_start 
 +        else cnt_mode2 <= cnt_mode2 + 1'​b1;​ 
 +        state_back <= MODE2; 
 +        case(cnt_mode2) 
 +            4'​d0: ​  begin state <= START; end   //​I2C通信时序中的START 
 +            4'​d1: ​  begin data_wr <= dev_addr<<​1;​ state <= WRITE; end   //​设备地址 
 +            4'​d2: ​  begin data_wr <= reg_addr; state <= WRITE; end  //​寄存器地址 
 +            4'​d3: ​  begin state <= START; end   //​I2C通信时序中的START 
 +            4'​d4: ​  begin data_wr <= (dev_addr<<​1)|8'​h01;​ state <= WRITE; end//​设备地址 
 +            4'​d5: ​  begin ack <= ACK; state <= READ; end    //​读寄存器数据 
 +            4'​d6: ​  begin dat_l <= data_r; end 
 +            4'​d7: ​  begin ack <= NACK; state <= READ; end   //​读寄存器数据 
 +            4'​d8: ​  begin dat_h <= data_r; end 
 +            4'​d9: ​  begin state <= STOP; end    //​I2C通信时序中的STOP 
 +            4'​d10: ​ begin state <= MAIN; end    //​返回MAIN 
 +            default: state <= IDLE; //​如果程序失控,进入IDLE自复位状态 
 +        endcase 
 +    end 
 +</​code>​ 
 + 
 +因为用到延时,也设计成一个状态,程序实现如下: 
 + 
 +<code verilog>​ 
 +DELAY:begin //​延时模块 
 +        if(cnt_delay >= num_delay) begin 
 +            cnt_delay <= 1'​b0;​ 
 +            state <= MAIN;  
 +        end else cnt_delay <= cnt_delay + 1'​b1;​ 
 +    end 
 +</​code>​ 
 + 
 +最后我们编程控制状态机按照驱动例程代码中流程运行,程序实现如下: 
 + 
 +<code verilog>​ 
 +4'​d0: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h00;​reg_data<​=8'​h00;​state<​=MODE1;​ end  
 +4'​d1: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h01;​reg_data<​=8'​hff;​state<​=MODE1;​ end  
 +4'​d2: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h02;​reg_data<​=8'​hff;​state<​=MODE1;​ end  
 +4'​d3: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h03;​reg_data<​=8'​hff;​state<​=MODE1;​ end  
 +4'​d4: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h0e;​reg_data<​=8'​h01;​state<​=MODE1;​ end  
 +4'​d5: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h0f;​reg_data<​=8'​h20;​state<​=MODE1;​ end  
 +4'​d6: ​  begin dev_addr<​=7'​h39;​reg_addr<​=8'​h80|8'​h00;​reg_data<​=8'​h0f;​state<​=MODE1;​ end  
 +4'​d7: ​  begin state <= DELAY; dat_valid <= 1'b0; end    //​12ms延时 
 +4'​d8: ​  begin dev_addr <= 7'h39; reg_addr <= 8'​ha0|8'​h14; ​ state <= MODE2; end  
 +4'​d9: ​  begin ch0_dat <= {dat_h,​dat_l};​ end //​读取数据 
 +4'​d10: ​ begin dev_addr <= 7'h39; reg_addr <= 8'​ha0|8'​h16; ​ state <= MODE2; end  
 +4'​d11: ​ begin ch1_dat <= {dat_h,​dat_l};​ end //​读取数据 
 +4'​d12: ​ begin dev_addr <= 7'h39; reg_addr <= 8'​ha0|8'​h18; ​ state <= MODE2; end   
 +4'​d13: ​ begin prox_dat <= {dat_h,​dat_l};​ end    //​读取数据 
 +4'​d14: ​ begin dat_valid <= 1'b1; end    //​读取数据 
 +</​code>​ 
 + 
 +##### 系统总体实现 
 + 
 +程序中我们做了一个简单的滤波处理,为了保证数据的有效,将瞬间变化太大的采样数据舍弃,程序实现如下: 
 + 
 +<code verilog>​ 
 +reg [15:0] prox_dat0,​prox_dat1,​prox_dat2;​ 
 +always @(posedge dat_valid) begin 
 +    prox_dat0 <= prox_dat; 
 +    prox_dat1 <= prox_dat0;​ 
 +if(((prox_dat1-prox_dat0) >= 16'​h200)||((prox_dat1-prox_dat0) >= 16'​h200))  
 +prox_dat2 <= prox_dat2;​ 
 +    else prox_dat2 <= prox_dat0;​ 
 +end 
 +</​code>​ 
 + 
 +我们从传感器读取的距离信息为16位数据,有效范围Full Scale ADC Counts为0~1023,对应0到16‘h3ff,可以设置一个阈值,当采样回来的数据与阈值比较控制手机屏幕的显示与否,本实验要求用能量条的方式显示距离的远近,我们设计一个编码器将0到16‘h3ff的范围控制8个led灯的控制,程序实现如下: 
 + 
 +<code verilog>​ 
 +always@(prox_dat2[9:​7]) 
 +    case (prox_dat2[9:​7]) 
 +        3'​b000:​ Y_out 8'​b11111110;​ 
 +        3'​b001:​ Y_out 8'​b11111100;​ 
 +        3'​b010:​ Y_out 8'​b11111000;​ 
 +        3'​b011:​ Y_out 8'​b11110000;​ 
 +        3'​b100:​ Y_out = 8'​b11100000;​ 
 +        3'​b101:​ Y_out = 8'​b11000000;​ 
 +        3'​b110:​ Y_out = 8'​b10000000;​ 
 +        3'​b111:​ Y_out = 8'​b00000000;​ 
 +        default:​Y_out = 8'​b11111111;​ 
 +    endcase 
 +</​code>​ 
 + 
 +在顶层设计中例化两个模块,将信号连接,程序实现如下: 
 + 
 +<code verilog>​ 
 +wire dat_valid;​ 
 +wire [15:0] ch0_dat, ch1_dat, prox_dat; 
 +APDS_9901_Driver u1( 
 +.clk            (clk            ),  //​系统时钟 
 +.rst_n ​         (rst_n ​         ),  //​系统复位,低有效 
 +.i2c_scl ​       (i2c_scl ​       ),  //​I2C总线SCL 
 +.i2c_sda ​       (i2c_sda ​       ),  //​I2C总线SDA 
 +.dat_valid ​     (dat_valid ​     ),  //​数据有效脉冲 
 +.ch0_dat ​       (ch0_dat ​       ),  //​ALS数据 
 +.ch1_dat ​       (ch1_dat ​       ),  //​IR数据 
 +.prox_dat ​      ​(prox_dat ​      ​) ​  //​Prox数据 
 +); 
 + 
 +Decoder u2( 
 +.dat_valid ​     (dat_valid ​     ), 
 +.prox_dat ​      ​(prox_dat ​      ), 
 +.Y_out ​         (led            ) 
 +); 
 +</​code>​ 
 + 
 +综合后的设计框图如下: 
 + 
 +{{:​9-RTL设计框图.png?​800|RTL设计框图}} 
 + 
 + 
 + 
 +#### 实验步骤
   - 双击打开Quartus Prime工具软件;   - 双击打开Quartus Prime工具软件;
   - 新建工程:File → New Project Wizard(工程命名,工程目录选择,设备型号选择,EDA工具选择);   - 新建工程:File → New Project Wizard(工程命名,工程目录选择,设备型号选择,EDA工具选择);
行 29: 行 410:
  
  
-====实验现象====+#### 实验现象 
 + 
 +将设计加载到FPGA,手指在接近光传感器上下移动,观察核心板上8个LED灯的状态,APDS-9901还是环境光传感器,有兴趣的同学可以尝试一下其他应用。 
 + 
 +{{:​9-实验现象.png?​600|实验现象示意}}