Primeros pasos con la pantalla de tinta electrónica reTerminal E Serie en Arduino

Introducción
La reTerminal E Serie representa el último avance de Seeed Studio en soluciones HMI industriales, incorporando ESP32-S3 como controlador principal y pantallas de tinta electrónica integradas. Esta guía te explicará cómo programar la pantalla de tinta electrónica en los dispositivos reTerminal E Serie usando Arduino IDE, lo que te permitirá crear interfaces y aplicaciones personalizadas con excelente visibilidad y un consumo de energía ultrabajo.
Materiales necesarios
Para completar este tutorial, prepara uno de los siguientes dispositivos reTerminal E Serie:
| reTerminal E1001 | reTerminal E1002 | reTerminal E1003 | reTerminal E1004 |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
Preparación del entorno
Para programar la pantalla de tinta electrónica reTerminal E Serie con Arduino, necesitas configurar Arduino IDE con soporte para ESP32.
Si es la primera vez que usas Arduino, te recomendamos encarecidamente que consultes Primeros pasos con Arduino.
Configuración de Arduino IDE
Paso 1. Descarga e instala el Arduino IDE y ejecuta la aplicación Arduino.

Paso 2. Añade el soporte de la placa ESP32 a Arduino IDE.
En Arduino IDE, ve a File > Preferences y añade la siguiente URL en el campo "Additional Boards Manager URLs":
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
Paso 3. Instala el paquete de la placa ESP32.
Navega a Tools > Board > Boards Manager, busca "esp32" e instala el paquete ESP32 de Espressif Systems.
Paso 4. Selecciona la placa correcta.
Ve a Tools > Board > ESP32 Arduino y selecciona XIAO_ESP32S3.
Paso 5. Conecta tu pantalla de tinta electrónica reTerminal E Serie a tu ordenador usando un cable USB-C.
Paso 6. Selecciona el puerto correcto en Tools > Port.
Programación de la pantalla de tinta electrónica
La reTerminal E1001 incorpora una pantalla de tinta electrónica en blanco y negro de 7,5 pulgadas, mientras que la reTerminal E1002 está equipada con una pantalla de tinta electrónica a todo color de 7,3 pulgadas. Ambas pantallas ofrecen una excelente visibilidad en diversas condiciones de iluminación con un consumo de energía ultrabajo, lo que las hace ideales para aplicaciones industriales que requieren pantallas siempre encendidas con un uso mínimo de energía.
Uso de la biblioteca Seeed_GFX
Para controlar la pantalla de tinta electrónica, utilizaremos la biblioteca Seeed_GFX, que proporciona compatibilidad completa con varios dispositivos de pantalla de Seeed Studio.
Paso 1. Descarga la biblioteca Seeed_GFX desde GitHub:
Paso 2. Instala la biblioteca añadiendo el archivo ZIP en Arduino IDE. Ve a Sketch > Include Library > Add .ZIP Library y selecciona el archivo ZIP descargado.
Si has instalado previamente la biblioteca TFT_eSPI, es posible que tengas que eliminarla temporalmente o cambiarle el nombre en la carpeta de bibliotecas de Arduino para evitar conflictos, ya que Seeed_GFX es un fork de TFT_eSPI con funciones adicionales para pantallas de Seeed Studio.
- Programar reTerminal E1001
- Programar reTerminal E1002
- Programming reTerminal E1003
- Programming reTerminal E1004
Programar reTerminal E1001 (pantalla de tinta electrónica en blanco y negro de 7,5 pulgadas)
Vamos a explorar un ejemplo sencillo que demuestra las operaciones básicas de dibujo en la pantalla de tinta electrónica en blanco y negro.
Paso 1. Abre el ejemplo de sketch de la biblioteca Seeed_GFX: File > Examples > Seeed_GFX > ePaper > Basic > HelloWorld
Paso 2. Crea un nuevo archivo llamado driver.h en la misma carpeta que tu sketch. Puedes hacerlo haciendo clic en el botón de flecha en Arduino IDE y seleccionando "New Tab", y luego nombrándolo driver.h.

Paso 3. Ve a la herramienta de configuración Seeed GFX y selecciona reTerminal E1001 de la lista de dispositivos.

Paso 4. Copia el código de configuración generado y pégalo en el archivo driver.h. El código debería verse así:
#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)
Paso 5. Carga el sketch en tu reTerminal E1001. Deberías ver en la pantalla varios gráficos, incluidas líneas, texto y formas que demuestran las capacidades básicas de dibujo.

