メインコンテンツまでスキップ

Arduino クックブック: ePaper ディスプレイ (reTerminal E シリーズ)

ハードウェア周辺機能を探していますか?

このページでは Arduino からのePaper 画面の駆動に焦点を当てています。オンボードの LED、ブザー、ボタン、SHT4x センサ、バッテリーモニタ、microSD カードスロットを使いたい場合は、Arduino クックブック: オンボード周辺機能 を参照してください。RTC、低消費電力モード、オンボードマイク、静電容量式タッチ描画については、Arduino クックブック: RTC、低消費電力、オーディオ & タッチ を参照してください。

共通のボイラープレート — Arduino IDE のセットアップ、ESP32 ボードパッケージ、Seeed_GFX のインストール、driver.h の生成 — は Work with Arduino にも記載されています。Seeed の ePaper で Arduino を使うのが初めての場合は、まずそちらに目を通してください。

はじめに

reTerminal E シリーズは、XIAO ESP32-S3 をベースに、ePaper ディスプレイを一体化した Seeed Studio の産業用 HMI ラインです。このクックブックでは、画面にテキスト、グラフィックス、画像を描画するために必要な内容を一通り解説します。

  • E1001 / E1002 / E1003 / E1004 のハードウェア概要と購入リンク
  • 4 モデル共通の Arduino IDE 環境構築(XIAO_ESP32S3 ボード、OPI PSRAM)
  • Seeed_GFX ライブラリ(対応する BOARD_SCREEN_COMBO)を使った各モデルでの最初の Hello World
  • Seeed_GFX を用いたパネル別の高度なサンプルE1001 での 4 階調グレースケールE1003 での 16 階調グレースケール
  • 人気ライブラリ GxEPD2 を使った別アプローチの Hello World
  • ePaper のリフレッシュ問題や書き込み失敗に関するトラブルシューティング

必要なもの

このチュートリアルを完了するには、次の reTerminal E シリーズデバイスのいずれかを用意してください。

reTerminal E1001reTerminal E1002reTerminal E1003reTerminal E1004

環境準備

reTerminal E シリーズの ePaper ディスプレイを Arduino でプログラムするには、ESP32 対応の Arduino IDE をセットアップする必要があります。

ヒント

Arduino を初めて使用する場合は、Getting Started with Arduino を参照することを強くお勧めします。

Arduino IDE のセットアップ

Step 1. Arduino IDE をダウンロードしてインストールし、Arduino アプリケーションを起動します。


Step 2. Arduino IDE に ESP32 ボードサポートを追加します。

Arduino IDE で File > Preferences を開き、「Additional Boards Manager URLs」フィールドに次の URL を追加します。

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Step 3. ESP32 ボードパッケージをインストールします。

Tools > Board > Boards Manager に移動し、「esp32」を検索して Espressif Systems の ESP32 パッケージをインストールします。

Step 4. 正しいボードを選択します。

Tools > Board > ESP32 Arduino を開き、XIAO_ESP32S3 を選択します。

Step 5. reTerminal E シリーズの ePaper ディスプレイを USB-C ケーブルでコンピュータに接続します。

Step 6. Tools > Port から正しいポートを選択します。

ePaper ディスプレイのプログラミング

reTerminal E1001 には 7.5 インチの白黒 ePaper ディスプレイが搭載されており、reTerminal E1002 には 7.3 インチのフルカラー ePaper ディスプレイが搭載されています。どちらのディスプレイも、さまざまな照明条件下で優れた視認性を提供し、超低消費電力で動作するため、常時表示と最小限の電力消費が求められる産業用途に最適です。

Seeed_GFX ライブラリの使用

ePaper ディスプレイを制御するために、さまざまな Seeed Studio 製ディスプレイデバイスを幅広くサポートする Seeed_GFX ライブラリを使用します。

Step 1. GitHub から Seeed_GFX ライブラリをダウンロードします。


Step 2. Arduino IDE で ZIP ファイルを追加してライブラリをインストールします。Sketch > Include Library > Add .ZIP Library を開き、ダウンロードした ZIP ファイルを選択します。

注記

以前に TFT_eSPI ライブラリをインストールしている場合、競合を避けるために、一時的に Arduino のライブラリフォルダから削除またはリネームする必要があるかもしれません。Seeed_GFX は TFT_eSPI をベースに、Seeed Studio 製ディスプレイ向けの機能を追加したフォークであるためです。

reTerminal E1001 のプログラミング(7.5 インチ白黒 ePaper)

白黒 ePaper ディスプレイ上での基本的な描画操作を示す、シンプルなサンプルを試してみましょう。

Step 1. Seeed_GFX ライブラリのサンプルスケッチを開きます。File > Examples > Seeed_GFX > ePaper > Basic > HelloWorld

Step 2. Arduino IDE で OPI PSRAM を有効にします。Tools > PSRAM > OPI PSRAM

Step 3. スケッチと同じフォルダに driver.h という名前の新しいファイルを作成します。Arduino IDE の矢印ボタンをクリックして「New Tab」を選択し、driver.h と名付けることで作成できます。

Step 4. 生成された設定コードをコピーして driver.h ファイルに貼り付けます。コードは次のようになります。

#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)

Step 5. スケッチを reTerminal E1001 に書き込みます。線、テキスト、図形など、基本的な描画機能を示すさまざまなグラフィックスがディスプレイに表示されるはずです。

Seeed_GFX を使った多階調グレースケール

上記の Hello World スケッチは、すべてのモデルで動作するように、あえて最小限の内容になっています。E1001 と E1003 のモノクロパネルは、単なる白黒に加えて実際には多階調グレースケールをサポートしており、E1001 では 4 階調、E1003 では 16 階調です。Seeed_GFX は、epaper.initGrayMode(...) と一連の TFT_GRAY_* パレット定数を通じて、両方のモードを提供します。以下の 2 つのサンプルでそれぞれを説明します。

reTerminal E1001 での 4 階調グレースケール

