☰
ESP32+GY-30(BH1750)光照传感器实战:半小时读取环境亮度
2026/10/8 15:31:24 网站建设 项目流程

如果你也对“家里自动感光”“根据阳光自动拉窗帘”这类场景感兴趣,就迟早得解决同一个问题:怎么让单片机“看懂”环境亮度。我给ESP32接上GY-30光照传感器,只用两块模块加四根杜邦线,就实现了实时光照度读取,整个过程不需要任何模电基础,零基础也能照做。这篇文章会从模块选型、接线、开发环境配置,一直讲到手写通信代码和实际踩坑,目标是让你看完后自己也能在半小时内从串口看到真实的勒克斯数值。

GY-30模块的核心是一颗型号为BH1750的数字环境光传感器,它输出的是I2C数字信号,不像光敏电阻那样还得自己换算电压。对新手而言,这意味着硬件上只需接两根信号线,代码上只要读两个字节,学习曲线非常友好。我当年第一次接这类传感器时,踩了不少“I2C扫不到设备”“数据一直为0”之类的坑,所以这篇文章里我会把排查方法一起说清楚,尽量让你一次点亮。

1. 为什么入门光照测量首选GY-30,而不是光敏电阻

1.1 光敏电阻和数字传感器,差别在哪里

网上很多教程会让新手用光敏电阻(比如GL5528)配合ESP32的ADC引脚测光,理由是元件便宜、原理简单。但实际用起来,光敏电阻有三个让新手劝退的问题:阻值随光照是非线性变化的,不同批次元件差异很大;ADC读取到的电压需要自己标定换算成物理亮度单位;它还容易受温度、湿度影响。

GY-30这类数字传感器则完全不同。BH1750芯片内部集成了光电二极管、放大器和ADC,直接把接收到的光信号转换成数字量,通过I2C总线交给你。你在代码里拿到的直接就是“勒克斯(lx)”数值。可以这么类比:光敏电阻像老式的旋钮水龙头,开大一点小一点全凭手感;GY-30像带刻度的电子水表,读数明确,误差可控。

1.2 BH1750芯片的几个关键参数

在动手之前,先搞清楚这颗芯片能干什么,很有利于后续排错。我把实际最常用的几个特性整理成了表格:

参数数值说明
供电范围3.0V ~ 5.5VGY-30模块一般带稳压,3.3V供电最省心
测量范围1 ~ 65535 lx室外的烈日直射可达6万到10万lx,模块会饱和
分辨率1 lx / 0.5 lx / 4 lx由测量模式决定,下文会细说
典型误差±20%日常环境判断完全够用,别拿它做科学仪器
通信接口I2C支持标准100kHz和快速400kHz
模块尺寸约19mm × 13mm比一块硬币大不了多少

这里要特别说一下“测量范围到65535”的问题。很多资料写它的量程是“0-65535 lx”,听起来很猛,但BH1750手册里明确推荐的分辨率组合,H分辨率模式2下最大能测到约54612 lx,L模式约更高一些。实际项目里,正午阳光直射常常超过6万lx,这时候读数会封顶或者进入饱和误差,所以用在室外场景时要给模块加遮阳罩或选更低分辨率的测量模式。

1.3 买GY-30模块时的三个避坑点

TB上搜“GY-30”或“BH1750”,能搜到好几种长得差不多的模块,有GY-30、GY-302,还有光秃秃的裸板。我买过几次,给新手的建议是:

  • 优先买带稳压电路、自带I2C上拉电阻的模块,这样既可以用3.3V供电,信号线也不会因为缺少上拉而出现乱码。
  • 注意看模块上有没有“ADDR”引脚。有些模块把这个引脚固定接地,有些固定接高,这决定了I2C地址,直接影响你代码里的设备地址。
  • 买的时候让店家送几根母对母杜邦线,新手做实验不需要焊接,面包板加杜邦线就够。

2. 接线、环境与第一个点灯测试:先把手弄脏

2.1 认识ESP32开发板的引脚

