En este tutorial aprenderás cómo coordinar animaciones de zonas Parola en una pantalla de matriz LED MAX7219. La biblioteca MD_Parola es muy popular y facilita la programación de animaciones como el desplazamiento de texto. Además, te permite dividir la pantalla en varias zonas y ejecutar diferentes animaciones en cada una de ellas.
Sin embargo, coordinar animaciones en varias zonas no es sencillo. Por ejemplo, ¿cómo reiniciar una animación en una zona sin afectar la animación en otra zona? ¿O cómo reiniciar todas las animaciones solo cuando todas hayan terminado, incluso si se ejecutan a diferentes velocidades?
Este tutorial te proporcionará una estructura de código para manejar fácilmente este tipo de escenarios de animación.
Componentes necesarios
He utilizado un Arduino Uno para este proyecto, pero cualquier otra placa Arduino, o una placa ESP8266/ESP32, también funcionará. Además, he usado una pantalla de matriz LED MAX7219 con 4 módulos. Sin embargo, el código y el cableado para pantallas con diferente número de módulos es prácticamente el mismo.

Pantalla de matriz LED MAX7219

Arduino Uno

Juego de cables Dupont

Cable USB para Arduino UNO
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.
Conexión de la pantalla LED MAX7219 con Arduino
En este tutorial usaremos una pantalla de matriz LED MAX7219 para mostrar animaciones. El controlador de pantalla MAX7219 se comunica con Arduino mediante SPI (Interfaz Periférica Serial). Por lo tanto, lo primero que debes hacer es conectar la pantalla al Arduino usando la interfaz SPI.
Cableado
El siguiente diagrama muestra el cableado entre la pantalla MAX7219 y un Arduino Uno. Asegúrate de conectar al lado de entrada (DIN) y no al lado de salida (DOUT) de la pantalla.

Empieza conectando los pines de la interfaz SPI: el pin 11 del Arduino se conecta al pin DIN de la pantalla. Luego conecta el pin 3 a CS y el pin 13 a CLK. Finalmente, conecta 5V a VCC y GND a GND. La siguiente tabla muestra de nuevo las conexiones
| Pantalla | Arduino |
|---|---|
| VCC | 5 V |
| GND | GND |
| DIN | 11 (MOSI) |
| CS | 3 (SS) |
| CLK | 13 (SCK) |
Los pines anteriores son para SPI por hardware, que es más rápido que SPI por software pero requiere usar pines específicos que varían según la placa. Para más detalles consulta la MAX7219 LED dot matrix display Arduino tutorial .
Ahora vamos a escribir y ejecutar un código de prueba para asegurarnos de que las conexiones son correctas.
Instalar las bibliotecas MD_Parola y MD_MAX72XX
Primero, necesitas instalar la MD_MAX72XX y la MD_Parola biblioteca. Abre el Arduino IDE y ve a Tools > Manage Libraries , lo que abrirá el Library Manager. Busca «MD_MAX72XX» y verás las bibliotecas relevantes en la lista. La captura de pantalla de abajo muestra las dos bibliotecas (marcadas en amarillo) después de haber sido instaladas.

