Pular para o conteúdo principal

Livro de Receitas Arduino: Periféricos Onboard (reTerminal E Série)

Procurando a parte de display?

Esta página se concentra em controlar os periféricos de hardware onboard do reTerminal E Série com Arduino. Se você quiser renderizar texto, gráficos ou imagens na tela ePaper, em vez disso, vá para Livro de Receitas Arduino: Display ePaper.

Introdução

O reTerminal E Série é mais do que apenas uma tela ePaper — cada modelo também expõe um LED onboard, um buzzer, três botões de usuário, um sensor de temperatura e umidade SHT4x, monitoramento de tensão da bateria e um slot para cartão microSD. Este livro de receitas reúne exemplos prontos para gravar no Arduino para cada um desses periféricos, além de um pipeline de imagem ponta a ponta que carrega um arquivo JPEG / BMP / PNG do cartão SD, aplica dithering para a paleta do painel e o renderiza na tela ePaper — um sketch pronto por variante de painel (E1001 BW, E1001 Gray4, E1002, E1003, E1004).

O que este livro de receitas cobre:

  • Controle de LED no GPIO6 (lógica invertida).
  • Buzzer para alertas e tons musicais no GPIO45.
  • Três botões de usuário (KEY0 / KEY1 / KEY2) com detecção de estado com debounce.
  • Sensor SHT4x via I²C (GPIO19 SDA / GPIO20 SCL) usando a biblioteca da Sensirion.
  • Monitoramento de tensão da bateria através do circuito de ADC + pino de habilitação.
  • Cartão microSD montagem / detecção / listagem de arquivos no barramento SPI compartilhado.
  • Exemplo avançado — pipeline de imagem com cartão SD: escolha qualquer JPEG / BMP / PNG no cartão SD, passe por um dos cinco algoritmos de dithering integrados e renderize no painel com âncora, modo de ajuste e escala configuráveis.

Materiais Necessários

Este livro de receitas se aplica a todos os quatro modelos reTerminal E Série. Escolha o dispositivo que você tiver em mãos:

reTerminal E1001reTerminal E1002reTerminal E1003reTerminal E1004

Pré-requisitos

Antes de executar qualquer exemplo abaixo, você já deve ter:

  • A IDE Arduino instalada com o pacote de placas ESP32 e a placa XIAO_ESP32S3 selecionada.
  • Um cabo de dados USB-C funcional e a porta serial correta selecionada.
  • Verificado que você consegue gravar um sketch básico no dispositivo — veja a preparação do ambiente em Livro de Receitas Arduino: Display ePaper se você ainda não fez isso.

Todos os sketches neste livro de receitas imprimem informações de depuração através de Serial1 nos pinos GPIO44 (RX) / GPIO43 (TX) a 115200 baud. Abra o Monitor Serial do Arduino e selecione a porta e taxa de baud correspondentes para acompanhar.

Controle de LED

O reTerminal E Série possui um LED onboard que pode ser controlado via GPIO. Observe que a lógica do LED é invertida (LOW = LIGADO, HIGH = DESLIGADO). O pino do LED difere entre os modelos:

ModeloGPIO do LED
E1001 / E1002GPIO6
E1003GPIO16
E1004GPIO48
// reTerminal E1001/E1002 - 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);
}

Controle do Buzzer

O reTerminal E Série inclui um buzzer no GPIO45 que pode produzir vários tons e sons 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);
}

Buzzer com Tons

Clique para expandir o código completo de exemplo do Buzzer
#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);
}

Funções do buzzer:

  • digitalWrite(): Controle simples LIGADO/DESLIGADO para bipes básicos
  • tone(pin, frequency, duration): Gera frequências específicas para melodias ou alertas
  • noTone(pin): Interrompe a geração de tom

Padrões de alerta comuns:

  • Bipe único: Confirmação
  • Bipe duplo: Aviso
  • Bipe triplo: Erro
  • Contínuo: Alerta crítico

Botões do usuário

A reTerminal E Series possui três botões programáveis pelo usuário que podem ser usados para vários propósitos de controle. Esta seção demonstra como ler os estados dos botões e responder aos pressionamentos usando Arduino.

A reTerminal E Series possui três botões conectados ao ESP32-S3 via KEY0 (GPIO3), KEY1 (GPIO4) e KEY2 (GPIO5). Todos os botões são ativos em nível baixo, o que significa que leem BAIXO quando pressionados e ALTO quando soltos.

A disposição física e a função desses botões diferem entre os modelos:

TeclaE1001 / E1002 / E1003E1004
KEY0 (GPIO3)Botão direito (botão verde)Botão direcional direito (frontal)
KEY1 (GPIO4)Botão do meioBotão direcional esquerdo (frontal)
KEY2 (GPIO5)Botão esquerdoBotão de atualizar (frontal esquerdo)
nota

O E1004 possui botões tanto na parte frontal quanto na parte traseira do dispositivo. As conexões KEY0–KEY2 listadas acima correspondem aos botões no painel frontal.

Exemplo básico de leitura de botões

Este exemplo demonstra como detectar pressionamentos de botões e imprimir mensagens no monitor serial.

// 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
}

Como o código funciona:

  1. Definição de pinos: Definimos constantes para o número do pino GPIO de cada botão.

  2. Configuração de pinos: Em setup(), configuramos cada pino de botão como INPUT.

  3. Detecção de botões: Em loop(), verificamos continuamente o estado de cada botão usando digitalRead(). Quando um botão é pressionado, o pino lê BAIXO.

  4. Debouncing: Um simples atraso de 200 ms após cada pressionamento de botão evita múltiplas detecções de um único toque devido ao bounce mecânico.

  5. Saída serial: Cada pressionamento de botão aciona uma mensagem para o monitor serial para depuração e verificação.


Passo 1. Envie o código para o seu dispositivo reTerminal E Series.

Passo 2. Abra o Serial Monitor na Arduino IDE (Tools > Serial Monitor).

Passo 3. Defina a taxa de baud para 115200.

Passo 4. Pressione cada botão e observe a saída no Serial Monitor.

Saída esperada ao pressionar os botões:

=================================
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)

A reTerminal E Series inclui um sensor integrado de temperatura e umidade SHT4x conectado via I2C.

Instalando as bibliotecas necessárias

Instale duas bibliotecas via Arduino Library Manager (Tools > Manage Libraries...):

  1. Pesquise e instale "Sensirion I2C SHT4x"
  2. Pesquise e instale "Sensirion Core" (dependência)

Exemplo básico de temperatura e umidade

// 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("%");
}
}

Função setup:

  1. Inicialização serial: Usa Serial1 com os pinos 44 (RX) e 43 (TX) específicos da reTerminal E Series
  2. Inicialização I2C: Configura o I2C com os pinos 19 (SDA) e 20 (SCL)
  3. Inicialização do sensor: Chama sht4x.begin(Wire, 0x44) para inicializar o sensor SHT4x no endereço 0x44
  4. Leitura do número de série: Lê e exibe o número de série exclusivo do sensor para verificação

Função loop:

  1. Atraso: Aguarda 5 segundos entre as medições para evitar superamostragem
  2. Medição: Usa measureHighPrecision() para leituras precisas (leva ~8,3 ms)
  3. Tratamento de erros: Verifica erros e os converte em mensagens legíveis usando errorToString()
  4. Exibir resultados: Imprime a temperatura em Celsius e a umidade relativa em porcentagem

Saída 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 gerenciamento de bateria

A reTerminal E Series inclui capacidade de monitoramento da tensão da bateria por meio de um pino ADC com circuito divisor de tensão.

Monitoramento simples da tensão da bateria

// 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);
}

Explicação do código:

  • O GPIO1 lê a tensão dividida da bateria através do ADC
  • GPIO21 habilita o circuito de monitoramento da bateria
  • A tensão real da bateria é o dobro da tensão medida devido ao divisor de tensão
  • Para uma bateria LiPo totalmente carregada, espere cerca de 4,2 V
  • Quando a bateria está fraca, a tensão cai para cerca de 3,3 V

Saída esperada

Battery Voltage Monitor

Battery: 4.18 V
Battery: 4.19 V
Battery: 4.18 V

Usando o cartão MicroSD

Para aplicações que exigem armazenamento adicional, como um porta-retratos digital ou registro de dados, o reTerminal E Series inclui um slot para cartão MicroSD.

Insira um cartão microSD se você planeja usar o dispositivo como um porta-retratos digital ou se precisar de armazenamento adicional.

nota

O reTerminal E Series suporta apenas cartões MicroSD de até 64 GB formatados com o sistema de arquivos Fat32.

Operações básicas com cartão SD: listando arquivos

Este exemplo demonstra como inicializar o cartão SD, detectar quando ele é inserido ou removido e listar todos os arquivos e diretórios em sua raiz. O pino de habilitação de energia do cartão SD (SD_EN_PIN) varia entre os modelos:

ModeloSD_EN_PINGPIO
E1001 / E1002 / E100416GPIO16
E100339GPIO39

