Arduino 中 reTerminal E 系列 ePaper 显示屏入门指南

介绍
reTerminal E 系列代表了 Seeed Studio 在工业 HMI 解决方案方面的最新进展,以 ESP32-S3 作为主控制器并集成了 ePaper 显示屏。本指南将引导您使用 Arduino IDE 对 reTerminal E 系列设备上的 ePaper 显示屏进行编程,使您能够创建具有出色可视性和超低功耗的自定义界面和应用程序。
所需材料
要完成本教程,请准备以下 reTerminal E 系列设备之一:
环境准备
要使用 Arduino 对 reTerminal E 系列电子纸显示屏进行编程,您需要设置支持 ESP32 的 Arduino IDE。
如果这是您第一次使用 Arduino,我们强烈建议您参考 Arduino 入门指南。
Arduino IDE 设置
步骤 1. 下载并安装 Arduino IDE,然后启动 Arduino 应用程序。

步骤 2. 向 Arduino IDE 添加 ESP32 开发板支持。
在 Arduino IDE 中,转到 文件 > 首选项,并将以下 URL 添加到"附加开发板管理器网址"字段中:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
步骤 3. 安装 ESP32 开发板包。
导航到 工具 > 开发板 > 开发板管理器,搜索 "esp32" 并安装 Espressif Systems 的 ESP32 包。
步骤 4. 选择正确的开发板。
转到 工具 > 开发板 > ESP32 Arduino 并选择 XIAO_ESP32S3。
步骤 5. 使用 USB-C 线缆将您的 reTerminal E 系列 ePaper 显示器连接到计算机。
步骤 6. 从 工具 > 端口 选择正确的端口。
ePaper 显示器编程
reTerminal E1001 配备 7.5 英寸黑白 ePaper 显示器,而 reTerminal E1002 配备 7.3 英寸全彩 ePaper 显示器。两种显示器都在各种照明条件下提供出色的可见性,功耗极低,非常适合需要始终开启显示且功耗最小的工业应用。
使用 Seeed_GFX 库
为了控制 ePaper 显示器,我们将使用 Seeed_GFX 库,该库为各种 Seeed Studio 显示设备提供全面支持。
步骤 1. 从 GitHub 下载 Seeed_GFX 库:
步骤 2. 通过在 Arduino IDE 中添加 ZIP 文件来安装库。转到 项目 > 加载库 > 添加 .ZIP 库 并选择下载的 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 库打开示例代码:文件 > 示例 > Seeed_GFX > ePaper > Basic > HelloWorld
步骤 2. 在与您的代码相同的文件夹中创建一个名为 driver.h
的新文件。您可以通过点击 Arduino IDE 中的箭头按钮并选择"新建标签页",然后将其命名为 driver.h
来完成此操作。

步骤 3. 转到 Seeed GFX 配置工具 并从设备列表中选择 reTerminal E1001。

步骤 4. 复制生成的配置代码并将其粘贴到 driver.h
文件中。代码应如下所示:
#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)
步骤 5. 将代码上传到您的 reTerminal E1001。您应该会看到显示屏显示各种图形,包括线条、文本和形状,展示基本的绘图功能。

编程 reTerminal E1002(7.3英寸全彩电子纸)
全彩电子纸显示屏支持红色、黑色和白色,可以实现更丰富的视觉界面。
步骤 1. 从 Seeed_GFX 库中打开彩色示例代码:File > Examples > Seeed_GFX > ePaper > Colorful > HelloWorld
步骤 2. 在与您的代码相同的文件夹中创建一个名为 driver.h
的新文件,按照之前相同的步骤进行。

步骤 3. 前往 Seeed GFX 配置工具 并从设备列表中选择 reTerminal E1002。

步骤 4. 复制生成的配置代码并将其粘贴到 driver.h
文件中。代码应该如下所示:
#define BOARD_SCREEN_COMBO 521 // reTerminal E1002 (UC8179C)
步骤 5. 将代码上传到您的 reTerminal E1002。显示屏将显示彩色图形,展示 ePaper 显示屏的全彩功能。

