Skip to main content

Text Displays

Wiring character LCDs and small OLEDs to an ESP32 over I²C, and dividing a screen into Text Fields for radio frequencies, transponder codes and other cockpit readouts.

Text Display Types

When a value needs more than digits — "COM1 118.750", "XPDR 7000 ALT", a page of a radio stack — a character LCD or a small OLED does the job. Both sit on the ESP32's I²C bus , so they cost no extra pins beyond SDA and SCL, and both are treated as a grid of character cells.

DisplayGridI²C addressSupply
HD44780 LCD + PCF8574 backpack8–40 columns × 1–4 rows (16×2, 20×4)0x20–0x27 (PCF8574), 0x38–0x3F (PCF8574A) — usually 0x27 or 0x3F5 V
SSD1306 / SH1106 OLED, 128×6421×8 at text size 1, 10×4 at size 20x3C or 0x3D3.3 V
SSD1306 / SH1106 OLED, 128×3221×4 at text size 1, 10×2 at size 20x3C or 0x3D3.3 V
HD44780 Character LCD — Wiring
GPIO 21 GPIO 22 5 V GND SDA SCL VCC PCF8574 backpack 0x27 / 0x3F contrast COM1 118.750 STBY 121.500 HD44780 16×2 / 20×4

A bare HD44780 needs six or more pins. Buy it with a PCF8574 I²C backpack soldered on (or solder one yourself) and it needs only the two bus wires plus power. The backpack carries a contrast trimmer — if the screen powers up showing nothing or a row of solid blocks, turn it until the characters appear — and a jumper that switches the backlight .

Backpack pinConnect toColour
SDAI²C SDA (e.g. GPIO 21)Orange
SCLI²C SCL (e.g. GPIO 22)Yellow
VCC5 V (VIN / USB 5 V)Red
GNDGNDBlack

Arduino code

// 20×4 character LCD on a PCF8574 backpack (LiquidCrystal_I2C library)
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);   // address, columns, rows

void setup() {
    Wire.begin(21, 22);               // SDA, SCL
    lcd.init();
    lcd.backlight();
    lcd.setCursor(0, 0);              // column 0, row 0
    lcd.print("COM1 118.750");
    lcd.setCursor(0, 1);
    lcd.print("STBY 121.500");
}

void loop() {}
SSD1306 / SH1106 OLED — Wiring
GPIO 21 GPIO 22 3.3 V GND SDA SCL VCC GND XPDR 7000 ALT BARO 29.92 SSD1306 / SH1106 · 0x3C

The common 0.96" and 1.3" OLED modules are 128 pixels wide and 64 or 32 high, driven by an SSD1306 or an SH1106 (most 1.3" modules). The two chips need different drivers, so check which one your module has. They run on 3.3 V and wire straight to the bus. The address is 0x3C , or 0x3D with the address jumper on the back moved.

SettingValuesNotes
Text size1 or 2A fixed 6×8 font: 21×8 cells at size 1, 10×4 at size 2 on a 128×64 panel
Contrast0–255Dims the whole panel; 207 if you leave it out
Flippedon / offFor a panel mounted upside down behind the bezel

Arduino code

// 128×64 SSD1306 OLED (Adafruit_SSD1306 library)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 oled(128, 64, &Wire, -1);

void setup() {
    Wire.begin(21, 22);
    oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
    oled.clearDisplay();
    oled.setTextSize(1);              // 6×8 cells: 21 columns × 8 rows
    oled.setTextColor(SSD1306_WHITE);
    oled.setCursor(0, 0);
    oled.print("XPDR 7000 ALT");
    oled.display();
}

void loop() {}
Text Fields — Several Values on One Screen

One screen almost always shows several independent values — a radio's active and standby frequency on one 20×4 LCD, for example. So rather than binding the whole screen, you divide it into Text Fields : a run of character cells on one row, given by its row , starting column and width . Each field shows one value.

FieldRowColumnWidthShows
COM1 active057118.750
COM1 standby157121.500
Transponder code2547000

The text a field receives is final: it is drawn left-aligned, cut to the field's width, and the rest of the field is blanked. Characters outside printable ASCII show as blanks. Two fields on the same row must not overlap.

// Write one field: left-aligned, cut to its width, rest blanked
void writeField(uint8_t row, uint8_t column, uint8_t width, const char* text) {
    lcd.setCursor(column, row);
    for (uint8_t i = 0; i < width; i++) {
        char c = text[i];
        if (c == '\0') {
            break;
        }
        lcd.write(c >= 32 && c <= 126 ? c : ' ');
    }
    for (uint8_t i = strlen(text); i < width; i++) {
        lcd.write(' ');
    }
}

// Two independent values on one screen
writeField(0, 5, 7, "118.750");   // COM1 active
writeField(1, 5, 7, "121.500");   // COM1 standby

On a CockpitConnect controller you don't write this: add the LCD or OLED to the controller in the Flow Builder, place a Text Field node per value, and bind it to a dataref. Right alignment, padding, decimals and units come from the Mapping's format or lookup , so the screen shows exactly what the Flow sent.

Cockpit Applications
ApplicationDisplayNotes
COM / NAV radio16×2 LCDActive on row 0, standby on row 1 — two Text Fields
Radio stack summary20×4 LCDCOM1, COM2, NAV1, NAV2 on one screen, one field each
Transponder128×32 OLED, text size 2Large code plus mode ("7000 ALT")
Autopilot mode annunciation128×64 OLEDLateral and vertical modes on separate rows, blanked by a Gate when unpowered
FMC scratchpad20×4 LCDCombine with a key matrix keypad for a compact CDU