Todos os outros pinos do cartão SD (DET, CS, MOSI, MISO, SCK) são os mesmos em todos os modelos. Selecione a aba do seu dispositivo e copie o código para o sketch da sua IDE Arduino.

Clique para expandir o código completo de exemplo do cartão SD
#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
}

Explicação do código

  • Definições de pinos: O código começa definindo os pinos GPIO usados para o slot do cartão MicroSD. Observe que os pinos SPI (MOSI, SCK) são compartilhados com o display de e-paper, mas um Chip Select separado (SD_CS_PIN) e uma instância SPI dedicada (spiSD) garantem que eles possam ser usados de forma independente.
  • Inicialização do SPI: Instanciamos um novo objeto SPI, spiSD(HSPI), para usar o segundo controlador SPI de hardware do ESP32 (HSPI). Esta é a melhor prática para evitar conflitos com outros dispositivos SPI.
  • Detecção do cartão: A função isCardInserted() lê o SD_DET_PIN. No hardware do reTerminal, este pino é puxado para LOW quando um cartão está presente.
  • Montar/Desmontar: A função mountSD() habilita a alimentação do cartão, configura o barramento HSPI com os pinos corretos e chama SD.begin() para inicializar o sistema de arquivos. unmountSD() libera os recursos.
  • Listagem de arquivos: listRoot() abre o diretório raiz (/), e listDir() é uma função recursiva que percorre o sistema de arquivos, imprimindo os nomes de todos os arquivos e diretórios.
  • setup(): Inicializa Serial1 para saída, configura o pino de detecção do cartão e realiza uma verificação inicial para ver se um cartão já está inserido quando o dispositivo é ligado.
  • loop(): Em vez de verificar o cartão constantemente, o código usa um temporizador não bloqueante (millis()) para verificar uma mudança no status do cartão uma vez por segundo. Se uma mudança for detectada (cartão inserido ou removido), ele monta ou desmonta o cartão e imprime o status no monitor serial.

Resultados esperados

  1. Envie o código para o seu reTerminal.
  2. Abra o Serial Monitor da Arduino IDE (Tools > Serial Monitor).
  3. Certifique-se de que a taxa de baud está configurada para 115200.

Você verá uma saída correspondente às seguintes ações:

  • Na inicialização sem cartão: O monitor imprimirá [SD] No card detected at startup...
  • Quando você inserir um cartão: O monitor imprimirá [SD] Card inserted., seguido por uma listagem completa de todos os arquivos e diretórios no cartão.
  • Quando você remover o cartão: O 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

Exemplo avançado: pipeline de imagem do cartão SD → ePaper

Este é o exemplo principal para o reTerminal E Series. Ele carrega um arquivo JPEG / BMP / PNG do cartão microSD, o processa por um pipeline de dithering configurável e renderiza o resultado no painel de ePaper — com controles para algoritmo de dithering, brilho, posição de ancoragem e ajuste / escala. A mesma estrutura de código funciona em todas as quatro variantes de painel; o que muda por modelo é apenas a profundidade de cor de saída (1-bit PB, 2-bit Gray4, 4-bit Gray16 ou 6 cores E6).

Cinco sketches prontos para gravação são fornecidos com a biblioteca Seeed_GFX — escolha aquele que corresponde ao seu hardware:

Índice de exemplos

DispositivoSketch de exemploResolução do painelPaleta de saída
reTerminal E1001 (BW)reTerminal_E1001_SDcard_BW800 × 480Preto / branco de 1 bit
reTerminal E1001 (Gray4)reTerminal_E1001_SDcard_Gray4800 × 480Escala de cinza de 2 bits e 4 níveis
reTerminal E1002reTerminal_E1002_SDcard_Color6800 × 4806 cores (P / B / V / A / G / B)
reTerminal E1003reTerminal_E1003_SDcard_Gray161872 × 1404Escala de cinza de 4 bits e 16 níveis
reTerminal E1004reTerminal_E1004_SDcard_Color61200 × 16006 cores (P / B / V / A / G / B)

Todos os cinco sketches estão em Seeed_GFX/examples/ePaper/reTerminal_SDcard_Bitmap/. Cada pasta é totalmente autônoma — nenhuma biblioteca extra para instalar, basta abrir e gravar.

O que o pipeline faz

Image Processing Pipeline: microSD to ePaper

