693|3

325

帖子

5

TA的资源

纯净的硅(中级)

楼主
 

【DigiKey“智造万物,快乐不停”创意大赛】M5Paper 超低功耗素雅网络相册 [复制链接]

  本帖最后由 HonestQiao 于 2023-12-13 00:24 编辑

这篇分享,是结合M5Paper的墨水屏,以及其核心ESP32的低功耗模式,实现一个超低功耗的电子相册。

其核心功能如下:

  • 通过网络更新图片
  • 使用石墨屏显示
  • 使用深度睡眠模式降低功耗,并定时唤醒以便更新图片
  • 使用Python构建图片服务器,进行图片的灰度转换

下面分享具体的实现过程。

 

一、ESP32低功耗了解

ESP32的工作模式,分为以下几种:

关机当然是功耗最低的了,但是需要人工启动,或者靠外部设备启动。

下面是各种常见模式的运行电路/外设和功耗:

1. 正常工作模式:

 

 

2. 轻度睡眠模式:

 

3. 深度睡眠模式:

  

显而易见,不关机的情况下,深度睡眠,就是功耗最低的模式了。

在休眠模式下,ESP32进入省电模式,将所有数据保存在 RAM 中。此时,任何不必要的外围设备的电源都会被切断,而RAM被供电来保留其数据。

外围设备都断电了,那显示屏怎么办,还亮不亮? 还好M5Paper是墨水屏,只要显示了,断电,不怕的,继续显示。

 

二、在Arduino控制ESP32深度休眠

在Arduino中, 要控制ESP32深度休眠,非常简便,通过以下的调用即可完成:

#define uS_TO_S_FACTOR 1000000ULL /* 微秒到秒 */
#define TIME_TO_SLEEP 15           /* 岁杪时间(s) */

// 定义深度休眠的时长
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

// 进入深度休眠状态
esp_deep_sleep_start();

通过上述代码,就能轻松控制了。

 

另外,要想在睡眠状态,还能保存变量数据,是的唤醒后继续生效,需要使用显现的方式定义变量:

RTC_DATA_ATTR int bootCount = 0;  /* 启动次数计数 */

RTC_DATA_ATTR定义了这个变量,在ESP32休眠的时候,保存到RTC 内存中。

 

三、M5Paper从网络获取图片显示

M5Paper的EPD库,提供了专门的方法,来显示网络图片,具体调用如下:

  // 创建画布
  canvas.createCanvas(540, 960);

  // 从网络获取并绘制图片
  Serial.println("Fetch and draw the picture");
  canvas.drawJpgUrl( "http://192.168.1.15:18080/album/get_img/refresh");
  canvas.pushCanvas(0, 0, UPDATE_MODE_GC16);

网址 http://192.168.1.15:18080/album/get_img/refresh 是我本地使用Python写的一个简单图片服务器,专门给M5Paper提供灰度图片。

因为M5Paper只能显示灰度图片,所以使用Python服务来进行转换,这样子可以提高从网络获取数据的速度,并提高显示的效率。

 

四、Python图片服务器

上面给M5Paper提供的图片服务器,使用Python语言编写,并使用了 flask 来建立WEB服务,使用 Pillow 来提供图片处理,使用 numpy 来进行图片到手绘风格的转换。

尝试过,直接使用Pillow把彩色图片给灰度化的话,界面上会有很多元素留下灰色的点,效果不好。

经过一番研究,可以用 numpy 辅助处理,把彩色图片转换到手绘风格,并去掉一些无关的点,效果很好。

最终的具体代码如下:

#!/usr/bin/env python
# coding=utf-8

from flask import Flask,Response
from flask import render_template
import os
from PIL import Image
import numpy as np

img_index = 0
img_max = 10
img_depth = 10.
app = Flask(__name__)

