1. 项目概述
BH1750FVI是ROHM公司推出的一款数字环境光传感器,它通过I²C接口直接输出16位光照强度数值(单位:lux)。与传统的模拟光敏电阻相比,BH1750内置了ADC和逻辑电路,无需外接元件即可实现近似人眼的光谱响应,测量范围覆盖1-65535 lux。
在实际项目中,我使用STM32F103C8T6开发板通过软件模拟I2C的方式驱动BH1750传感器。这个方案特别适合那些硬件I2C资源紧张或者需要灵活配置I2C引脚的项目。整个驱动代码采用模块化设计,包含了初始化、单次测量、连续测量等功能,并提供了完整的开源代码。
2. BH1750传感器详解
2.1 传感器特性与工作原理
BH1750采用光电二极管阵列和内置ADC转换器,能够将环境光强转换为数字信号输出。它的核心优势在于:
- 直接数字输出,省去了外部ADC的需求
- 光谱响应接近人眼视觉曲线(峰值灵敏度约560nm)
- 测量范围广(1-65535 lux)
- 低功耗设计(典型工作电流仅120μA)
传感器内部结构包含光学滤波器、光电二极管阵列、运算放大器和16位ADC。当光线照射到光电二极管上时,产生的光电流经过放大和AD转换,最终通过I2C接口输出。
2.2 测量模式解析
BH1750支持三种分辨率模式和两种工作方式:
分辨率模式:
- 高分辨率模式1(120ms测量时间,1lx分辨率)
- 高分辨率模式2(120ms测量时间,0.5lx分辨率)
- 低分辨率模式(16ms测量时间,4lx分辨率)
工作方式:
- 单次测量(One Time Measurement):测量完成后自动进入Power Down模式
- 连续测量(Continuous Measurement):持续进行测量,适合实时监测场景
在实际应用中,高分辨率模式2虽然测量时间相同,但通过内部算法将分辨率提高了一倍,适合需要精确测量的场景。
3. I2C通信实现
3.1 BH1750的I2C协议特点
BH1750的I2C实现相对简单,它没有传统意义上的寄存器概念,而是通过直接发送指令来控制。设备支持两个I2C地址:
- ADDR引脚接GND:0x23(0100011)
- ADDR引脚接VCC:0x5C(1011100)
指令集概览:
| 指令名称 | 操作码 | 说明 |
|---|---|---|
| POWER_DOWN | 0x00 | 进入低功耗模式 |
| POWER_ON | 0x01 | 唤醒设备 |
| RESET | 0x07 | 复位数据寄存器 |
| CONT_H_RES | 0x10 | 连续高分辨率模式1 |
| CONT_H_RES2 | 0x11 | 连续高分辨率模式2 |
| CONT_L_RES | 0x13 | 连续低分辨率模式 |
| ONE_TIME_H | 0x20 | 单次高分辨率模式1 |
| ONE_TIME_H2 | 0x21 | 单次高分辨率模式2 |
| ONE_TIME_L | 0x23 | 单次低分辨率模式 |
3.2 软件I2C实现细节
由于STM32的硬件I2C有时会出现稳定性问题,我选择使用GPIO模拟I2C协议。这种方式虽然速度较慢,但稳定性高且引脚配置灵活。
关键函数实现:
void soft_i2c_start(SOFT_I2C_TypeDef soft_i2c) { soft_i2c_set_sda_level(soft_i2c,1); soft_i2c_set_scl_level(soft_i2c,1); soft_i2c_delay_us(); soft_i2c_set_sda_level(soft_i2c,0); soft_i2c_delay_us(); soft_i2c_set_scl_level(soft_i2c,0); } uint8_t soft_i2c_read_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t ack) { uint8_t i,receive = 0; for (i = 0; i < 8; i++) { soft_i2c_set_scl_level(soft_i2c,0); soft_i2c_delay_us(); soft_i2c_set_scl_level(soft_i2c,1); receive <<= 1; if (soft_i2c_get_sda_level(soft_i2c)) receive++; soft_i2c_delay_us(); } soft_i2c_set_scl_level(soft_i2c, 0); soft_i2c_delay_us(); if (!ack) soft_i2c_nack(soft_i2c); else soft_i2c_ack(soft_i2c); return receive; }注意:软件I2C的关键在于精确的时序控制。在实际调试中发现,延时4μs(soft_i2c_delay_us())在STM32F103上能够稳定工作在标准模式(100kHz)下。如果通信不稳定,可以适当调整这个延时。
4. 驱动代码实现
4.1 初始化与基本功能
驱动代码采用分层设计,底层是软件I2C实现,上层是BH1750专用函数。这种设计提高了代码的复用性和可维护性。
关键数据结构:
// I2C地址定义 #define BH1750_ADDR_L 0x23 #define BH1750_WRITE_ADDR_L 0x23 #define BH1750_WRITE_ADDR_H 0x5C // 命令定义 #define BH1750_POWER_DOWN 0x00 #define BH1750_POWER_ON 0x01 #define BH1750_RESET 0x07 #define BH1750_CONT_H_RES 0x10 #define BH1750_CONT_H_RES2 0x11 #define BH1750_CONT_L_RES 0x13 #define BH1750_ONE_TIME_H 0x20 #define BH1750_ONE_TIME_H2 0x21 #define BH1750_ONE_TIME_L 0x23初始化函数:
uint8_t bh1750_init(uint8_t addr, uint8_t mode) { uint8_t ret; // 1. Power On ret = bh1750_send_cmd(addr, BH1750_POWER_ON); if (ret) return ret; // 2. Reset ret = bh1750_send_cmd(addr, BH1750_RESET); if (ret) return ret; // 3. 设置测量模式 ret = bh1750_send_cmd(addr, mode); if (ret) return ret; // 4. 等待第一次测量完成 if ((mode == BH1750_CONT_H_RES) || (mode == BH1750_CONT_H_RES2)) { delay_ms(180); } else if (mode == BH1750_CONT_L_RES) { delay_ms(24); } return 0; }4.2 照度读取实现
照度值的计算基于原始数据和当前测量模式。BH1750输出的原始数据是16位无符号整数,转换为lux的公式为:
基本公式:lux = raw / 1.2
对于高分辨率模式2(0.5lx分辨率),需要额外除以2: lux = (raw / 1.2) / 2
连续测量模式实现:
int bh1750_read_lux(uint8_t addr, uint8_t mode, float *lux) { uint8_t ret; uint8_t raw_data[2]; uint16_t raw; ret = bh1750_read_raw(addr, raw_data); if (ret) return -1; raw = (raw_data[0] << 8) | raw_data[1]; *lux = (float)raw / 1.2f; if(mode == BH1750_CONT_H_RES2 || mode == BH1750_ONE_TIME_H2){ *lux = *lux/2; } return 0; }单次测量模式实现:
int bh1750_read_lux_single(uint8_t addr,uint8_t mode, float *lux) { uint8_t ret; // 1. Power On ret = bh1750_send_cmd(addr, BH1750_POWER_ON); if (ret) return -1; // 2. 发送单次测量命令 ret = bh1750_send_cmd(addr, mode); if (ret) return -1; // 3. 等待测量完成 delay_ms(180); // 4. 读取数据 return bh1750_read_lux(addr, mode, lux); }5. 高级功能实现
5.1 测量灵敏度调整
BH1750允许通过修改MTreg(测量时间寄存器)的值来调整传感器灵敏度。默认值为69,对应120ms的测量时间。当需要补偿光学窗口的衰减时,可以增大MTreg值。
灵敏度调整实现:
uint8_t bh1750_set_mtreg(uint8_t addr, uint8_t mt_val) { uint8_t ret; uint8_t high = BH1750_MT_H | ((mt_val >> 5) & 0x07); // MT[7:5] uint8_t low = BH1750_MT_L | (mt_val & 0x1F); // MT[4:0] ret = bh1750_send_cmd(addr, high); if (ret) return ret; ret = bh1750_send_cmd(addr, low); return ret; }调整后的照度计算公式变为: lux = (raw / 1.2) * (69.0 / mtreg)
5.2 实际应用示例
连续测量示例:
void exp_continuous_measure() { float lux; bh1750_init(BH1750_WRITE_ADDR_L,BH1750_CONT_H_RES); while(1) { if (bh1750_read_lux(BH1750_WRITE_ADDR_L, BH1750_CONT_H_RES, &lux) == 0) { printf("Light = %.2f lux\r\n", lux); } delay_ms(1000); } }单次测量示例:
void exp_single_measure() { float lux; while(1) { if (bh1750_read_lux_single(BH1750_WRITE_ADDR_L, BH1750_ONE_TIME_H, &lux) == 0) { printf("Light = %.2f lux\r\n", lux); } delay_ms(1000); } }6. 常见问题与解决方案
在实际项目开发中,可能会遇到以下典型问题:
问题1:I2C通信无响应
- 可能原因:
- 地址配置错误(ADDR引脚电平不对应)
- 上拉电阻缺失(I2C总线需要4.7kΩ上拉)
- 时序问题(延时不足)
- 解决方案:
- 确认ADDR引脚连接(GND对应0x23,VCC对应0x5C)
- 检查SCL/SDA线上是否有4.7kΩ上拉电阻
- 调整soft_i2c_delay_us()的延时值
问题2:读取数据始终为0
- 可能原因:
- 未等待足够的测量时间
- 未正确发送POWER_ON命令
- 解决方案:
- 高分辨率模式至少等待180ms
- 确保在测量前发送了POWER_ON命令
问题3:数据不稳定
- 可能原因:
- 电源噪声
- 光学干扰
- 解决方案:
- 在VCC引脚添加0.1μF去耦电容
- 避免传感器直接暴露在强光下
7. 性能优化建议
低功耗优化:
- 优先使用单次测量模式,测量间隔期间芯片自动进入Power Down状态
- 在电池供电场景下,可以延长测量间隔(如每10秒测量一次)
精度优化:
- 使用高分辨率模式2(0.5lx分辨率)
- 避免传感器暴露在直射阳光下,必要时增加光学扩散片
软件优化:
- 将频繁调用的函数(如soft_i2c_delay_us())定义为内联函数
- 使用DMA传输减少CPU占用(如果使用硬件I2C)
抗干扰设计:
- I2C走线尽量短,避免与高频信号线平行
- 在恶劣电磁环境下,可以考虑使用屏蔽线
8. 项目扩展思路
基于这个基础驱动,可以进一步开发更复杂的应用:
智能照明系统:
- 根据环境光强自动调节LED亮度
- 结合人体感应实现智能开关
农业监测系统:
- 监测温室内的光照强度
- 结合温湿度传感器实现综合环境监控
节能控制系统:
- 办公室自动照明控制
- 根据自然光照调节窗帘开合
移动设备应用:
- 手机/平板自动背光调节
- 电子书阅读器的自动亮度控制
9. 完整代码获取
本项目所有代码和资料完全开源,可以通过以下方式获取:
GitHub仓库:https://github.com/snqx-lqh/ProjectReleasePage
Gitee镜像(国内访问更快):https://gitee.com/snqx-lqh/ProjectOpenSourceReleasePage
在仓库中查找项目编号B004,包含了:
- 完整的STM32工程文件
- BH1750数据手册
- 示例程序
- 原理图说明
10. 开发心得
在实际开发过程中,有几点经验值得分享:
I2C地址问题:最初调试时忽略了ADDR引脚的电平设置,导致一直无法通信。后来用逻辑分析仪抓取波形才发现地址不对。建议在I2C通信失败时,先用工具确认实际通信地址。
测量时间控制:高分辨率模式需要足够的时间完成测量。最初没有添加延时,导致读取的数据不准确。数据手册中的测量时间是典型值,实际应用中建议留有余量。
软件I2C的优势:虽然硬件I2C效率更高,但软件实现更灵活。在一个项目中,硬件I2C引脚被其他功能占用,软件I2C可以自由选择任意GPIO,解决了引脚冲突问题。
灵敏度调整:在为传感器增加保护罩后,发现读数偏低。通过调整MTreg值补偿了透光率的影响,这个功能在实际应用中非常实用。
这个项目虽然不大,但涵盖了嵌入式开发的多个关键环节:外设驱动开发、协议实现、性能优化等。对于初学者来说,是一个很好的实战练习;对于有经验的开发者,模块化的设计思路和问题排查经验也值得参考。