Cómo Conectar una Pantalla LCD 128×64 con Controlador ST7920 a Arduino
Las pantallas LCD de caracteres 16×2 cumplen de sobra para mostrar un par de líneas de texto, pero en cuanto quieres dibujar un gráfico, un icono o una barra de progreso, se quedan cortas. Ahí es donde entra el clásico LCD gráfico 128×64 con controlador ST7920 (a veces etiquetado simplemente como «12864»): una pantalla monocroma barata, muy extendida en el mundo maker, capaz de dibujar cualquier píxel de sus 8.192 puntos de forma independiente. En este tutorial vas a ver cómo cablearla a un Arduino Uno usando su modo serie (solo 3 cables de datos), cómo instalar la librería U8g2 y cómo pasar de un simple «Hola Mundo» a un panel con textos, formas, gráficas de barras y hasta tu propio logo en bitmap.
Puntos clave
- El modo serie del ST7920 simplifica mucho el cableado: con el pin PSB a GND solo necesitas 3 líneas de datos además de alimentación.
- La RAM del Arduino Uno es el principal cuello de botella: un buffer completo de pantalla ocupa la mitad de sus 2 KB de SRAM; el modo de «buffer por páginas» de U8g2 lo resuelve.
- El contraste mal ajustado es la causa nº1 de «pantalla en blanco»: antes de sospechar del cableado, prueba a girar el potenciómetro.
- drawStr() posiciona el texto por su línea base, no por el borde superior: con Y=0 se corta la parte alta de las letras.
¿Qué es una pantalla gráfica 128×64?
A diferencia de un LCD de caracteres, donde solo puedes escribir letras en posiciones fijas de una rejilla, un LCD gráfico 128×64 controla cada uno de sus píxeles de forma individual: 128 columnas por 64 filas, 8.192 píxeles en total. Como cada píxel solo puede estar encendido o apagado, una imagen completa de pantalla ocupa exactamente 8.192 bits, es decir, 1.024 bytes. Ese dato importa más de lo que parece: un Arduino Uno solo tiene 2 KB de SRAM, así que un buffer de pantalla completo se comería la mitad de toda su memoria disponible. Más adelante veremos cómo la librería U8g2 resuelve esto con su modo de «buffer por páginas».
Los tres modos de comunicación del ST7920
El controlador ST7920 admite tres formas de hablar con un microcontrolador:
- Paralelo de 8 bits: rápido, pero exige muchísimos pines de datos.
- Paralelo de 4 bits: un compromiso intermedio, sigue usando bastantes pines.
- Serie síncrono: solo necesita reloj, datos y selección de chip.
El pin PSB es el que decide el modo: conectado a VCC activa el modo paralelo, conectado a GND activa el modo serie. En este tutorial usamos siempre el modo serie, tanto porque libera muchísimos pines del Arduino como porque es el que espera U8g2 de forma más directa. Merece la pena aclarar que el «modo serie» del ST7920 no es SPI estándar (el orden y el empaquetado de bits son distintos), pero la librería U8g2 se encarga de toda esa traducción por ti, así que en la práctica no tienes que pensar en ello.
Qué necesitas
- Un Arduino Uno o una placa compatible.
- Un módulo LCD 128×64 con controlador ST7920 (a menudo vendido como «12864B»).
- Un potenciómetro o trimmer de 10 kΩ para el contraste.
- Una resistencia pull-up de 10 kΩ para el pin de reset.
- Una resistencia de 100–220 Ω para el retroiluminado, si tu módulo no la lleva ya incorporada.
- Cables de conexión y una protoboard.
Muchos módulos ya integran en su propia placa tanto el ajuste de contraste como la resistencia del retroiluminado. Antes de añadir componentes extra, revisa la serigrafía de tu módulo concreto: puede que ya lo traiga resuelto.
Pinout del ST7920 128×64
| Pin | Nombre | Función |
|---|---|---|
| 1 | GND | Masa |
| 2 | VCC | Alimentación (normalmente 5V) |
| 3 | V0 | Tensión de contraste (cursor del potenciómetro) |
| 4 | RS / CS | Selección de chip (serie) / registro (paralelo) |
| 5 | R/W / SID | Dato serie (SID) en modo serie |
| 6 | E / SCLK | Reloj serie (SCLK) en modo serie |
| 7–14 | DB0–DB7 | Bus de datos paralelo; sin uso en modo serie |
| 15 | PSB | Selector de modo: VCC = paralelo, GND = serie |
| 16 | NC | Sin conexión |
| 17 | RST | Reset (activo en bajo); no debe quedar al aire |
| 18 | VOUT | Salida de tensión negativa interna, referencia del contraste |
| 19 | BLA | Ánodo del retroiluminado (+) |
| 20 | BLK | Cátodo del retroiluminado (−) |
Como en modo serie no usamos el bus paralelo, los pines 7 a 14 quedan simplemente sin conectar.
Cómo cablear el Arduino Uno al LCD
| ST7920 | Arduino Uno | Función |
|---|---|---|
| GND | GND | Masa común |
| VCC | 5V | Alimentación |
| V0 | Cursor del potenciómetro | Contraste |
| RS / CS | D10 | Selección de chip |
| R/W / SID | D11 | Dato serie |
| E / SCLK | D13 | Reloj serie |
| PSB | GND | Activa el modo serie |
| RST | 5V a través de una resistencia de 10 kΩ | Mantiene el reset inactivo |
| BLA | 5V a través de una resistencia de 100–220 Ω | Retroiluminado (+) |
| BLK | GND | Retroiluminado (−) |

