☰
基于ESP32的智能太阳能路灯设计与实现
2026/10/19 8:45:08 网站建设 项目流程

路灯是城市里最容易忽略却最离不开的基础设施之一。传统路灯布线成本高、维护靠人工巡检、白天太阳再好晚上也照旧拉闸。我花了两周时间,基于ESP32做了一套智能太阳能路灯的完整方案:光控电路决定什么时候亮,锂电池精细监测防止过充过放,无线视频监控直接看现场画面,还有配套APP随时调参。这套方案硬件加软件都能落地,低成本、可量产,也能当课程设计和嵌入式入门实战项目。下面我把整个设计和踩过的坑完整拆出来,从系统架构到每一条ADC采样代码,全部按可直接复制的标准来写。

整个项目跑下来可以用“模块化硬件 + 轻量化协议 + 看板式APP”来概括。ESP32是主控核心,太阳能板负责取电,锂电池负责储能,光照传感器负责环境感知,摄像头负责现场画面,后端用MQTT做数据和视频通道,前端App负责控制和展示。听起来模块很多,但选型和接口全部向“少引脚、低功耗、易调试”靠拢。

1. 整体系统设计与核心思路

1.1 用ESP32做路灯主控,底气在哪

很多人一听路灯第一反应是STM32或者51单片机,但ESP32在这个场景几乎是碾压级的。先说成本:ESP32模组批量采购价压到十块钱以内,但片上集成了双核240MHz CPU、WiFi、蓝牙、ADC、触摸引脚、RTC、CAN控制器,外设丰富度远超同价位MCU。再说联网能力:路灯的核心诉求是感知和控制无线化,ESP32自带WiFi模块,省掉外接ESP8266或者透传模块的麻烦,单芯片把“采数据、联网、跑协议栈”全干完了。

功耗这块ESP32也有讲究。阳光下跑全速没毛病,阴雨天要靠电池余粮,我用了Modem Sleep和Light Sleep搭配,视频关闭状态整机功耗能压到0.8W左右,实际测试夜间纯光控模式连20Wh的小电池都能撑两三天。当然视频监控开启时功耗会飙升到1.5W以上,这块我会在第五节单独讲怎么取舍。

从开发效率来说,ESP32直接吃Arduino生态和ESP-IDF,库函数齐全,OTA升级、MQTT客户端、摄像头驱动都有现成的实现,两周出原型完全来得及。A同学试过用51方案做同款,光调TCP/IP协议栈就折腾了一周半,ESP32这边一个小时就出活了。

1.2 硬件架构的模块划分

整个系统我拆成六个功能块,每块单独设计、单独测试,再合体联调,出问题好定位。

  • 电源管理模块:太阳能板输入经过降压稳压芯片转为电池充电电流,电池再通过升压芯片稳定输出5V/3.3V给系统供电,这一块决定整个路灯的续航天花板。
  • 感知模块:光照传感器(我用的BH1750)实时读取环境光强度,夜晚自动开灯、白天自动关灯,还可以叠加阈值延时避免瞬时遮挡误触发。
  • 电量监测模块:锂电池电压和电量百分比直接关系到设备续航判断,我用INA226做库仑计采样,同时用ESP32内部ADC做双保险。
  • 控制模块:MOS管驱动电路控制LED灯板的开关,支持PWM调光,可以根据不同时段(前半夜全亮、后半夜半亮)自动调节亮度。
  • 无线视频模块:ESP32-CAM子板负责抓拍和视频流推送,通过WiFi传到APP端实时查看现场画面,同时支持本地SD卡存储。
  • 人机交互模块:手机APP通过MQTT协议下发控制指令、接收监测数据,不需要复杂的P2P穿透,公网代理即可完成。

这样的模块化设计还有一个额外好处:量产维修时不用整机返工,哪块坏了换哪块,成本极低。

1.3 为什么光照检测不用光敏电阻

