做手势感应、自动亮屏、靠近触发这类小项目,最烦两件事:一个传感器只能测距离,想顺便看环境光还得再加一颗;测距传感器动不动要自己算时间差、算脉宽,代码写一堆。VCNL4040把这两件事合在一个芯片里——0到200毫米的接近距离,0.0125到6553勒克斯的环境照度,全部I2C读出来,不用自己算任何东西。
这颗芯片来自Vishay,自带一颗红外LED做接近检测,内置环境光感光管,通信只走SCL/SDA两根线。Adafruit的这块小板子把稳压和电平转换都做了,3.3V和5V的板都能直接插,还有STEMMA QT/Qwiic插座,不焊也行。这篇就按官方vcnl4040_test示例走一遍,把接线、装库、读数和几个容易踩的坑说清楚,Arduino Uno和ESP32都能用。

先认识这颗芯片:能测什么、测多准
VCNL4040不是超声波那种"声呐"式测距,它靠自身红外LED发射、目标反射回来被接收管接收,数值越大代表越近。有效量程0到200毫米,大概7英寸多一点,适合靠近触发这种场景——手扫过亮灯、脸靠近亮屏、物体靠近报警,超过20厘米它就力不从心了,别拿去做远距离避障。
照度这边分辨率细,0.0125勒克斯起步,上限6553勒克斯。日常室内100到500勒克斯、阴天室外1000勒克斯左右、直射阳光几万勒克斯,室内做自动调光完全够。芯片还能单独读出raw white light,就是白光通道原始值,做白平衡判断或简单色温比较能用。
板子上带一颗LDO稳压器,VIN接3V到5V都可以,自己出3.3V给芯片;I2C引脚已经做了电平转换,Arduino Uno这种5V板直接接SDA/SCL不会烧芯片。地址固定0x60,上电就能找到,不需要手动改地址。
引脚与供电
模块一共6个焊盘,真正用的就4个:VIN、GND、SCL、SDA。剩下INT是中断脚,可以配置成"距离小于阈值时拉低"或者"照度超过阈值时触发",不用中断就悬空。3Vo是板上3.3V稳压输出,最多带100mA,一般留着不用。

- VIN:供电脚,3到5V都行。Arduino Uno接5V,ESP32接3.3V
- GND:共地,必须接
- SCL:I2C时钟,接主控的SCL(Uno是A5,ESP32一般是GPIO22)
- SDA:I2C数据,接主控的SDA(Uno是A4,ESP32一般是GPIO21)
- INT:中断输出,可选
- 3Vo:板上3.3V输出,一般不用
STEMMA QT版本的板子两边各有一个JST-SH 4Pin插座,直接用Qwiic/STEMMA QT线插,对应VIN/GND/SCL/SDA,不用自己接杜邦线。注意这种线的VIN是跟着板子供电走的,主控那边如果是5V逻辑,VIN就是5V;如果是ESP32这类3.3V板,VIN就是3.3V,模块都能吃。
接线:四根线搞定
按官方推荐接法,Arduino Uno/Nano直连就是5V、GND、A5、A4四根。ESP32-DevKitC一般是3V3、GND、GPIO22(SCL)、GPIO21(SDA)。VCNL4040的I2C地址0x60,接好后上电就能扫到。

这里有一个小坑:部分廉价ESP32开发板在USB供电时3V3不稳,VCNL4040偶尔会在begin()时报"Couldn't find VCNL4040 chip"。这种情况先别急着怀疑代码,换一根短一点的USB线,或者给3V3并联一个100μF电解电容,基本能解决。I2C上拉在模块里已经有了,不用自己外接上拉电阻。
装库:先把Adafruit_VCNL4040装上
打开Arduino IDE,菜单"草图→包含库→管理库"(老版本在"项目"菜单下),弹出库管理器窗口后搜索"Adafruit VCNL4040",点安装。安装过程会自动提示装依赖,把Adafruit BusIO一起装上。ESP32开发板如果还没装过板卡包,要先在"文件→首选项→附加开发板管理器网址"里加Espressif的json,再去开发板管理器装ESP32。

