移植好U8g2图形库的STM32F407标准库工程模板,0.96寸OLED驱动程序

U8g2 그래픽 라이브러리를 이식한 STM32F407 표준 라이브러리 프로젝트 템플릿, 0.96인치 OLED 디스플레이(SSD1306) 사용, 하드웨어 IIC로 구동.

이식하는 데 한 밤을 꼬박 새웠습니다. 개발 보드 메인 MCU는 STM32F407VET6이며, I2C 인터페이스는 I2C1, SCL은 PB6, SDA는 PB7에 연결되어 있습니다.

임베디드 관련 글: https://blog.zeruns.com/category/IOT/

전자/회로 관련 글: https://blog.zeruns.com/category/electrical/

전자/마이크로컨트롤러 기술 단톡: 2169025065

U8g2 소개

U8g2 그래픽 라이브러리는 임베디드 장치용 흑백 그래픽 라이브러리로, SSD1306, ST7920 등 다양한 흑백 OLED 및 LCD 디스플레이 컨트롤러를 지원합니다. U8g2 라이브러리는 Arduino IDE의 라이브러리 관리자에서 설치하거나 STM32 등의 플랫폼으로 이식할 수 있습니다. U8g2 라이브러리는 전체 화면 캐시 모드, 페이지 캐시 모드, U8x8 문자 모드의 세 가지 드로잉 모드를 지원합니다. U8g2 라이브러리 사용 시 적절한 생성자를 선택하고, 디스플레이를 초기화하며, 핀 번호를 설정하고, 콜백 함수와 드로잉 명령을 작성해야 합니다.

U8g2 그래픽 라이브러리의 장점은 다양한 글꼴을 사용할 수 있고, 중국어 표시를 지원하며, 선, 사각형, 원, 비트맵 등 풍부한 그래픽 기능을 제공한다는 것입니다. 단점은 일정한 메모리 공간을 차지하고, 속도가 느리며, 컨트롤러가 없는 디스플레이는 지원하지 않는다는 점입니다. U8g2 그래픽 라이브러리의 활용 예로는 센서 데이터 표시, 시계 제작, 메뉴 표시, 애니메이션 표시 등이 있습니다. U8g2 그래픽 라이브러리는 강력한 기능, 우수한 호환성, 쉬운 사용성을 갖춘 흑백 그래픽 라이브러리입니다.

실행 사진


데모 영상: https://www.bilibili.com/video/BV17W4y197WW/

크리스탈 발진기 관련 문제

저는 8MHz 크리스탈 발진기를 사용하고 있으며, 클럭 트리 설정에서 PLL 파라미터를 수정하여 MCU의 메인 클럭을 168MHz로 맞췄습니다. 다른 주파수의 크리스탈 발진기를 사용할 경우 파라미터를 수정해야 하며, 구체적인 수정 방법은 구글링하시면 됩니다.

수정할 부분은 다음과 같습니다:

stm32f4xx.h 파일의 137번째 줄.

#define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
// 여기의 8000000을 자신의 크리스탈 발진기 주파수로 바꾸세요, 단위는 Hz

system_stm32f4xx.c 파일의 364번째 줄과 394번째 줄.

#if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F401xx) || defined(STM32F469_479xx)
 /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
 #define PLL_M      4
