En este artículo veremos como utilizar un panel CrowPanel de 5.79″ para monitorear el valor del Bitcoin en tiempo real tomando datos de un sitio en Internet. Para ello usaremos la popular librería GxEPD2 y Arduino.
Introducción
En un artículo anterior vimos las características principales de los paneles ePaper CrowPanel y te expliqué a través de un ejemplo sencillo, cómo usar las librerías de Elecrow para controlar un panel de 5.79″ en Arduino.
En esta oportunidad seguiremos profundizando en las posibilidades de esta pantalla pero ahora empleando la librería GxEPD2, una popular librería de Arduino para controlar pantallas ePaper y aplicaremos todo lo aprendido en un proyecto concreto: un monitor de la cotización de Bitcoin en tiempo real.
La librería GxEPD2
GxEPD2 es la evolución de GxEPD, la primera versión de una librería para Arduino especializada en el control de pantallas de tinta electrónica o EPD (Electronic Paper Display) con interfaz SPI. Desarrollada por Jean-Marc Zingg (ZinggJM), GxEPD2 amplía las capacidades de su predecesora y ofrece soporte para una gran variedad de pantallas de los principales fabricantes, como Good Display y Waveshare.
La librería GxEPD2 depende de otra librería ampliamente utilizada, Adafruit_GFX, para su funcionamiento. Adafruit_GFX fue desarrollada por la empresa Adafruit y es una de las librerías más populares en el ecosistema Arduino. Proporciona un conjunto de funciones que permiten dibujar texto y formas geométricas en una variedad de pantallas, como LCD y OLED.
La relación entre ambas librerías se puede resumir de la siguiente manera: mientras que Adafruit_GFX se ocupa de las primitivas gráficas y proporciona las herramientas básicas para representar gráficos y texto, GxEPD2 complementa su uso al gestionar específicamente el control de pantallas de tinta electrónica (EPD) y su interfaz de comunicación SPI. De esta forma, Adafruit_GFX maneja la parte visual y GxEPD se enfoca en el manejo y funcionamiento de las pantallas EPD.
A continuación te explicaré como instalar las librerías GxEDP2 y Adafruit_GFX en el Arduino IDE 2 para usarlas con el panel Elecrow de 5.79″ y como aprovechar sus capacidades para mostrar textos, gráficos e imágenes.
En este tutorial asumo que ya tienes instalado el Arduino IDE 2 y que lo has configurado para usar el CrowPanel de 5.79 pulgadas. Si no es así te recomiento que leas este artículo anterior donde explico como hacerlo.
Instalación
Antes de poder utilizar estas librerías, es necesario instalarlas en el IDE siguiendo el procedimiento habitual.
Dado que GxEPD2 depende de Adafruit_GFX, basta con instalar GxEPD2 directamente. Durante el proceso de instalación, el IDE detectará la dependencia y nos preguntará si deseamos instalar la librería de Adafruit. En ese momento, simplemente debemos confirmar y proceder con la instalación.
El procedimiento es el mismo que debemos seguir para la instalación de cualquier librería, hacemos click en el Gestor de Bibliotecas en la barra de la izquierda o elegimos Herramientas – Gestionar bibliotecas en el menú principal.
Siguiendo cualquiera de los dos métodos te aparecerá un cuadro de búsqueda. Escribe GxEPD2 para que busque la librería y cuando la veas, pulsa sobre el botón INSTALAR.
Versión: Asegurate de instalar la última versión de GxEPD2, no te confundas con GxEPD, que es la versión anterior.
En el IDE deberías ver algo como lo siguiente:
Luego de pulsar INSTALAR, el IDE notará la dependencia con otras librerías (como Adafruit_GFX) y te preguntará si también quieres instalarlas. Confirma que si haciendo click sobre INSTALAR TODO.
Luego de unos instantes, el IDE descargará e instalará todos los archivos necesarios.
Conexiones
Una de las primeras tareas al trabajar con la librería GxEPD2 es configurar las conexiones del ePaper, indicando qué pines del microcontrolador están asignados a las diferentes señales de control. En este caso, necesitamos especificar las señales BUSY, RST (Reset), DC (Data/Command), CS (Chip Select), SCK (Serial Clock) y MOSI (Master Out, Slave In).
Por suerte, Elecrow ha compartido el esquemático de este panel en su repositorio. Esto nos permite identificar fácilmente, mediante inspección, a qué pines del ESP32 están conectadas dichas señales.
Las conexiones que nos interesan son las marcadas en rojo en la imagen anterior:
Señal ePaper | GPIO |
---|---|
BUSY | 48 |
RST | 47 |
DC | 46 |
CS | 45 |
SCK | 12 |
MOSI | 11 |
Con todas las librerías instaladas y la información de las conexiones del ePaper en mano, estamos listos para comenzar a escribir nuestro código utilizando las diversas funciones que ofrece GxEPD. Para hacerlo más interesante, lo haremos con un objetivo concreto: desarrollar una aplicación específica que aproveche al máximo este panel.
Monitor Bitcoin en tiempo real
A continuación, exploraremos diversas funciones de GxEPD2 que nos permitirán controlar nuestro ePaper. Lo haremos con un propósito específico: construir un monitor para la cotización del Bitcoin. Este dispositivo obtendrá el valor actual de la criptomoneda desde un servicio en línea y lo mostrará en la pantalla. Incluiremos imágenes, textos y números, presentando información clave de forma visualmente atractiva.
El resultado final será algo como esto:
Para desarrollar el código de esta aplicación, iremos aprendiendo a controlar cada elemento por separado. Analizaremos cada uno paso a paso, así que comencemos a desglosarlos individualmente.
Mostrando texto
Comencemos con lo más básico: mostrar texto en la pantalla. Esto no solo nos ayudará a familiarizarnos con las funciones de texto, sino que también nos permitirá aprender cómo inicializar correctamente el panel y las librerías.
GxEPD ofrece varias funciones para trabajar con textos, como escribir mensajes, seleccionar tipos de letra, ajustar colores y definir tamaños.
A continuación, te muestro un ejemplo que despliega un texto clásico, “¡Hola mundo!”, utilizando diferentes fuentes y tamaños de letras:
// Incluir las bibliotecas de GxEPD2 #include <GxEPD2_BW.h> //Incluir las definiciones de las fuentes #include <Fonts/FreeMono9pt7b.h> #include <Fonts/FreeMono12pt7b.h> #include <Fonts/FreeMonoBold12pt7b.h> #include <Fonts/FreeMonoOblique12pt7b.h> #include <Fonts/FreeSerifBold18pt7b.h> #include <Fonts/FreeSansBold24pt7b.h> // Definición de pines para CrowPanel const int EINK_BUSY = 48; const int EINK_RST = 47; const int EINK_DC = 46; const int EINK_CS = 45; const int EINK_SCK = 12; // (SCK) const int EINK_MOSI = 11; // (MOSI) //GDEY0579T93 5.79" b/w 792x272, SSD1683 //Crea objeto del display GxEPD2_BW<GxEPD2_579_GDEY0579T93, GxEPD2_579_GDEY0579T93::HEIGHT> display(GxEPD2_579_GDEY0579T93(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)); void displayPowerOn () { pinMode(7, OUTPUT); digitalWrite(7, HIGH); // Activa la alimentacion del ePaper } void setup() { displayPowerOn (); // Prende el ePaper // Inicialización del epaper display.init(115200); display.setFullWindow(); display.fillScreen(GxEPD_WHITE); display.setRotation(0); display.setTextColor(GxEPD_BLACK); display.setTextSize(1); display.setFont(&FreeMono9pt7b); display.setCursor(10, 20); display.print ("Hola mundo!"); display.setFont(&FreeMono12pt7b); display.setCursor(10, 50); display.print ("Hola mundo!"); display.setFont(&FreeMonoBold12pt7b); display.setCursor(10, 80); display.print ("Hola mundo!"); display.setFont(&FreeMonoOblique12pt7b); display.setCursor(10, 110); display.print ("Hola mundo!"); display.setFont(&FreeSerifBold18pt7b); display.setCursor(10, 150); display.print ("Hola mundo!"); display.setFont(&FreeSansBold24pt7b); display.setCursor(10, 200); display.print ("Hola mundo!"); display.display (); } void loop() { //No hace nada }
Vamos desmenuzando el código para entender como funciona:
En las primeras líneas del código encontramos varias directivas #include
. La primera incorpora la librería GxEPD2, específicamente la versión diseñada para pantallas monocromáticas (de ahí la adición de BW). Las siguientes directivas #include
se utilizan para cargar las definiciones de las fuentes que emplearemos más adelante al mostrar textos en la pantalla.
// Incluir las bibliotecas de GxEPD2 #include <GxEPD2_BW.h> //Incluir las definiciones de las fuentes #include <Fonts/FreeMono9pt7b.h> #include <Fonts/FreeMono12pt7b.h> #include <Fonts/FreeMonoBold12pt7b.h> #include <Fonts/FreeMonoOblique12pt7b.h> #include <Fonts/FreeSerifBold18pt7b.h> #include <Fonts/FreeSansBold24pt7b.h>
Las fuentes disponibles están definidas en la librería GFX_Adafruit, dentro de la carpeta Fonts. Cada fuente está definida usando un archivo de cabecera (.h)
El nombre de cada fuente nos indica el tipo de letra, estilo y tamaño. Por ejemplo, la fuente FreeSansBold24pt7b que usamos en el ejemplo anterior tiene un nombre que puede desglosarse en los siguientes elementos:
- Free: Es una fuente libre, sin restricciones de licencia
- Sans: Es una fuente de estilo moderno, como la letra Arial. También puede ser Mono, una fuente simple y monoespaciada o Serif, mas adornada como la Times New Roman.
- Bold: Significa que está resaltada con negrita. También puede ser Oblique (itálica) o regular si no dice nada.
- 24pt: Es el tamaño en puntos de la fuente.
- 7b: Indica que la fuente está definida usando valores de 7 bits.
Para seleccionar en que fuente se verá un texto debes usar el método setFont, pero primero debes cargar la definición de la fuente incluyendo el archivo de cabecera correspondiente.
Al continuar con el análisis del código, encontramos la definición de varias constantes que especifican las conexiones del ePaper, como vimos anteriormente. Estos valores serán fundamentales y los utilizaremos más adelante al crear el objeto que representará la pantalla.
// Definición de pines para CrowPanel const int EINK_BUSY = 48; const int EINK_RST = 47; const int EINK_DC = 46; const int EINK_CS = 45; const int EINK_SCK = 12; // (SCK) const int EINK_MOSI = 11; // (MOSI)
El siguiente paso es crear un objeto asociado a la pantalla. Para ello, utilizamos una plantilla (template) llamada GxEPD2_BW, que recibe como parámetros:
- GxEPD2_579_GDEY0579T93: el identificador de la clase que actúa como controlador específico para este modelo de pantalla.
- GxEPD2_579_GDEY0579T93::HEIGHT: una constante que define la altura del display y que también determina si se utiliza el modo paginado o no paginado (más adelante te explicaré qué significa esto, así que no te preocupes).
El CrowPanel de 5.79″ (así como otros modelos) no está listado entre las pantallas soportadas por GxEPD2, porque es un dispositivo que integra varios elementos además de la pantalla ePaper. Según mis pruebas, las características de la pantalla incluída coinciden con el modelo GDEY0579T93 de Good Display que es el que uso en el código de este ejemplo.
Finalmente, se usa un constructor para el objeto display (le puedes cambiar el nombre si quieres) al que se le pasa como parámetros el identificador de display nuevamente y el detalle de los pines de conexión (usando las constantes que definimos antes).
//GDEY0579T93 5.79" b/w 792x272, SSD1683 //Crea objeto del display GxEPD2_BW<GxEPD2_579_GDEY0579T93, GxEPD2_579_GDEY0579T93::HEIGHT> display(GxEPD2_579_GDEY0579T93(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
Otra característica particular del CrowPanel es que incluye un circuito para controlar el encendido de la pantalla y de esta manera gestionar eficientemente el consumo de energía.
El pin de control de este circuito es GPIO7, así que antes de intentar acceder a la pantalla, lo primero que debemos hacer es encenderla poniedo en alto (HIGH) este pin.
Para ello, he incluído la función displayPowerOn que cumple con esta tarea:
void displayPowerOn () { pinMode(7, OUTPUT); digitalWrite(7, HIGH); // Activa la alimentacion del ePaper }
Siguiendo con el código, empieza la función setup de Arduino.
Como puedes ver, consisten en llamadas a distintos métodos pertenecientes al objeto display, por eso se usa la sintaxis display.método (parámetros). Veamos que hace cada uno:
- init inicializa varios elementos del objeto display. En este caso sólo le proporcionamos el valor 115200 que es la velocidad en baudios para la salida de diagnóstico por el monitor serie. Lo puedes desactivar dandole el valor 0, pero es útil para saber qué está haciendo la librería.
- setFullWindow indica que vas a usar la pantalla completa, porque también es posible usar sólo una parte para acelerar el proceso de refresco (esto se llama refresco parcial y lo veremos luego).
- fillScreen rellena la pantalla, en este caso de color blanco (GxEPD_WHITE) borrando en contenido que podría haber quedado anteriormente. Se puede usar cualquiera de los colores que soporta este display:
- Negro (
GxEPD_BLACK
) - Blanco (
GxEPD_WHITE
)
- Negro (
- setRotation define la orientación de la pantalla en múltiplos de 90 grados.
El método setTextColor establece el color del texto, setTextSize el tamaño de las letras.
void setup() { displayPowerOn (); // Prende el ePaper // Inicialización del epaper display.init(115200); display.setFullWindow(); display.fillScreen(GxEPD_WHITE); display.setRotation(0); display.setTextColor(GxEPD_BLACK); display.setTextSize(1);
Para terminar, hay una serie de bloques que muestran textos usando distintas fuentes: el método setFont indica la fuente o tipo de letra que se usará, setCursor ubica el cursor en la posición deseada (el origen 0,0 se ubica en la esquina superior izquierda) y print imprime el texto.
display.setFont(&FreeMono9pt7b); display.setCursor(10, 20); display.print ("Hola mundo!"); display.setFont(&FreeMono12pt7b); display.setCursor(10, 50); display.print ("Hola mundo!"); display.setFont(&FreeMonoBold12pt7b); display.setCursor(10, 80); display.print ("Hola mundo!"); display.setFont(&FreeMonoOblique12pt7b); display.setCursor(10, 110); display.print ("Hola mundo!"); display.setFont(&FreeSerifBold18pt7b); display.setCursor(10, 150); display.print ("Hola mundo!"); display.setFont(&FreeSansBold24pt7b); display.setCursor(10, 200); display.print ("Hola mundo!"); display.display (); }
En realidad, los métodos anteriores no muestra nada en la pantalla. Todas las instrucciones realizan sus acciones en un buffer o área de memoria que luego debe ser volcado a la pantalla para que realmente se visualice. Una de las formas de hacerlo es empleando el método display, luego veremos otra.
Este volcado del buffer de memoria a la pantalla también se denomina refresco (refresh).
Así se ve el código en funcionamiento
Gráficos
La librería GxEPD2 también contiene métodos que permiten dibujar elementos gráficos como puntos y líneas y formas geométricas como rectángulos, círculos y triángulos, tanto vacíos como rellenos.
En el otro ejemplo uso algunos de estos métodos para mostrar un gráfico de barras. El código es el siguiente:
// Incluir las bibliotecas necesarias #include <GxEPD2_BW.h> //Incluir las definiciones de las fuentes #include <Fonts/FreeMonoBold9pt7b.h> // Definición de pines para CrowPanel const int EINK_BUSY = 48; const int EINK_RST = 47; const int EINK_DC = 46; const int EINK_CS = 45; const int EINK_SCK = 12; // (SCK) const int EINK_MOSI = 11; // (MOSI) //GDEY0579T93 5.79" b/w 792x272, SSD1683 //Crea objeto del display GxEPD2_BW<GxEPD2_579_GDEY0579T93, GxEPD2_579_GDEY0579T93::HEIGHT> display(GxEPD2_579_GDEY0579T93(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)); void displayPowerOn () { pinMode(7, OUTPUT); digitalWrite(7, HIGH); // Activa la alimentacion del ePaper } void setup() { // Definir canvas para contener al gráfico int canvasW = 500; int canvasH = 200; int canvasX = display.width()/2 - canvasW/2; int canvasY = 50; // Array para 50 valores int values [50]; // Prender pantalla displayPowerOn (); // Inicialización del epaper display.init(115200); display.setFullWindow(); display.fillScreen(GxEPD_WHITE); display.setRotation(0); display.setTextSize(1); // Configuración del texto display.setTextColor(GxEPD_BLACK); display.setFont(&FreeMonoBold9pt7b); // Limpiar la pantalla display.setFullWindow(); display.fillScreen(GxEPD_WHITE); // Fondo blanco // Dibujar Canvas display.drawRect(canvasX, canvasY, canvasW, canvasH, GxEPD_BLACK); // Escala vertical for (int x=1; x<50; x++) { display.drawLine (canvasX+(10*x), canvasY, canvasX+(10*x), canvasY+canvasH-1, GxEPD_BLACK); } // Escala horizontal for (int y=1; y<20; y++) { display.drawLine (canvasX, canvasY+(10*y), canvasX+canvasW-1, canvasY+(10*y), GxEPD_BLACK); } // Etiquetas de valores verticales display.setCursor(canvasX-70, canvasY); display.print ("$100K"); display.setCursor(canvasX-70, canvasY+(canvasH/2)); display.print (" $50K"); display.setCursor(canvasX-70, canvasY+canvasH); display.print (" $0"); //Llenar el array con valores aleatorios for (int i=0; i<50; i++) { values [i] = 70 + random(-20, 21); } // Factor de escala int scale = canvasH / 100; // Graficar los 50 valores for (int i=0; i<50; i++) { int y = values[i]*scale; display.fillRect (canvasX+(i*10), canvasY + (canvasH -y), 10, y, GxEPD_BLACK); } // Refrescar pantalla display.display (); } void loop() { //No hace nada }
Como puedes apreciar, toda la primera parte es muy parecida al ejemplo del “Hola Mundo!”. En este caso también tenemos los include, la definición de pines, la creación del objeto del display. También es muy similar la inicialización de la pantalla al inicio de setup.
Luego tenemos la sección que imprime el gráfico. Para ello he usado varias veces dos métodos que permiten dibujar líneas y rectángulos:
- drawLine (x0, y0, x1, y2, color): Dibuja una línea desde el punto (x0, y0) al punto (x1, y1) del color especificado.
- drawRect (x, y, w, h, color): Dibuja un rectángulo empezando arriba a la izquierda en (x, y) con un ancho de w pixeles y un alto (hacia abajo) de h pixeles y el color especificado. En este caso es importante notar que el origen del rectángulo es su parte superior y se lo dibuja “hacia abajo”.
- fillRect (x, y, w, h, color): Igual que el anterior pero el rectángulo está relleno con el color especificado.
Específicamente el ejemplo crea un lienzo o canvas de 500 pixeles de ancho y 200 de alto que luego es subdividido usando líneas horizontales y verticales. También tiene unas etiquetas del lado izquierdo que indican distintos valores. Como el canvas se dividie horizontalmente en 50 partes, se define un array conteniendo 50 valores aleatorios que luego se muestran como barras de color negro.
Si necesitas graficar otras entidades, la librería tiene métodos que te permiten dibujar rectángulos redondeados, círculos y triángulos, tanto vacíos como rellenos.
Imágenes
Si los elementos gráficos mencionados anteriormente no son suficientes y deseas mostrar imágenes de tipo bitmap, no te preocupes, GxEPD2 también lo permite. Además, no es necesario que la imagen ocupe todo el tamaño de la pantalla; por el contrario, puedes mostrar bitmaps de cualquier dimensión y ubicarlos en cualquier posición.
El procedimiento es el mismo que te expliqué en un artículo anterior: lo primero es tomar la imagen (que debe ser monocromática) y convertirla a un archivo de cabecera (“.h”) usando el programa image2lcd. Luego, debes incluir ese archivo en tu código con una directiva #include y ubicarlo en pantalla con el método drawBitmap.
La sintaxis de drawBitmap es la siguiente:
drawBitmap (x, y, bitmap, w, h, color);
Donde:
- x, y: la coordenada donde va a empezar el bitmap (esquina superior izquierda).
- bitmap: Identificador de la imagen (está dentro del archivo “.h”)
- w, h: ancho y alto del bitmap en pixeles
- color: el color del “frente” para diferenciarlo del fondo de pantalla.
La única precaucion que debes tener es hacerle un espejo horizontal a la imagen antes de convertirla con image2lcd.
En el siguiente ejemplo te muestro cómo cargar un bitmap (el símbolo de bitcoin en este caso) para mostrarlo en el centro de la pantalla.
// Incluir las bibliotecas necesarias #include <GxEPD2_BW.h> // Incluir el bitmap #include "bitcoin.h" // Definición de pines para CrowPanel const int EINK_BUSY = 48; const int EINK_RST = 47; const int EINK_DC = 46; const int EINK_CS = 45; const int EINK_SCK = 12; // (SCK) const int EINK_MOSI = 11; // (MOSI) //GDEY0579T93 5.79" b/w 792x272, SSD1683 //Crea objeto del display GxEPD2_BW<GxEPD2_579_GDEY0579T93, GxEPD2_579_GDEY0579T93::HEIGHT> display(GxEPD2_579_GDEY0579T93(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)); void displayPowerOn () { pinMode(7, OUTPUT); digitalWrite(7, HIGH); // Activa la alimentación del ePaper } void setup() { displayPowerOn (); // Inicialización del epaper display.init(115200); display.setFullWindow(); display.setRotation(0); // Limpiar la pantalla display.fillScreen(GxEPD_WHITE); // Fondo blanco // Ancho y alto del bitmap int bitmapW = 250; int bitmapH = 250; int bitmapX = (display.width () - bitmapW)/2; int bitmapY = (display.height() - bitmapH)/2; //Cargar bitmap display.drawBitmap(bitmapX, bitmapY, gImage_bitcoin, 250, 250, GxEPD_BLACK); // Refrescar pantalla display.display (); } void loop() { //No hace nada }
En este ejemplo se puede destacar el uso de los métodos width() y height() para devolver las dimensiones de la pantalla y así calcular con facilidad las coordenadas de origen (bitmapX y bitmapY) para que el bitmap quede bien centrado.
Luego de correr el código anterior se obtiene la siguiente imagen:
Refresco parcial
En los ejemplos anteriores, hemos utilizado diferentes métodos para mostrar textos, elementos gráficos e imágenes. En cada caso, actualizamos la pantalla completa mediante el método display()
, el cual se encarga de renderizar en la pantalla los resultados de las operaciones realizadas. Además, en todos los ejemplos inicializamos la pantalla con setFullWindow()
. Esto asegura que display()
redibuje toda la pantalla, realizando lo que se denomina un refresco completo o total.
Actualizar toda la pantalla es un proceso relativamente lento. Esto no suele ser un problema si se realiza una sola vez al inicio del programa, pero se vuelve inconveniente cuando es necesario actualizar un valor de forma frecuente, como en el caso de un sensor de temperatura que actualiza su lectura cada dos segundos. En situaciones como esta, la demora resulta inaceptable.
Por suerte, el CrowPanel 5.79″ admite una funcionalidad llamada refresco parcial, que permite actualizar únicamente una parte específica de la pantalla, sin necesidad de redibujar todos los píxeles. Esto no solo es significativamente más rápido, sino que también elimina el parpadeo característico del refresco total. Gracias al refresco parcial, es posible mostrar en el ePaper valores dinámicos que cambien con mayor rapidez.
No todos los ePaper admiten el refresco parcial. Es mas frecuente en los ePaper monocromo que en los que tienen varios colores.
El siguiente video sirve para comparar las dos técnicas de refresco. Consiste en mostrar el valor de un contador de 1 a 10, primero con refresco total y luego con refresco parcial. La diferencia es notable.
Puedes ver el código del ejemplo del video mas abajo.
En este ejemplo he organizado los métodos de una forma algo diferente, usando lo que se llama en GxEPD el modo paginado.
El modo paginado existe para aquellos microcontroladores que tienen poca memoria RAM (como un Arduino UNO). En vez de almacenar en memoria un buffer para la pantalla completa, se almacena una fracción (la mitad por ejemplo). Luego el refresco se hace en dos etapas: primero se genera en el buffer el contenido de una mitad de la pantalla y se lo transfiere y luego se usa el mismo buffer para preparar la segunda mitad y se vuelve a transferir. Esto es mas lento que hacerlo de una vez, pero usa la mitad de memoria.
Para usar el modo paginado, primero se se debe especificar que el buffer es una fracción de la pantalla completa. Esto se hace en la creación del objeto de la pantalla (display) . Para que sea la mitad debes usar HEIGHT/2 por ejemplo.
Luego, se deben escribir los métodos de acceso a la pantalla de la siguiente forma:
- Indicar el inicio del ciclo de paginación con el método firstPage
- Encerrar los métodos que modifican la pantalla en un lazo do..while.
- El bucle se debe repetir mientras el método nextPage devuelva Verdadero, lo que indica que todavía hay información que falta enviar a la pantalla.
Volviendo al ejemplo, aunque definiré el buffer para albergar la pantalla completa (el ESP32 S3 del panel tiene memoria suficiente) si organizaré el código al estilo del modo paginado:
// Incluir las librerias de GxEPD2 #include <GxEPD2_BW.h> //Incluir las definiciones de las fuentes #include <Fonts/FreeSansBold24pt7b.h> // Definición de pines para CrowPanel const int EINK_BUSY = 48; const int EINK_RST = 47; const int EINK_DC = 46; const int EINK_CS = 45; const int EINK_SCK = 12; // (SCK) const int EINK_MOSI = 11; // (MOSI) //GDEY0579T93 5.79" b/w 792x272, SSD1683 //Crea objeto del display GxEPD2_BW<GxEPD2_579_GDEY0579T93, GxEPD2_579_GDEY0579T93::HEIGHT> display(GxEPD2_579_GDEY0579T93(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)); void displayPowerOn () { pinMode(7, OUTPUT); digitalWrite(7, HIGH); // Activa la alimentacion del ePaper } void setup() { int x=0; // Contador displayPowerOn (); // Prende el ePaper // Inicialización del epaper display.init(115200); display.setRotation(0); display.setTextColor(GxEPD_BLACK); display.setTextSize(1); display.setFont(&FreeSansBold24pt7b); // Modo de refresco completo o total display.setFullWindow(); for (x=1; x<11; x++) { display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(10, 35); display.print(x); } while (display.nextPage()); delay (100); } display.fillScreen(GxEPD_WHITE); // Modo de refresco parcial display.setPartialWindow (0,0,80,40); for (x=1; x<11; x++) { display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(10, 35); display.print(x); } while (display.nextPage()); delay (100); } } void loop() { //No hace nada }
Como puedes ver la diferencia fundamental es que en la primera parte se inicializa la pantalla con setFullWindow() mientras que luego se lo hace con setPartialWindow().
Lo que hace setPartialWindow es definir una “ventana” dentro de la pantalla que es la que se va a modificar y luego actualizar, sin modificar lo que esté afuera.
La sintaxis de este método es:
setPartialWindow (x, y, w, h)
Donde:
- x, y: Coordenadas de inicio de la ventana de refresco parcial.
- w, h: Ancho y alto de la ventana.
Teclas de interfaz
Todos los modelos CrowPanel cuentan con una serie de teclas en su lateral para posibilitar la interacción del usuario. Son dos teclas y un interruptor rotatorio conectados al ESP32-S3 cuyo estado podemos leer desde nuestro programa.
Los pines de conexión podemos verlos en el esquemático:
Es decir que MENU está conectado a GPIO2, EXIT a GPIO1, la rueda para arriba a GPIO6, la rueda para abajo a GPIO4 y pulsar la rueda activa GPIO5. Cada entrada tiene un PULL_UP así que se activan por BAJO.
Uniendo las piezas
Bueno, al fin hemos llegado al final. Si llegaste hasta aquí, felicitaciones por la paciencia! 👏
Vamos a integrar todo lo aprendido en una aplicación práctica: un monitor del valor del Bitcoin. Este monitor obtendrá la cotización de esta criptomoneda en tiempo real desde CoinGecko, un sitio que ofrece una API muy sencilla. Esta API nos permite realizar solicitudes tipo GET sin necesidad de registrarnos ni de utilizar una API KEY.
Realizaremos una solicitud cada 30 segundos (es importante no hacerlas con demasiada frecuencia, ya que el sitio podría devolver un valor de 0). El valor obtenido se mostrará en el panel utilizando un refresco parcial. Además, implementaremos un control con teclas: al pulsar MENU, la cotización se mostrará en dólares estadounidenses (USD), y al pulsar EXIT, se mostrará en euros (EUR).
El endpoint utilizado para las solicitudes es el siguiente:
https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd,eur
Y los valores se devuelven en formato JSON:
{"bitcoin":{"usd":101971,"eur":98173}}
Para separar los distintos campos (lo que comúnmente se conoce como “parsear el JSON”) usaremos la librería ArduinoJson.
Puedes ver el listado completo del código a continuación:
// Incluir las bibliotecas necesarias #include <GxEPD2_BW.h> // GxEPD2 monocromo #include <WiFi.h> // Funciones para la conexión WiFi #include <HTTPClient.h> // Para hacer peticiones HTTP #include <ArduinoJson.h> // Para parsear JSON //Incluir las definiciones de las fuentes #include <Fonts/FreeSansBold24pt7b.h> // Incluir los bitmaps #include "bitcoin.h" #include "menuLateral.h" // Definición de pines para CrowPanel const int EINK_BUSY = 48; const int EINK_RST = 47; const int EINK_DC = 46; const int EINK_CS = 45; const int EINK_SCK = 12; // (SCK) const int EINK_MOSI = 11; // (MOSI) // Teclas MENU y EXIT const int KEY_MENU = 2; const int KEY_EXIT = 1; // Monedas para cotizacion const int CURR_USD = 0; const int CURR_EUR = 1; // Configuración de la red Wi-Fi const char* ssid = "******"; // Usa el nombre de tu red const char* password = "******"; // Usa tu contraseña // URL de la API de CoinGecko para obtener el precio de Bitcoin en USD y euros const char* endpoint = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd,eur"; unsigned long intervalo = 30 * 1000; // Retardo deseado (en milisegundos) unsigned long tiempoAnterior = millis() - intervalo; // Valor inicial atrasado char valorBTC [15]; // Para el valor como string int currency = CURR_USD; // Arranca en USD //GDEY0579T93 5.79" b/w 792x272, SSD1683 //Crea objeto del display GxEPD2_BW<GxEPD2_579_GDEY0579T93, GxEPD2_579_GDEY0579T93::HEIGHT> display(GxEPD2_579_GDEY0579T93(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)); void displayPowerOn () { // Activa la alimentación del ePaper pinMode(7, OUTPUT); digitalWrite(7, HIGH); } bool keyExitPressed () { // Devuelve true si está apretada EXIT if (digitalRead(KEY_EXIT)==0) { delay (100); if (digitalRead (KEY_EXIT)==0) return true; else return false; } else return false; } bool keyMenuPressed () { // Devuelve true si está apretada MENU if (digitalRead(KEY_MENU)==0) { delay (100); if (digitalRead (KEY_MENU)==0) return true; else return false; } else return false; } void displayPrintPartial (char *valor) { // Imprime el valor del BTC con refresco parcial // Definir área parcial int windowX = 400; int windowY = 132; int windowW = 320; int windowH = 64; // Configurar ventana parcial display.setPartialWindow(windowX, windowY, windowW, windowH); // Mostrar valor en ePaper con refresco parcial display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(windowX, windowY + 40); // Ajusta la posición del cursor según la fuente display.setTextColor(GxEPD_BLACK); display.print(valor); } while (display.nextPage()); } //////////////////////////////////////////////// void setup() { // Entradas de las teclas pinMode (KEY_MENU, INPUT); // MENU pinMode (KEY_EXIT, INPUT); // EXIT Serial.begin (115200); // Intentar conectar al WiFi Serial.println("Conectando a WiFi..."); WiFi.begin(ssid, password); // Espera hasta que se conecte while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // Conexión exitosa Serial.println(""); Serial.print("Conectado a: "); Serial.println(WiFi.SSID()); displayPowerOn (); // Enciende pantalla // Inicialización del epaper display.init(115200); display.setFullWindow(); display.setRotation(0); // Limpiar la pantalla display.fillScreen(GxEPD_WHITE); // Fondo blanco //Dibujar marco display.drawRect(0, 0, 792, 272, GxEPD_BLACK); display.drawRect(1, 1, 790, 270, GxEPD_BLACK); // Cargar bitmaps display.drawBitmap(50, 10, gImage_bitcoin, 250, 250, GxEPD_BLACK); // BTC display.drawBitmap (760,0, gImage_menuLateral, 32, 272, GxEPD_BLACK); // Menu lateral // Mostrar textos display.setTextColor(GxEPD_BLACK); display.setTextSize(1); display.setFont(&FreeSansBold24pt7b); display.setCursor(350, 100); display.print ("Cotizacion BTC"); // Refrescar pantalla display.display (); } void loop() { unsigned long tiempoActual = millis(); //Teclas if (keyExitPressed()) { currency = CURR_EUR; tiempoAnterior = tiempoActual - intervalo; // Fuerza actualizacion } if (keyMenuPressed()) { currency = CURR_USD; tiempoAnterior = tiempoActual - intervalo; // Fuerza actualizacion } // Verifica si ha pasado el intervalo if (tiempoActual - tiempoAnterior >= intervalo) { // Actualiza el tiempo registrado tiempoAnterior = tiempoActual; // Solo intenta hacer la petición si está conectado if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin(endpoint); // Prepara la conexión int httpCode = http.GET(); // Realiza la petición GET a la API de CoinGecko if (httpCode > 0) { // Si la respuesta es positiva, leemos el contenido String payload = http.getString(); // Usamos ArduinoJson para parsear DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, payload); if (!error) { // Parsear valores float priceUSD = doc["bitcoin"]["usd"]; float priceEUR = doc["bitcoin"]["eur"]; // Preparar según la moneda if (currency == CURR_USD) sprintf(valorBTC, "USD %.0f", priceUSD); else sprintf(valorBTC, "EUR %.0f", priceEUR); Serial.println(valorBTC); // Imprime valor usando Refresco Parcial displayPrintPartial (valorBTC); } else { // Si hay un error al parsear Serial.print("Error al parsear JSON: "); Serial.println(error.c_str()); } } else { // Si el código de respuesta HTTP es negativo o falla Serial.print("Error en la petición: "); Serial.println(httpCode); } http.end(); // Cierra la conexión } else { Serial.println("WiFi desconectado, intentando reconectar..."); WiFi.begin(ssid, password); } } }
Así se ve el monitor Bitcoin en funcionamiento:
Repositorios
El código del monitor Bitcoin y todos los ejemeplo que te mostré en este artículo están disponibles en este repositorio de GitHub.
Paneles EPD CrowPanel – CrowPanel EPD panels
Conclusiones
En este artículo, exploramos las capacidades avanzadas del CrowPanel de 5.79 pulgadas al utilizar la librería GxEPD2, una herramienta versátil y ampliamente adoptada para controlar pantallas ePaper. Desde la configuración inicial hasta la integración de funcionalidades como la impresión de texto, gráficos, y la actualización parcial, logramos construir un monitor de Bitcoin que combina diseño y funcionalidad.
A lo largo del proceso, aprendimos a:
- Instalar y configurar librerías esenciales, como GxEPD2 y Adafruit_GFX, en el entorno Arduino IDE.
- Definir conexiones físicas del panel ePaper, basándonos en los esquemáticos proporcionados por Elecrow.
- Utilizar técnicas avanzadas como el refresco parcial, que optimiza el rendimiento de las pantallas ePaper al reducir el tiempo de actualización y eliminar parpadeos innecesarios.
- Incorporar controles físicos, como las teclas del CrowPanel, para ofrecer una interfaz interactiva al usuario.
- Implementar un monitor Bitcoin en tiempo real, aprovechando la API de CoinGecko para obtener cotizaciones actualizadas en dólares y euros.
La construcción de este monitor no solo pone en práctica las funcionalidades del CrowPanel y la librería GxEPD2, sino que también ofrece un enfoque práctico para desarrollar aplicaciones dinámicas en microcontroladores, especialmente en el ámbito de la visualización de datos.
Si bien este ejemplo estuvo centrado en un monitor Bitcoin, las herramientas y conceptos presentados son aplicables a una amplia variedad de proyectos con pantallas ePaper, desde monitores ambientales hasta paneles de información. Con los ejemplos disponibles en el repositorio de GitHub, puedes personalizar y extender este proyecto para adaptarlo a tus propias necesidades.
Como siempre, espero que este contenido te haya sido de utilidad. Si quieres apoyar mi trabajo puedes hacer una donación aquí o en mi perfil de Patreon. Cualquier duda o sugerencia puedes dejarla mas abajo en la sección de comentarios. Hasta la próxima!