How to initialize a 128x32 COG LCD display in code?
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
| Command | Hex Value | Description | Notes |
|---|---|---|---|
| Display OFF | 0xAE | Turn off the display to prevent garbage during init | Must be first |
| Set MUX Ratio | 0xA8, 0x1F | Multiplex ratio = 31 (32 rows, 0 to 31) | 0x1F = 31 decimal |
| Set Display Offset | 0xD3, 0x00 | No vertical shift | Default |
| Set Start Line | 0x40 | Start line at row 0 | 0x40 to 0x7F |
| Charge Pump Enable | 0x8D, 0x14 | Enable internal DC-DC converter | 0x14 = enable, 0x10 = disable |
| Memory Addressing Mode | 0x20, 0x00 | Horizontal addressing mode | 0x00 = horizontal, 0x01 = vertical, 0x02 = page |
| Segment Remap | 0xA1 | Column 127 mapped to SEG0 (mirror horizontal) | 0xA0 = normal |
| COM Scan Direction | 0xC8 | Scan from COM[N-1] to COM0 (mirror vertical) | 0xC0 = normal |
| COM Pins Hardware Config | 0xDA, 0x02 | Alternative COM pin configuration for 128x32 | 0x02 for 32 rows, 0x12 for 64 rows |
| Set Contrast | 0x81, 0x7F | Contrast level (0 to 255) | 0x7F = 127 typical |
| Pre-charge Period | 0xD9, 0xF1 | Phase 1: 1 DCLK, Phase 2: 15 DCLK | Datasheet recommended |
| VCOMH Deselect Level | 0xDB, 0x40 | ~0.77 x VCC | 0x40 = 64 decimal |
| Display ON | 0xAF | Turn on the display | Final 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
| Parameter | SSD1306 | SH1106 |
|---|---|---|
| Charge pump enable | 0x8D, 0x14 | 0xAD, 0x8B |
| Internal RAM size | 128x64 | 132x64 |
| Segment remap | 0xA0/0xA1 | 0xA0/0xA1 (same) |
| COM pins config | 0xDA, 0x02 | 0xDA, 0x02 (same) |
| Display start line | 0x40 to 0x7F | 0xDC, 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