市面上常见的ESP32开发板是DevKitC、NodeMCU-32S这类带有USB转串口芯片的版本。无论是哪种,板上都有一排排金属针脚。新手不需要把所有引脚都弄明白,只需要记住几个重点:

  • 3V3:输出3.3V电源,给GY-30供电
  • GND:公共地线
  • GPIO21与GPIO22:默认的I2C数据线SDA和时钟线SCL

为什么默认是21和22?这是ESP32芯片内部定义的I2C控制器默认映射。只要不是极其特殊的情况,新手完全不用改映射,接21和22就行。

2.2 GY-30模块的四根线,一根都不能接错

GY-30模块绝大多数是四个引脚:VCC、GND、SCL、SDA。接法极其简单:

GY-30引脚接到ESP32引脚说明
VCC3V33.3V供电
GNDGND共地
SCLGPIO22I2C时钟线
SDAGPIO21I2C数据线

有人可能会问,GY-30模块既然支持5V,为什么要接3.3V?原因有二:第一,ESP32的GPIO是3.3V电平,如果模块用5V供电,SDA/SCL上的高电平也会被拉到5V左右,长期直连可能损伤ESP32引脚;第二,模块内部如果带稳压和上拉,3.3V供电已经能稳定工作。我在测试中分别用3.3V和5V供电对比过,读数没有可感知的差别,因此一律推荐3.3V。

2.3 开发环境:Arduino IDE二选一的建议

我见过不少零基础的读者在“环境搭建”这一步卡住超过一周。这里我只推荐两条路:

对于只是想快速上手、用完即走的人,选Arduino IDE。安装好后,在“文件 → 首选项 → 附加开发板管理器网址”里填入ESP32的包管理地址(官方推荐的json地址,网上很容易搜到),然后在“工具 → 开发板 → 开发板管理器”里搜索esp32并安装。需要注意,国内网络有时候下载很慢,如果卡住可以换网络或找离线安装包方式,这也是很多群里流传“esp32离线包”的原因。

如果想认真做项目、将来要写很多文件和管理依赖,推荐PlatformIO。它作为一个VSCode插件,不像Arduino IDE那样用“草图”组织代码,而是工程化地管理库和源码。我日常开发用PlatformIO比较多,因为它能一键配置开发板型号。新手如果没接触过VSCode,建议还是先用Arduino IDE跑通第一个程序,再考虑迁移。

装好环境后,第一件事不是连传感器,而是先点个内置LED。在开发板工具栏里选择对应的ESP32型号,把下面这个最简单程序烧进去,串口能看到“Hello ESP32”,就说明环境和驱动都没问题。

3. GY-30的I2C通信原理:地址、指令与测量模式

3.1 I2C两根线,怎么把数据传输回来

I2C总线只有两根线:SCL(时钟)和SDA(数据)。所有挂在总线上的设备都共用这两根线,靠“地址”区分彼此。可以把它理解成教室里的点名:

  • 主机(ESP32)喊一个地址:“0x23,起来回答问题。”
  • 对应地址的从机(GY-30)响应,开始读写。
  • 其他地址的设备假装没听见,继续挂机。

BH1750上电后默认地址由ADDR引脚决定。ADDR接地时,7位地址是0x23;ADDR接VCC时,7位地址是0x5C。这个7位地址在Arduino的Wire库里基本可以直接填进Wire.beginTransmission()函数。但很多教程、库文档说法不一致,导致新手经常犯迷糊。我强烈建议:接线后先跑I2C扫描程序,让代码把实际设备地址打印出来,你再用这个地址填进业务代码。这样比所有猜测都靠谱。

3.2 BH1750的指令系统:动动嘴巴就能控制

BH1750没有细分的中断寄存器操作,它通过I2C写入一个8位指令字节,就能完成大部分设置。新手先把下面这几条记住就够了:

指令字节含义备注
0x00关机进入低功耗
0x01开机上电后一般要发送一次
0x07重置清除寄存器状态
0x10连续H分辨率模式1 lx分辨率,测量约120ms
0x13连续L分辨率模式4 lx分辨率,测量约16ms
0x20一次性H分辨率模式测量完成后自动关机

以连续H分辨率模式为例,代码里只需要三步:

  1. Wire.beginTransmission(设备地址)
  2. Wire.write(0x10)
  3. Wire.endTransmission()

写完指令后,芯片会开始采样。等120ms以上,再从芯片里读两个字节出来合并,就得到了光照原始值。

3.3 分辨率和换算:为什么H模式要除以1.2

BH1750内部测量结果是一个16位的原始值,范围是0到65535。如果不做换算,这个原始值并不直接等于勒克斯。常用的H分辨率模式下,数据的单位其实是“0.71 lx”?不对,这里需要说清楚。

很多教程里的公式是:lux = raw / 1.2。这个1.2是H分辨率模式下精度校正系数。BH1750手册给出的典型关系是:H模式(1 lx分辨率)下,lux = raw / 1.2。为什么不是直接除以1?因为芯片出厂时为了保证产品一致性,把ADC结果做了一定比例缩放,1.2是手册给出的常规转换系数。如果进入H分辨率模式2(0.5 lx分辨率),换算系数就要变成0.6。

这个换算关系我建议直接把它固化到读取代码里,而不是每次手动算。代码看一眼就知道数值含义,方便很多。

4. 手写25行代码,让ESP32读回光照数值

4.1 原理先行:读取流程拆解

完整读取一次光照度,实际上是“发指令、等采样、收数据、做换算”四个动作:

  1. 发送“连续H分辨率模式”指令,让传感器进入测量周期。
  2. 延时150ms左右,等待芯片完成内部采样。H模式典型测量时间是120ms,预留150ms是为了确保数据稳定,时间太长不影响性能,太短则可能读到上一帧旧值。
  3. 发一个requestFrom请求,让ESP32从传感器设备读取2字节数据。
  4. 把2字节拼成16位整数,除以1.2,得到勒克斯值。

4.2 完整可复制的Arduino代码

下面这段代码不需要任何第三方库,只依赖ESP32自带的Wire库,编译后直接就能用。为了使新人能看懂,我把注释写得比较啰嗦:

#include <Wire.h> // 根据I2C扫描结果确定地址: // ADDR引脚接地 -> 7位地址0x23 // ADDR引脚接高 -> 7位地址0x5C #define BH1750_ADDR 0x23 void setup() { Serial.begin(115200); // GPIO21 -> SDA, GPIO22 -> SCL Wire.begin(21, 22); // 开机指令:让传感器从低功耗状态恢复 Wire.beginTransmission(BH1750_ADDR); Wire.write(0x01); Wire.endTransmission(); delay(200); Serial.println("GY-30 sensor ready"); } // 读取当前光照度,单位lx float readLux() { uint8_t buffer[2] = {0, 0}; // 发送连续H分辨率模式指令(0x10) Wire.beginTransmission(BH1750_ADDR); Wire.write(0x10); Wire.endTransmission(); // 等待采样完成,留一点余量 delay(150); // 向传感器请求2字节数据 Wire.requestFrom(BH1750_ADDR, 2); if (Wire.available() < 2) { return -1.0f; } buffer[0] = Wire.read(); buffer[1] = Wire.read(); // 高字节在前,合成16位原始值 uint16_t raw = (buffer[0] << 8) | buffer[1]; // H分辨率模式换算系数为1.2 float lux = raw / 1.2f; return lux; } void loop() { float lux = readLux(); if (lux < 0) { Serial.println("read error"); } else { Serial.printf("Light: %.1f lx\r\n", lux); } delay(1000); }

复制后点上传,打开串口监视器,波特率选115200,每秒钟应该能看到一行光照数据。用手遮住模块感光窗口,数值会明显下降;用手电筒或手机LED灯照过去,数值会快速上升。

4.3 如果读数一直是0,先做这三件事

