💡 划重点

  • 主控:ESP32-S3,赋能低功耗蓝牙与本地机器学习
  • 电量:MAX17048 高精度库仑计,直接读取电压、电流、剩余容量
  • 显示:0.96 寸 OLED,同 I2C 总线,状态一目了然
  • 交互:双按键,切换预测模式/历史曲线
  • 预测:卡尔曼滤波 + 简单线性回归,动态估算剩余使用时间
  • 成本:单套物料约 91 元,控制在 95 元内
  • 结构:3D 打印表壳,袖珍可靠,极客专属

需求洞察

目标用户:喜欢捣鼓硬件的极客、嵌入式学习者、想拥有独特可穿戴设备的数码玩家。

真实痛点:市面上的智能手表中电池百分比往往不准,尤其在低电量时突然关机,用户永远无法获得“还能用多久”的确定性。普通电压法不能反映电池老化、温度和内阻变化,导致误判。

为什么值得做:MAX17048 不依赖电压,直接通过库仑计数和电池建模输出精确 SoC,ESP32-S3 则赋予项目本地处理和无线能力。一块能自信地告诉你“还能听 2 小时歌”的极客手表,既实用又充满技术魅力,完全可以作为开源方案在社区传播。

方案设计

系统分为四层:

感知层

控制层

ESP32-S3 运行 MicroPython 或 Arduino 固件。主要任务:

  1. 每 1 秒读取 MAX17048,滑动窗口存储最近 120 个样本。
  2. 运行在线卡尔曼滤波器,抑制电流尖峰噪声。
  3. 基于最近 30 秒的平均功耗,结合剩余容量计算“电量耗尽时间”,称为 RUNTIME 预测。
  4. 管理 OLED 显示、按键中断、低功耗休眠。
  5. 预留蓝牙广播功能,可发送电量百分比至手机(可选)。

执行层

日志/联网层

物料清单

序号 物料名称 数量 用途 估算单价(元)
1 ESP32-S3 开发板 (合宙或微雪) 1 主控与无线 28
2 MAX17048 锂电池燃料计模块 1 精确电量测量 16
3 0.96 寸 OLED 模块 (SSD1306 I2C) 1 信息显示 12
4 小型锂聚合物电池 3.7V 400mAh 1 手表供电 15
5 轻触按键 6×6mm 2 用户输入 1
6 3D 打印表壳材料 (PLA) 1 外壳 3
7 振动马达 1027 1 低电量提醒 5
8 电阻电容排母排针等 若干 焊接与连接 4
9 表带 (尼龙) 1 佩戴 7
合计 91

总价约 91 元,预留 4 元用于热缩管、导线等辅料,总成本控制在 95 元内。

接线与结构

所有模块基于同一 I2C 总线,节省 GPIO。具体分配:

外设/引脚 GPIO 说明
MAX17048 SDA 8 I2C 数据
MAX17048 SCL 9 I2C 时钟
OLED SDA 8 (同总线) 共享 I2C 数据
OLED SCL 9 (同总线) 共享 I2C 时钟
按键 K1 3 模式切换,内部上拉
按键 K2 4 确认/复位,内部上拉
振动马达 10 PWM 控制,加 NPN 驱动

说明:OLED 与 MAX17048 共享 I2C,总线地址不冲突(MAX17048 默认 0x36,OLED 默认 0x3C),无需微调。额外增加振动马达至 GPIO10,因为 GPIO 3/4 已被按键占用。

结构:ESP32-S3 板放在表壳底层,锂电池在上方。OLED 屏幕固定于表盘窗口,按键在侧面。3D 打印外壳设计两个按键柱,与微动开关接触。整体用尼龙表带。

核心代码

以下为 MicroPython 骨架,在 ESP32-S3 上可直接运行(依赖 ssd1306.pymax17048.py 库,可自行编写或从社区获取)。


import ssd1306
import max17048
import time
import math

# I2C 初始化
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
fuel = max17048.MAX17048(i2c)

# 按键
k1 = Pin(3, Pin.IN, Pin.PULL_UP)
k2 = Pin(4, Pin.IN, Pin.PULL_UP)

# 振动马达 PWM
vib = PWM(Pin(10), freq=1000, duty=0)

