环境光传感器驱动 王安然 STEP FPGA
BH1750
BH1750 引脚介绍
BH1750 硬件连接 ADDR 管脚接下拉电阻,I2C 设备 BH1750 从机地址为 0100011,7 h23 DVI 管脚连接 FPGA 管脚,FPGA 控制异步复位操作
I2C 总线介绍 由飞利浦开发并获得专利 ( 现属 NXP), 将低速外围设备连接至主板 嵌入式系统或其它设备 特性 - 是一种支持多主机的串行总线 - 一条数据线 (SDA) 一条时钟线 (SCL) 均为双向开漏需加上拉电阻 - 每个连接入总线的从属设备均有唯一的 7 位 /10 位设备地址 - 主机控制通讯的时钟信号 Vdd SCL SDA I2C Mster I2C Slve Rp I2C Slve I2C Slve
I2C 总线从机 I2C 从机接口无法操作时钟线 I2C Slve Device 根据通信流程发送 ACK 信号 对 I2C 从机进行操作实际是对其内部寄存器读取和写入的过程 从机根据其寄存器数据变化而执行对应操作 Slve 设备通常都包含一个 Device ID 寄存器, 内部存储着该 I2C Slve 的设备码 ( 非设备地址 ) 这个设备码是需要向 NXP 申请并付费才能够获得 SCL SDA I2C Slve interfc e Logic Circuit 00 10 20 Register Mp 01 11 21 02 12 22 Logic Circuit ADC/Sensor/Other Interfce
I2C 总线 - 主机 I2C Mster Device FPGA/MCU/SOC FIFO MCU/SOC Core Bus Bus I2C Mster Controller SCL SDA I2C 主机接口操作时钟线 配置 I2C 总线的工作流程 配置 I2C 总线的地址与数据 多主机时总线忙的判定 Memory or Registers
I2C 总线连接 I2C Slve Device I2C Mster Device FPGA/MCU/SOC SCL SDA I2C Slve interfc e 00 10 Register Mp 01 11 02 12 FIFO 20 21 22 MCU/SO C Core Bus I2C Mster Controlle r SCL SDA Logic Circuit Bus Logic Circuit Memory or Registers ADC/Sensor/Other Interfce
I2C 总线原理 主器件用于启动总线传送数据, 并产生时钟以开放传送的器件, 此时任何被寻址的器件均被认为是从器件 如果主机要发送数据给从器件, 则主机首先寻址从器件, 然后主动发送数据至从器件, 最后由主机终止数据传送 ; Vdd SCL SDA Rp 如果主机要接收从器件的数据, 首先由主器件寻址从器件. 然后主机接收从器件发送的数据, 最后由主机终止接收过程 I2C Mster I2C Slve I2C Slve I2C Slve
I2C 总线字节格式 发送到 SDA 线上的每个字节必须为 8 位, 每次传输可以发送的字节数量不受限制 每个字节后必须跟一个响应位 首先传输的是数据的最高位 (MSB), 如果从机要完成一些其他功能后 ( 例如一个内部中断服务程序 ) 才能接收或发送下一个完整的数据字节, 可以使时钟线 SCL 保持低电平, 迫使主机进入等待状态, 当从机准备好接收下一个数据字节并释放时钟线 SCL 后数据传输继续
I2C 总线启动和停止 在时钟线 SCL 保持高电平期间, 数据线 SDA 上的电平被拉低 ( 即负跳变 ), 定义为 I2C 总线总线的启动信号, 它标志着一次数据传输的开始 启动信号是一种电平跳变时序信号, 而不是一个电平信号 启动信号是由主控器主动建立的, 在建立该信号之前 I2C 总线必须处于空闲状态 在时钟线 SCL 保持高电平期间, 数据线 SDA 被释放, 使得 SDA 返回高电平 ( 即正跳变 ), 称为 I2C 总线的停止信号, 它标志着一次数据传输的终止 停止信号也是一种电平跳变时序信号, 而不是一个电平信号, 停止信号也是由主控器主动建立的, 建立该信号之后,I2C 总线将返回空闲状态
I2C 总线应答响应 数据传输必须带响应, 相关的响应时钟脉冲由主机产生 在响应的时钟脉冲期间, 发送器释放 SDA 线 ( 上拉电阻拉高 ), 接收器必须将 SDA 线拉低, 使它在这个时钟脉冲的高电平期间保持稳定的低电平, 这种情况下是应答, 如果在这个时钟脉冲的高电平期间 SDA 线没有被拉低则表示没有应答 通常被寻址的接收器在接收到的每个字节后, 必须产生一个应答 当从机接收器不应答时, 主机产生一个停止或重复起始条件
I2C 总线通信速率 常见的 I²C 总线依传输速率的不同而有不同的模式 : 标准模式 (100 Kbit/s) 低速模式 (10 Kbit/s), 但时钟频率可被允许下降至零, 这代表可以暂停通信 而新一代的 I2C 总线可以和更多的节点 ( 支持 10 比特长度的地址空间 ) 以更快的速率通信 : 快速模式 (400 Kbit/s) 高速模式 (3.4 Mbit/s)
常见写时序 假设主机向从机写命令, 所需要经过的流程如下 : (1) 主机发送起始信号 (2) 主机访问设备地址 + 写信号 (3) 从机应答 (4) 主机发送指令数据 (5) 从机应答 (6) 主机发送停止信号 假设主机向从机发送数据, 所需要经过的流程如下 : (1) 主机发送起始信号 (2) 主机访问设备地址 + 写信号 (3) 从机应答 (4) 主机发送寄存器地址 (5) 从机应答 (6) 主机发送寄存器数据 (7) 从机应答 (8) 主机发送停止信号
常见读数据时序 假设主机读取从机寄存器的数据, 所需要经过的流程如下 : (1) 主机发送起始信号 (2) 主机访问设备地址 + 写信号 (3) 从机应答 (4) 主机发送寄存器地址 (5) 从机应答 (6) 主机再次发送起始信号 (7) 主机访问设备地址 + 读信号 (8) 从机应答 (9) 主机读取数据 (10) 若主机只读取 1 次, 则发送 NACK 信号, 跳转至 (11), 若连续读取, 则发送 ACK 信号并跳转至 (9) 继续读取 (11) 主机发送停止信号, 停止通讯
BH1750 通信速率 支持高速模式 400KHz 兼容正常模式 100KHz
I2C 设计时钟 SDA SCL // 使用计数器分频产生 400KHz 时钟信号 clk_400khz reg clk_400khz; reg [9:0] cnt_400khz; lwys@(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
I2C 状态机分析 I2C 时序可以分解成基本单元 ( 启动 停止 发送 接收 发应答 读应答 ), 整个 I2C 通信都是由这些单元按照不同的顺序组合, 我们设计一个状态机, 将这些基本单元做成状态, 控制状态机的跳转就能实现 I2C 通信时序 主机每次发送数据都要接收判断从机的响应, 每次接收数据也要向从机发送响应, 所以发送单元和读应答单元可以合并, 接收单元和写应答单元可以合并 启动状态 发送状态 接收状态 停止状态
启动和停止状态实现 START:begin //I2C 通信时序中的起始 START if(cnt_strt >= 3'd5) cnt_strt <= 1'b0; // 对 START 中的子状态执行控制 cnt_strt else cnt_strt <= cnt_strt + 1'b1; cse(cnt_strt) 3'd0: begin sd <= 1'b1; scl <= 1'b1; end // 将 SCL 和 SDA 拉高, 保持 4.7us 以上 3'd1: begin sd <= 1'b1; scl <= 1'b1; end // 每个周期 2.5us, 需要两个周期 3'd2: begin sd <= 1'b0; end //SDA 拉低到 SCL 拉低, 保持 4.0us 以上 3'd3: begin sd <= 1'b0; end //clk_400khz 每个周期 2.5us, 需要两个周期 3'd4: begin scl <= 1'b0; end //SCL 拉低, 保持 4.7us 以上 3'd5: begin scl <= 1'b0; stte <= stte_bck; end // 每个周期 2.5us, 两个周期 defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end STOP:begin //I2C 通信时序中的结束 STOP if(cnt_stop >= 3'd5) cnt_stop <= 1'b0; // 对 STOP 中的子状态执行控制 cnt_stop else cnt_stop <= cnt_stop + 1'b1; cse(cnt_stop) 3'd0: begin sd <= 1'b0; end //SDA 拉低, 准备 STOP 3'd1: begin sd <= 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 sd <= 1'b1; end //SDA 拉高 3'd5: begin sd <= 1'b1; stte <= stte_bck; end // 完成 STOP 操作 defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end
写数据状态实现 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 cse(cnt_write) // 按照 I2C 的时序传输数据 3'd0: begin scl <= 1'b0; sd <= dt_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 sd <= 1'bz; end // 释放 SDA 线, 准备接收从设备的响应信号 3'd5: begin scl <= 1'b1; end //SCL 拉高, 保持 4.0us 以上 3'd6: begin ck_flg <= i2c_sd; end // 获取从设备的响应信号 3'd7: begin scl <= 1'b0; if(ck_flg)stte <= stte; else stte <= stte_bck; end //SCL 拉低, 如果不应答循环写 defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end
读数据状态实现 READ:begin //I2C 通信时序中的读操作 READ 和返回 ACK 的操作 if(cnt <= 3'd6) begin // 共需要接收 8bit 的数据, 这里控制循环的次数 if(cnt_red >= 3'd3) begin cnt_red <= 1'b0; cnt <= cnt + 1'b1; end else begin cnt_red <= cnt_red + 1'b1; cnt <= cnt; end end else begin if(cnt_red >= 3'd7) begin cnt_red <= 1'b0; cnt <= 1'b0; end // 复位变量值 else begin cnt_red <= cnt_red + 1'b1; cnt <= cnt; end end cse(cnt_red) // 按照 I2C 的时序接收数据 3'd0: begin scl <= 1'b0; sd <= 1'bz; end //SCL 拉低, 释放 SDA 线 3'd1: begin scl <= 1'b1; end //SCL 拉高, 保持 4.0us 以上 3'd2: begin dt_r[7-cnt] <= i2c_sd; end // 读取从设备返回的数据 3'd3: begin scl <= 1'b0; end //SCL 拉低, 准备接收下 1bit 的数据 // 向从设备发送响应信号 3'd4: begin sd <= ck; 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; stte <= stte_bck; end //SCL 拉低 defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end
BH1750 异步复位
BH1750 工作流程
BH1750 指令
BH1750 写指令时序 MODE1:begin // 单次写操作 if(cnt_mode1 >= 4'd4) cnt_mode1 <= 1'b0; // 对 START 中的子状态执行控制 cnt_strt else cnt_mode1 <= cnt_mode1 + 1'b1; stte_bck <= MODE1; cse(cnt_mode1) 4'd0: begin stte <= START; end //I2C 通信时序中的 START 4'd1: begin dt_wr <= dev_ddr<<1; stte <= WRITE; end // 设备地址 4'd2: begin dt_wr <= cmd_dt; stte <= WRITE; end // 寄存器地址 4'd3: begin stte <= STOP; end //I2C 通信时序中的 STOP 4'd4: begin stte <= MAIN; end // 返回 MAIN defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end
BH1750 读数据时序 MODE2:begin // 两次读操作 if(cnt_mode2 >= 4'd7) cnt_mode2 <= 4'd0; // 对 START 中的子状态执行控制 cnt_strt else cnt_mode2 <= cnt_mode2 + 1'b1; stte_bck <= MODE2; cse(cnt_mode2) 4'd0: begin stte <= START; end //I2C 通信时序中的 START 4'd1: begin dt_wr <= (dev_ddr<<1) 8'h01; stte <= WRITE; end // 设备地址 4'd2: begin ck <= ACK; stte <= READ; end // 读寄存器数据 4'd3: begin dt_h <= dt_r; end 4'd4: begin ck <= NACK; stte <= READ; end // 读寄存器数据 4'd5: begin dt_l <= dt_r; end 4'd6: begin stte <= STOP; end //I2C 通信时序中的 STOP 4'd7: begin stte <= MAIN; end // 返回 MAIN defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end
BH1750 驱动流程
BH1750 驱动过程 MAIN:begin if(cnt_min >= 4'd8) cnt_min <= 4'd3; // 写完控制指令后循环读数据 else cnt_min <= cnt_min + 1'b1; cse(cnt_min) 4'd0: bh_dvi <= 1'b1; //DVI 输出拉高 4'd1: begin dev_ddr <= 7'h23; cmd_dt <= 8'h00; stte <= MODE1; end //POWER OFF 4'd2: begin dev_ddr <= 7'h23; cmd_dt <= 8'h01; stte <= MODE1; end //POWER ON 4 d3: begin dev_ddr <= 7 h23; cmd_dt <= 8 h10; stte <= MODE1; end // 测量 4'd4: begin num_dely <= 24'd72000; stte <= DELAY; end //180ms 延时 4'd5: begin dev_ddr <= 7'h23; stte <= MODE2; end // 读取数据操作 4'd6: begin ls_code <= {dt_h,dt_l}; end // 环境光数据输出 4'd7: ls_pulse <= 1'b1; 4'd8: ls_pulse <= 1'b0; defult: stte <= IDLE; // 如果程序失控, 进入 IDLE 自复位状态 endcse end
除以 1.2 数据处理 reg [19:0] ls_dt1; // 光强数据编码运算除以 1.2 先乘以 5, 然后除以 6 lwys @(posedge ls_pulse or negedge rst_n) if(!rst_n) ls_dt1 <= 1'b0; else ls_dt1 <= ls_code * 4'd5; wire [3:0] remin; wire [19:0] ls_dt2; lpm_div u2 // 例化除法器 IP 核, 实现除以 6 运算 (.numer (ls_dt1 ), // 分子.denom (4'd6 ), // 分母.quotient (ls_dt2 ), // 商.remin (remin ) // 余数 );
BCD 转码滚动显示 wire [19:0] ls_dt3; bin_to_bcd u3 (.rst_n (rst_n ), // 系统复位, 低有效.bin_code (ls_dt2[15:0]), // 需要进行 BCD 转码的二进制数据.bcd_code (ls_dt3 ) // 转码后的 BCD 码型数据输出 ); wire [19:0] ls_dt4; ssign ls_dt4[19:16] = ls_dt3[19:16]? ls_dt3[19:16]:4'h; ssign ls_dt4[15:12] = ls_dt3[19:12]? ls_dt3[15:12]:4'h; ssign ls_dt4[11: 8] = ls_dt3[19: 8]? ls_dt3[11: 8]:4'h; ssign ls_dt4[ 7: 4] = ls_dt3[19: 4]? ls_dt3[ 7: 4]:4'h; ssign ls_dt4[ 3: 0] = ls_dt3[ 3: 0]; // // 高位为 0 不显示 // 高位为 0 不显示 // 高位为 0 不显示 // 高位为 0 不显示 dot_rry_driver u4 (.clk (clk ),.rst_n (rst_n ),.dt (ls_dt4 ),.row (row ),.col (col ) );
Thnks 扫描二维码 关注小脚丫微信公众号 了解更多 FPGA 知识