reTerminal E1001 の 7.5 インチモノクロパネルは、純粋な白黒ではなく、4 階調のグレースケール を描画できます。Seeed_GFX は、epaper.initGrayMode(GRAY_LEVEL4) と次の 4 つのパレット定数を通じてこれを提供します:

定数表示される階調
TFT_GRAY_0
TFT_GRAY_1濃いグレー
TFT_GRAY_2薄いグレー
TFT_GRAY_3

以下のサンプルでは、まず 4 本の横ストライプ(各グレーレベルにつき 1 本)を描画してパレットを目視で確認し、その後 800×480 のグレースケールビットマップを画面にブリット転送します。Seeed_GFX ライブラリには、すでに変換済みの image.h を含む書き込み準備完了のサンプルとして同梱されているため、自分でビットマップデータを生成する必要はありません。

ステップ 1. Seeed_GFX ライブラリからサンプルスケッチを開きます:File > Examples > Seeed_GFX > ePaper > Gray > GrayLevel4。スケッチとそれに付随する image.h がエディタで開きます。

ステップ 2. Arduino IDE で OPI PSRAM を有効にします:Tools > PSRAM > OPI PSRAM

ステップ 3. Hello World と同じワークフローで、サンプルと同じ場所に driver.h ファイルを追加し、E1001 のボードとスクリーンの組み合わせを選択します:

#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)

ステップ 4. スケッチを書き込みます。ディスプレイには、まず 4 本のグレースケールストライプ(上から黒、濃いグレー、薄いグレー、下が白)が表示され、その後クリアされて image.h のビットマップが描画されます。

参考として、サンプルスケッチは次のようになっています:

/*
* 4-Level Grayscale demo for reTerminal E1001
* (Seeed_GFX/examples/ePaper/Gray/Shape/Shape.ino)
*
* The 7.5" monochrome panel supports 4 gray levels:
* TFT_GRAY_0 black
* TFT_GRAY_1 dark gray
* TFT_GRAY_2 light gray
* TFT_GRAY_3 white
*/
#include "TFT_eSPI.h"
#include "image.h"

#ifdef EPAPER_ENABLE // Defined when an ePaper combo is selected in driver.h
EPaper epaper;
#endif

void setup() {
#ifdef EPAPER_ENABLE
epaper.begin();
epaper.fillScreen(TFT_WHITE);
epaper.update();
epaper.initGrayMode(GRAY_LEVEL4);

// Draw four horizontal stripes, one per gray level
epaper.fillRect(0, 0, epaper.width(), epaper.height() / 4, TFT_GRAY_0);
epaper.fillRect(0, epaper.height() * 1 / 4, epaper.width(), epaper.height() / 4, TFT_GRAY_1);
epaper.fillRect(0, epaper.height() * 2 / 4, epaper.width(), epaper.height() / 4, TFT_GRAY_2);
epaper.fillRect(0, epaper.height() * 3 / 4, epaper.width(), epaper.height() / 4, TFT_GRAY_3);
epaper.update();

// Then clear and show a 800x480 grayscale bitmap from image.h
epaper.fillScreen(TFT_GRAY_3);
epaper.pushImage(0, 0, 800, 480, (uint16_t *)L4_GRAY);
epaper.update();
#endif
}

void loop() {
// Nothing to do — ePaper holds the last frame without power
}
独自の画像を使いたいですか?

image.h 内の L4_GRAY 配列は、800×480 のグレースケールビットマップをあらかじめ C 配列に変換したものにすぎません。自分の画像に差し替えるには、任意の一般的な「画像から C 配列への変換」ツールを使って 800×480 のグレースケール画像から配列を再生成し、image.h 内の L4_GRAY を置き換えてください。スケッチ自体を変更する必要はありません。

ヒント

4 階調グレースケールのリフレッシュは、コントローラが各ピクセルを 2 つではなく 4 つの目標電圧に対して駆動するため、1 ビット白黒更新のおよそ 4 倍遅くなります。写真、イラスト、細部の多いダッシュボードなどの静的コンテンツにはこれを使用し、高速な UI 更新には標準の 1 ビットモードを使用してください。

GxEPD2 ライブラリの使用

Seeed_GFX に加えて、GxEPD2 ライブラリを使用して reTerminal の ePaper ディスプレイを駆動することもできます。Seeed は人気の GxEPD2 ライブラリをフォークし、reTerminal E10xx シリーズ向けの専用サポートを追加しているため、reTerminal ユーザーにはこのライブラリを使用することを推奨します。

Seeed_GxEPD2 ライブラリのインストール

このライブラリを reTerminal 製品で使用するには、Seeed_GxEPD2 をインストールする必要があります。これは reTerminal E10xx シリーズ向けに特別に調整された Seeed 独自のフォークです。

ステップ 1. Seeed_GxEPD2 の GitHub リポジトリにアクセスします。"Code" ボタンをクリックし、"Download ZIP" を選択してライブラリをコンピュータに保存します。


ステップ 2. Arduino IDE で、ダウンロードしたファイルからライブラリをインストールします。Sketch > Include Library > Add .ZIP Library... に移動し、先ほどダウンロードした ZIP ファイルを選択します。

ステップ 3. Seeed_GxEPD2 ライブラリは動作のために Adafruit GFX Library を必要とするため、これもインストールする必要があります。最も簡単な方法はライブラリマネージャを使うことです:Tools > Manage Libraries... に移動し、"Adafruit GFX Library" を検索して "Install" をクリックします。

注記

Seeed_GxEPD2 はオリジナルの GxEPD2 ライブラリに対する Seeed 独自のフォークであり、reTerminal E10xx シリーズ向けの専用ドライバと最適化が含まれています。お使いの reTerminal デバイスとの完全な互換性を確保するため、アップストリーム版ではなくこのフォークを使用することを強く推奨します。

reTerminal E1001 のプログラミング(7.5 インチ白黒スクリーン)

