Matriz LED MAX7219 con Arduino: texto en movimiento y reloj
Matriz LED MAX7219 con Arduino: conexión de módulos 8x8 y 4 en 1 8x32, texto en movimiento con MD_Parola, texto en espejo con FC16_HW, reloj y ESP32.
El MAX7219 es un chip controlador barato que maneja 64 LED él solo, y es la forma más sencilla de usar una matriz LED con Arduino. Lo encontrarás sobre todo en dos versiones: un módulo con una sola matriz de puntos 8x8 y un módulo 4 en 1 de 8x32 puntos con cuatro matrices una al lado de otra. El MAX7219 solo necesita 3 cables de señal del Arduino (DIN, CS, CLK); él mismo se encarga de barrer las filas cientos de veces por segundo, de ajustar el brillo y de recordar el estado de cada LED. Así el Arduino queda libre para texto en movimiento, relojes, marcadores o animaciones, y puedes alargar la pantalla tanto como quieras encadenando módulos.
En este artículo aprenderás cómo funciona el MAX7219, cómo conectar el módulo por SPI a un Arduino Uno, un ESP32 y una Raspberry Pi Pico, cómo encadenar módulos y cómo mostrar tu primer texto en movimiento con la librería MD_Parola. Después resolverás el problema del texto en espejo o al revés, el que más atasca a los principiantes, eligiendo el tipo de hardware correcto (FC16_HW); calcularás el brillo y el consumo, mostrarás tildes, la ñ y caracteres propios, y construirás un reloj con un módulo RTC y una animación píxel a píxel. Las últimas secciones explican cómo enviar mensajes desde el móvil con un ESP32, el código MicroPython para la Pico y las soluciones a los errores más comunes.

¿Qué es el MAX7219 y cómo funciona?
No es posible conectar 64 LED uno a uno a los pines del Arduino, por eso las matrices LED usan multiplexación. En una matriz 8x8 los LED están conectados en una rejilla de 8 filas y 8 columnas. En cada momento, el MAX7219 activa solo un grupo de LED (un "digit" en la hoja de datos, que en la matriz es una fila o una columna), indica por las demás líneas cuáles de los ocho LED de ese grupo se encienden y pasa al siguiente grupo. Los ocho grupos se recorren unas 800 veces por segundo; el ojo no puede seguir esa velocidad, así que la imagen parece fija. Mientras tanto, el chip guarda el patrón de cada grupo en su propia RAM de 8x8 bits, limita la corriente de los LED con una sola resistencia y regula el brillo en 16 niveles.
El Arduino se comunica con el MAX7219 mediante una interfaz serie de 3 hilos que funciona de forma parecida a SPI. Cada orden es un paquete de 16 bits: el byte alto indica en qué registro se escribe y el byte bajo lleva el valor. Mientras CS (LOAD en la hoja de datos) está en LOW, los bits entran por DIN en cada flanco de subida de CLK; cuando CS vuelve a HIGH, el paquete se guarda en el registro. Es la misma idea que el registro de desplazamiento 74HC595, pero como el MAX7219 también se ocupa del barrido y de limitar la corriente, da mucho menos trabajo. Encontrarás cómo funciona SPI en general en ¿Qué es SPI?.
| Característica | Valor |
|---|---|
| Tensión de alimentación | 4,0–5,5 V (en la práctica, 5 V) |
| Interfaz | Serie de 3 hilos (DIN, CLK, CS), hasta 10 MHz |
| LED que controla | 64 (una matriz 8x8 u 8 dígitos de 7 segmentos) |
| Frecuencia de barrido | 800 Hz típicos (8 dígitos) |
| Corriente por segmento | Se fija con la resistencia RSET, máximo 40 mA (en los módulos suele ser de 10 kΩ) |
| Brillo | 16 niveles, ciclo de trabajo de 1/32 a 31/32 |
| Corriente en modo apagado (shutdown) | Unos 150 µA |
| Umbral HIGH de entrada | Al menos 3,5 V (con alimentación de 5 V) |
| Encadenado | La salida DOUT va a la entrada DIN del siguiente chip |
El MAX7219 tiene 14 registros direccionables, pero los que necesitas para una matriz LED están en la tabla de abajo. Las librerías los configuran por ti; aun así, saber para qué sirven ayuda mucho al buscar fallos. Por ejemplo, al recibir alimentación por primera vez el chip arranca en modo shutdown con la pantalla apagada, de modo que si el código de inicialización no se ejecuta, el módulo parece no responder en absoluto. Su hermano, el MAX7221, tiene los mismos registros, pero añade un pin CS realmente compatible con SPI y drivers de segmento con pendiente limitada que reducen las interferencias electromagnéticas.
| Dirección | Registro | Función |
|---|---|---|
| 0x00 | No-Op | Deja este chip sin cambios mientras un paquete va a otro chip de la cadena |
| 0x01–0x08 | Digit 0–7 | El patrón de 8 bits de cada una de las ocho filas (o columnas) |
| 0x09 | Decode Mode | Decodificación BCD para 7 segmentos; desactivada (0x00) en una matriz |
| 0x0A | Intensity | Brillo, de 0x0 a 0xF |
| 0x0B | Scan Limit | Número de dígitos que se barren; 7 (los ocho) en una matriz |
| 0x0C | Shutdown | 0: modo apagado, 1: funcionamiento normal |
| 0x0F | Display Test | 1: todos los LED se encienden al máximo brillo |
¿Módulo 8x8 individual o módulo 4 en 1 de 8x32?
Los dos módulos que verás con más frecuencia tienen el mismo circuito; la diferencia está en cómo se conecta la matriz LED al MAX7219 y en la forma de la placa. El módulo individual de placa verde lleva un MAX7219 en encapsulado DIP (24 patillas) y una matriz 8x8 como la 1088AS, con los pines de entrada (VCC, GND, DIN, CS, CLK) en un extremo y los de salida (VCC, GND, DOUT, CS, CLK) en el otro. En el módulo 4 en 1 de placa azul, cuatro MAX7219 SMD van debajo de las matrices y los cuatro bloques vienen encadenados de fábrica; la placa suele llevar la marca FC-16. Para un texto largo en movimiento, el módulo 4 en 1 es más barato y más ordenado.

