Pular para o conteúdo principal

Livro de Receitas Arduino: Display ePaper (reTerminal E Series)

Procurando os periféricos de hardware?

Esta página se concentra em controlar a tela ePaper a partir do Arduino. Se você quiser usar o LED onboard, buzzer, botões, sensor SHT4x, monitor de bateria ou slot para cartão microSD, vá para Livro de Receitas Arduino: Periféricos Onboard.

O boilerplate compartilhado — configuração da IDE Arduino, pacote de placa ESP32, instalação de Seeed_GFX, geração de driver.h — também está em Trabalhar com Arduino. Dê uma olhada nisso primeiro se você é novo em Arduino em ePaper da Seeed.

Introdução

A reTerminal E Series é a linha HMI industrial da Seeed Studio, construída sobre o XIAO ESP32-S3 e com displays ePaper integrados. Este livro de receitas percorre tudo o que você precisa para renderizar texto, gráficos e imagens na tela:

  • Visão geral de hardware e links de compra para E1001 / E1002 / E1003 / E1004.
  • Configuração do ambiente da IDE Arduino para todos os quatro modelos (placa XIAO_ESP32S3, OPI PSRAM).
  • Um primeiro Hello World em cada modelo usando a biblioteca Seeed_GFX (com o BOARD_SCREEN_COMBO correspondente).
  • Exemplos avançados específicos de painel com Seeed_GFX — escala de cinza de 4 níveis no E1001 e escala de cinza de 16 níveis no E1003.
  • Um Hello World alternativo usando a popular biblioteca GxEPD2.
  • Dicas de solução de problemas para falhas de atualização do ePaper e falhas de upload.

Materiais Necessários

Para concluir este tutorial, prepare um dos seguintes dispositivos reTerminal E Series:

reTerminal E1001reTerminal E1002reTerminal E1003reTerminal E1004

Preparação do Ambiente

Para programar o Display ePaper do reTerminal E Series com Arduino, você precisará configurar a IDE Arduino com suporte a ESP32.

dica

Se esta é a sua primeira vez usando Arduino, recomendamos fortemente que você consulte Getting Started with Arduino.

Configuração da IDE Arduino

Passo 1. Baixe e instale a IDE Arduino e inicie o aplicativo Arduino.


Passo 2. Adicione o suporte à placa ESP32 na IDE Arduino.

Na IDE Arduino, vá em File > Preferences e adicione a seguinte URL ao campo "Additional Boards Manager URLs":

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

Passo 3. Instale o pacote de placas ESP32.

Navegue até Tools > Board > Boards Manager, procure por "esp32" e instale o pacote ESP32 da Espressif Systems.

Passo 4. Selecione a placa correta.

Vá em Tools > Board > ESP32 Arduino e selecione XIAO_ESP32S3.

Passo 5. Conecte o Display ePaper do seu reTerminal E Series ao computador usando um cabo USB-C.

Passo 6. Selecione a porta correta em Tools > Port.

Programação do Display ePaper

O reTerminal E1001 possui um display ePaper preto e branco de 7,5 polegadas, enquanto o reTerminal E1002 é equipado com um display ePaper colorido completo de 7,3 polegadas. Ambos os displays oferecem excelente visibilidade em várias condições de iluminação com consumo de energia ultrabaixo, tornando-os ideais para aplicações industriais que exigem telas sempre ligadas com uso mínimo de energia.

Usando a Biblioteca Seeed_GFX

Para controlar o display ePaper, usaremos a biblioteca Seeed_GFX, que fornece suporte abrangente para vários dispositivos de display da Seeed Studio.

Passo 1. Baixe a biblioteca Seeed_GFX do GitHub:


Passo 2. Instale a biblioteca adicionando o arquivo ZIP na IDE Arduino. Vá em Sketch > Include Library > Add .ZIP Library e selecione o arquivo ZIP baixado.

nota

Se você já instalou a biblioteca TFT_eSPI anteriormente, talvez seja necessário removê-la temporariamente ou renomeá-la na pasta de bibliotecas do Arduino para evitar conflitos, pois Seeed_GFX é um fork de TFT_eSPI com recursos adicionais para displays da Seeed Studio.

Programando reTerminal E1001 (ePaper Preto & Branco de 7,5 polegadas)

Vamos explorar um exemplo simples que demonstra operações básicas de desenho no display ePaper preto e branco.

Passo 1. Abra o sketch de exemplo da biblioteca Seeed_GFX: File > Examples > Seeed_GFX > ePaper > Basic > HelloWorld

Passo 2. Ative o OPI PSRAM na IDE Arduino: Tools > PSRAM > OPI PSRAM

Passo 3. Crie um novo arquivo chamado driver.h na mesma pasta do seu sketch. Você pode fazer isso clicando no botão de seta na IDE Arduino e selecionando "New Tab", depois nomeando-o como driver.h.

Passo 4. Copie o código de configuração gerado e cole-o no arquivo driver.h. O código deve se parecer com isto:

#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)

Passo 5. Faça o upload do sketch para o seu reTerminal E1001. Você deverá ver o display mostrando vários gráficos, incluindo linhas, texto e formas demonstrando as capacidades básicas de desenho.

Tons de Cinza em Múltiplos Níveis com Seeed_GFX

Os sketches Hello World acima são intencionalmente mínimos para caberem em todos os modelos. Os painéis monocromáticos no E1001 e E1003 na verdade suportam tons de cinza em múltiplos níveis além do simples preto e branco — 4 níveis no E1001 e 16 níveis no E1003 — e a Seeed_GFX expõe ambos os modos por meio de epaper.initGrayMode(...) mais um conjunto de constantes de paleta TFT_GRAY_*. Os dois exemplos abaixo percorrem cada um deles.

Tons de Cinza em 4 Níveis no reTerminal E1001

O painel monocromático de 7,5" do reTerminal E1001 pode renderizar 4 níveis de tons de cinza em vez de apenas preto e branco. A Seeed_GFX expõe isso por meio de epaper.initGrayMode(GRAY_LEVEL4) e quatro constantes de paleta:

ConstanteTom Renderizado
TFT_GRAY_0Preto
TFT_GRAY_1Cinza escuro
TFT_GRAY_2Cinza claro
TFT_GRAY_3Branco

O exemplo abaixo primeiro pinta quatro faixas horizontais — uma por nível de cinza — para que você possa verificar visualmente a paleta e, em seguida, faz o blit de um bitmap em tons de cinza 800×480 na tela. A biblioteca Seeed_GFX já inclui isso como um exemplo pronto para gravação, incluindo o image.h pré-convertido, então você não precisa gerar nenhum dado de bitmap por conta própria.

Passo 1. Abra o sketch de exemplo da biblioteca Seeed_GFX: File > Examples > Seeed_GFX > ePaper > Gray > GrayLevel4. O sketch e seu image.h correspondente serão abertos no editor.

Passo 2. Ative OPI PSRAM na Arduino IDE: Tools > PSRAM > OPI PSRAM.

Passo 3. Adicione um arquivo driver.h junto ao exemplo (mesmo fluxo de trabalho do Hello World) e selecione a combinação de placa–tela E1001:

#define BOARD_SCREEN_COMBO 520 // reTerminal E1001 (UC8179)

Passo 4. Envie o sketch. O display primeiro mostra quatro faixas em tons de cinza — preto na parte superior, depois cinza escuro, cinza claro e branco na parte inferior — e então limpa e renderiza o bitmap de image.h.

Para referência, o sketch de exemplo é assim:

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

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

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

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

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

void loop() {
// Nothing to do — ePaper holds the last frame without power
}
Quer usar sua própria imagem?

O array L4_GRAY em image.h é apenas um bitmap em tons de cinza 800×480 pré-convertido para um array em C. Para substituir pela sua própria imagem, gere novamente o array a partir de uma fonte em tons de cinza 800×480 usando qualquer conversor padrão de "imagem para array em C" e substitua L4_GRAY em image.h. O sketch em si não precisa ser alterado.

dica

A atualização em tons de cinza de 4 níveis é aproximadamente 4× mais lenta do que uma atualização preto e branco de 1 bit porque o controlador leva cada pixel por quatro tensões alvo em vez de duas. Use-a para conteúdo estático como fotos, ilustrações ou dashboards ricos em detalhes e mantenha o modo padrão de 1 bit para atualizações rápidas de interface.

Usando a biblioteca GxEPD2

Além da Seeed_GFX, você também pode usar a biblioteca GxEPD2 para controlar o display ePaper do reTerminal. A Seeed fez um fork da popular biblioteca GxEPD2 e adicionou suporte dedicado para a série reTerminal E10xx, tornando-a a escolha recomendada para usuários do reTerminal.

Instalando a biblioteca Seeed_GxEPD2

Para usar esta biblioteca com produtos reTerminal, você precisa instalar Seeed_GxEPD2 — o fork personalizado da Seeed, especificamente adaptado para a série reTerminal E10xx.

Passo 1. Acesse o repositório Seeed_GxEPD2 no GitHub. Clique no botão "Code" e selecione "Download ZIP" para salvar a biblioteca no seu computador.


Passo 2. Na Arduino IDE, instale a biblioteca a partir do arquivo baixado. Navegue até Sketch > Include Library > Add .ZIP Library... e selecione o arquivo ZIP que você acabou de baixar.

Passo 3. A biblioteca Seeed_GxEPD2 requer a Adafruit GFX Library para funcionar, que você também deve instalar. A maneira mais fácil de fazer isso é pelo Library Manager: vá em Tools > Manage Libraries..., procure por "Adafruit GFX Library" e clique em "Install".

nota

Seeed_GxEPD2 é o fork personalizado da Seeed da biblioteca GxEPD2 original, com drivers e otimizações dedicados para a série reTerminal E10xx. Recomendamos fortemente o uso deste fork em vez da biblioteca original para garantir total compatibilidade com o seu dispositivo reTerminal.

Programando o reTerminal E1001 (Tela Preto e Branco de 7,5")

O reTerminal E1001 possui um display ePaper preto e branco de 7,5" (800×480, painel GDEY075T7, controlador UC8179). O exemplo abaixo demonstra várias telas, incluindo uma tela de abertura, informações do sistema, tipografia, geometria, padrões e um layout de dashboard.

Após instalar a biblioteca Seeed_GxEPD2, você pode encontrar este exemplo na Arduino IDE em File > Examples > Seeed_GxEPD2 > GxEPD2_reTerminal_E1001, ou localizá-lo manualmente em Seeed_GxEPD2/examples/GxEPD2_reTerminal_E1001/GxEPD2_reTerminal_E1001.ino.

Clique aqui para ver o código completo
#include <SPI.h>
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeMono9pt7b.h>

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

SPIClass hspi(HSPI);

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

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

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

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

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

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

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

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

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

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

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

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

void loop() {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

A figura a seguir mostra o efeito real de exibição do exemplo do E1001:

Usando a biblioteca GxEPD2 para exibição em escala de cinza multinível

Além dos modos padrão em preto e branco e de 6 cores, alguns displays reTerminal suportam renderização em escala de cinza multinível. A biblioteca Seeed_GxEPD2 inclui exemplos dedicados de escala de cinza que ignoram os drivers normais GxEPD2_BW / GxEPD2_7C e, em vez disso, controlam diretamente o controlador do display usando formas de onda LUT personalizadas, ainda aproveitando Adafruit_GFX para desenho.

  • reTerminal E1001 (7.5" P&B): O controlador UC8179 suporta um modo de escala de cinza de 4 níveis por meio de tabelas LUT especializadas VCOM/WW/KW/WK/KK. É usado um framebuffer de 2bpp (96 KB), e cada pixel pode ser Preto, Cinza Escuro, Cinza Claro ou Branco.
  • reTerminal E1003 (10.3" Monocromático): O controlador IT8951 suporta nativamente escala de cinza de 16 níveis por meio de seu modo de forma de onda GC16. Um framebuffer de 4bpp (~1,25 MB) é alocado na PSRAM, e cada pixel pode renderizar um de 16 níveis de cinza.

Programando o reTerminal E1001 — escala de cinza de 4 níveis

O controlador UC8179 do E1001 pode ser alternado do seu modo normal de 1 bit para um modo de escala de cinza de 4 níveis enviando tabelas LUT personalizadas (VCOM, LUTWW, LUTKW, LUTWK, LUTKK). Este exemplo cria um Gray4Canvas (2bpp, 96 KB) e usa Adafruit_GFX para desenho, depois envia dois planos de bits para o controlador para renderização em escala de cinza.

Após instalar a biblioteca Seeed_GxEPD2, você pode encontrar este exemplo na IDE Arduino em File > Examples > Seeed_GxEPD2 > GxEPD2_reTerminal_E1001_Gray4, ou localizá-lo manualmente em Seeed_GxEPD2/examples/GxEPD2_reTerminal_E1001_Gray4/GxEPD2_reTerminal_E1001_Gray4.ino.

Clique aqui para ver o código completo
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>

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

#define EPD_W 800
#define EPD_H 480

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

uint8_t* buffer() { return _buf; }

private:
uint8_t* _buf;
};

Gray4Canvas canvas(EPD_W, EPD_H);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void loop() {}

A figura a seguir mostra o efeito real de exibição do exemplo de 4 níveis de escala de cinza do E1001:

nota

Os displays ePaper têm uma taxa de atualização relativamente lenta. Os displays preto e branco (E1001, E1003) normalmente atualizam em 1–3 segundos, enquanto os displays de 6 cores (E1002, E1004) podem levar de 25 a 40 segundos para uma atualização completa. Este é um comportamento normal e é um compromisso em troca do consumo de energia ultrabaixo e da excelente visibilidade sem retroiluminação.

Solução de problemas

P1: Por que o display ePaper do reTerminal não mostra nada ou não atualiza ao executar o código acima?

Esse problema pode ocorrer se você tiver inserido um cartão MicroSD no reTerminal. A razão é que o cartão MicroSD e o display ePaper compartilham o mesmo barramento SPI no reTerminal. Se um cartão MicroSD estiver inserido, mas seu pino de habilitação (chip select) não for gerenciado corretamente, isso pode causar um conflito no barramento SPI. Especificamente, o cartão MicroSD pode manter a linha BUSY em nível alto, o que impede o funcionamento correto do display ePaper — resultando em nenhuma atualização ou renovação da tela.

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

Para resolver isso, você deve garantir que o cartão MicroSD seja habilitado corretamente usando o código fornecido acima. O código inicializa e habilita o cartão MicroSD definindo os estados corretos dos pinos, o que evita conflitos no barramento SPI e permite que o cartão SD e o display ePaper funcionem juntos. Sempre use o código de inicialização recomendado ao usar um cartão MicroSD com o reTerminal para evitar esse tipo de problema.

Se o cartão MicroSD não for usado no seu projeto, recomendamos desligar o dispositivo e remover o cartão antes de executar o programa de exibição. Se o cartão tiver sido inserido no reTerminal, você precisará adicionar o código acima para garantir que a tela possa ser exibida corretamente, independentemente de você estar usando um cartão MicroSD ou não.

P2: Por que não consigo enviar programas para o reTerminal?

Se você encontrar o seguinte erro ao enviar um programa para o reTerminal.

Então, é provável que o seu Arduino IDE esteja configurado com uma velocidade de upload excessivamente alta. Altere-a para 115200 baud para resolver esse problema.

Suporte técnico e discussão sobre o produto

Obrigado por escolher nossos produtos! Estamos aqui para oferecer 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...