Programar reTerminal E1002 (pantalla de tinta electrónica a todo color de 7,3 pulgadas)
La pantalla de tinta electrónica a todo color admite colores rojo, negro y blanco, lo que permite interfaces visualmente más ricas.
Paso 1. Abre el ejemplo de sketch en color de la biblioteca Seeed_GFX: File > Examples > Seeed_GFX > ePaper > Colorful > HelloWorld
Paso 2. Crea un nuevo archivo llamado driver.h en la misma carpeta que tu sketch, siguiendo el mismo proceso que antes.

Paso 3. Ve a la herramienta de configuración Seeed GFX y selecciona reTerminal E1002 de la lista de dispositivos.

Paso 4. Copia el código de configuración generado y pégalo en el archivo driver.h. El código debería verse así:
#define BOARD_SCREEN_COMBO 521 // reTerminal E1002 (UC8179C)
Paso 5. Carga el sketch en tu reTerminal E1002. La pantalla mostrará gráficos en color que demuestran las capacidades a todo color de la pantalla de tinta electrónica.

Programación de reTerminal E1003 (ePaper de 10,3 pulgadas)
Sigue el mismo flujo de trabajo utilizando la biblioteca Seeed_GFX para configurar y controlar el ePaper en el reTerminal E1003.
Paso 1. Abre un sketch de ejemplo de la biblioteca Seeed_GFX: File > Examples > Seeed_GFX > ePaper > Basic > HelloWorld
Paso 2. Crea un nuevo archivo llamado driver.h en la misma carpeta que tu sketch.

Paso 3. Ve a la Seeed GFX Configuration Tool y selecciona reTerminal E1003 de la lista de dispositivos.

Paso 4. Copia el código de configuración generado y pégalo en el archivo driver.h para el E1003.
#define BOARD_SCREEN_COMBO 522 // reTerminal E1003 (ED103TC2)
Paso 5. Carga el sketch en tu reTerminal E1003 para verificar las primitivas de dibujo, el renderizado de texto y los comportamientos de actualización de pantalla completa.

Programación de reTerminal E1004 (ePaper a todo color de 13,3 pulgadas)
Utiliza la biblioteca Seeed_GFX para configurar y controlar la pantalla ePaper a todo color E Ink® Spectra™ 6 en el reTerminal E1004.
Paso 1. Abre el sketch de ejemplo en color de la biblioteca Seeed_GFX: File > Examples > Seeed_GFX > ePaper > Basic > HelloWorld
Paso 2. Crea un nuevo archivo llamado driver.h en la misma carpeta que tu sketch.

Paso 3. Ve a la Seeed GFX Configuration Tool y selecciona reTerminal E1004 de la lista de dispositivos.

Paso 4. Copia el código de configuración generado y pégalo en el archivo driver.h para el E1004.
#define BOARD_SCREEN_COMBO 523 // reTerminal E1004 (T133A01)
Paso 5. Carga el sketch en tu reTerminal E1004 para verificar el renderizado de color, las primitivas de dibujo, el renderizado de texto y los comportamientos de actualización de pantalla completa.