使用 GxEPD2 库
除了 Seeed_GFX,您还可以使用 GxEPD2
库来驱动 reTerminal 的 ePaper 显示屏。GxEPD2
是一个功能强大且流行的库,支持广泛的电子纸显示屏。
安装 GxEPD2 库
为了确保您拥有最新的功能和设备支持,最好从其 GitHub 仓库手动安装 GxEPD2
库。
步骤 1. 前往 GxEPD2 GitHub 仓库。点击"Code"按钮,然后选择"Download ZIP"将库保存到您的计算机。
步骤 2. 在 Arduino IDE 中,从下载的文件安装库。导航到 Sketch > Include Library > Add .ZIP Library... 并选择您刚刚下载的 ZIP 文件。
步骤 3. GxEPD2
库需要 Adafruit GFX Library
才能正常工作,您也必须安装此库。最简单的方法是通过库管理器:前往 Tools > Manage Libraries...,搜索"Adafruit GFX Library",然后点击"Install"。
虽然 GxEPD2
在 Arduino 库管理器中可用以便于使用,但那里的版本通常可能过时。GitHub 仓库是最新版本的权威来源,包含最新的功能、错误修复和对最新电子纸显示屏的支持。因此,直接从 GitHub 下载库是确保您拥有最新代码的推荐方法。
- Programming reTerminal E1001
- Programming reTerminal E1002
编程 reTerminal E1001(黑白屏幕)
以下是使用 GxEPD2
库在 reTerminal E1001 的黑白 ePaper 显示屏上显示"Hello World!"的示例代码。将 EPD_SELECT
设置为 0
以选择 E1001 的驱动程序。
#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 英寸全彩电子纸显示屏选择适当的驱动程序。其余代码保持不变。
#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 系列有一个板载 LED,可以通过 GPIO6 进行控制。请注意 LED 逻辑是反向的(LOW = 开启,HIGH = 关闭)。
// 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()
: 简单的开/关控制,用于基本的蜂鸣声tone(pin, frequency, duration)
: 生成特定频率,用于旋律或警报noTone(pin)
: 停止音调生成
常见警报模式:
- 单次蜂鸣: 确认
- 双次蜂鸣: 警告
- 三次蜂鸣: 错误
- 连续蜂鸣: 关键警报
用户按钮
reTerminal E 系列配备三个用户可编程按钮,可用于各种控制目的。本节演示如何使用 Arduino 读取按钮状态并响应按钮按压。
reTerminal E 系列有三个连接到 ESP32-S3 的按钮:
- 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 库管理器安装两个库(工具 > 管理库...):
- 搜索并安装 "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("%");
}
}
设置函数:
- 串口初始化:使用
Serial1
,配置 reTerminal E 系列专用的引脚 44(RX)和 43(TX) - I2C 初始化:配置 I2C,使用引脚 19(SDA)和 20(SCL)
- 传感器初始化:调用
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 启用电池监控电路
- 由于分压器的存在,实际电池电压是测量电压的两倍
- 对于充满电的锂聚合物电池,预期电压约为 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 系列仅支持最大 64GB 的 MicroSD 卡,且需要使用 Fat32 文件系统格式化。
基本 SD 卡操作:列出文件
此示例演示如何初始化 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
)与电子纸显示屏共享,但单独的片选(SD_CS_PIN
)和专用的 SPI 实例(spiSD
)确保它们可以独立使用。 - SPI 初始化: 我们实例化一个新的 SPI 对象
spiSD(HSPI)
,以使用 ESP32 的第二个硬件 SPI 控制器(HSPI)。这是避免与其他 SPI 设备冲突的最佳实践。 - 卡检测:
isCardInserted()
函数读取SD_DET_PIN
。在 reTerminal 硬件上,当卡存在时,此引脚被拉低。 - 挂载/卸载:
mountSD()
函数启用卡的电源,使用正确的引脚配置 HSPI 总线,并调用SD.begin()
来初始化文件系统。unmountSD()
释放资源。 - 文件列表:
listRoot()
打开根目录(/
),listDir()
是一个递归函数,遍历文件系统,打印所有文件和目录的名称。 setup()
: 初始化Serial1
用于输出,配置卡检测引脚,并执行初始检查以查看设备上电时是否已插入卡。loop()
: 代码不是持续检查卡,而是使用非阻塞定时器(millis()
)每秒检查一次卡状态的变化。如果检测到变化(插入或移除卡),它会挂载或卸载卡并将状态打印到串行监视器。
预期结果
- 将代码上传到您的 reTerminal。
- 打开 Arduino IDE 的串行监视器(工具 > 串行监视器)。
- 确保波特率设置为 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卡读取位图(.bmp
)图像文件并在reTerminal的电子纸屏幕上显示。这展示了该设备的实际应用场景。
程序将在SD卡的根目录中查找名为test.bmp
的文件。
准备工作
在运行代码之前,您必须正确准备MicroSD卡和图像文件。这是确保图像正确显示的最关键步骤。
1. 格式化MicroSD卡
准备一张MicroSD卡(建议64GB或更小)并使用FAT32文件系统格式化。
2. 准备图像文件
准备图像的方法根据您的reTerminal型号略有不同。请按照与您设备匹配的指南操作。
- 适用于reTerminal E1001(黑白屏)
- 适用于reTerminal E1002(彩色屏)
黑白屏只能显示黑白像素。虽然我们的代码可以实时将彩色图像转换为灰度图像,但通过在计算机上预先将图像转换为高质量灰度图像,您将获得更好的对比度和细节。
-
调整图像大小: 将您的图片调整为800x480像素。
-
转换为灰度(推荐): 在您的图像编辑器中,首先将图像转换为灰度。在GIMP中:
- 转到菜单颜色 > 去色 > 去色...。选择"亮度"等模式以获得最佳效果。
-
保存为标准BMP: 按照彩色屏指南的相同步骤保存文件。即使图像是灰度的,将其保存为24位BMP可确保与代码的最大兼容性。
- 转到文件 > 导出为...,命名为
test.bmp
。 - 在导出对话框中,在高级选项下,选择**"24位:R8 G8 B8"**。
- 点击导出。
- 转到文件 > 导出为...,命名为
-
复制到SD卡: 将最终的
test.bmp
文件复制到MicroSD卡的根目录。
彩色屏可以显示6种颜色:黑色、白色、红色、黄色、蓝色和绿色。提供的代码包含一个"最近颜色"算法,可以智能地将源图像中的任何颜色映射到屏幕上最佳的可用颜色。为了获得最佳效果,请按照以下步骤操作:
-
调整图像大小: 使用任何图像编辑器,将您的图片调整为800x480像素。
-
保存为标准BMP: 代码设计用于读取未压缩的24位或32位BMP文件。使用专业图像编辑器是确保格式正确的最佳方法。我们推荐免费开源软件GIMP:
- 在GIMP中打开调整大小后的图像。
- 转到菜单文件 > 导出为...。
- 将文件命名为
test.bmp
并点击导出。 - 在出现的"将图像导出为BMP"对话框中,展开高级选项。
- 选择**"24位:R8 G8 B8"**。这是最兼容的未压缩格式。
- 点击导出。
-
复制到SD卡: 将最终的
test.bmp
文件复制到MicroSD卡的根目录。
如果您想使用现成的图像进行测试,可以使用GxEPD2提供的示例图像。
代码
这是最终验证的代码。它包含所有必要的检查和高级颜色匹配算法。只需将EPD_SELECT
宏设置为0
(适用于E1001黑白屏)或1
(适用于E1002彩色屏)。
- 适用于reTerminal E1001(黑白屏)
- For reTerminal E1002 (Color Screen)
#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 总线、电子纸显示屏,最后是 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
宏设置为1
(用于 reTerminal E1002)或0
(用于 E1001)。 - 将准备好的 MicroSD 卡插入 reTerminal。
- 上传代码。
- 以波特率
115200
打开串口监视器。您将看到进度日志,几分钟后,图像将在电子纸显示屏上渲染。
屏幕刷新速度可能较慢,有时屏幕在上传程序后 2~3 分钟才会响应。
故障排除
Q1:为什么 reTerminal 的电子纸显示屏在运行上述代码时不显示任何内容或不刷新?
如果您在 reTerminal 中插入了 MicroSD 卡,可能会出现此问题。原因是 MicroSD 卡和电子纸显示屏在 reTerminal 上共享同一个 SPI 总线。如果插入了 MicroSD 卡但其使能(片选)引脚没有正确管理,可能会在 SPI 总线上造成冲突。具体来说,MicroSD 卡可能会保持 BUSY 线为高电平,这会阻止电子纸显示屏正常工作——导致没有显示更新或刷新。
// 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 波特率以解决此问题。

技术支持与产品讨论
感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。