microSDJPG / BMP / PNGPSRAMRGB888Scaled Bufferscaled RGB888Palette Bufferíndices da paleta do painelePaperatualização do paineldecodepngle / jpeg / bmpredimensionarvizinho mais próximoditherBayer / FS / ...pushSprite
  1. Decode — o formato do arquivo é detectado por bytes mágicos (FF D8, BM ou 89 50 4E 47). Uma extensão enganosa é corrigida automaticamente e um aviso é registrado.
  2. Resize (opcional) — redução de escala por vizinho mais próximo com base em DISPLAY_FIT / DISPLAY_SCALE.
  3. Dither — um de cinco algoritmos quantiza RGB de 24 bits na pequena paleta do painel (2 / 4 / 6 / 16 níveis).
  4. Push — o buffer quantizado é gravado no Sprite do ePaper na posição de ancoragem e, em seguida, epaper.update() o envia para o painel.

Etapa 1 — Abra o exemplo para o seu modelo

No Arduino IDE: File → Examples → Seeed_GFX → ePaper → reTerminal_SDcard_Bitmap → (escolha o seu modelo).

reTerminal_SDcard_Bitmap/
└── reTerminal_E1001_SDcard_BW/
├── reTerminal_E1001_SDcard_BW.ino ← config + setup() + loop()
├── dither.{h,cpp} ← BW dither algorithms
├── image_loader.{h,cpp} ← JPEG / BMP / PNG decoder
├── pngle.{h,c} + miniz.{h,c} ← PNG backend (MIT, vendored)
└── driver.h ← selects Setup520 (UC8179, 800x480)
Sketches autossuficientes

Você não precisa instalar pngle, miniz ou qualquer outra coisa pelo Arduino Library Manager. O código-fonte do decodificador PNG é fornecido dentro de cada pasta de exemplo, então o Arduino IDE o detecta automaticamente quando compila o sketch.

Código completo do sketch

O código-fonte completo .ino para cada variante é mostrado abaixo. Todas as configurações ajustáveis pelo usuário (caminho da imagem, algoritmo de dithering, âncora, ajuste/escala) estão no bloco de CONFIGURAÇÃO DO USUÁRIO próximo ao topo — o restante do arquivo é código padrão que normalmente não precisa ser editado.

Clique aqui para visualizar o código completo — reTerminal_E1001_SDcard_BW.ino
#include <SPI.h>
#include <FS.h>
#include <SD.h>
#include "TFT_eSPI.h"
#include "dither.h"
#include "image_loader.h"

#ifndef EPAPER_ENABLE
#error "This example requires Setup520_Seeed_reTerminal_E1001 -- check driver.h selects BOARD_SCREEN_COMBO 520"
#endif

EPaper epaper;

static constexpr int PIN_SD_SCK = 7;
static constexpr int PIN_SD_MISO = 8;
static constexpr int PIN_SD_MOSI = 9;
static constexpr int PIN_SD_CS = 14;
static constexpr int PIN_SD_EN = 16;
static constexpr int PIN_SD_DET = 15;
static constexpr int PIN_DBG_RX = 44;
static constexpr int PIN_DBG_TX = 43;
#define LOG Serial1
#define TAG "[e1001-bw]"

// =============================================================================
// USER CONFIGURATION
// =============================================================================

static const char* IMAGE_PATH = "/img/demo.jpg";

// DITHER_NONE / DITHER_BAYER8 / DITHER_FS / DITHER_JARVIS / DITHER_ATKINSON
static const DitherMethod DITHER_METHOD = DITHER_FS;
// 1.0 = neutral, >1.0 darkens, <1.0 brightens
static const float DITHER_GAMMA = 1.0f;
// false = normal, true = invert black/white
static const bool DITHER_INVERT = false;

// ANCHOR_TOP_LEFT / ANCHOR_TOP_CENTER / ANCHOR_TOP_RIGHT /
// ANCHOR_MIDDLE_LEFT / ANCHOR_CENTER / ANCHOR_MIDDLE_RIGHT /
// ANCHOR_BOTTOM_LEFT / ANCHOR_BOTTOM_CENTER / ANCHOR_BOTTOM_RIGHT
enum DisplayAnchor {
ANCHOR_TOP_LEFT, ANCHOR_TOP_CENTER, ANCHOR_TOP_RIGHT,
ANCHOR_MIDDLE_LEFT, ANCHOR_CENTER, ANCHOR_MIDDLE_RIGHT,
ANCHOR_BOTTOM_LEFT, ANCHOR_BOTTOM_CENTER, ANCHOR_BOTTOM_RIGHT,
};
static const DisplayAnchor DISPLAY_ANCHOR = ANCHOR_CENTER;

