Home / Tutorials / Arduino Tutorial / Arduino 128×64 LCD Interfacing Using the ST7920

Arduino 128×64 LCD Interfacing Using the ST7920

pcbway

A 16×2 character LCD is enough when you only need to display a few numbers or short messages. However, once your project needs icons, graphs, progress bars, larger fonts, or a proper user interface, a graphical LCD becomes a much better option. In this tutorial, I will show you how to interface a 128×64 graphical LCD with an Arduino Uno. The display used here is the common 12864 module based on the ST7920 LCD controller.

We will start by displaying text, then move on to drawing shapes, creating a simple sensor dashboard, and finally displaying a custom bitmap image.

Important: “128×64” only describes the screen resolution. There are other 128×64 displays based on controllers such as the KS0108, SSD1306, SH1106, and ST7565. The wiring and code in this tutorial are specifically for an ST7920-based LCD.

What Is a 128×64 Graphical LCD?

A 128×64 graphical LCD contains a grid of 128 horizontal pixels and 64 vertical pixels. Unlike a character LCD, its pixels can be individually controlled.

This gives us a total of:

128\cdot64=8192\; pixels

Because the display is monochrome, each pixel normally requires only one bit. A complete screen image therefore, needs:

\frac{8}{8192}=1024 \;bytes

This is worth remembering when using an Arduino Uno. The Uno has only 2 KB of SRAM, so storing a full 1024-byte screen buffer consumes about half of its available RAM before we even include variables, library data, and other parts of the program.

To avoid running out of memory, we will use the page-buffer version of the U8g2 library.

ST7920 Interface Modes

The ST7920 supports three communication modes:

    • 8-bit parallel
    • 4-bit parallel
    • Synchronous serial

Parallel mode is faster, but it requires many Arduino pins. Serial mode needs only three control signals, so it is usually the better choice for Arduino projects.

The PSB pin selects the interface:

    • PSB connected to VCC: parallel mode
    • PSB connected to GND: serial mode

In serial mode, the ST7920 uses its SID, SCLK, and CS signals. The controller’s serial interface is write-only and uses a slightly different data format from ordinary SPI devices, but the U8g2 library handles these details for us.

128x64 LCD pinout

Components Required

For this tutorial, you will need:

  • Arduino Uno or compatible board
  • 128×64 ST7920 graphical LCD
  • 10 kΩ potentiometer or trimmer
  • 10 kΩ resistor for the LCD reset pull-up
  • 100 Ω to 220 Ω resistor for the backlight
  • Jumper wires
  • Breadboard, optional

Some ST7920 modules already include an onboard contrast adjustment and backlight resistor. Check the PCB before adding external components.

ST7920 128×64 LCD Pinout

The common ST7920 module has a 20-pin connector.

Pin Label Function
1 GND or VSS Ground
2 VCC or VDD 5 V supply
3 VO LCD contrast input
4 RS or CS Chip select in serial mode
5 R/W or SID Serial data input
6 E or SCLK Serial clock
7 DB0 Parallel data bit 0
8 DB1 Parallel data bit 1
9 DB2 Parallel data bit 2
10 DB3 Parallel data bit 3
11 DB4 Parallel data bit 4
12 DB5 Parallel data bit 5
13 DB6 Parallel data bit 6
14 DB7 Parallel data bit 7
15 PSB Parallel/serial mode selection
16 NC Normally not connected
17 RST LCD reset
18 VOUT Contrast-voltage output
19 BLA or A Backlight positive
20 BLK or K Backlight negative

Pins 7 through 14 are not needed when the LCD is operating in serial mode.

Module manufacturers sometimes change the functions or labels of pins 16 through 18. Always check the labels printed on your LCD PCB before applying power.

Arduino 128×64 LCD Wiring Diagram

We will connect the display using serial mode.

ST7920 LCD Arduino Uno Purpose
GND GND Ground
VCC 5V LCD power
VO Potentiometer wiper Contrast
RS/CS D10 Chip select
R/W/SID D11 Serial data
E/SCLK D13 Serial clock
PSB GND Select serial mode
RST 5V through 10 kΩ Keep LCD out of reset
VOUT One end of potentiometer Contrast reference
BLA 5V through 100–220 Ω Backlight positive
BLK GND Backlight negative

Connect the remaining end of the contrast potentiometer to GND. Its center pin or wiper connects to VO.

The contrast circuit will therefore look like this:

Slowly turn the potentiometer after powering the circuit. Incorrect contrast is one of the most common reasons an LCD appears completely blank even when the code and communication wiring are correct.

Why Use Pins 10, 11, and 13?

Pins 11 and 13 are the Arduino Uno’s normal MOSI and SCK pins. However, the first examples will use U8g2’s software-serial constructor, which lets us explicitly define the clock, data, and chip-select pins.

You can use different Arduino digital pins as long as you also update the constructor in the sketch.

Arduino 16x4 LCD wiring

Suggested image 4 — Actual assembled circuit:
Insert after the wiring diagram. Use a real photograph of the completed wiring. The LCD should be powered and displaying a simple message. This gives readers a reference when checking wire orientation and pin numbering.

Installing the U8g2 Library

We will use the U8g2 library to control the display. It supports the ST7920 and provides functions for displaying text, drawing shapes, and rendering bitmap images.

To install it:

  1. Open the Arduino IDE.
  2. Go to Sketch > Include Library > Manage Libraries.
  3. Search for U8g2.
  4. Install U8g2 by olikraus.
  5. Restart the Arduino IDE if the examples do not immediately appear.

The library provides separate constructors for different controllers, communication methods, and buffer sizes. For the ST7920, constructors beginning with U8G2_ST7920_128X64 are available for software SPI, hardware SPI, and parallel interfaces.

Arduino 128×64 LCD Hello World Code

Let us begin with a simple text example.

#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>

// ST7920 software serial connection:
// clock = D13
// data  = D11
// CS    = D10
//
// The LCD reset pin is connected to 5V through a pull-up,
// so U8X8_PIN_NONE is used for the reset argument.

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R0,
  13,
  11,
  10,
  U8X8_PIN_NONE
);

void setup() {
  u8g2.begin();
}

void loop() {
  u8g2.firstPage();

  do {
    u8g2.setFont(u8g2_font_ncenB14_tr);

    u8g2.drawStr(5, 24, "Teach Me");
    u8g2.drawStr(20, 48, "Micro");

  } while (u8g2.nextPage());

  delay(1000);
}

Upload the sketch to the Arduino. You should see two lines of text on the LCD.

Suggested image 5 — Hello World result:
Insert after the code. Show the actual LCD displaying “Teach Me Micro.” Keep the image tightly cropped so the text and pixel quality are clearly visible.

How the Code Works

The first three lines include the required Arduino, SPI, and U8g2 headers:

#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>

The following statement creates the display object:

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R0,
  13,
  11,
  10,
  U8X8_PIN_NONE
);

Let us break down the constructor name:

  • U8G2 selects the full graphics API.
  • ST7920 selects the display controller.
  • 128X64 selects the display resolution.
  • 1 selects a one-page display buffer.
  • SW_SPI selects software-generated serial communication.

The constructor arguments are:

rotation, clock, data, chip_select, reset

U8G2_R0 means that the display uses its normal landscape orientation. U8g2 also provides U8G2_R1, U8G2_R2, and U8G2_R3 for rotating the display by 90, 180, and 270 degrees.

Initializing the Display

The display is initialized inside setup():

u8g2.begin();

This sends the required setup commands, clears the display, and takes it out of power-save mode.

Understanding the Picture Loop

Because we selected the page-buffer constructor, every drawing command must be placed inside the picture loop:

u8g2.firstPage();

do {
// Drawing commands go here
} while (u8g2.nextPage());

The library draws one section of the screen at a time. It then repeats the drawing code for the next section until the complete display has been updated.

The advantage is lower SRAM usage. The disadvantage is that every object on the screen must be redrawn during each page pass.

Selecting a Font

This line selects a font:

u8g2.setFont(u8g2_font_ncenB14_tr);

U8g2 includes many fonts, but larger fonts also consume more flash memory. For small status displays, compact fonts such as the following are useful:

u8g2_font_5x7_tr
u8g2_font_6x10_tr
u8g2_font_ncenB08_tr
u8g2_font_ncenB14_tr

Display Coordinates

The top-left corner of the screen is coordinate (0, 0).

The usable coordinate range is:

X: 0 to 127
Y: 0 to 63

The drawStr() Y coordinate represents the text baseline rather than the top of the letters. This is why placing text at Y coordinate zero normally cuts off most of the characters.

Using a Full Screen Buffer

On boards with more RAM, you can replace the page-buffer constructor:

