💡 划重点
- 本项目用 ESP32-S3 做主控,搭配 MAX17048 电量计芯片与 0.96 寸 OLED,做一块能实时预测剩余续航的极客手表。MAX17048 直接输出荷电状态与充放电速率,比单纯读电压准得多;按键切换页面,串口输出日志,可选 Wi-Fi 上报。整套物料控制在 95 元内,适合有焊接和 Arduino 基础的玩家。
需求洞察
目标用户是极客、嵌入式学习者和喜欢 DIY 手表的人。真实痛点有三个:一是普通手表用电量百分比,本质是电压查表,负载一变就跳,低电量突然关机;二是充电决策靠猜,不知道还能撑几小时;三是想学 I2C、低功耗、传感器融合,却找不到小而完整的项目。MAX17048 自带 ModelGauge 算法,能给出较稳的 SOC 和 CRATE,用它做“实时预测”比电压法更有说服力。手表形态又逼着开发者考虑功耗、显示刷新和按键交互,学习密度高,成品也有日常佩戴价值,所以值得做。
方案设计
感知层:MAX17048 通过 I2C 读取电池电压、SOC、CRATE、温度。CRATE 表示当前充放电速率,单位 %/h,用它算剩余时间最直接。控制层:ESP32-S3 每 2 秒采样一次,对 CRATE 做滑动平均,抑制负载突变造成的抖动;再用 SOC 除以平均放电速率,得到剩余小时数。执行层:OLED 显示电量、电压、剩余时间;两个按键分别切换页面和强制刷新。日志/联网层:串口打印 CSV,方便校准;可选 Wi-Fi 或 BLE 把数据发到手机,做趋势记录。低功耗上,屏幕常亮时用 20 秒自动熄屏,主控进入轻睡眠,按键中断唤醒。
物料清单
| 物料 | 数量 | 用途 | 估算单价 | 合计 |
|---|---|---|---|---|
| ESP32-S3 开发板 | 1 | 主控、蓝牙/Wi-Fi | 38 元 | 38 元 |
| MAX17048 模块 | 1 | 电量计、SOC/CRATE | 16 元 | 16 元 |
| 0.96 寸 OLED I2C | 1 | 显示电量与预测 | 11 元 | 11 元 |
| 轻触按键 | 2 | 切页、刷新 | 0.5 元 | 1 元 |
| 3.7V 500mAh 锂电池 | 1 | 供电 | 13 元 | 13 元 |
| TP4056 充电板 | 1 | 锂电充电保护 | 2.5 元 | 2.5 元 |
| 表壳与表带 | 1 | 佩戴结构 | 10 元 | 10 元 |
| 导线、热缩管 | 若干 | 连接与绝缘 | 2 元 | 2 元 |
| 合计 | 93.5 元 |
接线与结构
I2C 保留给定引脚:SDA=GPIO8,SCL=GPIO9,MAX17048 与 OLED 共用总线。MAX17048 默认地址 0x36,OLED 常用 0x3C,不冲突。按键原定 GPIO3/GPIO4,但 GPIO3 是 ESP32-S3 的启动模式 strapping 脚,上电被拉低可能进入下载模式,因此微调为 GPIO4 和 GPIO5,分别接按键一端,另一端接地,内部上拉。若坚持用 GPIO3,需加 RC 延迟并在上电时避免按下。MAX17048 的 BAT 接电池正极,GND 共地,VCC 接 3.3V;ALRT 可接 GPIO6 做低电量中断。结构上,OLED 开窗粘在表壳正面,按键放在右侧,电池和充电板叠在底层,用海绵双面胶固定。
核心代码
以下为 Arduino 骨架,需安装 Adafruit MAX17048 与 U8g2 库。
#include <Wire.h>
#include <Adafruit_MAX17048.h>
#include <U8g2lib.h>
Adafruit_MAX17048 maxl;
const int KEY_NEXT = 4, KEY_RATE = 5;
float rateBuf[8]; int idx = 0; bool showRate = false;
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
oled.begin();
if (!maxl.begin()) { Serial.println("MAX17048 not found"); while (1); }
}
float avgRate() {
float s = 0; for (int i = 0; i < 8; i++) s += rateBuf[i];
return s / 8.0;
}
void loop() {
float soc = maxl.cellPercent();
float v = maxl.cellVoltage();
float rate = maxl.chargeRate();
rateBuf[idx++ % 8] = rate;
float ar = avgRate();
float hours = (ar < -0.01) ? soc / -ar : 99.0;
if (!digitalRead(KEY_NEXT)) { showRate = !showRate; delay(200); }
if (!digitalRead(KEY_RATE)) { /* 强制刷新 */ delay(200); }
oled.clearBuffer();
oled.drawStr(0, 12, showRate ? "Rate page" : "Battery watch");
oled.setCursor(0, 30); oled.print(soc, 1); oled.print(" %");
oled.setCursor(0, 58);
if (hours < 99) { oled.print(hours, 1); oled.print(" h left"); }
else oled.print("charging / idle");
oled.sendBuffer();
delay(2000);
}
调试步骤
第一步,只接 MAX17048,跑 I2C 扫描,确认 0x36 出现;读电压应与万用表相差 20mV 内。第二步,接 OLED,确认 0x3C 出现且不花屏。第三步,测按键,按下时串口打印键值,检查上拉是否正常。第四步,接电池,观察 SOC 从空到满的充电曲线,若跳变明显,在代码里把 CRATE 滑动平均窗口从 8 调到 16。第五步,用已知负载放电,记录预测剩余时间与实测误差,误差超过 20% 就做线性修正。第六步,测整机电流,熄屏后应低于 5mA,否则关闭不用的外设并延长休眠。