HonestQiao 发表于 2023-12-13 00:18

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

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

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

<p>其核心功能如下:</p>

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

<p>下面分享具体的实现过程。</p>

<p>&nbsp;</p>

<p><strong>一、ESP32低功耗了解</strong></p>

<p>ESP32的工作模式,分为以下几种:</p>

<p></p>

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

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

<p>1. 正常工作模式:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>2. 轻度睡眠模式:</p>

<p>&nbsp;</p>

<p>3. 深度睡眠模式:</p>

<p> &nbsp;&nbsp;</p>

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

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

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

<p>&nbsp;</p>

<p><strong>二、在Arduino控制ESP32深度休眠</strong></p>

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

<pre>
<code class="language-cpp">#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();</code></pre>

<p>通过上述代码,就能轻松控制了。</p>

<p>&nbsp;</p>

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

<pre>
<code class="language-cpp">RTC_DATA_ATTR int bootCount = 0;/* 启动次数计数 */</code></pre>

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

<p>&nbsp;</p>

<p><strong>三、M5Paper从网络获取图片显示</strong></p>

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

<pre>
<code class="language-cpp">// 创建画布
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);</code></pre>

<p>网址&nbsp;<a href="http://192.168.1.15:18080/album/get_img/refresh" target="_blank">http://192.168.1.15:18080/album/get_img/refresh</a>&nbsp;是我本地使用Python写的一个简单图片服务器,专门给M5Paper提供灰度图片。</p>

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

<p>&nbsp;</p>

<p><strong>四、Python图片服务器</strong></p>

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

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

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

<p>最终的具体代码如下:</p>

<pre>
<code class="language-cpp">#!/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("&lt;img src='/album/get_img/show'&gt;", mimetype="text/html")
    return resp

@app.route('/album/get_img/&lt;action&gt;')
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 &gt;= img_max:
            img_index = 0

    return resp

if __name__ == '__main__':
    app.run(debug=True, port=18080, host='0.0.0.0')</code></pre>

<p>&nbsp;</p>

<p>在上述代码中:</p>

<ul>
        <li>image_convert_hand() 用于把图片转换为手绘风格</li>
        <li>pad_image()用于缩放图片到540x960,并适当的进行补白</li>
        <li>
        <p>get_image() 用于提供最新的图片</p>
        </li>
        <li>
        <p>查看当前的图片: http://ip:18080//album/get_img/show</p>
        </li>
        <li>
        <p>查看当前的图片并指向下一章: http://ip:18080//album/get_img/refresh</p>
        </li>
</ul>

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

<p>&nbsp;</p>

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

<p> &nbsp;</p>

<p>其中:</p>

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

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

<p></p>

<p>&nbsp;</p>

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

<p>&nbsp;</p>

<p><strong>五、M5Paper 超低功耗素雅电子相册</strong></p>

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

<pre>
<code class="language-cpp">#include &lt;M5EPD.h&gt;
#include &lt;WiFi.h&gt;

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

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

M5EPD_Canvas canvas(&amp;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() {
}
</code></pre>

<p>&nbsp;</p>

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

<p>&nbsp;</p>

<p><strong>六、运行效果</strong></p>

<p>运行的效果,就直接上视频,更直观:</p>

<p>95c6e68d611b5b36ec32bb60ce566ffe<br />
&nbsp;</p>

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

<p>&nbsp;</p>

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

<p></p>

<p>&nbsp;</p>

<p><strong>七、总结&nbsp;</strong></p>

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

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

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

<p>&nbsp;</p>

<p><strong>八、鸣谢</strong></p>

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

<ul>
        <li><a href="https://www.yiboard.com/thread-1847-1-1.html">详解ESP32深度睡眠模式及其唤醒源 - 乐鑫ESP32</a>(www.yiboard.com/thread-1847-1-1.html)</li>
        <li><a href="https://blog.csdn.net/qq_67171848/article/details/127043203">ESP32 低功耗模式_esp32低功耗-CSDN博客</a>(blog.csdn.net/qq_67171848/article/details/127043203)</li>
        <li><a href="https://blog.51cto.com/u_15284384/3051452">【IoT】ESP32 Arduino 超低功耗模式 Deep-sleep_51CTO博客</a>(blog.51cto.com/u_15284384/3051452)</li>
        <li><a href="https://www.jianshu.com/p/6f044e035511">玩转 ESP32 + Arduino (十七) deepsleep深睡眠模式 - 简书(</a>www.jianshu.com/p/6f044e035511)</li>
        <li><a href="https://blog.csdn.net/qq_35189715/article/details/94594562">Python彩色图片转手绘风格-CSDN博客</a>(blog.csdn.net/qq_35189715/article/details/94594562)</li>
        <li><a href="https://blog.csdn.net/JZJZ73/article/details/108471960">python PIL 填充图片 更改到规定尺寸_pil pad 到固定大小-CSDN博客</a>(blog.csdn.net/JZJZ73/article/details/108471960)</li>
</ul>

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

<p>&nbsp;</p>

sipower 发表于 2023-12-16 15:15

<p>这个处理成手绘风格的图片操作非常NB呀,楼主有没有考虑打包成一个exe的软件发布一下,用于黑白打印机效果很好。<img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan88.gif" width="59" /></p>

HonestQiao 发表于 2023-12-16 21:17

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

<p>可以考虑呀。</p>

<p>&nbsp;</p>

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

CoderX9527 发表于 2024-10-28 10:59

<p>点赞点赞</p>
页: [1]
查看完整版本: 【DigiKey“智造万物,快乐不停”创意大赛】M5Paper 超低功耗素雅网络相册