reTerminal E1001 には 7.5 インチの白黒 ePaper ディスプレイ(800×480、GDEY075T7 パネル、UC8179 コントローラ)が搭載されています。以下のサンプルでは、スプラッシュ画面、システム情報、タイポグラフィ、図形、パターン、ダッシュボードレイアウトなど、複数の画面をデモします。

Seeed_GxEPD2 ライブラリをインストールした後、Arduino IDE の File > Examples > Seeed_GxEPD2 > GxEPD2_reTerminal_E1001 からこのサンプルを見つけることができます。または、Seeed_GxEPD2/examples/GxEPD2_reTerminal_E1001/GxEPD2_reTerminal_E1001.ino を手動で探すこともできます。

完全なコードを表示するにはここをクリック
#include <SPI.h>
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeMono9pt7b.h>

// ===== Pin mapping =====
#define EPD_SCK_PIN 7
#define EPD_MOSI_PIN 9
#define EPD_CS_PIN 10
#define EPD_DC_PIN 11
#define EPD_RES_PIN 12
#define EPD_BUSY_PIN 13

SPIClass hspi(HSPI);

// ===== Display: 7.5" B&W 800x480 =====
#define MAX_DISPLAY_BUFFER_SIZE 16000u
#define MAX_HEIGHT(EPD) \
(EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) \
? EPD::HEIGHT \
: MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))

GxEPD2_BW<GxEPD2_750_GDEY075T7, MAX_HEIGHT(GxEPD2_750_GDEY075T7)>
display(GxEPD2_750_GDEY075T7(EPD_CS_PIN, EPD_DC_PIN, EPD_RES_PIN, EPD_BUSY_PIN));

void setup()
{
Serial.begin(115200);
delay(200);
Serial.println(F("[E1001] GxEPD2 reTerminal E1001 Demo (7.5\" B&W)"));

pinMode(EPD_RES_PIN, OUTPUT);
pinMode(EPD_DC_PIN, OUTPUT);
pinMode(EPD_CS_PIN, OUTPUT);

hspi.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, -1);
display.epd2.selectSPI(hspi, SPISettings(2000000, MSBFIRST, SPI_MODE0));
display.init(0);

Serial.println(F("[E1001] Screen 1: Splash"));
showSplashScreen();
delay(3000);

Serial.println(F("[E1001] Screen 2: System Info"));
showSystemInfo();
delay(3000);

Serial.println(F("[E1001] Screen 3: Typography"));
showTypographyDemo();
delay(3000);

Serial.println(F("[E1001] Screen 4: Geometry"));
showGeometryDemo();
delay(3000);

Serial.println(F("[E1001] Screen 5: Patterns"));
showPatternDemo();
delay(3000);

Serial.println(F("[E1001] Screen 6: Dashboard"));
showDashboardDemo();

Serial.println(F("[E1001] Demo complete. Hibernating."));
delay(2000);
display.hibernate();
}

void loop() {}

// =====================================================================
void drawCenteredText(const char* text, int16_t y, const GFXfont* font)
{
display.setFont(font);
int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(text, 0, 0, &tbx, &tby, &tbw, &tbh);
display.setCursor((display.width() - tbw) / 2 - tbx, y);
display.print(text);
}

// =====================================================================
// Screen 1: Splash
// =====================================================================
void showSplashScreen()
{
const uint16_t W = display.width();
const uint16_t H = display.height();
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.drawRect(10, 10, W - 20, H - 20, GxEPD_BLACK);
display.drawRect(14, 14, W - 28, H - 28, GxEPD_BLACK);

display.setTextColor(GxEPD_BLACK);
drawCenteredText("reTerminal E1001", H / 2 - 60, &FreeSansBold24pt7b);
drawCenteredText("7.5\" e-Paper Display", H / 2 - 10, &FreeSansBold12pt7b);
display.drawFastHLine(W / 4, H / 2 + 10, W / 2, GxEPD_BLACK);
drawCenteredText("GxEPD2 + UC8179 Driver Demo", H / 2 + 45, &FreeSansBold12pt7b);
drawCenteredText("800 x 480 pixels | Black & White", H / 2 + 75, &FreeSans9pt7b);
drawCenteredText("Seeed Studio x GxEPD2", H - 40, &FreeSans9pt7b);
} while (display.nextPage());
}

// =====================================================================
// Screen 2: System Info
// =====================================================================
void showSystemInfo()
{
const uint16_t W = display.width();
const uint16_t H = display.height();
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.fillRect(0, 0, W, 40, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
drawCenteredText("System Information", 30, &FreeSansBold12pt7b);

display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeMonoBold9pt7b);

const char* labels[] = {"MCU", "Display", "Panel", "Controller", "Interface", "Color Depth"};
char chipBuf[48];
snprintf(chipBuf, sizeof(chipBuf), "ESP32-S3 @ %lu MHz", (unsigned long)ESP.getCpuFreqMHz());
const char* values[] = {chipBuf, "800 x 480", "GDEY075T7", "UC8179", "SPI (HSPI) @ 2MHz", "B&W (1-bit)"};

int y = 95;
for (int i = 0; i < 6; i++) {
display.setCursor(50, y);
display.print(labels[i]);
display.setCursor(250, y);
display.print(": ");
display.print(values[i]);
y += 48;
if (i < 5) display.drawFastHLine(50, y - 18, W - 100, GxEPD_BLACK);
}

display.drawFastHLine(30, H - 40, W - 60, GxEPD_BLACK);
drawCenteredText("reTerminal E1001 | GxEPD2 Demo", H - 20, &FreeSans9pt7b);
} while (display.nextPage());
}

