ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ESP32墨水屏手表低功耗设计:Deep Sleep+RTC唤醒7天续航

ESP32墨水屏手表低功耗设计:Deep Sleep+RTC唤醒7天续航 上个月我把做好的墨水屏手表戴到办公室同事看了一眼说“墨水屏不是很省电吗”第二天手表就没电躺平了现场非常尴尬。拆开量完电流我才明白墨水屏省电但“整机省电”完全是另一回事——ESP32一直醒着墨水屏驱动刷完还在待命电池当然顶不住。这篇文章就把我后来怎么靠硬件级休眠和RTC唤醒把功耗抠下去、最终稳定跑出7天续航的完整思路拆开聊一聊全程都是实测数据和踩坑记录适合所有想做低功耗DIY手表、传感器节点或任何电池供电项目的朋友。1. 墨水屏手表掉电快先拆清三大耗电黑洞1.1 第一个黑洞你以为墨水屏省电整机却不省墨水屏确实有个天生优势显示内容不刷新时屏幕本身不耗电靠的是双稳态像素保持画面。但“屏幕不耗电”不代表“系统不耗电”。手表里除了屏幕还有主控、传感器、驱动IC、LDO稳压器、电平转换电路每一个都在默默吃电。很多DIY项目的代码刚跑起来时WiFi/蓝牙默认开着串口日志疯狂往外打定时器在active状态里轮询GPIO悬空产生漏电板上LED常亮——这几个因素叠加起来整机电流窜到50mA甚至更高都不奇怪。你就算屏幕一个月只刷新一次其他时间系统都在空转电池也撑不过两天。所以做这类项目必须接受一个事实续航是系统级问题不是屏幕问题。墨水屏只是把“显示”这部分的功耗压在极低水平剩下所有模块能不能断电、主控能不能进入真正意义上的硬件深睡才是决定续航的关键。1.2 第二个黑洞MCU 的 active / light sleep 状态很贵ESP32 的功耗分好几个档位很多人对它最大的误解是“开了睡眠模式就省电了”。其实睡眠模式也有三六九等差距能到几千倍。我把典型数值整理过大致如下工作状态典型电流唤醒方式适用场景Active关WiFi/BTCPU全速30-60mA-刷新屏幕、处理逻辑Modem SleepRF关闭CPU运行20-40mA-长时间本地计算Light SleepCPU暂停外设可配0.5-1mA定时器/GPIO/触摸待机但要求快速响应Deep SleepRTC域保留7-15uA视模组定时器/GPIO/RTC手表、传感器节点主休息状态外设全断电 Deep Sleep接近RTC芯片底噪外部硬件中断本项目最终目标重点说两个陷阱一是Light Sleep远没有那么省电。它确实比active省掉90%以上但依旧停留在毫安级对动辄要跑几天几周的可穿戴设备来说依然太奢侈。你每分钟醒一次、每次停留100ms如果之间全在Light Sleep一天下来光等待功耗就相当可观。二是很多模组标称的Deep Sleep电流只有几uA。但你的板子如果带着USB转串口芯片、电源指示灯、稳压器、分压电阻这些外围器件能把你辛辛苦苦抠出来的uA级底噪直接拉回到几十甚至上百uA。这就是为什么后面我会强调开发板做手表天然不适合要用模组或自己重新设计最小系统。1.3 第三个黑洞墨水屏驱动和外设的空载损耗墨水屏驱动板的工作状态比你想的复杂。1.54寸这种小屏驱动IC是SSD1681或相近型号工作时需要内部DC-DC把电压抬到十几伏驱动像素刷新时刻瞬时电流可以到30-50mA。刷新完成后如果驱动IC继续上电它的待机电流虽然比刷新时小但依然存在。不少现成的墨水屏模块上还集成了稳压芯片、限流电阻、电平转换、状态LED这些模块静态电流有的能做到uA级有的能做到几十uA甚至几百uA。如果你的墨水屏模块直接挂在电池上哪怕ESP32已经深睡了模块上的稳压器和驱动IC的漏电流依然在放血。另外传感器也一样。很多DIY手表喜欢集成温度、气压、加速度计但你没有意识到这些传感器在“待机模式”下也会用掉0.5uA到几uA多个叠加起来就让底噪翻倍。对这些外设正确的做法不是让它“待机”而是物理上断掉它的电源。把这三个黑洞看清楚后项目方向就很明确了MCU进入Deep Sleep外部RTC负责硬件级闹钟唤醒所有模块电源用MOS开关物理切断。下面一个一个说。2. 硬件级降耗三板斧芯片选型、外挂RTC、模块电源开关2.1 芯片选型为什么我最后选的是ESP32-C3而不是经典ESP32经典ESP32确实也能做手表但它的Deep Sleep数据手册标称大约10uA再加上模组上的LDO漏电整机底噪极难压到20uA以内。ESP32-C3在标称参数上就更有优势官方数据表里Deep Sleep模式典型值在5uA上下芯片尺寸小RISC-V内核引脚够用但不多正好适合手表这种GPIO紧张的项目。我实际用两块方案对比过。同样外接RX8025T、同样执行“唤醒→刷新→深睡”的循环经典ESP32整机底噪大概在20-25uAC3能稳定做到12-16uA。别小看这不到10uA的差距如果按每分钟唤醒一次的节奏跑一天下来就会差出0.2mAh左右对一块60-120mAh的小电池来说直接影响能不能撑过第七天。还有一个容易忽略的点ESP32-C3模组本身有板载PCB天线体积和功耗都适合手表。但注意我选的是“模组”而不是“开发板”。开发板上那颗USB转串口芯片只要上电不管你有没有插USB它通常会吃掉1-5mA甚至更多这东西对续航是灾难级的。如果需要调试我会在烧录完程序后把USB芯片的供电路径切断或者干脆买不带USB转串口的最小模组。2.2 外部RTC内部时钟在深睡里根本不配当“手表”ESP32的Deep Sleep模式下保留了一个内部的慢速RTC时钟确实可以定时唤醒但精度很一般。用手表场景显示时间需要的是“真实世界时刻”而不只是“距上次唤醒经过了多长时间”。如果ESP32内部RTC算秒一天漂移几秒到几十秒很正常再加上休眠期间温度变化累计误差会越来越大。所以我选择了外挂RTC芯片由它来维护绝对时间并在闹钟时间到达时输出一个硬件中断把ESP32从Deep Sleep里硬生生叫醒。外部RTC芯片很多我当时对比过几种RTC芯片计时电流优点注意点DS3231约2-3uA精度极高自带温度补偿晶振功耗偏大对超长续航不友好PCF8563约0.25-0.5uA功耗极低价格便宜精度依赖外部32.768K需要好晶振RX8025T约1uA以内内置温度补偿晶振精度好I2C接口货源选型需要确认封装和假货问题我最后选的是RX8025T。原因很简单功耗处于uA级下段精度比PCF8563配普通晶振稳很多而且闹钟中断输出可以配置成电平保持模式正好用来唤醒ESP32。如果你手头有PCF8563且对精度要求不高完全可以用它的计时电流甚至更低。DS3231虽然方便好用但3uA量级的固定开销在手表的预算里太奢侈了。这里还要强调一个概念外部RTC是“硬件级”唤醒的关键。它不需要ESP32运行任何代码也不需要ESP32的定时器参与只要它检测到闹钟时间到了就会把GPIO拉低ESP32的ext1唤醒源看到这个电平变化后自己从Deep Sleep里起来。这才是“硬件级休眠RTC唤醒”的真正含义。2.3 外设电源开关每个耗电模块都要有一把“闸刀”软件级控制设备进入休眠远不如物理断电来得干净。我给每一个非必须的外设都加了MOS管或负载开关包括墨水屏驱动板、传感器、电平转换电路。硬件拓扑大概是这样的电池正极直接进LDOLDO输出3.3V给ESP32模组供电。LDO选的是静态电流极低的型号比如TPS7A02系列实际静态功耗可以到几百nA到1uA左右。电池到墨水屏驱动板之间串一个P-MOS开关由ESP32的GPIO控制。传感器模块也单独走一个MOS开关不工作时彻底断开。电池电压检测的分压电阻不直接接电池而是经过一个MOS控制只在测量瞬间接通避免电阻分压长期漏电。为什么用LDO而不是DC-DC很多新手觉得DC-DC效率高就更好但对小电流休眠场景不一定是。DC-DC芯片即使轻载也可能有几uA到几十uA的静态电流反而把底噪抬高了。选一颗低压差的线性稳压器自身静态电流在uA级甚至更低虽然工作时效率可能不如DC-DC但手表大部分时间都在深睡底噪才是决定续航的关键。如果你要做的是经常高强度工作、响应时间长的设备那再重新评估DC-DC这个选型要按实际功耗模型来定。3. 软件核心Deep Sleep RTC硬件唤醒的完整实现3.1 状态机设计不要用轮询要用事件驱动整个手表软件不是一个一直跑的循环而是三个状态之间的切换工作态、唤醒处理态、深睡态。工作态完成一次时间刷新包括读RTC、计算显示内容、驱动墨水屏刷新、保存需要保留的变量整个过程控制在1秒到1.5秒内完成。唤醒处理态主要做唤醒原因判断。如果是RTC闹钟产生的中断说明该刷新时间了如果是首次上电或看门狗复位就执行完整初始化。深睡态ESP32执行esp_deep_sleep_start()CPU停止取指大部分RAM掉电只保留RTC域的最小资源等待外部引脚唤醒。我见过很多项目失败在“每隔多久醒来”的定时器思路上。定时器本身没问题但它只能告诉你“时间到了”不能告诉你“现在真实时间是多少”。手表最终要显示的是真实世界的时分所以就必须让RTC芯片来主导时间线。RTC知道自己什么时候闹钟响就能在整分钟或整小时时刻发出硬件中断ESP32醒了之后直接读取当时的时间再刷新屏幕逻辑很顺。3.2 GPIO 唤醒与RTC闹钟中断的接法别让短脉冲白唤醒外部RTC芯片的闹钟中断输出引脚接到ESP32的RTC域GPIO上。我在C3上的接法是INT引脚接到GPIO4这个引脚支持Deep Sleep外部的电平唤醒。真正的坑在于ext1唤醒源识别的是“电平状态”不是“边沿跳变”。ESP32的ext1配置成ANY_LOW模式意思是只要指定GPIO组里有任意一个处于低电平芯片就唤醒。如果你的RTC闹钟输出的是短暂的100ms脉冲击低理论上也能唤醒但实测中如果RTC中断引脚在唤醒后立刻恢复高电平CPU进入主循环时排查唤醒原因可能会不稳定甚至遇到唤醒丢失。我最终的方案是把RX8025T的闹钟中断配置成“输出低电平直到主机通过I2C读寄存器清除闹钟标志后才恢复高电平”。这样ESP32看到的是一个持续的低电平ext1能可靠识别且在清除标志前不会再次触发误唤醒。具体RTC寄存器设置手册里写得很清楚关键是“保持低直到清除”这个行为要靠配置而不是默认状态。关键代码如下这里用ESP-IDF风格接口Arduino框架也兼容#include esp_sleep.h #include driver/gpio.h #define RTC_INT_GPIO GPIO_NUM_4 #define EPD_PWR_GPIO GPIO_NUM_5 void enter_deep_sleep(void) { // 先关掉墨水屏电源 gpio_set_level(EPD_PWR_GPIO, 0); // ext1唤醒任意GPIO为低电平就唤醒 esp_sleep_enable_ext1_wakeup(1ULL RTC_INT_GPIO, ESP_EXT1_WAKEUP_ANY_LOW); esp_deep_sleep_start(); }注意在进入深睡之前还需要把RTC_INT以外的GPIO设置为合理的电平尤其不能悬空。悬空引脚在深睡期间的微弱漏电看似不起眼但多个引脚叠加就会把底噪拉高。3.3 唤醒后的完整工作流与代码骨架唤醒之后程序会从头开始执行而不是从之前停下来的地方继续。所以要在main/setup最开头判断唤醒原因。完整的流程如下读取esp_sleep_get_wakeup_cause()判断是不是EXT1唤醒。如果是EXT1说明是老朋友闹钟来了走轻量刷新流程如果是别的原因或冷启动做完整初始化。通过I2C读取RX8025T的当前时分秒如果需要校准或者记录日期一并读出来。打开墨水屏电源MOS延时等待电压稳定初始化SSD1681。根据当前时间计算要显示的内容。如果和上次显示内容变化不大用局部刷新跨小时或跨天时做一次全刷消除残影。等驱动IC的BUSY拉高表示刷新完成发送深度休眠命令给墨水屏驱动然后把MOS关掉。回到enter_deep_sleep()等待下一次硬件中断。一个比较典型的代码骨架void setup() // Arduino框架如果是IDF从app_main开始 { esp_sleep_source_t cause esp_sleep_get_wakeup_cause(); if (cause ESP_SLEEP_WAKEUP_EXT1) { // 外部RTC闹钟唤醒进入轻量刷新 handle_rtc_alarm(); } else { // 冷启动或复位做完整初始化 init_all(); } // 刷新完成后进入深睡 enter_deep_sleep(); } void handle_rtc_alarm() { rtc_read_time(); epd_power_on(); epd_init(); if (need_full_refresh()) { epd_full_refresh(); } else { epd_partial_refresh(); } while (epd_busy()) delay(10); epd_sleep(); // 驱动IC进入睡眠 epd_power_off(); // 切MOS }这里有个技巧不要在唤醒后启动WiFi或蓝牙。手表显示时间不需要联网每次启动WiFi不仅要多花几十毫安还会拖长工作时间。如果你后续要做NTP校时或消息推送建议把“联网任务”设计成一个独立的“特殊状态”比如在按钮唤醒时进入而不是每次RTC闹钟都启动。3.4 电源开关的时序顺序先开后关顺序错了就残影墨水屏项目管理电源最容易出事的就是时序。我总结了一个必须严格遵守的流程开电源时先打开MOS开关等待3-5毫秒让电容电压稳定再初始化驱动IC否则模组的电平转换或DC-DC可能没进入稳定工作状态初始化失败会偶发出现。刷新完成时先读取BUSY引脚确认最后一笔像素已经写完然后发送PWR_DOWN命令给驱动IC再等待驱动IC进入低功耗状态最后切MOS断电。如果刷完直接切MOS驱动IC的VCOM电压没释放下次上电后屏幕可能残留严重的上一帧图案这就是我后面在踩坑部分会详细讲的问题。另外如果墨水屏和ESP32共用3.3VMOS开关只控制墨水屏模块的电源输入是不够的。模块驱动板上的电平转换电路、稳压器都要一起断电。所以MOS开关要放在模块电源入口的总线上而不是只断某一个子功能。4. 实测功耗数据与7天续航的达成路径4.1 平民级的功耗测量方法做低功耗项目测量工具很重要。专业做法是使用源表或功耗分析仪可以同步看到电压、电流和时间轴但对大多数业余爱好者不现实。我分享一下没有高大上设备时的测量方案。第一种测静态深睡电流用万用表的uA档串联在电池回路里读数稳定后就是静态底噪。这个过程很简单但必须确保唤醒事件不会发生可以临时把RTC闹钟关掉再测。第二种测动态事件电流单个电流脉冲太快万用表反应不过来。我在电池输出端串联一个10欧姆精密采样电阻用示波器电阻两端的电压波形再用欧姆定律换算成电流。示波器能看到每次唤醒和刷屏的完整电流轮廓还能读出时间长度。第三种全周期平均电流如果手里有带mAh累计功能的USB表或充电监测模块可以给锂电池充电到固定电压跑一整晚看累计消耗了多少mAh。这是对前面理论计算的最佳应验办法。4.2 各阶段实测电流和时长以我的C3 RX8025T 合宙1.54墨水屏方案为例实测得到的数据如下阶段实测电流持续时间说明整机Deep Sleep13-16uA长期其中ESP32约8uARTC约1-2uALDO与MOS漏电约4-6uA唤醒启动20-30mA约30ms从深睡唤醒到进入主函数RTC读取与时间计算20mA约50-100msI2C操作逻辑处理不启动WiFi墨水屏局部刷新30-40mA约800-1200ms1.54寸局部刷新时间取决于改动面积关屏与清理5mA约20ms等BUSY、发PWR_DOWN、切MOS每次唤醒完整周期大约1秒到1.5秒按平均电流30mA算单次耗电约0.008-0.012mAh。这个数字是续航估算的关键。4.3 平均功耗模型与电池预算先给一个最核心的公式日均容量 深睡电流 × 24小时 唤醒次数 × 单次唤醒耗电拿我最常用的120mAh锂电池为例按每分钟起床刷新一次算深睡部分15uA × 24h 0.36mAh/天唤醒部分1440次 × 0.01mAh 14.4mAh/天合计约14.76mAh/天120mAh标称能用8天考虑电池自放电和低电量保护实际7天左右如果改成每5分钟唤醒一次唤醒次数降到288次唤醒部分约2.88mAh/天合计3.24mAh/天120mAh可以用37天以上这两组数字帮我找到一个重要的直觉7天续航其实不算苛刻反而是很稳健的中间档。你不需要把深睡底噪压到极致只需保证两点——深睡时底噪在20uA以内每次唤醒的工作时间控制在1.5秒以下。相反如果唤醒后习惯性地跑WiFi、打印日志、等待某个传感器稳定单次唤醒时间拖到3秒甚至5秒那每分钟刷一次的方案日均耗电立刻翻三倍7天续航直接崩塌。4.4 不同刷新频率下的续航估算我把这组计算整理成表格方便直接对号入座刷新策略唤醒次数/天刷新耗电深睡耗电日均合计120mAh续航每分钟刷新144014.4mAh0.36mAh14.76mAh约8天每5分钟刷新2882.88mAh0.36mAh3.24mAh约37天每小时刷新240.24mAh0.36mAh0.6mAh约200天实际受限于电池自放电这里也解释了一个很多人困惑的点为什么每小时刷新一次“理论上”能跑一年实际却很难因为电池自放电、LDO退化、MOS漏电、墨水屏驱动板在断电瞬间的瞬态漏电都会随长时间积累暴露出来。所以“理论续航”看看就好实际项目里最好按照理论值打五折做设计。5. 踩坑记录与避雷指南5.1 墨水屏断电顺序不对重影白屏我第一版代码在刷新完成后直接关MOS结果第二天早上发现屏幕刷出了残留重影甚至偶尔开机全白。排查后确认是驱动IC没有进入深度休眠就被断电了。屏幕驱动IC的像素电压在刷新后处于一个特殊电位如果直接切断电源VCOM和栅极驱动之间的残余电荷没有被正确释放下一次上电时就会出现奇怪的行残留。解决方法是严格按照SSD1681数据手册的睡眠时序先发PWR_DOWN命令0x02再读BUSY直至就绪最后再切MOS。我加了这一步之后再没出现过重影问题。5.2 唤醒后RTC芯片I2C不响应死等项目跑到一半出现过这样的情况RTC闹钟把ESP32叫醒了但I2C总线上读RX8025T始终无应答程序卡在读时间这一步。后来定位到是因为进入深睡前I2C引脚的电平状态不对导致总线被“锁住”。解决方法有两层。第一层把I2C的两个引脚配置为开漏输出并加上拉电阻保证不通信时总线处于已知空闲状态。第二层在进入深睡前将这两个GPIO释放为普通输入模式不要让内部上拉或下拉电阻生效。唤醒后初始化I2C驱动前先对总线做一次软复位切换CLK线几次把可能残留的异常状态清掉。从那以后I2C“死等”的问题再没复现。如果你不想跟硬件I2C外设的初始化过程较劲用GPIO模拟的软件I2C也是一个完全可行的选择。软件I2C不依赖外设状态唤醒后直接重新配置引脚就可以工作牺牲一点时序性能但换来了极强的稳定性。5.3 C3上的ext1可用引脚有限别乱接经典ESP32支持很多GPIO参与ext1唤醒但ESP32-C3的RTC域GPIO数量少很多并不是所有引脚都能唤醒。我第一次把RTC中断接到了GPIO8结果闹钟怎么配置芯片都醒不过来。一翻数据手册才发现C3上支持ext1的引脚是有特定范围的不是所有普通GPIO都能参与Deep Sleep外部唤醒。所以在画板子之前先在乐鑫官方手册或ESP-IDF头文件里确认一下RTC_GPIO的定义选择明确的可用引脚。我在C3上最终选了GPIO4作为RTC中断输入实测稳定。还有一点如果你在Arduino框架下写代码部分引脚定义有别名最好直接使用GPIO_NUM_4这类宏别凭记忆写数字。5.4 深睡期间的漏电路径LDO、分压电阻、串口芯片整机深睡电流一开始怎么都降不到20uA以下后来一块块排查发现是三个地方在漏电一是电池电压采样电阻。我最初直接用两个100K电阻分压到ADC结果这两个电阻在深睡状态下常年吃电流算下来好几百uA。后来把分压电阻的电源端也用MOS开关控制只在测量时打开测量完之后立刻关闭。二是开发板上的USB转串口芯片。只要芯片上电即使没有USB连接也会消耗数毫安到数十毫安。所以成品验证阶段我都是直接买模组不用开发板或者在完成调试后把USB芯片的供电彻底断开。三是LDO使能引脚。有些LDO的EN脚如果不接明确电平会在深睡时出现额外漏电甚至引起输出不稳定。EN脚直接接电池正极或者用电阻上拉不能悬空。排查漏电有个笨办法但非常有效从整机电流反推把所有外设逐一用跳线断开每断一个就重新测一次深睡电流最后会找到所有可疑漏电路径。这个过程虽然慢但能养成很好的硬件嗅觉。5.5 电压检测的坑ADC不准导致误判低电量手表一定会有电量显示但ESP32的ADC在唤醒后第一次采样经常不准。我用内部参考电压做校准后低电量阈值仍不稳定有一阵子电池还剩60%就报低电。问题根源有两个。一是唤醒后ADC需要一点时间稳定第一次采样结果往往偏低二是分压检测回路如果存在开关切换刚打开的瞬间电容没有充满采样值会严重失真。后来我在代码里加了“测量前延时多次采样取中位数”的机制并且把分压电阻开关在启动测量前提前打开几毫秒让电压稳定。实测下来精度可用了。对于不要求精确百分比显示、只求估算电量的手表来说完全够用。折腾完这一圈我最实际的心得是低功耗项目的上限不是某一颗芯片或某块屏幕决定的而是被所有微小的漏电路径和每一个唤醒事件的时间长度决定的。你把深睡底噪从100uA压到15uA同时把单次唤醒时间从3秒压缩到1秒这两件事做好7天续航只是起点。希望这篇笔记能帮你少走几天弯路。
返回列表