// FIT_ORIGINAL / FIT_CONTAIN / FIT_SCALE
enum DisplayFit { FIT_ORIGINAL, FIT_CONTAIN, FIT_SCALE };
static const DisplayFit DISPLAY_FIT = FIT_SCALE;
static const float DISPLAY_SCALE = 0.7f;

// =============================================================================
// (implementation below -- no need to edit)
// =============================================================================

static void log_mem(const char* tag) {
LOG.printf("[mem] %-22s heap=%lu kB PSRAM free=%lu/%lu kB\n", tag,
(unsigned long)(ESP.getFreeHeap() / 1024),
(unsigned long)(ESP.getFreePsram() / 1024),
(unsigned long)(ESP.getPsramSize() / 1024));
LOG.flush();
}

static void list_sd_root(int max_entries = 32) {
File root = SD.open("/");
if (!root || !root.isDirectory()) { LOG.println("[sd] cannot open '/'"); return; }
LOG.println("[sd] contents of '/' :");
int n = 0;
File entry = root.openNextFile();
while (entry) {
if (entry.isDirectory()) LOG.printf(" <DIR> %s\n", entry.name());
else LOG.printf(" %7lu B %s\n", (unsigned long)entry.size(), entry.name());
entry.close();
if (++n >= max_entries) { LOG.printf(" ... (truncated at %d)\n", max_entries); break; }
entry = root.openNextFile();
}
if (n == 0) LOG.println(" (empty)");
root.close(); LOG.flush();
}

static void compute_target_size(int src_w, int src_h, DisplayFit fit, float scale,
int panel_w, int panel_h, int* out_w, int* out_h) {
switch (fit) {
case FIT_ORIGINAL: *out_w = src_w; *out_h = src_h; break;
case FIT_CONTAIN: {
double s = (double)panel_w/src_w; if ((double)panel_h/src_h < s) s = (double)panel_h/src_h;
if (s > 1.0) s = 1.0;
*out_w = (int)(src_w*s+0.5); *out_h = (int)(src_h*s+0.5); break;
}
case FIT_SCALE: *out_w = (int)(src_w*scale+0.5); *out_h = (int)(src_h*scale+0.5); break;
}
if (*out_w & 7) *out_w &= ~7;
if (*out_w < 8) *out_w = 8;
if (*out_h < 1) *out_h = 1;
}

static void compute_anchor_xy(int img_w, int img_h, DisplayAnchor a,
int panel_w, int panel_h, int* out_x, int* out_y) {
int x = 0, y = 0;
switch (a) {
case ANCHOR_TOP_LEFT: x=0; y=0; break;
case ANCHOR_TOP_CENTER: x=(panel_w-img_w)/2; y=0; break;
case ANCHOR_TOP_RIGHT: x=panel_w-img_w; y=0; break;
case ANCHOR_MIDDLE_LEFT: x=0; y=(panel_h-img_h)/2; break;
case ANCHOR_CENTER: x=(panel_w-img_w)/2; y=(panel_h-img_h)/2; break;
case ANCHOR_MIDDLE_RIGHT: x=panel_w-img_w; y=(panel_h-img_h)/2; break;
case ANCHOR_BOTTOM_LEFT: x=0; y=panel_h-img_h; break;
case ANCHOR_BOTTOM_CENTER: x=(panel_w-img_w)/2; y=panel_h-img_h; break;
case ANCHOR_BOTTOM_RIGHT: x=panel_w-img_w; y=panel_h-img_h; break;
}
if (x & 7) x &= ~7;
*out_x = x; *out_y = y;
}

static bool show_image_on_panel(RgbImage* img) {
int W, H;
compute_target_size(img->width, img->height, DISPLAY_FIT, DISPLAY_SCALE,
EPD_WIDTH, EPD_HEIGHT, &W, &H);
int x, y;
compute_anchor_xy(W, H, DISPLAY_ANCHOR, EPD_WIDTH, EPD_HEIGHT, &x, &y);

if (W != img->width || H != img->height) {
if (!resize_image(img, W, H)) { LOG.println("[layout] resize OOM"); return false; }
}
const size_t npx = (size_t)W * H;
uint8_t* idx = (uint8_t*)ps_malloc(npx);
if (!idx) idx = (uint8_t*)malloc(npx);
if (!idx) { LOG.println(TAG " OOM idx"); return false; }

static const char* kDN[] = {"NONE","BAYER8","FS","JARVIS","ATKINSON"};
LOG.printf(TAG " dithering BW with %s, gamma=%.2f\n", kDN[(int)DITHER_METHOD], DITHER_GAMMA);
if (!dither_image(img->pixels, W, H, PAL_BW, DITHER_METHOD, DITHER_GAMMA, DITHER_INVERT, idx)) {
free(idx); return false;
}
image_free(img);

const size_t bm_bytes = ((size_t)W+7)/8 * (size_t)H;
uint8_t* bm = (uint8_t*)ps_malloc(bm_bytes);
if (!bm) bm = (uint8_t*)malloc(bm_bytes);
if (!bm) { free(idx); return false; }
pack_1bpp_msb(idx, bm, W, H, true);
free(idx);

epaper.drawBitmap(x, y, bm, W, H, TFT_BLACK, TFT_WHITE);
epaper.update();
free(bm);
return true;
}

