이 부분에서는 U8g2 라이브러리를 사용하여 널리 사용되는 SSD1306 OLED 디스플레이를 STM32와 연결해 보겠습니다. U8g2를 통해 디스플레이를 초기화하고 텍스트와 그래픽을 렌더링 하는 방법을 살펴보겠습니다.
이 가이드에서는 다음 내용을 다룹니다.
이전 튜토리얼 참고 U8G2 그래픽 라이브러리를 STM32로 포팅하기 1부: 소개
- 프로젝트에 U2G8 라이브러리를 추가합니다.
- 지연, GPIO 및 통신 콜백을 개발합니다.
- 펌웨어 개발.
- 결과.

5. 프로젝트에 U8G2 라이브러리 추가:
Github 저장소에서 라이브러리를 다운로드한 후 압축을 풉니다.
추출이 완료되면 다음과 같이 csrc 폴더를 driver 폴더로 복사합니다.

다음으로, 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 속성을 클릭합니다.

다음으로, 프로젝트 속성 -> C/C++ 빌드 -> 설정 -> GCC 컴파일러 -> 포함 경로를 열고 추가 버튼을 클릭하여 여기에 폴더 경로를 추가합니다.

다음과 같이 경로를 추가하고 적용 및 닫기를 클릭합니다.

이제 라이브러리가 추가되어 아무 문제 없이 컴파일할 수 있습니다.
6. 지연, GPIO 및 통신 콜백 함수 개발:
"uC 특정" GPIO 및 지연 콜백
GPIO 및 지연 콜백은 GPIO 및 지연과 관련된 모든 메시지를 처리합니다.
지연 메시지에는 디스플레이에 필요한 지연(대부분 밀리초)이나 소프트웨어 SPI 또는 I2C가 구현된 경우 SPI 또는 I2C 클록을 생성하는 데 필요한 지연(대부분 나노 또는 마이크로)이 포함될 수 있습니다.
GPIO 메시지 에는 디스플레이 인터페이스에 사용되는 GPIO 핀의 설정 및 재설정이 포함됩니다. 이러한 GPIO 핀에는 데이터 핀 (병렬 모드에서 D0, D1, D2 등)이나 제어 핀 (SPI 모드에서 CS, RST, DC)이 포함될 수 있습니다.
GPIO 및 지연 콜백 함수는 다음과 같습니다.
uint8_t u8x8_gpio_and_delay_template(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
switch(msg)
{
case U8X8_MSG_GPIO_AND_DELAY_INIT: // called once during init phase of u8g2/u8x8
break; // can be used to setup pins
case U8X8_MSG_DELAY_NANO: // delay arg_int * 1 nano second
break;
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
break;
case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
break;
case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
break;
case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
break; // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
case U8X8_MSG_GPIO_D0: // D0 or SPI clock pin: Output level in arg_int
//case U8X8_MSG_GPIO_SPI_CLOCK:
break;
case U8X8_MSG_GPIO_D1: // D1 or SPI data pin: Output level in arg_int
//case U8X8_MSG_GPIO_SPI_DATA:
break;
case U8X8_MSG_GPIO_D2: // D2 pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_D3: // D3 pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_D4: // D4 pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_D5: // D5 pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_D6: // D6 pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_D7: // D7 pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_E: // E/WR pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_CS: // CS (chip select) pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_DC: // DC (data/cmd, A0, register select) pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_RESET: // Reset pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_CS1: // CS1 (chip select) pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_CS2: // CS2 (chip select) pin: Output level in arg_int
break;
case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
break; // arg_int=1: Input dir with pullup high for I2C clock pin
case U8X8_MSG_GPIO_I2C_DATA: // arg_int=0: Output low at I2C data pin
break; // arg_int=1: Input dir with pullup high for I2C data pin
case U8X8_MSG_GPIO_MENU_SELECT:
u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_NEXT:
u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_PREV:
u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_HOME:
u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}
하드웨어 i2c를 사용하고 올바르게 구성했기 때문에 다음과 같이 지연 함수만 포트에 연결하면 됩니다.
먼저, 다음과 같이 u8g2 라이브러리를 포함시킵니다.
#include "u8g2.h"
다음으로, u8g2 구조체를 다음과 같이 선언합니다.
u8g2_t myDisplay ;
다음과 같이 지연 및 gpio 함수를 선언합니다.
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
switch(msg)
{
case U8X8_MSG_DELAY_MILLI:
HAL_Delay(arg_int);
break;
}
return 1;
}
i2c는 하드웨어 수준이기 때문에 지연 기능에만 관심이 있습니다.
함수 인수
u8x8_t *u8x8
- U8x8 구조체(라이브러리의 내부 상태)를 가리키는 포인터입니다.
- 고급 작업을 하지 않는 한 이것을 직접 사용할 필요는 없습니다.
uint8_t msg
U8g2가 보낸 메시지 코드로, 함수에 수행할 작업을 알려줍니다.
메시지의 예:
U8X8_MSG_GPIO_AND_DELAY_INIT → GPIO와 타이머를 초기화합니다.
U8X8_MSG_DELAY_MILLI → 지연 arg_int 시간(밀리초).
U8X8_MSG_DELAY_10MICRO → 지연 arg_int * 10 µs.
U8X8_MSG_GPIO_CS → 칩 선택(CS) 핀을 높음/낮음으로 설정합니다.
U8X8_MSG_GPIO_DC → 데이터/명령(DC) 핀을 설정합니다.
U8X8_MSG_GPIO_RESET → 리셋(RES) 핀을 설정합니다.
uint8_t arg_int
메시지와 함께 전달되는 정수 매개변수입니다.
사용법은 다음에 따라 달라집니다 msg:
지연 메시지의 경우 → 밀리초 또는 10µs 단위.
GPIO 메시지의 경우 → 값 0 (낮음) 또는 1 (높음).
void *arg_ptr
포인터 인수(GPIO/지연에는 거의 사용되지 않음).
주로 바이트 콜백에서 SPI 데이터 전송과 같은 것에 사용되며 여기서는 사용되지 않습니다.
GPIO/지연 핸들러의 경우 일반적으로 이를 무시합니다.
커뮤니케이션 콜백
통신 콜백은 디스플레이와의 통신 중에 사용되는 메시지를 처리하는 데 사용됩니다. 이러한 메시지에는 전송 시작, 바이트 전송, 전송 종료 등이 포함됩니다.
uint8_t u8x8_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
static uint8_t buffer[32]; /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
static uint8_t buf_idx;
uint8_t *data;
switch(msg)
{
case U8X8_MSG_BYTE_SEND:
data = (uint8_t *)arg_ptr;
while( arg_int > 0 )
{
buffer[buf_idx++] = *data;
data++;
arg_int--;
}
break;
case U8X8_MSG_BYTE_START_TRANSFER:
buf_idx = 0;
break;
case U8X8_MSG_BYTE_END_TRANSFER:
#define OLED_Addr (0x3D<<1)
HAL_I2C_Master_Transmit(&hi2c1, OLED_Addr, buffer, buf_idx, 100);
break;
default:
return 0;
}
return 1;
}
인수:
이는 I²C 바이트 핸들러 콜백입니다.
U8g2는 이를 사용하여 I²C를 통해 디스플레이로 데이터를 전송합니다.
- u8x8 → U8x8 객체(라이브러리 컨텍스트)를 가리키는 포인터입니다. 여기서는 사용되지 않습니다.
- msg → U8g2의 메시지 코드(어떤 작업을 해야 하는지).
- arg_int → 정수 값입니다. 메시지에 따라 다릅니다.
- arg_ptr → 포인터. 종종 전송할 데이터 바이트를 가리킨다.
함수 내부
로컬 정적 변수:
U8g2는 한 번에 32바이트 이상을 (START와 END 사이) 보내도록 요청하지 않을 것을 보장합니다.
이렇게 하면 버퍼가 항상 충분히 크게 유지됩니다.
static uint8_t buffer[32]; // Local buffer for I2C data
static uint8_t buf_idx; // Current write index
다양한 메시지 처리
1. U8X8_MSG_BYTE_SEND
data = (uint8_t *)arg_ptr;
while( arg_int > 0 )
{
buffer[buf_idx++] = *data;
data++;
arg_int--;
}
arg_ptr U8g2가 보내려는 데이터 덩어리(1바이트 이상)를 가리킵니다.
arg_int 바이트 수를 알려줍니다.
코드는 해당 바이트를 로컬에 복사하여 buffer증가시킵니다 buf_idx.
아직 아무것도 전송되지 않았습니다. 단지 저장만 했을 뿐입니다.
2. U8X8_MSG_BYTE_시작_전송
buf_idx = 0 ;
I²C 전송의 시작을 표시합니다.
버퍼 인덱스를 재설정하여 데이터가 처음부터 채워지도록 합니다.
3. U8X8_MSG_BYTE_END_TRANSFER
#define OLED_Addr (0x3D<<1)
HAL_I2C_Master_Transmit(&hi2c1, OLED_Addr, buffer, buf_idx, 100);
I²C 전송의 끝을 표시합니다.
이제 모든 것을 buffer 한 번의 I²C 전송으로 OLED로 전송합니다.
OLED_Addr = 장치 주소 ( 0x3D 1만큼 왼쪽으로 이동 → 8비트 I²C 주소) (적절하게 변경)
HAL 함수를 사용합니다: HAL_I2C_Master_Transmit(&hi2c1, OLED_Addr, buffer, buf_idx, 100); 여기서
&hi2c1 → I²C 핸들(CubeMX에서 구성됨).
OLED_Addr → SSD1306의 I²C 주소.
buffer → 수집된 데이터를 가리키는 포인터.
buf_idx → 보낼 바이트 수.
100 → 시간 초과(ms)
4. 기본값
return 0;
인식 msg 되지 않으면 0(처리되지 않음)을 반환합니다.
반환 값
문제가 해결되면 항상 반환하세요 1.
흐름 요약
- START_TRANSFER → 버퍼 인덱스를 재설정합니다.
- BYTE_SEND → 들어오는 바이트를 버퍼에 복사합니다.
- END_TRANSFER → 모든 것을 다음을 통해 보냅니다 HAL_I2C_Master_Transmit.
따라서 이 함수는 U8g2와 STM32의 HAL I²C 드라이버를 연결하는 다리 역할을 합니다. 디스플레이 명령을 버퍼에 일괄 처리하여 한 번의 I²C 트랜잭션으로 효율적으로 전송합니다.
이것이 I2C 디스플레이 포팅에 대한 모든 내용입니다.
7. 펌웨어 개발:
사용자 코드 begin 2의 메인 함수에서 다음과 같이 디스플레이를 버스에 연결합니다.
u8g2_Setup_ssd1306_i2c_128x64_noname_f ( & myDisplay , U8G2_R0 , u8x8_i2c , u8x8_gpio_and_delay );
다음으로, 다음과 같이 디스플레이를 초기화합니다.
u8g2_InitDisplay ( & myDisplay ); // 디스플레이에 init 시퀀스를 전송하고, 이후 디스플레이는 절전 모드가 됩니다.
초기화 후에는 디스플레이가 절전 모드로 전환되므로 절전 모드를 종료하세요.
u8g2_SetPowerSave ( & myDisplay , 0 ); // 디스플레이 깨우기
다음으로, 디스플레이에 쓰기를 시작하겠습니다.
먼저, 다음과 같이 디스플레이를 지웁니다.
u8g2_ClearDisplay ( & myDisplay );
글꼴 유형 설정:
u8g2_SetFont ( & myDisplay , u8g2_font_ncenB14_tr );
다음과 같이 문자열을 표시합니다.
u8g2_DrawStr ( & myDisplay , 0 , 15 , "안녕하세요 세상" );
다음과 같이 모양(원)을 그립니다.
u8g2_DrawCircle ( & myDisplay , 60 , 30 , 10 , U8G2_DRAW_ALL );
다음과 같이 새로운 콘텐츠를 디스플레이에 보냅니다.
u8g2_SendBuffer ( & myDisplay );
펌웨어는 여기까지입니다. 프로젝트를 저장하고 다음과 같이 MCU에서 실행해 보세요.

