Skip to Content

Redesenho Paginado do Ecrã de Papel Eletrónico

Redesenho Paginado do Ecrã de Papel Eletrónico

Neste tutorial, vais aprender como realizar um redesenho por páginas num ecrã E-Paper com um Arduino e por que é frequentemente necessário quando se usam microcontroladores com pouca memória.

Comparado com computadores pessoais ou telemóveis, microcontroladores como o Arduino têm uma memória muito mais limitada. Esta memória restrita pode tornar difícil lidar com imagens grandes ou gráficos complexos. Os ecrãs E-Paper frequentemente requerem uma quantidade significativa de memória para armazenar o conteúdo total do ecrã. Quando queres atualizar o ecrã, podes não ter RAM suficiente para conter a imagem completa.

O redesenho por páginas ajuda a resolver este problema. Em vez de carregar todo o conteúdo do ecrã na memória de uma só vez, divides o ecrã em secções menores ou “páginas”. Depois podes carregar e atualizar uma página de cada vez. Este método reduz o uso de memória e permite atualizações mais suaves.

Nas secções seguintes vais aprender como realizar um redesenho por páginas num ecrã E-Paper usando um Arduino Uno.

Peças Necessárias

Vais precisar de um ecrã E-Paper. Para este tutorial escolhi um ecrã monocromático de 2,9 polegadas com resolução de 296×128 pixels. No entanto, podes também usar um E-Paper de tamanho diferente.

Quanto ao microcontrolador, qualquer Arduino ou ESP32/ESP8266 funciona, mas para realmente perceberes a necessidade dos redesenhos por páginas, sugiro que uses um Arduino Uno. A sua memória limitada torna os redesenhos por páginas essenciais, mesmo para ecrãs E-Paper pequenos.

Ecrã E-Paper 2,9″

Arduino

Arduino Uno

USB Data Sync cable Arduino

Cabo USB para Arduino UNO

Dupont wire set

Conjunto de fios Dupont

Half_breadboard56a

Breadboard

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.

O que é um Redesenho por Páginas

O redesenho por páginas é uma técnica usada em programação gráfica, especialmente em ambientes com pouca memória como microcontroladores. Permite atualizar um ecrã desenhando o conteúdo em “páginas” ou secções separadas.

Vamos ver um exemplo concreto. O ecrã E-Paper de 2,9″ que vamos usar neste tutorial tem uma resolução de 128×296 pixels. Assumindo que cada pixel é de 1-bit para um ecrã monocromático (preto e branco), isto significa que precisamos de (128×296)/8 = 4736 bytes para armazenar toda a matriz de pixels (imagem, frame) na RAM.

Normalmente, a imagem a ser exibida é primeiro escrita num buffer de ecrã dentro da RAM do microcontrolador e depois enviada para o controlador do E-Paper para atualizar o conteúdo mostrado. No entanto, um Arduino Uno tem apenas 2KB de SRAM = 2048 bytes, o que significa que não conseguiríamos preparar uma imagem completa na RAM. Em outras palavras, num Arduino Uno não podemos criar um buffer de ecrã grande o suficiente para conter uma imagem 128×296.

Redesenho por Páginas

É aqui que o Redesenho por Páginas entra em ação. Em vez de preparar e enviar a imagem inteira de uma só vez, dividimos a imagem em secções menores chamadas “páginas” que cabem na RAM do microcontrolador. Depois processamos uma página de cada vez, enviamo-las para o controlador do E-Paper, que monta a imagem e atualiza o ecrã quando a imagem está completa. Vê a imagem seguinte para ilustração:

Paged Redraw of an Image
Redesenho por Páginas de uma Imagem

Se usares a GxEPD2 biblioteca, podes ver este processo refletido no código. Para um redesenho por páginas, primeiro chamas firstPage() para iniciar o redesenho. Depois chamas repetidamente nextPage() num ciclo até que todas as páginas sejam transferidas para o ecrã.

  epd.firstPage();
  do {
    ...  // Graphics code
  } while (epd.nextPage());

