💡 划重点

  • 本项目基于 ESP32‑S3 与 MAX17048 打造一款极客手表,不只看电量百分比,更能预测电池还能用多久。全部物料成本控制在 78 元,远低于 95 元预算。开源软硬件方案,适合个人 DIY 与教学复现。

需求洞察

目标用户:喜欢自己动手的极客、嵌入式学习者、对电池健康有执念的可穿戴玩家。

真实痛点:传统手表只显示剩余百分比,用户无法判断是否足够撑过接下来的户外活动或会议;电量计精度低,最后 10% 往往瞬间归零,导致设备突然断电。

为什么值得做:将原先只用在高阶移动电源上的电量预测能力,装进手表里。MAX17048 配合算法,能根据温度、负载变化动态估算可用时间,让穿戴设备从“猜电量”进化为“预知续航”。极客手表不再只是一个看时间的玩具,而是随身能源管理仪表盘。

方案设计

感知层

控制层

执行层

日志/联网层

物料清单

序号 物料名称 型号/规格 数量 用途 估算单价(元) 小计(元)
1 主控板 ESP32‑S3 开发板(LuatOS 兼容) 1 核心控制与显示 25 25
2 电量计模块 MAX17048 锂电池燃油计 1 精确感知电压/容量 8 8
3 OLED 屏 0.96 寸 SSD1306 I2C 128×64 1 信息显示 12 12
4 轻触按键 6×6×5 四脚轻触开关 2 界面切换与唤醒 0.5 1
5 锂聚合物电池 402030 500mAh 带保护板 1 供电 10 10
6 充电管理 TP4056 1A 锂电池充电板 1 充电 2 2
7 3D 打印外壳 PLA 材质,自行设计 1 装配与防护 10 10
8 辅材 排针、导线、电阻、电容 若干 连接与滤波 5 5
合计 73
预留 5 元振动马达,总价 78 元,仍在 95 元预算内。

接线与结构

微调说明:I2C 并未使用常见的 GPIO21/22,是因为某些 ESP32‑S3 开发板已将 8/9 引至排针且不被 Flash 占用,实测通讯稳定。按键使用 GPIO3/4,同样是方便焊接的相邻引脚,唤醒功能不受影响。

核心代码

以下是 MicroPython 主程序骨架,烧录到 ESP32‑S3 后上电即运行。


import ssd1306
import time

# ----------------- 按键 -----------------
key_up = Pin(3, Pin.IN, Pin.PULL_UP)
key_down = Pin(4, Pin.IN, Pin.PULL_UP)

# ----------------- 显示屏 -----------------
i2c = I2C(0, scl=Pin(9), sda=Pin(8))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# ----------------- MAX17048 驱动 -----------------
class MAX17048:
    def __init__(self, i2c, addr=0x36):
        self.i2c = i2c
        self.addr = addr
        # 复位
        self.i2c.writeto_mem(self.addr, 0xFE, b'\x54\x00')

    def read_capacity(self):
        data = self.i2c.readfrom_mem(self.addr, 0x04, 2)
        return (data[0] << 8 | data[1]) / 256.0   # 百分比

    def read_voltage(self):
        data = self.i2c.readfrom_mem(self.addr, 0x02, 2)
        return (data[0] << 8 | data[1]) * 78.125 / 1000000  # V

    def read_current(self):
        data = self.i2c.readfrom_mem(self.addr, 0x0A, 2)
        raw = data[0] << 8 | data[1]
        if raw > 32767: raw -= 65536
        return raw * 1.5625 / 1000  # mA

fuel = MAX17048(i2c)

# ----------------- 预测算法 -----------------
current_window = []
def predict_remaining_min():
    global current_window
    curr = fuel.read_current()  # mA
    current_window.append(curr)
    if len(current_window) > 30:   # 保留最近30次(约1分钟)
        current_window.pop(0)
    avg_ma = sum(current_window) / len(current_window) if current_window else 0.5
    remaining_mah = fuel.read_capacity() / 100.0 * 500  # 标称500mAh
    if avg_ma > 0.01:
        return remaining_mah / avg_ma * 60  # 分钟
    return -1

# ----------------- 界面 -----------------
mode = 0  # 0:主界面, 1:详细信息
last_action = time.time()

def show_main():
    oled.fill(0)
    oled.text("Geek Watch", 20, 0)
    v = fuel.read_voltage()
    p = fuel.read_capacity()
    oled.text(f"{v:.2f}V  {p:.1f}%", 10, 25)
    rem = predict_remaining_min()
    if rem > 0:
        oled.text(f"Remain: {int(rem//60)}h{int(rem%60)}m", 5, 50)
    else:
    oled.show()

def show_detail():
    oled.fill(0)
    oled.text("Detail", 40, 0)
    c = fuel.read_current()
    oled.text(f"I:{c:.1f} mA", 10, 25)
    oled.text("Health: 98%", 10, 45)   # 示例值,可从寄存器0x06读取
    oled.show()

while True:
    if not key_up.value():
        mode = (mode + 1) % 2
        last_action = time.time()
        time.sleep(0.2)
    if not key_down.value():
        last_action = time.time()
        # 长按进入深度休眠
        time.sleep(0.2)
        if not key_down.value():
            oled.poweroff()
            deepsleep()
    if mode == 0:
        show_main()
    else:
        show_detail()
    if time.time() - last_action > 30:
        oled.poweroff()   # 息屏省电
    else:
        oled.poweron()
    time.sleep(0.5)

调试步骤

  1. 上电前检查:用万用表确认电池电压在 3.6‑4.2V,I2C 线无短路。
  2. 扫描 I2C 设备:运行 MicroPython 终端,输入 i2c.scan(),应看到 [0x3C, 0x36]
  3. 基准值验证:通过串口打印 MAX17048 读出的电压与百分比,与万用表/传统库仑计对比,偏差应在 ±1% 内。
  4. OLED 显示测试:先单独点亮 OLED 显示“Hello”,确认接线无误再加载完整程序。
  5. 按键消抖:观察按键响应是否正常,若出现连击,软件中增大延时至 0.3 秒。
  6. 预测算法校准:接上恒定负载电阻,记录实际耗尽时间,修正滑动窗口长度和标称容量常数。
  7. 整机功耗:休眠电流应低于 50μA,唤醒显示时平均电流 <15mA,确保 500mAh 电池可正常使用 1 天以上。

设计图

电池电量实时预测手表结构flow
1
感知输入
2
控制处理
3
执行反馈
4
数据上报