# 卡尔曼滤波器简易实现
class KalmanFilter:
    def __init__(self, q=0.01, r=0.1):
        self.q = q
        self.r = r
        self.p = 1.0
        self.x = 0.0
    def update(self, measurement):
        self.p += self.q
        k = self.p / (self.p + self.r)
        self.x += k * (measurement - self.x)
        self.p *= (1 - k)
        return self.x

kf = KalmanFilter()
current_samples = []
runtime_pred = 0
display_mode = 0  # 0: main, 1: power curve

# 主循环
while True:
    # 读取电量数据
    voltage = fuel.voltage()
    current = fuel.current()
    soc = fuel.soc()
    remaining_capacity = fuel.remaining_capacity()
    
    # 滤波电流
    current_filtered = kf.update(current)
    if len(current_samples) > 120:
        current_samples.pop(0)
    
    # 平均功耗(mA)
    avg_current = sum(current_samples[-30:]) / min(len(current_samples), 30)
    if avg_current <= 0:
        avg_current = 1  # 防止除零
    runtime_pred = remaining_capacity / avg_current  # 小时
    
    # 低电量振动提醒
    if soc <= 5:
        vib.duty(512)
        time.sleep_ms(100)
        vib.duty(0)
    else:
        vib.duty(0)
    
    # 按键处理
    if not k1.value():
        time.sleep_ms(20)
        if not k1.value():
            display_mode = (display_mode + 1) % 2
            while not k1.value():
                pass
    
    if not k2.value():
        time.sleep_ms(20)
        if not k2.value():
            # 复位累计电量(MAX17048 学习复位)
            fuel.quick_start()
            while not k2.value():
                pass
    
    # 显示更新
    oled.fill(0)
    if display_mode == 0:
        oled.text("SOC: {}%".format(soc), 0, 18)
        oled.text("V: {:.2f}V".format(voltage/1000), 0, 30)
        oled.text("Remain:{}mAh".format(int(remaining_capacity)), 0, 42)
        oled.text("Est: {:.1f}h".format(runtime_pred), 0, 54)
    else:
        oled.text("Power (mA)", 0, 0)
        # 绘制电流曲线简图
            y1 = 63 - int((current_samples[i-1]) / 5)
            y2 = 63 - int((current_samples[i]) / 5)
            if 0 <= y1 < 64 and 0 <= y2 < 64:
                oled.line(i-1, y1, i, y2, 1)
    oled.show()
    
    time.sleep(1)

(实际应用需补充完整驱动及错误处理,此处仅展示项目骨架。)

调试步骤

  1. 单模块测试:单独连接 MAX17048 至 I2C,运行示例代码读取电压、SoC,确认地址 0x36 通信正常。记录电池空载和带载电压。
  2. OLED 验证:在相同 I2C 总线上挂载 OLED,运行 SSD1306 演示程序,确保无地址冲突。
  3. 联合调试:将两模块共 I2C 运行,观察串口打印数据是否正常,OLED 刷新有无卡顿。注意 I2C 上拉电阻,若波形畸变可外接 4.7kΩ 上拉。
  4. 滤波器调优:通过串口绘图软件观察电流原始值与滤波后曲线,调整卡尔曼 Q、R 参数,使响应既灵敏又平滑。
  5. 预测算法验证:人为改变负载(如点亮 LED 或振动马达),观察预测剩余时间是否线性变化,确保算法无溢出。
  6. 低功耗优化:若需延长续航,将 ESP32-S3 配置为 light sleep,用定时器唤醒采样,平均功耗可降至 mA 级。
  7. 结构安装:所有电子部分用热熔胶或螺丝固定于 3D 打印壳内,按键帽对齐,屏幕窗口贴透明保护片。

设计图

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

流程说明

感知输入:MAX17048 库仑计采集电池电压、电流、剩余容量;按键获取用户指令。

控制处理:ESP32-S3 进行卡尔曼滤波、平均功耗建模、运行时间预测,并管理状态机。

执行反馈:OLED 展示电量百分比、电压、预测时长;振动马达低电提醒。

数据上报:通过 BLE 或串口将历史数据发送至上位机,供深入分析。