void setup() {
LOG.begin(115200, SERIAL_8N1, PIN_DBG_RX, PIN_DBG_TX);
delay(2500);
LOG.println("==============================================");
LOG.println(" reTerminal E1001 -- SD Bitmap (BW)");
LOG.println("==============================================");
log_mem("start");

pinMode(PIN_SD_EN, OUTPUT); digitalWrite(PIN_SD_EN, HIGH);
pinMode(PIN_SD_DET, INPUT_PULLUP); delay(50);

epaper.begin();
epaper.fillScreen(TFT_WHITE);
epaper.update();

// UC8179 is write-only (TFT_MISO=-1 in Setup520). Re-init SPI with MISO for SD.
SPIClass& spi = epaper.getSPIinstance();
spi.end();
spi.begin(PIN_SD_SCK, PIN_SD_MISO, PIN_SD_MOSI, -1);

if (!SD.begin(PIN_SD_CS, spi)) {
LOG.println(TAG " SD.begin FAILED -- aborting"); return;
}
list_sd_root();

RgbImage img;
if (!load_image_from_sd(IMAGE_PATH, 0, 0, &img)) {
LOG.println(TAG " load failed -- aborting"); return;
}
log_mem("after decode");
show_image_on_panel(&img);
image_free(&img);

epaper.sleep();
LOG.println(TAG " done.");
}

void loop() { delay(1000); }
Código simplificado vs. código-fonte completo

Os blocos de código acima são levemente condensados (funções auxiliares embutidas, chamadas de log verbosas removidas) para mantê-los legíveis aqui. Os sketches na biblioteca — abertos via File → Examples → Seeed_GFX → ePaper → reTerminal_SDcard_Bitmap — contêm o log de diagnóstico completo, toda a lógica de ajuste/ancoragem e todos os comentários.

Etapa 2 — Prepare o cartão microSD

  1. Formate o cartão como FAT32.

  2. Crie uma estrutura de pastas que corresponda ao caminho que você definirá no sketch — o padrão é /img/demo.jpg:

    <SD root>/
    └── img/
    └── demo.jpg ← or demo.png / demo.bmp
  3. Insira o cartão no reTerminal antes de ligar o dispositivo (hot-plug funciona, mas é menos confiável).

Etapa 3 — Prepare sua imagem

O carregador aceita três formatos prontos para uso:

FormatoO que funcionaO que evitar
JPEG (.jpg / .jpeg)Baseline 8-bit, YCbCr ou escala de cinza, qualquer subamostragem de croma (4:4:4 / 4:2:2 / 4:2:0).JPEG progressivo, CMYK, fontes com rotação apenas por EXIF.
BMP (.bmp)BGR de 24 bits sem compactação, ou indexado de 4 bits (paleta + BI_RGB).BI_BITFIELDS, BMPs compactados com RLE.
PNG (.png)Qualquer PNG padrão (8 bits, 16 bits, paleta, entrelaçado, RGBA). RGBA é composto sobre branco porque os painéis de ePaper são opacos.Nenhum — o pngle lida com todas as variantes padrão de PNG.

O formato real do arquivo é detectado pelos magic bytes, não pela extensão. Um JPEG salvo como .bmp ainda funciona (você só verá um aviso no log serial).

Orientação de tamanho por painel:

O painel é 800 × 480. Qualquer fonte de até aproximadamente 1600 × 1200 é decodificada sem problemas em 8 MB de PSRAM. Imagens maiores ainda são aceitas, mas você vai querer DISPLAY_FIT = FIT_CONTAIN para que o carregador possa reduzi-las antes da quantização.

Etapa 4 — Configure o sketch

Todas as opções ajustáveis pelo usuário ficam em um bloco de configuração no topo de cada arquivo .ino. Os quatro controles mais importantes são explicados abaixo.

IMAGE_PATH — qual arquivo exibir

