i2c-oled.c
The SSD1306 I2C protocol boils down to two control bytes: 0x00 for a command, 0x40 for data. This program sends the init sequence then a test image, with no library — the best way to understand master I2C.
Hardware
A Nucleo H7 board (STM32H743ZI, Cortex-M7 @ 480 MHz) programmed via ST-LINK (USB), 3.3 V pins. 1 SSD1306 I2C OLED module (0x3C), 3.3 V supply.
Wiring
SDA to PB7, SCL to PB6 (typical I2C1 pins on Nucleo boards, confirm in the .ioc), VCC 3.3 V, GND.
How it works
Each I2C frame starts with address byte 0x78 then the control byte. The sequence sets the charge pump and scan; 0xAF turns the panel on, then the 1024-byte frame fills the GDDRAM.
Running it
STM32CubeIDE project based on the Nucleo-H743ZI: drop the code into main.c, declare peripherals in the .ioc, then Run over ST-LINK. The ST-LINK serial monitor shows printf at 115200.
Variants
Add a 5x7 font to print strings (printf → bitmap), or switch to I2C DMA to free the CPU during refresh.
i2c-oled.c
// OLED SSD1306 (0x3C) : envoi de commandes/data en I2C HAL.
#include "main.h"
#include <string.h>
extern I2C_HandleTypeDef hi2c1;
#define SSD1306_ADDR 0x78 // 0x3C << 1 (format HAL)
static void cmd(uint8_t c) {
uint8_t d[2] = { 0x00, c }; // 0x00 = byte de commande
HAL_I2C_Master_Transmit(&hi2c1, SSD1306_ADDR, d, 2, 10);
}
static void data(const uint8_t *p, uint16_t n) {
uint8_t buf[129];
buf[0] = 0x40; // 0x40 = byte de donnees
memcpy(buf + 1, p, n);
HAL_I2C_Master_Transmit(&hi2c1, SSD1306_ADDR, buf, n + 1, 100);
}
static void init(void) {
const uint8_t seq[] = { 0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00,
0x40, 0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8,
0xDA, 0x12, 0x81, 0xFF, 0xD9, 0xF1, 0xDB,
0x40, 0xA4, 0xA6, 0xAF }; // AF = on
for (unsigned i = 0; i < sizeof(seq); i++) cmd(seq[i]);
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init(); // SCL/PB6, SDA/PB7 dans le .ioc (400 kHz)
init();
uint8_t trame[1024];
memset(trame, 0xAA, sizeof(trame)); // damier de test 128x64
data(trame, sizeof(trame));
for (;;);
}