有四种情况会让读数一直为0或报错:第一,模块没真正进入测量模式,Power On指令没发成功;第二,地址填错,requestFrom返回的可用字节数不足;第三,供电不稳导致传感器复位,I2C总线卡死;第四,杜邦线接触不良。建议在loop里打印原始值,而不是只打印换算后的lux,通过原始值可以快速区分“芯片完全没响应”和“芯片响应了但数据异常”。如果raw长时间为0,优先检查接线;如果lux为-1,优先检查地址和上拉电阻。

5. 实测数据与五个高频踩坑点

5.1 一组有参考价值的实测数据

把GY-30放在不同的典型环境里,我采集到的数据大致如下:

场景测量值体感说明
深夜关灯房间1 ~ 10 lx接近黑暗
拉窗帘的白天室内50 ~ 150 lx微暗
书桌台灯照射下300 ~ 800 lx正常阅读照明
窗台旁散射光1000 ~ 3000 lx明亮但不刺眼
正午阳光直射窗边40000 lx以上可能接近量程上限

这些数值并不是绝对标准,但可以帮你判断读数是否正常。如果你在室内开着普通日光灯读数却只有几十,多半是传感器感光窗口被遮挡,或者模块安装方向不对。

5.2 坑一:I2C扫描不到设备,排查链路看这里

这是所有I2C项目最常见的问题,没有之一。我的排查顺序固定是:

  1. 确认供电:用万用表量模块VCC和GND之间是不是3.3V左右,没有万用表就先换一根USB线重新上电试试。
  2. 确认接线顺序:SDA和SCL有没有接反,GND有没有漏接。杜邦线从面包板孔里松脱也是常有的事。
  3. 跑I2C扫描程序,看是否有设备在总线上应答。扫描程序代码很短,网上到处都有,核心就是遍历地址0x01到0x7F,逐个beginTransmission并检查endTransmission返回值。
  4. 如果扫描不到,而模块单独接线又正常,考虑是不是总线缺少上拉电阻。部分廉价的GY-30模块板载没有上拉电阻,可以在SDA、SCL上分别接一个4.7kΩ电阻到3.3V。

我把这一步叫作“先让总线说话,再让传感器工作”。直接跑业务代码排查会很难受,但扫描程序能快速缩小范围。

5.3 坑二:地址0x23和0x46、0x5C到底怎么填

关于BH1750地址的争执,在各大论坛从没停过。我现在用比较直白的说法总结:

  • BH1750手册上把地址写为“0100011”(二进制)时,这是7位地址,对应0x23。这是多数模块的默认地址。
  • Arduino的Wire库内部会把你传进去的地址左移一位作为8位地址发送,所以你在代码里填7位地址0x23即可。
  • 有些封装库用8位地址,此时会填0x46(0x23左移一位)。如果你看到某份代码填0x5C但你的模块扫出来是0x23,不要盲目怀疑,看看是不是ADDR引脚电平不同。

最稳妥的方法依然是先跑I2C扫描,扫描打印出的地址就是代码里该填的地址。有了扫描结果,任何“为什么别人的代码地址和我不一样”的问题都能迎刃而解。

5.4 坑三:供电电压与电平匹配

模块标称支持3V到5V,不代表5V就一定更“稳”。在ESP32上接3.3V,整条链路都是同一电平域,出问题概率最小。如果供电高于3.6V,SDA/SCL高电平可能超过ESP32的GPIO耐压,长期使用会有损坏风险。而且模块自带的LDO在压差较大时会微弱发热,虽然在绝对数值上不算严重,但在长时间运行的物联网设备上,没有理由给自己埋这个隐患。

5.5 坑四:感光窗口朝向与物理遮挡

GY-30模块正面那个方形或圆形的小窗口是感光区域,它前面有一层透镜。安装时注意两点:

  • 窗口不能被外壳完全封死。很多新手把模块塞进塑料盒,只留排针出来,读数直接落到个位数。需要在窗口位置开一个透明孔洞。
  • 传感器最好不要垂直对着强光源安装,否则反射光会偏大。例如测桌面照度时,正确姿势是把感光窗口朝上,平放在桌面,而不是竖着朝天花板。

