STM32 기반 SSD1306 OLED 드라이버 구현: 하드웨어 제어에서 그래픽 렌더링까지

I2C 프로토콜과 SSD1306 제어를 통한 임베디드 디스플레이 시스템 설계

OLED 디스플레이는 고대비, 얇은 두께, 낮은 전력 소모 등의 장점으로 인해 IoT 장치, 웨어러블 기기, 산업용 HMI 등 다양한 분야에서 널리 사용되고 있다. 특히 SSD1306 칩셋을 탑재한 128x64 해상도의 모노크롬 OLED는 STM32 계열 마이크로컨트롤러와의 호환성이 뛰어나며, 별도의 그래픽 컨트롤러 없이도 소프트웨어만으로 완전한 디스플레이 엔진을 구축할 수 있다. 본 문서에서는 GPIO를 이용한 소프트웨어 I2C 구현부터 시작하여, SSD1306 초기화 및 픽셀 제어에 이르는 전체 과정을 설명한다.

SSD1306 제어 레지스터 심층 분석

SSD1306은 명령어 기반의 드라이버 IC로, 모든 동작은 명령어(Command)와 데이터(Data)를 I2C를 통해 전송함으로써 제어된다. 각 명령어는 1바이트의 헥사코드로 구성되며, 특정 기능을 수행한다.

주요 제어 명령어:

  • 0xAE / 0xAF: 디스플레이 비활성화/활성화
  • 0xA6 / 0xA7: 정상 표시(0=검정, 1=흰색) / 반전 표시
  • 0xA8: 멀티플렉스 비율 설정 (예: 0x3F = 64라인)
  • 0xD3: 디스플레이 오프셋 설정
  • 0x8D: 내부 충전 펌프 활성화 (0x14: ON, 0x10: OFF)

충전 펌프를 반드시 활성화해야만 OLED 패널에 충분한 전압이 공급되어 발광이 가능하다. 이는 초기화 절차에서 빠져서는 안 되는 핵심 단계이다.

소프트웨어 기반 I2C 구현

하드웨어 I2C 모듈이 없거나, 핀 배치의 유연성이 필요할 경우, GPIO를 이용한 비동기식 I2C 소프트웨어 구현이 유리하다. 아래는 STM32F103 기준의 소프트 I2C 핵심 함수 예시이다.

#include "stm32f10x.h"

// SCL 및 SDA 핀 정의 (PB6: SCL, PB7: SDA)
#define OLED_I2C_SCL_HIGH()   GPIOB->BSRR = GPIO_Pin_6
#define OLED_I2C_SCL_LOW()    GPIOB->BRR  = GPIO_Pin_6
#define OLED_I2C_SDA_HIGH()   GPIOB->BSRR = GPIO_Pin_7
#define OLED_I2C_SDA_LOW()    GPIOB->BRR  = GPIO_Pin_7
#define OLED_I2C_SDA_READ()   ((GPIOB->IDR & GPIO_Pin_7) != 0)

static void i2c_delay(void) {
    for(volatile int i = 0; i < 5; i++);
}

static void i2c_start(void) {
    OLED_I2C_SDA_HIGH(); OLED_I2C_SCL_HIGH();
    i2c_delay();
    OLED_I2C_SDA_LOW();  // SDA 하강 에지 (Start Condition)
    i2c_delay();
    OLED_I2C_SCL_LOW();
}

static void i2c_stop(void) {
    OLED_I2C_SDA_LOW();  OLED_I2C_SCL_HIGH();
    i2c_delay();
    OLED_I2C_SDA_HIGH(); // SDA 상승 에지 (Stop Condition)
    i2c_delay();
}

static void i2c_write_byte(uint8_t byte) {
    for(int i = 0; i < 8; i++) {
        if(byte & 0x80) OLED_I2C_SDA_HIGH();
        else            OLED_I2C_SDA_LOW();
        i2c_delay();
        OLED_I2C_SCL_HIGH();
        i2c_delay();
        OLED_I2C_SCL_LOW();
        byte <<= 1;
    }
    // ACK 읽기 무시
    OLED_I2C_SDA_HIGH();
    i2c_delay();
    OLED_I2C_SCL_HIGH(); i2c_delay();
    OLED_I2C_SCL_LOW();
}

