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 实际应用中的注意事项
最后再唠叨几句实际项目中会遇到的问题:
- 遮光罩问题: BH1750对光线的入射角度很敏感。如果你把它装在气象站的外壳里,一定要在传感器上方开一个透光孔,并且用透明的亚克力或者玻璃做防水处理。我见过有人直接用热熔胶封死,结果读数直接少了30%。
- 温度影响: BH1750在-40°C到+85°C范围内都能工作,但温度变化会引起测量偏差。数据手册上给出的典型温漂是±0.5 lux/°C,对于气象站来说可以忽略不计。
- 多次测量取平均: 我习惯连续读5次数据,去掉最大值和最小值,然后取平均。这样能有效滤除瞬间的光线抖动,比如有人走过遮挡了一下。
好了,BH1750的内容就讲到这里。下一章咱们聊聊气压传感器,那个东西比光照传感器有意思多了,因为它涉及到高度换算和天气预测。到时候我会把我当年做无人机气压定高时踩过的坑都抖出来,你们准备好笔记本就行。