| Característica | Módulo 8x8 individual | Módulo 4 en 1 de 8x32 (FC-16) |
|---|---|---|
| Resolución | 8x8 = 64 LED | 32x8 = 256 LED |
| Número de MAX7219 | 1 (encapsulado DIP) | 4 (encapsulado SMD) |
| Tipo de hardware en el código | Normalmente GENERIC_HW | Normalmente FC16_HW |
| Caracteres visibles a la vez | 1 | 4–6 (según el ancho de las letras) |
| Corriente máxima con brillo al máximo | Unos 330 mA | Unos 1,3 A |
| Uso ideal | Iconos, dado, caritas, juegos pequeños | Texto en movimiento, relojes, marcadores |
Conexión del MAX7219: Arduino Uno, ESP32 y Pico
El módulo tiene cinco pines: VCC y GND para la alimentación, DIN para los datos, CLK para el reloj y CS para la selección del chip (carga). Por defecto, MD_Parola y MD_MAX72XX usan SPI por hardware; en ese caso DIN debe ir al pin MOSI de tu placa y CLK al pin SCK, mientras que para CS puedes elegir cualquier pin digital. Puedes comprobar dónde están los pines SPI por hardware de tu placa con la herramienta Pinout. Conecta siempre los cables al lado de entrada (IN) del módulo, el extremo marcado como DIN; el extremo marcado como DOUT es para el siguiente módulo.
| Pin del módulo | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC | 5V | 5V (VIN) | VBUS (5 V) |
| GND | GND | GND | GND |
| DIN | D11 (MOSI) | GPIO 23 (MOSI) | GP19 (SPI0 TX) |
| CS | D10 | GPIO 5 | GP17 |
| CLK | D13 (SCK) | GPIO 18 (SCK) | GP18 (SPI0 SCK) |
Los pines del ESP32 y de la Pico son de 3,3 V, mientras que la hoja de datos del MAX7219 pide al menos 3,5 V como nivel HIGH con alimentación de 5 V. Es decir, las señales de 3,3 V quedan técnicamente un poco por debajo del límite. La mayoría de los módulos funcionan igualmente, pero con cables largos o con algunos lotes de chips pueden aparecer puntos encendidos al azar e imágenes corruptas. La solución fiable es un buffer como el 74HCT125, que acepta 3,3 V como HIGH y entrega 5 V, o un conversor de nivel lógico rápido en las líneas DIN, CLK y CS. Mantener los cables de señal por debajo de 20 cm también suele resolver el problema.
Encadenar módulos (daisy chain)
El pin DOUT del MAX7219 saca los datos que entraron por DIN 16,5 ciclos de reloj después. Cuando unes el lado OUT de un módulo con el lado IN del siguiente, DOUT va al DIN del chip siguiente, mientras que CLK y CS son comunes a todos. Así, del Arduino siguen saliendo solo 3 cables de señal, pero la pantalla puede tener 8, 16 o 32 bloques. En cada actualización la librería envía paquetes por toda la cadena y, cuando CS sube, todos se actualizan a la vez. En los módulos 4 en 1 esta conexión ya viene hecha en la placa; para formar una pantalla de 8 bloques (64x8) con dos módulos 4 en 1, basta con unir los pines OUT del primero a los pines IN del segundo y poner MAX_DEVICES a 8 en el código.
En la librería, la numeración de los bloques empieza en el módulo más cercano a la entrada DIN y aumenta hacia la izquierda; para MD_MAX72XX, el origen de coordenadas es la esquina superior derecha de la pantalla. Por eso la entrada DIN debe quedar a la derecha para que el texto se lea bien. Cuanto más larga es la cadena, más se convierte la alimentación en el verdadero límite: cada 4–8 bloques vuelve a llevar 5 V y GND a la cadena con cables gruesos y añade en la entrada de alimentación de cada módulo un condensador electrolítico de 10 µF y uno cerámico de 100 nF. La hoja de datos también recomienda esta pareja para suavizar el rizado que provocan los picos de corriente de los LED.
Instalación de las librerías: MD_MAX72XX y MD_Parola
Las dos librerías más usadas para el MAX7219 son del mismo autor (majicDesigns) y trabajan juntas. MD_MAX72XX hace el trabajo de bajo nivel: inicializa los módulos, escribe píxeles y columnas y gestiona la fuente. MD_Parola se apoya en ella y añade texto en movimiento, efectos, alineación y zonas. La librería LedControl que verás en ejemplos antiguos también funciona, pero tendrás que programar el desplazamiento del texto tú mismo. Si nunca has instalado una librería, lee primero Cómo añadir una librería en Arduino.
- En el Arduino IDE abre el Gestor de librerías (Herramientas > Administrar bibliotecas) y busca MD_Parola.
- Instala la librería MD_Parola de majicDesigns. Cuando te pregunte por las dependencias, elige Install all para que también se instale MD_MAX72XX.
- Conecta el módulo según la tabla anterior y, para un módulo 4 en 1, pon HARDWARE_TYPE a FC16_HW y MAX_DEVICES a 4 en el código.
- Sube el sketch. Si el texto aparece al revés, en espejo o girado, no te preocupes: lo corregirás con una sola línea en la sección del tipo de hardware.
Primer programa: texto en movimiento con MD_Parola
1// MAX7219 4'lü modülde kayan yazı (MD_Parola) / Scrolling text on a 4-in-1 MAX7219 module (MD_Parola)
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5
6#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // mavi PCB'li 4'lü modül / blue-PCB 4-in-1 module
7#define MAX_DEVICES 4 // zincirdeki 8x8 blok sayısı / number of 8x8 blocks in the chain
8#define CS_PIN 10 // DIN = D11 (MOSI), CLK = D13 (SCK)
9
10MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
11
12void setup() {
13 display.begin();
14 display.setIntensity(4); // parlaklık 0–15 / brightness 0–15
15 display.displayClear();
16 // metin, hizalama, hız (ms/kare), bekleme (ms), giriş efekti, çıkış efekti
17 // text, alignment, speed (ms/frame), pause (ms), entry effect, exit effect
18 display.displayText("Merhaba BlueGrays!", PA_CENTER, 50, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
19}
20
21void loop() {
22 if (display.displayAnimate()) { // efekt bittiğinde true döner / returns true when the effect ends
23 display.displayReset(); // aynı yazıyı baştan başlat / restart the same text
24 }
25}La línea más importante es displayText(): recibe, por este orden, el texto, la alineación (PA_LEFT, PA_CENTER, PA_RIGHT), el tiempo entre fotogramas en milisegundos, la pausa cuando el texto llega a su sitio y los efectos de entrada y salida. Cuanto menor es el valor de velocidad, más rápido se mueve el texto; entre 25 y 60 ms se consigue un desplazamiento legible. displayAnimate() avanza como mucho un fotograma cada vez que la llamas y vuelve enseguida. Por eso hay que llamarla continuamente en loop() y no debes usar delay() en el resto del código; si no, el texto avanzará a tirones. En el artículo millis() frente a delay() verás cómo esperar sin bloquear el programa.
| Función | Qué hace |
|---|---|
| begin() | Inicializa los módulos, los saca del modo apagado y borra la pantalla |
| displayText(texto, alineación, velocidad, pausa, entrada, salida) | Define el texto y los efectos que se mostrarán e inicia la animación |
| displayAnimate() | Dibuja el siguiente fotograma; devuelve true cuando el efecto termina |
| displayReset() | Vuelve a reproducir el mismo texto y efecto desde el principio |
| print("...") | Muestra texto fijo sin efecto |
| setIntensity(0–15) | Ajusta el brillo de todos los módulos |
| setInvert(true) | Intercambia los LED encendidos y apagados (imagen en negativo) |
| displayShutdown(true) | Pone la pantalla en modo apagado; con false la vuelve a encender |
| begin(2) y setZone(0, 0, 1) | Divide la pantalla en zonas independientes, cada una con su texto y su efecto |
Además de PA_SCROLL_LEFT hay muchos efectos de entrada y salida: PA_PRINT (el texto aparece de golpe), PA_SCROLL_UP y PA_SCROLL_DOWN (desplazamiento vertical), PA_WIPE (barrido), PA_OPENING y PA_CLOSING (apertura y cierre desde el centro), PA_FADE (fundido con el brillo), PA_DISSOLVE, PA_BLINDS, PA_RANDOM y PA_GROW_UP. Para verlos todos, sube el ejemplo Parola_Animation_Catalog desde Archivo > Ejemplos > MD_Parola; no olvides cambiar las líneas HARDWARE_TYPE y MAX_DEVICES según tu módulo.
Elegir el tipo de hardware (FC16_HW) y corregir el texto en espejo
Cada fabricante conecta la matriz 8x8 al MAX7219 de una manera: en unas placas las líneas "digit" del chip van a las filas, en otras a las columnas, y el orden de filas o columnas puede estar invertido. La librería compensa esa diferencia con el ajuste HARDWARE_TYPE. Si eliges el tipo equivocado, el módulo no está averiado, pero las letras se ven en espejo, al revés o giradas 90 grados en cada bloque 8x8. Como muchos códigos antiguos de internet usan PAROLA_HW o GENERIC_HW, este es el problema más frecuente con los módulos 4 en 1 azules que se venden hoy. La tabla muestra qué cambiar según el síntoma; está pensada para el módulo 4 en 1 FC-16.
| Lo que ves | Causa probable | Solución |
|---|---|---|
| Las letras se ven en espejo (izquierda-derecha) en cada bloque | PAROLA_HW seleccionado, orden de columnas invertido | Usa FC16_HW |
| Las letras están giradas 90 grados en cada bloque | GENERIC_HW seleccionado, filas y columnas intercambiadas | Usa FC16_HW |
| Las letras están boca abajo en cada bloque | ICSTATION_HW seleccionado, orden de filas y columnas invertido | Usa FC16_HW |
| Todo el texto está boca abajo y se desplaza de izquierda a derecha | El módulo está girado, con la entrada DIN a la izquierda | Gira el módulo 180 grados para que la entrada DIN quede a la derecha |
| Solo algunos bloques se ven bien, el resto está vacío o revuelto | MAX_DEVICES no coincide con el número real de bloques | Pon en MAX_DEVICES el número real de bloques |
Ajuste de brillo y consumo
El brillo depende de dos cosas. La primera es la resistencia RSET del módulo: el chip entrega a los LED unas 100 veces la corriente que sale del pin ISET. Los módulos suelen llevar 10 kΩ, lo que supone una corriente de pico algo superior a 40 mA con LED rojos y de unos 40 mA con LED azules y blancos. La segunda es el registro Intensity que ajustas por software: un valor n de 0 a 15 mantiene los LED encendidos durante (2n+1)/32 de cada intervalo de barrido, así que 0 es el brillo mínimo (1/32) y 15 el máximo (31/32). Además, como cada fila solo se enciende una octava parte del tiempo, incluso con el brillo al máximo un LED consume de media unos 5 mA. Puedes consultar las tensiones directas según el color del LED en la herramienta Tensiones de LED.
Puedes estimar de forma aproximada la corriente media de un módulo así: I ≈ 8 mA + 5 mA × (2n+1)/32 × L. Aquí, 8 mA es el consumo propio del chip, n el valor de brillo y L el número de LED encendidos en ese módulo (0–64); 5 mA es la octava parte de la corriente de pico de 40 mA. Por ejemplo, con brillo 15 y los 64 LED encendidos salen 8 + 5 × 0,97 × 64 ≈ 318 mA, lo que concuerda con los 330 mA de la hoja de datos. Con texto en movimiento normalmente solo se enciende entre el 25 y el 35 por ciento de los LED, así que el consumo real es mucho menor. La tabla muestra valores típicos para un módulo 4 en 1.
| Situación | Por módulo | Módulo 4 en 1 (32x8) |
|---|---|---|
| Todos los LED encendidos, brillo 15 | ≈ 320 mA | ≈ 1,3 A |
| Todos los LED encendidos, brillo 7 | ≈ 160 mA | ≈ 630 mA |
| Texto en movimiento (~30 % de los LED), brillo 7 | ≈ 55 mA | ≈ 210 mA |
| Texto en movimiento (~30 % de los LED), brillo 2 | ≈ 25 mA | ≈ 90 mA |
| Modo apagado (displayShutdown) | ≈ 0,15 mA | ≈ 0,6 mA |
Para atenuar la pantalla por la noche puedes ajustar el brillo automáticamente con un sensor de luz LDR y dormir la pantalla con displayShutdown(true) cuando no haya nadie. Si diseñas un cartel a pilas, introduce los valores anteriores en la herramienta Duración de la batería para calcular cuántas horas aguantará. En interiores suele bastar con un brillo de 2–4 en lugar de 15; cansa menos la vista y reduce la corriente de los LED a entre un tercio y una sexta parte, aproximadamente.
Tildes, ñ y caracteres especiales
El Arduino IDE guarda tu código en UTF-8, así que una letra como "ñ" o "á" no ocupa un byte en el texto, sino dos. MD_Parola busca cada byte como un número de carácter en la fuente, por eso las letras acentuadas aparecen como símbolos sin sentido. La fuente por defecto de la librería incluye todo el juego Latin-1 (á, é, í, ó, ú, ñ, ü, ¿, ¡ y más), de modo que basta una pequeña función que convierta UTF-8 a los códigos de la fuente. Las letras que no están en Latin-1, como las turcas ğ, ı y ş del ejemplo, hay que dibujarlas píxel a píxel y añadirlas con addChar(). El sketch hace las dos cosas.
1// MAX7219 + MD_Parola: Türkçe karakterli kayan yazı / Scrolling text with Turkish letters
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5
6#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
7#define MAX_DEVICES 4
8#define CS_PIN 10
9
10MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
11
12// Fontta olmayan 6 harf: ilk bayt genişlik, sonrakiler sütunlar (bit 0 = en üst satır)
13// 6 letters missing from the font: first byte is the width, then the columns (bit 0 = top row)
14const uint8_t trG[] = {5, 56, 69, 70, 85, 116}; // Ğ
15const uint8_t trg[] = {4, 25, 166, 166, 125}; // ğ
16const uint8_t trI[] = {3, 68, 125, 68}; // İ
17const uint8_t tri[] = {1, 124}; // ı
18const uint8_t trS[] = {5, 70, 73, 201, 73, 49}; // Ş
19const uint8_t trs[] = {4, 88, 212, 84, 52}; // ş
20
21// UTF-8 metni fontun kodlarına çevirir / converts UTF-8 text to the font's codes
22void utf8ToFont(const char *src, char *dst, size_t size) {
23 size_t n = 0;
24 while (*src && n < size - 1) {
25 uint8_t c = *src++;
26 if (c < 0x80) { dst[n++] = c; continue; } // ASCII aynen kalır / ASCII stays as is
27 if ((c & 0xE0) == 0xC0 && *src) { // 2 baytlık harf / 2-byte letter
28 uint16_t u = ((c & 0x1F) << 6) | (*src++ & 0x3F);
29 switch (u) {
30 case 0x11E: dst[n++] = (char)0x80; break; // Ğ
31 case 0x11F: dst[n++] = (char)0x81; break; // ğ
32 case 0x130: dst[n++] = (char)0x82; break; // İ
33 case 0x131: dst[n++] = (char)0x83; break; // ı
34 case 0x15E: dst[n++] = (char)0x84; break; // Ş
35 case 0x15F: dst[n++] = (char)0x85; break; // ş
36 default: dst[n++] = (u < 0x100) ? (char)u : '?'; // ç ö ü Ç Ö Ü ° fontta var / already in the font
37 }
38 continue;
39 }
40 while ((*src & 0xC0) == 0x80) src++; // desteklenmeyen karakteri atla / skip an unsupported character
41 dst[n++] = '?';
42 }
43 dst[n] = '\0';
44}
45
46char message[96];
47
48void setup() {
49 display.begin();
50 display.setIntensity(3);
51 display.addChar(0x80, trG); // addChar begin()'den sonra / addChar after begin()
52 display.addChar(0x81, trg);
53 display.addChar(0x82, trI);
54 display.addChar(0x83, tri);
55 display.addChar(0x84, trS);
56 display.addChar(0x85, trs);
57 utf8ToFont("Günaydın İstanbul! Şehirde hava 22°C, çok güzel bir gün.", message, sizeof(message));
58 display.displayText(message, PA_LEFT, 40, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
59}
60
61void loop() {
62 if (display.displayAnimate()) display.displayReset();
63}En cada array de carácter propio, el primer byte indica el ancho de la letra en columnas y los siguientes son las columnas de izquierda a derecha; el bit 0 de cada byte es la fila superior. Por ejemplo, la ı turca (i sin punto) es una sola columna, y el valor 124 (0b01111100) enciende las filas 2 a 6; solo se diferencia de la i normal en que le falta el punto. En las mayúsculas Ğ e İ la letra se acortó dos filas (5 en lugar de 7) para dejar sitio al signo de arriba. addChar() no copia los datos, solo guarda su dirección; por eso los arrays deben ser globales y añadirse después de begin(). Se eligieron los códigos 0x80–0x85 porque nunca aparecen en un texto normal. Del mismo modo puedes añadir cualquier símbolo, como un corazón o una flecha.
Ejemplo de reloj: reloj digital con un módulo RTC

El contador millis() del Arduino vuelve a cero al cortarse la corriente y, por el resonador cerámico del Uno, puede desviarse hasta varios minutos al día. Para un reloj de verdad usa un módulo de reloj RTC. Conecta el módulo DS1307 al bus I2C (SDA → A4, SCL → A5, VCC → 5V, GND → GND); el MAX7219 usa los pines SPI, así que los dos módulos no se estorban. Para el código, instala la librería RTClib de Adafruit desde el Gestor de librerías.
1// MAX7219 + DS1307 RTC ile dijital saat / Digital clock with MAX7219 + DS1307 RTC
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5#include <RTClib.h> // Adafruit RTClib
6
7#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
8#define MAX_DEVICES 4
9#define CS_PIN 10
10
11MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
12RTC_DS1307 rtc; // SDA = A4, SCL = A5
13
14char timeText[8]; // "12:34" + sonlandırıcı / + terminator
15uint8_t lastSecond = 99;
16
17void setup() {
18 display.begin();
19 display.setIntensity(2);
20 display.setTextAlignment(PA_CENTER); // print() bu hizalamayı kullanır / print() uses this alignment
21 if (!rtc.begin()) {
22 display.print("RTC?"); // modül bulunamadı / module not found
23 while (true) {}
24 }
25 if (!rtc.isrunning()) {
26 rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // ilk kurulumda derleme saati / compile time on first run
27 }
28}
29
30void loop() {
31 DateTime now = rtc.now();
32 if (now.second() != lastSecond) { // saniyede bir güncelle / update once a second
33 lastSecond = now.second();
34 // ':' ve boşluk aynı genişlikte, yazı kaymaz / ':' and space are equally wide, nothing shifts
35 sprintf(timeText, (lastSecond % 2) ? "%02d %02d" : "%02d:%02d", now.hour(), now.minute());
36 display.print(timeText);
37 }
38}El sketch lee la hora una vez por segundo y hace parpadear los dos puntos. En la fuente por defecto, los dos puntos (:) y el espacio tienen el mismo ancho (2 columnas), así que los dígitos no se mueven. La primera vez que funciona, el RTC se pone en la hora en que se compiló el sketch. Un DS1307 puede desviarse alrededor de un minuto al mes; para un reloj más preciso, compra un módulo DS3231 y usa en el código RTC_DS3231 en lugar de RTC_DS1307 y cambia la condición if (!rtc.isrunning()) por if (rtc.lostPower()). Gracias a su cristal con compensación de temperatura, el DS3231 solo se desvía cerca de un minuto al año. También puedes dividir la pantalla en dos zonas para mostrar la hora en un lado y la temperatura en el otro.
Animación: dibujar píxel a píxel con MD_MAX72XX
Para ir más allá del texto en movimiento puedes usar directamente la librería MD_MAX72XX. Esta trata toda la cadena como una única pantalla de píxeles: un módulo 4 en 1 tiene 32 columnas y 8 filas. setPoint(fila, columna, estado) enciende o apaga un solo LED, setColumn() escribe de una vez los 8 bits de una columna y clear() borra la pantalla. El origen (0, 0) es la esquina superior derecha; los números de columna aumentan hacia la izquierda y los de fila hacia abajo. El siguiente código dibuja una pelota que rebota en los bordes de la pantalla:
1// MD_MAX72XX ile seken top animasyonu / Bouncing ball animation with MD_MAX72XX
2#include <MD_MAX72xx.h>
3#include <SPI.h>
4
5#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
6#define MAX_DEVICES 4
7#define CS_PIN 10
8
9MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
10
11int x = 0, y = 0; // topun konumu (sütun, satır) / ball position (column, row)
12int dx = 1, dy = 1; // hareket yönü / direction of travel
13
14void setup() {
15 mx.begin();
16 mx.control(MD_MAX72XX::INTENSITY, 2);
17 mx.clear();
18}
19
20void loop() {
21 const int lastCol = mx.getColumnCount() - 1; // 4 modülde 31 / 31 with 4 modules
22 mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); // çizim bitene kadar gönderme / hold updates while drawing
23 mx.setPoint(y, x, false); // eski konumu sil / erase the old position
24 x += dx;
25 y += dy;
26 if (x <= 0 || x >= lastCol) dx = -dx; // kenara çarpınca dön / bounce off the edge
27 if (y <= 0 || y >= 7) dy = -dy;
28 mx.setPoint(y, x, true); // yeni konumu yak / light the new position
29 mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON); // hepsini tek seferde gönder / send everything at once
30 delay(40);
31}En cada fotograma primero se desactiva la actualización automática (UPDATE), se borra el píxel anterior y se dibuja el nuevo; después se vuelve a activar UPDATE para que los cambios lleguen a los módulos de una sola vez. Así nunca se ve un fotograma a medio dibujar. Con la misma idea puedes dibujar un dado, caritas, el juego de la serpiente o barras de nivel de sonido. Los ejemplos de MD_MAX72XX incluyen además animaciones listas como Pac-Man, ojos de robot y un reloj de arena. Si usas MD_Parola, accedes a las mismas funciones de dibujo con getGraphicObject().
Mensajes por internet con un ESP32: texto en movimiento desde el móvil
Con un ESP32 puedes convertir tu letrero de texto en movimiento en un letrero inalámbrico. El siguiente código se conecta a tu red doméstica, primero desplaza su propia dirección IP por la pantalla y después muestra la hora obtenida de internet (NTP). Abre esa dirección IP en el navegador de un móvil conectado a la misma Wi-Fi y verás un cuadro de texto; el mensaje que escribas empezará a desplazarse en la siguiente vuelta y se alternará con la hora. El módulo se conecta a los pines VSPI como en la tabla: CLK al GPIO 18, DIN al GPIO 23 y CS al GPIO 5. Si es tu primera vez con el ESP32, lee antes ¿Qué es el ESP32?.
1// ESP32: telefondan mesaj + internet saati / ESP32: message from your phone + internet time
2#include <WiFi.h>
3#include <WebServer.h>
4#include <time.h>
5#include <MD_Parola.h>
6#include <MD_MAX72xx.h>
7#include <SPI.h>
8
9#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
10#define MAX_DEVICES 4
11#define CS_PIN 5 // VSPI: CLK = GPIO 18, DIN = GPIO 23
12
13const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
14const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
15
16MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
17WebServer server(80);
18
19char message[128];
20char nextMessage[128];
21char clockText[8] = "--:--";
22bool newMessage = false;
23bool showClock = false;
24
25const char PAGE[] = R"rawliteral(<!DOCTYPE html><html><head><meta charset="utf-8">
26<meta name="viewport" content="width=device-width"><title>MAX7219</title></head><body>
27<h2>Kayan yazı / Scrolling text</h2>
28<form action="/"><input name="m" maxlength="100"> <button>Gönder / Send</button></form>
29</body></html>)rawliteral";
30
31void handleRoot() {
32 if (server.hasArg("m")) { // formdan gelen mesaj / message from the form
33 server.arg("m").toCharArray(nextMessage, sizeof(nextMessage));
34 newMessage = true;
35 }
36 server.send(200, "text/html; charset=utf-8", PAGE);
37}
38
39void setup() {
40 display.begin();
41 display.setIntensity(3);
42 display.print("WiFi..");
43 WiFi.begin(ssid, password);
44 while (WiFi.status() != WL_CONNECTED) delay(500);
45 configTime(3 * 3600, 0, "pool.ntp.org"); // Türkiye UTC+3 / Turkey is UTC+3
46 snprintf(message, sizeof(message), "IP: %s", WiFi.localIP().toString().c_str());
47 server.on("/", handleRoot);
48 server.begin();
49 display.displayText(message, PA_LEFT, 40, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
50}
51
52void loop() {
53 server.handleClient();
54 if (display.displayAnimate()) { // önceki gösterim bitti / the previous show has ended
55 showClock = !showClock;
56 if (newMessage) { // yeni mesaj sırayı keser / a new message jumps the queue
57 strlcpy(message, nextMessage, sizeof(message));
58 newMessage = false;
59 showClock = false;
60 }
61 if (showClock) {
62 struct tm t;
63 if (getLocalTime(&t, 10)) strftime(clockText, sizeof(clockText), "%H:%M", &t);
64 display.displayText(clockText, PA_CENTER, 40, 3000, PA_SCROLL_DOWN, PA_SCROLL_UP);
65 } else {
66 display.displayText(message, PA_LEFT, 40, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
67 }
68 }
69}handleRoot() no manda el mensaje del formulario directamente a la pantalla, sino que lo deja esperando en nextMessage. El mensaje se copia solo cuando la animación en curso termina (cuando displayAnimate() devuelve true); así el texto que MD_Parola está leyendo nunca cambia a mitad de camino. El valor 3 × 3600 de configTime() corresponde a la zona horaria UTC+3 de Turquía; en la España peninsular sustituye esa línea por configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"), que además tiene en cuenta el horario de verano, y en otros países pon tu propia diferencia horaria. Para mensajes con tildes y ñ, añade a este sketch las líneas addChar() y la función utf8ToFont() de arriba, y cambia la línea de strlcpy() por utf8ToFont(nextMessage, message, sizeof(message)). Para la hora por internet y paneles web en tiempo real, consulta Reloj NTP con ESP32 y Servidor web y WebSocket con ESP32.
MAX7219 con Raspberry Pi Pico y MicroPython
En la Pico con MicroPython, lo más práctico es copiar el driver de código abierto max7219.py (micropython-max7219) a la Pico con Thonny. Como el driver usa el módulo framebuf de MicroPython, trae listas funciones de dibujo como text(), pixel(), line() y rect(), y funciona con la orientación correcta en los módulos 4 en 1 FC-16. Alimenta el módulo desde VBUS (5 V) y conecta DIN a GP19, CLK a GP18 y CS a GP17. MicroPython también reserva el pin MISO de SPI0 (GP16), así que no lo uses para otra cosa. La fuente de framebuf es de 8x8 píxeles y solo ASCII, por lo que un módulo 4 en 1 muestra 4 letras a la vez y no muestra tildes. Para la puesta en marcha, consulta ¿Qué es la Raspberry Pi Pico? y Fundamentos de MicroPython.
1# Pico + 4'lü MAX7219 modül: kayan yazı / Pico + 4-in-1 MAX7219 module: scrolling text
2from machine import Pin, SPI
3import time
4import max7219 # max7219.py Pico'da olmalı / must be on the Pico
5
6# GP18 = SCK -> CLK, GP19 = MOSI -> DIN, GP17 -> CS; GP16 (MISO) boş kalır / GP16 (MISO) stays free
7spi = SPI(0, baudrate=5_000_000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(19))
8display = max7219.Matrix8x8(spi, Pin(17, Pin.OUT), 4)
9display.brightness(3) # parlaklık 0–15 / brightness 0–15
10
11def scroll(text, delay_ms=35):
12 width = len(text) * 8 # her harf 8 piksel / each letter is 8 px wide
13 for x in range(32, -width, -1): # sağdan girip solda kaybolur / enters right, leaves left
14 display.fill(0)
15 display.text(text, x, 0, 1)
16 display.show()
17 time.sleep_ms(delay_ms)
18
19while True:
20 scroll("BlueGrays + Pico")
21 display.fill(0)
22 display.text("Pico", 0, 0, 1) # 4 harf tam sığar / exactly 4 letters fit
23 display.show()
24 time.sleep(1)
25 display.fill(0)
26 display.rect(0, 0, 32, 8, 1) # ekran çerçevesi / display frame
27 display.show()
28 time.sleep_ms(500)Ideas de proyectos con el MAX7219
- Letrero de texto en movimiento: crea un tablón de mensajes para una tienda, un aula o la puerta de tu cuarto y cambia el mensaje desde el móvil con un módulo Bluetooth HC-05.
- Reloj, calendario y temperatura: muestra por turnos la hora y la fecha del RTC y la temperatura y la humedad de un sensor DHT11.
- Reloj por internet y tiempo: desplaza con un ESP32 la hora NTP y la temperatura obtenida de una API del tiempo.
- Marcador: controla una pantalla de dos zonas para un partido de ping-pong o baloncesto con un mando IR.
- Minijuegos: juega a la serpiente, al Pong o a esquivar obstáculos en la pantalla de 32x8 con un joystick; tienes más ideas en Proyectos de juegos con Arduino.
- Medidor de nivel de sonido: dibuja las lecturas de un sensor de sonido como un gráfico de barras de 32 columnas.
- Panel de animaciones de 32x32: apila cuatro módulos 4 en 1 y reproduce animaciones como lluvia, estrellas o fuegos artificiales.
Errores comunes y cómo solucionarlos
| Síntoma | Causa probable | Solución |
|---|---|---|
| No se enciende ningún LED | No hay alimentación, los cables están en el lado OUT o el pin CS no coincide con el código | Conecta los cables al lado IN marcado como DIN y revisa CS_PIN y la alimentación de 5 V |
| Todos los LED están al máximo y no cambian | No llegan datos: DIN y CLK están intercambiados o el chip se quedó en modo de prueba | Conecta DIN a MOSI y CLK a SCK, y desconecta y vuelve a conectar la alimentación |
| Las letras salen en espejo, boca abajo o giradas 90 grados | HARDWARE_TYPE incorrecto | Prueba FC16_HW en el módulo 4 en 1 y GENERIC_HW en el módulo individual |
| Todo el texto está boca abajo y se desplaza de izquierda a derecha | El módulo está montado al revés, con la entrada DIN a la izquierda | Gira el módulo 180 grados; la entrada DIN debe quedar a la derecha |
| Solo funciona un bloque o el texto aparece cortado | MAX_DEVICES incorrecto o conexión rota entre módulos | Pon en MAX_DEVICES el número de bloques y revisa los cables OUT → IN |
| El Arduino se reinicia o la imagen parpadea al subir el brillo | Se supera el límite de 500 mA del USB y cae la tensión | Usa una fuente externa de 5 V y 2 A, une los GND y baja el brillo |
| Puntos al azar y caracteres corruptos con el ESP32 | La señal de 3,3 V está por debajo del umbral HIGH de 3,5 V y los cables son largos | Eleva el nivel con un 74HCT125 y acorta los cables |
| Las tildes aparecen como símbolos sin sentido | Los bytes UTF-8 llegan tal cual a la fuente | Usa el método utf8ToFont() y addChar() |
| El texto avanza a tirones | Hay un delay() o una tarea larga dentro de loop() | Llama a displayAnimate() a menudo y gestiona las esperas con millis() |
El MAX7219 es una de las formas más baratas de conseguir una pantalla llamativa con unos pocos cables: el chip se encarga del barrido, de limitar la corriente y del brillo, y tú solo te ocupas de qué mostrar. Empieza dibujando iconos en un módulo 8x8 y pasa después al texto en movimiento, a un reloj y a mensajes inalámbricos con un ESP32 en un módulo 4 en 1. Si quieres LED de colores controlados uno a uno, los WS2812B NeoPixel son una buena opción, y para un proyecto que solo muestre números, un display de 7 segmentos. En la app BlueGrays encontrarás proyectos con el MAX7219, como un reloj y calendario de matriz de puntos, una estación meteorológica, animaciones de 32x8 y texto en movimiento, paso a paso con esquemas y código.
Preguntas frecuentes
¿Por qué el texto de mi módulo MAX7219 sale en espejo o al revés?
Cada fabricante conecta la matriz 8x8 al MAX7219 de una forma distinta, y la librería lo compensa con el ajuste HARDWARE_TYPE. La mayoría de los módulos 4 en 1 azules que se venden hoy se ven bien con FC16_HW, y los módulos individuales verdes con GENERIC_HW. Si la imagen sigue mal, prueba también PAROLA_HW e ICSTATION_HW; en el código solo cambia una línea.
¿Cuántos módulos MAX7219 se pueden encadenar?
El chip no tiene límite y, como MD_MAX72XX guarda el número de módulos en un byte, la librería admite hasta 255. En la práctica el límite lo ponen la alimentación y la calidad de la señal: cada 4–8 bloques vuelve a llevar 5 V y GND a la cadena, usa cables cortos y añade a cada módulo un condensador de 10 µF y otro de 100 nF. Cuanto más larga es la cadena, más tarda cada actualización y más RAM ocupa.
¿Funciona el MAX7219 con un ESP32 a 3,3 V?
El MAX7219 necesita al menos 4 V de alimentación, así que alimenta el módulo desde el pin 5V (VIN) del ESP32. La hoja de datos pide 3,5 V como nivel HIGH con alimentación de 5 V; las señales de 3,3 V del ESP32 funcionan igualmente en la mayoría de los módulos, pero están por debajo de ese límite. Para un funcionamiento fiable, coloca un elevador de nivel como el 74HCT125 en las líneas DIN, CLK y CS.
¿Cuánta corriente consume una matriz LED MAX7219?
Un módulo 8x8 con todos los LED encendidos al máximo brillo consume unos 330 mA, y un módulo 4 en 1 de 8x32 hasta 1,3 A. Con texto en movimiento solo se enciende parte de los LED y el brillo suele estar entre 2 y 7, así que un módulo 4 en 1 consume normalmente unos 100–250 mA. En modo apagado cada chip gasta solo unos 150 µA.
¿Cómo mostrar la ñ y las tildes en una matriz MAX7219?
La fuente por defecto de MD_Parola incluye la ñ, las vocales acentuadas y el resto de caracteres Latin-1, pero el Arduino IDE los guarda como dos bytes UTF-8. Convierte el texto a los códigos de la fuente con una pequeña función antes de mostrarlo. Los caracteres que no están en Latin-1 puedes dibujarlos tú y añadirlos con addChar().
¿Se puede controlar un display de 7 segmentos con el MAX7219?
Sí. El MAX7219 se diseñó en realidad para displays de 7 segmentos de cátodo común de hasta 8 dígitos, y tiene un modo BCD que decodifica los números por sí solo. Puedes usar fácilmente los módulos de 8 dígitos con MAX7219 con la función setDigit() de la librería LedControl. Los displays de ánodo común, en cambio, no se pueden conectar directamente a este chip.
Proyectos para hacer después de leer esto
Ver todos los proyectos
Reloj y calendario de matriz de puntos (MAX7219, DS1307)
Un panel luminoso para tu mesa o tu pared que muestra la hora y la fecha con texto en movimiento. En la pantalla LED de 8x32 se ve un reloj de números grandes cuyos dos puntos parpadean cada segundo;

Reloj matricial, calendario y estación meteorológica (MAX7219, DS1307)
Un panel de texto en movimiento para tu mesa o tu pared que muestra por turnos la hora, la fecha y el clima de la habitación. En la pantalla LED de 8x32 aparece primero la temperatura en grados Celsiu

4x 32x8 Matriz LED Animación Y Texto
Si apilas cuatro módulos largos de matriz LED, obtienes una pantalla cuadrada con más de mil puntos de luz. Primero caen letras desde arriba como una lluvia de película, luego se abre un telón y en do

Matriz 4x 32x8 LED - Pack de 6 animaciones
Cuatro paneles LED de 32x8 colocados uno encima de otro forman una pantalla cuadrada donde se suceden seis animaciones: lluvia digital que cae, una espiral que crece, células que nacen y mueren por sí

Serpiente de píxeles Matriz 8x8
Monta con tus propias manos el clásico juego de la serpiente y juega. En una matriz de luces de colores de 8x8 guías una serpiente verde con un joystick y te comes manzanas rojas; con cada manzana la

ESP32: hora real desde Internet con NTP
El ESP32 se conecta a Wi-Fi y obtiene la hora real con precisión de milisegundos desde servidores NTP (Network Time Protocol). Gracias a `configTzTime()`, se aplica automáticamente la zona horaria de Turquía (TRT, UTC+3) y el cambio DST se gestiona mediante la cadena POSIX TZ. La hora se muestra en el monitor serie y en una pantalla LCD I2C opcional. `configTzTime()` inicia el servicio SNTP; se realizan resincronizaciones periódicas mientras hay acceso a la red, y el reloj del sistema sigue avan

Reloj digital con ESP32 y RTC DS1307 (I2C + LCD)
En este proyecto, mostramos la hora y la fecha en una pantalla LCD I2C de 16x2 mediante el módulo de reloj en tiempo real (RTC) DS1307. El DS1307 es un RTC con batería de respaldo que conserva la hora incluso si el ESP32 se apaga. Ambos módulos comparten la misma línea I2C (SDA/SCL), por lo que aprendemos a controlar varios dispositivos con solo dos cables de señal. Con este proyecto conocerás el protocolo I2C, el funcionamiento de los módulos RTC y la visualización dinámica en pantallas LCD.

Reloj digital con display de 7 segmentos de 4 dígitos
Un reloj digital luminoso de cuatro cifras, parecido al del horno de la cocina. El módulo de reloj DS1302 guarda la hora y cada segundo el Arduino lee las horas y los minutos y los muestra en el indic
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 160 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

