Can't get proper output on SSD1322-based 128x64 OLED (via 4-wire SPI) - Page 1
Forum section: electronics / projects

author: pxi1085
author_rank: Newbie
timestamp: September 01, 2025, 11:44:55 am
content: Hi,I'm working with a 128x64 OLED display using the SSD1322 driver, connected via 4-wire SPI to an STM32 microcontroller.I've spent a long time trying to get it working, but I still can't achieve a clean and proper image. The display behaves as if it's 256x64, not 128x64 — only the left half shows readable content, while the right side is filled with garbage.In fact, I had to manually offset text by 57 pixels from the left just to make it display properly. Without that, even the left part of the screen would appear distorted.Here’s what I’ve tried so far:Reviewed the Arduino example code provided by the display manufacturer.Examined two GitHub repos written for SSD1322, but they both target 256x64 panels.Even tried some AI tools for troubleshooting, but none of the suggestions resolved the issue.I'm now wondering — maybe someone here has experience using an SSD1322 display with a physical resolution of 128x64, not 256x64?Could the issue be caused by incorrect column address setup, remap configuration, or GDDRAM write pattern?I’ll share my code if needed. Thanks in advance!

author: pxi1085
author_rank: Newbie
timestamp: September 01, 2025, 11:46:15 am
content: My codes are here;/* oled_ssd1322.c */#include <string.h>#include <stdio.h>#include "oled_ssd1322.h"#include "font6x8.h"/* If your logo is large, get it as extern */extern const uint8_t NHD_Logo[];/* Inline control helpers */static inline void CS_LOW (void) { HAL_GPIO_WritePin(SSD1322_CS_Port, SSD1322_CS_Pin, GPIO_PIN_RESET); }static inline void CS_HIGH(void) { HAL_GPIO_WritePin(SSD1322_CS_Port, SSD1322_CS_Pin, GPIO_PIN_SET); }static inline void DC_CMD (void) { HAL_GPIO_WritePin(SSD1322_DC_Port, SSD1322_DC_Pin, GPIO_PIN_RESET); }static inline void DC_DAT (void) { HAL_GPIO_WritePin(SSD1322_DC_Port, SSD1322_DC_Pin, GPIO_PIN_SET); }static inline void DEBUG_TOGGLE(void) { HAL_GPIO_TogglePin(DEBUG_PIN_PORT, DEBUG_PIN_PIN); }static inline void DEBUG_HIGH(void) { HAL_GPIO_WritePin(DEBUG_PIN_PORT, DEBUG_PIN_PIN, GPIO_PIN_SET); }static inline void DEBUG_LOW(void) { HAL_GPIO_WritePin(DEBUG_PIN_PORT, DEBUG_PIN_PIN, GPIO_PIN_RESET); }/* Transmit with SPI (retry) */static HAL_StatusTypeDef ssd1322_spi_tx(const uint8_t *data, uint16_t len){HAL_StatusTypeDef ret;for (int attempt = 0; attempt < SSD1322_SPI_RETRY_MAX; ++attempt) {ret = HAL_SPI_Transmit(&hspi2, (uint8_t*)data, len, 100);if (ret == HAL_OK) return HAL_OK;HAL_Delay(1);}return ret;}void SSD1322_EntireDisplayOn(void) {SSD1322_SendCommand(0xA5); // Entire display ON (all pixels white)}void SSD1322_EntireDisplayOff(void) {SSD1322_SendCommand(0xA4); // Entire display OFF (normal)}/* Send command */void SSD1322_SendCommand(uint8_t cmd){DC_CMD();CS_LOW();ssd1322_spi_tx(&cmd, 1);CS_HIGH();}/* Command + data */void SSD1322_SendCommandWithData(uint8_t cmd, const uint8_t *data, uint16_t len){DC_CMD();CS_LOW();ssd1322_spi_tx(&cmd, 1);if (len) {DC_DAT();ssd1322_spi_tx(data, len);}CS_HIGH();}/* Reset pulse */static void SSD1322_Reset(void){HAL_GPIO_WritePin(SSD1322_RST_Port, SSD1322_RST_Pin, GPIO_PIN_RESET);HAL_Delay(150);HAL_GPIO_WritePin(SSD1322_RST_Port, SSD1322_RST_Pin, GPIO_PIN_SET);HAL_Delay(150);}/* Column/row settings */void SSD1322_SetColumn(uint8_t a, uint8_t b){SSD1322_SendCommandWithData(0x15, (uint8_t[]){a, b}, 2);}void SSD1322_SetRow(uint8_t a, uint8_t b){SSD1322_SendCommandWithData(0x75, (uint8_t[]){a, b}, 2);}/* Display ON/OFF */void SSD1322_DisplayOnOff(bool on){if (on) SSD1322_SendCommand(0xAF);else SSD1322_SendCommand(0xAE);}/* Initialization sequence */void SSD1322_Init(void){SSD1322_Reset();SSD1322_DisplayOnOff(false);SSD1322_SendCommandWithData(0xFD, (uint8_t[]){0x12},1); // Command LockSSD1322_SendCommandWithData(0xB3, (uint8_t[]){0x91},1); // Display ClockSSD1322_SendCommandWithData(0xCA, (uint8_t[]){0x3F},1); // MUX RatioSSD1322_SendCommandWithData(0xA2, (uint8_t[]){0x00},1); // Display OffsetSSD1322_SendCommandWithData(0xAB, (uint8_t[]){0x01},1); // Function Select (internal VDD)SSD1322_SendCommandWithData(0xA1, (uint8_t[]){0x00},1); // Start LineSSD1322_SendCommandWithData(0xA0, (uint8_t[]){0x16,0x11},2); // RemapSSD1322_SendCommandWithData(0xC7, (uint8_t[]){0x0F},1); // Master ContrastSSD1322_SendCommandWithData(0xC1, (uint8_t[]){0x9F},1); // ContrastSSD1322_SendCommandWithData(0xB1, (uint8_t[]){0x72},1); // Phase LengthSSD1322_SendCommandWithData(0xBB, (uint8_t[]){0x1F},1); // Precharge VoltageSSD1322_SendCommandWithData(0xB4, (uint8_t[]){0xA0,0xFD},2);// Display Enhancement A (VSL)SSD1322_SendCommandWithData(0xBE, (uint8_t[]){0x04},1); // VCOMHSSD1322_SendCommand(0xA6); // Normal DisplaySSD1322_SendCommand(0xA9); // Exit PartialSSD1322_SendCommandWithData(0xD1, (uint8_t[]){0xA2,0x20},2); // Display Enhancement BSSD1322_SendCommandWithData(0xB5, (uint8_t[]){0x00},1); // GPIOSSD1322_SendCommand(0xB9); // Default GrayscaleSSD1322_SendCommandWithData(0xB6, (uint8_t[]){0x08},1); // 2nd PrechargeSSD1322_DisplayOnOff(true);}/* Framebuffer: 2-bit grayscale (0..3), 64 rows x 128 columns */uint8_t framebuf;/* 2-bit -> byte mapping */static inline uint8_t gray2byte(uint8_t g) {switch (g & 0x03) {case 0: return 0x00;case 1: return 0x55;case 2: return 0xAA;case 3: return 0xFF;default: return 0x00;}}/* Writes framebuffer to GDDRAM */void SSD1322_RefreshFromFramebuffer(void){SSD1322_SetColumn(0x00, 0x7F);SSD1322_SetRow(0x00, 0x3F);SSD1322_SendCommand(0x5C); // Write RAMuint8_t linebuf;for (int row = 0; row < 64; row++) {for (int col = 0; col < 128; col++) {uint8_t b = gray2byte(framebuf);// linebuf[col * 2 + 0] = b;// linebuf[col * 2 + 1] = b;linebuf = b;}DC_DAT();CS_LOW();ssd1322_spi_tx(linebuf, sizeof(linebuf));CS_HIGH();}}void DrawText(const char *s, int y){int len = strlen(s);int x0 = 57; // leaves 57 pixels of space from the leftfor (int i = 0; i < len; i++) {SSD1322_DrawChar(x0 + i * 6, y, s);}}/* Simple character drawing (6x8) */void SSD1322_DrawChar(int x, int y, char c){if (c < 32 || c > 127) return;const uint8_t *glyph = Font6x8[c - 32];for (int col = 0; col < 6; col++) {int fx = x + col;if (fx < 0 || fx >= 128) continue;uint8_t column_bits = glyph;for (int row = 0; row < 8; row++) {int fy = y + row;if (fy < 0 || fy >= 64) continue;uint8_t pixel_on = (column_bits >> row) & 0x01;framebuf = pixel_on ? 3 : 0;}}}/* Clears the display via framebuffer */void SSD1322_Clear(void){for (int r=0; r<64; r++)for (int c=0; c<128; c++)framebuf = 0;SSD1322_RefreshFromFramebuffer();}/* Centered string (single line) */void SSD1322_DrawStringCentered(const char *s){int len = 0;for (const char *p = s; *p; ++p) len++;int total_width = len * 6 + (len - 1) * 1;int x0 = (128 - total_width) / 2;int y0 = (64 -/ 2;/* clear */for (int r=0;r<64;r++)for (int c=0;c<128;c++)framebuf=0;for (int i = 0; i < len; i++) {int x = x0 + i * (6 + 1);SSD1322_DrawChar(x, y0, s);}SSD1322_RefreshFromFramebuffer();}/* Draws a scrolling string with offset (horizontal scroll) */void SSD1322_DrawStringAtOffset(const char *s, int y, int offset){// Clear only that linefor (int row = y; row < y + 8; row++)for (int col = 0; col < 128; col++)if (row >= 0 && row < 64)framebuf = 0;int x = -offset;for (int i = 0; s; i++) {SSD1322_DrawChar(x, y, s);x += 7; // 6px + 1 space}}/* Scroll line structure and management */void ScrollLine_Init(scrolling_line_t *line, const char *fmt, int y){snprintf(line->text, sizeof(line->text), fmt);int len = 0;for (const char *p = line->text; *p; ++p) len++;line->text_pixel_width = len * 7 - 1;line->offset = 0;line->direction = 1;line->y = y;}void ScrollLine_Tick(scrolling_line_t *line){if (line->text_pixel_width <= 128) {int pad = (128 - line->text_pixel_width) / 2;SSD1322_DrawStringAtOffset(line->text, line->y, -pad);} else {SSD1322_DrawStringAtOffset(line->text, line->y, line->offset);line->offset += line->direction;if (line->offset + 128 >= line->text_pixel_width) line->direction = -1;if (line->offset <= 0) line->direction = 1;}}/* Helper to clear the framebuffer */void SSD1322_ClearFramebuffer(void){// Since static framebuf is defined in this file, we can access it directlyfor (int r = 0; r < 64; r++) {for (int c = 0; c < 128; c++) {framebuf = 0;}}}// Set a single pixelvoid SSD1322_SetPixel(int x, int y, uint8_t gray){if (x < 0 || x >= 128 || y < 0 || y >= 64) return;framebuf= gray & 0x03; // 0..3}

