Skip to content
Vol. VII · No. 14 · Tokyo
Ranking Japan 2026-08-06

How to interface a 1.54 inch 128x64 OLED with STM32?

To interface a 1.54 inch 128x64 OLED with STM32, you need to connect the display’s SPI pins to the microcontroller’s corresponding SPI peripherals, configure the STM32’s GPIO and SPI registers, and write firmware to initialize the display and send pixel data. The 1.54 inch 128x64 oled display typically uses an SSD1309 or SH1106 driver, which operates over SPI with a maximum clock frequency of 10 MHz for the SSD1309 and 20 MHz for the SH1106. The display has a resolution of 128x64 pixels, monochrome, with a pixel pitch of 0.27 mm, and a viewing angle of over 160 degrees. It requires a supply voltage of 3.3V to 5V, but the logic level is 3.3V, so you must ensure the STM32’s GPIO pins are also at 3.3V to avoid damage. The typical power consumption is 20 mA to 30 mA when all pixels are on, and 0.1 mA in sleep mode, making it suitable for battery-powered projects.

First, let’s break down the hardware connections. The display has 7 pins: VCC, GND, SCL, SDA, RES, DC, and CS. VCC connects to a 3.3V supply from the STM32, but if you’re using a 5V source, you need a voltage regulator like the AMS1117-3.3 to drop it down. GND goes to ground. SCL is the SPI clock, which should be connected to the STM32’s SPI SCK pin, for example, PA5 on the STM32F103. SDA is the SPI data line, connected to the MOSI pin, like PA7. RES is the reset pin, active low, and you can connect it to any GPIO, such as PB0, with a 10k ohm pull-up resistor to 3.3V to ensure it stays high during normal operation. DC is the data/command pin, used to tell the display whether the incoming bytes are commands (low) or pixel data (high). Connect it to a GPIO like PB1. CS is the chip select, active low, and you can use a GPIO like PB2. If you’re using multiple SPI devices, you can share the SCL and SDA lines but need separate CS pins. The display’s SPI mode is mode 0, meaning CPOL=0 and CPHA=0, so the clock is idle low and data is sampled on the rising edge.

Now, let’s talk about the STM32 side. I’ll use the STM32F103C8T6 (Blue Pill) as an example, but the same principles apply to other STM32 series like the STM32F4 or STM32L0. The SPI peripheral on the STM32F103 can run at up to 18 MHz, but for the SSD1309, you should limit it to 10 MHz to avoid timing issues. You need to enable the SPI clock in the RCC register, configure the GPIO pins to alternate function push-pull for SCK and MOSI, and set the CS, DC, and RES pins as general-purpose output push-pull. The SPI configuration involves setting the baud rate prescaler, data frame format (8 bits), and the clock polarity and phase. For example, if the system clock is 72 MHz, you can set the SPI prescaler to 8 to get a 9 MHz SPI clock, which is within the SSD1309’s limit. Here’s a typical register setup: SPI_CR1 register with bits set for master mode, baud rate control, and CPOL=0, CPHA=0. You also need to set the SSM bit to enable software slave management, and set the SSI bit to high to avoid a mode fault error.

Once the hardware and SPI are configured, you need to initialize the display. The initialization sequence for the SSD1309 is well-documented and involves sending a series of commands via SPI. The commands are sent with the DC pin low, and data is sent with DC high. The typical sequence includes: turning off the display (0xAE), setting the display clock divide ratio and oscillator frequency (0xD5, then 0x80), setting the multiplex ratio to 63 for 64 rows (0xA8, then 0x3F), setting the display offset to 0 (0xD3, then 0x00), setting the display start line to 0 (0x40), enabling charge pump (0x8D, then 0x14), setting the memory addressing mode to horizontal (0x20, then 0x00), setting the segment re-map to column 127 (0xA1), setting the COM output scan direction to normal (0xC8), setting the COM pins hardware configuration (0xDA, then 0x12), setting the contrast (0x81, then 0x7F), enabling the display (0xAF), and clearing the display by sending 128x64/8 = 1024 bytes of 0x00. The entire initialization takes about 100 ms to complete, including the power-on reset delay. If you use the SH1106 driver, the initialization is similar but has a different multiplex ratio and column address range, so double-check the datasheet.

