How to initialize a 128x32 COG LCD display in code?

By admin

How to initialize a 128x32 COG LCD display in code

To initialize a 128x32 COG LCD display, you need to send a specific sequence of commands over SPI or I2C, depending on the driver chip—most commonly the SSD1306 or SH1106. The initialization process typically involves setting the display off, configuring the multiplex ratio, adjusting the display offset, setting the start line, enabling charge pump (for internal DC-DC), setting the memory addressing mode, segment and COM remap, configuring the COM pins hardware, setting contrast, pre-charge period, VCOMH deselect level, and finally turning the display on. For a standard 128x32 cog lcd display using the SSD1306, the init sequence is around 20 bytes. Let me walk you through the exact steps, with real register values and timing constraints, based on the datasheet and practical experience.

Step 1: Hardware prerequisites

Before sending any commands, ensure the power supply is stable at 3.3V (typical) or 5V if the module includes a regulator. The VCC pin should be connected to a clean source, and the ground must be common. For SPI mode, you need at least 4 wires: SCK (clock), MOSI (data), CS (chip select), and DC (data/command). Additionally, RESET (reset) pin is mandatory—if you don’t use a dedicated GPIO, you can tie it to VCC with a 10k pull-up, but that’s risky. I recommend using a microcontroller pin for reset, as the display may glitch on power-up. The initialization sequence must start with a hardware reset: pull RESET low for at least 10 microseconds, then release it high, and wait 100 milliseconds before sending commands. This is non-negotiable.

Step 2: Command sequence breakdown

The SSD1306 datasheet defines a set of fundamental commands. For a 128x32 display, the multiplex ratio is 32 (0x1F), not 64 as in the 128x64 version. Here’s the exact byte sequence in hex, with explanations:

Table 1: Initialization command sequence for SSD1306 128x32

CommandHex ValueDescriptionNotes
Display OFF0xAETurn off the display to prevent garbage during initMust be first
Set MUX Ratio0xA8, 0x1FMultiplex ratio = 31 (32 rows, 0 to 31)0x1F = 31 decimal
Set Display Offset0xD3, 0x00No vertical shiftDefault
Set Start Line0x40Start line at row 00x40 to 0x7F
Charge Pump Enable0x8D, 0x14Enable internal DC-DC converter0x14 = enable, 0x10 = disable
Memory Addressing Mode0x20, 0x00Horizontal addressing mode0x00 = horizontal, 0x01 = vertical, 0x02 = page
Segment Remap0xA1Column 127 mapped to SEG0 (mirror horizontal)0xA0 = normal
COM Scan Direction0xC8Scan from COM[N-1] to COM0 (mirror vertical)0xC0 = normal
COM Pins Hardware Config0xDA, 0x02Alternative COM pin configuration for 128x320x02 for 32 rows, 0x12 for 64 rows
Set Contrast0x81, 0x7FContrast level (0 to 255)0x7F = 127 typical
Pre-charge Period0xD9, 0xF1Phase 1: 1 DCLK, Phase 2: 15 DCLKDatasheet recommended
VCOMH Deselect Level0xDB, 0x40~0.77 x VCC0x40 = 64 decimal
Display ON0xAFTurn on the displayFinal step

Step 3: Timing and delays

Between commands, you generally don’t need delays, but after the “Display ON” command, wait at least 100 milliseconds for the charge pump to stabilize. Some displays require a 10 ms delay after the charge pump enable command (0x8D, 0x14). I’ve seen cases where skipping this causes the display to stay blank. Also, after the hardware reset, a 100 ms delay is safe. The SPI clock frequency should be kept below 10 MHz to avoid timing issues—most microcontrollers run at 8 MHz or less, but if you’re using a high-end chip, cap it at 4 MHz for reliability.

Step 4: Code example in C (Arduino-style)

Here’s a practical implementation for an Arduino Uno or similar, using the SPI library. This assumes you’ve defined the pins: CS = 10, DC = 9, RESET = 8. The code sends the exact sequence from Table 1.

Listing 1: Initialization function for 128x32 SSD1306