// 여기의 4는 아래 클럭 트리 사진의 /M에 해당하는 /4입니다.
#if defined (STM32F40_41xxx)
#define PLL_N      168
// 여기의 168은 아래 클럭 트리 사진의 *N에 해당하는 x168입니다.
```![](upload://nfU4Qva05kQFBqJ1LAyXx0Og3KQ.png)

## 부품 구매 링크

- 0.96인치 OLED 디스플레이: [https://u.jd.com/1zjpDNC](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BARAJK1olXwQCXVheC0IUBV8IGloTXw4AU1hVDkweBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksWAmkKE1kSWw4EU1dbFxJSXzI4WzB2K1NjPSw9VzFBQBpJTg5iPBhWElJROEonA24JGloSWgAAXG5tCEwnQgEIGV4SXwcFVW5cOEsQCmgNHV0UWg4EUVttD0seM20IGFgQWgQLUUJUDEoUAGY4K2sWbQECXUpbegpFF2l6K2sVbQUyVF9dAEgXA2cOGFMJXQMGXF9cFEsQCmgNHV0UXAEFVlttCkoWB2Y4K2tVGQViISotXEpUR2xIeSNWKGJREh4DXxF5AS1dSD0SFQNXVQoHfghLSm8LKw)
- STM32F407 개발 보드: [https://s.click.taobao.com/7AtnHFu](https://s.click.taobao.com/t?e=m%3D2%26s%3DczQqv026Zjtw4vFB6t2Z2ueEDrYVVa64LKpWJ%2Bin0XLjf2vlNIV67lRwohxEizvIxa9spvDO8Cl%2FmzaS0s2vXCsmIF0UHtujBkxkgN7jKg%2BiJaQjFxYBevWfPAv4x3d%2FOlGkMkr%2BSUUYmUGuVZn0uRDBbf95h6seTxpmwkkouzqi1jMNxDhLMnotgd7NXRy%2F3OppJwt5ethtXyEYK9MgsI1cD97yFV3TmSpANgNSVXSPM7MeivsNdkAXdMlWzMNyHk3i9xXmKdKMJi1gYBJvNFSsXCNd9EoxE59iYTGkDbWFi4V5KBpmMoBZLSyIfgF7gR2l5BVRVVbGDmntuH4VtA%3D%3D&union_lens=lensId%3APUB%401686477136%40210562f5_0a55_188a9dee240_346f%4001%40eyJmbG9vcklkIjo2MTM1NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIn0ie)
- 점퍼 와이어: [https://u.jd.com/1zjFP0Z](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAQcJK1olVQAKVlZZCUoXM2oJElkcXwUHXVtYOA9IWzFXKwJQGEdAX0BDUA5DX3BTTkRHA1ocUV9UCkIVAGoBHl4KBENeCW4NDA9CAGcLXjlHWnBqEycFACNOCxZoF1clXDYCVV9cCUwQBW0AK2sVWjZDOllYAE0XAF8JK1sSVAEHUldZCU4fBWo4HFscbQQCV11YD0keBnMBH1oWXg8yZG5eOEwXCnsOaRpHSQBwZG5dOEgnA24IE1gQXwYHV1lBCEkWAW8IB1sSVAEHUlhdAUweCmY4GVoUWQ8yZG4YXDFkd2tXEjhHCXxLB1wAXzsVAmxfRAN7X19ZVgg8VhZMYB94fChoFEVmZA)

## 코드

전체 프로젝트 파일 다운로드: [https://url.zeruns.com/JUoKJ](https://url.zeruns.com/JUoKJ) 압축 해제 코드: t6wt

일부 코드:

**main.c**

```C
#include "stm32f4xx.h"
// #include "Timer.h"
#include "Delay.h"
#include "u8g2.h"
#include "OLED.h"
#include "IWDG.h"

uint8_t u8x8_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr);
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
void u8g2_Init(u8g2_t *u8g2);
void draw(u8g2_t *u8g2);

int main(void)
{
      uint8_t t = 0;
      IWDG_Configuration(); // 와치독 초기화
      OLED_I2C_Init();      // OLED 초기화
      u8g2_t u8g2;
      u8g2_Init(&u8g2); // U8g2 초기화

      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // GPIOA 외부 장치 클럭 활성화

      GPIO_InitTypeDef GPIO_InitStructure;                   // 구조체 정의
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;          // GPIO 모드를 출력으로 설정
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // GPIO 핀 6 설정
      GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;       // GPIO 속도 100MHz로 설정
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;         // 푸시-풀 출력으로 설정
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;       // 풀업/풀다운 없음
      GPIO_Init(GPIOA, &GPIO_InitStructure);                 // GPIO 초기화

      Delay_ms(100);

      u8g2_DrawLine(&u8g2, 0, 0, 127, 63); // 선 그리기, 시작 좌표(0,0), 끝 좌표(127,63)
      u8g2_SendBuffer(&u8g2);              // 버퍼 데이터 전송
      u8g2_DrawLine(&u8g2, 127, 0, 0, 63);
      u8g2_SendBuffer(&u8g2);

      Delay_ms(300);

      u8g2_ClearBuffer(&u8g2);  // 버퍼 데이터 지우기
      draw(&u8g2);
      u8g2_SendBuffer(&u8g2);
      Delay_ms(1000);
      
      u8g2_ClearBuffer(&u8g2);
      IWDG_FeedDog(); // 와치독 갱신, CPU 리셋 방지
      u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);    // 글꼴 선택
      u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");

      u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese2);
      u8g2_DrawUTF8(&u8g2, 0, 30, "H你好世界");

      u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_chinese2);
      u8g2_DrawUTF8(&u8g2, 0, 43, "H你好世界");
      
      u8g2_SetFont(&u8g2, u8g2_font_fur11_tr);
      u8g2_DrawUTF8(&u8g2, 0, 59, "blog.zeruns.com");

      u8g2_SendBuffer(&u8g2);

      Delay_ms(1300);

      while (1)
      {
            Delay_ms(100);
            u8g2_ClearBuffer(&u8g2);// 버퍼 데이터 지우기
            if (++t >= 32)
                  t = 1;
            u8g2_DrawCircle(&u8g2, 64, 32, t, U8G2_DRAW_ALL);   // 원 그리기
            u8g2_DrawCircle(&u8g2, 32, 32, t, U8G2_DRAW_ALL);
            u8g2_DrawCircle(&u8g2, 96, 32, t, U8G2_DRAW_ALL);
            u8g2_SendBuffer(&u8g2); // 버퍼 데이터 전송
            GPIO_ToggleBits(GPIOA, GPIO_Pin_6);
            IWDG_FeedDog(); // 와치독 갱신, CPU 리셋 방지
      }
}
// https://blog.zeruns.com
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
      static uint8_t buffer[32]; /* u8g2/u8x8은 START_TRANSFER와 END_TRANSFER 사이에 32바이트 이상 전송하지 않습니다 */
      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_INIT:
            /* I2C 하위 시스템 초기화를 위한 사용자 지정 코드 추가 */
            break;

      case U8X8_MSG_BYTE_START_TRANSFER:
            buf_idx = 0;
            break;

      case U8X8_MSG_BYTE_END_TRANSFER:
            HW_I2cWrite(buffer, buf_idx);   // 하드웨어 I2C 바이트 쓰기
            break;

      default:
            return 0;
      }
      return 1;
}
// https://blog.zeruns.com
uint8_t u8g2_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
{
      switch (msg)
      {
      case U8X8_MSG_GPIO_AND_DELAY_INIT:
            OLED_I2C_Init();    // 초기화
            break;

      case U8X8_MSG_DELAY_MILLI:
            Delay_ms(arg_int);  // 지연
            break;

      case U8X8_MSG_GPIO_I2C_CLOCK:
            break;

      case U8X8_MSG_GPIO_I2C_DATA:
            break;

      default:
            return 0;
      }
      return 1; // 명령이 성공적으로 처리됨
}

void u8g2_Init(u8g2_t *u8g2)
{
      // u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); // u8g2 초기화, 소프트웨어 I2C
      u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8g2_gpio_and_delay); // u8g2 초기화, 하드웨어 I2C
      u8g2_InitDisplay(u8g2);                                                                       // 선택한 칩에 따라 초기화 수행, 초기화 완료 후 디스플레이는 꺼진 상태
      u8g2_SetPowerSave(u8g2, 0);                                                                   // 디스플레이 켜기
      u8g2_SetContrast(u8g2, 88);                                                                   // 밝기 설정
      u8g2_ClearBuffer(u8g2);                                                                       // 버퍼 지우기
}
// https://blog.vpszj.cn
void draw(u8g2_t *u8g2)
{
      u8g2_SetFontMode(u8g2, 1);              /* 글꼴 모드 선택 */
      u8g2_SetFontDirection(u8g2, 0);         /* 글꼴 방향 선택 */
      u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /* 글꼴 선택 */
      u8g2_DrawStr(u8g2, 0, 20, "U");

      u8g2_SetFontDirection(u8g2, 1);
      u8g2_SetFont(u8g2, u8g2_font_inb30_mn);
      u8g2_DrawStr(u8g2, 21, 8, "8");

      u8g2_SetFontDirection(u8g2, 0);
      u8g2_SetFont(u8g2, u8g2_font_inb24_mf);
      u8g2_DrawStr(u8g2, 51, 30, "g");
      u8g2_DrawStr(u8g2, 67, 30, "\xb2");

      u8g2_DrawHLine(u8g2, 2, 35, 47);
      u8g2_DrawHLine(u8g2, 3, 36, 47);
      u8g2_DrawVLine(u8g2, 45, 32, 12);
      u8g2_DrawVLine(u8g2, 46, 33, 12);

      u8g2_SetFont(u8g2, u8g2_font_4x6_tr);
      u8g2_DrawStr(u8g2, 1, 54, "github.com/olikraus/u8g2");
}
```OLED.c

```C
#include "stm32f4xx.h"
#include "OLED_Font.h"
#include "Delay.h"

/*OLED 화면 주소*/
#define OLED_ADDRESS 0x78

//I2C 대기 시간 초과 시간
#define I2C_TIMEOUT 5000

/*핀 초기화*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//I2C1 클럭 활성화
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//GPIOB 클럭 활성화
    
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1);  //PB6의 대체 기능을 I2C1에 연결
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);
 
	/*STM32F407 칩의 하드웨어 I2C: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;                   //GPIO 구성을 위한 구조체 정의
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;		    //GPIO 핀 모드를 대체 IO 모드로 설정
	GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;		//GPIO 핀 속도 100Mhz로 설정
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;          //GPIO 핀을 개방 드레인 출력으로 설정
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;           //GPIO 풀업 모드 설정
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7; //GPIO 핀 설정
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2C1);	//I2C1 외부 장치 레지스터를 기본값으로 재설정
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//작동 모드
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//클럭 듀티 사이클, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//호스트 I2C 주소, 사용하지 않으면 아무 값이나 써도 영향 없음
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//응답 비트 활성화
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//주소 길이 7비트로 설정
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//I2C 전송 속도, 400K, 사용하는 칩 매뉴얼에서 지원하는 속도 확인.
	I2C_Init(I2C1, &I2C_InitStructure);

	I2C_Cmd(I2C1, ENABLE);
}
// https://blog.zeruns.com
void HW_I2cWrite(uint8_t *buf,uint8_t len)
{
	if(len<=0)
		return ;
	uint32_t wait_time=0;

	/* busy 플래그가 재설정될 때까지 대기 */
	while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_GenerateSTART(I2C1, ENABLE);//I2C1 시작
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))/*EV5, 마스터 모드*/
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);	//장치 주소 -- 기본값 0x78
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	for(uint8_t i=0;i<len;i++)
	{
		I2C_SendData(I2C1, buf[i]);//데이터 전송
		while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
		{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
			}
		}
	}
	I2C_GenerateSTOP(I2C1, ENABLE);//I2C1 버스 닫기
}

오픈소스 프로젝트 추천

추천 읽기