U8G2_ST7920_128X64_1_SW_SPI

with the full-buffer constructor:

U8G2_ST7920_128X64_F_SW_SPI

You can then draw the display using:

u8g2.clearBuffer();

// Drawing commands

u8g2.sendBuffer();

The full buffer makes animation and screen updates easier, but a 128×64 monochrome image requires approximately 1024 bytes. The page-buffer version is generally safer for an Arduino Uno or Nano.

Drawing Shapes on the 128×64 LCD

A graphical LCD is not limited to text. U8g2 includes functions for drawing lines, boxes, circles, triangles, frames, and individual pixels.

Here is a simple graphics demonstration:

#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R0,
  13,
  11,
  10,
  U8X8_PIN_NONE
);

void setup() {
  u8g2.begin();
}

void loop() {
  u8g2.firstPage();

  do {
    // Outer border
    u8g2.drawFrame(0, 0, 128, 64);

    // Header
    u8g2.drawBox(1, 1, 126, 15);
    u8g2.setDrawColor(0);
    u8g2.setFont(u8g2_font_6x10_tr);
    u8g2.drawStr(34, 12, "GRAPHICS");
    u8g2.setDrawColor(1);

    // Shapes
    u8g2.drawCircle(20, 39, 12);
    u8g2.drawDisc(52, 39, 10);
    u8g2.drawTriangle(75, 51, 88, 25, 101, 51);
    u8g2.drawRFrame(106, 27, 16, 25, 3);

  } while (u8g2.nextPage());

  delay(1000);
}

Some of the most useful drawing functions are:

Function Purpose
drawPixel(x, y) Draws one pixel
drawLine(x1, y1, x2, y2) Draws a line
drawHLine(x, y, width) Draws a horizontal line
drawVLine(x, y, height) Draws a vertical line
drawFrame(x, y, width, height) Draws a rectangular outline
drawBox(x, y, width, height) Draws a filled rectangle
drawCircle(x, y, radius) Draws a circle outline
drawDisc(x, y, radius) Draws a filled circle
drawTriangle(...) Draws a triangle
drawRFrame(...) Draws a rounded rectangular frame

Suggested image 6 — Graphics demonstration:
Insert after the graphics code. Show the LCD output containing the border, header, circle, filled circle, triangle, and rounded rectangle.

Creating a Simple Arduino Sensor Dashboard

Now, let us create something closer to a real project. The following sketch reads an analog value from A0 and displays the reading as both a number and a bar graph.

You can connect the center pin of a 10 kΩ potentiometer to A0. Connect the other two potentiometer pins to 5V and GND.

#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R0,
  13,
  11,
  10,
  U8X8_PIN_NONE
);

const int sensorPin = A0;

void setup() {
  u8g2.begin();
}

void loop() {
  int sensorValue = analogRead(sensorPin);
  int percentage = map(sensorValue, 0, 1023, 0, 100);
  int barWidth = map(sensorValue, 0, 1023, 0, 122);

  u8g2.firstPage();

  do {
    // Header
    u8g2.drawBox(0, 0, 128, 14);
    u8g2.setDrawColor(0);
    u8g2.setFont(u8g2_font_6x10_tr);
    u8g2.drawStr(22, 11, "ANALOG MONITOR");
    u8g2.setDrawColor(1);

    // Raw ADC value
    u8g2.setFont(u8g2_font_6x10_tr);
    u8g2.drawStr(3, 28, "ADC:");

    u8g2.setCursor(35, 28);
    u8g2.print(sensorValue);

    // Percentage
    u8g2.drawStr(78, 28, "PCT:");

    u8g2.setCursor(105, 28);
    u8g2.print(percentage);

    // Bar graph
    u8g2.drawFrame(2, 36, 124, 16);

    if (barWidth > 0) {
      u8g2.drawBox(3, 37, barWidth, 14);
    }

    // Scale labels
    u8g2.setFont(u8g2_font_5x7_tr);
    u8g2.drawStr(2, 62, "0");
    u8g2.drawStr(57, 62, "50");
    u8g2.drawStr(112, 62, "100");

  } while (u8g2.nextPage());

  delay(100);
}

Notice that sensorValue, percentage, and barWidth are calculated before entering the picture loop. The values must remain unchanged while U8g2 is drawing all the display pages.

A similar screen could be used for:

  • Temperature readings
  • Battery-voltage monitoring
  • Motor speed
  • Tank level
  • Pressure readings
  • Light intensity
  • Signal strength
  • Servo position