OLED 초기화 프로시저

다음은 128x64 해상도 OLED를 초기화하는 함수이다. 순서가 매우 중요하며, 일부 명령은 다른 설정 후에 적용되어야 한다.
void OLED_DisplayInit(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef gpio;
    gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    gpio.GPIO_Mode = GPIO_Mode_Out_OD;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &gpio);

    Delay_ms(100);  // 전원 안정화 대기

    i2c_start();
    i2c_write_byte(0x78);  // SSD1306 I2C 쓰기 주소
    i2c_write_byte(0xAE);  // 디스플레이 끄기
    i2c_write_byte(0xD5);  i2c_write_byte(0x80);  // 클록 설정
    i2c_write_byte(0xA8);  i2c_write_byte(0x3F);  // MUX ratio = 63 (64라인)
    i2c_write_byte(0xD3);  i2c_write_byte(0x00);  // 오프셋 없음
    i2c_write_byte(0x40);  // 시작 행 0
    i2c_write_byte(0x8D);  i2c_write_byte(0x14);  // 충전 펌프 활성화
    i2c_write_byte(0x20);  i2c_write_byte(0x00);  // 메모리 어드레싱 모드: 가로
    i2c_write_byte(0xA1);  // 세그먼트 리매핑 (왼쪽 시작)
    i2c_write_byte(0xC8);  // COM 스캔 방향 역순
    i2c_write_byte(0xDA);  i2c_write_byte(0x12);  // COM 핀 구성
    i2c_write_byte(0x81);  i2c_write_byte(0xCF);  // 대비도 조절
    i2c_write_byte(0xD9);  i2c_write_byte(0xF1);  // 프리차지 설정
    i2c_write_byte(0xDB);  i2c_write_byte(0x40);  // VCOMH 설정
    i2c_write_byte(0xA4);  // RAM 내용에 따라 표시
    i2c_write_byte(0xA6);  // 정상 색상 모드
    i2c_write_byte(0xAF);  // 디스플레이 켜기
    i2c_stop();
}

그래픽 버퍼 관리 및 픽셀 조작

OLED은 페이지 기반 아키텍처를 사용한다. 128x64 크기의 경우 8개의 페이지(Page 0~7)와 각 페이지당 128바이트(각 바이트 = 8픽셀 세로)로 구성된다. 전체 화면을 위한 버퍼 선언 예:
uint8_t oled_buffer[128 * 8];  // 1024바이트 전체 프레임 버퍼

void OLED_SetPixel(int x, int y, uint8_t color) {
    if(x < 0 || x >= 128 || y < 0 || y >= 64) return;
    
    int page = y / 8;
    int index = x + page * 128;
    
    if(color)
        oled_buffer[index] |= (1 << (y % 8));
    else
        oled_buffer[index] &= ~(1 << (y % 8));
}

void OLED_Refresh(void) {
    for(int page = 0; page < 8; page++) {
        i2c_start();
        i2c_write_byte(0x78);
        i2c_write_byte(0xB0 + page);           // 페이지 주소 설정
        i2c_write_byte(0x00);                  // 열 저주소
        i2c_write_byte(0x10);                  // 열 고주소
        i2c_start();
        i2c_write_byte(0x7A);                  // 데이터 모드 진입
        for(int i = 0; i < 128; i++) {
            i2c_write_byte(oled_buffer[page*128 + i]);
        }
        i2c_stop();
    }
}
이러한 저수준 제어를 기반으로 문자 출력, 선 그리기, 이미지 렌더링 등의 고수준 함수를 구현할 수 있으며, 최종적으로 사용자 정의 GUI 엔진 개발이 가능하다.

태그: STM32 SSD1306 OLED I2C embedded systems

7월 11일 22:39에 게시됨