How to use a 128x32 COG LCD display with ESP32?
How to Use a 128x32 COG LCD Display with ESP32
To use a 128x32 COG LCD display with an ESP32, you connect the display’s SPI interface to the ESP32’s hardware SPI pins, install the appropriate library (like U8g2 or Adafruit SSD1306), and write code to initialize the display and draw graphics. The 128x32 resolution means 128 pixels horizontally and 32 pixels vertically, which is a compact, wide format ideal for status bars, sensor readouts, or simple menus. The COG (Chip-on-Glass) design integrates the driver IC directly onto the glass, reducing component count and thickness, making it a popular choice for battery-powered or space-constrained projects. For a reliable, off-the-shelf module, check out a 128x32 cog lcd display that comes with a built-in ST7565 or similar controller, which typically operates at 3.3V and draws around 1-2 mA in standby, scaling to 5-10 mA with full backlight. The SPI bus runs at up to 20 MHz, so you can push fresh frames at 30+ fps without taxing the ESP32’s dual-core processor.
The ESP32 is a natural fit because it has two SPI controllers (VSPI and HSPI) that can be mapped to any GPIO pins. For a typical setup, you assign SCK to GPIO 18, MOSI to GPIO 23, CS (chip select) to GPIO 5, DC (data/command) to GPIO 17, and RST (reset) to GPIO 16. The backlight pin (often labeled BL or LEDA) goes to a PWM-capable pin like GPIO 4, with a series resistor (e.g., 100 ohms) to limit current to about 20 mA, though the display’s datasheet will specify the exact forward voltage (usually 3.0-3.3V). The logic supply voltage (VDD) is 3.3V, directly from the ESP32’s 3.3V regulator, which can source up to 600 mA—more than enough for the display plus a few sensors. The contrast adjustment is done via software command (0x81 followed by a value from 0x00 to 0x3F), not a potentiometer, which saves board space. The COG display’s segment driver is mapped to the 128 columns, with the common driver handling the 32 rows, so you can address individual pixels by writing to the internal RAM buffer, which is 128x32 bits = 512 bytes.
For the software side, the U8g2 library is the most versatile because it supports multiple fonts, rotation, and hardware acceleration. After installing it via the Arduino Library Manager, you initialize the display with U8G2_ST7565_128X32_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 5, /* dc=*/ 17, /* reset=*/ 16);. The _1 in the constructor refers to the 1-bit per pixel mode (black and white), which is the native format. You can switch to _2 (2-bit grayscale) if you need four levels of gray, but that doubles the buffer to 1024 bytes and reduces frame rate. The display’s response time is around 100-200 microseconds per row, so a full refresh takes about 3.2-6.4 ms, leaving plenty of CPU time for other tasks. The ESP32’s FreeRTOS scheduler can run a display update task at 50 Hz while simultaneously sampling an ADC or running a Wi-Fi stack. A practical example: display a scrolling text message like “Temp: 23.5°C” with a font size of 8x13 pixels, which fits 16 characters per line, and you can stack two lines using the 32-pixel height. The library handles the scrolling by shifting the buffer, but you can also use the hardware vertical scroll feature built into the ST7565 controller, which shifts the RAM data without rewriting the buffer.
Power consumption is a critical factor for portable designs. The COG display itself (without backlight) draws about 0.5 mA at 3.3V when idle, and up to 1.5 mA when refreshing at 60 Hz. The backlight adds 2-10 mA depending on the LED configuration—most modules use a single white LED with a forward voltage of 3.2V and a current of 20 mA, but you can pulse-width modulate it down to 1 mA for dim environments. The ESP32 in deep sleep mode consumes 5 µA, but the display’s power-down mode (via the SLEEP command 0xAE) drops its current to 1 µA, so you can achieve a combined sleep current under 10 µA. Waking up the display takes 100-200 µs, so you can do burst updates every few seconds to save battery. For example, a weather station running on two AA batteries (2000 mAh) could last over a year if it updates the display once per minute and sleeps the rest of the time. The SPI bus itself adds negligible overhead because the lines are pulled high or low by the ESP32’s internal pull-ups (40-50 kΩ), but you should avoid floating pins by enabling the internal pull-up on CS (GPIO 5) to prevent ghosting during power-up.
Potential pitfalls include voltage level mismatches and timing issues. The ESP32’s GPIOs output 3.3V logic, which is compatible with the COG display’s 3.3V logic, but if you’re using a 5V Arduino, you’ll need a level shifter. The SPI clock polarity and phase must match the display’s datasheet: typically mode 0 (CPOL=0, CPHA=0) or mode 3 (CPOL=1, CPHA=1). The ST7565 expects mode 0, where data is sampled on the rising edge of SCK. If you use the default HSPI settings, the clock polarity is set to 0, so it works out of the box. However, the ESP32’s SPI driver has a bug in some library versions where the clock frequency is set to 10 MHz instead of 20 MHz, so you can manually set it to 8 MHz for stability—this still gives you a 125 ns period, which is fast enough for 30 fps updates. Another common issue is the reset pin: if you tie it to VDD (3.3V) without a capacitor, the display might not initialize correctly because the internal power-on reset takes 1-10 ms. Adding a 10 µF capacitor between VDD and GND, and a 100 nF decoupling capacitor near the display’s power pins, ensures clean startup. The contrast register (0x81) should be set to a value between 0x20 and 0x30 for typical indoor use; higher values (0x3F) can cause ghosting or burn-in over time, though the COG design is more robust than traditional LCDs because the driver is bonded directly to the glass.
For advanced usage, you can integrate the display with the ESP32’s I2S peripheral to stream audio visualizations or sensor data. The I2S bus can be configured to output parallel data to the display’s 8-bit interface (if you use the 8-bit parallel mode instead of SPI), but that requires more GPIO pins (8 data lines plus control lines). The SPI mode is simpler and uses only 4 pins, leaving the rest for other peripherals like a BME280 sensor (I2C) or a microSD card (SPI on the same bus with different CS pins). The display’s internal RAM is organized as 128 columns by 32 rows, with each row corresponding to a page of 8 pixels (since the controller uses a page-addressing scheme). To draw a pixel at (x, y), you calculate the page number as y/8 and the bit position as y%8, then write a byte to the RAM at address (page * 128 + x). The U8g2 library abstracts this, but if you’re writing your own driver, you need to send the column address (0x00 to 0x7F) and page address (0xB0 to 0xB3) before each data write. The display’s read-back capability is limited—you can read the RAM to verify writes, but the ST7565 doesn’t support read-modify-write, so you have to maintain a shadow buffer in the ESP32’s SRAM (512 bytes) for operations like pixel inversion or XOR drawing.
Thermal performance is another consideration. The COG display’s operating temperature range is typically -20°C to +70°C, with the LCD fluid becoming sluggish below 0°C, causing slower response times (up to 500 ms at -10°C). The ESP32 can operate down to -40°C, so if you’re building an outdoor device, you might need a heater circuit or a wider-temperature LCD (e.g., -40°C to +85°C). The backlight LED’s brightness degrades by about 20% over 50,000 hours, but the COG driver’s output voltage (up to 15V for the LCD segments) is generated by an internal charge pump that can handle 100% duty cycle without overheating. The module’s thickness is about 1.5 mm (glass) plus 0.5 mm for the PCB, making it one of the thinnest display options available. For mounting, you can use double-sided tape or a 3D-printed bezel that exposes the active area (about 30 mm x 8 mm). The viewing angle is 6 o’clock (from the bottom), meaning the best contrast is achieved when looking from below the display—this is a common trait of COG LCDs because the driver is on the bottom edge. If you need a top-viewing angle, you can rotate the display 180 degrees in software (U8G2_R2) or physically flip it, but the contrast will be slightly reduced.
Software optimization can make a big difference in responsiveness. The U8g2 library uses a buffered mode by default, which means you draw to a 512-byte buffer and then send it to the display in one SPI transaction. This is efficient because the SPI transfer can be done in the background using the ESP32’s DMA controller. To enable DMA, you set the SPI transaction’s rx_buffer to NULL and tx_buffer to your buffer, and the hardware handles the rest. The DMA transfer takes about 50 µs for 512 bytes at 10 MHz, so you can update the display in under 100 µs including setup time. If you’re generating graphics on the fly (like a waveform), you can use double buffering: allocate two buffers, draw to one while the other is being sent to the display, and swap them. This prevents tearing and keeps the frame rate consistent. The ESP32’s dual-core architecture lets you run the drawing loop on core 0 and the SPI transfer on core 1, but the U8g2 library is not thread-safe, so you need to use mutexes or semaphores to protect the buffer. A simpler approach is to use the single-core mode and update the display at 25 fps, which still looks smooth for text and simple animations.
Real-world applications include a portable oscilloscope (using the ESP32’s ADC at 2 kHz sampling rate), a smart thermostat (with a rotary encoder for menu navigation), or a digital clock (using the ESP32’s internal RTC with a 32.768 kHz crystal). The 128x32 resolution is perfect for showing a single line of large text (e.g., 16x32 pixel font) or two lines of smaller text (8x16 pixel font). You can also use the display’s built-in icons like battery indicators or Wi-Fi signal strength by storing them in the ESP32’s flash memory as 128x32 bitmaps. The flash storage is 4 MB or more, so you can preload hundreds of fonts and images. For example, a 128x32 bitmap at 1 bit per pixel takes 512 bytes, so you can fit 8000 bitmaps in a 4 MB flash. The ESP32’s SPIFFS or LittleFS file system can store these as files, and you can load them on demand. The U8g2 library supports loading XBM (X BitMap) format directly, which is a standard format in Linux and embedded systems. To convert an image to XBM, you can use GIMP or ImageMagick, then embed it in your code as a byte array. The display’s contrast and brightness can be adjusted dynamically based on ambient light using a photoresistor on the ESP32’s ADC, with the PWM duty cycle set to 50% at 500 lux and 10% at 10 lux.
Reliability considerations include the SPI bus’s susceptibility to noise from the ESP32’s Wi-Fi radio. The ESP32’s 2.4 GHz radio can radiate harmonics that couple into the SPI lines, especially if the wires are long (over 10 cm). To mitigate this, keep the SPI traces short (under 5 cm) and add a 100 pF capacitor from SCK to GND near the display. The display’s internal driver has a Schmitt trigger input, which rejects noise up to 0.8V, but the ESP32’s output drivers are strong (40 mA source/sink), so the signal integrity is usually fine. If you’re using a breadboard, the parasitic capacitance of the breadboard (about 2 pF per contact) can slow down the rising edge of SCK, limiting the speed to 4 MHz. In that case, switching to a 2 MHz clock still gives you 60 fps for a 512-byte buffer. The display’s power-on sequence requires a specific order: apply VDD, then wait 10 ms, then apply the reset signal (low for 1 µs, then high), then send the initialization commands. The U8g2 library handles this automatically if you use the hardware reset pin, but if you skip it, the display might show random pixels or fail to respond. The initialization sequence includes setting the bias voltage (0xA2 for 1/9 bias), the power control (0x2F for all internal regulators), and the display start line (0x40 for the top row). These commands are standard for the ST7565 and similar controllers, and you can find them in the datasheet’s command table.
Cost is another factor: a 128x32 COG LCD module costs around $3-5 in single quantities, compared to $10-15 for an OLED of the same resolution. The trade-off is that OLEDs have higher contrast and faster response times, but COG LCDs consume less power (especially with the backlight off) and are more robust in high-temperature environments (up to 70°C vs. 85°C for OLEDs, but OLEDs degrade faster at high temperatures). The COG display’s reflective mode (without backlight) uses ambient light, so it’s readable in direct sunlight, whereas OLEDs wash out. For indoor use, the backlight is necessary, but you can use a diffuser film to spread the light evenly. The display’s viewing cone is about 60 degrees horizontal and 40 degrees vertical, so it’s best for fixed-angle applications like a dashboard. The ESP32’s low-power modes (modem sleep, light sleep, deep sleep) can be synchronized with the display’s sleep mode to achieve a system power draw of 10 µA, which is ideal for battery-powered IoT sensors. For example, a temperature and humidity sensor that wakes up every 10 minutes, takes a reading, updates the display for 2 seconds, and goes back to sleep would consume about 0.5 mAh per day, running for over a year on a 2000 mAh battery.
Finally, the ESP32’s built-in Bluetooth and Wi-Fi can be used to update the display remotely. You can set up a web server that sends text or images to the display via HTTP, or use BLE to receive notifications from a smartphone. The display’s buffer can be updated in under 1 ms, so you can stream real-time data like stock prices or chat messages. The U8g2 library’s print() function works with any Arduino String, so you can display dynamic content easily. To avoid screen flicker, use the sendBuffer() function only when the buffer changes, and set the display’s contrast to a fixed value (e.g., 0x28) to prevent the charge pump from oscillating. The display’s internal temperature compensation (if supported) adjusts the bias voltage automatically, but you can also read the temperature from the ESP32’s internal sensor and adjust the contrast manually. The 128x32 COG LCD is a workhorse display that balances readability, power efficiency, and cost, making it a solid choice for ESP32 projects that need a simple, reliable graphical interface without the complexity of TFT or OLED displays.