💡 划重点

  • 核心逻辑:用 SCD41 实时感知书桌 CO₂ 浓度,当浓度超过 1000ppm 时自动切换 WS2812 灯环为警示色并触发通风提醒,浓度正常时保持暖白光专注模式。
  • 成本控制:主控 + 传感器 + 灯环 + 零散配件总价控制在 120 元内,无外壳时成本可压至 100 元左右。
  • 交互闭环:按键切换「专注白光 / 呼吸彩光 / 智能自动」三档,灯环颜色同时作为可视化数据反馈,蓝牙负责把日志发到手机。
  • 技术选型:ESP32-C3 自带 WiFi/BLE 但功耗低,Arduino 框架开发快,SCD41 的 I2C 地址固定且支持单次测量(省电)。
  • 接线要点:I2C 用 GPIO8/9(软 I2C 任意脚可配),灯环走 GPIO10,按键接 GPIO3(内部上拉,按下为低电平)。
  • 可扩展性:代码预留 BLE 服务特征值,后期可接入 Home Assistant 或手机小程序做长期数据曲线。

需求洞察

目标用户是居家办公/备考学生、儿童房书桌场景下的长期伏案人群。这些人每天在书桌前连续待 4-8 小时,尤其冬天开暖气或夏天开空调时门窗紧闭,室内 CO₂ 浓度很容易从室外 400-500ppm 飙升至 1500-2000ppm。

真实痛点在于:CO₂ 浓度超过 1000ppm 后人会开始感到昏沉、注意力分散,超过 1500ppm 时甚至出现轻度头痛。但大多数人不知道是空气质量导致效率低,只会怪自己不够专注,导致困了强撑、累了刷手机,效率越来越差。

为什么值得做:市面上有 CO₂ 检测仪,但摆在那里也只是个数字,和「学习专注」没有直接情感关联。台灯是书桌天然标配,把环境数据转化为灯的色调和亮度变化,人类天然对光线变化有潜意识反馈——灯变红了,就意味着「该开窗了」。这比看仪表盘更直觉化。此外成本控制在 120 元内,对 DIY 玩家毫无压力,成品价值却远超物料,值得动手。

方案设计

整个系统分四层:

- 专注模式(常亮):统一暖白色 2700K,亮度可调。

- 智能自动模式:CO₂ < 800ppm 显示淡青色(清爽),800-1000ppm 显示淡黄色(轻微注意),1000-1500ppm 显示橙色(轻度预警),> 1500ppm 显示红色呼吸灯(强通风提醒)。

- 彩灯模式:用户自定义循环渐变色,纯粹调节心情。

物料清单

物料 数量 用途 估算单价(元) 小计(元)
ESP32-C3 SuperMini 1 主控,带 BLE/WiFi 12.50 12.50
SCD41 传感器模块 1 CO₂ + 温湿度测量,I2C 接口 75.00 75.00
WS2812B 24 位灯环(内径约 6cm) 1 台灯光源 / 状态彩灯 18.00 18.00
EC11 旋转编码器(带按键) 1 用户交互输入(旋转调亮度,按键切换模式) 3.50 3.50
光敏电阻 + 10kΩ 电阻 各1 感知环境光强,自动降亮度(功能可选,但做出来体验好) 1.00 1.00
5V 2A USB 电源线(或 DC 座) 1 供电 4.00 4.00
杜邦线 + 洞洞板(或热缩管) 若干 连接和焊接 3.00 3.00
台灯灯罩 / 漫反射板(透光亚克力/描图纸) 1 柔化灯环光线,防眩光 2.00 2.00
74AHCT125 电平转换(双向) 1 SCD41 I2C 需要(可选,若 SCD41 模块内置安全则省略) 0.00 0.00
合计 - - - 119.00
注:SCD41 模块购买时尽量选带 I2C 电平转换的版本,因为 ESP32-C3 的 GPIO 是 3.3V,SCD41 原片需要 2.4-5.5V 供电,通讯逻辑电平 2.4-3.6V,直接连模块通常 OK。如果买的是裸片+分压电路,则必须加转换,但本清单预算按安全模块版设计,超出的 1 元用于可焊接的排针或胶水。

接线与结构

接线定义(默认无洞洞板裸接逻辑)

模块 引脚 连接目标
SCD41 VIN 3.3V(外接供电) ESP32-C3 3V3 或 USB 5V(用 LDO 转 3.3)
SCD41 GND GND ESP32-C3 GND
SCD41 SCL ESP32-C3 GPIO8 软 I2C 时钟
SCD41 SDA ESP32-C3 GPIO9 软 I2C 数据
WS2812 灯环 DIN ESP32-C3 GPIO10 灯环数据输入
WS2812 灯环 5V+ USB 5V 注意灯环全亮会超过 0.5A,若板载 AMS1117-3.3 则不可从 3V3 取电,直接从 5V 供电并共地
WS2812 灯环 GND GND 共地
EC11 按键引脚 SW GPIO3 内部上拉,按下为低电平(有效)
EC11 旋转 A / B GPIO1 / GPIO2 编码器相位检测(若只用按键可省略)
光敏电阻(+10k 分压) GPIO0(AH) (ADC) 读取环境亮度