装完在"文件→示例→Adafruit VCNL4040"里能看到vcnl4040_test,这就是官方测试示例,不用自己写一行代码就能读数。
跑示例、看读数
代码不长,setup()里初始化串口,调用vcnl4040.begin()探测芯片,然后把当前LED电流、占空比、积分时间这些配置打印一遍;loop()里每500毫秒读一次Proximity(接近值)、Lux(照度)、WhiteLight(白光原始值),三行数据打到串口。完整代码如下,注意include用引号而不是尖括号——这是发布规范里的铁律,尖括号在部分strip流程里会被吞。
#include "Adafruit_VCNL4040.h"
Adafruit_VCNL4040 vcnl4040 = Adafruit_VCNL4040();
void setup() {
Serial.begin(115200);
while (!Serial) { delay(1); }
Serial.println("Adafruit VCNL4040 Config demo");
if (!vcnl4040.begin()) {
Serial.println("Couldn't find VCNL4040 chip");
while (1);
}
Serial.println("Found VCNL4040 chip");
Serial.print("Proximity LED current set to: ");
switch(vcnl4040.getProximityLEDCurrent()) {
case VCNL4040_LED_CURRENT_50MA: Serial.println("50 mA"); break;
case VCNL4040_LED_CURRENT_75MA: Serial.println("75 mA"); break;
case VCNL4040_LED_CURRENT_100MA: Serial.println("100 mA"); break;
case VCNL4040_LED_CURRENT_120MA: Serial.println("120 mA"); break;
case VCNL4040_LED_CURRENT_140MA: Serial.println("140 mA"); break;
case VCNL4040_LED_CURRENT_160MA: Serial.println("160 mA"); break;
case VCNL4040_LED_CURRENT_180MA: Serial.println("180 mA"); break;
case VCNL4040_LED_CURRENT_200MA: Serial.println("200 mA"); break;
}
Serial.print("Proximity LED duty cycle set to: ");
switch(vcnl4040.getProximityLEDDutyCycle()) {
case VCNL4040_LED_DUTY_1_40: Serial.println("1/40"); break;
case VCNL4040_LED_DUTY_1_80: Serial.println("1/80"); break;
case VCNL4040_LED_DUTY_1_160: Serial.println("1/160"); break;
case VCNL4040_LED_DUTY_1_320: Serial.println("1/320"); break;
}
Serial.print("Ambient light integration time set to: ");
switch(vcnl4040.getAmbientIntegrationTime()) {
case VCNL4040_AMBIENT_INTEGRATION_TIME_80MS: Serial.println("80 ms"); break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_160MS: Serial.println("160 ms"); break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_320MS: Serial.println("320 ms"); break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_640MS: Serial.println("640 ms"); break;
}
Serial.print("Proximity integration time set to: ");
switch(vcnl4040.getProximityIntegrationTime()) {
case VCNL4040_PROXIMITY_INTEGRATION_TIME_1T: Serial.println("1T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_1_5T: Serial.println("1.5T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_2T: Serial.println("2T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_2_5T: Serial.println("2.5T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_3T: Serial.println("3T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_3_5T: Serial.println("3.5T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_4T: Serial.println("4T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_8T: Serial.println("8T"); break;
}
Serial.print("Proximity measurement high resolution? ");
Serial.println(vcnl4040.getProximityHighResolution() ? "True" : "False");
Serial.println("");
}
void loop() {
Serial.print("Proximity:"); Serial.println(vcnl4040.getProximity());
Serial.print("Ambient light:"); Serial.println(vcnl4040.getLux());
Serial.print("Raw white light:"); Serial.println(vcnl4040.getWhiteLight());
Serial.println("");
delay(500);
}
选好对应的开发板和串口,上传。打开"工具→串口监视器",波特率设115200,应该先看到一串配置打印,然后每0.5秒刷三行数字。

手慢慢靠近模块,Proximity数值会从几十涨到几千(手贴上去一般到6000+),这是反射强度,不是毫米数。想做"手靠近触发",在实际环境里测一个阈值就行,比如大于200认为有物体靠近。Ambient light是换算后的勒克斯值,用手捂住芯片会降到个位数,拿手机手电筒照一下能飙到几千。
几个参数怎么调
示例里所有setXXX()都注释掉了,用的是芯片默认配置,基本够用。真要根据场景调,记住这几条:
LED电流(setProximityLEDCurrent)默认50mA,电流越大红外越亮、测越远,但功耗也大。电池供电的项目建议保持50mA,桌面固定供电可以开到100mA或200mA再试距离。
LED占空比(setProximityLEDDutyCycle)默认1/40,是LED亮的时间比例。占空比越小(比如1/320)越省电,但抗光干扰能力下降。室内用1/40没问题,户外强光下可以调大占空比。
环境光积分时间(setAmbientIntegrationTime)默认80ms,这一档量程最宽(最大6553.5 lux)但分辨率最粗。做夜灯感应、关心低光精度,可以调到640ms,最大量程缩到819.2 lux,但每个bit代表的光强更小,微弱变化也能抓到。
接近积分时间默认1T,测速度最快;8T分辨率更高但每轮测量更慢。做高速手势识别用1T,做"是否有人靠近"的静态检测用8T稳。ProximityHighResolution默认开,建议保持True,关掉会丢掉最后一位分辨率。
ESP32上特别提醒
ESP32用这颗芯片几乎零改动,库自带支持,只要把Wire对应的SDA/SCL传进去就行。默认情况下begin()会用Wire,ESP32 Arduino核心默认SDA=21、SCL=22,和上面接线图一致。如果要改引脚,在begin()之前调用Wire.begin(SDA_PIN, SCL_PIN)再vcnl4040.begin()即可。
另外ESP32在双核下loop()跑的很快,如果担心I2C冲突可以把读传感器放到一个task里或者简单加个millis()判断定时读,不用每帧都读,毕竟人眼对光照变化的响应也就百毫秒级别,500ms读一次完全够。
到这里VCNL4040就能在Arduino和ESP32上稳定出数了。它不是一颗"万能距离传感器",20厘米是硬上限,想做小车避障得换VL53L0X这类激光ToF;但在桌面小玩意、自动夜灯、手势触发、靠近唤醒这些场景里,一颗芯片带两个功能、接线又简单,性价比比分开买两颗高得多。