Con PSB a GND, el ST7920 solo necesita tres líneas de datos: chip select, dato serie y reloj.
El circuito del potenciómetro de contraste
El pin V0 no se conecta directamente a nada fijo: recibe la tensión del cursor de un potenciómetro de 10 kΩ, con sus dos extremos entre 5V y GND. Girando el potenciómetro cambias esa tensión intermedia y, con ella, el contraste del panel.

El cursor del potenciómetro entrega a V0 un valor intermedio entre 0V y 5V: ese es el ajuste de contraste.
Un consejo práctico: tras el primer montaje, gira el potenciómetro despacio de un extremo a otro observando la pantalla. Si el módulo recibe alimentación y el cableado de datos es correcto, en algún punto del recorrido deberían aparecer bloques oscuros o el texto de la prueba. Un contraste mal ajustado es, con diferencia, la causa más habitual de que una pantalla «no funcione» cuando en realidad sí lo hace.
Por qué D10, D11 y D13
Esos tres pines no son arbitrarios: coinciden con las señales estándar CS, MOSI y SCK del Arduino Uno. El constructor de software-SPI de U8g2 te permite indicar explícitamente qué pines usar, así que si tu proyecto ya tiene esos pines ocupados, puedes elegir otros distintos siempre que actualices el constructor en el código.
Instalar la librería U8g2
- Abre el IDE de Arduino.
- Ve a Programa > Incluir Librería > Gestionar Bibliotecas.
- Busca «U8g2».
- Instala la librería «U8g2 by olikraus».
- Si los ejemplos no aparecen enseguida en el menú, reinicia el IDE.
U8g2 incluye constructores distintos según el controlador, el método de comunicación y el tipo de buffer de memoria. Para el ST7920 concretamente, busca los nombres que empiezan por U8G2_ST7920_128X64, tanto para SPI por software como por hardware o para el bus paralelo.
Tu primer «Hola Mundo» en la pantalla
Con el cableado hecho y la librería instalada, este sketch conecta con el LCD por SPI software y escribe dos líneas de texto:
#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>
U8G2_ST7920_128X64_1_SW_SPI u8g2(
U8G2_R0,
/* clock=*/ 13,
/* data=*/ 11,
/* cs=*/ 10,
/* reset=*/ U8X8_PIN_NONE
);
void setup() {
u8g2.begin();
}
void loop() {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.drawStr(5, 24, "Descubre");
u8g2.drawStr(20, 48, "Arduino");
} while (u8g2.nextPage());
delay(1000);
}Cómo se construye el objeto u8g2
El nombre del constructor ya cuenta buena parte de la historia: U8G2 (la API), ST7920 (el controlador), 128X64 (la resolución), 1 (buffer por páginas) y SW_SPI (SPI implementado por software, sin usar el periférico SPI del chip). Los parámetros que recibe son, en orden: la rotación, el pin de reloj, el pin de datos, el pin de selección de chip y el pin de reset.
U8G2_R0 es la orientación normal; U8G2_R1, U8G2_R2 y U8G2_R3 giran la imagen 90°, 180° y 270° respectivamente, muy útil si montas la pantalla girada dentro de una carcasa.
Inicializar la pantalla
u8g2.begin() envía la secuencia de comandos de arranque al controlador, limpia la pantalla y la deja en modo normal, lista para dibujar.
Entendiendo el bucle de página
Con el constructor «1» (buffer por páginas), todo lo que quieras dibujar tiene que ir dentro de este patrón:
u8g2.firstPage();
do {
// aquí van tus instrucciones de dibujo
} while (u8g2.nextPage());La ventaja es que U8g2 solo mantiene en RAM una franja pequeña de la pantalla a la vez, y va enviando el resto por partes; la contrapartida es que todas las instrucciones de dibujo se ejecutan varias veces, una por cada franja o «página» que se procesa internamente. No es un problema para la mayoría de sketches, pero conviene saber que ese bucle se repite.
Elegir una fuente
La llamada u8g2.setFont(...) selecciona qué tipografía usar antes de escribir texto. Algunas fuentes compactas y muy prácticas para pantallas pequeñas:
u8g2_font_5x7_tru8g2_font_6x10_tru8g2_font_ncenB08_tru8g2_font_ncenB14_tr
Las fuentes más grandes ocupan más memoria flash del Arduino, así que si tu sketch ya anda justo de espacio, merece la pena probar con una fuente más compacta.
El sistema de coordenadas de la pantalla
La esquina superior izquierda es el origen (0, 0); el eje X va de 0 a 127 y el eje Y de 0 a 63, creciendo hacia abajo.

