WLED 实战:为 Wemos D1 mini / Wemos32 mini 扩展板添加 OLED 状态屏与温度传感器(usermod 详解)
【免费下载链接】WLEDControl WS2812B and many more types of digital RGB LEDs with an ESP32 over WiFi!项目地址: https://gitcode.com/GitHub_Trending/wl/WLED
WLED 提供了 v1 usermod 机制(userSetup/userConnected/userLoop三个钩子),允许在不改动主代码的前提下给固件附加自定义功能。本文基于仓库中 usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md 及其配套源码 usermod.cpp、usermod_bme280.cpp,完整讲解如何把 SSD1306 OLED 状态屏和 DS18B20 / BME280 温度传感器接入 Wemos D1 mini(ESP8266)或 Wemos32 mini(ESP32)扩展板:包括安装方法、PlatformIO 依赖配置、引脚定义、显示刷新与自动休眠逻辑、以及每 60 秒一次的 MQTT 温湿度/气压上报实现。读完后,你可以复现整套带屏幕状态指示与传感器遥测的 WLED 构建方案。
这个 usermod 解决什么问题
WLED 默认没有本地显示屏,设备挂在墙上的灯带控制器上时,你无法直接看到它的 IP 地址、连接的 WiFi、当前灯效和调色板——这些正是配网后最常被需要的信息。这个 usermod 利用扩展板(Wemos shield)上的 I2C OLED 把以下信息常驻显示:
- SSID(第一行)与本机 IP(第二行),AP 模式且亮度为 0 时第二行改显示 AP 密码
- 当前灯效(mode)名称与调色板(palette)名称(第三、四行)
- WiFi、Home、太阳/月亮(亮/灭状态)三个图标
同时它还实现了 readme 中列出的完整功能清单:
| 功能 | 实现依据 |
|---|---|
| SSD1306 128x32 或 128x64 I2C OLED | U8g2 库的U8X8_SSD1306_128X32_UNIVISION_HW_I2C/U8X8_SSD1306_128X64_NONAME_HW_I2C构造函数 |
| 屏幕上显示 IP、SSID 与控制器状态(开关、当前灯效) | userLoop()中的重绘逻辑 |
| 自动熄屏以延长 OLED 寿命 | 3 分钟无变化后调用u8x8.setPowerSave(1) |
| Dallas 温度传感器(DS18B20) | Dallas()快速读数函数 + OneWire 库 |
| 温度上报 MQTT broker | 以mqttDeviceTopic/temperature为主题定时发布 |
| 继电器省电 | 扩展板自带继电器;从源码看用户固件本身未直接操作继电器引脚,推断由 WLED 主程序的RLYPIN机制(亮度为 0 时断电)承担 |
文件清单与替换规则(readme 的原始安装说明):
- 默认(Dallas 传感器)版本:把 usermod.cpp 复制并替换WLED 源码树
wled00目录下的usermod.cpp; - BME280 版本:改用 usermod_bme280.cpp,复制到
wled00后重命名为usermod.cpp; - 第三控制器选项:Heltec WiFi-Kit-8,同样带 OLED(纯 DIY 硬件,见下文引脚说明)。
WLED 的 v1 usermod 入口骨架见 wled00/usermod.cpp(userSetup启动时调用一次、userConnected每次 WiFi(重)连时调用、userLoop每轮主循环调用)。这个 usermod 正是填充这三个钩子:userSetup()初始化 OLED 并触发首次传感器读数,userLoop()负责 MQTT 发布与屏幕刷新,userConnected()留空。usermod 的目录组织规范(每个 usermod 一个文件夹、附 readme 说明所需改动)可在 usermods/readme.md 中查看。
引脚定义:ESP8266 与 ESP32 的差异
两个源文件用编译期宏区分两种主控板,这是复现硬件连接的关键:
#ifdef ARDUINO_ARCH_ESP32 // Wemos32 mini uint8_t SCL_PIN = 22; uint8_t SDA_PIN = 21; uint8_t DALLAS_PIN = 23; #else // ESP8266 Wemos D1 mini uint8_t SCL_PIN = 5; uint8_t SDA_PIN = 4; uint8_t DALLAS_PIN = 13; // uint8_t RST_PIN = 16; // Heltec WiFi-Kit-8 时取消注释 #endif即:
| 信号 | Wemos D1 mini(ESP8266) | Wemos32 mini(ESP32) |
|---|---|---|
| I2C SCL | GPIO5 | GPIO22 |
| I2C SDA | GPIO4 | GPIO21 |
| DS18B20(Dallas)数据脚 | GPIO13 | GPIO23 |
| OLED RESET(仅 Heltec WiFi-Kit-8 需要) | GPIO16 | — |
Heltec WiFi-Kit-8 的 OLED 带独立 RESET 线,此时需要:取消RST_PIN = 16及U8X8_PIN_RESET的注释,并把 OLED 对象构造从U8X8_PIN_NONE改为U8X8_PIN_RESET(源码中以“第三选项”注释形式给出)。readme 中提到的“第三个控制器 Heltec WiFi-Kit-8”即由此实现。
OLED 驱动对象的选择(默认启用第一行):
// 第一选项:廉价 128x32 0.91" I2C OLED U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // 第二选项:128x64 0.96" 或 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(...); // 第三选项:Heltec WiFi-Kit-8(使用 RESET 脚) //U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA);构造函数参数依次为 Reset、SCL、SDA;硬件 I2C(_HW_I2C)意味着 SDA/SCL 必须与上表匹配,否则显示不出来或显示错乱(源码注释建议对照 u8g2 的 u8x8setupcpp 参考页核对)。
安装与 PlatformIO 依赖配置
步骤一:替换 usermod 文件
将对应版本的.cpp放入wled00并命名/替换为usermod.cpp(WLED 只编译wled00/usermod.cpp这一个 v1 入口,这也是 readme 中“copy and replace file in wled00 directory”的原因)。
步骤二:声明外部库依赖
readme 给出的platformio.ini配置片段如下——Dallas 传感器版本:
# platformio.ini ... [platformio] ... ; default_envs = esp07 default_envs = d1_mini ... [common] ... lib_deps_external = ... #For use SSD1306 OLED display uncomment following U8g2@~2.27.3 #For Dallas sensor uncomment following 2 lines DallasTemperature@~3.8.0 OneWire@~2.3.5 ...BME280 传感器版本:
# platformio.ini ... [platformio] ... ; default_envs = esp07 default_envs = d1_mini ... [common] ... lib_deps_external = ... #For use SSD1306 OLED display uncomment following U8g2@~2.27.3 #For BME280 sensor uncomment following BME280@~3.0.0 ...即依赖组合为:OLED 必选U8g2@~2.27.3;Dallas 版追加DallasTemperature@~3.8.0和OneWire@~2.3.5;BME280 版追加BME280@~3.0.0。
适用前提提醒(基于当前仓库实际情况):上述片段中的default_envs = d1_mini、esp07以及lib_deps_external段落来自原始 WLED-wemos-shield 项目(readme 中列出的外部项目仓库/Wiki/预编译固件)的platformio.ini。而在当前 WLED 主仓库的 platformio.ini 中,并没有d1_mini/esp07构建环境,也没有lib_deps_external这一键名。若你要在当前仓库的源码树上构建该 usermod,正确做法是利用extra_configs机制——platformio.ini 中声明了extra_configs = platformio_override.ini platformio_release.ini——即按 platformio_override.sample.ini 的样例新建一份platformio_override.ini,在其中自定义d1_mini构建环境(board = d1_mini,ESP8266 平台)并把U8g2、OneWire、DallasTemperature(或BME280)写入该环境的lib_deps。readme 中的; default_envs = esp07注释也暗示原项目同时支持 ESP8266 的 esp07 环境,可按同样思路扩展。
步骤三:编译烧录
用 PlatformIO 编译你选择的d1_mini(ESP8266)或对应 ESP32 mini 环境并烧录即可。readme 列出的实测验证环境为:Wemos D1 mini 原版 v3.1 及克隆板、Wemos32 mini、PlatformIO 构建流程、SSD1306 128x32 I2C 屏、DS18B20 温度传感器、BME280 温湿度气压传感器、N.O. 瞬动按钮(扩展板上的按钮由 WLED 主程序的BTNPIN机制处理)。
源码走读:userSetup 初始化
Dallas 版 usermod.cpp 的userSetup()逻辑:
void userSetup() { Dallas(DALLAS_PIN, 1); // 触发一次 DS18B20 转换并延时 1s,保证首次读数是有效值 u8x8.begin(); u8x8.setPowerSave(0); // 退出省电模式,点亮屏幕 u8x8.setFlipMode(1); // 翻转画面(按屏的贴装方向调整) u8x8.setContrast(10); // 低对比度以延长 OLED 寿命,需要更亮可调到 255 u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0, 0, "Loading..."); }两个值得注意的细节:
- 启动即预热传感器:
Dallas(DALLAS_PIN, 1)的第二个参数start控制“转换—延时—重试”的循环次数。DS18B20 的0x44转换指令需要时间才能算出数据,启动时先跑一轮并delay(1000),可避免后续读到上一次残留值。 - 对比度只有 10:OLED 的对比度寄存器越小画面越暗,这里刻意压暗以降低有机像素的老化速度,与 readme 中“auto display shutoff for extending display lifetime”的寿命策略一脉相承。
Dallas 快速读数函数本身(usermod.cpp)是 OneWire 协议的“免库存”写法:reset()总线复位后发跳过 ROM 命令0xCC和读 Scratchpad 命令0xBE,取 2 字节温度低 12 位;result>>=4后按符号位扩展(data[1]&128时或上61440),并处理了 DS18B20 的 +0.5 LSB 舍入位(data[0]&8)。返回的是 1/2 摄氏度(0.5 °C 精度)的整数。
BME280 版 usermod_bme280.cpp 的userSetup()则额外初始化 I2C 总线并做传感器探测重试:
Wire.begin(SDA_PIN, SCL_PIN); while(!bme.begin()) { Serial.println("Could not find BME280I2C sensor!"); delay(1000); } switch(bme.chipModel()) { case BME280::ChipModel_BME280: Serial.println("Found BME280 sensor! Success."); break; case BME280::ChipModel_BMP280: Serial.println("Found BMP280 sensor! No Humidity available."); break; default: Serial.println("Found UNKNOWN sensor! Error!"); }while(!bme.begin())是死循环重试——I2C 传感器上电慢时能自恢复;chipModel()区分 BME280 与 BMP280,后者没有湿度通道(后续发布的/humidity值会是 NaN)。传感器对象BME280I2C bme默认参数为强制模式、1000 ms 待机、气压/温度/湿度均 1 次过采样、无滤波(源码注释已写明)。
userLoop 上半段:每 60 秒一次 MQTT 遥测
两个版本的userLoop()都以“传感器发布”开头。Dallas 版(usermod.cpp):
temptimer = millis(); if (temptimer - lastMeasure > 60000) { // 60 秒周期 lastMeasure = temptimer; #ifndef WLED_DISABLE_MQTT if (mqtt != nullptr) { // 未连接 MQTT 时跳过,防止 8266 崩溃 #ifdef Celsius int16_t board_temperature = Dallas(DALLAS_PIN, 0); // start=0:只读,不再触发新转换 #else int16_t board_temperature = (Dallas(DALLAS_PIN, 0) * 1.8 + 32); #endif String t = String(mqttDeviceTopic); t += "/temperature"; mqtt->publish(t.c_str(), 0, true, String(board_temperature).c_str()); } #endif }要点:
- 主题由 WLED UI 决定:
mqttDeviceTopic是 WLED 主程序全局变量(在 Web UI 的 MQTT 设置里配置的设备主题),usermod 追加/temperature子主题发布,因此下游 Home Assistant 等系统订阅<你的设备主题>/temperature即可。 WLED_DISABLE_MQTT条件编译:如果构建时用 build flag 禁用了 MQTT,这段代码不编译;运行期还要检查mqtt != nullptr——源码注释特别提到不检查会让 ESP8266 崩溃。Celsius宏决定温标:定义时(#define Celsius)以 0.5 °C 步进的整数摄氏度发布;取消定义则按×1.8 + 32换算为华氏度。BME280 版对应地在UpdateBME280Data()里切换BME280::TempUnit_Celsius与TempUnit_Fahrenheit,压力单位固定为 Pa。start=0的含义:循环里每次发布前再调一次Dallas(pin, 0),只读 Scratchpad 并重新发起转换但不阻塞等待,实现“读的同时预热下一次”。
BME280 版发布的是三个主题(usermod_bme280.cpp),同一 60 秒节拍内连续发布:
UpdateBME280Data(); // 读取并缓存 temp / hum / pres(单位:所选温标、%、Pa) mqtt->publish(t.c_str(), 0, true, String(SensorTemperature).c_str()); // <deviceTopic>/temperature mqtt->publish(p.c_str(), 0, true, String(SensorPressure).c_str()); // <deviceTopic>/pressure mqtt->publish(h.c_str(), 0, true, String(SensorHumidity).c_str()); // <deviceTopic>/humidityUpdateBME280Data()调用bme.read(pres, temp, hum, tempUnit, presUnit)后把结果写入三个全局 float(初值均为NAN,BMP280 的湿度将保持 NaN)。
userLoop 下半段:屏幕刷新与自动熄屏
显示部分是一个“5 秒一拍、按需重绘”的节流状态机(usermod.cpp):
- 节拍控制:
#define USER_LOOP_REFRESH_RATE_MS 5000,距上次检查不足 5 秒直接return。 - 自动熄屏:
lastRedraw之后 3 分钟(3*60*1000ms)没有任何重绘,就u8x8.setPowerSave(1)关机屏并置displayTurnedOff;下次需要重绘时先setPowerSave(0)唤醒。 - 变更检测:与“上次已知值”(
knownSsid/knownIp/knownBrightness/knownMode/knownPalette)逐一比对,任一项变化才置needRedraw = true:
if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { needRedraw = true; } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { needRedraw = true; } else if (knownBrightness != bri) { ... } else if (knownMode != strip.getMainSegment().mode) { ... } else if (knownPalette != strip.getMainSegment().palette) { ... }注意两个细节:AP 模式(apActive)下 IP 被固定显示为占位值4.3.2.1(ESP8266 版的 SSID 也改用WiFi.softAPSSID()),真实 IP 只在 STA 模式下取WiFi.localIP();效果/调色板取自strip.getMainSegment()的主段,即 UI 上你正在操作的那个灯段。
- 四行两图标布局(重绘时):
| 行 | 内容 | 说明 |
|---|---|---|
| 第 1 行 | SSID | 超过列宽截断,并在末尾补~表示被截断;左侧配 WiFi 图标(open_iconic 80 号字形) |
| 第 2 行 | IP,或 AP 模式且bri == 0时的 AP 密码 | 左侧 Home 图标(68 号字形);这是“无屏配网”的实用设计——忘记密码时把灯关掉就能看到 |
| 第 3 行 | 灯效名 | 通过extractModeName(knownMode, JSON_mode_names, ...)取 16 字符以内名称,左侧太阳/月亮图标表示亮/灭(bri > 0时切到亮月字形 69,否则 66) |
| 第 4 行 | 调色板名 | extractModeName(knownPalette, JSON_palette_names, ...) |
灯效名/调色板名的提取由 WLED 主程序提供:extractModeName()定义于 wled00/util.cpp(声明见 wled00/fcn_declare.h),它从strip.getModeData(mode)拷贝到遇到\0或@(SR 扩展分隔符)为止,并保证写入长度不超过maxLen。usermod 传入 16 字节缓冲、上限 15,正好适配 128x32 屏每行 32 列的版式。这也体现了 v1 usermod 的典型模式:usermod 只做外设交互,状态数据(SSID、IP、亮度bri、灯段 mode/palette)全部复用 WLED 全局变量与工具函数。
两套版本怎么选、有哪些限制
- Dallas(DS18B20)版:只需温度,占一个 GPIO(D1 mini 的 13 / Wemos32 mini 的 23),依赖
OneWire+DallasTemperature;温度精度 0.5 °C(12 位读取),发布为整数。 - BME280 版:温度 + 湿度 + 气压三合一,走 I2C 与 OLED 共享 SDA/SCL(WLED 的 I2C 默认引脚与此处
Wire.begin(SDA_PIN, SCL_PIN)一致,D1 mini 为 4/5),依赖BME280@~3.0.0;注意若实际芯片是 BMP280,湿度无意义。 - 显示刷新是事件驱动 + 5 秒节拍的,任何一项(SSID/IP/亮度/灯效/调色板)变化最坏 5 秒内上屏;静态场景 3 分钟熄屏。
- 该 usermod 属 v1 API(直接替换
wled00/usermod.cpp),同一构建只能启用一个 v1 usermod 文件;若需要多个 usermod 共存或更多 API,仓库推荐转向 v2 usermod API(可参考 usermods/readme.md 中提到的EXAMPLE_v2与Temperature示例)。 - 由于它直接引用
mqtt、mqttDeviceTopic、bri、apActive、apPass、strip等 WLED 内部全局符号,随 WLED 大版本演进存在兼容风险,这与 usermods/readme.md 中“usermods 可能随 WLED 更新而损坏、维护责任在作者”的声明一致。
小结
这个 Wemos 扩展板 usermod 用约 200 行代码演示了 v1 usermod 的完整套路:在userSetup中初始化 I2C 外设(OLED 用 U8g2 的硬件 I2C 构造,BME280 用带重试的begin()),在userLoop中做“定时遥测 + 节流失效重绘 + 低功耗熄屏”三件事,并全部复用 WLED 的全局状态与工具函数(extractModeName、mqttDeviceTopic、主灯段 mode/palette)。配合 readme 给出的 PlatformIO 依赖清单(U8g2、OneWire/DallasTemperature或BME280),再按当前仓库的platformio_override.ini机制补齐d1_mini构建环境,即可得到一台带状态屏、能自我熄屏、并把环境数据持续推给 MQTT 的 WLED 控制器。
【免费下载链接】WLEDControl WS2812B and many more types of digital RGB LEDs with an ESP32 over WiFi!项目地址: https://gitcode.com/GitHub_Trending/wl/WLED
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考