by Professor Petabyte
SSD1306 OLED I2C screens are and available in a number of sizes.
Resolution | Description | Typical Size |
---|---|---|
128x64 | Most common and widely supported | 0.96", 1.3" |
128x32 | Narrower variant, used where height is limited | 0.91" |
96x16 | Very small, used in minimal displays | ~0.69" |
72x40 | Less common, non-standard size | Custom |
This page contains a few notes explaining how such drivers work.
Writing an I2C driver for an SSD1306 OLED display (the most common SSD display) involves a structured process that includes initializing the I2C interface, sending initialization commands to the display, and writing display data.
If coding for a non-standard resolution (e.g., 72x40), make sure to:
#define SSD1306_ADDR 0x3C << 1 // Shifted for HAL (Hardware Abstraction Layer) #define SSD1306_CMD 0x00 #define SSD1306_DATA 0x40
void SSD1306_WriteCommand(uint8_t cmd) { uint8_t data[2] = {SSD1306_CMD, cmd}; HAL_I2C_Master_Transmit(&hi2c1, SSD1306_ADDR, data, 2, HAL_MAX_DELAY); } void SSD1306_WriteData(uint8_t* data, size_t size) { uint8_t buffer[size + 1]; buffer[0] = SSD1306_DATA; memcpy(&buffer[1], data, size); HAL_I2C_Master_Transmit(&hi2c1, SSD1306_ADDR, buffer, size + 1, HAL_MAX_DELAY); }
void SSD1306_Init(void) { HAL_Delay(100); // Wait after power-up SSD1306_WriteCommand(0xAE); // Display OFF SSD1306_WriteCommand(0xD5); // Set display clock SSD1306_WriteCommand(0x80); // Suggested ratio SSD1306_WriteCommand(0xA8); // Set multiplex SSD1306_WriteCommand(0x3F); // 64MUX for 128x64 SSD1306_WriteCommand(0xD3); // Set display offset SSD1306_WriteCommand(0x00); SSD1306_WriteCommand(0x40); // Set start line at 0 SSD1306_WriteCommand(0x8D); // Charge pump SSD1306_WriteCommand(0x14); SSD1306_WriteCommand(0x20); // Memory addressing mode SSD1306_WriteCommand(0x00); // Horizontal mode SSD1306_WriteCommand(0xA1); // Segment remap SSD1306_WriteCommand(0xC8); // COM scan direction SSD1306_WriteCommand(0xDA); // COM pins hardware config SSD1306_WriteCommand(0x12); SSD1306_WriteCommand(0x81); // Contrast SSD1306_WriteCommand(0x7F); SSD1306_WriteCommand(0xD9); // Pre-charge SSD1306_WriteCommand(0xF1); SSD1306_WriteCommand(0xDB); // VCOM detect SSD1306_WriteCommand(0x40); SSD1306_WriteCommand(0xA4); // Resume to RAM content display SSD1306_WriteCommand(0xA6); // Normal display SSD1306_WriteCommand(0xAF); // Display ON }
#define SSD1306_WIDTH 128 #define SSD1306_HEIGHT 64 uint8_t framebuffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8]; void SSD1306_UpdateScreen(void) { for (uint8_t page = 0; page < 8; page++) { SSD1306_WriteCommand(0xB0 + page); // Set page address SSD1306_WriteCommand(0x00); // Set lower column start SSD1306_WriteCommand(0x10); // Set higher column start SSD1306_WriteData(&framebuffer[SSD1306_WIDTH * page], SSD1306_WIDTH); } }
void SSD1306_DrawPixel(uint8_t x, uint8_t y, uint8_t color) { if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) return; if (color) framebuffer[x + (y / 8) * SSD1306_WIDTH] |= (1 << (y % 8)); else framebuffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8)); }
A charge pump is a type of electronic circuit that generates a higher voltage (or sometimes a negative voltage) from a lower voltage without using inductors like traditional power supplies.
In the context of an OLED display like the SSD1306, the charge pump is used to generate the higher voltage needed to drive the OLED pixels, since the logic voltage (e.g. 3.3V or 5V) is too low to do that directly.
OLED displays typically require 7-8 volts to properly light up the organic materials in the pixels. But microcontrollers like Arduino, STM32, or ESP32 supply only 3.3V or 5V.
So the SSD1306 includes an internal charge pump that:
This command tells the SSD1306 whether to enable or disable the internal charge pump.
0x8D [val]
Values | |
---|---|
0x10 | Disable charge pump |
0x14 | Enable charge pump |
uint8_t init_sequence[] = { 0xAE, // Display OFF 0x8D, 0x14, // Enable charge pump 0xAF // Display ON };To see how such an initailisation sequence would be implemented, have a look at the function "init_display(self)" in the 'Fully Working Example' below. That technique can be adapted to supply all sorts of initialisation commands to all sorts of I2C devices (not limited to just displays).
Term | Description |
---|---|
SSD1306 | Provides ~7.5V from 3.3V/5V to power OLED pixels |
Enable Command | 0x8D, 0x14 |
Disable Command | 0x8D, 0x10 |
The 72x40 SSD1306 OLED display is a low-resolution variant of the popular SSD1306 family, typically using I2C communication and controlled via commands sent to the SSD1306 driver chip. Although your display is 72x40, the command set is identical to the standard SSD1306 (typically 128x64 or similar). The only difference is the display RAM mapping—commands and functions remain the same.
Here is a list of SSD1306 command bytes grouped by purpose:
Command | Hex | Description |
---|---|---|
Set Contrast | 0x81 + 1 byte | Sets brightness level (0-255) |
Entire Display ON | 0xA4 | Resume from RAM content display |
Entire Display ON (Override) | 0xA5 | Entire display ON, ignores RAM |
Set Normal Display | 0xA6 | Normal display (0=black, 1=white) |
Set Inverse Display | 0xA7 | Inverse display (0=white, 1=black) |
Display OFF | 0xAE | Turns display off |
Display ON | 0xAF | Turns display on |
Command | Hex | Description |
---|---|---|
Set Memory Addressing Mode | 0x20 + 1 byte | 0x00: Horizontal, 0x01: Vertical, 0x02 |
Set Column Address | 0x21 + start + end | (0-127 typical, for 72x40 use 0-71) |
Set Page Address | 0x22 + start + end | Page range (0-7 for 64px high; use 0-4 for 40px) |
Command | Hex | Description |
---|---|---|
Right Horizontal Scroll | 0x26 + 6 bytes | Setup right scroll |
Left Horizontal Scroll | 0x27 + 6 bytes | Setup left scroll |
Vertical & Right Scroll | 0x29 + 6 bytes | Combo scroll |
Vertical & Left Scroll | 0x2A + 6 bytes | Combo scroll |
Activate Scroll | 0x2F | Start scrolling |
Deactivate Scroll | 0x2E | Stop scrolling |
Set Vertical Scroll Area | 0xA3 + 2 byte | (top fixed area, scroll area rows) |
Command | Hex | Description |
---|---|---|
Set Display Start Line | 0x40-0x7F | Sets RAM start line (0-63) |
Set Segment Remap | 0xA0 or 0xA1 | Mirror display horizontally |
Set COM Output Scan Direction | 0xC0 or 0xC8 | Mirror vertically |
Set Multiplex Ratio | 0xA8 + 1 byte | Typically 0x27 for 40px height |
Set Display Offset | 0xD3 + 1 byte | Vertical offset |
Set COM Pins Hardware Config | 0xDA + 1 byte | For 40px: use 0x02 (sequential COM) |
Command | Hex | Description |
---|---|---|
Set Display Clock Divide Ratio | 0xD5 + 1 byte | Oscillator setting |
Set Pre-charge Period | 0xD9 + 1 byte | Charge time for pixels |
Set VCOMH Deselect Level | 0xDB + 1 byte | Voltage setting |
NOP | 0xE3 | No Operation |
Command | Hex | Description |
---|---|---|
Charge Pump Setting | 0x8D + 1 byte | 0x14=Enable, 0x10=Disable |
cmds = [ 0xAE, # Display OFF 0x20, 0x00, # Horizontal addressing mode 0xB0, # Page 0 start address 0xC8, # COM scan direction 0x00, # Low column start 0x10, # High column start 0x40, # Display start line 0x81, 0x8F, # Contrast 0xA1, # Segment remap 0xA6, # Normal display 0xA8, 0x27, # Multiplex ratio (for 40px height) 0xD3, 0x00, # Display offset 0xD5, 0x80, # Display clock 0xD9, 0xF1, # Pre-charge 0xDA, 0x02, # COM pins hardware config for 40px 0xDB, 0x20, # VCOMH 0x8D, 0x14, # Charge pump ON 0xAF # Display ON ]
Commands like line(), rect(), fill_rect(), pixel() etc. do not exist in the SSD1306 hardware itself — and they are not part of the SSD1306 command set. These are software-level drawing functions provided by driver libraries, not the display controller.
They come from graphics libraries that sit on top of the SSD1306 driver. Here are the key layers:
oled.pixel(x, y, color) oled.line(x1, y1, x2, y2, color) oled.rect(x, y, w, h, color) oled.fill_rect(x, y, w, h, color) oled.text("Hi", x, y)
If you're using MicroPython on a Raspberry Pi Pico:
from machine import I2C, Pin import ssd1306 i2c = I2C(0, scl=Pin(5), sda=Pin(4)) oled = ssd1306.SSD1306_I2C(72, 40, i2c) oled.fill(0) oled.line(0, 0, 71, 39, 1) oled.rect(10, 5, 30, 20, 1) oled.fill_rect(15, 10, 10, 10, 1) oled.show()
Here's what happens
Feature | Exists in SSD1306? | Provided by |
---|---|---|
oled.line() | NO | Graphics library |
oled.rect() | NO | Graphics library |
oled.pixel() | NO | Graphics library |
oled.show() | Translates to SSD1306 commands | Driver |
oled.fil() | NO | Clears buffer (library) |
# MicroPython driver for 72x40 pixel SSD1306 OLED # using I2C interface only by Paul Geare 8 June 2025 from micropython import const import framebuf # register definitions SET_CONTRAST = const(0x81) SET_ENTIRE_ON = const(0xA4) SET_NORM_INV = const(0xA6) SET_DISP = const(0xAE) SET_MEM_ADDR = const(0x20) SET_COL_ADDR = const(0x21) SET_PAGE_ADDR = const(0x22) SET_DISP_START_LINE = const(0x40) SET_SEG_REMAP = const(0xA0) SET_MUX_RATIO = const(0xA8) SET_COM_OUT_DIR = const(0xC0) SET_DISP_OFFSET = const(0xD3) SET_COM_PIN_CFG = const(0xDA) SET_DISP_CLK_DIV = const(0xD5) SET_PRECHARGE = const(0xD9) SET_VCOM_DESEL = const(0xDB) SET_CHARGE_PUMP = const(0x8D) # FrameBuffer provides support for graphics primitives # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): def __init__(self, width, height, external_vcc): self.width = width self.height = height self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) self.init_display() def init_display(self): for cmd in ( 0x21, # Set column address... 0x00, # to start at column 0 0x47, # and end at column 71 0x22, # Set page address... 0x00, # to start at page 0 0x04, # and end at page 4 0xD3, # Set display offset... 0x00, # 0 X-offset 0x40, # Set display start line to 0 SET_DISP | 0x00, # off # address setting SET_MEM_ADDR, 0x00, # horizontal # resolution and layout SET_DISP_START_LINE | 0x00, SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 SET_MUX_RATIO, self.height - 1, SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 SET_DISP_OFFSET, 0x00, SET_COM_PIN_CFG, 0x02 if self.width > 2 * self.height else 0x12, # timing and driving scheme SET_DISP_CLK_DIV, 0x80, SET_PRECHARGE, 0x22 if self.external_vcc else 0xF1, SET_VCOM_DESEL, 0x30, # 0.83*Vcc # display SET_CONTRAST, 0xFF, # maximum SET_ENTIRE_ON, # output follows RAM contents SET_NORM_INV, # not inverted # charge pump SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, SET_DISP | 0x01, ): # on self.write_cmd(cmd) self.fill(0) self.show() def poweroff(self): self.write_cmd(SET_DISP | 0x00) def poweron(self): self.write_cmd(SET_DISP | 0x01) def contrast(self, contrast): self.write_cmd(SET_CONTRAST) self.write_cmd(contrast) def invert(self, invert): self.write_cmd(SET_NORM_INV | (invert & 1)) def show(self): x0 = 28 x1 = self.width + 27 self.write_cmd(SET_COL_ADDR) self.write_cmd(x0) self.write_cmd(x1) self.write_cmd(SET_PAGE_ADDR) self.write_cmd(0) self.write_cmd(self.pages - 1) self.write_data(self.buffer) class SSD1306_I2C(SSD1306): def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): self.i2c = i2c self.addr = addr self.temp = bytearray(2) self.write_list = [b"\x40", None] # Co=0, D/C#=1 super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.temp[0] = 0x80 # Co=1, D/C#=0 self.temp[1] = cmd self.i2c.writeto(self.addr, self.temp) def write_data(self, buf): self.write_list[1] = buf self.i2c.writevto(self.addr, self.write_list)
from machine import Pin, I2C #from machine import RTC from SSD1306_i2c72x40_Only_PHG import SSD1306_I2C from time import sleep import sys def clear_blink(blinks): for i in range (blinks): oled.fill(1) oled.show() sleep(0.1) oled.fill(0) oled.show() sleep(0.1) SSD1306_bus = 0 SSD1306_scl = machine.Pin(17) SSD1306_sda = machine.Pin(16) SSD1306_freq = 200000 SSD1306_width = 72 SSD1306_height = 40 SSD1306_i2c = machine.I2C(SSD1306_bus,scl=SSD1306_scl, sda=SSD1306_sda, freq=SSD1306_freq) SSD1306_addr = 0X3C oled = SSD1306_I2C(SSD1306_width, SSD1306_height, SSD1306_i2c, SSD1306_addr) clear_blink(5) #while True:oled.fill(0) oled.text("Bus "+str(SSD1306_bus)+",", 0, 0) oled.text("Addr"+str(SSD1306_addr), 0, 8) oled.text("SDA GP16", 0, 16) oled.text("SCL GP17", 0, 24) oled.text("PicoPower", 0, 32) oled.show() sleep(2) clear_blink(3) while True: for x in range(0,72,4): y=int((40/72)*x) oled.line(x,0,72,y,0) oled.line(72,y,72-x,40,0) oled.line(72-x,40,0,40-y,0) oled.line(0,40-y,x,0,0) oled.show() for x in range(0,72,4): y=int((40/72)*x) oled.line(x,0,72,y,1) oled.line(72,y,72-x,40,1) oled.line(72-x,40,0,40-y,1) oled.line(0,40-y,x,0,1) oled.show()
OLED (Organic Light Emitting Diode) displays, especially small ones like SSD1306-based modules (used frequently in Arduino and embedded projects), are typically controlled via commands sent over I2C or SPI. These commands configure and control the display. Below is a breakdown of common OLED commands, their parameters, and what they do, specifically for SSD1306 (one of the most common OLED controllers):
Each command is usually a single byte, followed by 0 or more data bytes (parameters). Commands are sent with a control byte that tells the display you are sending a command (0x00) or data (0x40).
Command | Name | Parameters | Description |
---|---|---|---|
0xAE | Display OFF | None | Turns off the display |
0xAF | Display ON | None | Turns on the display |
0xD5 | Set Display Clock Divide Ratio / Oscillator Frequency | 1 byte: `[A[3:0] | B[3:0]]` |
0xA8 | Set Multiplex Ratio | 1 byte: 0x3F (for 128x64) | Number of active rows |
0xD3 | Set Display Offset | 1 byte: 0x00 | Vertical shift of display |
0x40 | Set Display Start Line | None or 1 byte | Start line from RAM |
0x8D | Charge Pump Setting | 1 byte: 0x14 = Enable, 0x10 = Disable | Controls internal charge pump |
0xA1 | Segment Remap | None | Flip horizontal (mirror X) |
0xC8 | COM Output Scan Direction | None | Flip vertical (mirror Y) |
0xDA | COM Pins Hardware Configuration | 1 byte: 0x12 for 128x64 | Configures COM pin layout |
0x81 | Set Contrast Control | 1 byte: 0-255 | Adjusts brightness |
0xA4 | Entire Display ON (resume RAM content) | None | Uses display RAM |
0xA5 | Entire Display ON (ignore RAM) | None | All pixels ON |
0xA6 | Set Normal Display | None | Normal display (white on black) |
0xA7 | Set Inverse Display | None | Inverts pixels (black on white) |
0x20 | Set Memory Addressing Mode | 1 byte: 0x00 (Horizontal), 0x01 (Vertical), 0x02 (Page) | Controls RAM addressing mode |
0x21 | Set Column Address | 2 bytes: start, end | Column range for addressing |
0x22 | Set Page Address | 2 bytes: start, end | Page range (each page = 8 rows) |
0x2E | Deactivate Scroll | None | Stops any scrolling |
0x2F | Activate Scroll | None | Starts scrolling |
0x26 / 0x27 | Right/Left Horizontal Scroll | 6 bytes | Setup scroll direction and speed |
0x00-0x0F | Lower Column Address | Low nibble of column | |
0x10-0x1F | Higher Column Address | High nibble of column |
To draw, send 0x40 followed by 8-bit data (1 byte = 8 vertical pixels).
Each column is addressed via 0x21 and 0x22 commands to set column and page range.
0xAE // Display OFF 0xD5, 0x80 // Set display clock 0xA8, 0x3F // Set multiplex ratio (0x3F = 64) 0xD3, 0x00 // Set display offset 0x40 // Set start line = 0 0x8D, 0x14 // Charge pump ON 0x20, 0x00 // Horizontal addressing mode 0xA1 // Segment remap (mirror horizontally) 0xC8 // COM scan direction (mirror vertically) 0xDA, 0x12 // COM pins config 0x81, 0xCF // Set contrast 0xD9, 0xF1 // Pre-charge period 0xDB, 0x40 // VCOMH deselect level 0xA4 // Display follows RAM content 0xA6 // Normal display 0xAF // Display ON
Graphics-level commands available in MicroPython (or CircuitPython) when using the SSD1306 OLED driver. These higher-level methods are part of the ssd1306 module or compatible drivers (like framebuf), and they let you draw graphics primitives like lines, rectangles, and text.
Here's a guide to the common SSD1306 graphics commands in MicroPython:
from machine import I2C, Pin import ssd1306 i2c = I2C(0, scl=Pin(22), sda=Pin(21)) # Adjust pins for your board oled = ssd1306.SSD1306_I2C(128, 64, i2c)
Method | Description |
---|---|
oled.pixel(x, y, color) | Set a single pixel. color: 1 = on, 0 = off |
oled.line(x0, y0, x1, y1, color) | Draw a line from (x0, y0) to (x1, y1) |
oled.hline(x, y, width, color) | Draw a horizontal line |
oled.vline(x, y, height, color) | Draw a vertical line |
oled.rect(x, y, w, h, color) | Draw a rectangle outline |
oled.fill_rect(x, y, w, h, color) | Draw a filled rectangle |
oled.fill(color) | Fill the entire screen (1 = white/on, 0 = black/off) |
oled.text(string, x, y, color=1) | Draw ASCII text at (x, y) |
oled.scroll(dx, dy) | Scroll the screen by dx and dy pixels |
oled.blit(framebuf, x, y) | Copy another framebuffer (image or font) onto the display |
oled.show() | Send all drawing to the display (must be called to update screen) |
oled.fill(0) # Clear screen oled.text("Hello", 0, 0) oled.line(0, 10, 127, 10, 1) oled.rect(10, 20, 50, 30, 1) oled.fill_rect(70, 20, 50, 30, 1) oled.pixel(64, 32, 1) oled.show()
Always call oled.show() after drawing, or nothing will appear on screen.
The blit() method is a powerful way to copy graphics from one framebuffer (image, shape, font, etc.) to another — e.g. onto a SSD1306 OLED display.
oled.blit(source_framebuf, x, y)
source_framebuf: A FrameBuffer object (e.g., a sprite or bitmap image).
x, y: Top-left corner on the OLED where you want to place it.
A FrameBuffer is essentially a 2D array of pixel data, representing an image or graphical object. The ssd1306 driver uses one internally, and you can create your own to hold:
Here's how to draw a simple 8x8 bitmap and blit() it onto your OLED:
import framebuf
smiley = bytearray([ 0b00111100, 0b01000010, 0b10100101, 0b10000001, 0b10100101, 0b10011001, 0b01000010, 0b00111100 ]) # Create a framebuffer for it fb = framebuf.FrameBuffer(smiley, 8, 8, framebuf.MONO_HLSB) # Draw it to the OLED oled.blit(fb, 60, 30) oled.show()
Format | Description |
---|---|
framebuf.MONO_HLSB | 1-bit (monochrome), horizontal row order, LSB first — used for SSD1306 |
framebuf.MONO_HMSB | 1-bit, horizontal row order, MSB first |
framebuf.MONO_VLSB | 1-bit, vertical row order, LSB first (some other displays) |
framebuf.RGB565 | 16-bit color (used for color TFTs) |
Use MONO_HLSB for most OLEDs like SSD1306.