After initialization, you can write pixel data to the display. The SSD1309 uses a 1024-byte GDDRAM (graphic display data RAM), where each byte represents 8 vertical pixels. The memory is organized as 128 columns by 8 pages, where each page is 8 rows. To write to a specific pixel, you need to set the column address range (0x21, then start column, then end column) and the page address range (0x22, then start page, then end page). For example, to write to the entire screen, you set column start to 0, column end to 127, page start to 0, and page end to 7. Then you send 1024 bytes of pixel data. If you want to draw a single pixel at (x, y), you need to calculate the page number as y/8, the bit position within the byte as y%8, and then read the current byte from the GDDRAM, set the bit, and write it back. But since the SSD1309 doesn’t support read-back over SPI, you need to maintain a software buffer in the STM32’s RAM. A 1024-byte buffer is small, so you can allocate it as a global array. For example, uint8_t buffer[128][8] or uint8_t buffer[1024]. Then you update the buffer in software and send the entire buffer to the display when needed. This is the most common approach for drawing text or graphics.

Let’s talk about performance. Sending 1024 bytes over SPI at 9 MHz takes about 1.14 ms (1024 bytes * 8 bits / 9 MHz = 910 µs, plus overhead). If you’re updating the display at 60 Hz, that’s 16.6 ms per frame, so the SPI transfer takes only 5.5% of the frame time, leaving plenty of CPU cycles for other tasks. However, if you’re using a slower SPI clock, like 1 MHz, the transfer time becomes 8.2 ms, which is 49% of the frame time, so you might see flickering if you’re doing heavy updates. For smooth animations, I recommend using DMA to transfer the buffer to the SPI peripheral. The STM32F103 has a DMA controller that can handle SPI transfers in the background. You set up a DMA channel with the memory address of the buffer, the peripheral address of the SPI data register, and the transfer size of 1024 bytes. Then you trigger the DMA transfer and let it run while the CPU does other work. The DMA completion interrupt can signal when the transfer is done, so you can update the buffer for the next frame. This reduces CPU load and improves responsiveness.

Now, let’s dive into the firmware details. I’ll provide a C code skeleton for the STM32F103 using the standard peripheral library or HAL. For the HAL, you initialize the SPI handle with the following parameters: hspi.Instance = SPI1; hspi.Init.Mode = SPI_MODE_MASTER; hspi.Init.Direction = SPI_DIRECTION_2LINES; hspi.Init.DataSize = SPI_DATASIZE_8BIT; hspi.Init.CLKPolarity = SPI_POLARITY_LOW; hspi.Init.CLKPhase = SPI_PHASE_1EDGE; hspi.Init.NSS = SPI_NSS_SOFT; hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi.Init.TIMode = SPI_TIMODE_DISABLE; hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; Then call HAL_SPI_Init. For the GPIO, you configure PA5, PA7, PB0, PB1, PB2 as output push-pull, with PA5 and PA7 set to alternate function. For the CS, DC, and RES pins, you write a simple function to toggle them: #define OLED_CS_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); #define OLED_CS_HIGH HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET); Similarly for DC and RES. The reset sequence is: pull RES low for 10 ms, then high for 10 ms. Then send the initialization commands using a function that sets DC low, CS low, sends the command byte via HAL_SPI_Transmit, then CS high. For data, set DC high, CS low, send the data byte, then CS high. This is a blocking approach, but for DMA, you use HAL_SPI_Transmit_DMA.

Let’s look at the timing constraints. The SSD1309 datasheet specifies that the SPI clock high and low times must be at least 50 ns, so a 10 MHz clock (100 ns period) is fine. The setup time for data before the rising edge of SCK is 20 ns, and the hold time is 10 ns. The STM32’s SPI output meets these specs easily. The CS low time before the first SCK edge must be at least 100 ns, and the CS high time after the last SCK edge must be at least 100 ns. In practice, you can add a small delay of 1 µs between CS assertion and the first byte to be safe. The DC pin must be stable before the CS goes low, so set DC first, then CS low. These timing margins are generous, so you don’t need to worry about strict timing unless you’re running at very high SPI speeds.

For power management, the display can be put into sleep mode by sending the command 0xAE (display off) and then 0x8D with 0x10 to disable the charge pump. The current consumption drops to 0.1 mA. To wake it up, send 0x8D with 0x14 to enable the charge pump, then 0xAF to turn on the display. The wake-up time is about 100 ms, so you can’t toggle it rapidly. If you’re building a battery-powered device, you can use the STM32’s sleep mode and turn off the display when not in use. The STM32 itself can enter stop mode with a current of a few µA, and wake up via an external interrupt or timer.

