M5atomS3で試そうとして半日潰して無理だったので、arduinoUnoR4wifiを使って動かしてみた
べ、別に逃げたわけじゃないんだからねっ! ...嘘です、逃げです。ごめんなさい
はじめに
- 秋葉原の某店(自社ロゴのプリントTシャツを売ってる、誇り高き店)で、電子ペーパーモジュールを見つけたので買ってみた。
- ホームページには、arduino用のサンプルスケッチ(7z版),も載っていたので、簡単に動くだろうと思って手元のM5AtomS3で動くようにpin配置だけ追加で書きこんで動かそうとしたけど無理だった...
※ ちなみに、(zip版)も有るけど、中身が微妙に違う。これが中華クオリティ。 - ハードウエアspi前提のスケッチで、ソフトウエアspiで動かそうとしたのがマズかったのかもしれないけど、調べるのを諦めて、家に帰ってUnoR4wifiに繋いだら、当たり前だけど普通に動いた。
- ついでに、液晶ライブラリのu8g2のように、電子ペーパー用のライブラリGxEPD2が有ったので使ってみた。結論、GxEPD2はいいぞ。
公式サンプルの場合
- 現在販売されているのはv3なので、下記ファイルが公式サンプルとなる。
- 動かすと、一つの画像ファイルを元に、黒地、白地、白地赤色で画像が順次表示された後、無地にもどる。
- 2.9インチのサンプルは他にもあって、今回試した9b_V3以外に9bcと9dを試してみたけど、9bcは動かず、9dは動いたものの、白黒のみのサンプルで、赤の表示はなかった。なお、9dの場合は、電源ピンに刺していたピンを6番に刺す必要がある。サンプル中では、PWR_PINと表現されている。
GxEPD2の設定
-
ここから、zipで落としてarduinoIDEに取り込んだけど、ライブラリマネージャからもインストール可能みたい。
- スケッチ例のうち、GFX_Exampleを動かしてみた。
- まず、不要なヘッダーをコメントアウト。3色用のみとし、また、(old style)と(new style)については、深く考えずにnew styleを採用。
- 設定の大前提として、使っている制御チップをwaveshareの提供資料で調べると、UC8151Dとなっている。
- よって、ヘッダーファイルで、3-colorクラスのうち、この解像度のUC8151Dのコメントを外す。
- ありがたいことに、UnoR4wifiに対応済み。下の方に、ピン設定が書いてあるけど、DC、RSTのピン番号が、公式サンプルと逆だから、差し直す必要があることに注意。差し直さずに、コードを書き換えてもいいけど。
- 初期状態で_BWが選択されているのを_3Cに変更するのを忘れずに。(忘れると、エラーが出るから気づくけどね)
- 無駄な解像度をコメントアウト!削らないと、コンパイル時にオーバーフローする。これは気づくまで、1時間ほど無駄にした...。コンパイルの詳細を見て、「制御チップ別のcppを大量に読み込んでやがんな〜、UC8151Dしかコメントを外してしてないのにっ!くそっ犯人はどこだ!」って、無駄に探した。これを読んだ人は、忘れずに削ってね。
GxEPD2のサンプルスケッチ(GFX_Example)の動作
-
結構、長時間の動作なので、全てを写真や動画に収めるのは面倒だから、最初と最後以外は、適当に撮ったのを載せておく。
-
電子ペーパーは電源を落としても表示が残るから、焼き付け?みたいになるのが嫌なら、
void setup()
の最後の方でdisplay.fillScreen(GxEPD_WHITE);
を追加する。
GxEPD2で使えるコマンド
-
GxEPD2_GFX.h
の中身を見て想像して欲しい。
// 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
最後に
- GxEPD2は初めて知ったけど、結構洗練されてんじゃないかな?もともと、GxEPDが有って、それも2022年まで使われていて、バージョン3.1.3まで進んでた。
- 簡単に電子ペーパー表示の実装ができることを確認できたから、今度は大きいサイズの電子ペーパーを買ってみようかと思う。7色の5.65インチで1万円を切っているし。
- 昨日は、M5atomS3で挑んで半日潰して手も足も出なかったから、今日、なんとかGxEPD2の利用までたどり着けて良かった。これで、気持ちよく月曜日を迎えられるわ。週末にストレスをためるのは洒落にならないから、本当に良かった。ありがとう、GxEPD2!