接线微调说明:原定 I2C 用 8/9,SCD41 模块上电需要 20ms 稳定时间,GPIO8/9 在 ESP32-C3 上是普通 I/O,没有特殊禁止位。关于 GPIO3,在 ESP32-C3 数据手册中它是带内部上拉的模式引脚(可作为按键)。不过 GPIO3 在下载时有特殊作用(某些开发板用于 BOOT 指示),实际开发时若不方便按 BOOT 键重新烧录,建议改用 GPIO4。如果你手头板子 GPIO3 引脚被 Flash 占用或带电感,可以微调至 GPIO4 并同时把逻辑改掉。本方案初稿按 GPIO3 来写,若编译烧录时遇到不能自动下载,将按键线从 GPIO3 拔掉焊到 GPIO4 即可,无需改代码(用宏定义切换)。

结构安装

  1. 找一个底部直径略大于灯环外径的金属/纸质台灯罩,将 WS2812 灯环面朝上平放于底座中央,用热熔胶固定边缘。
  2. 在灯罩侧面开一个小孔(直径 5mm),把 SCD41 露出来,保证传感器通风口不被壳体完全挡住。CO₂ 传感器需要气体扩散,不要直接塞进密闭底座内部,否则测的是机器内部废气。建议将 SCD41 通过排线引出,固定在灯罩上沿接近桌面呼吸带高度(约 20-30cm 高处)。
  3. EC11 旋转编码器嵌在灯座侧面,方便随手旋转调节亮度、按下开关灯或切换模式。光敏电阻放灯罩顶部朝上(测环境光)并用热缩管做挡光避免台灯自带光源直射导致误判。
  4. ESP32-C3 主控板双面胶贴在底座内部远离发热区(SCD41 虽有自动加热但本体温度低)。

核心代码


// CO2 Desk Lamp for ESP32-C3 (Arduino Framework)
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_NeoPixel.h>
#include "Adafruit_SCD4x.h"  // SCD41 库

// ---- 引脚配置 ----
#define I2C_SDA   9
#define I2C_SCL   8
#define LED_PIN   10
#define BTN_PIN   3
#define PHOTO_ADC 0   // 光敏电阻 ADC

#define NUMPIXELS 24

// ---- 全局对象 ----
TwoWire myWire = TwoWire(0);  
Adafruit_SCD4x scd4x;
Adafruit_NeoPixel strip(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);

// ---- 状态 ----
volatile bool btnPressed = false;
uint8_t mode = 1;  // 0: 专注白光, 1: 智能自动, 2: 呼吸彩灯
uint16_t co2 = 0;  float temp = 0; float hum = 0;
int brightness = 100;  // 0-255 近似亮度百分比

void setup() {
  Serial.begin(115200);
  delay(500);
  
  // LED 灯环
  strip.begin(); strip.show();  // 初始化全灭
  strip.setBrightness(50);

  // 按键

  // I2C + SCD41(地址默认 0x62)
  if (!scd4x.begin(&myWire;)) {
    Serial.println("SCD41 not found!");
    while (1) { delay(10); } // 错误指示用红灯闪烁代替
  }
  if (!scd4x.startPeriodicMeasurement()) {
  }
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // 按键可以切换模式
  if (btnPressed) {
    btnPressed = false;
    mode = (mode + 1) % 3;
    // 切换模式闪烁一下确认
  }

  // 读取光敏电阻调节全局亮度(可选)
  int photo = analogRead(PHOTO_ADC);
  int autoBright = map(photo, 0, 4095, 255, 40);
  strip.setBrightness(autoBright);  // 自动亮度调节

  if (scd4x.readMeasurement()) {
    co2 = scd4x.getCO2();
    temp = scd4x.getTemperature();
    hum = scd4x.getHumidity();
    
    // 三种执行逻辑
    switch(mode) {
      case 0: renderFocusLight(); break;      // 固定暖白灯
      case 1: renderCO2Feedback(); break;     // 根据CO2变色/呼吸
      case 2: renderBreathRainbow(); break;   // 彩色呼吸
    }
    printDebugInfo();
  } else {
    // 若没有数据,让灯环稍微降低亮度闪烁(不喧宾夺主)
  }
  
  delay(500); // 2Hz 查询频率够用,但实际SCD41周期测量约5秒完一次
}

