yahboom
HomeLearn
Solution
Blog
Online Store
About Us
EN/中文
ESP32 Core Board📖
1. ESP32 Introduction
  • 1. ESP32S3 Overview
  • 2. Environment Setup Introduction
  • 3. Dev Board Introduction
2. ESP-IDF Development Tutorial
1. Before Development
  • 1. Environment Setup (Windows)
  • 2. Environment Setup (Linux)
  • 3. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. DMA
  • 8. ADC
  • 9. PWM Breathing LED
  • 10. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. PSRAM External Memory Experiment
  • 5. Flash Read and Write Experiment
  • 6. USB Emulated Mass Storage Experiment
4. FreeRTOS operating system
  • 1.FreeRTOS Core Concepts Theory
  • 2.Task Creation and Management
  • 3.Queue
  • 4.Binary Semaphore
  • 5.Mutex
  • 6.Event Groups
  • 7.Software Timer
  • 8.Task Scheduler
  • 9.Low Power Mode
  • 10.Deadlock
  • 11.Priority Inversion
5. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. Socket Programming Basics
  • 9. HTTP Client Experiment
  • 10. HTTP Server Experiment
  • 11. MQTT IoT Communication Protocol
  • 12. WIFI OTA Upgrade
  • 13. Bluetooth Fundamentals
  • 14. Bluetooth Slave Mode
  • 15. Bluetooth Master Mode
6. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
7. Xiaozhi AI Voice
  • 1. Burn Xiaozhi Firmware
  • 2. Network and Configuration
  • 3. AI Agent Settings
  • 4. Program Analysis
  • 5. Free Chat
3. Arduino Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. ADC
  • 8. PWM Breathing LED
  • 9. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication
  • 2. I2C OLED Display
  • 3. SPI Bus Communication
  • 4. Flash Read and Write
  • 5. USB Emulated Mass Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Server Experiment
  • 5. UDP Client Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT IoT Communication Protocol
  • 11. WIFI OTA Upgrade
  • 12. Bluetooth Fundamentals
  • 13. Bluetooth Slave Mode
  • 14. Bluetooth Master Mode
5. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
4. MicroPython Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
  • 3. Thonny IDE
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. TIMER_IT
  • 5. Watchdog Timer
  • 6. ADC
  • 7. PWM Breathing LED
  • 8. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. Flash Read and Write Experiment
  • 5. Print Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT
  • 11. Bluetooth Fundamentals
  • 12. Bluetooth Slave Mode
  • 13. Bluetooth Master Mode
5. Audio Applications
  • 1. I2S Audio Playback Experiment
  • 2. Repeater Experiment
Download
  • Code
  • Hardware_Info
  • 3D_Model_File
  • Software_Tools
  • VMware
  • VM_File
Download ZIP

Welcome to ESP32 Core Board repository

2. I2C_OLED Display Experiment

I2C_OLED Display Experiment


I2C_OLED Display Experiment1. I2C OLED Overview2. Core Concepts2.1 I2C Bus Principle2.1.1 Standard I2C Write Sequence (General Format)2.1.2 SSD1306 I2C Write Sequence (Actual Format for This Project)2.1.3 Segment-by-Segment Analysis2.1.4 Complete I2C Single Byte Transmit Process (Code Level)2.1.5 Speed Modes2.2 SSD1306 Display Principle2.3 Font Storage Format3. ESP32-S3 I2C Hardware Specifications4. Project Architecture and Flow5. Hardware Dependencies6. Source Code Details6.1 oled_bus.c — I2C Bus Initialization6.2 oled.c — Low-level Command/Data Sending6.3 ssd1306_init() — Controller Initialization6.4 oled_render_page() — Row-major Transpose Rendering6.5 app_main() — Entry Point7. Operation Flow7.1 Build and Flash Process7.2 Normal Display8. FAQ


1. I2C OLED Overview

I2C (Inter-Integrated Circuit) is a synchronous serial communication protocol that requires only SDA (data line) + SCL (clock line) two signal lines for bidirectional communication between MCU and multiple peripherals.

This project drives an OLED display (128×32 pixels) with SSD1306 controller via I2C to implement character display functionality.

Typical use cases:

ScenarioDescription
Embedded Device Info ScreenDisplay device status, IP address, sensor readings
Smart WearablesSmall size, low power real-time information display
InstrumentationThermometer, multimeter, and other small displays
Human-Machine InterfaceSimple menus, parameter setting interfaces