Suggested image 7 — Sensor dashboard:
Insert after the dashboard code. Show the LCD displaying a numeric ADC reading and a partially filled horizontal bar graph. Include the potentiometer connected to A0 in the photograph or wiring diagram.

Displaying a Bitmap Image

One of the main advantages of a graphical LCD is the ability to display logos, icons, symbols, and splash screens.

A bitmap image must first be converted into a C or C++ byte array. Manually creating a large byte array is possible, but it quickly becomes impractical.

For this purpose, you can use the LCD Bitmap Converter. The tool can convert JPG, PNG, or BMP images into an array that can be included in Arduino and other embedded projects.

Preparing the Image

For the best result:

  • Use a simple black-and-white image.
  • Remove fine details that will disappear at low resolution.
  • Resize the image to the area it will occupy on the display.
  • Use strong contrast.
  • Avoid converting a full-color photograph directly.

For a full-screen image, set the converter resolution to:

Width: 128
Height: 64

For a smaller logo, use a resolution such as:

Width: 64
Height: 32

After generating the array, place it near the top of your Arduino sketch.

#define logo_width 64
#define logo_height 32

static const unsigned char logo_bits[] U8X8_PROGMEM = {
// Paste the generated bitmap bytes here
};

The bitmap can then be drawn using:

u8g2.drawXBMP(
  32,
  16,
  logo_width,
  logo_height,
  logo_bits
);

The first two parameters are the X and Y coordinates. A 64-pixel-wide bitmap placed at X coordinate 32 will be horizontally centered on a 128-pixel-wide display:

\frac{128 - 64}{2} = 32

Complete Bitmap Template

#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R0,
  13,
  11,
  10,
  U8X8_PIN_NONE
);

#define logo_width 64
#define logo_height 32

static const unsigned char logo_bits[] U8X8_PROGMEM = {
  // Replace this section with the output
  // from the LCD Bitmap Converter.
};

void setup() {
  u8g2.begin();
}

void loop() {
  u8g2.firstPage();

  do {
    u8g2.drawXBMP(
      32,
      4,
      logo_width,
      logo_height,
      logo_bits
    );

    u8g2.setFont(u8g2_font_6x10_tr);
    u8g2.drawStr(18, 53, "System Starting");

    u8g2.drawFrame(14, 57, 100, 6);
    u8g2.drawBox(16, 59, 65, 2);

  } while (u8g2.nextPage());

  delay(1000);
}

U8g2’s drawXBMP() function is intended for XBM-format, one-bit bitmap data stored in program memory on AVR boards. Placing the bitmap in program memory prevents the image array from consuming the Uno’s limited SRAM.

Suggested image 8 — Bitmap-converter workflow:
Insert before the bitmap code. Show a three-part image:

Original black-and-white logo
Logo inside the LCD Bitmap Converter pixel preview
Generated hexadecimal array

Suggested image 9 — Bitmap displayed on LCD:
Insert after the complete bitmap template. Show the resulting logo and “System Starting” screen on the real ST7920 LCD.

Using Hardware SPI

The examples use the software-serial constructor:

U8G2_ST7920_128X64_1_SW_SPI

U8g2 also provides a hardware-SPI constructor:

U8G2_ST7920_128X64_1_HW_SPI u8g2(
  U8G2_R0,
  10,
  U8X8_PIN_NONE
);

With an Arduino Uno, the wiring remains:

Signal Arduino Uno
SID D11/MOSI
SCLK D13/SCK
CS D10

Hardware SPI may provide faster screen transfers. However, the software constructor is easier to understand and allows other pins to be selected when the normal SPI pins are already being used.

Connecting the LCD Reset Pin to Arduino

Instead of connecting RST permanently to 5V, you can control it using an Arduino pin.

For example, connect the LCD RST pin to D8 and change the constructor to:

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R0,
  13,
  11,
  10,
  8
);

The library can then reset the display during initialization.

Do not leave the RST pin floating. A floating reset input may cause inconsistent startup behavior.

Troubleshooting an Arduino 128×64 LCD

The Backlight Turns On, but Nothing Appears

The backlight and LCD controller are separate parts of the module. A working backlight does not mean that the controller has initialized.