void initDisplay() {
  // Hardware reset
  pinMode(8, OUTPUT);
  digitalWrite(8, LOW);
  delayMicroseconds(10);
  digitalWrite(8, HIGH);
  delay(100);
  
  // SPI setup
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz on 16 MHz Arduino
  
  // Send commands
  sendCommand(0xAE); // Display off
  sendCommand(0xA8);
  sendCommand(0x1F); // MUX ratio for 32 rows
  sendCommand(0xD3);
  sendCommand(0x00); // Offset
  sendCommand(0x40); // Start line
  sendCommand(0x8D);
  sendCommand(0x14); // Charge pump enable
  delay(10);         // Wait for charge pump
  sendCommand(0x20);
  sendCommand(0x00); // Horizontal addressing
  sendCommand(0xA1); // Segment remap
  sendCommand(0xC8); // COM scan direction
  sendCommand(0xDA);
  sendCommand(0x02); // COM pins config
  sendCommand(0x81);
  sendCommand(0x7F); // Contrast
  sendCommand(0xD9);
  sendCommand(0xF1); // Pre-charge
  sendCommand(0xDB);
  sendCommand(0x40); // VCOMH
  sendCommand(0xAF); // Display on
  delay(100);        // Stabilize
}

void sendCommand(byte cmd) {
  digitalWrite(9, LOW);  // DC = command mode
  digitalWrite(10, LOW); // CS = select
  SPI.transfer(cmd);
  digitalWrite(10, HIGH); // CS = deselect
}

Step 5: Differences for SH1106 driver

Some 128x32 COG displays use the SH1106 chip, which is similar but not identical. The SH1106 has a 132x64 internal RAM, but only 128x32 is visible. The init sequence differs in the multiplex ratio and the display start line. For SH1106, set MUX to 0x1F (same), but you must also set the page start and end registers. The charge pump command is 0xAD, 0x8B (enable), not 0x8D, 0x14. Also, the SH1106 doesn’t support horizontal scrolling natively. If you accidentally use SSD1306 commands on an SH1106, the display may show scrambled data. Always check the datasheet or the part number printed on the flex cable.

Table 2: Key differences between SSD1306 and SH1106 for 128x32

ParameterSSD1306SH1106
Charge pump enable0x8D, 0x140xAD, 0x8B
Internal RAM size128x64132x64
Segment remap0xA0/0xA10xA0/0xA1 (same)
COM pins config0xDA, 0x020xDA, 0x02 (same)
Display start line0x40 to 0x7F0xDC, value (register-based)

Step 6: Memory addressing and data transfer

After initialization, you need to send pixel data. The SSD1306 in horizontal addressing mode auto-increments the column address after each byte. For a 128x32 display, the frame buffer is 128 columns * 4 pages (each page is 8 rows, so 32/8 = 4 pages). That’s 512 bytes total. You can send all 512 bytes sequentially after setting the column and page range. For example, send 0x21 (set column), 0x00, 0x7F (columns 0 to 127), then 0x22 (set page), 0x00, 0x03 (pages 0 to 3). Then, set DC high (data mode) and blast 512 bytes. This is faster than sending each byte individually. The SH1106 requires a different approach: it uses a 132-column RAM, so you must set the column start and end to 0x02 and 0x81 (to center the 128 columns within the 132). If you don’t, the image will be shifted.

Step 7: Power-up sequence and brownout protection

Initialization is not just about sending commands—it’s about timing with the power supply. When the display first powers on, the internal charge pump needs time to generate the negative voltage for the OLED pixels. If you send the init sequence too quickly, the charge pump may not start, and the display stays off. Some datasheets recommend a 1 second delay after power-up before any SPI communication. In practice, I’ve found that a 500 ms delay after VCC reaches 3.3V is sufficient. Also, if your microcontroller resets quickly (e.g., due to a brownout), the display may not reset properly. Always include a hardware reset pin that you can toggle, and never rely on power-on reset alone. For battery-powered devices, consider adding a 100 µF capacitor across VCC and GND to smooth out dips.

Step 8: Common pitfalls and debugging

