Overview
The SSD1306 is an OLED display driver chip (OLED = organic light-emitting diode display, a self-emitting screen — every pixel glows on its own, no backlight needed; a pixel = the smallest "cell" of the screen); most common 0.96-inch OLED modules use it. A screen is essentially a dot matrix (a pattern made of little cells), and this tutorial drives a 128×64-resolution OLED screen over the I2C bus (a two-wire serial communication: SCL clock line sets the rhythm + SDA data line carries the content), displaying one line of date and time.
In plain words: an OLED screen is like a "mini TV", except each "pixel" (smallest glowing cell) lights up by itself — no backlight needed like old LCD screens. The I2C bus is like "two people on the phone": SCL is the rhythm of the speech, SDA is the content. The screen has a "door number" on the bus (I2C address 0x3C), and the program sends what to display to that door number — the screen draws the picture. This tutorial makes the screen display a hardcoded date and time.
This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version
release_bl_iot_sdk_1.6.40) exampleapplications/iot-solution/demo_ssd1306; the code can be found directly in the local SDK.
Wire per the official example (I2C address 0x3C, the 7-bit address on the datasheet):
| Ai-WB2 Pin | SSD1306 Module |
|---|---|
| IO12 | SCL (clock line) |
| IO3 | SDA (data line) |
| 3V3 | VCC |
| GND | GND |
💡 Common 0.96-inch OLED modules have 4 pins (VCC / GND / SCL / SDA) — just hook each up with a dupont wire (jumper wire with pins at both ends). The I2C address = the device’s “door number” on the bus; the program finds this screen by
0x3C, so before wiring, confirm your module’s address is0x3C(printed on the back silk screen or in the seller’s docs).
This tutorial directly uses the demo_ssd1306 example project shipped with the official SDK; open a terminal and enter it:
cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_ssd1306
Note:
cdis the “change directory” command — entering the demo_ssd1306 project directory; all subsequentmakebuild andmake flashflash commands must run in this directory first.
📌 This is a multi-file project: there’s another
demo_ssd1306/subdirectory under the project root, and all source lives inside it (main.c+ the screen driverssd1306_drive.c/h). Keeping the driver in its own file pays off later: to reuse this screen, just copy the driver files away.
Project structure:
| File | Purpose |
|---|---|
demo_ssd1306/main.c |
Main program source, the main file this tutorial looks at |
demo_ssd1306/ssd1306_drive.c / .h |
SSD1306 screen driver (self-developed), wraps I2C init, display char/number and other interfaces |
Makefile |
Build entry, usually no changes needed |
Open demo_ssd1306/demo_ssd1306/main.c — the complete code for this step has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/iot-solution/demo_ssd1306/demo_ssd1306/main.c).
Code highlights:
| Code | Purpose |
|---|---|
oled_i2c_driver_init(12, 3) |
Initializes I2C on IO12/IO3 and connects to the screen at address 0x3C; uninitialized, the screen never receives data |
oled_time_output(2025, 4, 2, 12, 34, 56) |
Draws the date and time onto the screen’s dot matrix (year/month/day/hour/minute/second); wrong parameter order = garbled display |
vTaskDelay(portTICK_RATE_MS * 1000) |
Refreshes the display every 1 second; without it the time draws once at power-on and never changes |
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program file) the board can run;-j8builds with 8 parallel CPU cores, faster.
On success a firmware build_out/demo_ssd1306.bin is generated.
⚠️ If it reports
riscv64-unknown-elf-gcc: command not found, the toolchain permissions aren’t configured — runcd toolchain/riscv/Linux && . chmod755.shfirst, then rebuild.
Keep the board connected via USB, confirm the serial device (usually /dev/ttyUSB0 on Linux), and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board’s chip. Afterp=comes the serial device (change it to your computer’s actual one — check withls /dev/ttyUSB*),b=is the flash baud rate (transfer speed).
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode (some boards enter automatically); wait for the progress bar to complete — that means the flash succeeded.
After flashing, the board automatically restarts and runs; watch the OLED screen:
The screen should show “Time”, then 2025/4/2 on the second line, then 12:34:56 on the third line (the time refreshes every second).
💡 The screen shows a hardcoded date and time from the example code: to show your own time, edit the 6 arguments of
oled_time_output(2025, 4, 2, 12, 34, 56)inmain.c(order: year, month, day, hour, minute, second), then rebuild and reflash. To light a single pixel, calloled_drive_set_pixels(x, y, color)from the driver (seessd1306_drive.h).
Seeing the screen display the three lines “Time / year-month-day / hour:minute:second” normally means success; if the screen is completely dark (blank) or only shows part of the content, it hasn’t succeeded yet — check the FAQ at the end.
API Summary for This Tutorial
oled_i2c_driver_init(oled_scl, oled_sda)
Initializes the I2C controller and connects the SSD1306 screen (self-developed driver interface, source in ssd1306_drive.c). Internally initializes I2C in master mode with the 7-bit address 0x3C at 400KHz, and sends the screen a full set of init commands (display on, set contrast, etc.).
Parameters:
oled_scl: I2C clock pin number, this tutorial passes12(IO12)oled_sda: I2C data pin number, this tutorial passes3(IO3)
Return: hosal_i2c_dev_t* I2C device handle; NULL on init failure
oled_time_output(yyyy, MM, dd, HH, mm, ss)
Draws the date and time onto the screen in three lines "Time / year-month-day / hour:minute:second" (self-developed driver interface, source in ssd1306_drive.c). Internally calls oled_output_char_num and similar to split each digit into 8×16 dot-matrix glyphs (dot matrix = a pattern made of cells), then calls oled_refresh_screen to refresh the screen.
Parameters:
yyyy: year, e.g.2025MM: month,1~12dd: day,1~31HH: hour,0~23mm: minute,0~59ss: second,0~59
Return: 0 on success
vTaskDelay(ms)
Suspends the current task for the given milliseconds, yielding the CPU to other tasks (FreeRTOS system API).
Parameters:
ms: delay in milliseconds, values: any non-negative integer (this tutorial usesportTICK_RATE_MS * 1000for 1 second)
Return: none
Full Code
Below is the complete demo_ssd1306/demo_ssd1306/main.c source, identical to the official example (applications/iot-solution/demo_ssd1306/demo_ssd1306/main.c):
📜 Click to expand the full demo_ssd1306/demo_ssd1306/main.c code
#include <stdio.h>
#include <FreeRTOS.h>
#include <task.h>
#include <hosal_i2c.h>
#include <bl_gpio.h>
#include <blog.h>
#include "ssd1306_drive.h"
int main(void)
{
oled_i2c_driver_init(12, 3);
for (;;) {
oled_time_output(2025, 4, 2, 12, 34, 56);
vTaskDelay(portTICK_RATE_MS * 1000);
}
return 0;
}FAQ & Troubleshooting
⚠️ Screen completely dark (blank)
Cause: SCL/SDA swapped, the I2C address isn't 0x3C, or insufficient module power
Fix: check the wiring IO12=SCL, IO3=SDA (swapped = guaranteed blank); 0x3C is the 7-bit address notation — some modules print 0x78 (the 8-bit notation, the same address), confirm the module's silk screen
⚠️ Screen lights but shows garbage or flickers
Cause: poor dupont wire contact, wires too long, or unstable 3V3 supply
Fix: re-seat all dupont wires, shorten the wires; confirm the module's VCC is on 3V3 (5V may burn the module)
⚠️ Build reports No such file or directory (main.c not found)
Cause: this is a multi-file project — the source lives in the second-level directory demo_ssd1306/demo_ssd1306/
Fix: run the build command at the project root ~/Ai-Thinker-WB2/applications/iot-solution/demo_ssd1306; make finds the source automatically — don't cd into the second-level directory yourself
⚠️ Flashing reports cannot open the serial port
Cause: wrong serial device or insufficient permission
Fix: confirm the device with ls /dev/ttyUSB*; if permission denied run sudo usermod -aG dialout $USER and log back in
⚠️ Flashing keeps waiting, progress bar doesn't move
Cause: download mode wasn't entered, or the cable only charges and can't transfer data
Fix: press and hold EN during flashing to enter download mode as prompted; try a Type-C data-capable cable
Self-Check
The OLED screen displays the three lines "Time / 2025/4/2 / 12:34:56" normally with the time refreshing every second — the SSD1306 driver is verified.