Check the following:

  • Adjust the contrast potentiometer.
  • Confirm that PSB is connected to GND.
  • Check the CS, SID, and SCLK wiring.
  • Confirm that the LCD and Arduino share the same ground.
  • Check that RST is pulled high.
  • Make sure the correct U8g2 constructor is used.

Only Dark Blocks or Random Pixels Appear

This is commonly caused by:

  • Incorrect contrast
  • A loose ground connection
  • PSB left floating
  • Incorrect pin order in the constructor
  • Unstable reset wiring
  • Wires that are too long
  • A weak 5V supply

Keep the clock, data, and ground wires reasonably short.

The Display Is Mirrored or Upside Down

Change the rotation parameter.

U8G2_R0  // Normal
U8G2_R1  // 90 degrees
U8G2_R2  // 180 degrees
U8G2_R3  // 270 degrees

For example:

U8G2_ST7920_128X64_1_SW_SPI u8g2(
  U8G2_R2,
  13,
  11,
  10,
  U8X8_PIN_NONE
);

The Bitmap Looks Corrupted

Check that:

  • The bitmap width and height match the generated image.
  • The correct array name is passed to drawXBMP().
  • The array is stored using U8X8_PROGMEM.
  • The image uses the expected monochrome/XBM byte order.
  • The drawing coordinates do not place part of the image outside the screen.

The Sketch Compiles, but the Arduino Behaves Unpredictably

The program may be running low on SRAM.

Try changing a full-buffer constructor:

U8G2_ST7920_128X64_F_SW_SPI

to the page-buffer version:

U8G2_ST7920_128X64_1_SW_SPI

You can also:

  • Use smaller fonts.
  • Store constant strings in flash using the F() macro.
  • Store bitmap arrays in program memory.
  • Remove unused libraries.
  • Reduce the number of large global arrays.

The Display Works Until Another SPI Device Is Added

Each SPI device should have its own chip-select signal. Make sure inactive devices have their chip-select pins in the correct inactive state.

Also, verify that the other device does not interfere with the ST7920’s clock or data lines during initialization.

Frequently Asked Questions

Can I power the ST7920 LCD from 3.3 V?

Many common 12864 ST7920 modules are designed for a 5 V supply. Check the module datasheet or PCB markings before using 3.3 V. A 3.3 V microcontroller may also require level shifting depending on the LCD module’s input thresholds.

Can I use an Arduino Mega?

Yes. With the software-serial constructor, you can use almost any digital pins. Simply change both the wiring and constructor values. For hardware SPI on the Mega, use its designated SPI pins.

Can I use an Arduino Nano?

Yes. The classic Arduino Nano uses the same ATmega328P and has the same D10, D11, and D13 signal arrangement used in this tutorial.

Can the ST7920 display color images?

No. The common ST7920 graphical LCD is monochrome. Each pixel is either on or off. The blue, green, or yellow appearance comes from the LCD panel and backlight rather than per-pixel color control.

Can I display live graphs?

Yes. Store a series of sensor readings in an array and draw a line between consecutive points using drawLine(). On an Arduino Uno, keep the history array reasonably small to avoid using too much SRAM.

Can I display numbers using larger fonts?

Yes. U8g2 includes many large numeric and general-purpose fonts. However, larger fonts use more flash memory and occupy more screen space.

Why is my display labeled 12864B instead of ST7920?

“12864B” is often a module or PCB designation. Check the controller marking, seller documentation, or pin labels. The presence of pins such as PSB, SID, SCLK, and VOUT usually indicates an ST7920-style module, but you should still verify the controller before connecting it.

Conclusion

The ST7920 128×64 LCD is a useful upgrade from an ordinary character display. It gives an Arduino enough screen space for menus, sensor dashboards, bar graphs, status icons, and simple user interfaces.

Using serial mode also keeps the wiring manageable. Only three Arduino control pins are needed for CS, serial data, and clock, while the U8g2 library handles the controller’s communication and graphics operations.

Once the basic display is working, try replacing the analog potentiometer example with an actual sensor. You can also create a logo, icon, or splash screen using the LCD Bitmap Converter and display it during project startup.

The same display can be used to build projects such as:

  • Digital meters
  • Weather stations
  • Battery monitors
  • Menu-driven controllers
  • Data loggers
  • Motor-control panels
  • Portable test instruments
  • Small embedded dashboards

The 128×64 resolution may sound limited compared with modern screens, but with carefully chosen fonts and simple graphics, it is enough to create a clean and practical embedded interface.

Index