esp-iot-solution BH1750 环境光传感器组件实战:从 I2C 接线到光照值读取
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
本篇以 esp-iot-solution 仓库中的 BH1750 环境光传感器组件为核心,完整讲清该组件的硬件接线方式(SDA/SCL 引脚分配与内部上拉配置)、一次测量(One-Time)与连续测量(Continuously)两种工作模式的差异,以及各核心 API 的调用流程。读完后,你可以直接基于 bh1750 驱动源码 和 测试例程 在 ESP32 系列开发板上通过 I2C 总线驱动 BH1750 光照传感器模块(如 GY-30 模块),稳定读出以 Lux 为单位的照度值。
组件定位:基于 i2c_bus 的外置 I2C 传感器读取方案
BH1750 是一款数字输出环境光照强度传感器,组件文档明确说明了它在本仓库中的教学定位:展示如何通过 I2C 总线读取外置 I2C 传感器数据,硬件上以 BH1750 光照传感器(GY-30 模块)为例。组件位于 components/sensors/light_sensor/bh1750 目录,CMakeLists.txt 中通过REQUIRES i2c_bus声明了它对仓库内 i2c_bus 组件 的依赖——也就是说,底层 I2C 通信由i2c_bus抽象层完成,驱动本身只负责 BH1750 的命令寄存器读写,这是理解整个调用链的起点。
BH1750 默认 I2C 器件地址为0x23(定义于 bh1750.h 中的BH1750_I2C_ADDRESS_DEFAULT)。
硬件接线:引脚分配与上拉电阻
组件文档(README.md)给出的接线方式如下:
- I2C 主机端引脚分配
GPIO18分配给 I2C 主机的数据线(SDA)GPIO19分配给 I2C 主机的时钟线(SCL)
- 传感器接线
- 传感器
SDA接GPIO18 - 传感器
SCL接GPIO19 - 无需外接上拉电阻:驱动会启用 GPIO 内部上拉电阻
- 传感器
"无需外接上拉电阻"这一点在源码层面有直接印证。测试例程 bh1750_test.c 创建 I2C 总线时,i2c_config_t中显式开启了内部上拉:
i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = I2C_MASTER_SDA_IO, // 测试例程中使用 GPIO1 .sda_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉 .scl_io_num = I2C_MASTER_SCL_IO, // 测试例程中使用 GPIO2 .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = I2C_MASTER_FREQ_HZ, // 100 kHz }; i2c_bus = i2c_bus_create(I2C_MASTER_NUM, &conf);需要注意:组件 README 中示例接线为 GPIO18/GPIO19,而 test_apps 例程 实际使用的宏定义是I2C_MASTER_SDA_IO 1与I2C_MASTER_SCL_IO 2(100 kHz 时钟)。两者并不冲突——i2c_bus_create接受任意 GPIO 配置,实际引脚以你的开发板走线为准,复制例程时请同步修改这两个宏即可。
测量模式:One-Time 与 Continuously 的区别
BH1750 的测量行为由"测量模式命令"控制,组件文档将模式分为两类:
- 一次测量模式(One-Time):传感器在收到一次测量命令后只测量一次。因此每次想获取新的光照值,都必须重新发送一次测量命令。
- 连续测量模式(Continuously):传感器在收到连续测量命令后持续测量,你只需发送一次命令,之后反复调用
bh1750_get_data()即可不断读到最新的光照值。
从 bh1750.h 中的命令枚举可以看到,每种模式还进一步细分为三种分辨率档位,命令值与数据手册寄存器定义一致:
| 枚举值 | 命令值 | 含义 |
|---|---|---|
BH1750_CONTINUE_1LX_RES | 0x10 | 连续测量,1 lx 分辨率(H-Resolution) |
BH1750_CONTINUE_HALFLX_RES | 0x11 | 连续测量,0.5 lx 分辨率(H-Resolution 2) |
BH1750_CONTINUE_4LX_RES | 0x13 | 连续测量,4 lx 分辨率(L-Resolution) |
BH1750_ONETIME_1LX_RES | 0x20 | 单次测量,1 lx 分辨率 |
BH1750_ONETIME_HALFLX_RES | 0x21 | 单次测量,0.5 lx 分辨率 |
BH1750_ONETIME_4LX_RES | 0x23 | 单次测量,4 lx 分辨率 |
除测量命令外,bh1750.c 中还定义了两个电源与数据管理命令,对应头文件中公开的 API:
0x00:进入 Power Down 低功耗模式(bh1750_power_down())0x01:Power On(bh1750_power_on())0x07:复位数据寄存器(bh1750_reset_data_register(),注意该命令在掉电模式下不可用,源码实现里会先执行bh1750_power_on()再写入复位命令,见 bh1750.c)
核心 API 与内部实现
对象创建与销毁:bh1750_create/bh1750_delete
驱动采用"句柄"模型。bh1750_create(bus, dev_addr)接收一个i2c_bus总线句柄和传感器 I2C 地址,内部通过i2c_bus_device_create()注册一个 I2C 设备(继承总线的当前时钟速度),成功后返回bh1750_handle_t,失败返回NULL(见 bh1750.c)。用完之后调用bh1750_delete(&handle)释放 I2C 设备句柄并回收内存。
设置模式:bh1750_set_measure_mode
该函数只向传感器写一个字节(即上文表格中的命令值),完成测量模式切换。头文件注释提醒:必须在调用bh1750_get_data()读取数据之前先调用它设置测量模式;若设置为一次测量模式,则只能读到一个测量结果;若设置为连续测量模式,则可反复调用bh1750_get_data()获取数据。
读取原始值:bh1750_get_data
bh1750_get_data(sensor, &data)从传感器读出 2 字节原始值,并完成浮点换算(bh1750.c):
uint8_t bh1750_data[2] = {0}; esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, NULL_I2C_MEM_ADDR, 2, &bh1750_data[0]); *data = ((bh1750_data[0] << 8 | bh1750_data[1]) / BH_1750_MEASUREMENT_ACCURACY);其中BH_1750_MEASUREMENT_ACCURACY定义为1.2(bh1750.c,注释说明为 BH1750 的典型测量精度系数),即最终光照值 = 16 位原始值 ÷ 1.2,单位为 Lux。头文件注释同时强调:必须等测量时间过去之后再读数据,不同测量模式的测量时间不同,这一点是实际开发中最容易踩的坑。
一步式读取:bh1750_get_light_intensity
组件文档特别指出,可以直接调用bh1750_get_light_intensity()一步完成光照值获取,其内部工作过程为三步:
- 发送测量命令;
- 延时一段长于测量时间的时长;
- 读取光照值。
源码实现(bh1750.c)印证了这个流程,并且根据分辨率档位选择了不同的等待时长:
esp_err_t ret = bh1750_set_measure_mode(sensor, cmd_measure); if (ret != ESP_OK) { return ret; } if ((cmd_measure == BH1750_CONTINUE_4LX_RES) || (cmd_measure == BH1750_ONETIME_4LX_RES)) { vTaskDelay(30 / portTICK_RATE_MS); // 4 lx 分辨率:约 30 ms 测量时间 } else { vTaskDelay(180 / portTICK_RATE_MS); // 1 lx / 0.5 lx 分辨率:约 180 ms 测量时间 } ret = bh1750_get_data(sensor, data);可以推断,30 ms 对应 BH1750 低分辨率(4 lx)档位的测量周期,180 ms 对应高分辨率档位的测量周期,两者都与"不同测量模式测量时间不同"的文档说明相吻合。如果你的业务循环周期较长、且只关心大致照度,选用 4 lx 档位(BH1750_ONETIME_4LX_RES/BH1750_CONTINUE_4LX_RES)可以让每次采样的等待时间缩短到约 1/6。
自定义测量时间:bh1750_change_measure_time
组件文档的最后一条 Notice 指出:BH1750 在不同测量模式下的测量时间不同,并且可以通过调用bh1750_change_measure_time()修改测量时间。源码实现(bh1750.c)向传感器连续写入 2 个字节,将 8 位测量时间参数拆分编码:
uint8_t buf[2] = {0x40, 0x60}; buf[0] |= measure_time >> 5; // 高 3 位 buf[1] |= measure_time & 0x1F; // 低 5 位 i2c_bus_write_bytes(sens->i2c_dev, NULL_I2C_MEM_ADDR, 2, &buf[0]);其中0x40/0x60是该组寄存器的命令头,measure_time的有效位宽为 8 位(高 3 位写入第一字节、低 5 位写入第二字节)。修改测量时间后,后续读取数据的等待时长也需要同步调整,这一点需要使用者自行把握。
测试例程走读:从 I2C 初始化到两种模式对比
test_apps/main/bh1750_test.c 是一个基于 Unity 框架的完整可运行例程,app_main()启动unity_run_menu(),测试入口为TEST_CASE("Sensor BH1750 test", "[bh1750][iot][sensor]")。例程分三步:初始化(bh1750_test_init)→ 交替采样(bh1750_test_get_data)→ 反初始化(bh1750_test_deinit)。
采样循环的逻辑是:每次先上电,然后分别以一次测量模式(BH1750_ONETIME_4LX_RES)和连续测量模式(BH1750_CONTINUE_4LX_RES)各读一次,中间统一等待 30 ms(4 lx 档位),每秒循环一轮、共 10 轮:
bh1750_power_on(bh1750); cmd_measure = BH1750_ONETIME_4LX_RES; bh1750_set_measure_mode(bh1750, cmd_measure); vTaskDelay(30 / portTICK_RATE_MS); ret = bh1750_get_data(bh1750, &bh1750_data); if (ret == ESP_OK) { printf("bh1750 val(one time mode): %f\n", bh1750_data); } else { printf("No ack, sensor not connected...\n"); }No ack的报错分支也提示了排查思路:若 I2C 写/读返回失败,通常是传感器未接线、地址不符或总线冲突。例程的反初始化阶段按"先删传感器、再删总线"的顺序释放资源:
bh1750_delete(&bh1750); i2c_bus_delete(&i2c_bus);依赖与构建配置方面:
- test_apps/main/idf_component.yml 要求
idf >= 4.4,并通过override_path将bh1750指向组件本体、i2c_bus指向仓库内的 i2c_bus 组件,保证测试始终使用本地最新源码; - test_apps/sdkconfig.defaults 中关闭了任务看门狗(
CONFIG_ESP_TASK_WDT_EN=n,IDF 4.4 为CONFIG_ESP_TASK_WDT=n)并设置CONFIG_FREERTOS_HZ=1000,使vTaskDelay(30 / portTICK_RATE_MS)这类 30 ms 级延时能够精确生效——这与"等待时长必须覆盖测量时间"的要求直接相关。
硬件抽象层接口:接入光照传感器统一框架
除独立使用外,BH1750 驱动在CONFIG_SENSOR_LIGHT_INCLUDED_BH1750编译开关下还实现了一组硬件抽象层(HAL)接口(bh1750.h):
light_sensor_bh1750_init(i2c_bus_handle_t handle):使用默认地址0x23创建传感器对象;light_sensor_bh1750_deinit():释放对象;light_sensor_bh1750_test():检查初始化状态;light_sensor_bh1750_acquire_light(float *l):一次性读取照度(单位 lux)。
其中acquire_light的实现固定采用BH1750_ONETIME_4LX_RES模式:先bh1750_power_on(),再设置模式、等待 30 ms、读取数据,失败时输出 0 并返回ESP_FAIL(bh1750.c)。这套init/deinit/test/acquire_*接口签名与仓库中其他光照传感器(如 VEML6040、VEML6075)保持一致,docs/zh_CN/sensors/light_sensor.rst 中列出的已适配环境光传感器表也标注 BH1750 已支持硬件抽象层。有了这层接口,上层可以直接通过 sensor_hub 的传感器接口以统一方式轮询光照值,而不必关心底层芯片差异;sensor_hub 测试中的虚拟光照传感器 同样实现了acquire_light这套回调结构,可用来在无真实硬件时联调上层逻辑。
典型运行输出与验证
仓库中还有一个基于 POSIX 风格 API 读取 BH1750 的完整示例工程 examples/extended_vfs/i2c/i2c_bh1750,其 README 给出了通过idf.py -p PORT flash monitor烧录后的典型串口输出,可作为读取结果正常性的对照基准:
Opening device /dev/i2c/0 for writing OK, fd=3. Sensor val: 190.00 [Lux]. Sensor val: 173.33 [Lux]. Sensor val: 173.33 [Lux]. Sensor val: 176.67 [Lux]. Sensor val: 193.33 [Lux]. Close device OK.可以看到数值稳定落在室内照度范围且以 Lux 为单位;该示例还支持在idf.py menuconfig的Example Configuration中修改 SDA/SCL 引脚号以及读取次数,方便适配不同开发板。
实践要点小结
- 接线:SDA/SCL 分别接主机 GPIO(README 示例为 GPIO18/GPIO19),创建总线时开启
GPIO_PULLUP_ENABLE内部上拉,无需外部上拉电阻; - 模式选择:低频采样用 One-Time 模式(每次读取前重发命令);高频连续采样用 Continuously 模式(一次设置、反复
bh1750_get_data()); - 等待时长:读数据前必须等待超过该分辨率档位的测量时间,
bh1750_get_light_intensity()内部已按 30 ms / 180 ms 处理,手动调用bh1750_set_measure_mode()+bh1750_get_data()时需自行vTaskDelay; - 换算关系:驱动输出的 Lux 值 = 16 位原始值 ÷ 1.2,如需更高精度场景可自行评估该系数;
- 调试手段:I2C 读写失败(无 ACK)时优先检查传感器供电、接线与
0x23地址是否被同总线其他器件占用。
以上所有说明均可在仓库对应文件中核对:组件 README、头文件 API 文档、驱动实现 与 测试例程,配合 i2c_bus 组件 与 extended_vfs BH1750 示例 即可完成从硬件接线到应用层读数的完整闭环。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考