def pad_image(image, target_size):

    """
    :param image: input image
    :param target_size: a tuple (num,num)
    :return: new image
    """

    iw, ih = image.size  # 原始图像的尺寸
    w, h = target_size  # 目标图像的尺寸

    print("original size: ",(iw,ih))
    print("new size: ", (w, h))

    scale = min(w / iw, h / ih)  # 转换的最小比例

    # 保证长或宽,至少一个符合目标图像的尺寸 0.5保证四舍五入
    nw = int(iw * scale+0.5)
    nh = int(ih * scale+0.5)

    print("now nums are: ", (nw, nh))

    image = image.resize((nw, nh), Image.Resampling.BICUBIC)  # 更改图像尺寸,双立法插值效果很好
    #image.show()
    new_image = Image.new('RGB', target_size, (255, 255, 255))  # 生成黑色图像
    # // 为整数除法,计算图像的位置
    new_image.paste(image, ((w - nw) // 2, (h - nh) // 2))  # 将图像填充为中间图像,两侧为黑色的样式
    #new_image.show()

    return new_image

def image_convert_hand(img_orig):
   # 图像的手绘
   """黑白风格
      边界的位置比较重
      相同或相近色彩趋近于白色
      咯有光源效果
   """
   # 读取彩色图片并转化为np数组
   a = np.array(img_orig.convert('L')).astype('float')

   depth = img_depth
   grad = np.gradient(a)
   grad_x, grad_y = grad
   grad_x = grad_x*depth/100
   grad_y = grad_y*depth/100
   A = np.sqrt(grad_x**2 + grad_y**2 + 1.)
   uni_x = grad_x/A
   uni_y = grad_y/A
   uni_z = 1./A

   vec_el = np.pi/2.2
   vec_ez = np.pi/4.
   dx = np.cos(vec_el)*np.cos(vec_ez)
   dy = np.cos(vec_el)*np.sin(vec_ez)
   dz = np.sin(vec_el)

   b = 255*(dx*uni_x + dy*uni_y + dz*uni_z)
   b = b.clip(0, 255)

   im = Image.fromarray(b.astype('uint8'))
   # 保存转化后的图片
   return im

@app.route('/')
def index():
    resp = Response("<img src='/album/get_img/show'>", mimetype="text/html")
    return resp

@app.route('/album/get_img/<action>')
def get_image(action):
    global img_index, img_max
    img_path = 'imgs_out/{}.jpg'.format(img_index)
    if not os.path.exists(img_path):
        img_path_src = 'imgs/{}.webp'.format(img_index)
        image = Image.open(img_path_src)
        image = image_convert_hand(image)
        size = (540, 960)
        newImage = pad_image(image, size)
        newImage.save(img_path)

    img_stream = open(img_path, "br")
    resp = Response(img_stream, mimetype="image/jpeg")
    resp.headers.add('content-length', str(os.path.getsize(img_path)))

    if action == 'refresh':
        img_index = img_index + 1
        if img_index >= img_max:
            img_index = 0

    return resp

if __name__ == '__main__':
    app.run(debug=True, port=18080, host='0.0.0.0')

 

在上述代码中:

  • image_convert_hand() 用于把图片转换为手绘风格
  • pad_image()用于缩放图片到540x960,并适当的进行补白
  • get_image() 用于提供最新的图片

  • 查看当前的图片: http://ip:18080//album/get_img/show

  • 查看当前的图片并指向下一章: http://ip:18080//album/get_img/refresh

代码中使用 img_max 一共定义了最大10张图片,需要提前准备好原始图片。

 

要运行上述代码,需要文件目录使用如下的结果:

 

其中:

  • album_server.py: 为Python图片服务器代码
  • imgs:为原始图片目录,因为我是从网络下载的图片,所以都是webp图片
  • imgs_out:为处理后的图片缓存起来备用,提供下一次访问的速度,删除后,访问时,会自动重新生成

运行上述代码后,就可以打开浏览器访问: http://ip:18080/,就可以查看最新执行的图片了,下面为原始图片和实际处理后的图片的对比:

 

因为M5Paper的墨水屏,只能显示灰度图片,所以我命名为素雅网络相册

 

五、M5Paper 超低功耗素雅电子相册

经过上面的步骤,完成了各个部分的功能,将所有功能整合起来,完整的代码如下:

#include <M5EPD.h>
#include <WiFi.h>

#define uS_TO_S_FACTOR 1000000ULL /* 微秒到秒 */
#define TIME_TO_SLEEP 15           /* 岁杪时间(s) */

RTC_DATA_ATTR int bootCount = 0;  /* 启动次数计数 */

M5EPD_Canvas canvas(&M5.EPD);


void setup() {
  // M5初始化
  M5.begin();
  M5.RTC.begin();

  Serial.println("System booting...");

  // 启动次数计数
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));


  // 配置休眠时长
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");

  // 连接到WiFi
  WiFi.begin("OpenBSD", "********");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("OK");

  // 设置竖屏
  M5.EPD.SetRotation(90);

  // 首次启动显示应用名称
  if (bootCount == 1) {
    Serial.println("M5Paper Album");
    Serial.println("Author: HonestQiao");

    // 首次启动时清屏
    M5.EPD.Clear(true);

    // 创建画布
    canvas.createCanvas(300, 600);

    // 显示文本
    canvas.setTextSize(12);
    canvas.drawString("M5Paper", 10, 0);
    canvas.drawString("Album", 10, 100);

    canvas.setTextSize(2);
    canvas.drawString("Author: HonestQiao", 0, 400);
    canvas.pushCanvas(120, 300, UPDATE_MODE_GL16);

    delay(3000);
    
    canvas.deleteCanvas();
  }

  // 创建画布
  canvas.createCanvas(540, 960);

  // 从网络获取并绘制图片
  Serial.println("Fetch and draw the picture");
  canvas.drawJpgUrl( "http://192.168.1.15:18080/album/get_img/refresh");
  canvas.pushCanvas(0, 0, UPDATE_MODE_GC16);

  delay(1000);

  // 即将进入睡眠
  Serial.println("Going to sleep now");

  delay(100);

  // 进入睡眠
  esp_deep_sleep_start();
}

