Arduino での reTerminal E シリーズ ePaper ディスプレイの使用開始

はじめに
reTerminal E シリーズは、ESP32-S3 をメインコントローラーとし、ePaper ディスプレイを統合した Seeed Studio の最新の産業用 HMI ソリューションです。このガイドでは、Arduino IDE を使用して reTerminal E シリーズデバイスの ePaper ディスプレイをプログラミングする方法を説明し、優れた視認性と超低消費電力を備えたカスタムインターフェースとアプリケーションの作成を可能にします。
必要な材料
このチュートリアルを完了するには、以下の reTerminal E シリーズデバイスのいずれかをご用意ください:
reTerminal E1001 | reTerminal E1002 |
---|---|
![]() | ![]() |
環境準備
reTerminal E シリーズ ePaper ディスプレイを Arduino でプログラミングするには、ESP32 サポート付きの Arduino IDE をセットアップする必要があります。
Arduino を初めて使用する場合は、Getting Started with Arduino を参照することを強くお勧めします。
Arduino IDE セットアップ
ステップ 1. Arduino IDE をダウンロードしてインストールし、Arduino アプリケーションを起動します。

ステップ 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
ステップ3. ESP32ボードパッケージをインストールします。
Tools > Board > Boards Managerに移動し、「esp32」を検索してEspressif SystemsのESP32パッケージをインストールします。
ステップ4. 正しいボードを選択します。
Tools > Board > ESP32 Arduinoに移動し、XIAO_ESP32S3を選択します。
ステップ5. reTerminal E SeriesのePaperディスプレイをUSB-Cケーブルでコンピュータに接続します。
ステップ6. Tools > Portから正しいポートを選択します。
ePaperディスプレイプログラミング
reTerminal E1001は7.5インチの白黒ePaperディスプレイを搭載し、reTerminal E1002は7.3インチのフルカラーePaperディスプレイを搭載しています。両方のディスプレイは、超低消費電力でさまざまな照明条件下で優れた視認性を提供し、最小限の電力使用で常時表示が必要な産業用アプリケーションに最適です。
Seeed_GFXライブラリの使用
ePaperディスプレイを制御するために、さまざまなSeeed Studioディスプレイデバイスの包括的なサポートを提供するSeeed_GFXライブラリを使用します。
ステップ1. GitHubからSeeed_GFXライブラリをダウンロードします:
ステップ2. Arduino IDEでZIPファイルを追加してライブラリをインストールします。Sketch > Include Library > Add .ZIP Libraryに移動し、ダウンロードしたZIPファイルを選択します。
以前にTFT_eSPIライブラリをインストールしている場合、競合を避けるためにArduinoライブラリフォルダから一時的に削除または名前を変更する必要がある場合があります。Seeed_GFXはTFT_eSPIのフォークで、Seeed Studioディスプレイ用の追加機能を備えています。
- Programming reTerminal E1001
- Programming reTerminal E1002
reTerminal E1001のプログラミング(7.5インチ白黒ePaper)
白黒ePaperディスプレイでの基本的な描画操作を実演する簡単な例を見てみましょう。
ステップ1. Seeed_GFXライブラリからサンプルスケッチを開きます:File > Examples > Seeed_GFX > ePaper > Basic > HelloWorld
ステップ2. スケッチと同じフォルダにdriver.h
という名前の新しいファイルを作成します。Arduino IDEの矢印ボタンをクリックして「New Tab」を選択し、driver.h
と名前を付けることでこれを行うことができます。

ステップ3. Seeed GFX Configuration Toolに移動し、デバイスリストからreTerminal E1001を選択します。

ステップ4. 生成された設定コードをコピーしてdriver.h
ファイルに貼り付けます。コードは次のようになります:
#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)
ステップ5. スケッチをreTerminal E1001にアップロードします。ディスプレイに線、テキスト、図形など、基本的な描画機能を実演する様々なグラフィックが表示されるはずです。

reTerminal E1002のプログラミング(7.3インチフルカラーePaper)
フルカラーePaperディスプレイは赤、黒、白の色をサポートし、より視覚的に豊かなインターフェースを可能にします。
ステップ1. Seeed_GFXライブラリからカラーサンプルスケッチを開きます:File > Examples > Seeed_GFX > ePaper > Colorful > HelloWorld
ステップ2. スケッチと同じフォルダにdriver.h
という新しいファイルを作成します。前回と同じプロセスに従ってください。

ステップ3. Seeed GFX Configuration Toolにアクセスし、デバイスリストからreTerminal E1002を選択します。

ステップ4. 生成された設定コードをコピーして、driver.h
ファイルに貼り付けます。コードは次のようになります:
#define BOARD_SCREEN_COMBO 521 // reTerminal E1002 (UC8179C)
ステップ5. スケッチをreTerminal E1002にアップロードします。ディスプレイにはカラフルなグラフィックが表示され、ePaperディスプレイのフルカラー機能がデモンストレーションされます。

