電子ペーパーモジュールを試してみた話【GxEPD2ライブラリ】

M5atomS3で試そうとして半日潰して無理だったので、arduinoUnoR4wifiを使って動かしてみた
べ、別に逃げたわけじゃないんだからねっ! ...嘘です、逃げです。ごめんなさい:sob:

# はじめに
- 秋葉原の某店（自社ロゴのプリントTシャツを売ってる、誇り高き店）で、[電子ペーパーモジュール](https://www.waveshare.com/wiki/2.9inch_e-Paper_Module_(B)_Manual#Working_With_Arduino)を見つけたので買ってみた。
- ホームページには、arduino用の[サンプルスケッチ(7z版)](https://files.waveshare.com/upload/3/39/E-Paper_code.7z),も載っていたので、簡単に動くだろうと思って手元のM5AtomS3で動くようにpin配置だけ追加で書きこんで動かそうとしたけど無理だった...
 ※ ちなみに、[(zip版)](https://files.waveshare.com/upload/7/71/E-Paper_code.zip)も有るけど、中身が微妙に違う。これが中華クオリティ。
- ハードウエアspi前提のスケッチで、ソフトウエアspiで動かそうとしたのがマズかったのかもしれないけど、調べるのを諦めて、家に帰ってUnoR4wifiに繋いだら、当たり前だけど普通に動いた。
- ついでに、液晶ライブラリのu8g2のように、電子ペーパー用のライブラリ[GxEPD2](https://github.com/ZinggJM/GxEPD2)が有ったので使ってみた。結論、<INS>_**GxEPD2はいいぞ。**_</INS>

# 公式サンプルの場合
- 現在販売されているのはv3なので、下記ファイルが公式サンプルとなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/7191f37d-b5e5-cc7d-8904-30183bf73fde.png)
- 動かすと、一つの画像ファイルを元に、黒地、白地、白地赤色で画像が順次表示された後、無地にもどる。
![17136767866160.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/8d30d2c5-8907-1cde-4b80-bffa630372f2.jpeg)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/344f9e5a-6227-9db1-2f9d-9242c668435d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/8d78d084-d71f-e245-a048-62c83593a508.png)
- 2.9インチのサンプルは他にもあって、今回試した9b_V3以外に9bcと9dを試してみたけど、9bcは動かず、9dは動いたものの、白黒のみのサンプルで、赤の表示はなかった。なお、9dの場合は、電源ピンに刺していたピンを6番に刺す必要がある。サンプル中では、PWR_PINと表現されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/74700de4-520e-71aa-8487-afbc587dc9f5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/0740f561-7a6e-adaf-5cbd-5d06d3586ac0.png)

# GxEPD2の設定
- [ここ](https://github.com/ZinggJM/GxEPD2)から、zipで落としてarduinoIDEに取り込んだけど、ライブラリマネージャからもインストール可能みたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/fb80a1a0-f16e-d80f-6c85-68841e645b68.png)
- スケッチ例のうち、GFX_Exampleを動かしてみた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/971aa6b3-a97f-56db-34cb-05ee83cba07f.png)
- まず、不要なヘッダーをコメントアウト。3色用のみとし、また、(old style)と(new style)については、深く考えずにnew styleを採用。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/1698a65a-05ee-7075-2900-0f6bb181a66e.png)
- 設定の大前提として、使っている制御チップを[waveshareの提供資料](https://files.waveshare.com/upload/a/af/2.9inch-e-paper-b-v3-specification.pdf)で調べると、UC8151Dとなっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/5e396712-8d39-6299-73d1-a70c0cd49eea.png)
- よって、ヘッダーファイルで、3-colorクラスのうち、この解像度のUC8151Dのコメントを外す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/9199babe-4126-6b88-a1fa-fa8fc44a5075.png)
- ありがたいことに、UnoR4wifiに対応済み。下の方に、ピン設定が書いてあるけど、DC、RSTのピン番号が、公式サンプルと逆だから、差し直す必要があることに注意。差し直さずに、コードを書き換えてもいいけど。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/ebf2b8ee-1954-0e2b-fe5b-d51db94d8de1.png)
- 初期状態で_BWが選択されているのを_3Cに変更するのを忘れずに。（忘れると、エラーが出るから気づくけどね）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/56405ecc-20e3-5c7a-551c-40f9d9848f7e.png)
- 無駄な解像度をコメントアウト！削らないと、コンパイル時にオーバーフローする。これは気づくまで、1時間ほど無駄にした...。コンパイルの詳細を見て、「制御チップ別のcppを大量に読み込んでやがんな〜、UC8151Dしかコメントを外してしてないのにっ！くそっ犯人はどこだ！」って、無駄に探した。これを読んだ人は、忘れずに削ってね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/26558957-47c9-91f9-5c80-c4646bb984c7.png)