void loop() {
}

 

因为各个部分,在前面已经做过介绍了,所以这里就不对代码继续说明了。

 

六、运行效果

运行的效果,就直接上视频,更直观:

4493_1702397605

 

从上面的视频效果可以看出,虽然显示的是灰度图片,但是显示出来的效果,还是非常不错的。

 

如果使用串口工具监听串口输出,也可以看到对应的信息:

 

七、总结 

感觉这次选择的M5Paper非常的合适,用起来很舒服。

石墨屏与ESP32结合,能够玩出很多普通屏幕所不能玩出的花样。

就像这次 超低功耗素雅网络相册,如果用普通的TFT屏,就没法做到,还非得是墨水屏才好用。

 

八、鸣谢

在这次分享的项目的探究过程中,参考了不少资料,下面是部分还记得出处资料:

对上述资料的作者,以及没有列出但学习过的资料的作者,都表示深深的感谢!!!

 

最新回复

点赞点赞   详情 回复 发表于 2024-10-28 10:59
点赞 关注
 
 

回复
举报

281

帖子

7

TA的资源

一粒金砂(高级)

沙发
 

这个处理成手绘风格的图片操作非常NB呀,楼主有没有考虑打包成一个exe的软件发布一下,用于黑白打印机效果很好。

点评

可以考虑呀。   封装一下,然后选择彩色图片,就能预览黑白手绘风格,然后调用打印机打印。  详情 回复 发表于 2023-12-16 21:17
 
 
 

回复

325

帖子

5

TA的资源

纯净的硅(中级)

板凳
 
sipower 发表于 2023-12-16 15:15 这个处理成手绘风格的图片操作非常NB呀,楼主有没有考虑打包成一个exe的软件发布一下,用于黑白打印机效果 ...

可以考虑呀。

 

封装一下,然后选择彩色图片,就能预览黑白手绘风格,然后调用打印机打印。

 
 
 

回复

68

帖子

2

TA的资源

一粒金砂(中级)

4
 

点赞点赞

个人签名

点个灯吧

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表