Dentro do ciclo, defines as operações gráficas que queres realizar para criar o conteúdo/imagem a ser exibida. O redesenho por páginas é obviamente ineficiente, pois tens de criar o mesmo conteúdo várias vezes, mas os E-Paper são lentos a atualizar, mesmo para uma atualização parcial (< 0,3 segundos), por isso isso não é um problema.

Altura do buffer

Embora uma página possa ser uma secção retangular arbitrária, a GxEPD2 biblioteca usa páginas na forma de faixas. O comprimento da faixa é o mesmo que a largura do ecrã e a altura depende da memória disponível. No caso de um ecrã monocromático, podes calcular a altura h de uma faixa ou do buffer do ecrã da seguinte forma,

h = buff / (width / 8)

onde buff é o tamanho em bytes do buffer do ecrã que podes usar e width é a largura do ecrã.

Para E-Papers com 4 níveis de cinzento, ou 3 ou 4 cores, precisamos de 2 bits por pixel e a fórmula torna-se:

h = (buff / 2) / (width / 8)

Um Arduino tem 2048 bytes de RAM, por isso a altura máxima h para o ecrã em modo retrato seria

h = 2048 / (128 / 8) = 128 pixels

No entanto, o buffer do ecrã e o código partilham a mesma memória e não podemos usar os 2048 bytes completos. Se usares metade da memória para o buffer do ecrã, obtemos

h = 1024 / (128 / 8) = 64 pixels,

o que significa que com uma altura de ecrã de 296 pixels, teríamos 296 / 64 = 5 páginas.

Dependendo do tamanho do código e da RAM disponível do teu microcontrolador, terás de ajustar o tamanho do buffer. Se receberes a seguinte mensagem de erro ao compilar o código, sabes que o tamanho do buffer é demasiado grande:

Not enough memory; see https://support.arduino.cc/hc/en-us/articles/360013825179 for tips on reducing your footprint. 
data section exceeds available space in board.
Compilation error: data section exceeds available space in board

Nas próximas secções vamos ligar o ecrã E-Paper ao Arduino e escrever algum código para demonstrar como um redesenho por páginas é implementado.

Instalar a biblioteca GxEPD2 para E-Paper

Antes de podermos desenhar ou escrever no E-Paper, precisamos de instalar duas bibliotecas. A Adafruit_GFX biblioteca é uma biblioteca gráfica base que fornece um conjunto comum de primitivas gráficas (texto, pontos, linhas, círculos, etc.). E a GxEPD2 biblioteca fornece o software do driver gráfico para controlar um E-Paper via SPI.

Abre o Library Manager, procura por “Adafruit_GFX” e “GxEPD2” e pressiona “INSTALL”. Após a instalação, as bibliotecas devem aparecer no Library Manager da seguinte forma.

Adafruit_GFX and GxEPD2 libraries in Library Manager
Bibliotecas Adafruit_GFX e GxEPD2 no Library Manager

Ligar o E-Paper ao Arduino

O diagrama de ligações abaixo mostra como ligar a interface SPI do ecrã E-Paper a um Arduino. A maioria dos pinos pode ser configurada livremente, mas para as linhas DIN e CLK precisamos de usar pinos específicos do SPI. No caso do Arduino Uno, DIN está no pino 11 e CLK no pino 13.

Connecting E-Paper to Arduino Uno via SPI
Ligar o E-Paper ao Arduino Uno via SPI

A tabela seguinte lista todas as outras ligações que tens de fazer.

Ecrã E-PaperArduino UNO
CS/SS4
SCL/SCK/CLK13
SDA/DIN/MOSI11
BUSY7
RES/RST6
DC5
VCC3.3V
GNDG

Redesenho por Páginas para E-Paper com Arduino Uno

Vamos desenhar a seguinte imagem de teste no ecrã E-Paper:

Test Picture
Imagem de Teste