# GxEPD2のサンプルスケッチ(GFX_Example)の動作
- 結構、長時間の動作なので、全てを写真や動画に収めるのは面倒だから、最初と最後以外は、適当に撮ったのを載せておく。
- 最初と途中
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/a8c06ed0-cac9-b390-945d-3bdfb6428852.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/c053ec19-a06c-cbf8-8f48-d3fe444a9667.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/ff8ed384-4974-180d-fbda-962fc7f34042.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/3629a480-8fce-3885-9d22-890c3240523f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/4db0bbdc-e42b-c17e-d062-9ce0df5d44df.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/fd364ac4-966b-25b1-2c61-e298d7c5415b.png)

- 最後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/63f1cf40-ba3d-41b6-3044-bcd84e267175.png)
- 電子ペーパーは電源を落としても表示が残るから、焼き付け？みたいになるのが嫌なら、 `void setup()` の最後の方で `display.fillScreen(GxEPD_WHITE);` を追加する。
- なお、`.firstPage()` と `.nextPage()` で挟むのがGxEPD2の流儀みたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/48c5e1bc-3c59-4ef1-97af-ee4d91ae03fa.png)

# GxEPD2で使えるコマンド
- `GxEPD2_GFX.h` の中身を見て想像して欲しい。
```cpp
// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines!
//
// Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/
//
// Author: Jean-Marc Zingg
//
// Version: see library.properties
//
// Library: https://github.com/ZinggJM/GxEPD2

#ifndef _GxEPD2_GFX_H_
#define _GxEPD2_GFX_H_

// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX
//#include <GFX.h>

#if defined(_GFX_H_)
#define GxEPD2_GFX_ROOT_CLASS GFX
#else
#include <Adafruit_GFX.h>
#define GxEPD2_GFX_ROOT_CLASS Adafruit_GFX
#endif

#include <GxEPD2_EPD.h>

class GxEPD2_GFX : public GxEPD2_GFX_ROOT_CLASS
{
  public:
    GxEPD2_GFX(GxEPD2_EPD& _epd2, int16_t w, int16_t h) : GxEPD2_GFX_ROOT_CLASS(w, h), epd2(_epd2) {};
    virtual uint16_t pages() = 0;
    virtual uint16_t pageHeight() = 0;
    virtual bool mirror(bool m) = 0;
    virtual void init(uint32_t serial_diag_bitrate = 0) = 0; // serial_diag_bitrate = 0 : disabled
    // init method with additional parameters:
    // initial false for re-init after processor deep sleep wake up, if display power supply was kept
    // this can be used to avoid the repeated initial full refresh on displays with fast partial update
    // NOTE: garbage will result on fast partial update displays, if initial full update is omitted after power loss
    // pulldown_rst_mode true for alternate RST handling to avoid feeding 5V through RST pin
    virtual void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false) = 0;
    virtual void fillScreen(uint16_t color) = 0; // 0x0 black, >0x0 white, to buffer
    // display buffer content to screen, useful for full screen buffer
    virtual void display(bool partial_update_mode = false) = 0;
    // display part of buffer content to screen, useful for full screen buffer
    // displayWindow, use parameters according to actual rotation.
    // x and w should be multiple of 8, for rotation 0 or 2,
    // y and h should be multiple of 8, for rotation 1 or 3,
    // else window is increased as needed,
    // this is an addressing limitation of the e-paper controllers
    virtual void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) = 0;
    virtual void setFullWindow() = 0;
    // setPartialWindow, use parameters according to actual rotation.
    // x and w should be multiple of 8, for rotation 0 or 2,
    // y and h should be multiple of 8, for rotation 1 or 3,
    // else window is increased as needed, 
    // this is an addressing limitation of the e-paper controllers
    virtual void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) = 0;
    virtual void firstPage() = 0;
    virtual bool nextPage() = 0;
    virtual void drawPaged(void (*drawCallback)(const void*), const void* pv) = 0;
    virtual void drawInvertedBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, uint16_t color) = 0;
    //  Support for Bitmaps (Sprites) to Controller Buffer and to Screen
    virtual void clearScreen(uint8_t value = 0xFF) = 0; // init controller memory and screen (default white)
    virtual void writeScreenBuffer(uint8_t value = 0xFF) = 0; // init controller memory (default white)
    // write to controller memory, without screen refresh; x and w should be multiple of 8
    virtual void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;
    virtual void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;
    virtual void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) = 0;
    virtual void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h) = 0; // default options false
    virtual void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) = 0;
    virtual void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h) = 0; // default options false
    // write sprite of native data to controller memory, without screen refresh; x and w should be multiple of 8
    virtual void writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;
    // write to controller memory, with screen refresh; x and w should be multiple of 8
    virtual void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;
    virtual void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;
    virtual void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) = 0;
    virtual void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h) = 0; // default options false
    virtual void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) = 0;
    virtual void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h) = 0; // default options false
    // write sprite of native data to controller memory, with screen refresh; x and w should be multiple of 8
    virtual void drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;
    virtual void refresh(bool partial_update_mode = false) = 0; // screen refresh from controller memory to full screen
    virtual void refresh(int16_t x, int16_t y, int16_t w, int16_t h) = 0; // screen refresh from controller memory, partial screen
    virtual void powerOff() = 0; // turns off generation of panel driving voltages, avoids screen fading over time
    virtual void hibernate() = 0; // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0)
  public:
    GxEPD2_EPD& epd2;
};

#endif
```
- 投げっぱなしでスマン。実のところ、まだ、何も試してないのです。
- サンプルスケッチの場合、`Adafruit_GFX` と同じコマンドが使えるみたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/584d7bd4-6a75-b2b0-6af0-d860415fb899.png)
- 実際、こんなコードがサンプルスケッチにあるし。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/70fcec65-307d-831b-3a43-0a0d054322bc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/a2e2e5f6-5250-915b-70cf-9c14d71e6627.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/894af33a-6959-1481-873d-decc0f44ce7a.png)

# 最後に
- GxEPD2は初めて知ったけど、結構洗練されてんじゃないかな？もともと、[GxEPD](https://github.com/ZinggJM/GxEPD)が有って、それも2022年まで使われていて、バージョン3.1.3まで進んでた。
- 簡単に電子ペーパー表示の実装ができることを確認できたから、今度は大きいサイズの電子ペーパーを買ってみようかと思う。7色の5.65インチで1万円を切っているし。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/f451f327-ae47-b1af-0681-bc314d565dff.png)
- 昨日は、M5atomS3で挑んで半日潰して手も足も出なかったから、今日、なんとかGxEPD2の利用までたどり着けて良かった。これで、気持ちよく月曜日を迎えられるわ。週末にストレスをためるのは洒落にならないから、本当に良かった。ありがとう、GxEPD2！