author: thm_w
author_rank: Super Contributor
timestamp: September 03, 2025, 01:09:04 am
content: Is this you or is this the code you tried to use?:https://github.com/hasandurna/oled_display_ssd1322/blob/main/oled_ssd1322.cI would expect if you used 256x64 code and halved every number relating to columns it would work but I could be wrong. You seem to have 256 used in some places and 128 used in others?

author: bostonman
author_rank: Super Contributor
timestamp: September 03, 2025, 02:47:45 am
content: I've been tinkering with displays, but mine is different I think.This code worked for mine, but looks like you're doing more elaborate stuff than basic stuff (it's a DIYTZT Real OLED Display 3.12 256 64 25664 Dots SSD1322 Controller white)#include <U8g2lib.h>U8G2_SSD1322_NHD_256X64_1_4W_SW_SPI u8g2(U8G2_R0, 13, 11, 10, 8, 7);void setup() {u8g2.begin();}void loop() {u8g2.firstPage();do {u8g2.setFont(u8g2_font_6x10_tr);u8g2.drawStr(10, 32, "Hello World");} while (u8g2.nextPage());while (true);}

author: GU754
author_rank: Newbie
timestamp: September 04, 2025, 01:00:15 am
content: It definitely sounds like a column address or remap configuration issue rather than bad hardware. The SSD1322 has a 480-column internal GRAM, and many 128×64 panels only use a window of that memory — if your init code is using the default 256×64 setup, it’s writing data outside the panel’s active window, which explains why the right side looks like garbage.Try explicitly setting the column start/end addresses to match your panel. For a 128-wide display, you might need something like:ssd1322_sendCommand(SET_COLUMN_ADDRESS);ssd1322_sendData(0x1C); // start column (panel dependent)ssd1322_sendData(0x5B); // end column (start + 127)Also check the “display remap” command (0xA0). Make sure you’re not enabling odd/even split COM remap unless your panel needs it. Some panels require the data to be packed 4 bits per pixel and aligned to nibble boundaries, so verify your GDDRAM write pattern matches the datasheet (two pixels per byte).If you can share the init sequence, people can spot mismatched parameters quickly — the manufacturer’s Arduino example might be for a 256×64 reference panel and not match your hardware.