El origen está arriba a la izquierda; el eje Y crece hacia abajo, al contrario que en unos ejes matemáticos habituales.
Hay un detalle que suele pillar a todo el mundo la primera vez: en drawStr(x, y, texto), la Y representa la línea base del texto, no su borde superior. Si usas Y=0, la parte alta de las letras queda cortada fuera de la pantalla porque no hay sitio por encima de la línea base para dibujarla.
Buffer completo frente a buffer por páginas
El sufijo del constructor también controla cuánta memoria RAM reserva U8g2 para la pantalla. Cambiando de:
U8G2_ST7920_128X64_1_SW_SPI u8g2(...);a:
U8G2_ST7920_128X64_F_SW_SPI u8g2(...);pasas del buffer por páginas (constructor «1») al buffer completo (constructor «F»), y entonces el patrón de dibujo cambia a:
u8g2.clearBuffer();
// instrucciones de dibujo
u8g2.sendBuffer();El buffer completo simplifica mucho las animaciones, porque solo dibujas una vez por fotograma en lugar de varias veces por el bucle de páginas, pero exige reservar unos 1.024 bytes de RAM de forma permanente.

En un Arduino Uno, el buffer completo se lleva la mitad de la SRAM disponible; el buffer por páginas apenas roza el 6%.
Para un Arduino Uno o Nano, con solo 2 KB de SRAM totales, el buffer por páginas suele ser la opción más segura: deja mucha más memoria libre para tus variables, arrays de lecturas de sensores o buffers de comunicación. Si trabajas con un ESP32 o una placa con más RAM, el buffer completo deja de ser un problema y puede simplificarte bastante el código de animaciones.
Dibujar formas geométricas
Además de texto, U8g2 incluye funciones de dibujo vectorial: líneas, rectángulos, círculos, triángulos… Este sketch dibuja un marco, una cabecera con texto invertido, un círculo, un disco relleno, un triángulo y un rectángulo de esquinas redondeadas:
void loop() {
u8g2.firstPage();
do {
u8g2.drawFrame(0, 0, 128, 64);
u8g2.drawBox(0, 0, 128, 14);
u8g2.setDrawColor(0);
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(38, 11, "GRAFICOS");
u8g2.setDrawColor(1);
u8g2.drawCircle(24, 40, 12);
u8g2.drawDisc(56, 40, 10);
u8g2.drawTriangle(78, 50, 90, 24, 102, 50);
u8g2.drawRFrame(6, 24, 0, 0, 0);
} while (u8g2.nextPage());
}Algunas de las funciones de dibujo más usadas de la librería:
| Función | Qué dibuja |
|---|---|
| drawPixel(x, y) | Un único píxel |
| drawLine(x1, y1, x2, y2) | Una línea entre dos puntos |
| drawHLine / drawVLine | Una línea horizontal o vertical |
| drawFrame(x, y, ancho, alto) | Un rectángulo hueco |
| drawBox(x, y, ancho, alto) | Un rectángulo relleno |
| drawCircle / drawDisc | Un círculo hueco o relleno |
| drawTriangle(…) | Un triángulo relleno |
| drawRFrame(…) | Un rectángulo hueco de esquinas redondeadas |
Crear un panel de sensor analógico
Un caso de uso muy habitual es leer un sensor analógico y mostrar su valor tanto en número como en una barra gráfica. Este ejemplo lee un potenciómetro en A0 (puedes sustituirlo por un sensor de temperatura, luz o lo que necesites) y dibuja un pequeño panel:
const int pinSensor = A0;
void loop() {
int valorCrudo = analogRead(pinSensor);
int porcentaje = map(valorCrudo, 0, 1023, 0, 100);
int anchoBarra = map(valorCrudo, 0, 1023, 0, 122);
u8g2.firstPage();
do {
u8g2.drawBox(0, 0, 128, 12);
u8g2.setDrawColor(0);
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(2, 10, "SENSOR A0");
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.setCursor(2, 28);
u8g2.print(valorCrudo);
u8g2.setCursor(70, 28);
u8g2.print(porcentaje);
u8g2.print("%");
u8g2.drawFrame(2, 40, 124, 12);
u8g2.drawBox(3, 41, anchoBarra, 10);
u8g2.setFont(u8g2_font_5x7_tr);
u8g2.drawStr(2, 62, "0");
u8g2.drawStr(60, 62, "50");
u8g2.drawStr(118, 62, "100");
} while (u8g2.nextPage());
}Un detalle importante: los cálculos de valorCrudo, porcentaje y anchoBarra están fuera del bucle de página, hechos una sola vez por vuelta de loop(). Como el bucle de página ejecuta las instrucciones de dibujo varias veces, si leyeras el sensor dentro de él podrías obtener valores ligeramente distintos entre una pasada y otra, con el consiguiente parpadeo o desajuste visual.
Este mismo esquema sirve para mostrar temperatura, voltaje de batería, velocidad de un motor, nivel de un depósito, presión, intensidad de luz, intensidad de señal o la posición de un servo: cualquier magnitud que puedas leer como número.
Mostrar una imagen bitmap
Para mostrar un logo o un icono, primero hay que convertir la imagen a un array de bytes en C. Existen varias herramientas online para esto (busca «LCD bitmap converter»); el resultado es un array como este:
#define logo_width 64
#define logo_height 32
static const unsigned char logo_bits[] U8X8_PROGMEM = {
// datos hexadecimales generados por la herramienta
};Recomendaciones para preparar la imagen
- Usa imágenes simples, en blanco y negro puro.
- Elimina detalles finos: se pierden a baja resolución.
- Ajusta el tamaño al área real donde se va a mostrar.
- Busca buen contraste entre el motivo y el fondo.
- Evita convertir directamente una fotografía a color: el resultado en monocromo suele ser ilegible.
Como referencia de tamaños: un logo a pantalla completa serían 128 x 64 px; un logo pequeño, unos 64 x 32 px.

