Neste blog mostro como pode usar a biblioteca GxEPD2 para desenhar num E-Paper CrowPanel de 4,2 polegadas.
Os E-Papers CrowPanel da Elecrow vêm com algum código de exemplo que demonstra como desenhar no ecrã. No entanto, a funcionalidade desse código é bastante limitada. Seria muito melhor se pudéssemos usar a mais capaz GxEPD2 Libary. Infelizmente, em dezembro de 2024 não existe suporte direto para nenhum dos ecrãs E-Paper CrowPanel na biblioteca GxEPD2 (veja GxEPD2_display_selection_new_style.h).
No entanto, com um pequeno ajuste podemos fazer a biblioteca GxEPD2 funcionar no E-Paper CrowPanel de 4,2 polegadas e nas secções seguintes vou mostrar como isso é feito.
Peças Necessárias
Vai precisar de um E-Paper CrowPanel. Neste tutorial foco-me na versão de 4,2 polegadas, que é a que deve adquirir. Existem outros tamanhos de ecrã e os exemplos de código devem funcionar em grande parte, mas terá de fazer alterações devido à diferença na resolução e no driver do ecrã.

E-Paper CrowPanel de 4,2 polegadas
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
E-Paper CrowPanel de 4,2 polegadas
O E-Paper de 4,2 polegadas da CrowPanel é um ecrã a preto e branco, com resolução de 400×300 pixels. Vem como um módulo integrado que contém um chip ESP32-S3, vários botões para controlar o ecrã e mais. A imagem seguinte mostra a frente do ecrã com três botões de controlo ao lado:

Botões
Os botões MENU e EXIT são botões simples de pressão, enquanto o Rotary Switch suporta funções de cima, baixo e confirmar/OK. Os botões são totalmente programáveis. A tabela seguinte mostra quais os pinos GPIO ligados a cada botão:
| Botão | GPIO |
|---|---|
| MENU | IO2 |
| Rotary Switch Down | IO4 |
| Rotary Switch Up | IO6 |
| Rotary Switch CONF | IO5 |
| EXIT | IO1 |
Características
Além disso, o módulo do ecrã contém um slot para cartão TF, uma interface para bateria LiPo (SH1.0-2Pin) com capacidade de carregamento e uma interface GPIO. A programação (e alimentação) é feita via uma porta USB-C.

GPIO
Para GPIO estão disponíveis os seguintes pinos: IO3; IO9; IO15; IO17; IO19; IO21; IO8; IO14; IO16; IO18; IO20; IO38 e o pinout da porta GPIO pode ser encontrado na serigrafia mas também na parte de trás do módulo:

Reset e Boot
Na parte de trás do módulo estão os botões Boot e Reset. Infelizmente, eles sobressaem para além da caixa e são facilmente pressionados acidentalmente ao colocar o módulo numa mesa. Sugiro que os corte um pouco (com um alicate) para evitar isso.

Outros E-Papers CrowPanel
O ecrã de 4,2 polegadas é um de uma série inteira de ecrãs E-Paper semelhantes mas com diferentes resoluções, chips ESP32 e drivers. Veja a tabela abaixo para uma visão geral:

Para este tutorial, usei o ecrã de 4,2 polegadas, mas a maior parte do código provavelmente funciona para os outros tamanhos também. No entanto, terá de encontrar um driver correspondente na biblioteca GxEPD2. Mais sobre isso na próxima secção.
Biblioteca GxEPD2
A GxEPD2 Libary é uma biblioteca Arduino/ESP32 para ecrãs E-Paper SPI. Como mencionado antes, suporta muitos ecrãs E-Paper diferentes, mas até agora não suporta diretamente os ecrãs E-Paper CrowPanel.
No entanto, comparada com o código demo que vem com os ecrãs CrowPanel, a GxEPD2 Libary é preferível. Tem mais functions e melhor documentação, pois deriva da Adafruit-GFX-Library, e pode ser instalada e usada facilmente.
O mais importante é que pode (re)usar código escrito com a GxEPD2 Libary numa vasta gama de ecrãs E-Paper diferentes, sem ter de reescrevê-lo para ecrãs específicos.
Carregar Código para o E-Paper CrowPanel
Carregar código para o módulo E-Paper via Arduino IDE é fácil. Basta ligar o cabo USB e selecionar a placa “ESP32S3 Dev Module”:

Em “Tools” selecione preferencialmente “Huge App” para o Partition Scheme e “OPI PSRAM” para as definições PSRAM. Embora não seja essencial para o código deste tutorial, parecem ser as definições recomendadas.

Código para usar a biblioteca GxEPD2 com o E-Paper CrowPanel
Nesta secção mostro como fazer a biblioteca GxEPD2 funcionar com o E-Paper CrowPanel. Há dois passos importantes. Primeiro, precisamos encontrar um driver GxEPD2 que tenha a mesma resolução e suporte o mesmo chip driver que o ecrã CrowPanel.
O ecrã de 4,2 polegadas usa o chip driver SSD1683 e tem resolução de 400×300 pixels. Se procurar na lista de drivers suportados em GxEPD2_display_selection_new_style.h encontrará duas correspondências:
//#define GxEPD2_DRIVER_CLASS GxEPD2_420_GDEY042T81 // GDEY042T81 400x300, SSD1683 (no inking) //#define GxEPD2_DRIVER_CLASS GxEPD2_420_GYE042A87 // GYE042A87, 400x300, SSD1683 (HINK-E042-A07-FPC-A1)
No entanto, se tentar esses drivers, só verá uma imagem muito fraca do que desenhar no ecrã. Analisei melhor o demo code e descobri que também tem de ligar a alimentação do ecrã. Isto é feito definindo a saída no GPIO7 para HIGH.
O exemplo seguinte imprime o texto “Makerguides” no centro do ecrã E-Paper de 4,2 polegadas. Dê uma vista de olhos rápida e depois discutimos os detalhes:
#include "GxEPD2_BW.h"
#define PWR 7
#define BUSY 48
#define RES 47
#define DC 46
#define CS 45
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
void epdPower(int state) {
pinMode(PWR, OUTPUT);
digitalWrite(PWR, state);
}
void epdInit() {
epd.init(115200, true, 50, false);
epd.setRotation(0);
epd.setTextColor(GxEPD_BLACK);
epd.setTextSize(4);
epd.setFullWindow();
}
void setup() {
epdPower(HIGH);
epdInit();
epd.fillScreen(GxEPD_WHITE);
epd.drawRect(0, 0, 400, 300, GxEPD_BLACK);
epd.setCursor(70, 130);
epd.print("Makerguides");
epd.display();
epd.hibernate();
epdPower(LOW);
}
void loop() {}
Incluir
Primeiro, incluímos a GxEPD2 Libary e, como estamos a usar um E-Paper a preto e branco, incluímos “GxEPD2_BW.h”:
#include "GxEPD2_BW.h"
Se ainda não instalou a GxEPD2 Libary, terá de o fazer para que este código funcione. Também vai precisar da biblioteca Adafruit_GFX. Basta install the libraries da forma habitual. Após a instalação, elas devem aparecer no Library Manager da seguinte forma.

Não precisa de nenhuma das demo code que vêm com os ecrãs CrowPanel.
Definições dos Pinos
De seguida, precisamos definir os pinos SPI que o ecrã usa para comunicar com o ESP32.
#define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45
Encontrei estes pinos ao olhar para o Schematics of the CrowPanel 4.2-inch E-Paper display. Aqui está a secção relevante dos esquemas que descreve a ligação ao E-Paper:

Objeto do Ecrã
Como mencionado antes, não há suporte direto para o E-Paper CrowPanel de 4,2 polegadas na GxEPD2 Libary. Portanto, usamos uma correspondência próxima para criar o objeto epd que representa o nosso ecrã E-Paper:
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
Alternativamente, pode também usar a classe GxEPD2_420_GDEY042T81. Ambas parecem funcionar, mas no código seguinte usei a classe GxEPD2_420_GYE042A87.
Função epdPower
A função epdPower() é crítica e especial. Para outros ecrãs E-Paper não é necessária, mas para o CrowPanel E-Paper é. Define o GPIO7 como alto ou baixo e assim ativa ou desativa o ecrã E-Paper.
void epdPower(int state) {
pinMode(PWR, OUTPUT);
digitalWrite(PWR, state);
}
Note que pode desativar o E-Paper (epdPower(LOW)) depois de desenhar nele. Ele continuará a mostrar a imagem, mesmo sem alimentação.
Função epdInit
A função epdInit() realiza a configuração inicial habitual para os gráficos, como rotação do ecrã, cor do texto, tamanho do texto e modo de atualização.
void epdInit() {
epd.init(115200, true, 50, false);
epd.setRotation(0);
epd.setTextColor(GxEPD_BLACK);
epd.setTextSize(4);
epd.setFullWindow();
}
Função setup
Na função setup(), ligamos o ecrã, preenchemos o ecrã de branco, desenhamos um quadro, imprimimos o texto “Makerguides”, mostramos-no e depois desligamos o ecrã novamente.
void setup() {
epdPower(HIGH);
epdInit();
epd.fillScreen(GxEPD_WHITE);
epd.drawRect(0, 0, 400, 300, GxEPD_BLACK);
epd.setCursor(70, 130);
epd.print("Makerguides");
epd.display();
epd.hibernate();
epdPower(LOW);
}
Se carregar e executar este código, deverá ver a seguinte saída no ecrã E-Paper:

Assim, com a adição da função epdPower() pode usar a GxEPD2 Libary normalmente para desenhar e escrever no E-Paper CrowPanel de 4,2 polegadas. Na próxima secção testamos a atualização parcial.
Atualização parcial do E-Paper CrowPanel
No código acima realizámos uma atualização completa do conteúdo do E-Paper. Esta atualização completa é ocasionalmente necessária para remover imagens residuais, mas é lenta (vários segundos) e faz o E-Paper piscar bastante.
Para a maioria das aplicações, quer atualizar apenas uma parte do ecrã, sem piscar e muito mais rápido. Por exemplo, para mostrar os dígitos de um relógio em funcionamento. Uma atualização parcial permite isso. Para mais detalhes, veja o tutorial Partial Refresh of e-Paper Display.
O código seguinte é essencialmente o mesmo usado nesse tutorial. Primeiro realiza uma atualização completa para limpar o ecrã e imprime o texto “msec:”. Depois realiza continuamente uma atualização parcial e atualiza os milissegundos em execução. Abaixo está uma captura de ecrã da saída do ecrã:

Aqui está o código completo para este teste. A única diferença para o do Partial Refresh of e-Paper Display é que precisamos usar novamente a função epdPower() para ligar o ecrã. E claro, o driver do ecrã é diferente.
#include "GxEPD2_BW.h"
#define PWR 7
#define BUSY 48
#define RES 47
#define DC 46
#define CS 45
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
void epdPower(int state) {
pinMode(PWR, OUTPUT);
digitalWrite(PWR, state);
}
void drawFull(const void* pv) {
epd.setFullWindow();
epd.setCursor(40, 60);
epd.print("msec: ");
}
void drawPartial(const void* pv) {
uint16_t x = 120, y = 50, w = 130, h = 34;
epd.setPartialWindow(x, y, w, h);
epd.setCursor(x + 30, y + 10);
epd.print(millis());
epd.drawRect(x, y, w, h, GxEPD_BLACK);
}
void setup() {
epdPower(HIGH);
epd.init(115200, true, 50, false);
epd.setRotation(0);
epd.setTextSize(2);
epd.setTextColor(GxEPD_BLACK);
epd.drawPaged(drawFull, 0);
}
void loop() {
epd.drawPaged(drawPartial, 0);
epd.hibernate();
}
Se carregar e executar este código, deverá ver a seguinte saída. Note que os milissegundos são atualizados em menos de 0,5 segundos e que quase não há piscar (apenas um pouco à volta do quadro):