一开始我把光照检测想简单了,直接用光敏电阻加分压电路接ADC,结果阴天、晴天倒是能区分,但黄昏和黎明的那几个小时输出严重不准。原因是光敏电阻的阻值和照度不是线性关系,温度漂移还特别大,同一时间两个光敏电阻能差出30%的读数。

后来换成了BH1750数字光照传感器。这是I2C接口的芯片,直接输出勒克斯(Lux)单位,量程0-65535Lx,分辨率能到1Lx,完全覆盖路灯场景的需求,而且内置16位ADC,不需要自己做线性标定。用I2C的另一个好处是抗干扰能力强,线束可以拉到1米以上,不像模拟信号线一长就被电机或开关电源干扰。

注意:BH1750有两个I2C地址可选(0x23或0x5C),如果板子上同时挂了其他I2C设备,要注意地址冲突。我这次地址选了0x23,和SSD1306 OLED屏错开。

1.4 通信协议与数据链路的取舍

数据链路我直接选MQTT,没有走自定义TCP协议,理由是:

  • MQTT的发布订阅模型天然适配“多个路灯对应一个APP”的场景,每盏路灯是一个client_id,APP通过通配符订阅所有路灯的状态。
  • QoS级别可以保证指令不丢失,我用QoS 1下发开关灯指令,实测在弱网环境下重试机制能有效避免指令丢失。
  • MQTT broker(我用的EMQX,可去平台化部署)自带心跳和遗嘱机制,路灯掉线APP能立刻感知。

视频流没走MQTT,这玩意儿不适合传大流量二进制。视频走HTTP MJPEG流,APP用WebView或者VideoView直接拉取,两条链路互不干扰。这是很多新手容易踩的坑:非要把视频塞进MQTT,最后把整个broker和带宽都搞死了。

2. 核心硬件模块的详细设计与选型

2.1 太阳能板与充电控制电路

太阳能路灯的源头是太阳能板组件,不能随便拿一块单晶板直接怼到电池上。我用的是18V/30W的单晶硅太阳能板,开路电压在21-22V,最大功率点电压约18V,之所以选18V而不是6V或12V,是因为18V板子才能在阴天弱光下依然输出高于电池电压的充电电压。

充电控制芯片我用了CN3791(兼容TI的BQ24650方案,但便宜很多)。这是一颗专门针对太阳能板最大功率点跟踪(MPPT)的升压型充电管理芯片,它通过调节PWM占空比让太阳能板始终工作在最大功率点附近,避免传统PWM充电在阴天时电流不足的问题。关键参数设置:

  • 充电目标电压:4.2V(针对单节18650锂电池)
  • 充电电流上限:2A,由外接采样电阻设定(这个用公式算,2A/0.05Ω=40mV基准电压)
  • 输入电压范围:9V-28V,匹配18V板子的输出范围
  • MPPT使能引脚:接太阳能板正极分压电阻,低于8V时自动进入待机状态

实际测试数据:晴天正午,18V/30W板子对3.7V/10Ah电池组充电,峰值充电电流达2.8A(略超设定上限,因为电池内阻低,CN3791限流了),日均有效充电容量约22Wh,足够支撑10W LED整晚点亮的能耗(10W×10小时=100Wh,配合电池容量和阴天策略足够)。

2.2 锂电池电压与电量检测方案对比

锂电池电量检测是整个系统的“仪表盘”,做不好就会出现“前一秒电量20%下一秒直接关机”的尴尬。我先对比了几种方案:

  • 方案一:电阻分压+ADC。成本最低,但只能测电压不能测电量,锂电池放电平台电压在3.7V到3.3V之间变化很平缓,无法精确估算SOC(荷电状态)。而且ESP32内部ADC在3.3V供电时有非线性误差,直接测4.2V还得加分压和电压跟随,精度雪上加霜。
  • 方案二:库仑计芯片(INA226)+电压采样。INA226是TI的高侧电流/电压监测芯片,可以同时测电池电压和充放电电流,内部累加电量(库仑计数)实现真正的SOC估算,精度可以做到5%以内。I2C接口,16位ADC,电压分辨率0.625mV,电流分辨率0.1mA(配合合适的采样电阻)。价格大概两三块钱,性价比极高。
  • 方案三:MAX17048(电量计)。号称免校准电量计,用专利算法估算SOC,但价格贵一倍,而且对小容量电池(<1000mAh)适配一般。

