前段时间 239 块买了个 NOTE4C,一块 4.2 寸的四色墨水屏——黑、白、红、黄,背后是 ESP32-S3。

官方固件其实是能用的:天气、日历、待办,连上 Wi-Fi 就能刷。但它只能显示厂商给的东西。我想要的是自己的那一屏:今天的日程、手环昨晚的睡眠、窗外的天气,一眼看完。

于是我开始给它写固件。四天,四个坑,每个坑的症状都差不多:屏幕不对。

首页:左边天气,右边今日任务,底栏是手环的步数、心率、睡眠和深睡

一、先把屏幕摸透

官方 wiki 只告诉你”支持四色”,没告诉你颜色怎么编码。翻参考固件的源码才搞明白:

  • 2bpp,每字节 4 个像素,整屏 400×300 正好 30000 字节
  • 颜色码是 00 黑 / 01 白 / 10 黄 / 11 红——红和黄是反的,按字母序猜必错
  • GPIO17 是电源锁存,自写固件不把它拉高,拔掉 USB 立刻关机

第一条就出了问题:图传上去颜色全错,红色的天变成了黄的。

二、字体比屏幕更难搞

屏幕只是硬件,排版才是地狱。

这套固件的字体链接之后,line_height 和 base_line 跟生成时不一致:DrawText(fb, w, x, y, ...) 里的 y 不是字形墨迹的顶部,16px 中文实际比 y 高出约 30px。所有绝对定位全歪,文字要么压线要么飘到框外。

修法不是加一个偏移量——偏移量会随字号变,加了等于碰运气。我加了 DrawTextTop(),按首个字形的实际度量做基准,把这个常数差约掉。

然后是图标。weather_icons 是从 FontAwesome 4 里抽的五个字形,我照着码位表挑了 U+F0C3 当雨伞,画出来是个瓶子——那是剪刀。真正的伞是 U+F0E9。

后来干脆把预报行的图标全换成了文字(阴 / 小雨 / 阵雨),四色屏上 16px 的小图标辨识度实在太低。

顺便说一句,那段时间的屏幕特别”抽象”:预报行的图标全是黑块,AQI 的两位数字叠在一起,温度旁边那个 ° 飘出去老远。看上去像坏了,其实只是排版没算对。

三、省电是能算出来的

墨水屏的卖点是”断电还能显示”,但 ESP32 不是。每次刷新都要完整开机、连 Wi-Fi、抓数据、刷屏、再睡。

一开始是每小时醒一次、24 小时不停,一天约 19mAh。改了三处:

  • 只在 8:00–18:00 醒,夜里直接睡到第二天 8 点
  • 唤醒对齐整点,而不是固定 60 分钟。固定间隔会一路漂移:16:29 → 17:29 → 18:00,你永远不知道下一次什么时候刷
  • 睡眠计时从”屏幕刷完”开始,不是从 Wi-Fi 连上开始。原来刷完还要空转 30 秒才睡,白烧电

结果:一天 10 次唤醒,4.4mAh/天。按 1000mAh 电池算,量级上是从 50 天变成 200 天出头——实际有损耗,但方向是这样。

四、三个”看起来一样”的故障

真正折磨人的不是写功能,是排障。墨水屏最阴的地方在于:它不会报错,只会沉默,然后你看到一片不对的颜色。

我连续遇到三次”屏幕不对”,看起来都是”没显示”,原因完全不同。

白屏

现象:整点刷新后一片白,什么都没有。

原因有两层。开机时 Init() 里先 EPD_Clear() 把显存填白,紧接着 EPD_Display() 把这片白刷上屏——每次唤醒第一件事就是把白刷上去。以前没暴露,是因为唤醒后马上抓数,几秒就被真内容盖住;改成整点刷新后,Wi-Fi 要连 30 秒,白屏就挂在那儿。

