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 엔진 개발이 가능하다.