方盒水动力灯——用ESP32-C3+LIS2DW12做的"会晃动的液体"

2026-10-133

一、项目灵感

一直想做个"看得见液体"的小玩具——不是真的装水,而是用LED点阵模拟一个密封盒子里装着半盒蓝色液体,无论你怎么翻动盒子,液体永远流向物理上的最低处;摇晃它,还能看到水花溅起。

选材的难点在于:真实感。市面上大多数LED水动效果都是"单向倾斜",盒子里是水,但盒子不能翻。而我要的是——

  • 盒子竖着放,水在底下 ✓
  • 向左倒90°,水完全倒向左侧 ✓
  • 倒过来,水"跑"到顶部 ✓
  • 用力晃一下,水花散开又消散 ✓

这就需要一个能实时判断重力方向的传感器,和一块高密度、单点可控的LED点阵。


方盒水动力灯——用ESP32-C3+LIS2DW12做的
方盒水动力灯——用ESP32-C3+LIS2DW12做的

二、硬件选型

模块 型号 作用
主控 DFRobot Beetle ESP32-C3 (DFR0868) 硬币大小、带锂电池管理、支持Arduino IDE
传感器 LIS2DW12 三轴加速度计 (SEN0405) 超低功耗、±2g量程、16位精度
显示 8×8 RGB LED Matrix (DFR0459) 64颗WS2812、单点可控
电源 改造充电宝(2×18650) 5V输出给LED,VIN给C3

为什么选这三个模块?

  • ESP32-C3:RISC-V单核,自带WiFi/BLE,方便后续扩展;Beetle板载充电管理省掉一颗充电芯片;板载LED和BOOT键方便调试。
  • LIS2DW12:相比MPU6050,它超低功耗(50nA)、抗冲击10000g,倾斜检测的灵敏度够用(1.3mg RMS)。
  • 8×8点阵:64个像素正好能表达"半盒水的形状",尺寸小到能塞进方盒。

方盒水动力灯——用ESP32-C3+LIS2DW12做的

三、电源改造:把充电宝"瘦身"

3.1 为什么改造?

原充电宝6节18650,体积太大,没法塞进与LED灯板同尺寸的外壳。改成2节18650并联(3.7V,约5000mAh),体积缩小到原来的1/3。

3.2 改造步骤

  1. 拆壳:保留充电宝的升压板(5V输出)和充电口,去掉多余的电池支架。
  2. 重新焊接:2节18650并联合焊到升压板BAT+/-端。
  3. 引出5V:从USB-A口的VCC/GND焊盘各引出一根线,焊接在对外开关上。
  4. 两条供电支路:
    • LED支路:开关 → LED灯板5V/GND
    • C3支路:另一条USB-A口(或从同口分流) → Beetle VIN引脚
  5. 共地:LED的GND、C3的GND、充电宝的GND全部相连。

3.3 接线图

【电源正极路径】
电池+ ──> 升压板 ──┬──> 开关 ──> 8×8 LED 点阵 VCC
│
└──> Beetle ESP32-C3 VIN

【公共负极路径(必须共地)】
电池- ──> 升压板 ──┬──> 8×8 LED 点阵 GND
│
└──> Beetle ESP32-C3 GND

【信号路径】
LIS2DW12 ──I2C──> Beetle ESP32-C3
Beetle ESP32-C3 GPIO 8 ──> 8×8 LED 点阵 DIN

3.4 完整系统接线表

从 到 说明
电池+ 升压板 BAT+ 3.7V 输入
电池- 升压板 BAT- 共地参考
升压板 5V 开关一端 输出 5V
开关另一端 LED点阵 VCC 控制LED通断
升压板 5V Beetle VIN 5V 给C3
升压板 GND LED点阵 GND 共地
升压板 GND Beetle GND 共地(关键)
LIS2DW12 VCC Beetle 3V3 传感器3.3V
LIS2DW12 GND Beetle GND 传感器地
LIS2DW12 SDA Beetle GPIO 4 I2C数据
LIS2DW12 SCL Beetle GPIO 1 I2C时钟
LED点阵 DIN Beetle GPIO 8 数据信号

3.5 三个要点提醒

  1. 一定要共地:升压板GND、LED点阵GND、Beetle GND、传感器GND 必须全部连在一起,否则数据信号无法正确识别。

  2. 共地方案二选一:

    • 方案A(推荐):升压板5V分别给LED和C3,GND在升压板输出端汇合
    • 方案B:从C3的GND再分出线给传感器——只要最终所有GND在同一节点即可
  3. 电容:在升压板5V输出端并一个 1000µF电解电容,正极接5V,负极接GND。能吸收LED点阵瞬态电流(64颗灯全亮可达1.15A),防止C3复位或LED闪烁。


方盒水动力灯——用ESP32-C3+LIS2DW12做的

四、外壳设计

外壳用3mm亚克力激光切割,外形尺寸和LED灯板一致(约60×60mm),分三层:

层 厚度 功能
前层 3mm 透明亚克力 透光窗口,把LED像素柔和化
中层 3mm 亚克力 容纳LED+传感器+电池,侧边开USB口
后层 3mm 黑色亚克力 底板,开开关小孔

USB开口对准充电宝的充电口。


方盒水动力灯——用ESP32-C3+LIS2DW12做的