2. Core Concepts

2.1 I2C Bus Principle

The I2C bus implements "wire-AND" logic through open-drain output + pull-up resistor: the bus is default pulled to high level by pull-up resistors. Any device can pull the bus low through internal NMOS, avoiding short circuit when one device outputs high and another outputs low.

2.1.1 Standard I2C Write Sequence (General Format)

The vast majority of I2C devices (sensors, EEPROM, etc.) follow a unified format for single-byte write:

​x
Sequence:  START → DevAddr(7b)+W → ACK → RegAddr(8b) → ACK → Data(8b) → ACK → STOP

        ├─── address frame ───┤ACK├── register address ──┤ACK├─── data ───┤ACK
        │   8-bit           │   │    8-bit            │   │   8-bit    │

SCL: ──┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌──

Standard I2C write sequence: START → device address+R/W → ACK → register address → ACK → data → ACK → STOP. "Register address" tells the slave which internal register to write to, "data" is the value to write. SCL has 27 clock cycles (3×9), SDA changes at SCL low level and is sampled at SCL high level.

2.1.2 SSD1306 I2C Write Sequence (Actual Format for This Project)

SSD1306 does not have traditional register addresses. Instead, it uses a control byte to distinguish whether subsequent bytes are commands or data. The control byte occupies the position of "register address" in standard I2C:

xxxxxxxxxx
Standard I2C:  DevAddr+R/W → ACK →  register address  → ACK →   data   → ACK → STOP
SSD1306:       DevAddr+R/W → ACK →  control byte      → ACK → cmd/data → ACK → STOP
                              (0x00=command)
                              (0x40=data)

Taking sending a command as an example (oled_cmd(c)):

xxxxxxxxxx
Sequence:  START → 0x78(Addr+W) → ACK → 0x00(Ctrl:command) → ACK → CmdByte → ACK → STOP

        ├──── address frame ────┤ACK├─── control byte ───┤ACK├── command byte ──┤ACK
        │ 0x78=write address  │   │ 0x00=command mode  │   │  command value  │

SCL: ──┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌──

Each segment's 8-bit data transmitted on SDA (each byte followed by 1-bit ACK):
  Address frame:   1 1 1 1 1 0 0 0 (→ ACK)    corresponds to 0x78 = 0x3C<<1 | W=0
  Control byte:    0 0 0 0 0 0 0 0 (→ ACK)    corresponds to 0x00 = next byte is command
  Command byte:    x x x x x x x x (→ ACK)    corresponds to command value, e.g. 0xAF = Display ON

SSD1306's control byte (0x00/0x40) occupies the position of standard device "register address" in I2C sequence, but the meaning is different — it identifies whether subsequent bytes are commands or data, not pointing to some internal register.

2.1.3 Segment-by-Segment Analysis

① Start Condition

xxxxxxxxxx
SCL: ───────────┐
                │
SDA: ─────────┐ │
              │ │
              └─┘

When SCL is high, SDA goes from high to low → START. Bus transitions from idle to busy, all slaves start listening for addresses.

② Address Frame

The first byte sent by master = 7-bit slave address + 1-bit read/write direction:

xxxxxxxxxx
Bit:  7   6   5   4   3   2   1   0
    ┌───┬───┬───┬───┬───┬───┬───┬───┐
    │ 1 │ 1 │ 1 │ 1 │ 0 │ 0 │ 0 │ R/W│
    └───┴───┴───┴───┴───┴───┴───┴───┘
      └────────── 0x3C ─────────┘  └┬─┘
                                   0=write,1=read
  • Complete 8-bit address byte = 0x3C << 1 | 0 = 0x78 (write) or 0x3C << 1 | 1 = 0x79 (read)

  • SSD1306 only receives commands and data, no readback provided, so R/W is always 0 (write)

③ Acknowledge Bit (ACK / NACK)

xxxxxxxxxx
         9th clock
         ┌──┐
SCL: ────┘  └───

       ┌──────┐
SDA: ───┘      └───  ← Slave pulls low = ACK

After every 8-bit transfer, the receiver must pull SDA low during the 9th SCL clock cycle as ACK. If no ACK (SDA stays high = NACK), master should stop or retry.

The 9 SCL pulses are generated by the master, but the SDA level during the 9th clock is controlled by the receiver (master releases SDA, slave pulls it low).

④ Data Frame

Same format as address frame: 8-bit data + 1-bit ACK. SSD1306 data frames are divided into two types:

Data BytePrefixMeaning
0x00 + command byteControl byte = 0x00Subsequent bytes are SSD1306 commands
0x40 + data byteControl byte = 0x40Subsequent bytes are GDDRAM display data

SSD1306 does not support pure I2C standard command/data distinction, but uses the first data byte (Co=0, D/C#=0/1) as control identifier, followed by the actual command or data value. This is an SSD1306-specific convention, not the I2C protocol standard.

⑤ Stop Condition

xxxxxxxxxx
SCL:        ┌────
      ──────┘
SDA:    ──────┐
              └────

When SCL is high, SDA goes from low to high → STOP. Bus is released, returns to idle state.

2.1.4 Complete I2C Single Byte Transmit Process (Code Level)

oled_cmd() sends a single command byte with complete steps:

xxxxxxxxxx
START → 0x78 → ACK(slave) → 0x00 → ACK(slave) → command → ACK(slave) → STOP

Master actions:
  i2c_master_transmit(oled_dev, {0x00, cmd_byte}, 2, -1)
                              ↑       ↑
                         control byte  command byte

Corresponding code implementation (oled.c:oled_cmd):

xxxxxxxxxx
void oled_cmd(uint8_t c)
{
    // [Ctrl byte, Cmd byte]
    uint8_t buf[2] = {0x00, c};
    i2c_master_transmit(oled_dev, buf, 2, -1);
    // ↑ ESP-IDF driver automatically completes START→address→ACK→buf[0]→ACK→buf[1]→ACK→STOP
}

ESP-IDF's i2c_master_transmit() encapsulates the low-level sequence: automatically sends START, address frame, waits for ACK, sends bytes one by one with ACK, finally sends STOP. Developer only needs to care about the byte content to send.

2.1.5 Speed Modes

ModeClock FrequencyUse Case
Standard (Sm)100 kHzTraditional low-speed devices
Fast (Fm)400 kHzCommon mode, this example
Fast Plus (Fm+)1 MHzHigh-speed sensors
High Speed (Hs)3.4 MHzRequires special master support

2.2 SSD1306 Display Principle

SSD1306 divides the 128×32 pixel OLED panel into 4 "pages" (Page 0~3), each page 8 rows of pixels:

xxxxxxxxxx
Page 0  ← Physical top (rows 0~7)
Page 1  ← Rows 8~15
Page 2  ← Rows 16~23
Page 3  ← Physical bottom (rows 24~31)

Each byte of data sent (0x40 + 8-bit) corresponds to 8 vertical pixels in one column — bit 0 at top, bit 7 at bottom. After writing, column address auto-increments, allowing continuous writing of a full page (128 columns).

xxxxxxxxxx
        Col0 Col1 Col2 ... Col127
Page0:  ■□□  ■□■  □□□      □■□
        □■□  □■□  □□□      ■□□
        ......(8 rows total)......
Page1:  ...and so on...

2.3 Font Storage Format

This project supports 4 font types, divided into two storage formats:

Row-major: Each uint16_t = one row of pixels, FontHeight entries = one character. During rendering, transpose (row→column) is required to write to SSD1306's page memory.

Column-major: Each uint16_t = one column, high byte = upper 8px (Page0), low byte = lower 8px (Page1). No transpose needed, direct write.

FontSizeEntries per CharacterStorage Format
Font7x107×1010Row-major
Font11x1811×1818Row-major
Font16x2616×2626Row-major
Font16x1616×1632Column-major

3. ESP32-S3 I2C Hardware Specifications

FeatureParameter
I2C Controller Count2 (I2C0, I2C1)
Speed ModesStandard (100kHz), Fast (400kHz), Fast+ (1MHz)
Addressing Modes7-bit and 10-bit
FIFO Depth32 bytes
DMA SupportSupported
Internal Pull-upOptional (enabled in this example, no external resistor needed)

4. Project Architecture and Flow

Three-layer architecture:

xxxxxxxxxx
main.c (Application Layer)
  └─ app_main()
       ├─ i2c_init()           Initialize I2C bus, mount 0x3C device
       ├─ ssd1306_init()       Send SSD1306 initialization sequence
       ├─ oled_clear()         Clear screen
       └─ oled_show_str()      Display string

oled_bus.c (Bus Layer)
  └─ i2c_init()
       ├─ i2c_new_master_bus()       Create I2C master (SDA=41, SCL=42, 400kHz)
       └─ i2c_master_bus_add_device()  Mount OLED device (address 0x3C)

oled.c (Driver Layer)
  ├─ oled_cmd() / oled_data()  Send command/data via I2C
  ├─ ssd1306_init()            29 command initialization sequence
  ├─ oled_clear()              4 pages × 128 columns write 0x00
  └─ oled_show_str()           Iterate string, call font rendering function
       └─ oled_render_page()   Row-major → column transpose → write to GDDRAM

5. Hardware Dependencies

Hardware Connection:

SignalGPIODescription
SDA41I2C data line
SCL42I2C clock line
OLED Address0x3C7-bit I2C address
Resolution128×32SSD1306 controller

Internal pull-up is enabled, no external pull-up resistor needed.

Required Components: driver/i2c_master, FreeRTOS


6. Source Code Details

6.1 oled_bus.c — I2C Bus Initialization

i2c_init() creates I2C master bus, mounts SSD1306 device at address 0x3C in 400kHz Fast Mode, and stores the device handle in global variable oled_dev for all driver layer functions to reference.

xxxxxxxxxx
#define I2C_SDA     GPIO_NUM_41                  // I2C data line
#define I2C_SCL     GPIO_NUM_42                  // I2C clock line
#define OLED_ADDR   0x3C
i2c_master_dev_handle_t oled_dev;

void i2c_init(void)
{
    i2c_master_bus_config_t bus_cfg = {
        .sda_io_num = I2C_SDA,
        .scl_io_num = I2C_SCL,
        .clk_source = I2C_CLK_SRC_DEFAULT,
        // Enable internal pull-up
        .flags.enable_internal_pullup = 1,
    };

    i2c_master_bus_handle_t bus;
    // Create master bus
    i2c_new_master_bus(&bus_cfg, &bus);

    i2c_device_config_t dev_cfg = {
        .device_address = OLED_ADDR,
        .scl_speed_hz = 400000,                  // 400kHz Fast mode
    };

    // Attach OLED device
    i2c_master_bus_add_device(bus, &dev_cfg, &oled_dev);
}

6.2 oled.c — Low-level Command/Data Sending

SSD1306 distinguishes command (0x00) and data (0x40) through I2C prefix byte:

xxxxxxxxxx
// Send single-byte command
void oled_cmd(uint8_t c)
{
    // [Control byte, Command byte]
    uint8_t buf[2] = {0x00, c};
    i2c_master_transmit(oled_dev, buf, 2, -1);    // Blocking send
}

// Send single-byte data
void oled_data(uint8_t d)
{
    // [Control byte, Data byte]
    uint8_t buf[2] = {0x40, d};
    i2c_master_transmit(oled_dev, buf, 2, -1);    // Blocking send
}

6.3 ssd1306_init() — Controller Initialization

SSD1306 requires configuring internal registers in a specific sequence after power-on. The initialization sequence has 29 commands, key registers as follows:

RegisterValueFunction
0xAE / 0xAF—Display OFF / ON
0xD50x80Clock divide ratio, default frequency
0xA80x1FMUX ratio = 32 (0x1F = 31)
0x8D0x14Charge pump enable, enable internal DC-DC
0x200x02Page addressing mode
0xA1—Segment remap (horizontal flip)
0xC0—COM scan direction (normal)
0xDA0x12COM pin hardware configuration (128×32 must be 0x12)
xxxxxxxxxx
void ssd1306_init(void)
{
    vTaskDelay(100);     // Wait for power stable

    oled_cmd(0xAE);      // Display OFF
    oled_cmd(0xD5); oled_cmd(0x80);  // Set clock divide
    oled_cmd(0xA8); oled_cmd(0x1F);  // MUX = 32 (0x1F = 31)
    oled_cmd(0xD3); oled_cmd(0x00);  // Display offset = 0
    oled_cmd(0x40);                   // Start line = 0
    oled_cmd(0x8D); oled_cmd(0x14);  // Charge pump enable
    oled_cmd(0x20); oled_cmd(0x02);  // Page addressing mode
    // Segment remap (horizontal flip)
    oled_cmd(0xA1);
    oled_cmd(0xC0);
    oled_cmd(0xDA); oled_cmd(0x12);
    oled_cmd(0x81); oled_cmd(0xCF);  // Set contrast
    oled_cmd(0xD9); oled_cmd(0xF1);  // Pre-charge period
    oled_cmd(0xDB); oled_cmd(0x40);  // VCOMH level
    oled_cmd(0x2E);                   // Stop scroll
    oled_cmd(0xA4);                   // Entire display ON
    // Normal display (non-inverted)
    oled_cmd(0xA6);

    // Clear screen, remove noise
    oled_clear();

    oled_cmd(0xAF);      // Display ON
    vTaskDelay(100);     // Wait for display stable
}

6.4 oled_render_page() — Row-major Transpose Rendering

Row-major font needs transpose to vertical column data before writing to SSD1306. Core logic: for rows in the specified range (start_row ~ start_row+row_count-1), traverse each column to extract bits, combine into one byte of vertical column data.

xxxxxxxxxx
// Row-major glyph bit extract
#define GLYPH_BIT(data, row, col)  (((data)[row]) & (1 << (15 - (col))))
static void oled_render_page(uint8_t x, const uint16_t *data,
                              int width, int start_row, int row_count, int page)
{
    // Set page + column address
    oled_set_pos(page, x);
    for (int col = 0; col < width; col++) {
        uint8_t byte = 0;
        for (int r = 0; r < row_count; r++) {
            if (GLYPH_BIT(data, start_row + r, col))
                // Row bit → vertical bit (MSB=top)
                byte |= (1 << (row_count - 1 - r));
        }
        // Write one column of 8 pixels
        oled_data(byte);
    }
}

6.5 app_main() — Entry Point

app_main() sequentially: I2C bus init → SSD1306 controller init → clear screen → select 16×26 font → show "YAHBOOM" at x=0.

xxxxxxxxxx
void app_main(void)
{
    // 1. Init I2C bus
    i2c_init();
    // 2. Init SSD1306 controller
    ssd1306_init();
    oled_clear();                            // 3. Clear screen
    // 4. Select 16x26 font
    oled_set_font(OLED_FONT_SEL_16X26);
    oled_show_str(0, "YAHBOOM");             // 5. Show string
}

7. Operation Flow

7.1 Build and Flash Process

For detailed flash process: 1. Before Development → 3. Build and Flash

7.2 Normal Display

After flashing, OLED screen displays "YAHBOOM" in the center (16×26 large font). Font and content can be changed by modifying oled_set_font() and oled_show_str().

Operation Result

The following example shows how to switch fonts and display text. Uncomment to preview each of the 4 fonts.

xxxxxxxxxx
oled_clear();                                 // Clear screen
// 11×18 font
oled_set_font(OLED_FONT_SEL_11X18);     oled_show_str(0, "0: 11x18");
// 16×26 font
oled_set_font(OLED_FONT_SEL_16X26);     oled_show_str(0, "1: 16x26");
// 7×10 font
oled_set_font(OLED_FONT_SEL_7X10);      oled_show_str(0, "2: 7x10");
// 16×16 font
oled_set_font(OLED_FONT_SEL_16X16);     oled_show_str(0, "3: 16x16");

8. FAQ

SymptomCauseSolution
Screen all black, no displayI2C wiring error / address mismatch / no powerCheck SDA=GPIO41, SCL=GPIO42 wiring, confirm module VCC=3.3V, GND connected
Screen garbled / random noiseInitialization sequence sending abnormal or power-on unstableCheck if I2C pull-up is enabled (enable_internal_pullup = 1), repower
Display content offset/misalignedPage addressing mode not correctly configured or column start position wrongConfirm 0x20→0x02 (page addressing) in ssd1306_init() is correctly sent
Screen flickeringI2C clock too high or signal quality poorLower scl_speed_hz from 400000 to 100000 for testing
Font display incompleteFont compile macros not enabledCheck if corresponding OLED_FONT_* macros are defined in oled_fonts.h
No display after USB connectedGPIO41/42 may be multiplexed by USB or PSRAMESP32-S3's GPIO41/42 on some boards are multiplexed with USB, check board pin assignment
Serial UART Communication Experiment
SPI Bus Communication Experiment

COMPANY

  • About us
  • Team Style
  • Blog

LEARNING & EXPLORATION

  • Tutorials
  • Edge AI hardware
  • Educational robots
  • Embedded intelligent hardware

WHERE TO BUY

  • Yahboom Online Store
  • Amazon
  • Alibaba
  • AliExpress
  • Mercado

TECHNOLOGY SUPPORT

  • Email: support@yahboom.com

BE A DISTRIBUTOR

  • Email: sales@yahboom.com
QR Code

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文