Skip to content

概述

BH1750 是一颗通过 I2C 总线(两根线的串行通信,一根时钟 SCL、一根数据 SDA,传感器最常用的接口之一)读取的光照度传感器,光照度就是"环境有多亮",单位是 lux(勒克斯):正午阳光约 10 万 lux,室内灯光约 100~500 lux。本教程用 Ai-WB2 开发板通过 I2C 读取 BH1750 的光照度,串口实时打印,并走完 接线 → 代码编写 → 编译 → 烧录(把编译好的程序写进开发板芯片)→ 运行验证 的完整流程。

用大白话讲:BH1750 就像一个自带"感光小眼睛"的传感器,主控(开发板)通过两根线(I2C 就像走廊里的两根线:一根喊号、一根传话)找到它的"门牌号"(I2C 地址),喊一声"开始测量!"(发送测量命令),它就报出当前亮度。手电筒照它数值变大,黑布蒙它数值变小。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/iot-solution/demo_bh1750 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过 I2C 总线读取 BH1750 光照度,串口每秒打印一次光照值,掌握 I2C 主机发送命令与接收数据。
🧰前置条件① Ai-WB2 开发板 + BH1750 光照度传感器模块 ② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [GPIO输出(点亮LED)](../basic/gpio_led)。
🔗相关章节I2C 基础原理见 [I2C 协议](../basic/i2c);同系列传感器见 [DHT11 温湿度传感器](./dht11)。

硬件接线

按官方示例接线(见 SDK applications/iot-solution/demo_bh1750/README.md),用杜邦线(两端带插针的连接线)连接:

Ai-WB2 引脚 BH1750 引脚
IO12 SCL
IO3 SDA
3V3 VCC
GND GND

💡 BH1750 是 I2C 设备,VCC 接 3.3V(部分模块也兼容 5V,但本教程统一 3.3V,避免电平不匹配)。 💡 I2C 的 SCL/SDA 是开漏信号(引脚只能主动拉低、不能主动拉高),需要上拉电阻(把线路默认拉到高电平的小电阻)配合,多数 BH1750 模块已板载上拉;裸芯片需在 SCL/SDA 上各接 4.7kΩ 上拉到 3V3。

进入示例工程

本教程直接使用官方 SDK 自带的 demo_bh1750 示例工程,打开终端进入该工程目录:

cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_bh1750

说明:cd 是「进入目录」命令,这里进入 BH1750 示例工程目录;后续的 make 编译、make flash 烧录命令都必须先在这个目录里执行。

工程目录结构说明:

文件 作用
demo_bh1750/main.c 主程序源码,本教程主要查看的文件
Makefile 编译入口,一般无需修改
proj_config.mk 工程配置(Flash 大小、功能开关等),一般无需修改
编写代码

打开 demo_bh1750/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/iot-solution/demo_bh1750/demo_bh1750/main.c)完全一致。

代码要点:

代码 作用
.scl = 12.sda = 3.freq = 100000 I2C 引脚 IO12/IO3、时钟 100kHz(标准模式),引脚接错就通信不上
BH1750_DEFAULT_ADDR 0x23 BH1750 的 I2C 地址(门牌号):ADDR 引脚悬空/接低为 0x23,接高为 0x5c,地址错就找不到传感器
cmd = BH1750_ONETIME_H_MODE (0x20) 发送「单次高分辨率测量」命令,让传感器开工测量;不发命令传感器不干活
hosal_i2c_master_send(&i2c0, addr, &cmd, 1, HOSAL_WAIT_FOREVER) 主机把 1 字节测量命令发给传感器,发送失败会一直等到成功
hosal_i2c_master_recv(&i2c0, addr, buffer, 2, 100) 接收 2 字节光照原始值(高字节在前),收不到就读不出光照度
result /= 1.2f BH1750 分辨率 1.2 lux/bit,真实光照度 = 原始值 ÷ 1.2(官方代码打印的是读取到的原始值)
编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8 表示用 8 个核并行编译,速度更快。

编译成功后生成固件 build_out/demo_bh1750.bin