8. 결과:
프로젝트를 실행하면 화면에 다음이 표시됩니다.
우리는 U8G2를 우리 프로젝트에 성공적으로 통합하여 디스플레이에 텍스트와 모양을 표시했습니다.
다음으로, 다양한 유형의 디스플레이에 대해 살펴보겠습니다. 계속 지켜봐 주세요.
즐거운 코딩되세요. 😉
튜토리얼 출처 참고 https://blog.embeddedexpert.io/?p=3700
'STM32' 카테고리의 다른 글
| stm32cube와 ST-Link V2 연결 케이블 (0) | 2025.10.29 |
|---|---|
| STM32 U8G2 그래픽 라이브러리 사용하는 방법 (0) | 2025.09.14 |
| STM32 MAX485 사용한 RS485 통신 (3) | 2025.08.27 |
| STM32 펄스 카운터 사용 (2) | 2025.08.27 |
| STM32 타이머 애플리케이션: PWM 입력 모드 (1) | 2025.08.25 |
| 새로운 AI 가속 STM32N6 시작하기 (1) | 2025.08.22 |
| STM32 중급 과정 ADC 개요 8 (0) | 2025.08.22 |
| STM32 중급 과정 Peripheral Hands-On: I2C 7-1 (0) | 2025.08.21 |
취업, 창업의 막막함, 외주 관리, 제품 부재!
당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약,
아이디어는 있지만 구현할 기술이 없는 막막함.
우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.
이제 고민을 멈추고, 캐어랩을 만나세요!
코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.
제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!
귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.
캐어랩