Abaixo encontras o código correspondente. Dá uma vista rápida primeiro, depois discutimos os detalhes.

#include "GxEPD2_BW.h"

#define EPD_CS 4
#define EPD_DC 5
#define EPD_RST 6
#define EPD_BUSY 7
// CLK = 13
// DIN = 11

#define BUFF 1024
#define HEIGHT(EPD) ((BUFF / 1) / (EPD::WIDTH / 8))

GxEPD2_BW<GxEPD2_290_BS, HEIGHT(GxEPD2_290_BS)>
   epd(GxEPD2_290_BS(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));

void setup() {
  epd.init(115200);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
  epd.setFullWindow();

  epd.firstPage();
  do {
    epd.fillScreen(GxEPD_WHITE);
    epd.fillRect(30, 30, 60, 60, GxEPD_BLACK);
    epd.fillTriangle (100, 120, 180, 120, 140, 60, GxEPD_BLACK);
    epd.setCursor(200, 60); epd.print("TEST");
  } while (epd.nextPage());

  epd.hibernate();
}

void loop() {}

Começamos por incluir a biblioteca gráfica necessária e definir algumas constantes para os pinos SPI. Lembra-te que os pinos DIN e CLK são fixos e específicos para o teu microcontrolador, enquanto os outros podem ser alterados.

#include "GxEPD2_BW.h"

#define EPD_CS 4
#define EPD_DC 5
#define EPD_RST 6
#define EPD_BUSY 7
// CLK = 13
// DIN = 11

De seguida definimos o tamanho do buffer do ecrã BUFF e a altura da página HIGHT. A altura é calculada através de uma macro que recebe o objeto do ecrã EPD como parâmetro e usa a sua propriedade de largura (EPD::WIDTH) para determinar a altura da página.

#define BUFF 1024
#define HEIGHT(EPD) ((BUFF / 1) / (EPD::WIDTH / 8))

O cálculo segue a fórmula que discutimos antes. Para um ecrã a três cores usarias a seguinte fórmula em vez disso:

#define HEIGHT(EPD) ((BUFF / 2) / (EPD::WIDTH / 8))

Agora podemos usar a macro HIGHT para criar o objeto do ecrã epd. Isto parece complexo, mas simplesmente usa as constantes dos pinos, a altura e um tipo de ecrã como parâmetros de template para criar o ecrã.

GxEPD2_BW<GxEPD2_290_BS, HEIGHT(GxEPD2_290_BS)>
   epd(GxEPD2_290_BS(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));

Se o teu ecrã não mostrar nada ou mostrar texto/imagens corrompidas, então ou o ecrã não está corretamente ligado ou o tipo de ecrã escolhido está errado.

Readme for GxEPD2 biblioteca lista todos os ecrãs suportados e podes encontrar os detalhes nos ficheiros header, por exemplo GxEPD2.h. Encontra o driver específico para o teu ecrã. E talvez dê uma vista de olhos ao tutorial Interfacing Arduino with E-ink Display, onde ligamos um E-Paper a três cores a um Arduino e a um ESP32.

Função Setup

Na função setup, definimos primeiro alguns parâmetros gráficos como a orientação (retrato), o tamanho do texto, a cor do texto e o modo de atualização (janela completa).

  epd.init(115200);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
  epd.setFullWindow();

Existe também uma “atualização parcial”, mas não vamos usá-la aqui. Se quiseres saber mais sobre isso, dá uma vista de olhos ao tutorial Partial Refresh of e-Paper Display.

Finalmente, chegamos à parte onde o redesenho por páginas é realizado. Começamos por chamar firstPage() para iniciar o redesenho. Depois desenhamos repetidamente o retângulo, triângulo e texto numa do-while loop até que nextPage() retorne falso.

  epd.firstPage();
  do {
    epd.fillScreen(GxEPD_WHITE);
    epd.fillRect(30, 30, 60, 60, GxEPD_BLACK);
    epd.fillTriangle (100, 120, 180, 120, 140, 60, GxEPD_BLACK);
    epd.setCursor(200, 60); epd.print("TEST");
  } while (epd.nextPage());