⚠️ 若提示 riscv64-unknown-elf-gcc: command not found,说明工具链权限未配置,先执行 cd toolchain/riscv/Linux && . chmod755.sh 再重新编译。

烧录固件

开发板保持 USB 连接,确认串口设备号(Linux 下通常为 /dev/ttyUSB0),执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件写进开发板芯片。p= 后面是串口设备号(要改成你电脑上实际的串口,可用 ls /dev/ttyUSB* 查看),b= 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式(部分开发板自动进入),等待进度条完成即烧录成功。 Windows 平台烧录方法见 Windows 平台快速开始

运行验证

烧录完成后开发板自动重启运行,打开串口助手(波特率 921600,波特率 = 串口传输速度,收发两端必须设成一样),每 1 秒打印一次光照值:

lux level: 123.00
lux level: 124.00
...

用手电筒照传感器,数值应明显变大;用黑布蒙住传感器,数值应明显变小。

看到串口每秒打印光照值、且光照值随亮度变化即为成功;如果一直打印 i2c timeout 或数值固定不变,说明还没成功,对照文末「常见问题与踩坑提示」排查。

💡 用逻辑分析仪观察 IO12/IO3 波形,可看到 I2C 的 START、地址、数据与 STOP 时序(见官方示例 img/logic_analyzer.jpg)。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

hosal_i2c_init(i2c)

dev 结构体中的引脚、频率、主从模式配置 I2C 控制器(本教程初始化主机模式读取 BH1750)。

参数

  • i2chosal_i2c_dev_t 结构体指针,必填。关键字段:config.mode(可选值:HOSAL_I2C_MODE_MASTER 主机 / HOSAL_I2C_MODE_SLAVE 从机)、config.scl/config.sda(SCL/SDA 引脚号,本教程 IO12/IO3)、config.freq(可选值:100000 标准 100kHz / 400000 快速 400kHz)、config.address_width(可选值:HOSAL_I2C_ADDRESS_WIDTH_7BIT / HOSAL_I2C_ADDRESS_WIDTH_10BIT

返回值:成功返回 0;失败返回负值错误码

hosal_i2c_master_send(i2c, dev_addr, data, size, timeout)

以主机身份向指定地址的从机发送一帧数据(含起始位/地址/停止位,本教程发送 BH1750 测量命令)。

参数

  • i2chosal_i2c_dev_t 结构体指针
  • dev_addr:从机设备地址(7 位地址左移 1 位后的字节),如 0x23
  • data:发送数据缓冲区指针,必填
  • size:发送字节数,可选值:1~256
  • timeout:等待超时(单位 ms),可选值:如 100HOSAL_WAIT_FOREVER0xFFFFFFFF)表示一直等

返回值:成功返回 0;失败(无 ACK/超时)返回负值错误码

hosal_i2c_master_recv(i2c, dev_addr, data, size, timeout)

以主机身份从指定从机读取一帧数据(本教程接收 BH1750 的 2 字节光照原始值)。

参数

  • i2chosal_i2c_dev_t 结构体指针
  • dev_addr:从机设备地址(同 master_send 规则)
  • data:接收数据缓冲区指针,必填
  • size:期望接收字节数(本教程为 2
  • timeout:等待超时(单位 ms),超时未收到返回失败

返回值:成功返回 0;失败返回负值错误码(本教程据此判断超时并打印 i2c timeout

blog_info(fmt, ...)

输出一条 INFO 级日志(UART0,受级别过滤),本教程打印光照值。

参数

  • fmt:格式化字符串,同 printf 用法,必填
  • ...:变参,与 fmt 占位符对应,可省略

返回值:无

blog_error(fmt, ...)

输出一条 ERROR 级错误日志,本教程在 I2C 超时(读不到传感器)时打印 i2c timeout

参数

  • fmt:格式化字符串,同 printf 用法,必填
  • ...:变参,与 fmt 占位符对应,可省略

返回值:无

vTaskDelay(ms)

让当前任务挂起指定毫秒数,期间让出 CPU 给其他任务。

参数

  • ms:延时毫秒数,可选值:任意非负整数(内部经 pdMS_TO_TICKS 换算为系统节拍)

返回值:无


完整代码

以下为 demo_bh1750/main.c 完整源码,与官方示例(applications/iot-solution/demo_bh1750/demo_bh1750/main.c)完全一致:

📜 点击展开 demo_bh1750/main.c 完整代码
c
#include <stdio.h>

#include <FreeRTOS.h>
#include <task.h>

#include <hosal_i2c.h>
#include <bl_gpio.h>
#include <blog.h>

#define BH1750_DEFAULT_ADDR BH1750_ADDR_L
#define BH1750_ADDR_H 0x5c
#define BH1750_ADDR_L 0x23
#define BH1750_POWER_DOWN 0x00
#define BH1750_POWER_ON 0x01
#define BH1750_RESET 0x07
#define BH1750_CONTINUOUS_H_MODE  0x10
#define BH1750_CONTINUOUS_H_MODE2  0x11
#define BH1750_CONTINUOUS_L_MODE  0x13
#define BH1750_ONETIME_H_MODE  0x20
#define BH1750_ONETIME_H_MODE2  0x21
#define BH1750_ONETIME_L_MODE  0x23

int main(void)
{
    static hosal_i2c_dev_t i2c0 = {
        .config = {
            .address_width = HOSAL_I2C_ADDRESS_WIDTH_7BIT,
            .freq = 100000,
            .mode = HOSAL_I2C_MODE_MASTER,
            .scl = 12,
            .sda = 3,
        },
        .port = 0,
    };

    hosal_i2c_init(&i2c0);

    for (;;) {
        
        uint8_t buffer[2];
        uint8_t cmd = BH1750_ONETIME_H_MODE;
        hosal_i2c_master_send(&i2c0, BH1750_DEFAULT_ADDR, &cmd, 1, HOSAL_WAIT_FOREVER);
        int ret = hosal_i2c_master_recv(&i2c0, BH1750_DEFAULT_ADDR, buffer, 2, 100);
        if (ret) {
            cmd = BH1750_POWER_ON;
            hosal_i2c_master_send(&i2c0, BH1750_DEFAULT_ADDR, &cmd, 1, 100);
            blog_error("i2c timeout\r\n");
        }
        else {
            uint16_t result = buffer[0];
            result <<= 8;
            result |= buffer[1];

            float luxlevel = result;
            result /= 1.2f;

            blog_info("lux level: %.02f\r\n", luxlevel);
        }

        vTaskDelay(portTICK_RATE_MS * 1000);
    }

    return 0;
}

常见问题与踩坑提示

⚠️ 一直打印 i2c timeout(读不到数据)
原因:接线错误、SCL/SDA 无上拉电阻、模块供电不足,或传感器已损坏
解决:对照接线表逐一核对 IO12/IO3/3V3/GND;确认 SCL/SDA 有 4.7kΩ 上拉;换一块模块测试

⚠️ 光照值恒为 0 或乱码
原因:VCC 供电电压不足、开发板与模块未共地(GND 没接),或杜邦线接触不良
解决:确认 VCC 接 3V3、GND 接 GND(共地 = 两个设备的地线必须接在一起,否则电压没有参考点);重新插紧杜邦线

⚠️ 光照值固定不变(遮光/强光都没反应)
原因:传感器没有收到测量命令(命令字节错误),或模块的 ADDR 引脚状态与代码不一致(接高时地址变为 0x5c)
解决:确认模块 ADDR 悬空或接低(地址 0x23);若 ADDR 接 3V3,把 BH1750_DEFAULT_ADDR 改为 BH1750_ADDR_H (0x5c) 后重新编译烧录

⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号

⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试

⚠️ 执行 make 报找不到 Makefile
原因:在错误的目录执行了编译命令(必须在示例工程目录内)
解决:先执行 cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_bh1750 进入工程目录,再执行 make -j8

运行自检

串口每秒打印一次光照值,用手电筒照数值变大、遮光数值变小,即 BH1750 光照度测量验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23