ARTICLE DETAIL

资讯详情

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

ESP32墨水屏手表低功耗方案:Deep Sleep与RTC唤醒实战拆解

ESP32墨水屏手表低功耗方案:Deep Sleep与RTC唤醒实战拆解 做了大半年墨水屏手表最常被问的一句话就是ESP32不是出了名的大功耗吗你怎么敢拿它做手表我一开始也这么担心实测下来才发现只要把硬件级休眠和RTC唤醒玩明白ESP32做墨水屏手表不仅可行还能抠出将近一周的续航。这篇文章就把我这套方案的核心设计、底层逻辑和踩坑记录完整拆开讲想复刻的可以直接照抄。1. 内容整体设计与思路拆解1.1 为什么选ESP32而不是nRF52840或ST32低功耗系列先说结论选ESP32不是因为它在低功耗上天赋异禀而是因为它性能强、生态成熟、便宜而且只要用了对的方式功耗完全可以被压到可接受的范围。大部分人潜意识里有一个误区觉得“低功耗设备就该用低功耗单片机”于是上来就想选nRF52840、STM32L4之类的。但这些都是ARM Cortex-M内核算力有限玩图形UI、处理传感器数据、跑网络协议栈资源很吃紧。ESP32是双核Xtensa LX6主频能到240MHz带Wi-Fi和蓝牙做交互、做显示、做数据解析余量远比M系列芯片足。关键在于ESP32最低功耗模式下可以跑到大约10uA级别Deep Sleep RTC这个量级对于一天看十几次时间的墨水屏手表来说已经够用。当然你要用Light Sleep甚至Modem Sleep那是肯定扛不住的。把这颗芯片当低功耗芯片用核心思路不是“它本身功耗低”而是“大部分时间直接断电只保留最小系统活着”。1.2 墨水屏为什么是手表的天然搭档墨水屏显示静态内容时不耗电只有刷新瞬间才消耗能量。这是它和TFT/LTPS屏幕之间最大的本质差异。手表的UI几乎全是静态页面——时间、日期、步数、天气图标、心率数值这些内容本质上就是几幅静态画面轮换简直是为墨水屏量身定做的。我用的是合宙1.54英寸黑白墨水屏分辨率200x200驱动芯片是SSD1681。它的刷新电流大概在20-30mA之间但一次全刷只要1-2秒平均功耗摊下来很低。局部刷新功耗更小能控制在几mA的水平。比起同尺寸TFT屏幕动辄四五十毫安的背光消耗墨水屏在手表场景下有着压倒性优势。不过墨水屏也有它自己的脾气比如刷新慢、残影重、低温下刷新失败率高。这些特性在手表方案里都需要针对性地处理。稍后会详细讲怎么让它在手表的实际使用中表现得足够“顺手”。1.3 整套低功耗方案的顶层思路能断电就断电这里得先把设计思想讲明白。很多人在低功耗上翻车不是因为方案想得不够多而是因为“没断干净”一直在找各种降低运行功耗的办法却忽略了断电才是功耗优化里最划算的操作。我的思路是三层递进第一层所有外设默认断电。屏幕上电、传感器供电、SD卡供电全部用MOS管或负载开关单独控制MCU的GPIO置低才能通电平时一律断开。第二层MCU绝大部分时间处于Deep Sleep只有RTC定时器或者外部事件按键、充电检测能把它唤醒。第三层即便在Deep Sleep期间也对唤醒源做了细分让每个“醒来”的动作尽量短、尽量少。一层层往下抠续航自然就上去了。后面所有内容基本上都是围绕这三层来展开的。2. 硬件级休眠与RTC唤醒的细节拆解2.1 ESP32的Deep Sleep模式到底做了什么ESP32的Deep Sleep模式下数字内核、大多数RAM、Wi-Fi和蓝牙模块全部断电只保留RTC存储器和RTC外设包括ULP协处理器、RTC定时器、RTC GPIO供电。简单点理解就是整个CPU直接“关灯睡觉”只留下一个值班闹钟在看着时间。这个模式下电流标称在10uA左右。注意这里有一个很容易踩坑的细节并不是你调用esp_deep_sleep_start()之后电流就一定能到10uA实际功耗受GPIO浮空、外部漏电、板载LDO静态电流等因素影响很大。很多人说“我的ESP32 Deep Sleep要1mA”十有八九就是某个引脚悬空导致的漏电或者板子上那个电源指示灯还在亮着。我用的模组是ESP32-WROOM-32E但在这里要提醒一句开发板上带的USB转串口芯片比如CP2102和LDO在Deep Sleep期间一样在耗电如果直接用开发板做手表Deep Sleep电流至少是30-80uA起步。所以我最后的设计是直接打板把核心模组和外围电源路径完全分开设计绕开了开发板的偷电问题。提示如果非要拿开发板做原型验证记得把板载稳压芯片的EN引脚拉低或者直接在电池到VIN之间加一个物理开关否则你测到的休眠电流会是“开发板电流”而不是模组本身的电流。2.2 RTC定时器唤醒的原理与误差来源RTC唤醒的核心机制是Deep Sleep状态下芯片内部的RTC定时器仍然运行在32.768kHz的慢速时钟上。当计数值达到设定阈值时产生一个唤醒事件芯片重新进入正常启动流程然后esp_sleep_get_wakeup_cause()就能读到这次唤醒的来源。正常来说ESP32的RTC时钟有两种选择内部RC振荡器默认和外部32.768kHz晶振。内部RC的精度比较差温度漂移明显实测一天可能偏出几十秒到几分钟。手表如果一天偏几分钟这个手表的可用性就基本为零。所以我在设计时做了两件事在PCB上预留了外部32.768kHz晶振的位置并且在固件初始化里主动把RTC时钟源切到外部晶振。在每整点唤醒时通过NTP对准一次时间。当然NTP需要连接Wi-Fi连接期间功耗上升但一天只连几次整体摊下来可接受。如果不想联网外部晶振本身的精度也足够在几天内保持合理走时。这个取舍背后的逻辑是RTC唤醒只是“知道该醒了”它不负责“知道现在几点”。准确的时间基准是另外一套系统这两者的职责必须分开。不少人把RTC唤醒当成了“实时时钟”本身在用醒过来发现时间偏了就觉得方案不行其实根本问题是没给ESP32配准时的时钟源。2.3 区分EXT0与EXT1外部唤醒按键唤醒的两种玩法ESP32在Deep Sleep状态下还支持两种外部唤醒方式EXT0直接指定某一个RTC GPIO的电平触发唤醒只能用一个引脚。EXT1多个RTC GPIO组合触发支持按“任一引脚满足指定电平”来唤醒最多支持8个脚。手表上按键一般有两个一个用来手动切页面一个用来唤醒屏幕。我直接用了EXT1把两个按键都配成高电平唤醒。这样用户按下任意一个按键都能把MCU从Deep Sleep里拉起来。EXT1的另一个好处是醒来后可以读esp_sleep_get_ext1_wakeup_status()知道具体是哪个引脚触发的直接决定这次醒来是切页还是亮屏省一次额外判断。有一点要注意外部唤醒脚需要设置为上拉或下拉让它在常态下有确定的电平。否则按键悬空时引脚电平乱跳要么反复误唤醒要么直接烧额外的漏电流。最好用的配置是GPIO上拉按键另一端接地按下时拉低唤醒条件设成低电平触发。按键回路里串一个1k电阻用来防止静电和抖动电流冲击GPIO。我实际踩过一个坑一开始用的是GPIO内部上拉但内部上拉阻值在几十k欧到上百k欧之间实际强度很弱在潮湿环境或者PCB有污染时引脚电平不稳定偶尔会半夜自己“醒”过来。后来改成外部10k欧上拉电阻问题直接消失。3. 功耗分析与分模块电流实测3.1 系统级电流预算先算一笔总账这样才能清楚知道每个模块能分到多少电流预算再决定什么该省、什么不能省。假设目标续航7天电池容量用一块90mAh的软包锂电池厚度控制在3mm以内能塞进表壳的尺寸。这里不取满容量按实际可用80mAh来算。7天一共168小时每小时平均功耗预算大概是0.476mAh平均电流约0.476mA。这个预算其实不算紧因为大部分时间系统处在Deep Sleep电流只有几十uA。真正的大头在于每天若干次刷新屏幕、联网校时、读取传感器这些瞬态高功耗动作。按照一天看60次手表、每次唤醒1.5秒、唤醒期间平均电流60mA计算60次 x 1.5秒 x 60mA 5400 mA·s 1.5mAh/天七天大约是10.5mAh。屏幕刷新一天按300次、每次全刷消耗0.5mAh来计算七天大约1.5mAh左右。联ftime如果一天做4次每次2秒、吃180mA的平均电流七天大约0.28mAh。把Deep Sleep本身漏电流按30uA算七天大约5mAh。多项加总之后七天的总消耗约17.3mAh离80mAh的可用容量还有很大的余量。这说明7天续航不仅可行而且把屏幕刷新做得再激进一些也能扛得住。3.2 用万用表还是用精度更高的功耗仪测功耗这件事我强烈建议不要只靠万用表。万用表采样率低对毫秒级的瞬态电流几乎无感而ESP32的唤醒流程充满了毫秒级尖峰。你用万用表看到的是一个“平均糊掉”的数字根本看不出哪一环节在偷电。我用的是一台Nordic Power Profiler Kit 2PPK2它的采样率能达到每秒几万次可以把电流波形完整画出来。如果没有这个设备也可以用INA219模块搭配Arduino做一个简易的电流记录仪采样能到1kHz左右虽然看细节不如PPK2清晰但定位问题足够了。具体测的时候要注意把测量点放在电池到系统电源入口之间这样才能把全部耗电路径都纳进来。我第一次测漏电时直接放在了模组的3.3V引脚后面结果漏电流根本没测准后来才意识到应该卡在“电池到整个系统板”的总入口上。3.3 实测数据各阶段电流分布下面这组数据是我在电池充满电、室温25度条件下测到的典型值供各位参考阶段电流持续时间Deep SleepRTC定时器EXT1待命12-25uA大部分时间启动→初始化外设40-80mA80-150msWi-Fi连接HTTP请求120-240mA1-2s墨水屏全刷18-30mA800-1500ms墨水屏局部刷新5-8mA300-600ms传感器单次读取SHT30加速度计2-6mA40-80ms这些数据让我明确了优化方向唤醒后过程要尽量压缩Wi-Fi和全刷次数每一个长时间高电流动作都必须“有目的、有节制”。4. 实操过程与核心环节实现4.1 硬件设计关键点电源开关、RTC晶振和按键既然要做手表硬件就不能含糊。这一版的硬件电路核心有三个点第一电源路径。电池输出经过一个P沟道MOSFETSi2301作为总开关GPIO控制它的栅极。MCU进入Deep Sleep之前会先把所有外设的电源域关掉最后才关掉总开关的“控制权”。不过要注意总开关不能把MCU自己也断了因为RTC定时器还要靠电池供电继续工作。所以这里的正确做法是总开关只管外设电源MCU直接接电池的常电。第二RTC晶振。ESP32的外部32.768kHz晶振两个引脚跨接在芯片的XTAL_32K_P和XTAL_32K_N上旁边两个负载电容取值8-10pF这个组合在量产板上很稳妥。晶振layout要贴近芯片引脚走线尽量短不能有高速信号在旁边横穿。第三按键脚。前面说了GPIO外部上拉10k欧按键另一端接地中间串1k欧小电阻。同时按键脚本身必须选RTC GPIO因为Deep Sleep下只有RTC GPIO能保持唤醒检测功能。ESP32-WROOM-32E上可用的RTC GPIO包括GPIO0、GPIO2、GPIO4、GPIO12-15、GPIO25-27、GPIO32-39选哪几个都行关键要避开默认的下载/启动约束脚比如GPIO0不能默认接地否则上电可能直接进下载模式。4.2 固件中Deep Sleep的实现代码这里直接上一段可用的代码基于Arduino框架逻辑清晰、可直接改。#include driver/rtc_io.h #include esp_sleep.h // 按键唤醒引脚接外部10k上拉按下为低电平 #define BUTTON_A_GPIO GPIO_NUM_4 #define BUTTON_B_GPIO GPIO_NUM_13 // 外设电源控制引脚高电平使能 #define PERIPHERAL_PWR GPIO_NUM_25 void setup() { // 先把外设断电 pinMode(PERIPHERAL_PWR, OUTPUT); digitalWrite(PERIPHERAL_PWR, LOW); // 配置按键唤醒 esp_sleep_enable_ext1_wakeup( (1ULL BUTTON_A_GPIO) | (1ULL BUTTON_B_GPIO), ESP_EXT1_WAKEUP_ANY_LOW ); // 配置RTC定时器唤醒20秒后自动醒一次用于周期性任务 esp_sleep_enable_timer_wakeup(20 * 1000000ULL); // 进入休眠 esp_deep_sleep_start(); } void loop() { // Arduino框架下Deep Sleep唤醒后会重新走setup // loop基本用不到关键逻辑放在setup里分派。 }注意Arduino框架下有个容易困惑的地方Deep Sleep唤醒后芯片并不会继续执行之前休眠的位置而是完整地重启一次重新进入setup()。所以你需要在setup()里判断这一届是从哪里醒来的是哪次唤醒原因再决定接下来要干什么。分派逻辑我放在一个handle_wakeup()函数里void handle_wakeup() { esp_sleep_wakeup_cause_t cause esp_sleep_get_wakeup_cause(); switch (cause) { case ESP_SLEEP_WAKEUP_EXT1: handle_ext1_wakeup(); break; case ESP_SLEEP_WAKEUP_TIMER: handle_timer_wakeup(); break; default: // 上电首次启动做完整初始化 full_init_and_wake_display(); break; } }4.3 局部刷新VS全屏刷新墨水屏的“残影经济学”墨水屏刷新策略直接决定了手表观感和功耗也直接决定了续航。SSD1681有两种刷新模式全屏刷新先把全屏打成黑白再刷目标内容对比度好但时间长电流高而且会有明显的闪烁。局部刷新只更新变化区域速度快功耗低但长时间局部刷新会出现残影积累底色发灰。我的原则是大范围变化或者时间间隔超过5分钟用全屏刷新只是分钟数字跳变这种小范围变化用局部刷新。这里有个关键点局部刷新不能无限用连续局部刷新超过8-10次后必须强制做一次全屏刷新来“洗底”。否则残影会越来越重最后显示内容会像“鬼影”一样叠在一起。以1.54英寸200x200墨水屏为例分钟数字区域大概是30x30像素。局部刷新一次只在这个区域打点功耗极低。我实测连着局部刷新15次之后做一次全屏刷新视觉上基本发现不了残影而整体刷新功耗能省一半还多。4.4 时间同步策略省电与精准的平衡手表必须走时准但如果每次都联网对时功耗就上去了。我的方案分成两档快档每30分钟尝试一次校时但这个频率只在“能连上Wi-Fi且信号好的环境”里才开启。慢档每4小时校时一次这是默认策略。校时动作本身也要聪明一点。不是一醒来就全速跑Wi-Fi而是先短时间开启Wi-Fi并尝试连接如果3秒内没连上就直接放弃回到Deep Sleep等下一个周期再试。这个“尝试窗口”把最坏情况下的功耗锁死在了可控范围内。校时用的协议是NTP实现代码网上很多Arduino下甚至有现成的库关键点是连接超时要设置短一点不要死等。我这边用的是WiFi.begin()之后最多等3000ms连不上就WiFi.disconnect()并进入睡眠。5. 常见问题与排查技巧实录5.1 Deep Sleep电流居高不下这是最多人遇到的问题。如果你测到Deep Sleep电流在几百uA甚至几mA先不要怀疑芯片坏了按这几个思路排查检查GPIO状态所有未使用的GPIO必须设置为INPUT_PULLUP或INPUT_PULLDOWN浮空引脚在睡眠时会产生额外漏电。检查外部上拉/下拉电阻如果LED指示灯串了电阻接在某个GPIO上即使GPIO置高也会持续漏电。检查外设电源所有外设的电源轨必须被MOS管隔离不能有任何一个传感器还挂在常电上。检查板载LDO和USB芯片开发板方案是最容易在这里漏电的。有个非常容易忽略的地方SPI Flash或者SD卡如果只做“片选拉高”不做电源切断它们在掉线状态下依然会有电流。正确做法是给SD卡单独做电源开关片选只是逻辑层面的隔离。5.2 RTC唤醒时间漂移严重如果每次醒来的时间越偏越多大概率是内部RC振荡器在作怪。ESP32内部RC精度本来就在几十ppm量级加上温度变化和个体差异一天偏几分钟完全有可能。解决方案就两个方向要么启用外部32.768kHz晶振要么引入NTP校时。实际场景里最好两个都上外部晶振保证短期走时精度NTP保证长期不漂。只是NTP会带来功耗开销所以我在手表上把NTP直接做成了“每天只有4次窗口期”的形式。还有一个细节外部晶振的匹配电容如果取值不当会导致晶振起振困难或者走时偏差。我的经验是8pF左右比较稳。如果你手头有频率计可以细调一下但正常按这个值做就够用。5.3 按键唤醒后误触发按键唤醒之后发现马上又熄屏或者频繁莫名亮屏多半是EXT1设置的触发电平和按键电路不匹配。我建议用低电平触发、外部上拉的方式不要用高电平触发。原因很简单高电平触发要求按键常态把引脚拉低睡眠状态下GPIO会持续灌入电流虽然不大但没有必要而且高电平触发在按键抖动时更容易产生毛刺误唤醒。低电平触发外部上拉是最稳妥的组合。如果同时还遇到“按下按键系统重启”的现象检查按键脚是否复用了下载引脚。GPIO0、GPIO2这类引脚在按键接地时有概率被内部启动逻辑拉到下载模式导致系统重启而不是正常唤醒。尽量避开这类引脚来放按键。5.4 墨水屏刷新在低温下失败冬天户外戴手表墨水屏很容易出现刷一半花屏甚至完全不刷的情况。SSD1681在0度以下刷新可靠性下降残影更严重。我的对策有三条刷新前做一次屏幕控制器软复位很多“莫名其妙花屏”的问题都能用这招解决。刷新模式里选带“温补”的驱动波形部分GxEPD2版本对不同温度区间有不同波形选择。如果温度低于0度就减少刷新频率比如只在整点刷一次期间显示上一次内容即可。这套“极寒模式”在室内外温差大的场景里很实用。5.5 电池电压测量不准手表肯定要显示电量但ESP32的ADC直接测电池电压会遇到几个问题量程不够ADC最大约2.45V或3.1V需要分压电阻分压电阻本身在睡眠时也会漏电。我的做法是电量检测只在唤醒期间做分压电阻通过一个GPIO控制的MOS管接到ADC采样点采样前临时通电采样完就断电。这样分压电阻的静态损耗就被隔离掉了。分压比例选两个100k欧的电阻采样时用电池电压经过分压后进入ADC同时用内部参考电压做校准。这个细节如果不处理哪怕只漏个几uA七天积累下来也不容小觑。6. 续航结果与一些最终经验补充整套系统做完之后我实测的续航数据是90mAh电池日常佩戴频率一天大概亮屏60次、校时4次、温度传感器读取30次坚持了大约6天半到7天。Deep Sleep状态下实测电流稳定在15-20uA左右比预估的还低一点。这个结果让我比较满意毕竟这不是一颗以低功耗见长的芯片全靠设计层面把功耗“抠”出来的。再多说两点我自己总结出来的经验。第一低功耗设计不能只看MCU的休眠电流要把“系统整体”的漏电路径全梳理干净。一个被忽略的电感、一个悬空的引脚、一块没断完电的传感器都有可能在后台悄悄拖走续航。建议拿到板子第一步就是逐模块断掉电源再量静态电流一步步寻找漏电路径。第二墨水屏手表这个项目最好玩的不是“显示时间”而是让整个系统形成一个可持续的、按需醒来的工作习惯。就像人一样该醒的时候醒干完事立刻睡。把这种状态机写顺了不止手表任何便携式设备都能受益。后续计划把心率传感器加进去通过光电容积脉搏波采集数据后再回落到Deep Sleep对续航应该不会造成太大压力等做完再来汇报结果。
返回列表