Uso de la biblioteca GxEPD2
Además de Seeed_GFX, también puedes utilizar la biblioteca GxEPD2 para controlar la pantalla ePaper del reTerminal. GxEPD2 es una biblioteca potente y popular que admite una amplia gama de pantallas de papel electrónico.
Instalación de la biblioteca GxEPD2
Para asegurarte de tener las funciones y compatibilidad de dispositivos más recientes, es mejor instalar la biblioteca GxEPD2 manualmente desde su repositorio de GitHub.
Paso 1. Ve al repositorio de GitHub de GxEPD2. Haz clic en el botón "Code" y luego selecciona "Download ZIP" para guardar la biblioteca en tu ordenador.
Paso 2. En el IDE de Arduino, instala la biblioteca desde el archivo descargado. Navega a Sketch > Include Library > Add .ZIP Library... y selecciona el archivo ZIP que acabas de descargar.
Paso 3. La biblioteca GxEPD2 requiere la Adafruit GFX Library para funcionar, que también debes instalar. La forma más sencilla de hacerlo es a través del Library Manager: ve a Tools > Manage Libraries..., busca "Adafruit GFX Library" y haz clic en "Install".
Aunque GxEPD2 está disponible en el Arduino Library Manager por comodidad, la versión que se encuentra allí a menudo puede estar desactualizada. El repositorio de GitHub es la fuente definitiva para la versión más reciente, que incluye las funciones más nuevas, correcciones de errores y compatibilidad con las pantallas de papel electrónico más recientes. Por lo tanto, descargar la biblioteca directamente desde GitHub es el enfoque recomendado para asegurarte de tener el código más actualizado.
- Programming reTerminal E1001
- Programming reTerminal E1002
Programación de reTerminal E1001 (pantalla en blanco y negro)
Aquí tienes el código de ejemplo para mostrar "Hello World!" en la pantalla ePaper en blanco y negro del reTerminal E1001 utilizando la biblioteca GxEPD2. Establece EPD_SELECT en 0 para seleccionar el controlador para el 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() {};
Programación de reTerminal E1002 (pantalla a todo color)
Para el reTerminal E1002, simplemente necesitas cambiar el valor de EPD_SELECT a 1. Esto seleccionará el controlador apropiado para la pantalla ePaper a todo color de 7,3 pulgadas. El resto del código permanece igual.
#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() {};
Las pantallas de papel electrónico tienen una velocidad de actualización relativamente lenta (normalmente de 1 a 3 segundos para una actualización completa). Este es un comportamiento normal y es una compensación por el consumo de energía ultrabajo y la excelente visibilidad sin retroiluminación.
Rutinas de uso para el hardware de reTerminal
Ahora exploremos las funciones principales de la reTerminal E Serie con ejemplos de código de Arduino.
Control del LED
La reTerminal E Serie tiene un LED integrado que se puede controlar mediante GPIO6. Ten en cuenta que la lógica del LED está invertida (LOW = ENCENDIDO, HIGH = APAGADO).
// 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);
}
Control del zumbador
La reTerminal E Serie incluye un zumbador en GPIO7 que puede producir varios tonos y sonidos de alerta.
// 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);
}
Zumbador con tonos
#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);
}
Funciones del zumbador:
digitalWrite(): Control simple de ENCENDIDO/APAGADO para pitidos básicostone(pin, frequency, duration): Genera frecuencias específicas para melodías o alertasnoTone(pin): Detiene la generación de tonos
Patrones de alerta comunes:
- Pitido único: Confirmación
- Doble pitido: Advertencia
- Triple pitido: Error
- Continuo: Alerta crítica
Botones de usuario
La reTerminal E Serie cuenta con tres botones programables por el usuario que se pueden utilizar para varios propósitos de control. Esta sección muestra cómo leer los estados de los botones y responder a las pulsaciones usando Arduino.
La reTerminal E Serie tiene tres botones conectados al ESP32-S3:
- KEY0 (GPIO3): Botón derecho (botón verde)
- KEY1 (GPIO4): Botón central
- KEY2 (GPIO5): Botón izquierdo
Todos los botones son activos en bajo, lo que significa que leen LOW cuando se presionan y HIGH cuando se sueltan.
Ejemplo básico de lectura de botones
Este ejemplo muestra cómo detectar pulsaciones de botones e imprimir mensajes en el monitor serie.
// 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
}
Cómo funciona el código:
-
Definición de pines: Definimos constantes para el número de pin GPIO de cada botón.
-
Configuración de pines: En
setup(), configuramos cada pin de botón comoINPUT. -
Detección de botones: En
loop(), comprobamos continuamente el estado de cada botón usandodigitalRead(). Cuando se presiona un botón, el pin lee LOW. -
Eliminación de rebotes: Un simple retardo de 200 ms después de cada pulsación de botón evita múltiples detecciones de una sola pulsación debido al rebote mecánico.
-
Salida serie: Cada pulsación de botón desencadena un mensaje al monitor serie para depuración y verificación.
Paso 1. Carga el código en tu dispositivo reTerminal E Serie.
Paso 2. Abre el Monitor Serie en Arduino IDE (Tools > Serial Monitor).
Paso 3. Configura la velocidad en baudios a 115200.
Paso 4. Pulsa cada botón y observa la salida en el Monitor Serie.
Salida esperada al pulsar los botones:
=================================
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!
Sensor ambiental (SHT4x)
La reTerminal E Serie incluye un sensor integrado de temperatura y humedad SHT4x conectado mediante I2C.
Instalación de las bibliotecas necesarias
Instala dos bibliotecas mediante el Administrador de Bibliotecas de Arduino (Tools > Manage Libraries...):
- Busca e instala "Sensirion I2C SHT4x"
- Busca e instala "Sensirion Core" (dependencia)
Ejemplo básico de temperatura y humedad
// 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("%");
}
}
Función setup:
- Inicialización serie: Usa
Serial1con los pines 44 (RX) y 43 (TX) específicos de reTerminal E Serie - Inicialización I2C: Configura I2C con los pines 19 (SDA) y 20 (SCL)
- Inicialización del sensor: Llama a
sht4x.begin(Wire, 0x44)para inicializar el sensor SHT4x en la dirección 0x44 - Lectura del número de serie: Lee y muestra el número de serie único del sensor para verificación
Función loop:
- Retardo: Espera 5 segundos entre mediciones para evitar el sobremuestreo
- Medición: Usa
measureHighPrecision()para lecturas precisas (toma ~8.3 ms) - Gestión de errores: Comprueba errores y los convierte en mensajes legibles usando
errorToString() - Mostrar resultados: Imprime la temperatura en grados Celsius y el porcentaje de humedad relativa
Salida esperada
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%
Sistema de gestión de batería
La reTerminal E Serie incluye capacidad de monitorización de voltaje de batería mediante un pin ADC con un circuito divisor de voltaje.
Monitorización simple del voltaje de la batería
// 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);
}
Explicación del código:
- GPIO1 lee el voltaje de batería dividido a través del ADC
- GPIO21 habilita el circuito de monitorización de batería
- El voltaje real de la batería es el doble del voltaje medido debido al divisor de voltaje
- Para una batería LiPo completamente cargada, se espera alrededor de 4.2 V
- Cuando la batería está baja, el voltaje cae a alrededor de 3.3 V
Salida esperada
Battery Voltage Monitor
Battery: 4.18 V
Battery: 4.19 V
Battery: 4.18 V
Uso de la tarjeta MicroSD
Para aplicaciones que requieren almacenamiento adicional, como un marco de fotos digital o registro de datos, la reTerminal E Serie incluye una ranura para tarjeta MicroSD.
Inserta una tarjeta microSD si planeas usar el dispositivo como marco de fotos digital o necesitas almacenamiento adicional.