La biblioteca MD_MAX72XX es básicamente el software controlador para la pantalla LED MAX7219. La biblioteca MD_Parola la utiliza para crear animaciones como efectos de texto desplazado y sprites. Si quieres saber más sobre las animaciones disponibles, echa un vistazo a la MAX7219 LED dot matrix display Arduino tutorial .
Código de prueba
El siguiente código es un ejemplo mínimo para probar el cableado y el funcionamiento de la pantalla. Simplemente desplaza el texto «Test» en la pantalla.
#include "MD_Parola.h"
#include "MD_MAX72xx.h"
#include "SPI.h"
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 3
MD_Parola disp = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
void setup() {
disp.begin();
disp.setIntensity(0);
disp.displayClear();
disp.displayText("Test", PA_CENTER, 100, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
void loop() {
if (disp.displayAnimate()) {
disp.displayReset();
}
}
Si subes y ejecutas este código deberías ver la siguiente salida en tu pantalla MAX7219:

Para más detalles sobre este código de prueba, consulta el tutorial Control Parola Animations on MAX7219 LED Display , donde usamos el mismo código de prueba.
Ten en cuenta que si usas un tipo diferente de pantalla LED MAX7219 o tienes una pantalla con más o menos módulos, tendrás que ajustar las siguientes definiciones:
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4
De igual forma, si usas SPI por software en vez de SPI por hardware, tendrás que cambiar cómo se crea el objeto de la pantalla:
MD_Parola disp = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
Puedes encontrar más información sobre esto en la MAX7219 LED dot matrix display Arduino tutorial .
En las siguientes secciones, veremos en detalle las zonas, cómo crearlas y usarlas, y lo más importante, cómo coordinar animaciones que se ejecutan en diferentes zonas.
¿Qué son las zonas Parola?
La biblioteca Parola trata una pantalla LED MAX7219 como una secuencia de módulos LED de 8×8, numerados desde 0 hasta n-1 . Una «Zona» es una sección de módulos consecutivos. Cada zona puede controlarse de forma independiente de las demás. El ejemplo de abajo muestra una pantalla MAX7219 con cuatro módulos (0…3) y dos definiciones de zona.

La zona 0 incluye los módulos 0 y 1, y la zona 1 incluye los módulos 2 y 3. Ten en cuenta que la numeración de los módulos empieza en 0 y desde el lado de entrada (lado derecho) de la pantalla. Dentro de cada zona puedes ejecutar diferentes animaciones con distintas fuentes, efectos de texto, cadenas de texto, velocidades e incluso brillos.
Las zonas no deben solaparse, pueden tener diferente longitud y el índice de zona no tiene por qué estar en el mismo orden secuencial que los módulos. Por ejemplo, el siguiente ejemplo muestra otra definición de zona válida. Aquí tenemos tres zonas (0,1,2) pero son de diferente longitud y los índices de zona no están en orden secuencial (0,2,1).

Cómo definir zonas Parola
Supongamos que queremos definir las siguientes zonas en la pantalla. La zona 0 va del módulo 0 al módulo 1, y la zona 1 va del módulo 2 al módulo 3:

En la función setup, primero llama a begin() con el número total de zonas (en este caso 2). Luego defines las zonas individuales llamando a setZone(z, start, end) . El primer argumento z es el id de la zona (0 o 1, en nuestro caso), y después el índice del primer y último módulo de esa zona.
#define MAX_ZONES 2
void setup() {
disp.begin(MAX_ZONES);
disp.setZone(0, 0, 1);
disp.setZone(1, 2, 3);
}
Como se mencionó antes, las zonas no deben solaparse, pero pueden hacerlo. Sin embargo, si se solapan, debes asegurarte de que las zonas superpuestas no ejecuten animaciones al mismo tiempo, de lo contrario obtendrás artefactos extraños.
Una vez que tienes las zonas definidas, puedes asignar animaciones a esas zonas. Por ejemplo, la siguiente línea de código define una animación para la zona 0 que desplaza el texto «Left» hacia la izquierda. Más sobre esto en la siguiente sección.
disp.displayZoneText(0, "Left", PA_CENTER, 100, 0, PA_SCROLL_RIGHT, PA_SCROLL_RIGHT);
Cómo animar zonas Parola
La mayoría de las funciones relacionadas con animaciones en Parola vienen en dos versiones. Una versión que funciona en toda la pantalla y otra que funciona dentro de una zona específica. Consulta la Parola Documentation para ver un resumen.
Por ejemplo, puedes ajustar el brillo de toda la pantalla o de una zona específica llamando a setIntensity , ya sea sin o con un índice de zona.
setIntensity(intensity); // entire display setIntensity(z, intensity); // zone z
Lo mismo ocurre con la mayoría de las otras funciones para ajustar fuentes, efectos de texto, cadenas de texto, velocidades, etc. El siguiente fragmento de código muestra cómo mostrar y animar diferentes textos en dos zonas. En la primera zona (0) desplazamos el texto «0» hacia la derecha, y en la segunda zona (1), desplazamos el texto «1» hacia la izquierda.
#define SPEED 100
void setup() {
disp.begin(MAX_ZONES);
disp.setZone(0, 0, 1);
disp.setZone(1, 2, 3);
disp.displayZoneText(0, "0", PA_CENTER, SPEED, 0, PA_SCROLL_RIGHT, PA_SCROLL_RIGHT);
disp.displayZoneText(1, "1", PA_CENTER, SPEED, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
En la función loop, simplemente llamamos a la habitual displayAnimate() para saber si alguna de las dos animaciones ha terminado y, si es así, reiniciamos la pantalla para todas las zonas mediante displayReset() .
void loop() {
if (disp.displayAnimate()) {
disp.displayReset();
}
}
Ten en cuenta que también podrías reiniciar zonas individualmente llamando a displayReset(z) con un identificador de zona. Usaremos esto más adelante.
void loop() {
if (disp.displayAnimate()) {
disp.displayReset(0);
disp.displayReset(1);
}
}
Esto funciona bien, especialmente porque ambas animaciones aquí se ejecutan a la misma velocidad (100). Pero si las animaciones se ejecutan a diferentes velocidades o tienen textos de diferente longitud, entonces una animación terminará antes que la otra. ¿Cómo manejamos este caso y especificamos exactamente qué debe ocurrir?
Esta coordinación entre animaciones en varias zonas es el tema de la siguiente sección.
Cómo coordinar zonas Parola
Como se mencionó antes, la llamada por defecto al bucle Parola es displayAnimate() , que devolverá true si cualquier animación en cualquier zona ha terminado. Puedes averiguar cuál ha terminado llamando a getZoneStatus(z) con el identificador de zona z .
Por ejemplo, si tienes dos zonas, podrías reiniciar las animaciones en ambas zonas cuando ambas hayan terminado escribiendo el siguiente código:
void loop() {
if (disp.displayAnimate()) {
if(disp.getZoneStatus(0) && disp.getZoneStatus(1)) {
disp.displayReset();
}
}
}
Esto funciona bien y es perfecto para este caso sencillo. Pero si tienes escenarios un poco más complejos o más zonas, fácilmente acabarás con un código muy confuso. Por ejemplo, supongamos que tenemos dos animaciones en dos zonas y una animación es más rápida que la otra. Esto da lugar al menos a seis posibles escenarios de animación diferentes
- Dejar que ambas animaciones se ejecuten solo una vez
- Dejar que la primera animación se ejecute una vez y repetir la otra
- Dejar que la segunda animación se ejecute una vez y repetir la otra
- Repetir ambas animaciones cuando ambas hayan terminado
- Repetir la animación más rápida mientras la más lenta sigue en marcha
- Reiniciar la animación más lenta cuando la más rápida haya terminado
En las siguientes secciones implementaremos estos seis escenarios de animación. Pero para simplificar las cosas, primero definiremos una función auxiliar getStatus() .
Función getStatus
En vez de llamar a getZoneStatus(z) para cada zona, la siguiente función getStatus() devuelve un vector de bits que indica el estado de todas las zonas a la vez.
byte getStatus() {
static byte status = 0;
disp.displayAnimate();
for (int z = 0; z < MAX_ZONES; z++) {
disp.getZoneStatus(z) ? bitSet(status, z) : bitClear(status, z);
}
return status;
}
Internamente, la función recorre todas las zonas y establece el estado de la zona en el byte status . Esto significa que el número máximo de zonas está limitado a 8, pero eso suele ser más que suficiente.
La siguiente imagen muestra un ejemplo de vector de bits o de estado que devuelve getStatus() . En este ejemplo, las animaciones en la zona 1 y la zona 3 han terminado:

Si llamas a getStatus() en el bucle principal, puedes imprimir fácilmente el vector de estado.
void loop() {
Serial.println(getStatus(), BIN);
}
Ten en cuenta que getStatus() internamente también llama a displayAnimate() , así que ya no es necesario llamarlo en la función loop.
Vamos a usar getStatus() en las siguientes secciones para implementar los seis escenarios de animación mencionados antes.
Ejemplo de código con dos zonas
Empecemos con un ejemplo de código que tiene todo excepto la implementación de la función loop.
#include "MD_Parola.h"
#include "MD_MAX72xx.h"
#include "SPI.h"
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define MAX_ZONES 2
#define CS_PIN 3
MD_Parola disp = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
byte getStatus() {
static byte status = 0;
disp.displayAnimate();
for (int z = 0; z < MAX_ZONES; z++) {
disp.getZoneStatus(z) ? bitSet(status, z) : bitClear(status, z);
}
return status;
}
void setup() {
disp.begin(MAX_ZONES);
disp.setZone(0, 0, 1);
disp.setZone(1, 2, 3);
disp.setIntensity(0);
disp.displayClear();
disp.displayZoneText(0, "0", PA_CENTER, 100, 0, PA_SCROLL_RIGHT, PA_SCROLL_RIGHT);
disp.displayZoneText(1, "1", PA_CENTER, 200, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
void loop() {
// TODO
}
En este código, primero incluimos las bibliotecas necesarias y definimos las constantes para la pantalla MAX7219. Si tienes un tipo de pantalla diferente o un número distinto de módulos, tendrás que cambiar estas constantes.
A continuación, creamos el objeto de la pantalla disp e implementamos la función getStatus() como se mostró antes.
En la función setup definimos dos animaciones en dos zonas. La primera animación en la zona 0 desplaza el texto «0» hacia la derecha. La segunda animación en la zona 1 desplaza el texto «1» hacia la izquierda. La imagen de abajo muestra las dos zonas con sus índices.

Ten en cuenta que la primera animación en la zona 0 va más rápido (velocidad=100) que la segunda animación (velocidad=200) en la zona 1. El vídeo corto de abajo muestra cómo se ve esto.

Podemos mantener el código anterior igual para los seis escenarios de animación y solo tenemos que cambiar la implementación de la función loop.
Dejar que ambas animaciones se ejecuten solo una vez
Empecemos con el caso más sencillo. Queremos que ambas animaciones se ejecuten solo una vez hasta que terminen. En este caso, la función loop solo tiene que llamar a getStatus() y no necesita hacer nada más.
void loop() {
getStatus();
}
Probablemente no necesites esto muy a menudo, pero si lo implementas y ejecutas, se verá así:

Sin embargo, ten en cuenta que como el vídeo está en bucle, parece que la animación se reinicia. En realidad, esto no ocurre y la animación se ejecuta solo una vez y luego la pantalla queda vacía para siempre. Puede ser útil, por ejemplo, para un mensaje de inicio que aparece solo una vez.
Dejar que la primera animación se ejecute una vez y repetir la otra
En el siguiente escenario dejamos que la primera animación en la zona 0 se ejecute solo una vez, pero repetimos la segunda. Esto significa que cada vez que la animación en la zona 1 termine, tenemos que reiniciar la pantalla para esa zona. Abajo está el código para la función loop en este escenario.
void loop() {
if(getStatus() & 0b00000010) {
disp.displayReset(1);
}
}
Obtenemos el estado y realizamos una operación bitwise AND (&) para comprobar si la animación en la zona 1 ha terminado. Si es así, llamamos a displayReset(1) para esa zona para repetir la animación. El siguiente clip muestra la animación resultante:

De nuevo, como el vídeo está en bucle, parece que la primera animación se repite, pero en realidad no es así.
Dejar que la segunda animación se ejecute una vez y repetir la otra
En el siguiente escenario intercambiamos los roles de la primera y la segunda animación. Dejamos que la segunda animación se ejecute una vez y repetimos la primera. El código de la función loop es básicamente el mismo que antes, solo cambiamos la zona que estamos comprobando.
void loop() {
if(getStatus() & 0b00000001) {
disp.displayReset(0);
}
}
El vídeo de abajo muestra la animación correspondiente.

Repetir ambas animaciones cuando ambas hayan terminado
Ahora, esperemos hasta que ambas animaciones hayan terminado y luego reiniciemos ambas. Esto es muy sencillo. Solo tenemos que comprobar si ambos bits de estado están activados. En este caso, no hacemos un AND (&) sino que comprobamos la igualdad (=)!
void loop() {
if(getStatus() == 0b00000011) {
disp.displayClear();
disp.displayReset();
}
}
En el siguiente clip puedes ver cómo se ve la animación en este caso.

Repetir la animación más rápida mientras la más lenta sigue en marcha
Como las dos animaciones se ejecutan a diferentes velocidades, necesitamos decidir qué hacer cuando la animación más rápida termina. ¿Queremos reiniciar la animación rápida inmediatamente o esperar a que la lenta termine? El caso de esperar lo cubrimos en la sección anterior.
Abajo está el código que cubre el primer caso. Simplemente comprobamos para cada zona si ha terminado y, si es así, reiniciamos la animación correspondiente.
void loop() {
if(getStatus() & 0b00000001) {
disp.displayReset(0);
}
if(getStatus() & 0b00000010) {
disp.displayReset(1);
}
}
Aquí tienes el vídeo que muestra este escenario en acción.

Reiniciar la animación más lenta cuando la más rápida haya terminado
El último escenario de animación es el caso inverso al anterior. En este escenario queremos reiniciar la animación más lenta tan pronto como la animación más rápida termine. Ten en cuenta que el código es bastante diferente al anterior.
void loop() {
if(getStatus() & 0b00000001) {
disp.displayReset(1);
disp.displayClear(1);
disp.displayReset(0);
}
}
En concreto, necesitamos limpiar la zona 1, ya que reiniciamos la animación en la zona 1 antes de que termine. Si no limpias la pantalla, quedarán artefactos.
El vídeo de abajo muestra esta animación. Observa que el desplazamiento a la izquierda del «1» se interrumpe y se reinicia en el momento en que el desplazamiento a la derecha del «0» termina.

Conclusiones
En este tutorial has aprendido cómo coordinar animaciones Parola que se ejecutan en varias zonas. Los ejemplos anteriores usaron solo dos zonas y son relativamente sencillos. Sin embargo, en cuanto tengas más de dos zonas, la función getStatus() simplificará mucho tu código.
Por ejemplo, puedes listar todos los posibles estados de zona en una sentencia switch y gestionarlos según corresponda. Aquí tienes un ejemplo para dos zonas que puedes ampliar fácilmente a más zonas.
void loop() {
switch (getStatus()) {
case 0b00000001:
...
break;
case 0b00000010:
...
break;
case 0b00000011:
...
break;
}
}
No hemos usado esta construcción en los ejemplos de código anteriores, ya que con dos zonas realmente no simplifica el código, pero con más zonas sí lo hará.
Además, si quieres encadenar varias animaciones o controlarlas mediante entradas externas, echa un vistazo al tutorial Control Parola Animations on MAX7219 LED Display .
Si quieres manipular píxeles individuales de la matriz LED, la biblioteca Parola no es la opción adecuada y es mejor usar FastLED o algo similar. Para un ejemplo, consulta la entrada Game of Life on a Dot Matrix Display with MAX7219 .
Espero que este tutorial te haya resultado útil. No dudes en dejar un comentario si tienes alguna pregunta.

