본문 바로가기

STM32

U8G2 그래픽 라이브러리를 STM32로 포팅 2부: SSD1306 OLED 디스플레이

반응형

 

이 부분에서는 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

반응형