我也踩过一次“传感器贴着墙纸,读数永远是个位数”的坑,排查了半天,最后发现是感光窗口被墙纸挡住了。

5.6 坑五:读数跳变,和光源类型有关

BH1750本身采样时间在毫秒级,测量的是某一时刻的瞬时照度。如果你用的LED灯是PWM调光,人眼看不出频闪,但传感器会捕捉到明暗交替,表现为读数不断跳动。这不是模块坏了,而是采样时刻撞上了PWM脉冲。解决方式有两种:软件上取多次平均值,例如连续读8次数值求平均;硬件上在感光窗口前加扩散片,让光线更均匀。对大多数家居场景,软件滤波就足够了。

6. 从“能读数”到“能落地”:三个实在的扩展方向

6.1 做一个最简单的自动补光灯,注意滞回区间

光有读数还不够有意思,很多人的第一个扩展动作是根据亮度自动控制灯。设计逻辑很简单:亮度低于阈值就开灯,高于阈值就关灯。但细节在阈值里——如果你只在30 lx时开、80 lx时关,那在临界点附近,读数上下抖动会导致继电器反复开关,一分钟能咔嗒十几次。

我的做法是引入“滞回”:低于40 lx开灯,高于100 lx才关灯,中间40到100是一个缓冲区间。这类似空调温控的温差设定,能避免设备在边界反复跳变。控制继电器时记得用ESP32引脚驱动三极管或继电器模块,不要直接用GPIO去驱动大电流负载。

6.2 接一个OLED屏幕,或者是Web页面展示

想实时看到数据,除了串口,还可以在ESP32上挂一个SSD1306的0.96寸OLED,用U8g2库把光照数值直接显示在屏幕上。这一步本质上只是把Serial.printf换成了display.print,但体验完全不同,特别适合做一个桌面小摆件。

如果想让手机、电脑随时看数据,ESP32最方便的地方是自带WiFi。可以起一个轻量Web服务器,浏览器打开IP地址就能看到当前光照度曲线。考虑到很多新手会纠结“ESP32连不上网”“配网很麻烦”,可以从“固定WiFi账号密码”的版本写起,之后再研究一键配网。这一步能让你把传感器从“调试台”真正搬到“使用场景”里。

6.3 一个ESP32接多个光照传感器,做更聪明的判断

最后给已经入门的人留一个进阶思路:一块ESP32可以同时接多个GY-30。做法是把不同模块的ADDR引脚分别接GND和VCC,这样两个模块的I2C地址就不一样,ESP32可以分别读取。比如一个传感器放在窗边测室外参考光,一个放在桌面测工作区实际亮度,然后程序里做加权平均或差值判断,实现更合理的自动调光逻辑。我自己的书桌就装了这么一套:窗边传感器判断“今天光线够不够”,桌面传感器判断“我要不要开台灯”,两个数据一起决定,比单点测量智能得多。

多传感器还会带来一个新问题:I2C总线上挂的设备越多,线路越长,信号完整性越差。超过两个模块后,如果读数偶尔出错,可以把I2C时钟频率从400kHz降到100kHz,代价只是读取速度变慢,但稳定性会明显好转。这个参数在Wire库初始化时可以直接指定,比如Wire.setClock(100000)。

回到文章开头说的“半小时跑通”这件事,其实并不夸张:接线十分钟,环境十分钟,代码四分钟,剩下六分钟全是跑I2C扫描和跟杜邦线较劲。如果非要说一个最值得你记住的操作习惯,那就是“先扫描,再写业务代码”这八个字。我见过太多人在没确认设备地址和接线的情况下反复烧录同一个程序,白白浪费大量时间。GY-30是一个非常宽容的入门传感器,只要供电正常、地址正确、接线不反,它几乎不会给你添麻烦。希望这篇内容能帮你顺利点亮第一个光照数值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询