五、程序解析(重点)

代码整体分四大模块。我逐一拆解核心逻辑。

5.1 模块一:传感器初始化

acce.softReset();
acce.setRange(DFRobot_LIS2DW12::e2_g);           // ±2g
acce.setDataRate(DFRobot_LIS2DW12::eRate_50hz);  // 50Hz
acce.setPowerMode(DFRobot_LIS2DW12::eContLowPwrLowNoise2_14bit);
acce.continRefresh(true);                         // 连续采集
acce.setFilterPath(DFRobot_LIS2DW12::eLPF);      // 低通滤波

为什么这样配?

  • ±2g:水杯晃动属于低加速度场景,小量程=高分辨率。
  • 50Hz:平衡功耗和响应,太快没必要,太慢水花会掉帧。
  • 低功耗低噪声模式:噪声小,倾斜判断更稳定。

5.2 模块二:重力方向计算(核心)

这是整个项目的灵魂。普通做法是"倾斜角度 → 水位高低",但这样盒子倒立时水会一直留在底部(错的)。正确做法是用重力在点阵平面上的投影方向来决定"水堆积中心"。

// 归一化加速度
float A = sqrt(ax*ax + ay*ay + az*az);
float gy = -ay / A;   // 重力在Y轴(点阵右)的分量
float gz = -az / A;   // 重力在Z轴(点阵上)的分量

// 取Y-Z平面上的投影
float projSize = sqrt(gy*gy + gz*gz);
targetDirY = gy / projSize;
targetDirZ = gz / projSize;

关键点:

  • 静止时加速度读数 = 比力方向(世界上方),重力方向 = 取反。
  • 点阵是平面的,重力垂直于平面时(点阵朝上/朝下)投影为0,此时默认水在下方。

然后做方向平滑,避免抖动导致水乱跳:

gDirY += (targetDirY - gDirY) * DIR_SMOOTH;  // DIR_SMOOTH = 0.25
gDirZ += (targetDirZ - gDirZ) * DIR_SMOOTH;

5.3 模块三:每个像素的"投影"判定

这是渲染的关键。水应该出现在哪?—— 重力方向那一侧。

for (int row = 0; row < MATRIX_H; row++) {
  for (int col = 0; col < MATRIX_W; col++) {
    // 像素在点阵平面上的归一化坐标
    float px = (col - 3.5) / 3.5;  // -1 左 ~ +1 右
    float py = (row - 3.5) / 3.5;  // -1 上 ~ +1 下
    
    // 该像素在重力方向上的投影
    float proj = px * gDirY + py * (-gDirZ);
  • proj > 0 → 像素在重力"下游" → 水体
  • proj ≈ 0 → 像素在水面线上
  • proj < 0 → 像素在重力"上游" → 空气

数学直觉:proj 就是该像素到"垂直于重力方向的平面"的带符号距离。你不需要去算"水流到哪了",只需要算"哪些像素在重力方向上"。

5.4 模块四:水花与蛇形布局修正

水花:当加速度跳变超过阈值时,splashIntensity 置为1,然后每帧×0.88 自然衰减。

if (dX > 350 || dY > 350 || dZ > 350) {
  splashIntensity = 1.0;
}
splashIntensity *= 0.88;

水花只在水面之上(proj < 0)出现,离水面越远概率越低:

float dist = -proj - SURFACE_WIDTH;
float splashProb = splashIntensity * (1.0 - dist * 2.0);

颜色:用 r/b 比例调出淡蓝色(30%红 + 70%绿 + 100%蓝)。

蛇形布局修正:这是踩过的坑!大多数WS2812矩阵是蛇形接线(奇数行反向)。如果不修正,就会看到"水一半在左、一半在右"的错乱。

physicalCol = (row % 2 == 0) ? col : (MATRIX_W - 1 - col);

六、调试踩坑记录

坑 现象 解决
I2C引脚冲突 GPIO 9是BOOT键,用它会无法烧录 改用GPIO 1/4
方向反了 向左倒,水往右流 翻转 SENSOR_Y_SIGN
水位不满 倾斜90°水只到一侧一半 删除 TILT_MAX 限幅
水位"锯齿" 一行在左一行在右 加蛇形布局修正
编译报错 DFRobot_LIS2DW12.h找不到 安装库时选对作者(DFRobot)
USB不识别 无法烧录 按住BOOT,点RST,松开BOOT

七、效果演示

📹 [这里插入动图/视频]

  1. 竖直放:蓝色水在下方,静静一片
  2. 向左倒:水"哗"地流到左边,屏幕左半边全亮
  3. 倒过来:水"跑"到了顶部,像真正密封的液体
  4. 用力晃:淡蓝色的水花在水面上方散开又消失
  5. 缓缓回正:水慢慢流回,水面线波动两下才安静


八、总结

这个项目的技术亮点不在硬件,而在用一个小小的向量投影公式,把"重力"和"像素颜色"直接挂钩——不需要模拟流体、不需要状态机,只需要把每个像素和重力方向做个点积,就得到了液体在盒子里的样子。

这就是创客的乐趣:用最少的代码,讲一个最直观的物理故事。

创作许可协议

本项目采用 CC BY(署名) 进行许可。

评论(0)
- 没有更多了 -