我最后选了INA226。为什么不用MAX17048,因为路灯电池容量通常在10Ah以上,INA226的采样电阻功耗可以忽略不计,而MAX17048的算法在10Ah级别精度反而不如库仑计直接累加准确。

INA226接线注意:采样电阻Rshunt我用的是5mΩ(5毫欧,合金电阻),高侧采样放在电池正极和负载之间。I2C地址通过A0/A1引脚组合设定,我设成0x40。读取寄存器时要注意先写配置寄存器(Configuration Register)使能电压和电流同时采样,然后读总线电压寄存器(Bus Voltage Register)和电流寄存器(Current Register),两个值乘起来就是实时功率。

下面是一段ESP32读取INA226的示例代码(Arduino框架):

#include <Wire.h> #include <INA226.h> INA226 ina226; void setup() { Wire.begin(21, 22); // 自定义I2C引脚 ina226.begin(0x40); ina226.setMaxCurrentShunt(10.0f, 0.005f); // 设置最大电流10A,采样电阻5mΩ ina226.setAverage(INA226_AVERAGE_64); // 64次平均,滤掉开关噪声 ina226.setAlertPol(INA226_ALERT_POL_LOW); } void loop() { float voltage = ina226.getBusVoltage(); // 单位V float current = ina226.getCurrent(); // 单位A float power = ina226.getPower(); // 单位W float shuntVoltage = ina226.getShuntVoltage(); // 单位mV printf("电池电压: %.3f V, 电流: %.3f A, 功率: %.3f W\n", voltage, current, power); delay(1000); }

实测校准:用电子负载设定1.000A放电,读到的INA226电流是0.998A,误差0.2%,完全够用。

2.3 光照检测模块的精确配置

BH1750的数据读取也走I2C。芯片需要大约120ms完成一次采样(高分辨率模式),所以在循环里不要狂读,100ms间隔足够。还要注意一点:BH1750分连续测量和单次测量两种模式,电池供电场景推荐用单次测量。

我用的库是SparkFun_BH1750(兼容各种Arduino板),初始化时设置MTreg值。默认MTreg=69,对应测量范围1-65535Lx。想要更低的功耗或者更宽高量程,可以调MTreg值,范围31-254。高量程场景可以把MTreg调小,但分辨率会下降。路灯场景我设MTreg=80,这样黄昏和正午都能区分。

开机后第一次读取会返回一个比较大的值(芯片内部校准时间),需要在程序初始化完成后延时500ms再读第一个有效值,不然判断光控阈值会误判。

2.4 LED驱动与MOS管控制电路

LED灯板这块我用了一颗大功率白光LED阵列(3.3V/10W),配合恒流驱动芯片PT4115。PT4115是降压型LED恒流驱动IC,输入电压范围6V-30V,输出电流由外接电阻设定,我用0.1Ω的采样电阻对应1A恒流输出,实测LED灯板电压降3.1V,功率约3.1W(10W灯板降到3W,是为了控制电池功耗,实际亮度足够夜间照明)。

控制开关和调光用了两颗AO3400 N沟道MOS管。一颗接LED驱动芯片的EN引脚(使能控制,实现开关灯),一颗接PWM信号(实现亮度调节)。ESP32的GPIO输出3.3V可以完全开启AO3400(其开启电压VGS(th)典型1.35V),但为了保险,我加了1kΩ栅极串联电阻和10kΩ下拉电阻,防止上电瞬间GPIO悬空导致LED闪亮。

PWM调光我用了ESP32的LEDC外设,频率设20kHz。为什么不用1kHz?因为人耳能听到20kHz以下的开关频率造成的啸叫(电感啸叫),LED灯珠虽然人耳听不到,但驱动电感的磁性材料会发出机械噪声。20kHz正好超过人耳听觉上限,同时LEDC在这个频率下的占空比分辨率也足够。