Este ciclo essencialmente preenche o buffer do ecrã com o conteúdo de uma página, envia essa página para o ecrã E-Paper, onde o controlador junta as páginas até a imagem estar completa.

Não vais ver o ecrã a atualizar página a página. Em vez disso, o controlador do ecrã espera até que todas as páginas tenham sido recebidas e depois realiza uma atualização completa do ecrã. Vê o pequeno vídeo abaixo:

Full refresh with Paged Redraw
Atualização completa com Redesenho por Páginas

A propósito, se quiseres saber a altura real da página que o ecrã está a usar, podes chamar a seguinte função na função setup para descobrir:

  Serial.println(epd.pageHeight());

Função Loop

A função loop neste exemplo está vazia, pois não há atualização periódica de conteúdo.

void loop() {}

Mas digamos que queres implementar um Digital Clock on e-Paper Display, a função loop conteria o código para o redesenho por páginas em vez da função setup.

Função de Desenho

Se tiveres aplicações mais complexas, é melhor usar uma função de desenho em vez da do-while loop para o redesenho por páginas. A biblioteca GxEPD2 permite definir uma função de desenho e depois chamar epd.drawPaged(...) para desenho por páginas. Aqui está a estrutura do código:

void draw(const void* pv) {
  ... // Graphics code
}

void setup() {
  ...
  epd.drawPaged(draw, 0);
  epd.hibernate();
}

void loop() {}

A função de desenho draw() recebe um ponteiro pv, que te permite passar parâmetros quando chamas epd.drawPaged(draw, 0). Mas podes simplesmente passar 0 como ponteiro de parâmetro, se não precisares disso.

O uso de funções de desenho para redesenhos por páginas torna o código mais legível e extensível. Aqui está o nosso código de teste novamente, mas agora usando uma função de desenho em vez da do-while loop:

#include "GxEPD2_BW.h"

#define EPD_CS 4
#define EPD_DC 5
#define EPD_RST 6
#define EPD_BUSY 7
// SCL(SCK)=13,
// SDA(MOSI)=11

#define BUFF 1024
#define HEIGHT(EPD) ((BUFF / 1) / (EPD::WIDTH / 8))

GxEPD2_BW<GxEPD2_290_BS, HEIGHT(GxEPD2_290_BS)>
  epd(GxEPD2_290_BS(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));


void draw(const void* pv) {
  epd.fillScreen(GxEPD_WHITE);
  epd.fillRect(30, 30, 60, 60, GxEPD_BLACK);
  epd.fillTriangle(100, 120, 180, 120, 140, 60, GxEPD_BLACK);
  epd.setCursor(200, 60); epd.print("TEST");
}

void setup() {
  epd.init(115200);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
  epd.setFullWindow();
  epd.drawPaged(draw, 0);
  epd.hibernate();
}

void loop() {}

E é isso! Agora sabes o que é um Redesenho por Páginas e como implementá-lo.

Conclusões

Neste tutorial aprendeste como realizar um redesenho por páginas num ecrã E-Paper monocromático com um Arduino Uno. Se tiveres um E-Paper tricolor em vez de monocromático ou um ESP32, dá uma vista de olhos ao Interfacing Arduino with E-ink Display.

Se quiseres aprender mais sobre atualização parcial vs completa, sugiro o nosso tutorial Partial Refresh of e-Paper Display.

Além disso, uma combinação de redesenho por páginas, atualização parcial e completa de ecrãs E-Paper de diferentes tamanhos é usada nos tutoriais Temperature Plotter on e-Paper Display, Digital Clock on e-Paper Display e Analog Clock on e-Paper Display. Se precisares de exemplos de aplicação, vais encontrá-los lá.

Se tiveres outras perguntas, não hesites em perguntar na secção de comentários.

Boas experiências a criar ; )