Assim, as atualizações completas e parciais funcionam bem no E-Paper CrowPanel de 4,2 polegadas com o driver GxEPD2 escolhido GxEPD2_420_GYE042A87.
Na próxima e última secção, mostro como usar a atualização parcial e os botões do ecrã CrowPanel para implementar um menu simples que controla três LEDs.
Controlar GPIO via Menu no E-Paper CrowPanel
Um dos demos para o ecrã CrowPanel mostra um menu simples com itens que podem ser ativados ou desativados através de pressionar botões.
Aqui vamos construir algo semelhante. Mas vamos usar a biblioteca GxEPD2 e os nossos botões vão controlar três LEDs. A captura de ecrã abaixo mostra como o menu vai parecer:

Vamos poder navegar no menu com os botões para cima e para baixo do rotary switch e selecionar um item do menu com o botão de confirmação. Se um item estiver selecionado, um LED da cor correspondente ligado ao GPIO será ligado, por exemplo, selecionar “Red” liga um LED vermelho:

Diagrama de Ligações
O diagrama de ligações abaixo mostra como ligar os três LEDs à porta GPIO do ecrã CrowPanel. O cátodo de cada LED está ligado ao terra (GND). O ânodo do LED vermelho está ligado ao GPIO3, o LED verde ao GPIO9 e o azul ao GPIO15. Usei um resistor limitador de corrente de 220Ω para cada LED:

Código para navegar no menu e controlar os LEDs
Abaixo encontra o código para navegar no menu, selecionar e desselecionar itens e controlar os LEDs em conformidade:
#include "GxEPD2_BW.h"
#include <Fonts/FreeMonoBold24pt7b.h>
#define PWR 7
#define BUSY 48
#define RES 47
#define DC 46
#define CS 45
#define W 400
#define H 300
#define HOME_KEY 2
#define EXIT_KEY 1
#define PREV_KEY 6
#define NEXT_KEY 4
#define OK_KEY 5
int cursor = 0;
const int xoff = 70;
const int n_items = 3;
const char* items[] = { "Red", "Green", "Blue" };
const int pins[] = { 3, 9, 15 };
bool states[] = { false, false, false };
const int ypos[] = { 100, 100 + 60, 100 + 2 * 60 };
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
void epdPower(int state) {
pinMode(PWR, OUTPUT);
digitalWrite(PWR, state);
}
void drawCursor() {
int16_t s = 12;
int16_t x0 = xoff - s;
int16_t y0 = ypos[cursor] - 12;
int16_t x1 = x0 - s;
int16_t y1 = y0 - s;
int16_t x2 = x0 - s;
int16_t y2 = y0 + s;
epd.fillTriangle(x0, y0, x1, y1, x2, y2, GxEPD_BLACK);
}
void updateCursor(const void* pv) {
epd.setPartialWindow(0, 0, xoff, 300);
drawCursor();
}
void drawItem(int index) {
int16_t xb, yb;
uint16_t wb, hb;
epd.setCursor(xoff + 5, ypos[index]);
epd.getTextBounds(items[index], xoff + 5, ypos[index], &xb, &yb, &wb, &hb);
if (states[index]) {
epd.setTextColor(GxEPD_WHITE);
epd.fillRect(xb - 5, yb - 3, W - 2 * xoff, hb + 6, GxEPD_BLACK);
} else {
epd.setTextColor(GxEPD_BLACK);
epd.fillRect(xb - 5, yb - 3, W - 2 * xoff, hb + 6, GxEPD_WHITE);
}
epd.print(items[index]);
}
void updateItems(const void* pv) {
epd.setPartialWindow(xoff, 0, W - xoff, 300);
drawItems();
}
void drawItems() {
for (int i = 0; i < n_items; i++) {
drawItem(i);
}
}
void initEPD() {
epdPower(HIGH);
epd.init(115200, true, 50, false);
epd.setRotation(0);
epd.setTextColor(GxEPD_BLACK);
epd.setFont(&FreeMonoBold24pt7b);
epd.setFullWindow();
epd.fillScreen(GxEPD_WHITE);
}
void initButtons() {
pinMode(HOME_KEY, INPUT);
pinMode(EXIT_KEY, INPUT);
pinMode(PREV_KEY, INPUT);
pinMode(NEXT_KEY, INPUT);
pinMode(OK_KEY, INPUT);
}
void initPins() {
for (int i = 0; i < n_items; i++) {
pinMode(pins[i], OUTPUT);
}
}
void setup() {
initEPD();
initButtons();
initPins();
drawItems();
drawCursor();
epd.display();
}
void loop() {
if (!digitalRead(PREV_KEY)) {
cursor = --cursor < 0 ? n_items - 1 : cursor;
epd.drawPaged(updateCursor, 0);
}
if (!digitalRead(NEXT_KEY)) {
cursor = ++cursor >= n_items ? 0 : cursor;
epd.drawPaged(updateCursor, 0);
}
if (!digitalRead(OK_KEY)) {
states[cursor] = !states[cursor];
epd.drawPaged(updateItems, 0);
digitalWrite(pins[cursor], states[cursor] ? HIGH : LOW);
}
if (!digitalRead(HOME_KEY)) {
cursor = 0;
epd.drawPaged(updateCursor, 0);
}
if (!digitalRead(EXIT_KEY)) {
for (int i = 0; i < n_items; i++) {
states[i] = false;
digitalWrite(pins[i], LOW);
}
epd.drawPaged(updateItems, 0);
}
delay(100);
}
Vamos decompor o código nos seus componentes para melhor compreensão.
Bibliotecas e Constantes
Começamos por incluir as bibliotecas necessárias para o ecrã E-Paper e fonts. As constantes definem os pinos usados para o ecrã e botões, bem como as dimensões do ecrã.
#include "GxEPD2_BW.h" #include <Fonts/FreeMonoBold24pt7b.h> #define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45 #define W 400 #define H 300
Definições dos Botões
Definimos os pinos para os botões que serão usados para navegar no menu. Cada botão é atribuído a uma função específica: HOME, EXIT, PREV, NEXT e OK.
#define HOME_KEY 2 #define EXIT_KEY 1 #define PREV_KEY 6 #define NEXT_KEY 4 #define OK_KEY 5
Variáveis
São declaradas várias variáveis para gerir o estado do menu. cursor acompanha a seleção atual, items contém os nomes dos LEDs, e states indica se cada LED está ligado ou desligado. ypos especifica onde os itens do menu estão localizados no ecrã.
int cursor = 0;
const char* items[] = { "Red", "Green", "Blue" };
const int pins[] = { 3, 9, 15 };
bool states[] = { false, false, false };
const int ypos[] = { 100, 160, 220 };
Objeto do E-Paper
Como antes, o objeto do ecrã E-Paper é criado usando a classe GxEPD2_BW. Isto configura o ecrã com os pinos especificados. Se tiver um ecrã CrowPanel diferente, terá de alterar esta linha.
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
Função de Controlo de Energia
A função epdPower() controla a energia para o ecrã E-Paper. Define o pino de energia como saída e escreve o estado especificado (HIGH ou LOW).
void epdPower(int state) {
pinMode(PWR, OUTPUT);
digitalWrite(PWR, state);
}
Função de Desenho do Cursor
A função drawCursor() representa visualmente a seleção atual no menu desenhando um triângulo na posição correspondente.
void drawCursor() {
int16_t s = 12;
int16_t x0 = xoff - s;
int16_t y0 = ypos[cursor] - 12;
epd.fillTriangle(x0, y0, x1, y1, x2, y2, GxEPD_BLACK);
}
Note que a função updateCursor() correspondente realiza uma atualização parcial e só atualiza a secção do ecrã onde o cursor é mostrado. Será chamada quando os botões para cima ou para baixo forem pressionados, por exemplo.
void updateCursor(const void* pv) {
epd.setPartialWindow(0, 0, xoff, 300);
drawCursor();
}
Função de Desenho de Itens
A função drawItem() mostra um item no menu. Verifica o estado do LED e muda o fundo para preto se o item estiver selecionado.
void drawItem(int index) {
epd.setCursor(xoff + 5, ypos[index]);
if (states[index]) {
epd.setTextColor(GxEPD_WHITE);
epd.fillRect(...);
} else {
epd.setTextColor(GxEPD_BLACK);
epd.fillRect(...);
}
epd.print(items[index]);
}
Para desenhar todos os itens do menu é usada a função drawItems():
void drawItems() {
for (int i = 0; i < n_items; i++) {
drawItem(i);
}
}
E o estado de todos os itens é atualizado realizando uma atualização parcial do ecrã, onde os itens do menu estão localizados:
void updateItems(const void* pv) {
epd.setPartialWindow(xoff, 0, W - xoff, 300);
drawItems();
}
Funções de Inicialização
São definidas várias funções de inicialização: initEPD() inicializa o ecrã E-Paper, initButtons() define os pinos dos botões como entradas, e initPins() define os pinos dos LEDs como saídas.
void initEPD() {
epdPower(HIGH);
...
}
void initButtons() {
pinMode(HOME_KEY, INPUT);
...
}
void initPins() {
for (int i = 0; i < n_items; i++) {
pinMode(pins[i], OUTPUT);
}
}
Função Setup
A função setup() é chamada uma vez quando o programa começa. Inicializa o ecrã E-Paper, os botões e os pinos dos LEDs, e desenha os itens iniciais e o cursor no ecrã.
void setup() {
initEPD();
initButtons();
initPins();
drawItems();
drawCursor();
epd.display();
}
Função Loop
A função loop() corre continuamente. Verifica o estado dos botões e atualiza a posição do cursor ou alterna os estados dos LEDs com base na entrada do utilizador. O ecrã é atualizado em conformidade usando um redesenho paginado com atualização parcial.
void loop() {
if (!digitalRead(PREV_KEY)) {
...
}
if (!digitalRead(NEXT_KEY)) {
...
}
if (!digitalRead(OK_KEY)) {
...
}
if (!digitalRead(HOME_KEY)) {
...
}
if (!digitalRead(EXIT_KEY)) {
...
}
delay(100);
}
O botão PREV_KEY move o cursor um item do menu para cima e o NEXT_KEY move-o um item para baixo. Se chegar ao fim ou ao início do menu, o cursor volta ao início ou ao fim.
Com o botão OK_KEY pode alternar o estado de um item. O botão HOME_KEY move o cursor para o primeiro item do menu e o EXIT_KEY desseleciona todos os itens.
Demonstração do Código
Em resumo, este código fornece uma interface de utilizador num ecrã E-Paper para controlar três LEDs. O utilizador pode navegar pelas opções e ligar ou desligar os LEDs usando botões físicos. O vídeo curto seguinte mostra como é:

Conclusões
Neste tutorial aprendeu como usar a biblioteca GxEPD2 para desenhar e escrever num E-Paper CrowPanel de 4,2 polegadas.
Se precisar de mais informações sobre ecrãs E-Paper, veja o Interfacing Arduino with E-ink Display e o tutorial Partial Refresh of e-Paper Display.
Além disso, se procura outras aplicações, os tutoriais Weather Station on e-Paper Display, Temperature Plotter on e-Paper Display, Digital Clock on e-Paper Display e Analog Clock on e-Paper Display podem ser do seu interesse.
Se tiver alguma dúvida, sinta-se à vontade para perguntar na secção de comentários. Boas experiências ; )
Links
Abaixo alguns links sobre o E-Paper CrowPanel de 4,2 polegadas que achei úteis ao escrever este tutorial:

