最近参加社区的 M5Paper 评测活动,拿到了一块 M5Stack 的电子墨水屏开发板,体验了一下 UIFlow2 + MicroPython 的开发模式,今天来跟大家聊聊我的使用感受。
说实话,电子墨水屏这东西我用过,但是不常用,主要是嫌它刷新慢、颜色少。
但 M5Paper 这块屏 540x960 的分辨率,4.7寸的大小,做桌面时钟或者信息看板倒是挺合适。
而且这玩意儿是ESP32的,支持 WiFi,可以直接同步网络时间,比我以前用 RTC 模块方便多了。
拿到新板子第一步自然是烧录固件。M5Paper 支持 UIFlow2,这是一个图形化编程环境,类似于 Arduino 的可视化编辑器。
M5Stack 提供了专门的烧录工具 M5Burner,操作很简单:
从 M5Stack 官网下载 M5Burner
选择对应的设备型号 M5Paper
选择 UIFlow2 固件版本
点击烧录,等待完成

烧录完成后,屏幕会显示一个二维码,用手机扫码就能进入 UIFlow2 的在线编程界面。
很多人以为 UIFlow2 只能图形化编程,其实不完全是。UIFlow2 提供的是 MicroPython 编程环境,你可以:
图形化编程:拖拽积木块,适合新手入门
直接写 Python 代码:点击右上角的代码图标,直接编写 MicroPython 程序
两种方式可以混用,图形化生成的代码你也能看到,反之亦然。
UIFlow2 内置了 M5模块,封装了 M5Paper 的各种硬件控制:
M5.Lcd.* - 屏幕显示控制
M5.Touch.* - 触摸屏控制
M5.Btn.* - 物理按键控制
network - WiFi 网络
ntptime - 网络时间同步
所以本质上,你就是在用 MicroPython 调用 M5Paper 的底层驱动,非常接近传统的嵌入式开发体验。
这是最重要的经验:电子墨水屏绝对不能连续快速刷新。
我第一次写代码的时候,写了个时钟程序,每秒刷新一次。结果你猜怎么着?屏幕开始闪烁、出现残影,吓我一跳。赶紧查资料才知道,电子墨水屏的刷新间隔至少要 2 秒以上,否则轻则残影,重则烧屏。
所以我的策略是:全屏每分钟刷新一次,触摸响应时只局部刷新。
M5Paper 内置的字体就那么几种,我一开始随手选了个字体,结果显示出来模糊不清。后来发现 DejaVu 系列是专门做屏幕显示的,清晰度比普通字体好很多。
我的经验是:
数字显示用 DejaVu72,够大够醒目
中文用 EFontCN24,大小适中
尽量别混用太多字体,耗内存
960x540 的屏幕,看着挺大,但坐标系跟普通显示屏不太一样。原点在左上角,x 向右,y 向下。
我的布局经验:
居中显示的文字,x 坐标用 180-250 就差不多
数字时钟这种大字,x=180 左右
中文内容因为字宽问题,x 可能要偏左一点
我的micropython开发工具,使用的是Thonny:
废话不多说,直接上代码:
import M5
from M5 import *
import time
import ntptime
import network
# WiFi 配置
WIFI_SSID = "你的WiFi"
WIFI_PASSWORD = "你的密码"
# 屏幕尺寸
SCREEN_WIDTH = 960
SCREEN_HEIGHT = 540
# 灰阶配色(墨水屏最适合的颜色方案)
COLOR_BG = 0xFFFFFF # 白底
COLOR_TIME = 0x333333 # 深灰时间
COLOR_DATE = 0x666666 # 中灰日期
COLOR_LINE = 0xCCCCCC # 浅灰分割线
# 诗句库
QUOTES = [
("山不在高,有仙则名", "刘禹锡《陋室铭》"),
("明月松间照,清泉石上流", "王维《山居秋暝》"),
("大漠孤烟直,长河落日圆", "王维《使至塞上》"),
# ... 10条经典诗文]
is_24h = True
current_minute = -1
last_touch = 0
WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
def connect_wifi():
"""连接 WiFi"""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
timeout = 30
while not wlan.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
return wlan.isconnected()
def sync_time():
"""同步网络时间"""
try:
ntptime.settime()
return True
except:
return False
def draw_background():
"""清屏"""
M5.Lcd.clear(COLOR_BG)
def draw_time():
"""画时间"""
global current_minute
t = time.localtime()
hour, minute = t[3], t[4]
current_minute = minute # 12/24小时制转换
if not is_24h and hour > 12:
hour -= 12
elif not is_24h and hour == 0:
hour = 12
time_str = "{:02d}:{:02d}".format(hour, minute)
M5.Lcd.setFont(M5.Lcd.FONTS.DejaVu72)
M5.Lcd.setTextColor(COLOR_TIME, COLOR_BG)
M5.Lcd.setCursor(180, 100)
M5.Lcd.print(time_str)
def draw_date():
"""画日期"""
t = time.localtime()
weekday = WEEKDAYS[t[6]]
date_str = "{:04d}年{:02d}月{:02d}日".format(t[0], t[1], t[2])
M5.Lcd.setFont(M5.Lcd.FONTS.EFontCN24)
M5.Lcd.setTextColor(COLOR_DATE, COLOR_BG)
M5.Lcd.setCursor(250, 180)
M5.Lcd.print(weekday)
M5.Lcd.setCursor(200, 215)
M5.Lcd.print(date_str)
def draw_divider():
"""画分割线"""
M5.Lcd.fillRect(170, 245, 400, 10, COLOR_LINE)
def draw_quote():
"""画每日一言"""
t = time.localtime()
quote, source = QUOTES[t[2] % len(QUOTES)]
M5.Lcd.setFont(M5.Lcd.FONTS.EFontCN24)
M5.Lcd.setTextColor(COLOR_TIME, COLOR_BG)
M5.Lcd.setCursor(80, 305)
M5.Lcd.print("「" + quote + "」")
M5.Lcd.setCursor(320, 355)
M5.Lcd.print("-- " + source)
def draw_all():
"""画全部"""
draw_background()
draw_time()
draw_date()
draw_divider()
draw_quote()
def check_touch():
"""处理触摸"""
global is_24h, last_touch
if M5.Touch.getCount() > 0:
touch = M5.Touch
if touch.getX() > 0:
current_time = time.ticks_ms()
# 500ms 防抖
if current_time - last_touch > 500:
last_touch = current_time
is_24h = not is_24h # 局部刷新
M5.Lcd.fillRect(0, 100, SCREEN_WIDTH, 150, COLOR_BG)
draw_time()
draw_date()
draw_divider()
time.sleep_ms(300)
def setup():
"""初始化"""
M5.begin()
M5.Lcd.setEpdMode(M5.Lcd.EPDMode.EPD_FAST)
if connect_wifi():
sync_time()
draw_all()
print("时钟启动!")
def loop():
"""主循环"""
global current_minute
M5.update()
check_touch()
t = time.localtime()
# 每分钟刷新一次
if t[4] != current_minute:
current_minute = t[4]
draw_all()
if __name__ == "__main__":
setup()
while True:
loop()M5.begin()M5.Lcd.setEpdMode(M5.Lcd.EPDMode.EPD_FAST)
setEpdMode 这个东西很重要,控制刷新速度和质量:
EPD_FAST:快,但可能有轻微残影
EPD_QUALITY:质量好,但慢
我一般用 FAST,够用了。
if not is_24h and hour > 12:
hour -= 12
elif not is_24h and hour == 0:
hour = 12
time_str = "{:02d}:{:02d}".format(hour, minute)12 小时制的时候,0 点要改成 12 点,别问我怎么知道的。
# 全屏刷新 M5.Lcd.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, COLOR_BG) draw_all() # 局部刷新(只刷新时间区域) M5.Lcd.fillRect(0, 100, SCREEN_WIDTH, 150, COLOR_BG) draw_time() draw_date()
触摸响应的时候,局部刷新对屏幕更友好。
if current_time - last_touch > 500: last_touch = current_time # 处理点击
没有防抖动的话,一次点击可能被识别成好几次。
运行后,显示效果如下:

简洁大方,放桌上或者书架上挺有感觉的。
M5Paper 这块板子用来做信息展示类的应用挺合适:
做桌面时钟
做天气看板
做待办事项显示
做系统监控面板
开发 MicroPython 上手快,调试方便,比起纯 C++ 开发效率高不少。
但是,有几个注意点:
墨水屏刷新间隔至少 2 秒
优先局部刷新,别动不动全屏刷新
字体别乱选,DejaVu 系列做数字显示效果最好
WiFi 连接要加超时处理,否则连不上就卡死了
好了,今天就分享这么多。如果大家感兴趣,我后续再分享一下其他应用的开发经验。
我要赚赏金