Let’s talk about common issues. One problem is that the display might show garbage or no data because the initialization sequence is not correct. Double-check the command bytes against the datasheet. For example, if you use the SSD1309 but send the SH1106 initialization, the display might not work because the multiplex ratio is different. Another issue is that the SPI clock polarity or phase is wrong. If you set CPOL=1 and CPHA=1, the display will not interpret the data correctly. Use a logic analyzer to check the SPI signals. I’ve seen cases where the CS pin is not pulled low properly, or the RES pin is floating, causing the display to reset randomly. Always use a pull-up resistor on RES. Also, the display’s VCC must be stable; if you’re using a battery, add a 10 µF capacitor between VCC and GND to filter noise. The STM32’s 3.3V regulator might not supply enough current if you’re also powering other peripherals, so use a separate regulator if needed.

For advanced features, you can implement partial updates by only sending the changed bytes to the display. For example, if you’re updating a small area, you can set the column and page addresses to that area and send only the relevant bytes. This reduces SPI traffic and power consumption. The SSD1309 supports horizontal, vertical, and page addressing modes. The horizontal mode is the most intuitive for full-screen updates, but for partial updates, page mode is often easier because you can set the start page and column, then send a continuous stream of data. The command sequence for partial update is: set column address (0x21, start, end), set page address (0x22, start, end), then send the data. The number of bytes needed is (end_column - start_column + 1) * (end_page - start_page + 1) * 8. For example, for a 10x10 pixel area, you need 10 columns * 2 pages (since 10 rows span 2 pages) = 20 bytes. This is much faster than sending 1024 bytes.

Another feature is the display’s contrast control. You can adjust the contrast by sending 0x81 followed by a value from 0x00 to 0xFF. A higher value makes the pixels brighter, but also increases power consumption. The typical contrast value is 0x7F for a balanced look. If you’re using the display in direct sunlight, you might need to set it to 0xFF, but then the current can go up to 30 mA. The display also supports a hardware scrolling feature, which can be used for text or image scrolling without CPU intervention. The scrolling commands are 0x26 for horizontal scroll, 0x27 for vertical and horizontal scroll, and 0x2E to stop scrolling. You can set the scroll speed by configuring the frame rate. For example, to scroll left at 2 frames per step, you send 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F. The parameters are the start page, end page, and speed. This is useful for displaying ticker-style text.

Let’s look at the software library options. You can write your own driver from scratch, which gives you full control, or you can use existing libraries like u8g2 or Adafruit_SSD1306. The u8g2 library supports the SSD1309 and SH1106 over SPI, and it’s highly optimized for memory-constrained microcontrollers. It uses a 1024-byte buffer by default, but you can also use a smaller buffer for partial updates. The library handles font rendering, bitmap drawing, and even Chinese character support. To port u8g2 to the STM32, you need to implement the SPI communication callbacks: u8x8_byte_hw_spi and u8x8_gpio_and_delay. The u8x8_gpio_and_delay function handles the CS, DC, RES pins and delays. The u8x8_byte_hw_spi function sends a byte over SPI. For the STM32 HAL, it looks like this: uint8_t u8x8_byte_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { case U8X8_MSG_BYTE_SEND: HAL_SPI_Transmit(&hspi1, (uint8_t *)arg_ptr, arg_int, 1000); break; case U8X8_MSG_BYTE_INIT: break; case U8X8_MSG_BYTE_SET_DC: HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int); break; case U8X8_MSG_BYTE_START_TRANSFER: HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_RESET); break; case U8X8_MSG_BYTE_END_TRANSFER: HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET); break; } return 1; } Then you call u8x8_Setup to initialize the display. The library also supports DMA if you modify the send function to use HAL_SPI_Transmit_DMA.

For the Adafruit SSD1306 library, it’s written for Arduino but can be ported to STM32 using the Arduino core for STM32. The library uses a similar buffer approach and supports many fonts. However, it’s heavier than u8g2 and might not fit in the STM32F103’s 64 KB flash if you include many fonts. The u8g2 library has a smaller footprint, with the core library around 10 KB, plus fonts that range from 1 KB to 10 KB each. For a typical project, you can use a 5x7 pixel font that takes 2 KB, and the total code size is around 20 KB, leaving plenty of room for your application code.

Let’s talk about the electrical characteristics in more detail. The display’s input logic high voltage is 0.7*VCC to VCC, and logic low is 0 to 0.3*VCC. At 3.3V, the high threshold is 2.31V, and low is 0.99V. The STM32’s GPIO output high is typically 3.3V with a 20 mA drive, so it’s well within spec. The input capacitance on each pin is 10 pF, so the SPI lines can