4、光照传感器(BH1750):工作原理、I2C地址配置、测量模式选择、驱动代码实现与光照度计算

各位同学,咱们今天来聊聊BH1750这个光照传感器。说实话,在环境光检测这个领域,BH1750是我个人用得最顺手的一个片子。它不像一些模拟输出的光敏电阻那样需要自己搭电路校准,也不像某些数字传感器那样寄存器配置复杂到让人头疼。BH1750就是那种——嗯,你把它焊上去,写几行代码,它就能老老实实给你返回光照度数值的器件。

4.1 工作原理:光电转换与数字输出

BH1750内部其实集成了一个高灵敏度的光电二极管,加上一个ADC转换器,还有一套I2C通信接口。光电二极管把光信号转成电流信号,然后ADC把这个电流信号量化成数字值。最后,芯片内部会按照固定的算法把这个数字值换算成我们熟悉的勒克斯(lux)。

说白了,你不需要关心它内部怎么算的。你只需要通过I2C总线告诉它:「哥们,开始测光吧」,然后等一会儿,再读两个字节回来,就完事了。我刚开始用的时候还特意拿照度计对比过,误差基本在±1lux以内,对于嵌入式气象站来说,这个精度完全够用。

核心参数速览:

  • 测量范围:1 ~ 65535 lux
  • 分辨率:高分辨率模式下可达1 lux
  • 工作电压:2.4V ~ 3.6V(注意,5V系统需要电平转换)
  • 接口:I2C(最大400kHz)
  • 功耗:典型值0.12mA(测量中)

4.2 I2C地址配置:ADDR引脚决定命运

BH1750的I2C地址只有两种可能,完全取决于ADDR引脚的电平状态。这个设计很巧妙,你想想看,一条I2C总线上最多可以挂两个BH1750,一个测室外光,一个测室内光,互不干扰。

ADDR引脚电平 7位I2C地址 写操作地址 读操作地址
低电平(GND) 0x23 0x46 0x47
高电平(VCC) 0x5C 0xB8 0xB9

我的经验: 我在项目里习惯把ADDR引脚直接接地,用0x23这个地址。因为很多现成的模块默认就是接地,省得再去改硬件。如果你买的是那种Grove接口的模块,通常也是默认0x23。

4.3 测量模式选择:四种模式怎么选?

BH1750提供了四种测量模式,我当年第一次看数据手册的时候也被绕晕过。其实你只需要记住两个维度:分辨率高低、测量时间长短。

  • 连续高分辨率模式(0x10): 分辨率1 lux,测量时间120ms。我最常用的模式,适合大多数场景。
  • 连续高分辨率模式2(0x11): 分辨率0.5 lux,测量时间120ms。适合弱光环境,比如夜晚或者室内暗处。
  • 连续低分辨率模式(0x13): 分辨率4 lux,测量时间16ms。适合快速采样,比如你要做光强变化检测。
  • 单次测量模式: 测量一次后自动进入掉电模式,适合低功耗应用。

为什么会搞出这么多模式?说白了就是功耗和精度的取舍。你想想看,气象站如果放在室外大太阳底下,用高分辨率模式2纯属浪费,0.5 lux的分辨率根本用不上。反过来,如果是放在室内做智能灯光控制,那0.5 lux的分辨率就很有价值了。

注意: 我曾经在做一个低功耗项目时,用了单次测量模式,结果忘了在每次测量前重新发送上电指令(0x01),导致读出来的数据全是0。排查了半天才发现是芯片进入掉电模式后,需要先唤醒才能再次测量。这个坑我替你们踩过了。

4.4 驱动代码实现:从初始化到数据读取

好,咱们直接上代码。我习惯把BH1750的驱动封装成一个独立的模块,这样移植起来方便。下面是我在STM32上写的驱动,I2C底层用的是HAL库。

// bh1750.h
#ifndef __BH1750_H
#define __BH1750_H

#include "stm32f1xx_hal.h"

// I2C地址(ADDR接GND)
#define BH1750_ADDR_WRITE  0x46
#define BH1750_ADDR_READ   0x47

// 指令定义
#define BH1750_POWER_ON    0x01
#define BH1750_RESET       0x07
#define BH1750_CONT_H_RES  0x10  // 连续高分辨率模式