2.5 无线视频监控的硬件实现

视频监控模块我选了ESP32-CAM(AI-Thinker版)。它自带ESP32-S芯片和OV2640摄像头,支持JPEG抓拍和MJPEG视频流,像素最高1600x1200,在局域网内可以跑流畅的VGA(640x480)15fps视频流。模块包含一个Micro SD卡槽,用于本地录像存储。

为什么视频不直接接在主控ESP32上?因为ESP32处理视频流会占用大量CPU和内存,分享处理光照检测和MQTT协议的能力会被挤占。用独立ESP32-CAM做视频采集,通过UART或Socket送给主控ESP32,或者更简单——两个ESP32各自独立连WiFi,主控只管控制和数据,CAM只管视频,互不干扰。

我在主控和CAM之间用UART通信,波特率115200,协议很简单:主控发“CAPTURE”命令,CAM拍摄一张JPEG照片通过UART发送回来。这样设计功耗可控,平时CAM处于深度睡眠,只有收到命令才唤醒拍照,非常适合路灯这种非24小时监控的场景。

如果要做实时视频流,建议直接用WiFi直连方式:CAM模块通过STREAM_URL广播自己的HTTP服务,APP直接用WebView访问,不需要经过主控中转。

3. 软件架构与APP设计

3.1 主控ESP32的程序框架

主控程序基于Arduino框架+FreeRTOS,实现三个核心任务:

  • 光照感知与控制任务:每500ms读取一次光照传感器,通过滑动平均滤波消除偶发干扰,然后与阈值比较决定开灯还是关灯,通过LEDC调节PWM亮度。
  • 电池管理任务:每2秒读取一次INA226,计算SOC并调用校准系数修正,超过阈值则触发报警(剩余电量低于20%时通过MQTT推送低电量告警)。
  • MQTT通信任务:负责订阅主题、解析指令、发布状态数据,同时维护心跳包和遗嘱消息。

任务间用FreeRTOS队列传递数据,避免全局变量互相覆盖。比如光照传感器任务把测量值打包成结构体发送到队列,控制任务从队列接收后决定输出PWM。这样数据流清晰,出问题也好排查。

主循环轮询的方式我也写过,但一旦加入WiFi和MQTT,500ms延时经常因为WiFi事件处理被拉长到2秒,响应不及时。所以别偷懒,直接用多线程任务。

3.2 MQTT主题设计与数据协议

主题设计是整个通信系统的骨架,我按照“设备类型/安装位置/设备ID/属性”的规范来做:

路灯/光照强度/{device_id} # 发布光照传感器读数,单位Lx 路灯/电池状态/{device_id} # 发布电压、电流、SOC、功率 路灯/控制指令/{device_id} # 订阅控制指令:ON、OFF、PWM、视频抓拍 路灯/在线状态/{device_id} # 发布在线状态和遗嘱消息 路灯/视频流/{device_id} # 发布视频流URL(仅在视频开启时)

MQTT消息体我用JSON格式,因为好解析、好扩展。举一个例子:

{ "device_id": "SL01", "type": "status", "payload": { "light_lux": 356, "battery_voltage": 3.82, "battery_current": -0.42, "soc": 87, "pwm": 200, "timestamp": 1735700000 } }

订阅端(APP)需要支持通配符“#”,这样一台手机就能订阅“路灯/#”查看所有路灯状态。

注意:MQTT client_id必须全局唯一,多路灯场景不要用默认的随机ID,直接用设备ID(如SL01、SL02),否则会导致broker互相踢线。

3.3 APP功能设计与开发路线

APP这一层有三种实现路线,我对比过:

  • 微信小程序/公众号H5:上线审核相对简单,适合个人开发者快速验证,但因为视频流和长连接限制,流畅度一般。
  • 原生Android APP(Java/Kotlin):适合做深度定制,视频流和控制体验最流畅,但开发周期长,要考虑手机权限适配。
  • MQTT面板型APP(如某开源物联网平台APP):最快,改改配置就能用,但定制性差,控制界面不灵活。