static const char* IMAGE_PATH = "/img/demo.jpg";

Use uma / inicial. O carregador detecta o formato pelos magic bytes, então a extensão é puramente cosmética — /photo.bmp contendo dados JPEG reais ainda é decodificado normalmente.

DITHER_METHOD — qual algoritmo de dithering

Os painéis de ePaper só podem exibir fisicamente 2 / 4 / 6 / 16 cores. Para representar os milhões de cores de uma foto típica, o carregador precisa quantizar cada pixel para uma dessas poucas entradas de paleta. O algoritmo de dithering decide como esse erro de quantização é espalhado pelos pixels vizinhos.

static const DitherMethod DITHER_METHOD = DITHER_FS;
OpçãoO que fazQuando usar
DITHER_NONECor mais próxima, sem difusão. Mais rápido, mais blocado.Diagnósticos ou quando você quer um visual posterizado.
DITHER_BAYER8Matriz Bayer ordenada 8×8. Determinístico, sem buffer de erro.A escolha mais segura no E1003 / E1004 na resolução do painel — nunca fica sem memória.
DITHER_FSDifusão de erro Floyd-Steinberg. A melhor relação qualidade / velocidade.Padrão no E1001 / E1002. Ótimo para fotos com gradientes suaves.
DITHER_JARVISJarvis-Judice-Ninke. Kernel mais amplo de 12 coeficientes, saída mais suave.Qualidade mais alta que FS, mas ~3× mais lento e usa mais PSRAM.
DITHER_ATKINSONAtkinson (Mac clássico). Difunde apenas 6/8 do erro → contraste mais alto, visual mais "gravado".Saída PB estilizada, conteúdo de quadrinhos / arte de linha.
Custo de memória da difusão de erro

DITHER_FS, DITHER_JARVIS e DITHER_ATKINSON precisam de um buffer de erro em ponto flutuante de aproximadamente W × H × N_channels × 4 bytes. Em 1872 × 1404 isso dá cerca de 31 MB para cor ou 10 MB para escala de cinza — bem acima da PSRAM disponível.

Quando ps_malloc falha, o carregador imprime

[dither] FS error buffer alloc FAILED (10358 kB) -- falling back to DITHER_NONE

e muda silenciosamente para DITHER_NONE. Se você não quiser esse fallback, mude para DITHER_BAYER8 (ordenado, alocação zero) ou reduza a imagem primeiro.

DITHER_GAMMA — compensação de brilho

static const float DITHER_GAMMA = 1.0f;

1.0 é neutro. Aumente para escurecer a saída (bom para fotos ao ar livre que ficam claras demais no ePaper). Diminua para clarear (bom para fotografia noturna ou capturas de tela). A faixa típica útil é 0,8 – 1,6.

DISPLAY_ANCHOR — onde a imagem é posicionada no painel

Uma grade 3×3 de pontos de ancoragem. A imagem é posicionada de forma que seu canto / borda / centro se alinhe com a localização correspondente no painel.

ANCHOR_TOP_LEFT       ANCHOR_TOP_CENTER       ANCHOR_TOP_RIGHT
ANCHOR_MIDDLE_LEFT ANCHOR_CENTER ANCHOR_MIDDLE_RIGHT
ANCHOR_BOTTOM_LEFT ANCHOR_BOTTOM_CENTER ANCHOR_BOTTOM_RIGHT
static const DisplayAnchor DISPLAY_ANCHOR = ANCHOR_CENTER;

Qualquer imagem menor que o painel é automaticamente preenchida com branco na área não utilizada, sem necessidade de redimensionar previamente para corresponder exatamente ao painel. Imagens maiores que o painel são recortadas simetricamente ao redor da âncora.

DISPLAY_FIT + DISPLAY_SCALE — dimensionando a imagem

static const DisplayFit  DISPLAY_FIT   = FIT_ORIGINAL;
static const float DISPLAY_SCALE = 1.0f;
ModoComportamento
FIT_ORIGINALMantém o tamanho decodificado como está. Padrão recomendado — previsível, sempre seguro.
FIT_CONTAINReduz a imagem para que caiba inteiramente dentro do painel preservando a proporção. Nunca amplia — uma imagem pequena permanece pequena (use FIT_SCALE para ampliar).
FIT_SCALEMultiplica o tamanho de origem por DISPLAY_SCALE. Suporta tanto redução (< 1.0) quanto ampliação (> 1.0).

Valores típicos para DISPLAY_SCALE: 0.25 um quarto, 0.5 metade, 1.0 original, 2.0 2×.