更隐蔽的是:如果 Wi-Fi 连不上,或者 HTTP 失败,根本不会有后续刷新。那片白会一直留到下一个整点。

修法不是”别刷白”,而是把上一帧存下来。每次刷完把 30KB 的 framebuffer 写进 SPIFFS,唤醒时读回来再刷。开机刷的是上次的画面,视觉上没有变化;抓数失败就一直显示旧数据,而不是空白。

黑屏

为了消白屏,我把 EPD_Display() 整个删了——既然它刷的是白,那就不刷。

结果全黑。

翻到驱动里才看明白:EPD_Display() 不只是”刷像素”,它内部调 EPD_TurnOnDisplay(),发的是面板的上电时序:

1
0x04 power on → 0x12 Display Refresh → 0x02 power off → 0x07 deep sleep

跳过它,面板停在未初始化状态,物理上就是黑的。

这次教训很贵:改显示初始化这种”上电时序”代码之前,先搞清楚每个函数是不是在履行硬件时序,不要只看它”刷了屏”就当成可省的步骤删掉。

半灰半黑

白屏和黑屏都修好之后,事情并没有结束。第二天早上 9 点,屏幕变成一半浅灰、一半深灰,还带着上一屏的字的残影。

8 点那次是好的,9 点这次坏了。

原因是唤醒流程本身:开机先刷上一帧(要 17 秒),8 秒后数据到了又全刷一次。四色屏一次全刷要 17–20 秒,连续两次间隔太短,第二次的波形没走完,像素停在中间态。8 点那次 Wi-Fi 慢、间隔够,所以没事;9 点 Wi-Fi 快,两次全刷挤在一起就刷坏了。

修法是给全刷加最小间隔(25 秒),并且在刷屏过程中禁止进睡——深睡会直接切断面板电源,画面就停在半幅。

更坑的是,这张坏画面已经被存进了 lastframe.bin,下次唤醒还会把它恢复出来。等于错误会自我复制。所以那次修完的第一件事,是让它刷出一张正常的图,把坏帧覆盖掉。

五、现在的样子

四个页面,上下键循环:

  • 首页:左边天气(城市、2x 大温度、现象、湿度风力),右边今日任务黄底卡片,底栏四格步数/心率/睡眠/深睡
  • 天气页:城市、大温度、48px 图标、AQI 卡片、四列指标、四天预报
  • 排期页:7×5 月历,有任务的日期黄底,今天红框
  • 健康页:三张数字卡 + 睡眠结构条(黑=深睡、黄=浅睡、红=REM)

天气页是四色屏最有”纸感”的一屏——黑的天、灰的云、黄色的”今天”下划线,比手机上的渐变色安静得多:

天气页:苏州 18°C 阴,AQI 98 良,湿度 71%,四天预报

排期页把整月摊开,有任务的日期铺黄底,今天套红框。墨水屏不刷新的时候零耗电,这页可以一直挂着当月:

排期页:7×5 月历,黄底是有任务的日期,红框是今天

健康页接的是小米手环的数据:步数、心率、睡眠总时长,下面那条黑黄红的结构条就是昨晚的睡眠比例——黑是深睡、黄是浅睡、红是 REM:

健康页:睡眠结构条黑=深睡、黄=浅睡、红=REM

整点自动刷新,右下角显示数据时间。抓数失败会自动重试两次;如果还是失败,时间前面会出现一个红色的 !——明确告诉你”这是旧数据”,而不是让你猜。

值不值

硬件 239 块,四天时间,代码两千多行,踩了四个坑。

但每天早上坐下,看到屏幕右下角的时间刚好是整点,天气是我窗外的天气,日程是我今天的日程,那种感觉挺奇妙的——它不太像”智能设备”,更像一块自己养出来的公告板。

墨水屏最好的地方其实不是省电,是不发光。它不抢注意力,不弹通知,你看它的时候它在,你不看的时候它也在,安静地显示着上一小时的世界。

顺便说一句:现在它比我准时。