我最后选了原生Android APP(基于Android Studio,用Java写),理由是需要实时视频流叠加MQTT控制,而且要做离线缓存和本地推送。核心功能模块:

  • 设备列表页:罗列所有接入的路灯设备,显示在线/离线状态,点击进入详情。
  • 设备详情页:显示光照强度、电池电压、电量SOC、功率等实时数据,以折线图方式展示历史趋势(本地SQLite存储历史数据,断网也能看)。
  • 控制面板:开关灯、PWM亮度调节滑块、视频抓拍按钮、视频流开关。
  • 设置页:调节光照阈值、低电量报警阈值、设备校时等参数。

APP通过Eclipse Paho Android Client库连接MQTT broker,视频流用WebView加载MJPEG地址来显示。Paho库自带断线自动重连机制,配合MQTT遗嘱消息,能实时感知路灯掉线。

下面是APP端连接MQTT并订阅主题的核心代码(Java):

MqttClient mqttClient = new MqttClient("tcp://broker_ip:1883", "APP_DEVICE", new MemoryPersistence()); MqttConnectOptions options = new MqttConnectOptions(); options.setCleanSession(false); options.setConnectionTimeout(10); options.setKeepAliveInterval(20); options.setWill(new MqttTopic("路灯/在线状态/RoadLight1"), new byte[]{0}, 0, true); mqttClient.setCallback(new MqttCallback() { @Override public void messageArrived(String topic, MqttMessage message) { runOnUiThread(() -> updateUI(topic, message.toString())); } @Override public void connectionLost(Throwable cause) { /* 自动重连逻辑 */ } @Override public void deliveryComplete(IMqttDeliveryToken token) { } }); mqttClient.connect(options); mqttClient.subscribe("路灯/#", 1);

3.4 本地显示与调试利器:OLED屏

虽然APP能看数据,但现场调试总掏手机太麻烦。我在主控板上加了一块0.96寸OLED显示屏(SSD1306),通过I2C挂在同一条总线上,显示四行滚动信息:

  • 第一行:当前光照值(Lx)和PWM占空比
  • 第二行:电池电压和电流
  • 第三行:SOC百分比和低电量报警状态
  • 第四行:MQTT连接状态和WiFi信号RSSI

OLED在灯光下亮度也够,功耗很低(20mA左右),但要注意别让它一直全亮屏,我用5秒刷新一次、无操作10分钟后关屏,省电效果明显。

4. 光照检测与电池电量计算的实操细节

4.1 光照阈值的标定方法

光照传感器输出的数值本身没有绝对意义,必须在实际场景标定阈值。我在不同时间点记录了传感器读数:

  • 正午阳光直射:约32000Lx
  • 阴天白天:约800-3000Lx
  • 黄昏(日落前30分钟):约120-300Lx
  • 夜晚(无月光):约0-2Lx
  • 夜晚(路灯亮起):约5-20Lx

开灯阈值我设在80Lx,关灯阈值设在350Lx,中间的区间是滞回区间,防止光在阈值附近跳动导致继电器反复通断。以80Lx和350Lx为例,当光照从亮变暗,从1000Lx降到200Lx时,还没到80Lx所以不动作,继续降到80Lx以下才开灯;从暗变亮同理,要超过350Lx才关灯。这样上下阈值之间至少差4倍,抗抖效果极好。

此外实际路灯还要考虑“黄昏延时”逻辑:如果光照在临界线上跳绳(比如被云层遮挡露出一丝阳光),可以用软件滤波来处理,比如连续3次采样都低于80Lx才执行开灯,单次瞬时低于80Lx不动作。

4.2 锂电池SOC的计算模型

直接用电压估算SOC容易翻车,我的做法是“库仑计为主,电压校准为辅”。

库仑计算法:SOC = 初始SOC + ∫(I_bat dt) / C_bat,需要INA226实时累加电流和时间片。ESP32上我开了1秒定时器,每秒读取一次电流值乘以1秒再累加到累计电量变量里。