Ampliação é propensa a OOM em painéis grandes

No E1003 (1872 × 1404) e E1004 (1200 × 1600), DISPLAY_SCALE maior que 1.0 esgota rapidamente a PSRAM. O carregador exibirá uma mensagem de falta de memória e abortará. Em vez disso, prefira recortar ou pré-redimensionar no host.

Profundidade de escala de cinza (apenas E1001)

O E1001 vem com dois sketches porque o mesmo painel UC8179 pode operar em BW (rápido, 1 bit) ou Gray4 (mais lento, 2 bits, quatro tons). Escolha com base no conteúdo:

ConteúdoSketch recomendado
Arte linear, códigos QR, texto, quadrinhos desenhados à mão.reTerminal_E1001_SDcard_BW
Fotografias, ilustrações com sombreamento suave.reTerminal_E1001_SDcard_Gray4

O E1003 usa incondicionalmente escala de cinza de 16 níveis (initGrayMode(16)) — esse modo é o recurso característico do painel. E1002 e E1004 são de 6 cores e não expõem uma escolha de profundidade de escala de cinza.

Etapa 5 — Compile, Grave, Observe os Logs

  1. Em Arduino IDE → Tools: selecione a placa XIAO_ESP32S3, PSRAM = OPI PSRAM, Flash = 8 MB, Partition Scheme = Default 8 MB.
  2. Insira o cartão microSD preparado.
  3. Envie o sketch.
  4. Abra um monitor serial na ponte USB-UART da carrier (GPIO43 TX / GPIO44 RX, 115200 baud, 8N1) — observe que este é o Serial1, não o USB-CDC Serial que o IDE abre automaticamente.

Saída típica de log (E1004 com um PNG 1080 × 1920):

[reTerm_E1004] dithering Color6 with BAYER8, gamma=1.00 ...
[mem] before image load heap=275 kB PSRAM free=8030/8192 kB
[png] IHDR 1080x1920, allocating RGB888 buffer: 6075 kB
[img] image decoded: 1080x1920 (6075 kB in PSRAM)
[mem] after image decoded heap=275 kB PSRAM free=1955/8192 kB
[mem] after dither heap=275 kB PSRAM free=1955/8192 kB
[reTerm_E1004] anchor=CENTER fit=ORIGINAL scale=1.00
[reTerm_E1004] frame pushed OK
[reTerm_E1004] done. Sleeping panel.

Depois disso o painel será atualizado — isso leva 15 – 45 segundos para uma atualização completa, dependendo do modelo e do modo de cinza / cor escolhido. Fique parado e não reinicie a placa durante a atualização.

Folha de Dicas do Orçamento de Memória

PainelBuffer RGB888Buffer de erro do FS (pico)Confortável com FS?
E1001 BW @ 800×4801.1 MB1.5 MB✅ sim
E1001 Gray4 @ 800×4801.1 MB1.5 MB✅ sim
E1002 E6 @ 800×4801.1 MB4.6 MB✅ sim
E1003 Gray16 @ 1872×14047.5 MB10.1 MB❌ não — use DITHER_BAYER8 ou reduza a origem
E1004 E6 @ 1200×16005.5 MB22.0 MB❌ não — use DITHER_BAYER8 ou reduza a origem

O módulo OPI PSRAM de 8 MB no módulo XIAO ESP32-S3 oferece aproximadamente 7.9 MB de espaço utilizável após a sobrecarga do runtime do Arduino. Se o carregador não conseguir satisfazer uma alocação, ele registra o tamanho exato de que precisava e ou redimensiona-e-tenta-novamente (quando DISPLAY_FIT = FIT_CONTAIN) ou volta para DITHER_NONE.

Sobre a velocidade de atualização

Após o envio, o ePaper pode permanecer em branco nos primeiros segundos enquanto o driver executa sua forma de onda inicial. Uma primeira atualização completa pode levar até alguns minutos em um painel frio — isso é a eletroquímica do painel, não um bug. Atualizações subsequentes são mais rápidas.

Solução de Problemas

Para problemas de configuração do Arduino IDE, problemas de driver USB, falhas de envio ou problemas de "o display ePaper não atualiza", consulte a seção Solução de Problemas de Arduino Cookbook: ePaper Display.

Suporte Técnico & Discussão de Produto

Obrigado por escolher nossos produtos! Estamos aqui para fornecer diferentes tipos de suporte para garantir que sua experiência com nossos produtos seja a mais tranquila possível. Oferecemos vários canais de comunicação para atender a diferentes preferências e necessidades.

Loading Comments...