// =====================================================================
// Screen 3: Typography
// =====================================================================
void showTypographyDemo()
{
const uint16_t W = display.width();
const uint16_t H = display.height();
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.fillRect(0, 0, W, 40, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
drawCenteredText("Typography Demo", 30, &FreeSansBold12pt7b);

display.setTextColor(GxEPD_BLACK);
int y = 85, x = 40;

display.setFont(&FreeSansBold24pt7b);
display.setCursor(x, y); display.print("Sans Bold 24pt");
y += 65;

display.setFont(&FreeSansBold18pt7b);
display.setCursor(x, y); display.print("Sans Bold 18pt");
y += 50;

display.setFont(&FreeSansBold12pt7b);
display.setCursor(x, y); display.print("Sans Bold 12pt - Clean and Modern");
y += 42;

display.setFont(&FreeSans9pt7b);
display.setCursor(x, y); display.print("Sans 9pt - Body text for dense info display.");
y += 40;

display.drawFastHLine(x, y, W - 80, GxEPD_BLACK);
y += 25;

display.setFont(&FreeMonoBold12pt7b);
display.setCursor(x, y); display.print("Mono Bold 12pt");
y += 38;

display.setFont(&FreeMono9pt7b);
display.setCursor(x, y); display.print("Mono 9pt: 0123456789 ABCDEF");
y += 38;

display.setFont(&FreeSansBold18pt7b);
display.setCursor(x, y); display.print("0 1 2 3 4 5 6 7 8 9");

drawCenteredText("Multiple fonts supported", H - 20, &FreeSans9pt7b);
} while (display.nextPage());
}

// =====================================================================
// Screen 4: Geometry
// =====================================================================
void showGeometryDemo()
{
const uint16_t W = display.width();
const uint16_t H = display.height();
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.fillRect(0, 0, W, 40, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
drawCenteredText("Geometry Demo", 30, &FreeSansBold12pt7b);
display.setTextColor(GxEPD_BLACK);

// Rectangles
for (int i = 0; i < 4; i++)
display.drawRect(40 + i * 80, 60, 60, 50, GxEPD_BLACK);
display.fillRect(40 + 4 * 80, 60, 60, 50, GxEPD_BLACK);

// Circles
for (int i = 0; i < 4; i++)
display.drawCircle(70 + i * 90, 170, 25 + i * 3, GxEPD_BLACK);
for (int i = 0; i < 3; i++)
display.fillCircle(70 + (i + 4) * 90, 170, 25 + i * 3, GxEPD_BLACK);

// Triangles
for (int i = 0; i < 5; i++) {
int tx = 50 + i * 120, sz = 40 + i * 5;
display.drawTriangle(tx, 270 + sz, tx + sz / 2, 270, tx + sz, 270 + sz, GxEPD_BLACK);
}

// Fan of lines
int fcx = 150, fcy = 410;
for (int a = 0; a < 180; a += 12) {
float rad = a * 3.14159f / 180.0f;
display.drawLine(fcx, fcy, fcx + (int)(60 * cosf(rad)), fcy - (int)(60 * sinf(rad)), GxEPD_BLACK);
}

// Concentric circles
for (int r = 8; r <= 56; r += 8)
display.drawCircle(400, 400, r, GxEPD_BLACK);

// Rounded rects
for (int i = 0; i < 3; i++)
display.drawRoundRect(550 + i * 8, 340 + i * 8, 120 - i * 16, 100 - i * 16, 8 + i * 4, GxEPD_BLACK);

drawCenteredText("Adafruit GFX primitives on 7.5\" e-Paper", H - 15, &FreeSans9pt7b);
} while (display.nextPage());
}

// =====================================================================
// Screen 5: Patterns
// =====================================================================
void showPatternDemo()
{
const uint16_t W = display.width();
const uint16_t H = display.height();
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.fillRect(0, 0, W, 40, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
drawCenteredText("Pattern Demo", 30, &FreeSansBold12pt7b);
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeSans9pt7b);

int bx = 30, by = 55, bw = 150, bh = 150, gap = 30;

// Checkerboard
display.setCursor(bx + 25, by + 15); display.print("Checker");
by += 20;
for (int py = 0; py < bh / 12; py++)
for (int px = 0; px < bw / 12; px++)
if ((px + py) & 1)
display.fillRect(bx + px * 12, by + py * 12, 12, 12, GxEPD_BLACK);

// H-stripes
int bx2 = bx + bw + gap; int by2 = by - 20;
display.setCursor(bx2 + 25, by2 + 15); display.print("H-Stripes");
by2 += 20;
for (int py = 0; py < bh; py += 8)
if ((py / 8) & 1)
display.fillRect(bx2, by2 + py, bw, 8, GxEPD_BLACK);

// V-stripes
int bx3 = bx2 + bw + gap; int by3 = by2 - 20;
display.setCursor(bx3 + 25, by3 + 15); display.print("V-Stripes");
by3 += 20;
for (int px = 0; px < bw; px += 8)
if ((px / 8) & 1)
display.fillRect(bx3 + px, by3, 8, bh, GxEPD_BLACK);

// Dot grid
int bx4 = bx3 + bw + gap; int by4 = by3 - 20;
display.setCursor(bx4 + 30, by4 + 15); display.print("Dot Grid");
by4 += 20;
for (int py = 0; py < bh; py += 12)
for (int px = 0; px < bw; px += 12)
display.fillCircle(bx4 + px + 6, by4 + py + 6, 3, GxEPD_BLACK);

// Dither gradient (bottom)
int gx = 30, gy = 310, gw = W - 60, gh = 120;
display.setFont(&FreeSans9pt7b);
display.setCursor(gx, gy - 5); display.print("Dither Gradient:");
display.drawRect(gx, gy, gw, gh, GxEPD_BLACK);
for (int py = 0; py < gh; py++)
for (int px = 0; px < gw; px++) {
int density = (px * 255) / gw;
if ((((px * 7 + py * 13) ^ (px * py)) & 0xFF) < density)
display.drawPixel(gx + px, gy + py, GxEPD_BLACK);
}

drawCenteredText("Fill patterns and dithering", H - 15, &FreeSans9pt7b);
} while (display.nextPage());
}