电压校准法:当电池处于长时间静置状态(比如连续5分钟电流小于50mA),认为电池电压已经稳定,用当前电压查表校正SOC。我用的3.7V锂电池放电曲线分段如下:

  • 4.15V以上电量约100%
  • 4.05V约90%
  • 3.92V约75%
  • 3.80V约50%
  • 3.70V约25%
  • 3.55V约10%
  • 3.30V以下基本放空,必须保护性关机

配合INA226的库仑计累计,实际误差控制在±5%以内。低电量阈值我设在20%,低于20%强制降亮度,低于10%彻底关闭LED,只保留控制板待机,最大程度保护锂电池寿命。

4.3 ESP32内部ADC的注意点

虽然用INA226做主采样,我还保留了一个ESP32内部ADC(GPIO34)接电池电压分压作为备用。实测发现ESP32 ADC有两大坑:

  • 3.3V供电的ADC基准电压是内部LDO参考,不是外部Vref,偏差能到0.1V,一定要做多点校准(至少两点)。
  • GPIO34之类的引脚悬空噪声大,需要加100nF电容滤波。

校准方法是找一台可调电源,给分压电路输入3.2V、3.6V、4.0V、4.2V四个电压点,记录ESP32 ADC原始值,然后做线性插值,得到修正系数。否则直接读ADC会把满电4.2V读成3.9V,低电量判断就会提前。

4.4 硬件防反接与保险丝设计

电池、太阳能板、负载回路的极性接反是烧板子的第一原因。我在电源输入入口做了三个保护:

  • 一个5x20mm保险丝(3A慢断型)串在太阳能板正极,过流时熔断。
  • 一颗SS34肖特基二极管做防反接,压降0.3V左右。
  • 一颗TVS管(SMBJ15A)并联在太阳能板输入端,吸收雷击浪涌,防止高压脉冲击穿CN3791。

TVS管的选择要按太阳能板的开路电压来定。18V板子开路电压约22V,SMBJ15A击穿电压在16.7V左右,可以钳位住大部分感应浪涌,同时不会在正常充电电压下漏电导通。

5. 实操过程与核心环节实现

5.1 7步完整搭建流程

动手之前先把材料裱好:

名称型号/规格数量
主控板ESP32 DevKit1
视频模块ESP32-CAM(OV2640)1
光照传感器BH17501
电量计INA226模块1
太阳能板18V/30W1
锂电池3.7V/10Ah聚合物电池1
充电管理CN37911
LED灯板3.3V/10W1
OLED屏0.96寸 SSD13061
MOS管及驱动AO3400等4

搭建顺序:

  1. 先把太阳能板和CN3791接好,输出接电池,确认电池电压能被正常拉高到4.2V,万用表测充电电流在预期范围。
  2. 接LED驱动电路,先用直流电源给灯板供电测试EN和PWM调光功能,别急着上电池。
  3. 接I2C总线(ESP32 GPIO21→SDA,GPIO22→SCL),把BH1750、INA226、OLED屏都挂上去,跑I2C扫描确认三个地址都在。
  4. 烧录基础固件调通传感器读取,OLED上能看到实时数据了。
  5. 接着接ESP32-CAM,先单独烧录摄像头测试固件,确认SD卡初始化正常、视频流能在浏览器访问。
  6. 在主控上实现UART协议控制和MQTT对接,编辑EMQX的ACL权限,确保只有合法设备能发布和订阅。
  7. 最后做整机联调,把路灯装到模拟光照箱里测光控逻辑,再装到室外实测太阳能充电和电池放电一整夜。

5.2 部分核心代码实现

  1. 光照检测读取和滤波:
BH1750 lightMeter; float lightValue = 0; const float filterCoeff = 0.2; // 低通滤波系数 void readLightSensor() { float lux = lightMeter.readLightLevel(); lightValue = filterCoeff * lux + (1 - filterCoeff) * lightValue; Serial.print("Light: "); Serial.print(lightValue); Serial.println(" lx"); }

