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입니다.
```
## 부품 구매 링크
- 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 버스 닫기
}
오픈소스 프로젝트 추천
- MSP430F149 최소 시스템 보드 설계하여 오픈소스로 공개: https://blog.zeruns.com/archives/713.html
- STM32F030C8T6 최소 시스템 보드 및 순차 점등(회로도 및 PCB): https://blog.zeruns.com/archives/715.html
- SY8205 동기식 강하 조정 가능 DCDC 전원 모듈(회로도 및 PCB): https://blog.zeruns.com/archives/717.html
- 2011년 전국 전자대회 문제 - 스위칭 전원 모듈 병렬 공급 시스템: https://blog.zeruns.com/archives/718.html
- 2007년 전자대회 전원 문제: 30~36V 조정 가능 승압 DCDC 모듈(UC3843): https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842
추천 읽기
- 높은 가성비와 저렴한 VPS/클라우드 서버 추천: https://blog.vpszj.cn/archives/41.html
- 개인 블로그 구축 방법: https://blog.zeruns.com/archives/218.html
- 마인크래프트 서버 구축 튜토리얼: https://blog.zeruns.com/tag/mc/
- STM32로 SHT3x 시리즈 온습도 센서 읽기: https://blog.zeruns.com/archives/700.html
- Keil 대신 VSCode를 사용하여 STM32 및 51 MCU 개구현: https://blog.zeruns.com/archives/690.html
- 잡초클라우드 홍콩 BGP 클라우드 서버 성능 평가, 1코어 2G 100메가 연 138위안: https://blog.vpszj.cn/archives/1474.html
- 우윤 수전 5900X 고방어 클라우드 서버 성능 평가, 2코어 4G 5메가 150G 방어 월 50위안: https://blog.vpszj.cn/archives/1125.html