GxEPD2ライブラリの使用
Seeed_GFXの他に、GxEPD2
ライブラリを使用してreTerminalのePaperディスプレイを駆動することもできます。GxEPD2
は強力で人気のあるライブラリで、幅広いe-paperディスプレイをサポートしています。
GxEPD2ライブラリのインストール
最新の機能とデバイスサポートを確実に利用するため、GitHubリポジトリからGxEPD2
ライブラリを手動でインストールするのが最適です。
ステップ1. GxEPD2 GitHubリポジトリにアクセスします。「Code」ボタンをクリックし、「Download ZIP」を選択してライブラリをコンピューターに保存します。
ステップ2. Arduino IDEで、ダウンロードしたファイルからライブラリをインストールします。**スケッチ > ライブラリをインクルード > .ZIP形式のライブラリをインストール...**に移動し、ダウンロードしたZIPファイルを選択します。
ステップ3. GxEPD2
ライブラリが機能するにはAdafruit GFX Library
も必要で、これもインストールする必要があります。最も簡単な方法はライブラリマネージャーを使用することです:**ツール > ライブラリを管理...**に移動し、「Adafruit GFX Library」を検索して「インストール」をクリックします。
GxEPD2
は便宜上Arduino Library Managerでも利用できますが、そこで見つかるバージョンは古いことがよくあります。GitHubリポジトリは最新バージョンの決定的なソースであり、最新の機能、バグ修正、最新のe-paperディスプレイのサポートが含まれています。したがって、最新のコードを確実に入手するため、GitHubから直接ライブラリをダウンロードすることが推奨されるアプローチです。
- Programming reTerminal E1001
- Programming reTerminal E1002
reTerminal E1001のプログラミング(白黒画面)
以下は、GxEPD2
ライブラリを使用してreTerminal E1001の白黒ePaperディスプレイに「Hello World!」を表示するサンプルコードです。E1001用のドライバーを選択するため、EPD_SELECT
を0
に設定します。
#include <GxEPD2_BW.h>
#include <GxEPD2_7C.h>
#include <Fonts/FreeMonoBold9pt7b.h>
// Define ePaper SPI pins
#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
// Select the ePaper driver to use
// 0: reTerminal E1001 (7.5'' B&W)
// 1: reTerminal E1002 (7.3'' Color)
#define EPD_SELECT 0
#if (EPD_SELECT == 0)
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7 // 7.5'' B&W driver
#elif (EPD_SELECT == 1)
#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // 7.3'' Color driver
#endif
#define MAX_DISPLAY_BUFFER_SIZE 16000
#define MAX_HEIGHT(EPD) \
(EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) \
? EPD::HEIGHT \
: MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
// Initialize display object
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)>
display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS_PIN, /*DC=*/EPD_DC_PIN,
/*RST=*/EPD_RES_PIN, /*BUSY=*/EPD_BUSY_PIN));
SPIClass hspi(HSPI);
void setup()
{
pinMode(EPD_RES_PIN, OUTPUT);
pinMode(EPD_DC_PIN, OUTPUT);
pinMode(EPD_CS_PIN, OUTPUT);
// Initialize SPI
hspi.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, -1);
display.epd2.selectSPI(hspi, SPISettings(2000000, MSBFIRST, SPI_MODE0));
// Initialize display
display.init(0);
helloWorld();
}
const char HelloWorld[] = "Hello World!";
void helloWorld()
{
display.setRotation(0);
display.setFont(&FreeMonoBold9pt7b);
display.setTextColor(GxEPD_BLACK);
int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
// center the bounding box by transposition of the origin:
uint16_t x = ((display.width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby;
display.setFullWindow();
display.firstPage();
do
{
display.fillScreen(GxEPD_WHITE);
display.setCursor(x, y);
display.print(HelloWorld);
}
while (display.nextPage());
}
void loop() {};
reTerminal E1002のプログラミング(フルカラースクリーン)
reTerminal E1002の場合、EPD_SELECT
の値を1
に変更するだけです。これにより、7.3インチフルカラーePaperディスプレイ用の適切なドライバが選択されます。コードの残りの部分は同じままです。
#include <GxEPD2_BW.h>
#include <GxEPD2_7C.h>
#include <Fonts/FreeMonoBold9pt7b.h>
// Define ePaper SPI pins
#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
// Select the ePaper driver to use
// 0: reTerminal E1001 (7.5'' B&W)
// 1: reTerminal E1002 (7.3'' Color)
#define EPD_SELECT 1
#if (EPD_SELECT == 0)
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7 // 7.5'' B&W driver
#elif (EPD_SELECT == 1)
#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // 7.3'' Color driver
#endif
#define MAX_DISPLAY_BUFFER_SIZE 16000
#define MAX_HEIGHT(EPD) \
(EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) \
? EPD::HEIGHT \
: MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
// Initialize display object
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)>
display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS_PIN, /*DC=*/EPD_DC_PIN,
/*RST=*/EPD_RES_PIN, /*BUSY=*/EPD_BUSY_PIN));
SPIClass hspi(HSPI);
void setup()
{
pinMode(EPD_RES_PIN, OUTPUT);
pinMode(EPD_DC_PIN, OUTPUT);
pinMode(EPD_CS_PIN, OUTPUT);
// Initialize SPI
hspi.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, -1);
display.epd2.selectSPI(hspi, SPISettings(2000000, MSBFIRST, SPI_MODE0));
// Initialize display
display.init(0);
helloWorld();
}
const char HelloWorld[] = "Hello World!";
void helloWorld()
{
display.setRotation(0);
display.setFont(&FreeMonoBold9pt7b);
// For the color screen, you can set different colors, e.g., GxEPD_BLACK, GxEPD_RED
display.setTextColor(GxEPD_GREEN);
int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
// center the bounding box by transposition of the origin:
uint16_t x = ((display.width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby;
display.setFullWindow();
display.firstPage();
do
{
display.fillScreen(GxEPD_WHITE);
display.setCursor(x, y);
display.print(HelloWorld);
}
while (display.nextPage());
}
void loop() {};
ePaperディスプレイは比較的遅いリフレッシュレート(通常、フルリフレッシュで1-3秒)を持ちます。これは正常な動作であり、超低消費電力とバックライトなしでの優れた視認性とのトレードオフです。
reTerminalハードウェアの使用ルーチン
それでは、Arduinoコード例を使ってreTerminal Eシリーズの主要機能を探ってみましょう。
LED制御
reTerminal EシリーズにはGPIO6経由で制御できるオンボードLEDがあります。LEDロジックは反転していることに注意してください(LOW = ON、HIGH = OFF)。
// reTerminal E Series - LED Control Example
#define SERIAL_RX 44
#define SERIAL_TX 43
#define LED_PIN 6 // GPIO6 - Onboard LED (inverted logic)
void setup() {
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) {
delay(10);
}
Serial1.println("LED Control Example");
// Configure LED pin
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Turn LED ON (LOW because it's inverted)
digitalWrite(LED_PIN, LOW);
Serial1.println("LED ON");
delay(1000);
// Turn LED OFF (HIGH because it's inverted)
digitalWrite(LED_PIN, HIGH);
Serial1.println("LED OFF");
delay(1000);
}
Buzzer Control
The reTerminal E Series includes a buzzer on GPIO7 that can produce various tones and alert sounds.
// reTerminal E Series - Buzzer Control Example
#define SERIAL_RX 44
#define SERIAL_TX 43
#define BUZZER_PIN 45 // GPIO45 - Buzzer
void setup() {
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) {
delay(10);
}
Serial1.println("Buzzer Control Example");
}
void loop() {
Serial1.println("Simple beep");
tone(BUZZER_PIN, 1000, 100); // 1kHz for 100ms
delay(1000);
Serial1.println("Double beep");
for (int i = 0; i < 2; i++) {
tone(BUZZER_PIN, 2000, 50); // 2kHz for 50ms
delay(100);
}
delay(900);
Serial1.println("Long beep");
tone(BUZZER_PIN, 800, 500); // 800Hz for 500ms
delay(1500);
Serial1.println("Alarm sound");
for (int i = 0; i < 5; i++) {
tone(BUZZER_PIN, 1500, 100);
delay(100);
tone(BUZZER_PIN, 1000, 100);
delay(100);
}
delay(2000);
}
Buzzer with Tones
#define SERIAL_RX 44
#define SERIAL_TX 43
#define BUZZER_PIN 45 // GPIO7 - Buzzer
// Reference: This list was adapted from the table located here:
// http://www.phy.mtu.edu/~suits/notefreqs.html
#define NOTE_C0 16.35 //C0
#define NOTE_Db0 17.32 //C#0/Db0
#define NOTE_D0 18.35 //D0
#define NOTE_Eb0 19.45 //D#0/Eb0
#define NOTE_E0 20.6 //E0
#define NOTE_F0 21.83 //F0
#define NOTE_Gb0 23.12 //F#0/Gb0
#define NOTE_G0 24.5 //G0
#define NOTE_Ab0 25.96 //G#0/Ab0
#define NOTE_A0 27.5 //A0
#define NOTE_Bb0 29.14 //A#0/Bb0
#define NOTE_B0 30.87 //B0
#define NOTE_C1 32.7 //C1
#define NOTE_Db1 34.65 //C#1/Db1
#define NOTE_D1 36.71 //D1
#define NOTE_Eb1 38.89 //D#1/Eb1
#define NOTE_E1 41.2 //E1
#define NOTE_F1 43.65 //F1
#define NOTE_Gb1 46.25 //F#1/Gb1
#define NOTE_G1 49 //G1
#define NOTE_Ab1 51.91 //G#1/Ab1
#define NOTE_A1 55 //A1
#define NOTE_Bb1 58.27 //A#1/Bb1
#define NOTE_B1 61.74 //B1
#define NOTE_C2 65.41 //C2 (Middle C)
#define NOTE_Db2 69.3 //C#2/Db2
#define NOTE_D2 73.42 //D2
#define NOTE_Eb2 77.78 //D#2/Eb2
#define NOTE_E2 82.41 //E2
#define NOTE_F2 87.31 //F2
#define NOTE_Gb2 92.5 //F#2/Gb2
#define NOTE_G2 98 //G2
#define NOTE_Ab2 103.83 //G#2/Ab2
#define NOTE_A2 110 //A2
#define NOTE_Bb2 116.54 //A#2/Bb2
#define NOTE_B2 123.47 //B2
#define NOTE_C3 130.81 //C3
#define NOTE_Db3 138.59 //C#3/Db3
#define NOTE_D3 146.83 //D3
#define NOTE_Eb3 155.56 //D#3/Eb3
#define NOTE_E3 164.81 //E3
#define NOTE_F3 174.61 //F3
#define NOTE_Gb3 185 //F#3/Gb3
#define NOTE_G3 196 //G3
#define NOTE_Ab3 207.65 //G#3/Ab3
#define NOTE_A3 220 //A3
#define NOTE_Bb3 233.08 //A#3/Bb3
#define NOTE_B3 246.94 //B3
#define NOTE_C4 261.63 //C4
#define NOTE_Db4 277.18 //C#4/Db4
#define NOTE_D4 293.66 //D4
#define NOTE_Eb4 311.13 //D#4/Eb4
#define NOTE_E4 329.63 //E4
#define NOTE_F4 349.23 //F4
#define NOTE_Gb4 369.99 //F#4/Gb4
#define NOTE_G4 392 //G4
#define NOTE_Ab4 415.3 //G#4/Ab4
#define NOTE_A4 440 //A4
#define NOTE_Bb4 466.16 //A#4/Bb4
#define NOTE_B4 493.88 //B4
#define NOTE_C5 523.25 //C5
#define NOTE_Db5 554.37 //C#5/Db5
#define NOTE_D5 587.33 //D5
#define NOTE_Eb5 622.25 //D#5/Eb5
#define NOTE_E5 659.26 //E5
#define NOTE_F5 698.46 //F5
#define NOTE_Gb5 739.99 //F#5/Gb5
#define NOTE_G5 783.99 //G5
#define NOTE_Ab5 830.61 //G#5/Ab5
#define NOTE_A5 880 //A5
#define NOTE_Bb5 932.33 //A#5/Bb5
#define NOTE_B5 987.77 //B5
#define NOTE_C6 1046.5 //C6
#define NOTE_Db6 1108.73 //C#6/Db6
#define NOTE_D6 1174.66 //D6
#define NOTE_Eb6 1244.51 //D#6/Eb6
#define NOTE_E6 1318.51 //E6
#define NOTE_F6 1396.91 //F6
#define NOTE_Gb6 1479.98 //F#6/Gb6
#define NOTE_G6 1567.98 //G6
#define NOTE_Ab6 1661.22 //G#6/Ab6
#define NOTE_A6 1760 //A6
#define NOTE_Bb6 1864.66 //A#6/Bb6
#define NOTE_B6 1975.53 //B6
#define NOTE_C7 2093 //C7
#define NOTE_Db7 2217.46 //C#7/Db7
#define NOTE_D7 2349.32 //D7
#define NOTE_Eb7 2489.02 //D#7/Eb7
#define NOTE_E7 2637.02 //E7
#define NOTE_F7 2793.83 //F7
#define NOTE_Gb7 2959.96 //F#7/Gb7
#define NOTE_G7 3135.96 //G7
#define NOTE_Ab7 3322.44 //G#7/Ab7
#define NOTE_A7 3520 //A7
#define NOTE_Bb7 3729.31 //A#7/Bb7
#define NOTE_B7 3951.07 //B7
#define NOTE_C8 4186.01 //C8
#define NOTE_Db8 4434.92 //C#8/Db8
#define NOTE_D8 4698.64 //D8
#define NOTE_Eb8 4978.03 //D#8/Eb8
void buzzer_tone (float noteFrequency, long noteDuration, int silentDuration){
if(silentDuration==0) {silentDuration=1;}
tone(BUZZER_PIN, noteFrequency, noteDuration);
delay(noteDuration); // milliseconds
noTone(BUZZER_PIN); // stop the tone
delay(silentDuration);
}
void setup() {
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) {
delay(10);
}
Serial1.println("Buzzer Control Example");
// Configure buzzer pin
pinMode(BUZZER_PIN, OUTPUT);
}
void loop() {
buzzer_tone(NOTE_C5, 80, 20);
buzzer_tone(NOTE_E5, 80, 20);
buzzer_tone(NOTE_G5, 80, 20);
buzzer_tone(NOTE_C6, 150, 0);
delay(30000);
}
ブザー機能:
digitalWrite()
: 基本的なビープ音のためのシンプルなON/OFF制御tone(pin, frequency, duration)
: メロディーやアラートのための特定の周波数を生成noTone(pin)
: トーン生成を停止
一般的なアラートパターン:
- 単一ビープ: 確認
- ダブルビープ: 警告
- トリプルビープ: エラー
- 連続: 重要なアラート
ユーザーボタン
reTerminal Eシリーズには、様々な制御目的に使用できる3つのユーザープログラマブルボタンが搭載されています。このセクションでは、Arduinoを使用してボタンの状態を読み取り、ボタンの押下に応答する方法を説明します。
reTerminal EシリーズにはESP32-S3に接続された3つのボタンがあります:
- KEY0 (GPIO3): 右ボタン(緑ボタン)
- KEY1 (GPIO4): 中央ボタン
- KEY2 (GPIO5): 左ボタン
すべてのボタンはアクティブローで、押されたときにLOWを読み取り、離されたときにHIGHを読み取ります。
基本的なボタン読み取りの例
この例では、ボタンの押下を検出し、シリアルモニターにメッセージを出力する方法を説明します。
// reTerminal E Series - Button Test
// Based on hardware schematic
// Define button pins according to schematic
const int BUTTON_KEY0 = 3; // KEY0 - GPIO3
const int BUTTON_KEY1 = 4; // KEY1 - GPIO4
const int BUTTON_KEY2 = 5; // KEY2 - GPIO5
// Button state variables
bool lastKey0State = HIGH;
bool lastKey1State = HIGH;
bool lastKey2State = HIGH;
void setup() {
// Initialize serial communication
Serial1.begin(115200, SERIAL_8N1, 44, 43);
while (!Serial1) {
delay(10); // Wait for serial port to connect
}
Serial1.println("=================================");
Serial1.println("reTerminal E Series - Button Test");
Serial1.println("=================================");
Serial1.println("Press any button to see output");
Serial1.println();
// Configure button pins as inputs
// Hardware already has pull-up resistors, so use INPUT mode
pinMode(BUTTON_KEY0, INPUT);
pinMode(BUTTON_KEY1, INPUT);
pinMode(BUTTON_KEY2, INPUT);
// Read initial states
lastKey0State = digitalRead(BUTTON_KEY0);
lastKey1State = digitalRead(BUTTON_KEY1);
lastKey2State = digitalRead(BUTTON_KEY2);
Serial1.println("Setup complete. Ready to detect button presses...");
}
void loop() {
// Read current button states
bool key0State = digitalRead(BUTTON_KEY0);
bool key1State = digitalRead(BUTTON_KEY1);
bool key2State = digitalRead(BUTTON_KEY2);
// Check KEY0
if (key0State != lastKey0State) {
if (key0State == LOW) {
Serial1.println("KEY0 (GPIO3) pressed!");
} else {
Serial1.println("KEY0 (GPIO3) released!");
}
lastKey0State = key0State;
delay(50); // Debounce delay
}
// Check KEY1
if (key1State != lastKey1State) {
if (key1State == LOW) {
Serial1.println("KEY1 (GPIO4) pressed!");
} else {
Serial1.println("KEY1 (GPIO4) released!");
}
lastKey1State = key1State;
delay(50); // Debounce delay
}
// Check KEY2
if (key2State != lastKey2State) {
if (key2State == LOW) {
Serial1.println("KEY2 (GPIO5) pressed!");
} else {
Serial1.println("KEY2 (GPIO5) released!");
}
lastKey2State = key2State;
delay(50); // Debounce delay
}
delay(10); // Small delay to prevent excessive CPU usage
}
コードの動作原理:
-
ピン定義: 各ボタンのGPIOピン番号の定数を定義します。
-
ピン設定:
setup()
で、各ボタンピンをINPUT
として設定します。 -
ボタン検出:
loop()
で、digitalRead()
を使用して各ボタンの状態を継続的にチェックします。ボタンが押されると、ピンはLOWを読み取ります。 -
デバウンス: 各ボタン押下後の200msの簡単な遅延により、機械的なバウンスによる単一押下からの複数検出を防ぎます。
-
シリアル出力: 各ボタン押下により、デバッグと検証のためにシリアルモニターにメッセージが送信されます。
ステップ1. コードをreTerminal Eシリーズデバイスにアップロードします。
ステップ2. Arduino IDEでシリアルモニターを開きます(ツール > シリアルモニター)。
ステップ3. ボーレートを115200に設定します。
ステップ4. 各ボタンを押して、シリアルモニターの出力を確認します。
ボタンを押したときの期待される出力:
=================================
reTerminal E Series - Button Test
=================================
Press any button to see output
KEY0 (GPIO3) pressed!
KEY0 (GPIO3) released!
KEY1 (GPIO4) pressed!
KEY1 (GPIO4) released!
KEY2 (GPIO5) pressed!
KEY2 (GPIO5) released!
環境センサー (SHT4x)
reTerminal E シリーズには、I2C 経由で接続された統合 SHT4x 温度・湿度センサーが含まれています。
必要なライブラリのインストール
Arduino ライブラリマネージャー(Tools > Manage Libraries...)経由で2つのライブラリをインストールします:
- "Sensirion I2C SHT4x" を検索してインストール
- "Sensirion Core" を検索してインストール(依存関係)
基本的な温度・湿度の例
// reTerminal E Series - SHT40 Temperature & Humidity Sensor Example
#include <Wire.h>
#include <SensirionI2cSht4x.h>
// Serial configuration for reTerminal E Series
#define SERIAL_RX 44
#define SERIAL_TX 43
// I2C pins for reTerminal E Series
#define I2C_SDA 19
#define I2C_SCL 20
// Create sensor object
SensirionI2cSht4x sht4x;
void setup() {
// Initialize Serial1 for reTerminal E Series
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) {
delay(10);
}
Serial1.println("SHT4x Basic Example");
// Initialize I2C with custom pins
Wire.begin(I2C_SDA, I2C_SCL);
uint16_t error;
char errorMessage[256];
// Initialize the sensor
sht4x.begin(Wire, 0x44);
// Read and print serial number
uint32_t serialNumber;
error = sht4x.serialNumber(serialNumber);
if (error) {
Serial1.print("Error trying to execute serialNumber(): ");
errorToString(error, errorMessage, 256);
Serial1.println(errorMessage);
} else {
Serial1.print("Serial Number: ");
Serial1.println(serialNumber);
Serial1.println();
}
}
void loop() {
uint16_t error;
char errorMessage[256];
delay(5000); // Wait 5 seconds between measurements
float temperature;
float humidity;
// Measure temperature and humidity with high precision
error = sht4x.measureHighPrecision(temperature, humidity);
if (error) {
Serial1.print("Error trying to execute measureHighPrecision(): ");
errorToString(error, errorMessage, 256);
Serial1.println(errorMessage);
} else {
Serial1.print("Temperature: ");
Serial1.print(temperature);
Serial1.print("°C\t");
Serial1.print("Humidity: ");
Serial1.print(humidity);
Serial1.println("%");
}
}
セットアップ関数:
- シリアル初期化: reTerminal Eシリーズ専用のピン44(RX)と43(TX)を使用して
Serial1
を使用 - I2C初期化: ピン19(SDA)と20(SCL)でI2Cを設定
- センサー初期化:
sht4x.begin(Wire, 0x44)
を呼び出してアドレス0x44でSHT4xセンサーを初期化 - シリアル番号読み取り: 検証のためにセンサーの固有シリアル番号を読み取り表示
ループ関数:
- 遅延: オーバーサンプリングを避けるため測定間に5秒待機
- 測定: 正確な読み取りのために
measureHighPrecision()
を使用(約8.3ms要) - エラーハンドリング: エラーをチェックし、
errorToString()
を使用して読み取り可能なメッセージに変換 - 結果表示: 摂氏温度と相対湿度パーセンテージを印刷
期待される出力
SHT4x Basic Example
Serial Number: 331937553
Temperature: 27.39°C Humidity: 53.68%
Temperature: 27.40°C Humidity: 53.51%
Temperature: 27.38°C Humidity: 53.37%
バッテリー管理システム
reTerminal Eシリーズには、分圧回路を備えたADCピンを通じてバッテリー電圧監視機能が含まれています。
シンプルなバッテリー電圧監視
// reTerminal E Series - Simple Battery Voltage Reading
// Serial configuration
#define SERIAL_RX 44
#define SERIAL_TX 43
// Battery monitoring pins
#define BATTERY_ADC_PIN 1 // GPIO1 - Battery voltage ADC
#define BATTERY_ENABLE_PIN 21 // GPIO21 - Battery monitoring enable
void setup() {
// Initialize serial
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) {
delay(10);
}
Serial1.println("Battery Voltage Monitor");
// Configure battery monitoring enable pin
pinMode(BATTERY_ENABLE_PIN, OUTPUT);
digitalWrite(BATTERY_ENABLE_PIN, HIGH); // Enable battery monitoring
// Configure ADC
analogReadResolution(12); // 12-bit resolution
analogSetPinAttenuation(BATTERY_ADC_PIN, ADC_11db);
delay(100); // Allow circuit to stabilize
}
void loop() {
// Enable battery monitoring
digitalWrite(BATTERY_ENABLE_PIN, HIGH);
delay(5);
// Read voltage in millivolts
int mv = analogReadMilliVolts(BATTERY_ADC_PIN);
// Disable battery monitoring
digitalWrite(BATTERY_ENABLE_PIN, LOW);
// Calculate actual battery voltage (2x due to voltage divider)
float batteryVoltage = (mv / 1000.0) * 2;
// Print voltage
Serial1.print("Battery: ");
Serial1.print(batteryVoltage, 2);
Serial1.println(" V");
delay(2000);
}
コード説明:
- GPIO1はADCを通じて分圧されたバッテリー電圧を読み取ります
- GPIO21はバッテリー監視回路を有効にします
- 電圧分圧器により、実際のバッテリー電圧は測定電圧の2倍になります
- フル充電されたLiPoバッテリーでは、約4.2Vが期待されます
- バッテリーが低下すると、電圧は約3.3Vまで下がります
期待される出力
Battery Voltage Monitor
Battery: 4.18 V
Battery: 4.19 V
Battery: 4.18 V
MicroSDカードの使用
デジタルフォトフレームやデータロギングなど、追加のストレージが必要なアプリケーションのために、reTerminal Eシリーズには MicroSDカードスロットが含まれています。
デバイスをデジタルフォトフレームとして使用する予定がある場合や、追加のストレージが必要な場合は、microSDカードを挿入してください。

reTerminal Eシリーズは、Fat32ファイルシステムでフォーマットされた最大64GBまでのMicroSDカードのみをサポートしています。
基本的なSDカード操作:ファイルの一覧表示
この例では、SDカードを初期化し、挿入または取り外しを検出し、ルートディレクトリ内のすべてのファイルとディレクトリを一覧表示する方法を示します。このコードはreTerminal E1001とreTerminal E1002の両方で同じです。
以下のコードをArduino IDEスケッチにコピーしてください。
#include <SD.h>
#include <SPI.h>
// SD Card Pin Definitions
#define SD_EN_PIN 16 // Power enable for the SD card slot
#define SD_DET_PIN 15 // Card detection pin
#define SD_CS_PIN 14 // Chip Select for the SD card
#define SD_MOSI_PIN 9 // Shared with ePaper Display
#define SD_MISO_PIN 8
#define SD_SCK_PIN 7 // Shared with ePaper Display
// Serial configuration for reTerminal E Series
#define SERIAL_RX 44
#define SERIAL_TX 43
// Use the HSPI bus for the SD card to avoid conflict with other peripherals
SPIClass spiSD(HSPI);
// Global variables to track SD card state
bool sdMounted = false;
bool lastCardPresent = false;
unsigned long lastCheckMs = 0;
const unsigned long checkIntervalMs = 1000; // Check for card changes every second
// Checks if a card is physically inserted.
// The detection pin is LOW when a card is present.
bool isCardInserted() {
return digitalRead(SD_DET_PIN) == LOW;
}
// Helper function to print indentation for directory listing
void printIndent(uint8_t level) {
for (uint8_t i = 0; i < level; ++i) {
Serial1.print(" ");
}
}
// Recursively lists files and directories
void listDir(File dir, uint8_t level) {
while (true) {
File entry = dir.openNextFile();
if (!entry) {
// No more entries in this directory
break;
}
printIndent(level);
if (entry.isDirectory()) {
Serial1.print("[DIR] ");
Serial1.println(entry.name());
// Recurse into the subdirectory
listDir(entry, level + 1);
} else {
// It's a file, print its name and size
Serial1.print("[FILE] ");
Serial1.print(entry.name());
Serial1.print(" ");
Serial1.print(entry.size());
Serial1.println(" bytes");
}
entry.close();
}
}
// Opens the root directory and starts the listing process
void listRoot() {
File root = SD.open("/");
if (!root) {
Serial1.println("[SD] Failed to open root directory.");
return;
}
if (!root.isDirectory()) {
Serial1.println("[SD] Root is not a directory.");
root.close();
return;
}
Serial1.println("[SD] Listing files in /");
listDir(root, 0);
root.close();
}
// Initializes the SPI bus and mounts the SD card
bool mountSD() {
// Enable power to the SD card slot
pinMode(SD_EN_PIN, OUTPUT);
digitalWrite(SD_EN_PIN, HIGH);
delay(5);
// Initialize the HSPI bus with the correct pins for the SD card
spiSD.end(); // Guard against repeated begin calls
spiSD.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
// Attempt to mount the SD card file system
if (!SD.begin(SD_CS_PIN, spiSD)) {
Serial1.println("[SD] MicroSD initialization failed. Check card formatting.");
return false;
}
Serial1.println("[SD] MicroSD mounted successfully.");
return true;
}
// Unmounts the SD card by releasing the SPI bus
void unmountSD() {
SD.end();
spiSD.end();
Serial1.println("[SD] MicroSD unmounted.");
}
void setup() {
// Start the secondary serial port for output
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) {
delay(10); // Wait for Serial1 to be ready
}
// Set up the card detection pin with an internal pull-up resistor
pinMode(SD_DET_PIN, INPUT_PULLUP);
// Set up the power enable pin
pinMode(SD_EN_PIN, OUTPUT);
digitalWrite(SD_EN_PIN, HIGH);
// Check for a card at startup
lastCardPresent = isCardInserted();
if (lastCardPresent) {
sdMounted = mountSD();
if (sdMounted) {
listRoot(); // If mounted, list files
}
} else {
Serial1.println("[SD] No card detected at startup. Please insert a card.");
}
}
void loop() {
// Periodically check for card insertion or removal without blocking the loop
unsigned long now = millis();
if (now - lastCheckMs >= checkIntervalMs) {
lastCheckMs = now;
bool present = isCardInserted();
if (present != lastCardPresent) {
lastCardPresent = present; // Update the state
if (present) {
Serial1.println("\n[SD] Card inserted.");
if (!sdMounted) {
sdMounted = mountSD();
}
if (sdMounted) {
listRoot(); // List files upon insertion
}
} else {
Serial1.println("\n[SD] Card removed.");
if (sdMounted) {
unmountSD();
sdMounted = false;
}
}
}
}
// You can place other non-blocking code here
}
コード説明
- ピン定義: コードは、MicroSDカードスロットに使用されるGPIOピンの定義から始まります。SPIピン(
MOSI
、SCK
)はe-paperディスプレイと共有されていますが、独立したチップセレクト(SD_CS_PIN
)と専用のSPIインスタンス(spiSD
)により、それらを独立して使用できることに注意してください。 - SPI初期化: ESP32の2番目のハードウェアSPIコントローラー(HSPI)を使用するために、新しいSPIオブジェクト
spiSD(HSPI)
をインスタンス化します。これは他のSPIデバイスとの競合を避けるためのベストプラクティスです。 - カード検出:
isCardInserted()
関数はSD_DET_PIN
を読み取ります。reTerminalハードウェアでは、カードが挿入されているときにこのピンはLOWにプルされます。 - マウント/アンマウント:
mountSD()
関数はカードへの電源を有効にし、正しいピンでHSPIバスを設定し、SD.begin()
を呼び出してファイルシステムを初期化します。unmountSD()
はリソースを解放します。 - ファイル一覧:
listRoot()
はルートディレクトリ(/
)を開き、listDir()
はファイルシステムを再帰的に走査し、すべてのファイルとディレクトリの名前を出力する再帰関数です。 setup()
: 出力用にSerial1
を初期化し、カード検出ピンを設定し、デバイスの電源投入時にカードがすでに挿入されているかどうかを確認する初期チェックを実行します。loop()
: カードを常時チェックする代わりに、コードはノンブロッキングタイマー(millis()
)を使用して、1秒に1回カードの状態変化をチェックします。変化が検出された場合(カードの挿入または取り外し)、カードをマウントまたはアンマウントし、シリアルモニターにステータスを出力します。
期待される結果
- コードをreTerminalにアップロードします。
- Arduino IDEのシリアルモニター(Tools > Serial Monitor)を開きます。
- ボーレートが115200に設定されていることを確認します。
以下のアクションに対応する出力が表示されます:
- カードなしでの起動時: モニターに
[SD] No card detected at startup...
が出力されます - カードを挿入したとき: モニターに
[SD] Card inserted.
が出力され、続いてカード上のすべてのファイルとディレクトリの完全な一覧が表示されます。 - カードを取り外したとき: モニターに
[SD] Card removed.
が出力されます
[FILE] live.0.shadowIndexGroups 6 bytes
[FILE] reverseStore.updates 1 bytes
[DIR] journals.repair
[FILE] Cab.modified 0 bytes
[FILE] live.1.indexPositionTable 8192 bytes
[FILE] live.1.indexTermIds 8192 bytes
[FILE] tmp.spotlight.loc 2143 bytes
[FILE] live.1.shadowIndexTermIds 624 bytes
[FILE] live.1.indexArrays 65536 bytes
[FILE] live.1.shadowIndexArrays 65536 bytes
[FILE] live.1.indexHead 4096 bytes
[FILE] live.1.indexPostings 4096 bytes
高度な例:SDカードからのBMP画像表示
この包括的な例は、前のセクションの機能を組み合わせたものです。MicroSDカードからBitmap(.bmp
)画像ファイルを読み取り、reTerminalのe-paperスクリーンに表示するプログラムを作成します。これは、デバイスの実用的で現実的なアプリケーションを実演します。
プログラムは、SDカードのルートディレクトリにあるtest.bmp
という名前のファイルを探します。
準備
コードを実行する前に、MicroSDカードと画像ファイルの両方を正しく準備する必要があります。これは、画像が正しく表示されることを確実にするための最も重要なステップです。
1. MicroSDカードのフォーマット
MicroSDカード(64GB以下を推奨)を準備し、FAT32ファイルシステムを使用してフォーマットします。
2. 画像ファイルの準備
画像を準備する方法は、reTerminalモデルによって若干異なります。お使いのデバイスに合ったガイドに従ってください。
- For reTerminal E1001 (B&W Screen)
- For reTerminal E1002 (Color Screen)
白黒スクリーンは白と黒のピクセルのみを表示できます。私たちのコードはカラー画像をリアルタイムでグレースケールに変換できますが、コンピューター上で画像を高品質のグレースケール画像に事前変換することで、はるかに良いコントラストと詳細を得ることができます。
-
画像のリサイズ: 画像を800x480ピクセルにリサイズします。
-
グレースケールに変換(推奨): 画像エディターで、まず画像をグレースケールに変換します。GIMPでは:
- メニュー**Colors > Desaturate > Desaturate...**に移動します。最良の結果を得るために「Luminosity」などのモードを選択します。
-
標準BMPとして保存: カラースクリーンガイドと同じ手順に従ってファイルを保存します。画像がグレースケールであっても、24ビットBMPとして保存することで、コードとの最大限の互換性が確保されます。
- **File > Export As...**に移動し、
test.bmp
と名前を付けます。 - エクスポートダイアログで、Advanced Optionsの下で**"24 bits: R8 G8 B8"**を選択します。
- Exportをクリックします。
- **File > Export As...**に移動し、
-
SDカードにコピー: 最終的な
test.bmp
ファイルをMicroSDカードのルートディレクトリにコピーします。
カラースクリーンは6色を表示できます:黒、白、赤、黄、青、緑。提供されたコードには、ソース画像の任意の色をスクリーン上で利用可能な最適な色にインテリジェントにマッピングする「最近色」アルゴリズムが含まれています。最適な結果を得るために、以下の手順に従ってください:
-
画像のリサイズ: 任意の画像エディターを使用して、画像を800x480ピクセルにリサイズします。
-
標準BMPとして保存: コードは非圧縮の24ビットまたは32ビットBMPファイルを読み取るように設計されています。プロフェッショナルな画像エディターを使用することが、フォーマットが正しいことを確実にする最良の方法です。無料でオープンソースのソフトウェアGIMPをお勧めします:
- リサイズした画像をGIMPで開きます。
- メニュー**File > Export As...**に移動します。
- ファイルに
test.bmp
と名前を付け、Exportをクリックします。 - 表示される「Export Image as BMP」ダイアログで、Advanced Optionsを展開します。
- **"24 bits: R8 G8 B8"**を選択します。これは最も互換性のある非圧縮フォーマットです。
- Exportをクリックします。
-
SDカードにコピー: 最終的な
test.bmp
ファイルをMicroSDカードのルートディレクトリにコピーします。
テスト用に既製の画像を使用したい場合は、GxEPD2が提供するサンプル画像を使用できます。
コード
これは最終的な検証済みコードです。必要なすべてのチェックと高度な色マッチングアルゴリズムが含まれています。E1001(白黒)の場合はEPD_SELECT
マクロを0
に、E1002(カラー)の場合は1
に設定するだけです。
- For reTerminal E1001 (B&W Screen)
- For reTerminal E1002 (カラー画面)
#include <SD.h>
#include <SPI.h>
#include <GxEPD2_BW.h>
#include <GxEPD2_7C.h>
#include <cmath>
// === Pin Definitions ===
// ePaper Display
#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
// SD Card
#define SD_EN_PIN 16
#define SD_DET_PIN 15
#define SD_CS_PIN 14
#define SD_MISO_PIN 8
// Serial Port
#define SERIAL_RX 44
#define SERIAL_TX 43
// File to display
const char* BMP_FILENAME = "/test.bmp";
// === ePaper Driver Selection ===
// 0: reTerminal E1001 (7.5'' B&W)
// 1: reTerminal E1002 (7.3'' Color)
#define EPD_SELECT 1
#if (EPD_SELECT == 0)
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7
#elif (EPD_SELECT == 1)
#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01
#endif
// For displays with RAM limitations
#define MAX_DISPLAY_BUFFER_SIZE 16000
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
// === Global Objects ===
SPIClass hspi(HSPI);
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)>
display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS_PIN, /*DC=*/EPD_DC_PIN, /*RST=*/EPD_RES_PIN, /*BUSY=*/EPD_BUSY_PIN));
// === BMP Drawing Function ===
// Helper functions to read values from the BMP file
uint16_t read16(File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
#if (EPD_SELECT == 1)
// Define the RGB values for the 6 available e-paper colors
const uint8_t palette[][3] = {
{ 0, 0, 0}, // 0: Black
{255, 255, 255}, // 1: White
{ 0, 255, 0}, // 2: Green
{ 0, 0, 255}, // 3: Blue
{255, 0, 0}, // 4: Red
{255, 255, 0}, // 5: Yellow
};
// Define the corresponding GxEPD2 color codes
const uint16_t epaper_colors[] = {
GxEPD_BLACK,
GxEPD_WHITE,
GxEPD_GREEN,
GxEPD_BLUE,
GxEPD_RED,
GxEPD_YELLOW,
};
const int num_colors = sizeof(palette) / sizeof(palette[0]);
// This function finds the closest e-paper color for a given RGB color
uint16_t findNearestColor(uint8_t r, uint8_t g, uint8_t b) {
long min_dist_sq = -1;
int best_color_index = 0;
for (int i = 0; i < num_colors; i++) {
long dr = r - palette[i][0];
long dg = g - palette[i][1];
long db = b - palette[i][2];
long dist_sq = dr * dr + dg * dg + db * db;
if (min_dist_sq == -1 || dist_sq < min_dist_sq) {
min_dist_sq = dist_sq;
best_color_index = i;
}
}
return epaper_colors[best_color_index];
}
#endif
// This function reads a BMP file and draws it to the screen.
// It includes robust error checking and a color-matching algorithm.
void drawBmp(const char *filename, int16_t x, int16_t y) {
File bmpFile;
int32_t bmpWidth, bmpHeight;
uint16_t bmpDepth;
uint32_t bmpImageoffset;
bool flip = true;
if ((x >= display.width()) || (y >= display.height())) return;
Serial1.print("Loading image '");
Serial1.print(filename);
Serial1.println("'");
bmpFile = SD.open(filename, FILE_READ);
if (!bmpFile) {
Serial1.println("File not found");
return;
}
if (read16(bmpFile) != 0x4D42) {
Serial1.println("Not a valid BMP file");
bmpFile.close();
return;
}
read32(bmpFile);
read32(bmpFile);
bmpImageoffset = read32(bmpFile);
read32(bmpFile);
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if (read16(bmpFile) != 1) {
Serial1.println("Unsupported BMP format (planes)");
bmpFile.close();
return;
}
bmpDepth = read16(bmpFile);
uint32_t compression = read32(bmpFile);
if (compression != 0) {
if (compression == 3) {
Serial1.println("Error: BMP file uses BI_BITFIELDS compression.");
Serial1.println("This example only supports uncompressed BMPs.");
Serial1.println("Please re-save the image with standard R8G8B8 (24-bit) or A8R8G8B8 (32-bit) format.");
} else {
Serial1.printf("Unsupported BMP format. Depth: %d, Compression: %d\n", bmpDepth, compression);
}
bmpFile.close();
return;
}
if (bmpDepth != 24 && bmpDepth != 32) {
Serial1.printf("Unsupported BMP bit depth: %d. Only 24-bit and 32-bit are supported.\n", bmpDepth);
bmpFile.close();
return;
}
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
Serial1.printf("Image: %d x %d, %d-bit\n", bmpWidth, bmpHeight, bmpDepth);
display.setPartialWindow(x, y, bmpWidth, bmpHeight);
uint8_t bytesPerPixel = bmpDepth / 8;
uint32_t rowSize = (bmpWidth * bytesPerPixel + 3) & ~3;
uint8_t sdbuffer[rowSize];
display.firstPage();
do {
for (int16_t row = 0; row < bmpHeight; row++) {
uint32_t rowpos = flip ? (bmpImageoffset + (bmpHeight - 1 - row) * rowSize) : (bmpImageoffset + row * rowSize);
bmpFile.seek(rowpos);
bmpFile.read(sdbuffer, rowSize);
for (int16_t col = 0; col < bmpWidth; col++) {
uint8_t b = sdbuffer[col * bytesPerPixel];
uint8_t g = sdbuffer[col * bytesPerPixel + 1];
uint8_t r = sdbuffer[col * bytesPerPixel + 2];
uint16_t GxEPD_Color;
#if (EPD_SELECT == 1) // Color Display
GxEPD_Color = findNearestColor(r, g, b);
#else // Black and White Display
if ((r * 0.299 + g * 0.587 + b * 0.114) < 128) GxEPD_Color = GxEPD_BLACK;
else GxEPD_Color = GxEPD_WHITE;
#endif
display.drawPixel(x + col, y + row, GxEPD_Color);
}
}
} while (display.nextPage());
bmpFile.close();
Serial1.println("Done!");
}
void setup() {
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) delay(10);
delay(2000); // A small delay to allow Serial Monitor to connect
Serial1.println("--- ePaper SD Card BMP Example ---");
// Initialize shared SPI bus
hspi.begin(EPD_SCK_PIN, SD_MISO_PIN, EPD_MOSI_PIN, -1);
// Initialize Display
display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
display.init(115200);
display.setRotation(0);
display.fillScreen(GxEPD_WHITE);
display.hibernate(); // Power down display until needed
// Initialize SD Card
pinMode(SD_EN_PIN, OUTPUT);
digitalWrite(SD_EN_PIN, HIGH);
pinMode(SD_DET_PIN, INPUT_PULLUP);
delay(100);
if (digitalRead(SD_DET_PIN) == HIGH) {
Serial1.println("No SD card detected. Please insert a card.");
display.firstPage();
do {
display.setCursor(10, 20);
display.print("No SD card detected.");
} while(display.nextPage());
return;
}
Serial1.println("SD card detected, attempting to mount...");
if (!SD.begin(SD_CS_PIN, hspi)) {
Serial1.println("SD Card Mount Failed!");
display.firstPage();
do {
display.setCursor(10, 20);
display.print("SD Card Mount Failed!");
} while(display.nextPage());
return;
}
Serial1.println("SD card mounted successfully.");
// Draw the BMP from the SD card
drawBmp(BMP_FILENAME, 0, 0);
display.hibernate(); // Power down display after drawing
}
void loop() {
// Nothing to do here for this example
}
#include <SD.h>
#include <SPI.h>
#include <GxEPD2_BW.h>
#include <GxEPD2_7C.h>
#include <cmath>
// === Pin Definitions ===
// ePaper Display
#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
// SD Card
#define SD_EN_PIN 16
#define SD_DET_PIN 15
#define SD_CS_PIN 14
#define SD_MISO_PIN 8
// Serial Port
#define SERIAL_RX 44
#define SERIAL_TX 43
// File to display
const char* BMP_FILENAME = "/test.bmp";
// === ePaper Driver Selection ===
// 0: reTerminal E1001 (7.5'' B&W)
// 1: reTerminal E1002 (7.3'' Color)
#define EPD_SELECT 0
#if (EPD_SELECT == 0)
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7
#elif (EPD_SELECT == 1)
#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01
#endif
// For displays with RAM limitations
#define MAX_DISPLAY_BUFFER_SIZE 16000
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
// === Global Objects ===
SPIClass hspi(HSPI);
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)>
display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS_PIN, /*DC=*/EPD_DC_PIN, /*RST=*/EPD_RES_PIN, /*BUSY=*/EPD_BUSY_PIN));
// === BMP Drawing Function ===
// Helper functions to read values from the BMP file
uint16_t read16(File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
#if (EPD_SELECT == 1)
// Define the RGB values for the 6 available e-paper colors
const uint8_t palette[][3] = {
{ 0, 0, 0}, // 0: Black
{255, 255, 255}, // 1: White
{ 0, 255, 0}, // 2: Green
{ 0, 0, 255}, // 3: Blue
{255, 0, 0}, // 4: Red
{255, 255, 0}, // 5: Yellow
};
// Define the corresponding GxEPD2 color codes
const uint16_t epaper_colors[] = {
GxEPD_BLACK,
GxEPD_WHITE,
GxEPD_GREEN,
GxEPD_BLUE,
GxEPD_RED,
GxEPD_YELLOW,
};
const int num_colors = sizeof(palette) / sizeof(palette[0]);
// This function finds the closest e-paper color for a given RGB color
uint16_t findNearestColor(uint8_t r, uint8_t g, uint8_t b) {
long min_dist_sq = -1;
int best_color_index = 0;
for (int i = 0; i < num_colors; i++) {
long dr = r - palette[i][0];
long dg = g - palette[i][1];
long db = b - palette[i][2];
long dist_sq = dr * dr + dg * dg + db * db;
if (min_dist_sq == -1 || dist_sq < min_dist_sq) {
min_dist_sq = dist_sq;
best_color_index = i;
}
}
return epaper_colors[best_color_index];
}
#endif
// This function reads a BMP file and draws it to the screen.
// It includes robust error checking and a color-matching algorithm.
void drawBmp(const char *filename, int16_t x, int16_t y) {
File bmpFile;
int32_t bmpWidth, bmpHeight;
uint16_t bmpDepth;
uint32_t bmpImageoffset;
bool flip = true;
if ((x >= display.width()) || (y >= display.height())) return;
Serial1.print("Loading image '");
Serial1.print(filename);
Serial1.println("'");
bmpFile = SD.open(filename, FILE_READ);
if (!bmpFile) {
Serial1.println("File not found");
return;
}
if (read16(bmpFile) != 0x4D42) {
Serial1.println("Not a valid BMP file");
bmpFile.close();
return;
}
read32(bmpFile);
read32(bmpFile);
bmpImageoffset = read32(bmpFile);
read32(bmpFile);
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if (read16(bmpFile) != 1) {
Serial1.println("Unsupported BMP format (planes)");
bmpFile.close();
return;
}
bmpDepth = read16(bmpFile);
uint32_t compression = read32(bmpFile);
if (compression != 0) {
if (compression == 3) {
Serial1.println("Error: BMP file uses BI_BITFIELDS compression.");
Serial1.println("This example only supports uncompressed BMPs.");
Serial1.println("Please re-save the image with standard R8G8B8 (24-bit) or A8R8G8B8 (32-bit) format.");
} else {
Serial1.printf("Unsupported BMP format. Depth: %d, Compression: %d\n", bmpDepth, compression);
}
bmpFile.close();
return;
}
if (bmpDepth != 24 && bmpDepth != 32) {
Serial1.printf("Unsupported BMP bit depth: %d. Only 24-bit and 32-bit are supported.\n", bmpDepth);
bmpFile.close();
return;
}
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
Serial1.printf("Image: %d x %d, %d-bit\n", bmpWidth, bmpHeight, bmpDepth);
display.setPartialWindow(x, y, bmpWidth, bmpHeight);
uint8_t bytesPerPixel = bmpDepth / 8;
uint32_t rowSize = (bmpWidth * bytesPerPixel + 3) & ~3;
uint8_t sdbuffer[rowSize];
display.firstPage();
do {
for (int16_t row = 0; row < bmpHeight; row++) {
uint32_t rowpos = flip ? (bmpImageoffset + (bmpHeight - 1 - row) * rowSize) : (bmpImageoffset + row * rowSize);
bmpFile.seek(rowpos);
bmpFile.read(sdbuffer, rowSize);
for (int16_t col = 0; col < bmpWidth; col++) {
uint8_t b = sdbuffer[col * bytesPerPixel];
uint8_t g = sdbuffer[col * bytesPerPixel + 1];
uint8_t r = sdbuffer[col * bytesPerPixel + 2];
uint16_t GxEPD_Color;
#if (EPD_SELECT == 1) // Color Display
GxEPD_Color = findNearestColor(r, g, b);
#else // Black and White Display
if ((r * 0.299 + g * 0.587 + b * 0.114) < 128) GxEPD_Color = GxEPD_BLACK;
else GxEPD_Color = GxEPD_WHITE;
#endif
display.drawPixel(x + col, y + row, GxEPD_Color);
}
}
} while (display.nextPage());
bmpFile.close();
Serial1.println("Done!");
}
void setup() {
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
while (!Serial1) delay(10);
delay(2000); // A small delay to allow Serial Monitor to connect
Serial1.println("--- ePaper SD Card BMP Example ---");
// Initialize shared SPI bus
hspi.begin(EPD_SCK_PIN, SD_MISO_PIN, EPD_MOSI_PIN, -1);
// Initialize Display
display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
display.init(115200);
display.setRotation(0);
display.fillScreen(GxEPD_WHITE);
display.hibernate(); // Power down display until needed
// Initialize SD Card
pinMode(SD_EN_PIN, OUTPUT);
digitalWrite(SD_EN_PIN, HIGH);
pinMode(SD_DET_PIN, INPUT_PULLUP);
delay(100);
if (digitalRead(SD_DET_PIN) == HIGH) {
Serial1.println("No SD card detected. Please insert a card.");
display.firstPage();
do {
display.setCursor(10, 20);
display.print("No SD card detected.");
} while(display.nextPage());
return;
}
Serial1.println("SD card detected, attempting to mount...");
if (!SD.begin(SD_CS_PIN, hspi)) {
Serial1.println("SD Card Mount Failed!");
display.firstPage();
do {
display.setCursor(10, 20);
display.print("SD Card Mount Failed!");
} while(display.nextPage());
return;
}
Serial1.println("SD card mounted successfully.");
// Draw the BMP from the SD card
drawBmp(BMP_FILENAME, 0, 0);
display.hibernate(); // Power down display after drawing
}
void loop() {
// Nothing to do here for this example
}
動作原理
setup()
:setup
関数は、必要なハードウェアを順次初期化します:デバッグ用のシリアルポート、共有SPIバス、e-paperディスプレイ、最後にSDカード。すべての初期化が成功すると、メインタスクを実行するためにdrawBmp()
を一度呼び出します。drawBmp()
: これがコア関数です。BMPファイルを開き、ヘッダーを解析してその寸法とプロパティを読み取り、重要な検証チェックを実行します。特にサポートされていない圧縮タイプをチェックし、見つかった場合は有用なエラーメッセージを提供します。- 描画ループ: この関数は、SDカードから画像を一行ずつ読み取ります。行内の各ピクセルについて、赤、緑、青の色値を抽出します。
- 色処理: ここで
EPD_SELECT
マクロに基づいてロジックが分岐します:- カラー用(E1002):
findNearestColor(r, g, b)
を呼び出します。この関数は、ピクセルの色と画面のパレットの6色それぞれとの「距離」を計算します。最小距離のパレット色を返し、可能な限り正確な色表現を保証します。 - 白黒用(E1001): 標準的な輝度式(
r * 0.299 + g * 0.587 + b * 0.114
)を使用してRGB色を単一の明度値に変換します。この値が閾値(128)を下回る場合、ピクセルは黒として描画され、そうでなければ白として描画されます。
- カラー用(E1002):
アップロードと実行
- Arduino IDEで、正しいボード(
XIAO_ESP32S3
)が選択されていることを確認してください。 - コードの上部にある
EPD_SELECT
マクロを、reTerminal E1002の場合は1
に、E1001の場合は0
に設定してください。 - 準備したMicroSDカードをreTerminalに挿入してください。
- コードをアップロードしてください。
- ボーレート
115200
でシリアルモニターを開いてください。進行状況ログが表示され、しばらくすると画像がe-paperディスプレイにレンダリングされます。
画面のリフレッシュ速度が遅い場合があり、プログラムをアップロードしてから2〜3分経つまで画面が応答しないことがあります。
トラブルシューティング
Q1: 上記のコードを実行してもreTerminalのePaperディスプレイに何も表示されない、またはリフレッシュされないのはなぜですか?
この問題は、reTerminalにMicroSDカードを挿入している場合に発生する可能性があります。理由は、MicroSDカードとePaperディスプレイがreTerminal上で同じSPIバスを共有しているためです。MicroSDカードが挿入されているが、そのイネーブル(チップセレクト)ピンが適切に管理されていない場合、SPIバス上で競合が発生する可能性があります。具体的には、MicroSDカードがBUSYラインをハイに保持し、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ボーに変更してください。

技術サポート & 製品ディスカッション
弊社製品をお選びいただき、ありがとうございます!弊社製品での体験が可能な限りスムーズになるよう、さまざまなサポートを提供しています。異なる好みやニーズに対応するため、複数のコミュニケーションチャンネルを提供しています。