// =====================================================================
// Screen 6: Dashboard
// =====================================================================
void drawCard(int x, int y, int w, int h, const char* title, const char* value, const char* unit)
{
display.drawRoundRect(x, y, w, h, 6, GxEPD_BLACK);
display.fillRoundRect(x + 2, y + 2, w - 4, 26, 4, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
display.setFont(&FreeSansBold12pt7b);
int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(title, 0, 0, &tbx, &tby, &tbw, &tbh);
display.setCursor(x + (w - tbw) / 2 - tbx, y + 22);
display.print(title);

display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeSansBold18pt7b);
display.getTextBounds(value, 0, 0, &tbx, &tby, &tbw, &tbh);
display.setCursor(x + (w - tbw) / 2 - tbx, y + h / 2 + 12);
display.print(value);

display.setFont(&FreeSans9pt7b);
display.getTextBounds(unit, 0, 0, &tbx, &tby, &tbw, &tbh);
display.setCursor(x + (w - tbw) / 2 - tbx, y + h - 10);
display.print(unit);
}

void showDashboardDemo()
{
const uint16_t W = display.width();
const uint16_t H = display.height();
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.fillRect(0, 0, W, 40, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
drawCenteredText("Dashboard Demo", 30, &FreeSansBold12pt7b);
display.setTextColor(GxEPD_BLACK);

int cw = 170, ch = 130, gap = 20;
int sx = (W - 4 * cw - 3 * gap) / 2;
int row1Y = 60;

char uptBuf[16]; snprintf(uptBuf, sizeof(uptBuf), "%lu", millis() / 1000);
char heapBuf[16]; snprintf(heapBuf, sizeof(heapBuf), "%lu", (unsigned long)(ESP.getFreeHeap() / 1024));

drawCard(sx, row1Y, cw, ch, "Temp", "23.5", "Celsius");
drawCard(sx + cw + gap, row1Y, cw, ch, "Humidity", "65", "% RH");
drawCard(sx + 2 * (cw + gap), row1Y, cw, ch, "Heap", heapBuf, "kB free");
drawCard(sx + 3 * (cw + gap), row1Y, cw, ch, "Uptime", uptBuf, "seconds");

// Log area
int logY = row1Y + ch + 20;
display.drawRoundRect(sx, logY, W - 2 * sx, 200, 6, GxEPD_BLACK);
display.fillRoundRect(sx + 2, logY + 2, W - 2 * sx - 4, 26, 4, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
display.setFont(&FreeSansBold12pt7b);
display.setCursor(sx + 15, logY + 22);
display.print("Activity Log");
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeMono9pt7b);

const char* logs[] = {
"[00:01] System boot - ESP32-S3",
"[00:02] Panel: GDEY075T7 800x480",
"[00:03] UC8179 controller ready",
"[00:04] SPI @ 2MHz (HSPI)",
"[00:05] Demo sequence started",
};
int ly = logY + 50;
for (int i = 0; i < 5; i++) {
display.setCursor(sx + 15, ly);
display.print(logs[i]);
ly += 28;
}

// Progress bar
int barY = logY + 210;
display.setFont(&FreeSansBold12pt7b);
display.setCursor(sx, barY + 15);
display.print("Progress:");
int barX = sx + 170, barW = W - 2 * sx - 180, barH = 20;
display.drawRect(barX, barY, barW, barH, GxEPD_BLACK);
display.fillRect(barX + 2, barY + 2, barW - 4, barH - 4, GxEPD_BLACK);
display.setFont(&FreeSans9pt7b);
display.setCursor(barX + barW + 8, barY + 14);
display.print("100%");

drawCenteredText("E-paper: zero power to maintain image", H - 15, &FreeSans9pt7b);
} while (display.nextPage());
}

次の図は、E1001 サンプルの実際の表示効果を示しています。

GxEPD2 ライブラリを使用した多階調グレースケール表示