void renderFocusLight() {
  // 暖白 (R 255, G 200, B 150)可以近似模拟白炽灯
  uint32_t warmWhite = strip.Color(255, 190, 120);
  fillSolid(warmWhite);
}

void renderCO2Feedback() {
  uint32_t c;
  if (co2 < 800) c = strip.Color(120, 255, 180);  // 翠绿偏舒适
  else if (co2 < 1000) c = strip.Color(200, 255, 120); // 黄绿
  else if (co2 < 1500) c = strip.Color(255, 150, 30);  // 橙红预警
  else {  // >1500 红色呼吸
    int breath = (sin(millis() * 0.002) * 0.5 + 0.5) * 200 + 55;
    return;
  }
  fillSolid(c);
}

void renderBreathRainbow() {
  int hue = (millis() / 20) % 65535;  // 色相渐变循环
  uint32_t rgb = strip.gamma32(strip.ColorHSV(hue, 255, brightness));
  fillSolid(rgb);
}

void fillSolid(uint32_t color) {
  for(int i=0; i<NUMPIXELS; i++) strip.setPixelColor(i, color);
  strip.show();
}

void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<NUMPIXELS; i++) {
  }
}

void IRAM_ATTR onButton() {
  btnPressed = true;  // 中断中不执行耗时操作
}

void printDebugInfo() {
  Serial.printf("CO2: %d ppm, Temp: %.1f°C, RH: %.0f%%\n", co2, temp, hum);
}
说明:如果你用的是 PlatformIO,在 platformio.ini 中增加 lib_deps = adafruit/Adafruit NeoPixel, adafruit/Adafruit SCD4x。IO 口定义有微调自由,I2C 库使用软件 TwoWire,GPIO8/9 没问题。传感器测量周期约 5 秒,但只判断 CO₂ 时 500ms 查询一次不会阻塞 I2C 总线。

调试步骤

  1. 第一优先级 - 通信验证:接线后先只烧 I2C 扫描代码,在串口确认 0x62 地址出现。SCD41 上电后 1 秒内不应响应,若有响应且重复出现,说明电气连接 OK。若扫描不到,大概率是接线错误或模块供电不足(换 USB 独立供电试试)。
  2. 灯环点亮测试:注释掉 SCD41 相关代码,设置 GPIO10 输出固定颜色,确认 LED 能亮起。如果完全不亮,检查 WS2812 DIN 口是否连对,GND 是否与主控共地,5V 供电是否足够(若用电脑 USB 可能电压不足,灯板全白亮度会显得很暗)。
  3. 按键中断测试:按下按键看串口是否有模式切换打印,如果没反应,多半是因为 GPIO3 在电路板上有外接电容或连接 BOOT 按键导致干扰,更换到 GPIO4(下载模式检查)。不要忽视——这个按键就是用户最直观的控制入口。
  4. 传感器精确度校准:SCD41 出厂已经校准过 CO₂,通常 400ppm 基准偏差 ±50ppm,不需额外校准。但若装置近期内受过酒精等污染,可以做一个 48 小时室外通风复位(户外开窗把设备放在窗台吹 48h 后调用 scd4x.performForcedCalibration(400) 指令)。
  5. 蓝牙调试:手机下载 nRF Connect 或 LightBlue,扫描到名字带 ESP32-C3 的设备,连上后读「CO2 Data」特征值,看 Hex 数据是否能正确解析(定义协议头 + CO₂ 高字节 + 低字节 + 温度 + 湿度 + 校验)。后期可加一个简单的自定义 Service 做双向指令。
  6. 全系统综合烧录:调用 scd4x.startPeriodicMeasurement() 后前 10 分钟数据上升属正常(模块自热效应)。让传感器在桌面静止放置 30 分钟后读数稳定在 600-900ppm(未开窗)。对着传感器哈一口气,看到 CO₂ 跳到 1500ppm 以上,灯环应变成红色呼吸提醒——此时测试通过。

设计图

CO₂ 学习专注监测台灯结构flow
1
感知输入
2
控制处理
3
执行反馈
4
数据上报

该流程沿顺时针方向重复:感知层首先采集 CO₂ 浓度与环境温湿度;ESP32-C3 在本地对数据插值、滤波并判断当前空气质量级别,同时检测按键状态(旋转编码器 / 轻触按键)以更改用户模式;随后 WS2812 灯环呈现暖白专注光、智能变色或呼吸彩灯,完成物理层面反馈;最后通过 BLE 将本次采集的数据记录和模式状态推送给手机 App(或断开连接,日志仅在本地保持可视状态)。如果未来接上 WiFi,可将数据打包走 MQTT,但当前蓝牙版本足以完成核心场景——即监测、警示、提醒通风、专注氛围切换。