Para centrar un logo de 64 px en una pantalla de 128 px, el margen a cada lado es (128 − 64) / 2 = 32 px.
void loop() {
u8g2.firstPage();
do {
u8g2.drawXBM(32, 4, logo_width, logo_height, logo_bits);
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 50, "DescubreArduino");
u8g2.drawFrame(14, 56, 100, 6);
u8g2.drawBox(14, 56, 60, 6);
} while (u8g2.nextPage());
}Un detalle que suele confundir: si usas drawBitmap() en lugar de drawXBM(), el parámetro de ancho se espera en bytes, no en píxeles (ancho_px / 8). Un logo de 64 píxeles de ancho equivale, en ese caso, a un valor de 8. Un bitmap de 64×32 ocupa 256 bytes en la memoria del programa: nada comparado con la RAM de vídeo, pero conviene tenerlo en cuenta si guardas varias imágenes.
Usar SPI por hardware
Si prefieres aprovechar el periférico SPI del propio chip en lugar de bit-banging por software, existe un constructor equivalente:
U8G2_ST7920_128X64_1_HW_SPI u8g2(
U8G2_R0,
/* cs=*/ 10,
/* reset=*/ U8X8_PIN_NONE
);En un Arduino Uno, el cableado no cambia: SID sigue yendo a D11 (MOSI), SCLK a D13 (SCK) y CS a D10. La ventaja del SPI por hardware es, potencialmente, una transferencia de pantalla algo más rápida; la desventaja es que el constructor por software es más fácil de entender y te permite elegir libremente cualquier pin digital.
Controlar el pin de reset desde el Arduino
En lugar de dejar RST fijo a 5V con una resistencia pull-up, puedes gestionarlo desde un pin digital del Arduino (por ejemplo D8):
U8G2_ST7920_128X64_1_SW_SPI u8g2(
U8G2_R0,
/* clock=*/ 13,
/* data=*/ 11,
/* cs=*/ 10,
/* reset=*/ 8
);Nunca dejes el pin RST al aire (sin conectar): un reset flotante provoca comportamientos de arranque inconsistentes, con la pantalla funcionando unas veces sí y otras no.
Solución de problemas comunes
El retroiluminado se enciende, pero no aparece nada
- Ajusta el potenciómetro de contraste, despacio, de un extremo a otro.
- Verifica que PSB está conectado a GND (modo serie).
- Repasa el cableado de CS, SID y SCLK.
- Confirma que el LCD y el Arduino comparten la misma masa.
- Comprueba que RST está en nivel alto (o correctamente controlado por un pin).
- Verifica que usas el constructor U8g2 correcto para tu tipo de conexión.
Solo aparecen bloques oscuros o píxeles aleatorios
Suele apuntar a: contraste incorrecto, masa floja, PSB al aire, orden de pines equivocado en el constructor, reset inestable, cables demasiado largos o una alimentación de 5V débil. Como norma general, mantén los cables de reloj, datos y masa razonablemente cortos.
La imagen sale invertida o del revés
Cambia el parámetro de rotación del constructor: U8G2_R0 (normal), U8G2_R1 (90°), U8G2_R2 (180°) o U8G2_R3 (270°).
El bitmap se ve corrupto
- Comprueba que el ancho y el alto declarados coinciden con los de la imagen generada.
- Verifica que pasas el nombre correcto del array a la función de dibujo.
- Confirma que el array está guardado con
U8X8_PROGMEM. - Revisa que la imagen usa el orden de bytes esperado (formato XBM).
- Asegúrate de que las coordenadas de dibujo no se salen de la pantalla.
La pantalla deja de funcionar al añadir otro dispositivo SPI
Cada dispositivo SPI necesita su propio pin de selección de chip independiente. Verifica que los dispositivos inactivos mantienen su CS en el estado correcto, y confirma que ningún otro periférico interfiere con el reloj o los datos del ST7920 durante la inicialización.
Preguntas frecuentes
¿Puedo alimentar el ST7920 a 3,3V?
La mayoría de módulos 12864 están pensados para 5V. Revisa la hoja de datos o la serigrafía de tu módulo concreto. Si tu microcontrolador trabaja a 3,3V, puede que necesites un adaptador de niveles según los umbrales de entrada del LCD.
¿Puedo usarlo con un Arduino Mega?
Sí. El constructor de SPI por software funciona con cualquier pin digital; solo tienes que actualizar el cableado y los valores del constructor. El SPI por hardware, en cambio, usa los pines dedicados del Mega.
¿Y con un Arduino Nano?
Sí, sin cambios relevantes: el Nano clásico usa el mismo ATmega328P y la misma disposición de pines D10, D11 y D13 que el Uno.
¿Puede el ST7920 mostrar imágenes en color?
No. Es monocromo: cada píxel está encendido o apagado. El tono azul, verde o amarillo que a veces se ve viene del propio panel LCD y de su retroiluminado, no de un control de color por píxel.
¿Puedo mostrar gráficas en tiempo real?
Sí. Guarda las lecturas del sensor en un array y dibuja líneas entre puntos consecutivos con drawLine(). En un Arduino Uno, conviene mantener ese histórico pequeño para no consumir demasiada SRAM.
¿Puedo usar números más grandes?
Sí, U8g2 incluye fuentes numéricas de mayor tamaño, pero ocupan más memoria flash y más espacio en pantalla.
¿Por qué mi módulo pone «12864B» en vez de «ST7920»?
«12864B» es la designación comercial del módulo o de la placa, no del controlador. Revisa la serigrafía del chip, la documentación del vendedor o los nombres de los pines: la presencia de PSB, SID, SCLK y VOUT suele indicar un módulo de estilo ST7920, pero conviene confirmarlo antes de conectar nada.
Conclusión
Un LCD gráfico 128×64 con ST7920 abre muchas puertas frente a una pantalla de caracteres: menús, paneles con varias magnitudes a la vez, gráficas, iconos y una interfaz bastante más cuidada, todo con un módulo barato y fácil de encontrar. El modo serie mantiene el cableado manejable (solo tres líneas de datos), y la librería U8g2 se encarga tanto de la comunicación de bajo nivel como de todas las funciones de dibujo. Con el «Hola Mundo» ya funcionando, el siguiente paso natural es enchufar un sensor real al panel de ejemplo y, cuando le cojas el punto, crear tu propio logo con un conversor de bitmaps. La resolución de 128×64 se queda corta comparada con las pantallas modernas, pero con las fuentes y los gráficos adecuados da para interfaces embebidas perfectamente cuidadas: medidores digitales, estaciones meteorológicas, monitores de batería, controladores con menú, registradores de datos, paneles de control de motores o instrumentos de prueba portátiles.