La reTerminal E Serie solo admite tarjetas MicroSD de hasta 64 GB formateadas con el sistema de archivos Fat32.
Operaciones básicas con la tarjeta SD: listar archivos
Este ejemplo muestra cómo inicializar la tarjeta SD, detectar cuándo se inserta o se extrae y listar todos los archivos y directorios en su raíz. El código es idéntico tanto para la reTerminal E1001 como para la reTerminal E1002.
Copia el siguiente código en tu sketch de 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
}
Explicación del código
- Definiciones de pines: El código comienza definiendo los pines GPIO utilizados para la ranura de la tarjeta MicroSD. Ten en cuenta que los pines SPI (
MOSI,SCK) se comparten con la pantalla de papel electrónico, pero un Chip Select separado (SD_CS_PIN) y una instancia SPI dedicada (spiSD) garantizan que puedan usarse de forma independiente. - Inicialización de SPI: Creamos una nueva instancia de SPI,
spiSD(HSPI), para usar el segundo controlador SPI por hardware del ESP32 (HSPI). Esta es la mejor práctica para evitar conflictos con otros dispositivos SPI. - Detección de tarjeta: La función
isCardInserted()lee elSD_DET_PIN. En el hardware del reTerminal, este pin se mantiene en nivel BAJO cuando hay una tarjeta presente. - Montar/Desmontar: La función
mountSD()habilita la alimentación de la tarjeta, configura el bus HSPI con los pines correctos y llama aSD.begin()para inicializar el sistema de archivos.unmountSD()libera los recursos. - Listado de archivos:
listRoot()abre el directorio raíz (/), ylistDir()es una función recursiva que recorre el sistema de archivos, imprimiendo los nombres de todos los archivos y directorios. setup(): InicializaSerial1para la salida, configura el pin de detección de tarjeta y realiza una comprobación inicial para ver si ya hay una tarjeta insertada cuando el dispositivo se enciende.loop(): En lugar de comprobar constantemente la tarjeta, el código utiliza un temporizador no bloqueante (millis()) para comprobar un cambio en el estado de la tarjeta una vez por segundo. Si se detecta un cambio (tarjeta insertada o retirada), monta o desmonta la tarjeta e imprime el estado en el monitor serie.
Resultados esperados
- Sube el código a tu reTerminal.
- Abre el Monitor Serie del Arduino IDE (Tools > Serial Monitor).
- Asegúrate de que la velocidad en baudios esté configurada en 115200.
Verás una salida correspondiente a las siguientes acciones:
- Al iniciar sin tarjeta: El monitor imprimirá
[SD] No card detected at startup... - Cuando insertes una tarjeta: El monitor imprimirá
[SD] Card inserted., seguido de un listado completo de todos los archivos y directorios de la tarjeta. - Cuando retires la tarjeta: El monitor imprimirá
[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
Ejemplo avanzado: Mostrar imágenes BMP desde la tarjeta SD
Este ejemplo completo combina las funcionalidades de las secciones anteriores. Escribiremos un programa que lea un archivo de imagen Bitmap (.bmp) desde una tarjeta MicroSD y lo muestre en la pantalla de papel electrónico del reTerminal. Esto demuestra una aplicación práctica y real del dispositivo.
El programa buscará un archivo llamado test.bmp en el directorio raíz de la tarjeta SD.
Preparación
Antes de ejecutar el código, debes preparar correctamente tanto la tarjeta MicroSD como el archivo de imagen. Este es el paso más crítico para garantizar que la imagen se muestre correctamente.
1. Formatear la tarjeta MicroSD
Prepara una tarjeta MicroSD (se recomienda de 64GB o menor) y formatéala usando el sistema de archivos FAT32.
2. Preparar el archivo de imagen
El método para preparar la imagen difiere ligeramente según el modelo de tu reTerminal. Sigue la guía que coincida con tu dispositivo.
- Para reTerminal E1001 (Pantalla B&N)
- Para reTerminal E1002 (Pantalla en color)
La pantalla en blanco y negro solo puede mostrar píxeles blancos y negros. Aunque nuestro código puede convertir una imagen en color a escala de grises en tiempo real, obtendrás mucho mejor contraste y detalle si pre-conviertes la imagen a una escala de grises de alta calidad en tu ordenador.
-
Redimensionar la imagen: Redimensiona tu imagen a 800x480 píxeles.
-
Convertir a escala de grises (recomendado): En tu editor de imágenes, convierte primero la imagen a escala de grises. En GIMP:
- Ve al menú Colors > Desaturate > Desaturate.... Elige un modo como "Luminosity" para obtener los mejores resultados.
-
Guardar como un BMP estándar: Sigue los mismos pasos que en la guía de pantalla en color para guardar el archivo. Aunque la imagen sea en escala de grises, guardarla como un BMP de 24 bits garantiza la máxima compatibilidad con el código.
- Ve a File > Export As..., ponle el nombre
test.bmp. - En el cuadro de diálogo de exportación, en Advanced Options, selecciona "24 bits: R8 G8 B8".
- Haz clic en Export.
- Ve a File > Export As..., ponle el nombre
-
Copiar a la tarjeta SD: Copia el archivo final
test.bmpal directorio raíz de tu tarjeta MicroSD.
La pantalla en color puede mostrar 6 colores: Negro, Blanco, Rojo, Amarillo, Azul y Verde. El código proporcionado incluye un algoritmo de "color más cercano" que asigna de forma inteligente cualquier color de tu imagen de origen al mejor color disponible en la pantalla. Para obtener resultados óptimos, sigue estos pasos:
-
Redimensionar la imagen: Usando cualquier editor de imágenes, redimensiona tu imagen a 800x480 píxeles.
-
Guardar como un BMP estándar: El código está diseñado para leer archivos BMP sin comprimir de 24 o 32 bits. Usar un editor de imágenes profesional es la mejor forma de asegurarte de que el formato sea correcto. Recomendamos el software libre y de código abierto GIMP:
- Abre tu imagen redimensionada en GIMP.
- Ve al menú File > Export As....
- Nombra el archivo
test.bmpy haz clic en Export. - En el cuadro de diálogo "Export Image as BMP" que aparece, despliega las Advanced Options.
- Selecciona "24 bits: R8 G8 B8". Este es el formato sin comprimir más compatible.
- Haz clic en Export.
-
Copiar a la tarjeta SD: Copia el archivo final
test.bmpal directorio raíz de tu tarjeta MicroSD.
Si quieres usar imágenes ya preparadas para hacer pruebas, puedes utilizar las imágenes de ejemplo proporcionadas por GxEPD2.
El código
Este es el código final y validado. Incluye todas las comprobaciones necesarias y el algoritmo avanzado de coincidencia de color. Simplemente establece la macro EPD_SELECT en 0 para el E1001 (B&N) o en 1 para el E1002 (Color).
- Para reTerminal E1001 (Pantalla B&N)
- Para reTerminal E1002 (pantalla en color)
#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
}
Cómo funciona
setup(): La funciónsetupinicializa en secuencia todo el hardware necesario: el puerto Serial para depuración, el bus SPI compartido, la pantalla de papel electrónico y, por último, la tarjeta SD. Si todas las inicializaciones se realizan correctamente, hace una única llamada adrawBmp()para ejecutar la tarea principal.drawBmp(): Esta es la función principal. Abre el archivo BMP, analiza la cabecera para leer sus dimensiones y propiedades, y realiza comprobaciones de validación cruciales. En concreto, verifica si hay tipos de compresión no compatibles y proporciona un mensaje de error útil si encuentra alguno.- Bucle de dibujo: La función lee la imagen desde la tarjeta SD una fila a la vez. Para cada píxel de la fila, extrae los valores de color Rojo, Verde y Azul.
- Gestión del color: Aquí es donde la lógica se divide según la macro
EPD_SELECT:- Para color (E1002): Llama a
findNearestColor(r, g, b). Esta función calcula la "distancia" entre el color del píxel y cada uno de los 6 colores de la paleta de la pantalla. Devuelve el color de la paleta con la distancia más pequeña, garantizando la representación de color más precisa posible. - Para B&N (E1001): Utiliza una fórmula de luminancia estándar (
r * 0.299 + g * 0.587 + b * 0.114) para convertir el color RGB en un único valor de brillo. Si este valor está por debajo de un umbral (128), el píxel se dibuja en negro; de lo contrario, se dibuja en blanco.
- Para color (E1002): Llama a
Cargar y ejecutar
- En el IDE de Arduino, asegúrate de tener seleccionada la placa correcta (
XIAO_ESP32S3). - Configura la macro
EPD_SELECTen la parte superior del código a1para el reTerminal E1002 o a0para el E1001. - Inserta tu tarjeta MicroSD preparada en el reTerminal.
- Carga el código.
- Abre el Monitor Serial a una velocidad en baudios de
115200. Verás los registros de progreso y, tras unos momentos, la imagen se representará en la pantalla de papel electrónico.
La velocidad de refresco de la pantalla puede ser lenta; a veces la pantalla no responderá hasta 2~3 minutos después de cargar el programa.
Solución de problemas
P1: ¿Por qué la pantalla de papel electrónico del reTerminal no muestra nada ni se refresca al ejecutar el código anterior?
Este problema puede producirse si has insertado una tarjeta MicroSD en el reTerminal. La razón es que la tarjeta MicroSD y la pantalla de papel electrónico comparten el mismo bus SPI en el reTerminal. Si se inserta una tarjeta MicroSD pero su pin de habilitación (chip select) no se gestiona correctamente, puede provocar un conflicto en el bus SPI. En concreto, la tarjeta MicroSD puede mantener la línea BUSY en alto, lo que impide que la pantalla de papel electrónico funcione correctamente, dando como resultado que no haya actualizaciones ni refrescos de la pantalla.
// Initialize SD Card
pinMode(SD_EN_PIN, OUTPUT);
digitalWrite(SD_EN_PIN, HIGH);
pinMode(SD_DET_PIN, INPUT_PULLUP);
Para resolver esto, debes asegurarte de que la tarjeta MicroSD esté correctamente habilitada utilizando el código proporcionado arriba. El código inicializa y habilita la tarjeta MicroSD configurando los estados correctos de los pines, lo que evita conflictos en el bus SPI y permite que tanto la tarjeta SD como la pantalla de tinta electrónica funcionen juntas. Utiliza siempre el código de inicialización recomendado cuando uses una tarjeta MicroSD con el reTerminal para evitar este tipo de problemas.
Si la tarjeta MicroSD no se utiliza en tu proyecto, recomendamos apagar el dispositivo y retirar la tarjeta antes de ejecutar el programa de la pantalla. Si la tarjeta se ha insertado en el reTerminal, tendrás que añadir el código anterior para asegurarte de que la pantalla pueda mostrarse correctamente, independientemente de si estás utilizando una tarjeta MicroSD o no.
P2: ¿Por qué no puedo cargar programas en el reTerminal?
Si encuentras el siguiente error al cargar un programa en el reTerminal.

Entonces, es probable que tu Arduino IDE esté configurado con una velocidad de carga excesivamente alta. Cámbiala a 115200 baudios para resolver este problema.

Soporte técnico y debate sobre el producto
Gracias por elegir nuestros productos. Estamos aquí para ofrecerte diferentes tipos de soporte y garantizar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para adaptarnos a diferentes preferencias y necesidades.



