💡 划重点
- 主控:ESP32-S3,赋能低功耗蓝牙与本地机器学习
- 电量:MAX17048 高精度库仑计,直接读取电压、电流、剩余容量
- 显示:0.96 寸 OLED,同 I2C 总线,状态一目了然
- 交互:双按键,切换预测模式/历史曲线
- 预测:卡尔曼滤波 + 简单线性回归,动态估算剩余使用时间
- 成本:单套物料约 91 元,控制在 95 元内
- 结构:3D 打印表壳,袖珍可靠,极客专属
需求洞察
目标用户:喜欢捣鼓硬件的极客、嵌入式学习者、想拥有独特可穿戴设备的数码玩家。
真实痛点:市面上的智能手表中电池百分比往往不准,尤其在低电量时突然关机,用户永远无法获得“还能用多久”的确定性。普通电压法不能反映电池老化、温度和内阻变化,导致误判。
为什么值得做:MAX17048 不依赖电压,直接通过库仑计数和电池建模输出精确 SoC,ESP32-S3 则赋予项目本地处理和无线能力。一块能自信地告诉你“还能听 2 小时歌”的极客手表,既实用又充满技术魅力,完全可以作为开源方案在社区传播。
方案设计
系统分为四层:
感知层
- MAX17048 燃料计:通过 I2C 读取电压(mV)、电流(mA)、剩余容量(mAh)与百分比,每秒采样一次。
- 板载温度传感器(芯片内部)辅助温度补偿。
- 双按键:K1 用于切换显示页面(瞬时功率 / 预测计时),K2 用于确认/长按复位累计电量。
控制层
ESP32-S3 运行 MicroPython 或 Arduino 固件。主要任务:
- 每 1 秒读取 MAX17048,滑动窗口存储最近 120 个样本。
- 运行在线卡尔曼滤波器,抑制电流尖峰噪声。
- 基于最近 30 秒的平均功耗,结合剩余容量计算“电量耗尽时间”,称为 RUNTIME 预测。
- 管理 OLED 显示、按键中断、低功耗休眠。
- 预留蓝牙广播功能,可发送电量百分比至手机(可选)。
执行层
- OLED 128×64 显示:使用 SSD1306 驱动,同 I2C 总线。主页面示剩余百分比、电压、预测小时数;次页面显示瞬时功率变化曲线。
- 振动马达(可选):电量低于 5% 时短振提醒。
日志/联网层
- 通过 ESP32-S3 的 Wi-Fi / BLE,可将电池统计数据(电压、电流、SoC、运行时间)定期上传到串口监视或 BLE 客户端,便于分析和进一步建模。日志频率 1 Hz。
物料清单
| 序号 | 物料名称 | 数量 | 用途 | 估算单价(元) |
|---|---|---|---|---|
| 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.py 及 max17048.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)
(实际应用需补充完整驱动及错误处理,此处仅展示项目骨架。)
调试步骤
- 单模块测试:单独连接 MAX17048 至 I2C,运行示例代码读取电压、SoC,确认地址 0x36 通信正常。记录电池空载和带载电压。
- OLED 验证:在相同 I2C 总线上挂载 OLED,运行 SSD1306 演示程序,确保无地址冲突。
- 联合调试:将两模块共 I2C 运行,观察串口打印数据是否正常,OLED 刷新有无卡顿。注意 I2C 上拉电阻,若波形畸变可外接 4.7kΩ 上拉。
- 滤波器调优:通过串口绘图软件观察电流原始值与滤波后曲线,调整卡尔曼 Q、R 参数,使响应既灵敏又平滑。
- 预测算法验证:人为改变负载(如点亮 LED 或振动马达),观察预测剩余时间是否线性变化,确保算法无溢出。
- 低功耗优化:若需延长续航,将 ESP32-S3 配置为 light sleep,用定时器唤醒采样,平均功耗可降至 mA 级。
- 结构安装:所有电子部分用热熔胶或螺丝固定于 3D 打印壳内,按键帽对齐,屏幕窗口贴透明保护片。
设计图
流程说明
→ 感知输入:MAX17048 库仑计采集电池电压、电流、剩余容量;按键获取用户指令。
→ 控制处理:ESP32-S3 进行卡尔曼滤波、平均功耗建模、运行时间预测,并管理状态机。
→ 执行反馈:OLED 展示电量百分比、电压、预测时长;振动马达低电提醒。
→ 数据上报:通过 BLE 或串口将历史数据发送至上位机,供深入分析。