標準的な白黒および 6 色モードに加えて、一部の reTerminal ディスプレイは多階調グレースケール描画をサポートしています。Seeed_GxEPD2 ライブラリには、通常の GxEPD2_BW / GxEPD2_7C ドライバをバイパスし、カスタム LUT 波形を使用してディスプレイコントローラを直接駆動しつつ、描画には Adafruit_GFX を活用する専用のグレースケールサンプルが含まれています。

  • reTerminal E1001 (7.5" B&W): UC8179 コントローラは、専用の VCOM/WW/KW/WK/KK LUT テーブルを介して 4 階調グレースケール モードをサポートします。2bpp(96 KB)のフレームバッファを使用し、各ピクセルは黒、ダークグレー、ライトグレー、白のいずれかになります。
  • reTerminal E1003 (10.3" Monochrome): IT8951 コントローラは、GC16 波形モードによりネイティブに 16 階調グレースケール をサポートします。4bpp(約 1.25 MB)のフレームバッファが PSRAM に確保され、各ピクセルは 16 段階のグレーのいずれかを描画できます。

reTerminal E1001 のプログラミング — 4 階調グレースケール

E1001 の UC8179 コントローラは、カスタム LUT テーブル(VCOM、LUTWW、LUTKW、LUTWK、LUTKK)をアップロードすることで、通常の 1 ビットモードから 4 階調グレースケールモードに切り替えることができます。このサンプルでは Gray4Canvas(2bpp、96 KB)を作成し、描画には Adafruit_GFX を使用し、その後 2 つのビットプレーンをコントローラにアップロードしてグレースケール描画を行います。

Seeed_GxEPD2 ライブラリをインストールした後、このサンプルは Arduino IDE の File > Examples > Seeed_GxEPD2 > GxEPD2_reTerminal_E1001_Gray4 から見つけることができます。または、Seeed_GxEPD2/examples/GxEPD2_reTerminal_E1001_Gray4/GxEPD2_reTerminal_E1001_Gray4.ino を手動で探すこともできます。

完全なコードを表示するにはここをクリック
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>

// ===== Pin mapping (E1001) =====
#define EPD_SCK_PIN 7
#define EPD_MOSI_PIN 9
#define EPD_CS_PIN 10
#define EPD_DC_PIN 11
#define EPD_RES_PIN 12
#define EPD_BUSY_PIN 13

#define EPD_W 800
#define EPD_H 480

SPIClass hspi(HSPI);
SPISettings spiSet(2000000, MSBFIRST, SPI_MODE0);

// Gray levels
#define G_BLACK 0
#define G_DARK_GRAY 1
#define G_LIGHT_GRAY 2
#define G_WHITE 3

// UC8179 gray LUT tables (verbatim from Seeed_GFX UC8179_Defines.h)
// Each LUT is 7 phases x 6 bytes = 42 bytes.
static const uint8_t LUT_VCOM_GRAY[] = {
0x00,0x00,0x06,0x08,0x07,0x01,
0x00,0x06,0x0A,0x0B,0x0A,0x01,
0x00,0x03,0x03,0x00,0x00,0x03,
0x00,0x05,0x09,0x06,0x06,0x01,
0x00,0x02,0x02,0x0A,0x0A,0x01,
0x00,0x0A,0x11,0x06,0x07,0x01,
0x00,0x02,0x01,0x02,0x01,0x01,
};

static const uint8_t LUT_WW_GRAY[] = {
0x15,0x00,0x06,0x08,0x07,0x01,
0x54,0x06,0x0A,0x0B,0x0A,0x01,
0x90,0x03,0x03,0x00,0x00,0x03,
0x2A,0x05,0x09,0x06,0x06,0x01,
0xAA,0x02,0x02,0x0A,0x0A,0x01,
0x00,0x0A,0x11,0x06,0x07,0x01,
0x28,0x02,0x01,0x02,0x01,0x01,
};

static const uint8_t LUT_KW_GRAY[] = {
0x2A,0x00,0x06,0x08,0x07,0x01,
0x59,0x06,0x0A,0x0B,0x0A,0x01,
0x90,0x03,0x03,0x00,0x00,0x03,
0x5A,0x05,0x09,0x06,0x06,0x01,
0xA8,0x02,0x02,0x0A,0x0A,0x01,
0x45,0x0A,0x11,0x06,0x07,0x01,
0xA8,0x02,0x01,0x02,0x01,0x01,
};

static const uint8_t LUT_WK_GRAY[] = {
0x16,0x00,0x06,0x08,0x07,0x01,
0xA0,0x06,0x0A,0x0B,0x0A,0x01,
0x90,0x03,0x03,0x00,0x00,0x03,
0x99,0x05,0x09,0x06,0x06,0x01,
0xA0,0x02,0x02,0x0A,0x0A,0x01,
0x40,0x0A,0x11,0x06,0x07,0x01,
0x20,0x02,0x01,0x02,0x01,0x01,
};

static const uint8_t LUT_KK_GRAY[] = {
0x26,0x00,0x06,0x08,0x07,0x01,
0x6A,0x06,0x0A,0x0B,0x0A,0x01,
0x90,0x03,0x03,0x00,0x00,0x03,
0x65,0x05,0x09,0x06,0x06,0x01,
0x50,0x02,0x02,0x0A,0x0A,0x01,
0x10,0x0A,0x11,0x06,0x07,0x01,
0x10,0x02,0x01,0x02,0x01,0x01,
};

static const uint8_t CMD_USER_GRAY[] = {
0x17, 0x3F, 0x3F, 0x07, 0x06, 0x12,
};

// ============================================================
// 4-level grayscale canvas (2bpp)
// ============================================================
class Gray4Canvas : public Adafruit_GFX
{
public:
Gray4Canvas(uint16_t w, uint16_t h) : Adafruit_GFX(w, h), _buf(nullptr) {}

bool begin()
{
uint32_t sz = uint32_t(_width) * _height / 4;
_buf = (uint8_t*)malloc(sz);
if (_buf) memset(_buf, 0xFF, sz); // fill white (0b11 = 3 = white)
return _buf != nullptr;
}

void drawPixel(int16_t x, int16_t y, uint16_t color) override
{
if (!_buf) return;
if (x < 0 || x >= width() || y < 0 || y >= height()) return;

int16_t rx = x, ry = y;
switch (getRotation()) {
case 1: rx = _width - 1 - y; ry = x; break;
case 2: rx = _width - 1 - x; ry = _height - 1 - y; break;
case 3: rx = y; ry = _height - 1 - x; break;
}

uint8_t g = color & 0x03;
uint32_t idx = uint32_t(ry) * (_width / 4) + rx / 4;
uint8_t shift = (3 - (rx & 3)) * 2;
_buf[idx] = (_buf[idx] & ~(0x03 << shift)) | (g << shift);
}

void fillScreen(uint16_t color) override
{
if (!_buf) return;
uint8_t g = color & 0x03;
uint8_t fill = (g << 6) | (g << 4) | (g << 2) | g;
memset(_buf, fill, uint32_t(_width) * _height / 4);
}

uint8_t getPixel(int16_t x, int16_t y) const
{
if (!_buf || x < 0 || x >= _width || y < 0 || y >= _height) return 0;
uint32_t idx = uint32_t(y) * (_width / 4) + x / 4;
uint8_t shift = (3 - (x & 3)) * 2;
return (_buf[idx] >> shift) & 0x03;
}

uint8_t* buffer() { return _buf; }

private:
uint8_t* _buf;
};

Gray4Canvas canvas(EPD_W, EPD_H);

// ============================================================
// UC8179 SPI helpers
// ============================================================
void checkBusy() {
delay(10);
while (!digitalRead(EPD_BUSY_PIN)) delay(10);
}

void writeCommand(uint8_t cmd) {
hspi.beginTransaction(spiSet);
digitalWrite(EPD_DC_PIN, LOW);
digitalWrite(EPD_CS_PIN, LOW);
hspi.transfer(cmd);
digitalWrite(EPD_CS_PIN, HIGH);
digitalWrite(EPD_DC_PIN, HIGH);
hspi.endTransaction();
}

void writeData(uint8_t data) {
hspi.beginTransaction(spiSet);
digitalWrite(EPD_CS_PIN, LOW);
hspi.transfer(data);
digitalWrite(EPD_CS_PIN, HIGH);
hspi.endTransaction();
}

void writeLUT(uint8_t cmd, const uint8_t* lut, uint16_t len) {
writeCommand(cmd);
for (uint16_t i = 0; i < len; i++) writeData(lut[i]);
}

// ============================================================
// UC8179 gray mode initialization
// ============================================================
void initGrayMode()
{
// Hardware reset
digitalWrite(EPD_RES_PIN, LOW); delay(10);
digitalWrite(EPD_RES_PIN, HIGH); delay(10);
checkBusy();

// Power setting (0x01)
writeCommand(0x01);
writeData(0x07);
writeData(CMD_USER_GRAY[0]);
writeData(CMD_USER_GRAY[1]);
writeData(CMD_USER_GRAY[2]);
writeData(CMD_USER_GRAY[3]);

// PLL (0x30)
writeCommand(0x30);
writeData(CMD_USER_GRAY[4]);

// VCOM DC (0x82)
writeCommand(0x82);
writeData(CMD_USER_GRAY[5]);

// Booster (0x06)
writeCommand(0x06);
writeData(0x27);
writeData(0x27);
writeData(0x28);
writeData(0x17);

// Power ON (0x04)
writeCommand(0x04);
delay(100);
checkBusy();

// Panel Setting (0x00)
writeCommand(0x00);
writeData(0x3F);

// Power saving (0xE3)
writeCommand(0xE3);
writeData(0x88);

// VCOM and Data interval (0x50)
writeCommand(0x50);
writeData(0x10);
writeData(0x07);

// PLL setting (0x52)
writeCommand(0x52);
writeData(0x00);

// Resolution (0x61)
writeCommand(0x61);
writeData(EPD_W >> 8);
writeData(EPD_W & 0xFF);
writeData(EPD_H >> 8);
writeData(EPD_H & 0xFF);

// Write LUTs for gray mode. CRITICAL ordering — UC8179:
// 0x20 = LUTC (VCOM) ← must be present
// 0x21 = LUTWW (W -> W)
// 0x22 = LUTKW (K -> W)
// 0x23 = LUTWK (W -> K)
// 0x24 = LUTKK (K -> K)
writeLUT(0x20, LUT_VCOM_GRAY, sizeof(LUT_VCOM_GRAY));
checkBusy();
writeLUT(0x21, LUT_WW_GRAY, sizeof(LUT_WW_GRAY));
checkBusy();
writeLUT(0x22, LUT_KW_GRAY, sizeof(LUT_KW_GRAY));
checkBusy();
writeLUT(0x23, LUT_WK_GRAY, sizeof(LUT_WK_GRAY));
writeLUT(0x24, LUT_KK_GRAY, sizeof(LUT_KK_GRAY));

Serial.println(F("[Gray4] UC8179 gray mode init done"));
}

// ============================================================
// Upload 2bpp canvas to UC8179 as two bit-planes.
// ============================================================
void uploadGray4Frame()
{
const uint32_t bytesPerRow = EPD_W / 4; // 200 bytes (2bpp, 4 pixels/byte)

// ---- Bit plane → DTM1 (command 0x10) ----
writeCommand(0x10);
hspi.beginTransaction(spiSet);
digitalWrite(EPD_CS_PIN, LOW);
for (uint16_t row = 0; row < EPD_H; row++) {
const uint8_t* rp = canvas.buffer() + uint32_t(row) * bytesPerRow;
for (uint16_t col8 = 0; col8 < EPD_W / 8; col8++) {
uint8_t out = 0;
for (uint8_t bit = 0; bit < 8; bit++) {
uint16_t px = col8 * 8 + bit;
uint32_t idx = px / 4;
uint8_t shift = (3 - (px & 3)) * 2;
uint8_t gray = 3 - ((rp[idx] >> shift) & 0x03); // INVERT
if (gray & 0x01) out |= (0x80 >> bit);
}
hspi.transfer(out);
}
}
digitalWrite(EPD_CS_PIN, HIGH);
hspi.endTransaction();

// ---- Bit plane → DTM2 (command 0x13) ----
writeCommand(0x13);
hspi.beginTransaction(spiSet);
digitalWrite(EPD_CS_PIN, LOW);
for (uint16_t row = 0; row < EPD_H; row++) {
const uint8_t* rp = canvas.buffer() + uint32_t(row) * bytesPerRow;
for (uint16_t col8 = 0; col8 < EPD_W / 8; col8++) {
uint8_t out = 0;
for (uint8_t bit = 0; bit < 8; bit++) {
uint16_t px = col8 * 8 + bit;
uint32_t idx = px / 4;
uint8_t shift = (3 - (px & 3)) * 2;
uint8_t gray = 3 - ((rp[idx] >> shift) & 0x03); // INVERT
if (gray & 0x02) out |= (0x80 >> bit);
}
hspi.transfer(out);
}
}
digitalWrite(EPD_CS_PIN, HIGH);
hspi.endTransaction();

Serial.println(F("[Gray4] Frame uploaded (2 bit planes)"));
}

void refreshDisplay()
{
unsigned long t0 = millis();
writeCommand(0x12); // Display Refresh
delay(100);
checkBusy();
Serial.printf("[Gray4] Refresh %lu ms\n", millis() - t0);
}

void sleepDisplay()
{
writeCommand(0x02); // Power OFF
checkBusy();
writeCommand(0x07); // Deep sleep
writeData(0xA5);
}

// ============================================================
// Demo drawing
// ============================================================
void drawCenteredText(const char* text, int16_t y, const GFXfont* font, uint8_t gray)
{
canvas.setFont(font);
canvas.setTextColor(gray);
int16_t tbx, tby; uint16_t tbw, tbh;
canvas.getTextBounds(text, 0, 0, &tbx, &tby, &tbw, &tbh);
canvas.setCursor((EPD_W - tbw) / 2 - tbx, y);
canvas.print(text);
}

void showGrayscaleDemo()
{
Serial.println(F("[Gray4] Drawing demo..."));
canvas.fillScreen(G_WHITE);

// Title bar
canvas.fillRect(0, 0, EPD_W, 40, G_BLACK);
drawCenteredText("4-Level Grayscale Demo - E1001", 30, &FreeMonoBold12pt7b, G_WHITE);

// 4 large gray bands
int bandH = 70, startY = 55;
const char* labels[] = {"Gray 0: Black", "Gray 1: Dark Gray", "Gray 2: Light Gray", "Gray 3: White"};
for (int i = 0; i < 4; i++) {
int y = startY + i * (bandH + 8);
canvas.fillRect(30, y, EPD_W - 60, bandH, i);
uint8_t textGray = (i < 2) ? G_WHITE : G_BLACK;
canvas.setFont(&FreeSansBold12pt7b);
canvas.setTextColor(textGray);
canvas.setCursor(60, y + bandH / 2 + 8);
canvas.print(labels[i]);
}

// Concentric gray circles
int areaTop = startY + 4 * (bandH + 8);
int areaBot = EPD_H - 30;
int cy = (areaTop + areaBot) / 2;
int cx = EPD_W - 80;
canvas.setFont(&FreeMonoBold9pt7b);
canvas.setTextColor(G_BLACK);
canvas.setCursor(30, cy - 6);
canvas.print("Concentric gray circles");
canvas.setCursor(30, cy + 14);
canvas.print("(black / dark / light / white)");
canvas.fillCircle(cx, cy, 38, G_BLACK);
canvas.fillCircle(cx, cy, 28, G_DARK_GRAY);
canvas.fillCircle(cx, cy, 18, G_LIGHT_GRAY);
canvas.fillCircle(cx, cy, 9, G_WHITE);

// Footer
canvas.fillRect(0, EPD_H - 30, EPD_W, 30, G_BLACK);
drawCenteredText("UC8179 4-gray LUT mode | E1001", EPD_H - 8, &FreeMonoBold9pt7b, G_WHITE);

Serial.println(F("[Gray4] Uploading..."));
uploadGray4Frame();
refreshDisplay();
Serial.println(F("[Gray4] Done."));
}

// ============================================================
void setup()
{
Serial.begin(115200);
delay(200);
Serial.println(F("[E1001 Gray4] Starting..."));

pinMode(EPD_CS_PIN, OUTPUT); digitalWrite(EPD_CS_PIN, HIGH);
pinMode(EPD_DC_PIN, OUTPUT); digitalWrite(EPD_DC_PIN, HIGH);
pinMode(EPD_RES_PIN, OUTPUT); digitalWrite(EPD_RES_PIN, HIGH);
pinMode(EPD_BUSY_PIN, INPUT);

hspi.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, -1);

if (!canvas.begin()) {
Serial.println(F("[Gray4] FATAL: alloc failed (96 KB)"));
while (true) delay(1000);
}
Serial.printf("[Gray4] Canvas OK (%lu bytes)\n", (unsigned long)(EPD_W * EPD_H / 4));

initGrayMode();
showGrayscaleDemo();
sleepDisplay();
}

void loop() {}

次の図は、E1001 の 4 階調グレースケール例の実際の表示効果を示しています:

注記

ePaper ディスプレイは、比較的リフレッシュレートが遅いです。白黒ディスプレイ(E1001、E1003)は通常 1〜3 秒でリフレッシュしますが、6 色ディスプレイ(E1002、E1004)はフルリフレッシュに 25〜40 秒かかる場合があります。これは正常な動作であり、超低消費電力とバックライトなしでも優れた視認性を実現するためのトレードオフです。

トラブルシューティング

Q1: 上記のコードを実行しても、reTerminal の ePaper ディスプレイに何も表示されない、またはリフレッシュされないのはなぜですか?

この問題は、reTerminal に MicroSD カードを挿入している場合に発生することがあります。理由は、reTerminal 上では MicroSD カードと ePaper ディスプレイが同じ SPI バスを共有しているためです。MicroSD カードが挿入されているにもかかわらず、そのイネーブル(チップセレクト)ピンが適切に制御されていないと、SPI バス上で競合が発生する可能性があります。具体的には、MicroSD カードが BUSY ラインを High のまま保持してしまい、その結果 ePaper ディスプレイが正しく動作できず、画面が更新・リフレッシュされない状態になります。

// Initialize SD Card
pinMode(SD_EN_PIN, OUTPUT);
digitalWrite(SD_EN_PIN, HIGH);
pinMode(SD_DET_PIN, INPUT_PULLUP);

これを解決するには、上記で示したコードを使用して MicroSD カードが正しくイネーブルされていることを確認する必要があります。このコードは、適切なピン状態を設定することで MicroSD カードを初期化および有効化し、SPI バスの競合を防いで SD カードと ePaper ディスプレイの両方が同時に動作できるようにします。reTerminal で MicroSD カードを使用する場合は、必ず推奨される初期化コードを使用して、このような問題を回避してください。

プロジェクト内で MicroSD カードを使用しない場合は、ディスプレイプログラムを実行する前に、デバイスの電源を切り、カードを取り外すことをお勧めします。すでに reTerminal にカードが挿入されている場合は、MicroSD カードを使用するかどうかにかかわらず、画面を正しく表示できるようにするために、上記のコードを追加する必要があります。

Q2: なぜ reTerminal にプログラムを書き込めないのですか?

reTerminal にプログラムを書き込む際に、次のようなエラーが発生する場合があります。

この場合、Arduino IDE のアップロード速度が高すぎる設定になっている可能性があります。ボーレートを 115200 に変更して、この問題を解決してください。

技術サポートと製品ディスカッション

当社の製品をお選びいただきありがとうございます。私たちは、製品をできるだけスムーズにご利用いただけるよう、さまざまなサポートを提供しています。お好みやニーズに応じて選べる、複数のコミュニケーションチャネルをご用意しています。

Loading Comments...