// 函数声明
void BH1750_Init(I2C_HandleTypeDef *hi2c);
uint8_t BH1750_ReadLight(I2C_HandleTypeDef *hi2c, float *lux);

#endif
// bh1750.c
#include "bh1750.h"

void BH1750_Init(I2C_HandleTypeDef *hi2c)
{
    uint8_t cmd;
    
    // 上电
    cmd = BH1750_POWER_ON;
    HAL_I2C_Master_Transmit(hi2c, BH1750_ADDR_WRITE, &cmd, 1, 100);
    
    // 复位(清除之前的测量数据)
    cmd = BH1750_RESET;
    HAL_I2C_Master_Transmit(hi2c, BH1750_ADDR_WRITE, &cmd, 1, 100);
    
    // 设置连续高分辨率模式
    cmd = BH1750_CONT_H_RES;
    HAL_I2C_Master_Transmit(hi2c, BH1750_ADDR_WRITE, &cmd, 1, 100);
    
    // 等待首次测量完成(至少120ms)
    HAL_Delay(180);
}

uint8_t BH1750_ReadLight(I2C_HandleTypeDef *hi2c, float *lux)
{
    uint8_t buf[2];
    
    // 读取2字节数据
    if (HAL_I2C_Master_Receive(hi2c, BH1750_ADDR_READ, buf, 2, 100) != HAL_OK)
    {
        return 0;  // 读取失败
    }
    
    // 计算光照度
    // 高分辨率模式下,1 lux = 1.2 原始值
    uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1];
    *lux = (float)raw / 1.2f;
    
    return 1;  // 读取成功
}

关于延时: 我初始化时给了180ms的延时,比数据手册要求的120ms多了一点。为什么?因为有些模块的晶振精度不够,或者电源上电慢,多等一会儿能保证第一次读数稳定。这是我在一个批量生产项目中总结出来的经验——宁可多等50ms,也别让产品在客户手里第一次读数就出错。

4.5 光照度计算:原始值到勒克斯的转换

这个计算其实很简单,但很多初学者会搞错。BH1750返回的原始值是一个16位的无符号整数,范围是0到65535。但这个值并不是直接的勒克斯数,需要除以一个系数。

不同模式下,这个系数不一样:

  • 高分辨率模式(1 lux): 原始值 / 1.2 = 勒克斯
  • 高分辨率模式2(0.5 lux): 原始值 / 2.4 = 勒克斯
  • 低分辨率模式(4 lux): 原始值 / 0.3 = 勒克斯

举个例子,如果你读到原始值是1200,在高分辨率模式下,光照度就是 1200 / 1.2 = 1000 lux。这个数值对应的是比较明亮的室内环境,或者阴天的室外。

避坑指南: 我曾经在调试时发现读到的光照度忽高忽低,后来发现是I2C时钟频率太高了。BH1750虽然支持400kHz,但有些模块的走线比较长,或者上拉电阻选得不对,就会导致通信不稳定。我的建议是:先用100kHz跑通,稳定了再慢慢提频率。

4.6 实际应用中的注意事项

最后再唠叨几句实际项目中会遇到的问题:

  1. 遮光罩问题: BH1750对光线的入射角度很敏感。如果你把它装在气象站的外壳里,一定要在传感器上方开一个透光孔,并且用透明的亚克力或者玻璃做防水处理。我见过有人直接用热熔胶封死,结果读数直接少了30%。
  2. 温度影响: BH1750在-40°C到+85°C范围内都能工作,但温度变化会引起测量偏差。数据手册上给出的典型温漂是±0.5 lux/°C,对于气象站来说可以忽略不计。
  3. 多次测量取平均: 我习惯连续读5次数据,去掉最大值和最小值,然后取平均。这样能有效滤除瞬间的光线抖动,比如有人走过遮挡了一下。

好了,BH1750的内容就讲到这里。下一章咱们聊聊气压传感器,那个东西比光照传感器有意思多了,因为它涉及到高度换算和天气预测。到时候我会把我当年做无人机气压定高时踩过的坑都抖出来,你们准备好笔记本就行。