If the display remains blank after init, check these things: (1) Is the CS pin toggling correctly? If you forget to set CS low, the display ignores all commands. (2) Is the DC pin polarity correct? Some modules swap the logic—on some, DC high means command, low means data. Check the datasheet. (3) Is the contrast too low? Try 0xFF for maximum brightness. (4) Is the charge pump enabled? If you skip 0x8D, 0x14, the display won’t generate the negative voltage, and you’ll see nothing. (5) Are you using the correct multiplex ratio? If you set 0x3F (64 rows) on a 32-row display, the pixels will be doubled vertically, causing artifacts. (6) Is the reset pin floating? If you don’t drive it, the display may be stuck in reset. (7) Are you using the right SPI mode? Mode 0 (CPOL=0, CPHA=0) is standard for SSD1306. Mode 3 may work but can cause glitches. (8) Is the data byte order correct? The SSD1306 expects MSB-first, which is default for most SPI controllers.

Step 9: Performance optimization

For fast updates, use DMA or a hardware SPI peripheral. On a 16 MHz Arduino, sending 512 bytes via SPI takes about 4 ms at 4 MHz clock. If you’re doing animations, you can reduce this by only updating changed regions. For example, use page addressing mode to write to specific pages, or use vertical scrolling commands (0x26, 0x27) to shift the display without rewriting the buffer. The SSD1306 supports hardware scrolling, which can save CPU cycles. But be careful—scrolling commands are not available on all drivers. For a 128x32 display, scrolling is less useful because the vertical space is small, but horizontal scrolling can be implemented with 0x26 (continuous horizontal scroll) or 0x2A (vertical and horizontal scroll). The command format is: 0x26 (or 0x27), then dummy byte, then start page, then frame interval, then end page, then dummy byte. For example, to scroll pages 0 to 3 right at 2 frames per step: 0x26, 0x00, 0x00, 0x07, 0x03, 0x00. Then send 0x2F to activate.

Step 10: Multi-platform considerations

If you’re using a Raspberry Pi with Python, the init sequence is the same, but you need to use the spidev library. For STM32 with HAL, use the SPI transmit function with a 10 ms timeout. For ESP32, the Arduino core works, but you can also use the ESP-IDF’s SPI master driver. The key is to ensure that the clock polarity and phase match. On some platforms, the SPI peripheral may invert the clock—check the logic analyzer. Also, the display’s logic level is 3.3V, but many 5V microcontrollers (like classic Arduino) have 5V logic outputs. The SSD1306 is 3.3V tolerant, but the SPI pins may be damaged if driven with 5V for long periods. Use a level shifter or a voltage divider. For the RESET pin, a simple 1k resistor in series can protect the input.

Step 11: Power consumption and initialization

The 128x32 COG display consumes about 20 mA during normal operation, but during initialization, the charge pump can draw up to 50 mA for a few milliseconds. If your power supply is weak (e.g., a coin cell battery), the voltage may drop below 3.0V, causing the display to reset. In such cases, add a 10 µF capacitor close to the VCC pin. Also, the init sequence itself can be optimized to reduce power: after turning on the display, you can set the display to sleep mode (0xAE) when not in use, and wake it up with 0xAF. The sleep mode current is less than 10 µA. But note that the charge pump takes time to restart—about 100 ms—so factor that into your sleep/wake cycle.

Step 12: Verifying initialization success

After sending the init sequence, you can read the status register of the SSD1306 (command 0x00) to check if the display is on. But not all modules support readback over SPI—some only have write-only interface. In that case, you can test by writing a pattern of alternating pixels (0xAA, 0x55) to the entire frame buffer. If you see checkerboard, the init worked. If you see a single row of pixels, the multiplex ratio or COM pins config is wrong. If you see nothing, re-check the reset and charge pump. A logic analyzer is invaluable here—capture the SPI lines and verify that the bytes match the expected sequence. I’ve spent hours debugging a missing 0x8D command.

Step 13: Alternative initialization sequences from libraries

Popular libraries like Adafruit_SSD1306 or U8g2 have their own init sequences, which are more complex but handle edge cases. For example, Adafruit’s sequence includes a 0x2E (deactivate scroll) command before init, to