这个一阶低通滤波的系数很关键。系数越小滤波越平滑,但响应越慢。0.2的系数对应约1秒的时间常数,对路灯光控这种慢变量刚刚好,既能滤掉云层瞬间遮挡的抖动,又能保证黄昏时光照下降能及时开灯。

  1. LED PWM调光:
#define LEDC_CHANNEL 0 #define LEDC_TIMER_13BIT 13 #define LEDC_BASE_FREQ 20000 void setupLed() { ledcSetup(LEDC_CHANNEL, LEDC_BASE_FREQ, LEDC_TIMER_13BIT); ledcAttachPin(LED_PIN, LEDC_CHANNEL); } void setLedBrightness(uint8_t brightness) { uint32_t duty = (4096L * brightness) / 100; // 0-100%对应0-4096 ledcWrite(LEDC_CHANNEL, duty); }

注意13位分辨率的满值是8191,但ESP32的LEDC在20kHz频率下建议用8位分辨率(0-255),这样精度对路灯足够了,占空比调整的量化噪声也小。上面用4096是按12位分辨率写的,二者语义相同,按需调整即可。

  1. MQTT数据发布:
#include <PubSubClient.h> WiFiClient espClient; PubSubClient mqtt(espClient); void publishStatus() { StaticJsonDocument<256> doc; doc["device_id"] = "SL01"; doc["lux"] = lightValue; doc["volt"] = batteryVoltage; doc["soc"] = batterySOC; char buffer[256]; serializeJson(doc, buffer); mqtt.publish("路灯/状态/SL01", buffer); }

5.3 装调优化与参数标定

整机调通后,继续做了三项优化:

  • 把MQTT的心跳间隔从默认的15秒调到30秒,降低无效网络流量;代价是掉线感知延迟变慢,我同时启用了遗嘱消息,设备掉线时broker自动推送离线消息到APP,弥补了延迟问题。
  • 电量的100%校准:第一次充满电时记录一个基准值,之后每次充电到4.15V以上时自动重置满电量标志。这样长期使用累积误差会被定期清零。
  • 增加“电池休眠深度保护”:SOC低于10%时彻底断开LED和非必要外设,只保留INA226和RTC唤醒,让路灯“冬眠”,等第二天太阳能板充电充到15%后再自动唤醒。实测这个模式待机电流只有2mA,几乎是微安级,保护作用明显。

5.4 OLED显示屏加持下的现场调试体验

调试阶段OLED屏帮了大忙。之前调MQTT连接只能看串口助手,人在户外根本看不清楚。现在直接在屏上显示HTTP状态码、MQTT连接日志和WiFi信号强度,遇到连接不上问题时,一看屏就能定位是IP、网关还是认证的问题。

OLED显示模块的驱动我用的u8g2库,这个库资料多、字体全,就是内存占用稍微大一点。ESP32的320KB RAM完全够用,但如果你用的是Arduino Uno这种2KB内存的板子,就需要换库了。

6. 常见问题与排查技巧实录

6.1 问题速查表

症状可能原因排查步骤解决方案
白天灯不亮,晚上也不亮LED驱动EN引脚损坏或电池亏空测电池电压,低于3.0V先充电;测EN引脚电压是否被拉高更换MOS管,补焊驱动电路,确保电池SOC>10%才允许开灯
灯闪烁不停PWM频率太低或电源纹波大用示波器抓LED驱动输出PWM频率升到20kHz,电源端并联470uF电解电容
光照读数一直为0BH1750接线反或I2C地址冲突跑I2C扫描,确认0x23是否存在重新焊接SDA/SCL,避免和OLED共用地址
电量计显示漂移采样电阻接触不良或未校准检查Rshunt两端电压是否和INA226读数一致重新焊接采样电阻,做两点校准
MQTT连不上服务器broker IP、端口、账号密码错误先在PC上用MQTT客户端测试同一个broker检查防火墙,确认密码配置正确
视频流卡顿不流畅WiFi信号弱或带宽不足看RSSI值,低于-70dBm属于弱信号调整天线角度,改用5GHz频段,或降低分辨率到VGA
白天正常,晚上掉线电池电量耗尽触发保护关机看SOC历史记录增大太阳能板功率或调低夜间亮度
OLED不亮I2C地址冲突或复位引脚悬空单独测试OLED模块核对模块地址,复位引脚接3.3V
APP收不到数据MQTT主题前缀不匹配在broker后台看订阅列表统一主题命名规范,确认订阅通配符正确
太阳能板充电电流太小角度不对或灰尘遮挡用万用表测板子短路电流调整板子朝向,定期清洁

6.2 几个特别的坑

第一,太阳能板的“24V虚电压”问题。拿万用表测空载电压有22V,一接上CN3791就掉到6V,这是板子输出电流能力不足的典型表现,不是充电控制坏了。判断标准是短路电流至少要在标称电流的80%以上。

第二,ESP32-CAM的PSRAM必须开启。我在Arduino IDE的Tools目录下选错Flash Mode,导致摄像头初始化失败报“Camera init failed”,折腾了一晚上才发现是PSRAM未启用。必须要选“Enable PSRAM”选项。

第三,充电接口和电池之间不能接反。CN3791的BAT引脚直连电池正极,但电池本身有极性,接反轻则芯片烧毁,重则电池鼓包。我以前吃过亏,后来所有电源接口都用了防反插端子(XH2.54带卡扣),物理上杜绝插反可能。

第四,太阳能板接地问题。金属支架的感应电压在雷雨天能到几十伏,不接大地会把TVS管烧穿。虽然城市路灯有避雷针保护,但DIY场景还是建议加一块可靠的TVS,同时在太阳能板支架底部做等电位连接。

6.3 精准调试分享

分享一个定位莫名重启的经验:某次路灯每天晚上九点准时重启,我一开始怀疑是光控逻辑写错,后来发现是MQTT每30秒发布一次心跳,凌晨正好和夜间全亮模式切换撞在一起,ESP32同时处理视频流抓帧、PWM调光、MQTT发布,内存暴涨触发看门狗复位。后来我把MQTT发布频率降到30秒一次,再把视频抓拍放到独立线程,重启问题彻底消失。

另一个小技巧是给ESP32加一个手动复位按钮(通过EN引脚接地),装在防水盒盖里,现场调试重启再也不用拔电池线。

7. 项目影响与场景扩展

这套系统不只是个路灯demo。把光照检测、电池管理、无线监控、远程控制四件事做扎实后,它几乎就是一个通用的低功耗物联网节点样板。后续扩展场景很明确:

  • 庭园灯、草坪灯:去掉视频监控,把功率降到3W,同样的方案直接量产。
  • 农业大棚补光灯:光照检测从“开/关”变成“调光曲线”,根据日照时长动态调整补光强度,APP上加一个光积算功能。
  • 牧场围栏警示灯:视频监控功能保留,夜间动物靠近时自动亮灯并拍一张照片,触发报警推送到手机。
  • 交通临时施工牌:使用太阳能供电,灯牌的亮度根据环境光自动调整,电池低压自动切换为闪烁模式,更加省电。
  • 灾害监测临时节点:摄像头定时抓拍,光照异常、电池异常通过MQTT上报后台,实现无人值守预警。

这些场景的共同点都是“能源约束强、环境变化大、需要远程感知和控制”,恰好是我这套系统全部覆盖的。

最后说一个我自己用的经验,做这种硬件加软件的综合项目,一定要先花半天时间把模块单独测试通过再开始联调,别指望“接上去就能跑”。我给每个模块画了一张接线图,标注清每个引脚的电压范围、I2C地址、串口波特率,联调时对着图查问题至少能省一半的排错时间。另外,买模块时留好备用件,ESP32和BH1750这类小元件容易在焊接过程中损坏,一次性买个三五套,就能确保项目连贯推进,不会因为等一个快递卡壳。这套方案是真正从头到尾跑通了的,你按文档走,至少要少踩三四个大坑。

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

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

立即咨询