STM32F1 기반 0.96인치 OLED 디스플레이 드라이버, 하드웨어/소프트웨어 I2C 지원

STM32F103 기반 0.96인치 OLED 디스플레이 드라이버 (4핀 I2C 인터페이스), 하드웨어 IIC/소프트웨어 IIC 지원.

이 드라이버는 상당히 완성도가 높으며, 영어, 정수, 실수, 한자, 이미지, 2진수, 16진수 표시가 가능하며 점, 직선, 사각형, 원, 타원, 삼각형 등을 그릴 수 있고 여러 글꼴을 지원해 간단한 그래픽 라이브러리 수준입니다.

이 프로그램은 Jiangxie Technology 코드를 2차 수정한 것으로, 원본은 소프트웨어 I2C만 지원했으나, 제가 수정해 하드웨어 I2C도 지원하며 매크로 정의를 바꾸면 소프트웨어 I2C로도 사용할 수 있습니다.

프로그램은 ST 표준 라이브러리로 작성되었습니다.

테스트 하드웨어는 STM32F103C8T6와 AIR32F103CBT6로, 두 MCU 모두에서 동작 확인했으며, 하드웨어 I2C 사용 시 STM32는 최대 1.3 Mbit/s, AIR32는 최대 600 kbit/s까지 통신 가능합니다.

OLED 구동 원리 및 드라이버 사용법은 Jiangxie Technology 영상 참고: https://url.zeruns.com/L7j6y

STM32 하드웨어 I2C로 SHTC3 온습도 센서 읽기: https://blog.zeruns.com/archives/692.html

U8g2 그래픽 라이브러리가 포팅된 STM32F407 표준 라이브러리 프로젝트 템플릿: https://blog.zeruns.com/archives/722.html

전자/MCU 기술 단톡: 2169025065

효과 사진

I2C 프로토콜 개요

I2C 통신 프로토콜(Inter-Integrated Circuit)은 Philips社가 개발했으며, 핀 수가 적고 하드웨어 구현이 간단하며 확장성이 뛰어나 USART, CAN 등의 통신 프로토콜처럼 외부 트랜시버(레벨 변환 칩)가 필요 없어 현재 시스템 내 여러 IC 간 통신에 폭넓게 사용됩니다.

I2C는 단일 데이터 버스 SDA(Serial Data Line)만 있어 1비트씩만 전송 가능한 시리얼 통신이며, 반이중 통신 방식입니다.

반이중 통신: 양방향 통신은 가능하나 동시 양방향은 불가하고 번갈아 전송해야 하며, 방향을 전환할 수 있는 단공 통신으로도 볼 수 있습니다. 동시에는 한 방향만 전송 가능해 데이터선 1개면 충분합니다.

I2C 통신 프로토콜은 물리 계층과 프로토콜 계층으로 나눕니다. 물리 계층은 기계·전기적 기능(하드웨어)을 규정해 물리 매체에서 원시 데이터 전송을 보장하고, 프로토콜 계층은 통신 로직을 규정해 송수신 측의 데이터 패킹/언패킹 기준(소프트웨어)을 통일합니다.

I2C 물리 계층

I2C 통신 장치 간 일반 연결 방식

(1) 다중 장치 버스 지원. “버스”는 여러 장치가 공유하는 신호선을 뜻하며, 하나의 I2C 버스에 여러 I2C 통신 장치(호스트·슬레이브 다수)를 연결할 수 있습니다.

(2) I2C 버스는 2개 선만 사용: 양방향 시리얼 데이터선 SDA(Serial Data Line)와 시리얼 클럭선 SCL(Serial Clock Line). 데이터선은 데이터 표현, 클럭선은 송수신 동기용입니다.

(3) 버스는 풀업 저항으로 전원에 연결됩니다. I2C 장치가 유휴 상태일 때는 하이 임피던스(High-Z) 출력이며, 모든 장치가 유휴로 High-Z일 때 풀업 저항이 버스를 하이 레벨로 당깁니다.

I2C 통신 시 MCU GPIO 핀은 오픈 드레인 출력으로 설정해야 하며, 그렇지 않으면 단락 위험이 있습니다.

STM32 I2C 관련 추가 정보 및 사용법은 다음 글 참고: https://url.zeruns.com/JC0Ah

그리고 Jiangxie Technology STM32 입문 강좌: https://www.bilibili.com/video/BV1th411z7sn?p=31

여기서는 자세히 설명하지 않겠습니다.

필요한 부품

  • STM32 개발보드 입문 키트: https://u.jd.com/fQS0YAe
  • STM32F103 최소 시스템 보드: https://s.click.taobao.com/TsFFvwt
  • OLED 모듈: https://s.click.taobao.com/EF0Evwt
  • 듀폰 와이어: https://s.click.taobao.com/VMkDvwt
  • 브레드보드: https://s.click.taobao.com/bhg8Txt
  • DAPLink(ST-Link 대체, 가상 COM 포트 포함): https://s.click.taobao.com/QVQ8Txt

Jiangxie Technology STM32 입문 키트: https://s.click.taobao.com/NTn9Txt

프로그램

전체 프로젝트 다운로드:

바이두 넷디스크: https://url.zeruns.com/kSxoe 압축해제 코드: wgc3

123 넷디스크(속도 제한 없음): https://www.123pan.com/s/2Y9Djv-HGcvH.html 압축해제 코드: m7sp

프로젝트는 Keil5로 생성했으며 Vscode+EIDE로 개발했고, 두 IDE 모두 열 수 있습니다.

모든 파일은 UTF-8 인코딩입니다. 열 때 글자가 깨지면 에디터 인코딩을 UTF-8로 변경하세요.

main.c 파일:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

/**
 * @brief 지정 GPIO 포트의 핀 전위를 반전합니다.
 * 
 * @param GPIOx GPIO 포트 포인터, 예: GPIOA, GPIOB 등.
 * @param GPIO_Pin 반전할 GPIO 핀, 단일 핀(GPIO_Pin_0)이나 다중 핀(GPIO_Pin_0 | GPIO_Pin_1) 모두 가능.
 * 
 * 이 함수는 먼저 지정 GPIO 포트의 지정 핀 출력 전위를 읽습니다. 현재가 Low(Bit_RESET)면 High(Bit_SET)로,
 * High면 Low로 설정해 전위를 반전합니다.
 */
void GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    // GPIO 전위 상태 읽기
    if (GPIO_ReadOutputDataBit(GPIOx, GPIO_Pin) == Bit_RESET)
    {
        // 현재가 Low면 High로 설정
        GPIO_SetBits(GPIOx, GPIO_Pin);
    }
    else
    {
        // 현재가 High면 Low로 설정
        GPIO_ResetBits(GPIOx, GPIO_Pin);
    }
}
``````c
int main(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // GPIOC 클럭 활성화
	// GPIO 핀 초기화
    GPIO_InitTypeDef GPIO_InitStructure;                // GPIO 설정 구조체 정의
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;   // GPIO 모드를 푸시-풀 출력으로 설정, 풀업 저항 필요
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   // GPIO 속도를 50MHz로 설정
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_13; 		// 핀을 OLED_SCL 및 OLED_SDA로 설정
    GPIO_Init(GPIOC, &GPIO_InitStructure);          	// GPIO 초기화

	GPIO_TogglePin(GPIOC, GPIO_Pin_13);	// LED 깜빡임

	/*OLED 초기화*/
	OLED_Init();
	
	/*(0, 0) 위치에 문자 'A' 표시, 글꼴 크기 8*16 도트 매트릭스*/
	//OLED_ShowChar(0, 0, 'A', OLED_8X16);
	
	/*(16, 0) 위치에 문자열 "Hello World!" 표시, 글꼴 크기 8*16 도트 매트릭스*/
	OLED_ShowString(0, 0, "blog.zeruns.com", OLED_8X16);
	
	/*(0, 18) 위치에 문자 'A' 표시, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowChar(0, 18, 'A', OLED_6X8);
	
	/*(16, 18) 위치에 문자열 "Hello World!" 표시, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowString(16, 18, "Hello World!", OLED_6X8);
	
	/*(0, 28) 위치에 숫자 12345 표시, 길이 5, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowNum(0, 28, 12345, 5, OLED_6X8);
	
	/*(40, 28) 위치에 부호 있는 숫자 -66 표시, 길이 2, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowSignedNum(40, 28, -66, 2, OLED_6X8);
	
	/*(70, 28) 위치에 16진수 0xA5A5 표시, 길이 4, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowHexNum(70, 28, 0xA5A5, 4, OLED_6X8);
	
	/*(0, 38) 위치에 2진수 0xA5 표시, 길이 8, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowBinNum(0, 38, 0xA5, 8, OLED_6X8);
	
	/*(60, 38) 위치에 부동 소수점 123.45 표시, 정수 부분 길이 3, 소수 부분 길이 2, 글꼴 크기 6*8 도트 매트릭스*/
	OLED_ShowFloatNum(60, 38, 123.45, 3, 2, OLED_6X8);
	
	/*(0, 48) 위치에 한자 문자열 "你好,世界。" 표시, 글꼴 크기 고정 16*16 도트 매트릭스*/
	OLED_ShowChinese(0, 48, "你好,世界。");
	
	/*(96, 48) 위치에 이미지 표시, 너비 16픽셀, 높이 16픽셀, 이미지 데이터는 Diode 배열*/
	OLED_ShowImage(96, 48, 16, 16, Diode);
	
	/*(96, 18) 위치에 형식화된 문자열 출력, 글꼴 크기 6*8 도트 매트릭스, 형식화 문자열 "[%02d]"*/
	OLED_Printf(96, 18, OLED_6X8, "[%02d]", 6);
	
	/*OLED_Update 함수 호출, OLED 메모리 배열의 내용을 OLED 하드웨어에 업데이트하여 표시*/
	OLED_Update();
	
	/*3000ms 지연, 현상 관찰*/
	Delay_ms(3000);

	GPIO_TogglePin(GPIOC, GPIO_Pin_13);	// LED 깜빡임
	
	/*OLED 메모리 배열 지우기*/
	OLED_Clear();
	
	/*(5, 8) 위치에 점 찍기*/
	OLED_DrawPoint(5, 8);
	
	/*(5, 8) 위치의 점 가져오기*/
	if (OLED_GetPoint(5, 8))
	{
		/*지정된 점이 켜져 있으면 (10, 4) 위치에 문자열 "YES" 표시, 글꼴 크기 6*8 도트 매트릭스*/
		OLED_ShowString(10, 4, "YES", OLED_6X8);
	}
	else
	{
		/*지정된 점이 꺼져 있으면 (10, 4) 위치에 문자열 "NO " 표시, 글꼴 크기 6*8 도트 매트릭스*/
		OLED_ShowString(10, 4, "NO ", OLED_6X8);
	}
	
	/*(40, 0)과 (127, 15) 위치 사이에 직선 그리기*/
	OLED_DrawLine(40, 0, 127, 15);
	
	/*(40, 15)와 (127, 0) 위치 사이에 직선 그리기*/
	OLED_DrawLine(40, 15, 127, 0);
	
	/*(0, 20) 위치에 직사각형 그리기, 너비 12픽셀, 높이 15픽셀, 미채움*/
	OLED_DrawRectangle(0, 20, 12, 15, OLED_UNFILLED);
	
	/*(0, 40) 위치에 직사각형 그리기, 너비 12픽셀, 높이 15픽셀, 채움*/
	OLED_DrawRectangle(0, 40, 12, 15, OLED_FILLED);
	
	/*(20, 20), (40, 25) 및 (30, 35) 위치 사이에 삼각형 그리기, 미채움*/
	OLED_DrawTriangle(20, 20, 40, 25, 30, 35, OLED_UNFILLED);
	
	/*(20, 40), (40, 45) 및 (30, 55) 위치 사이에 삼각형 그리기, 채움*/
	OLED_DrawTriangle(20, 40, 40, 45, 30, 55, OLED_FILLED);
	
	/*(55, 27) 위치에 원 그리기, 반지름 8픽셀, 미채움*/
	OLED_DrawCircle(55, 27, 8, OLED_UNFILLED);
	
	/*(55, 47) 위치에 원 그리기, 반지름 8픽셀, 채움*/
	OLED_DrawCircle(55, 47, 8, OLED_FILLED);
	
	/*(82, 27) 위치에 타원 그리기, 수평 반축 12픽셀, 수직 반축 8픽셀, 미채움*/
	OLED_DrawEllipse(82, 27, 12, 8, OLED_UNFILLED);
	// https://blog.zeruns.com
	/*(82, 47) 위치에 타원 그리기, 수평 반축 12픽셀, 수직 반축 8픽셀, 채움*/
	OLED_DrawEllipse(82, 47, 12, 8, OLED_FILLED);
	
	/*(110, 18) 위치에 호 그리기, 반지름 15픽셀, 시작 각도 25도, 종료 각도 125도, 미채움*/
	OLED_DrawArc(110, 18, 15, 25, 125, OLED_UNFILLED);
	
	/*(110, 38) 위치에 호 그리기, 반지름 15픽셀, 시작 각도 25도, 종료 각도 125도, 채움*/
	OLED_DrawArc(110, 38, 15, 25, 125, OLED_FILLED);
	
	/*OLED_Update 함수 호출, OLED 메모리 배열의 내용을 OLED 하드웨어에 업데이트하여 표시*/
	OLED_Update();
	
	/*1500ms 지연, 현상 관찰*/
	Delay_ms(1500);

	GPIO_TogglePin(GPIOC, GPIO_Pin_13);	// LED 깜빡임
	
	while (1)
	{
		

		for (uint8_t i = 0; i < 4; i ++)
		{
			/*OLED 메모리 배열의 일부 데이터 반전, (0, i * 16) 위치에서 시작, 너비 128픽셀, 높이 16픽셀*/
			OLED_ReverseArea(0, i * 16, 128, 16);
			
			/*OLED_Update 함수 호출, OLED 메모리 배열의 내용을 OLED 하드웨어에 업데이트하여 표시*/
			OLED_Update();
			
			/*1000ms 지연, 현상 관찰*/
			Delay_ms(1000);
			
			/*반전된 내용을 다시 뒤집기*/
			OLED_ReverseArea(0, i * 16, 128, 16);

			GPIO_TogglePin(GPIOC, GPIO_Pin_13);	// LED 깜빡임
		}
		// https://blog.zeruns.com
		/*OLED 메모리 배열의 모든 데이터 반전*/
		OLED_Reverse();
		
		/*OLED_Update 함수 호출, OLED 메모리 배열의 내용을 OLED 하드웨어에 업데이트하여 표시*/
		OLED_Update();
		
		/*1000ms 지연, 현상 관찰*/
		Delay_ms(1000);

		GPIO_TogglePin(GPIOC, GPIO_Pin_13);	// LED 깜빡임

	}
}

OLED.c 파일:


/***************************************************************************************
 * 이 프로그램은 Jiangxie Technology가 작성하고 무료로 오픈소스로 공개했습니다.
 * 자유롭게 확인, 사용 및 수정할 수 있으며 자신의 프로젝트에 적용할 수 있습니다.
 * 프로그램의 저작권은 Jiangxie Technology에 있으며, 누구도 이를 소유할 수 없습니다.
 *
 * 프로그램 이름: 0.96인치 OLED 디스플레이 드라이버 (4핀 I2C 인터페이스)
 * 프로그램 생성일: 2023.10.24
 * 현재 프로그램 버전: V1.1
 * 현재 버전 릴리스일: 2023.12.8
 *
 * Jiangxie Technology 공식 웹사이트: jiangxiekeji.com
 * Jiangxie Technology 공식 Taobao 상점: jiangxiekeji.taobao.com
 * 프로그램 소개 및 업데이트 동향: jiangxiekeji.com/tutorial/oled.html
 *
 * 프로그램의 버그나 오타를 발견하면 이메일로 피드백 주세요: [email protected]
 * 이메일 보내기 전 업데이트 동향 페이지에서 최신 프로그램을 확인해 주세요. 이미 수정된 문제라면 이메일을 보낼 필요가 없습니다.
 ***************************************************************************************
 */

/*
 * 이 프로그램은 zeruns에 의해 2차 수정되었습니다.
 * 수정 내용: 하드웨어 I2C 지원 추가, 매크로 정의 수정으로 하드웨어 I2C 활성화 여부 선택 가능
 * 수정일: 2024.2.25
 * 블로그: https://blog.zeruns.com
 * Bilibili 홈페이지: https://space.bilibili.com/8320520
*/

#include "stm32f10x.h"
#include "OLED.h"
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>

// 如果用到中文,编译器附加选项需要加 --no-multibyte-chars

/*
选择OLED驱动方式,默认使用硬件I2C。如果要用软件I2C就将硬件I2C那行的宏定义注释掉,将软件I2C那行的注释取消。
不能同时两个都同时取消注释!
*/
#define OLED_USE_HW_I2C		// 硬件I2C
//#define OLED_USE_SW_I2C	// 软件I2C

/*引脚定义,可在此处修改I2C通信引脚*/
#define OLED_SCL  GPIO_Pin_6 // SCL
#define OLED_SDA  GPIO_Pin_7 // SDA
#define OLED_GPIO GPIOB
#define OLED_RCC  RCC_APB2Periph_GPIOB
/*STM32F103芯片的硬件I2C1: PB6 -- SCL; PB7 -- SDA */

/*I2C接口,定义OLED屏使用哪个I2C接口*/
#define OLED_I2C     I2C1
#define OLED_I2C_RCC RCC_APB1Periph_I2C1

/*OLED从机地址*/
#define OLED_ADDRESS 0x3C << 1	// 0x3C是OLED的7位地址,左移1位最后位做读写位变成0x78

/*I2C超时时间*/
#define OLED_I2C_TIMEOUT 1000

/**
 * 数据存储格式:
 * 纵向8点,高位在下,先从左到右,再从上到下
 * 每一个Bit对应一个像素点
 *
 *      B0 B0                  B0 B0
 *      B1 B1                  B1 B1
 *      B2 B2                  B2 B2
 *      B3 B3  ------------->  B3 B3 --
 *      B4 B4                  B4 B4  |
 *      B5 B5                  B5 B5  |
 *      B6 B6                  B6 B6  |
 *      B7 B7                  B7 B7  |
 *                                    |
 *  -----------------------------------
 *  |
 *  |   B0 B0                  B0 B0
 *  |   B1 B1                  B1 B1
 *  |   B2 B2                  B2 B2
 *  --> B3 B3  ------------->  B3 B3
 *      B4 B4                  B4 B4
 *      B5 B5                  B5 B5
 *      B6 B6                  B6 B6
 *      B7 B7                  B7 B7
 *
 * 坐标轴定义:
 * 左上角为(0, 0)点
 * 横向向右为X轴,取值范围:0~127
 * 纵向向下为Y轴,取值范围:0~63
 *
 *       0             X轴           127
 *      .------------------------------->
 *    0 |
 *      |
 *      |
 *      |
 *  Y轴 |
 *      |
 *      |
 *      |
 *   63 |
 *      v
 *
 */

/*全局变量*********************/
/**
 * OLED显存数组
 * 所有的显示函数,都只是对此显存数组进行读写
 * 随后调用OLED_Update函数或OLED_UpdateArea函数
 * 才会将显存数组的数据发送到OLED硬件,进行显示
 */
uint8_t OLED_DisplayBuf[8][128];
/*********************全局变量*/

#ifdef OLED_USE_SW_I2C
/**
  * 函    数:OLED写SCL高低电平
  * 参    数:要写入SCL的电平值,范围:0/1
  * 返 回 值:无
  * 说    明:当上层函数需要写SCL时,此函数会被调用
  *           用户需要根据参数传入的值,将SCL置为高电平或者低电平
  *           当参数传入0时,置SCL为低电平,当参数传入1时,置SCL为高电平
  */
void OLED_W_SCL(uint8_t BitValue)
{
	/*根据BitValue的值,将SCL置高电平或者低电平*/
	GPIO_WriteBit(OLED_GPIO, OLED_SCL, (BitAction)BitValue);
	
	/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
	//...
}

/**
  * 函    数:OLED写SDA高低电平
  * 参    数:要写入SDA的电平值,范围:0/1
  * 返 回 值:无
  * 说    明:当上层函数需要写SDA时,此函数会被调用
  *           用户需要根据参数传入的值,将SDA置为高电平或者低电平
  *           当参数传入0时,置SDA为低电平,当参数传入1时,置SDA为高电平
  */
void OLED_W_SDA(uint8_t BitValue)
{
	/*根据BitValue的值,将SDA置高电平或者低电平*/
	GPIO_WriteBit(OLED_GPIO, OLED_SDA, (BitAction)BitValue);
	
	/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
	//...
}
#endif

/**
 * 函    数:OLED引脚初始化
 * 参    数:无
 * 返 回 值:无
 * 说    明:当上层函数需要初始化时,此函数会被调用
 *           用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
 */
void OLED_GPIO_Init(void)
{
    uint32_t i, j;

    /*在初始化前,加入适量延时,待OLED供电稳定*/
    for (i = 0; i < 1000; i++) {
        for (j = 0; j < 1000; j++)
            ;
    }
#ifdef OLED_USE_HW_I2C
    RCC_APB1PeriphClockCmd(OLED_I2C_RCC, ENABLE); 	// 使能I2C1时钟
#endif
    RCC_APB2PeriphClockCmd(OLED_RCC, ENABLE);		// 使能GPIO时钟

	GPIO_InitTypeDef GPIO_InitStructure;                 // 定义结构体配置GPIO
#ifdef OLED_USE_HW_I2C
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;     // 设置GPIO模式为复用开漏模式,需接上拉电阻
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    // 设置GPIO速度为50MHz
    GPIO_InitStructure.GPIO_Pin   = OLED_SCL | OLED_SDA; // 设置引脚为OLED_SCL和OLED_SDA
    GPIO_Init(OLED_GPIO, &GPIO_InitStructure);           // 初始化GPIO
#elif defined(OLED_USE_SW_I2C)
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;	// 设置GPIO模式为开漏输出模式,需接上拉电阻
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin   = OLED_SCL | OLED_SDA;// 设置引脚为OLED_SCL和OLED_SDA
 	GPIO_Init(OLED_GPIO, &GPIO_InitStructure);
	/*释放SCL和SDA*/
	OLED_W_SCL(1);
	OLED_W_SDA(1);
#endif

#ifdef OLED_USE_HW_I2C
    I2C_DeInit(OLED_I2C);                                                     // 将外设I2C寄存器重设为缺省值
    I2C_InitTypeDef I2C_InitStructure;                                        // 定义结构体配置I2C
    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          = 600000;// 设置I2C时钟频率,600kHz,建议设置400kHz(比较稳定),实测在STM32F103上最高可以跑1.3MHz,在AIR32F103上最高只能跑600kHz。
    I2C_Init(OLED_I2C, &I2C_InitStructure);                                   // 初始化I2C

    I2C_Cmd(OLED_I2C, ENABLE); // 使能I2C
#endif
}

// https://blog.zeruns.com

/*通信协议*********************/

/**
 * 函    数:I2C起始
 * 参    数:无
 * 返 回 值:无
 */
void OLED_I2C_Start(void)
{
#ifdef OLED_USE_HW_I2C
    for(uint16_t i = 0; I2C_GetFlagStatus(OLED_I2C, I2C_FLAG_BUSY) && i < OLED_I2C_TIMEOUT; i++);	// 判断IIC总线是否忙碌
    I2C_GenerateSTART(OLED_I2C, ENABLE); 						// 发送起始信号
    // 检查I2C的事件。事件(Event)是指I2C1状态发生变化时产生的信号。这里检查EV5(表示主模式)事件,直到其发生。这表示I2C已经成功切换到Master模式。
    for(uint16_t i = 0; !I2C_CheckEvent(OLED_I2C, I2C_EVENT_MASTER_MODE_SELECT) && i < OLED_I2C_TIMEOUT; i++);
    I2C_Send7bitAddress(OLED_I2C, OLED_ADDRESS, I2C_Direction_Transmitter); // 发送7位地址,I2C通信进入发送模式。
    for(uint16_t i = 0; !I2C_CheckEvent(OLED_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && i < OLED_I2C_TIMEOUT; i++); // 检测EV6事件,判断I2C通信是否进入发送模式
#elif defined(OLED_USE_SW_I2C)
	OLED_W_SDA(1);		//释放SDA,确保SDA为高电平
	OLED_W_SCL(1);		//释放SCL,确保SCL为高电平
	OLED_W_SDA(0);		//在SCL高电平期间,拉低SDA,产生起始信号
	OLED_W_SCL(0);		//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
#endif
}

/**
 * 函    数:I2C终止
 * 参    数:无
 * 返 回 值:无
 */
void OLED_I2C_Stop(void)
{
#ifdef OLED_USE_HW_I2C
    I2C_GenerateSTOP(OLED_I2C, ENABLE); // 关闭I2C1总线
#elif defined(OLED_USE_SW_I2C)
	OLED_W_SDA(0);		//拉低SDA,确保SDA为低电平
	OLED_W_SCL(1);		//释放SCL,使SCL呈现高电平
	OLED_W_SDA(1);		//在SCL高电平期间,释放SDA,产生终止信号
#endif
}

/**
 * 函    数:I2C发送一个字节
 * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
 * 返 回 值:无
 */
void OLED_I2C_SendByte(uint8_t Byte)
{
#ifdef OLED_USE_HW_I2C
    I2C_SendData(OLED_I2C, Byte);	// 发送一个字节
	// 检测EV8_2事件,判断I2C是否发送完成
    for(uint16_t i = 0; I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != 1 && i < OLED_I2C_TIMEOUT; i++);
#elif defined(OLED_USE_SW_I2C)
	uint8_t i;
	/*循环8次,主机依次发送数据的每一位*/
	for (i = 0; i < 8; i++)
	{
		/*使用掩码的方式取出Byte的指定一位数据并写入到SDA线*/
		/*两个!的作用是,让所有非零的值变为1*/
		OLED_W_SDA(!!(Byte & (0x80 >> i)));
		OLED_W_SCL(1);	//释放SCL,从机在SCL高电平期间读取SDA
		OLED_W_SCL(0);	//拉低SCL,主机开始发送下一位数据
	}
	OLED_W_SCL(1);		//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
#endif
}

/**
 * 函    数:OLED写命令
 * 参    数:Command 要写入的命令值,范围:0x00~0xFF
 * 返 回 值:无
 */
void OLED_WriteCommand(uint8_t Command)
{
    OLED_I2C_Start();           // I2C起始
#ifdef OLED_USE_SW_I2C
	OLED_I2C_SendByte(0x78);		//发送OLED的I2C从机地址
#endif
	OLED_I2C_SendByte(0x00);	//控制字节,给0x00,表示即将写命令
    OLED_I2C_SendByte(Command); // 写入指定的命令
    OLED_I2C_Stop();            // I2C终止
}

/**
 * 函    数:OLED写数据
 * 参    数:Data 要写入数据的起始地址
 * 参    数:Count 要写入数据的数量
 * 返 回 值:无
 */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
    uint8_t i;

    OLED_I2C_Start();        // I2C起始
#ifdef OLED_USE_SW_I2C
	OLED_I2C_SendByte(0x78);		//发送OLED的I2C从机地址
#endif
    OLED_I2C_SendByte(0x40); // 控制字节,给0x40,表示即将写数据
    /*循环Count次,进行连续的数据写入*/
    for (i = 0; i < Count; i++) {
        OLED_I2C_SendByte(Data[i]); // 依次发送Data的每一个数据
    }
    OLED_I2C_Stop(); // I2C终止
}

/*********************通信协议*/

/*硬件配置*********************/

/**
 * 函    数:OLED初始化
 * 参    数:无
 * 返 回 值:无
 * 说    明:使用前,需要调用此初始化函数
 */
void OLED_Init(void)
{
    OLED_GPIO_Init(); // 先调用底层的端口初始化

    /*写入一系列的命令,对OLED进行初始化配置*/
    OLED_WriteCommand(0xAE); // 设置显示开启/关闭,0xAE关闭,0xAF开启

    OLED_WriteCommand(0xD5); // 设置显示时钟分频比/振荡器频率
    OLED_WriteCommand(0x80); // 0x00~0xFF

    OLED_WriteCommand(0xA8); // 设置多路复用率
    OLED_WriteCommand(0x3F); // 0x0E~0x3F

    OLED_WriteCommand(0xD3); // 设置显示偏移
    OLED_WriteCommand(0x00); // 0x00~0x7F

    OLED_WriteCommand(0x40); // 设置显示开始行,0x40~0x7F

OLED_WriteCommand(0xA1); // 좌우 방향 설정, 0xA1은 정상, 0xA0은 좌우 반전

    OLED_WriteCommand(0xC8); // 상하 방향 설정, 0xC8은 정상, 0xC0은 상하 반전

    OLED_WriteCommand(0xDA); // COM 핀 하드웨어 구성 설정
    OLED_WriteCommand(0x12);

    OLED_WriteCommand(0x81); // 대비 설정
    OLED_WriteCommand(0xCF); // 0x00~0xFF

    OLED_WriteCommand(0xD9); // 프리차지 주기 설정
    OLED_WriteCommand(0xF1);

    OLED_WriteCommand(0xDB); // VCOMH 디셀렉트 레벨 설정
    OLED_WriteCommand(0x30);

    OLED_WriteCommand(0xA4); // 전체 디스플레이 온/오프 설정

    OLED_WriteCommand(0xA6); // 정상/반전 디스플레이 설정, 0xA6은 정상, 0xA7은 반전

    OLED_WriteCommand(0x8D); // 차지 펌프 설정
    OLED_WriteCommand(0x14);

    OLED_WriteCommand(0xAF); // 디스플레이 켜기

    OLED_Clear();  // 디스플레이 버퍼 배열 초기화
    OLED_Update(); // 디스플레이 업데이트, 화면 지우기, 초기화 후 내용 미표시 시 잡화면 방지
}

/**
 * 함수    : OLED 커서 위치 설정
 * 매개변수: Page 커서가 위치한 페이지, 범위: 0~7
 * 매개변수: X 커서가 위치한 X축 좌표, 범위: 0~127
 * 반환값  : 없음
 * 설명    : OLED 기본 Y축은 8비트 단위로만 기록 가능, 즉 1페이지는 8개 Y축 좌표
 */
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
    /* 1.3인치 OLED 사용 시 아래 주석 해제 */
    /* 1.3인치 OLED 드라이버 칩(SH1106)은 132열 */
    /* 화면 시작 열이 0열이 아닌 2열에 연결 */
    /* 따라서 X에 2를 더해야 정상 표시 */
    //	X += 2;

    /* 명령어로 페이지 주소와 열 주소 설정 */
    OLED_WriteCommand(0xB0 | Page);              // 페이지 위치 설정
    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); // X 위치 상위 4비트 설정
    OLED_WriteCommand(0x00 | (X & 0x0F));        // X 위치 하위 4비트 설정
}

/*********************하드웨어 구성*/

/*유틸리티 함수*********************/

/*유틸리티 함수는 내부 일부 함수에서만 사용*/

/**
 * 함수    : 거듭제곱 함수
 * 매개변수: X 밑수
 * 매개변수: Y 지수
 * 반환값  : X의 Y승
 */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1; // 결과 기본값 1
    while (Y--)          // Y번 곱하기
    {
        Result *= X; // 매번 X를 결과에 곱함
    }
    return Result;
}

/**
 * 함수    : 지정된 점이 다각형 내부에 있는지 판단
 * 매개변수: nvert 다각형 꼭짓점 수
 * 매개변수: vertx verty 다각형 꼭짓점 x, y 좌표 배열
 * 매개변수: testx testy 테스트 점 X, y 좌표
 * 반환값  : 지정 점이 다각형 내부 여부, 1: 내부, 0: 외부
 */
uint8_t OLED_pnpoly(uint8_t nvert, int16_t *vertx, int16_t *verty, int16_t testx, int16_t testy)
{
    int16_t i, j, c = 0;

    /* W. Randolph Franklin이 제안한 알고리즘 */
    /* 참고 링크: https://wrfranklin.org/Research/Short_Notes/pnpoly.html */
    for (i = 0, j = nvert - 1; i < nvert; j = i++) {
        if (((verty[i] > testy) != (verty[j] > testy)) &&
            (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
            c = !c;
        }
    }
    return c;
}

/**
 * 함수    : 지정된 점이 지정 각도 범위 내부에 있는지 판단
 * 매개변수: X Y 지정 점 좌표
 * 매개변수: StartAngle EndAngle 시작 각도와 종료 각도, 범위: -180~180
 *           수평 오른쪽이 0도, 수평 왼쪽이 180도 또는 -180도, 아래쪽이 양수, 위쪽이 음수, 시계 방향 회전
 * 반환값  : 지정 점이 각도 범위 내부 여부, 1: 내부, 0: 외부
 */
uint8_t OLED_IsInAngle(int16_t X, int16_t Y, int16_t StartAngle, int16_t EndAngle)
{
    int16_t PointAngle;
    PointAngle = atan2(Y, X) / 3.14 * 180; // 지정 점의 라디안 계산 후 도 단위로 변환
    if (StartAngle < EndAngle)             // 시작 각도가 종료 각도보다 작은 경우
    {
        /* 지정 각도가 시작~종료 사이면 점이 범위 내부로 판정 */
        if (PointAngle >= StartAngle && PointAngle <= EndAngle) {
            return 1;
        }
    } else // 시작 각도가 종료 각도보다 큰 경우
    {
        /* 지정 각도가 시작 각도보다 크거나 종료 각도보다 작으면 범위 내부로 판정 */
        if (PointAngle >= StartAngle || PointAngle <= EndAngle) {
            return 1;
        }
    }
    return 0; // 위 조건 불만족 시 범위 외부
}

/*********************유틸리티 함수*/

/*기능 함수*********************/

/**
 * 함수    : OLED 디스플레이 버퍼 배열을 OLED 화면으로 업데이트
 * 매개변수: 없음
 * 반환값  : 없음
 * 설명    : 모든 표시 함수는 OLED 디스플레이 버퍼 배열에만 읽고 씀
 *           이후 OLED_Update 함수 또는 OLED_UpdateArea 함수 호출 시
 *           버퍼 배열 데이터를 OLED 하드웨어로 전송하여 표시
 *           따라서 표시 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_Update(void)
{
    uint8_t j;
    /* 각 페이지 순회 */
    for (j = 0; j < 8; j++) {
        /* 커서를 해당 페이지 첫 번째 열로 설정 */
        OLED_SetCursor(j, 0);
        /* 128개 데이터 연속 기록, 버퍼 배열 데이터를 OLED 하드웨어로 기록 */
        OLED_WriteData(OLED_DisplayBuf[j], 128);
    }
}

/**
 * 함수    : OLED 디스플레이 버퍼 배열의 일부를 OLED 화면으로 업데이트
 * 매개변수: X 지정 영역 좌상단 X좌표, 범위: 0~127
 * 매개변수: Y 지정 영역 좌상단 Y좌표, 범위: 0~63
 * 매개변수: Width 지정 영역 너비, 범위: 0~128
 * 매개변수: Height 지정 영역 높이, 범위: 0~64
 * 반환값  : 없음
 * 설명    : 이 함수는 최소한 매개변수로 지정된 영역을 업데이트
 *           Y축 일부 페이지만 포함 시 같은 페이지 나머지도 함께 업데이트
 * 설명    : 모든 표시 함수는 OLED 디스플레이 버퍼 배열에만 읽고 씀
 *           이후 OLED_Update 함수 또는 OLED_UpdateArea 함수 호출 시
 *           버퍼 배열 데이터를 OLED 하드웨어로 전송하여 표시
 *           따라서 표시 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_UpdateArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
    uint8_t j;

    /* 매개변수 검사, 지정 영역이 화면 범위를 벗어나지 않도록 */
    if (X > 127) { return; }
    if (Y > 63) { return; }
    if (X + Width > 128) { Width = 128 - X; }
    if (Y + Height > 64) { Height = 64 - Y; }

    /* 지정 영역에 관련된 페이지 순회 */
    /* (Y + Height - 1) / 8 + 1은 (Y + Height) / 8 후 올림 */
    for (j = Y / 8; j < (Y + Height - 1) / 8 + 1; j++) {
        /* 커서를 관련 페이지 지정 열로 설정 */
        OLED_SetCursor(j, X);
        /* Width개 데이터 연속 기록, 버퍼 배열 데이터를 OLED 하드웨어로 기록 */
        OLED_WriteData(&OLED_DisplayBuf[j][X], Width);
    }
}

/**
 * 함수    : OLED 디스플레이 버퍼 배열 전체를 0으로
 * 매개변수: 없음
 * 반환값  : 없음
 * 설명    : 이 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_Clear(void)
{
    uint8_t i, j;
    for (j = 0; j < 8; j++) // 8페이지 순회
    {
        for (i = 0; i < 128; i++) // 128열 순회
        {
            OLED_DisplayBuf[j][i] = 0x00; // 버퍼 배열 데이터 전체 0
        }
    }
}

/**
 * 함수    : OLED 디스플레이 버퍼 배열의 일부를 0으로
 * 매개변수: X 지정 영역 좌상단 X좌표, 범위: 0~127
 * 매개변수: Y 지정 영역 좌상단 Y좌표, 범위: 0~63
 * 매개변수: Width 지정 영역 너비, 범위: 0~128
 * 매개변수: Height 지정 영역 높이, 범위: 0~64
 * 반환값  : 없음
 * 설명    : 이 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_ClearArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
    uint8_t i, j;

    /* 매개변수 검사, 지정 영역이 화면 범위를 벗어나지 않도록 */
    if (X > 127) { return; }
    if (Y > 63) { return; }
    if (X + Width > 128) { Width = 128 - X; }
    if (Y + Height > 64) { Height = 64 - Y; }

    for (j = Y; j < Y + Height; j++) // 지정 페이지 순회
    {
        for (i = X; i < X + Width; i++) // 지정 열 순회
        {
            OLED_DisplayBuf[j / 8][i] &= ~(0x01 << (j % 8)); // 버퍼 배열 지정 데이터 0
        }
    }
}

/**
 * 함수    : OLED 디스플레이 버퍼 배열 전체 반전
 * 매개변수: 없음
 * 반환값  : 없음
 * 설명    : 이 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_Reverse(void)
{
    uint8_t i, j;
    for (j = 0; j < 8; j++) // 8페이지 순회
    {
        for (i = 0; i < 128; i++) // 128열 순회
        {
            OLED_DisplayBuf[j][i] ^= 0xFF; // 버퍼 배열 데이터 전체 반전
        }
    }
}

/**
 * 함수    : OLED 디스플레이 버퍼 배열의 일부 반전
 * 매개변수: X 지정 영역 좌상단 X좌표, 범위: 0~127
 * 매개변수: Y 지정 영역 좌상단 Y좌표, 범위: 0~63
 * 매개변수: Width 지정 영역 너비, 범위: 0~128
 * 매개변수: Height 지정 영역 높이, 범위: 0~64
 * 반환값  : 없음
 * 설명    : 이 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_ReverseArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
    uint8_t i, j;

    /* 매개변수 검사, 지정 영역이 화면 범위를 벗어나지 않도록 */
    if (X > 127) { return; }
    if (Y > 63) { return; }
    if (X + Width > 128) { Width = 128 - X; }
    if (Y + Height > 64) { Height = 64 - Y; }

    for (j = Y; j < Y + Height; j++) // 지정 페이지 순회
    {
        for (i = X; i < X + Width; i++) // 지정 열 순회
        {
            OLED_DisplayBuf[j / 8][i] ^= 0x01 << (j % 8); // 버퍼 배열 지정 데이터 반전
        }
    }
}

/**
 * 함수    : OLED에 한 문자 표시
 * 매개변수: X 문자 좌상단 X좌표, 범위: 0~127
 * 매개변수: Y 문자 좌상단 Y좌표, 범위: 0~63
 * 매개변수: Char 표시할 문자, 범위: ASCII 가시 문자
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16		너비 8픽셀, 높이 16픽셀
 *                 OLED_6X8		너비 6픽셀, 높이 8픽셀
 * 반환값  : 없음
 * 설명    : 이 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_ShowChar(uint8_t X, uint8_t Y, char Char, uint8_t FontSize)
{
    if (FontSize == OLED_8X16) // 글자 크기 너비 8픽셀, 높이 16픽셀
    {
        /* ASCII 글꼴 라이브러리 OLED_F8x16의 지정 데이터를 8*16 이미지 형식으로 표시 */
        OLED_ShowImage(X, Y, 8, 16, OLED_F8x16[Char - ' ']);
    } else if (FontSize == OLED_6X8) // 글자 크기 너비 6픽셀, 높이 8픽셀
    {
        /* ASCII 글꼴 라이브러리 OLED_F6x8의 지정 데이터를 6*8 이미지 형식으로 표시 */
        OLED_ShowImage(X, Y, 6, 8, OLED_F6x8[Char - ' ']);
    }
}

/**
 * 함수    : OLED에 문자열 표시
 * 매개변수: X 문자열 좌상단 X좌표, 범위: 0~127
 * 매개변수: Y 문자열 좌상단 Y좌표, 범위: 0~63
 * 매개변수: String 표시할 문자열, 범위: ASCII 가시 문자로 구성된 문자열
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16		너비 8픽셀, 높이 16픽셀
 *                 OLED_6X8		너비 6픽셀, 높이 8픽셀
 * 반환값  : 없음
 * 설명    : 이 함수 호출 후 실제 화면에 반영하려면 업데이트 함수 필요
 */
void OLED_ShowString(uint8_t X, uint8_t Y, char *String, uint8_t FontSize)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i++) // 문자열 각 문자 순회
    {
        /* OLED_ShowChar 함수 호출로 각 문자 순서대로 표시 */
        OLED_ShowChar(X + i * FontSize, Y, String[i], FontSize);
    }
}/**
 * 함수: OLED에 숫자 표시(십진수, 양의 정수)
 * 매개변수: X 숫자 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 숫자 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Number 표시할 숫자, 범위: 0~4294967295
 * 매개변수: Length 숫자의 길이, 범위: 0~10
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16 가로 8픽셀, 세로 16픽셀
 *                 OLED_6X8 가로 6픽셀, 세로 8픽셀
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
    uint8_t i;
    for (i = 0; i < Length; i++) // 숫자의 각 자릿수를 순회
    {
        /*OLED_ShowChar 함수를 호출해 각 숫자를 차례로 표시*/
        /*Number / OLED_Pow(10, Length - i - 1) % 10으로 십진수 각 자리를 추출*/
        /*+'0'으로 숫자를 문자 형식으로 변환*/
        OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);
    }
}

/**
 * 함수: OLED에 부호 있는 숫자 표시(십진수, 정수)
 * 매개변수: X 숫자 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 숫자 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Number 표시할 숫자, 범위: -2147483648~2147483647
 * 매개변수: Length 숫자의 길이, 범위: 0~10
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16 가로 8픽셀, 세로 16픽셀
 *                 OLED_6X8 가로 6픽셀, 세로 8픽셀
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowSignedNum(uint8_t X, uint8_t Y, int32_t Number, uint8_t Length, uint8_t FontSize)
{
    uint8_t i;
    uint32_t Number1;

    if (Number >= 0) // 숫자가 0 이상
    {
        OLED_ShowChar(X, Y, '+', FontSize); // + 기호 표시
        Number1 = Number;                   // Number1에 Number 그대로 대입
    } else                                  // 숫자가 0 미만
    {
        OLED_ShowChar(X, Y, '-', FontSize); // - 기호 표시
        Number1 = -Number;                  // Number1에 Number의 부호 반전값 대입
    }

    for (i = 0; i < Length; i++) // 숫자의 각 자릿수를 순회
    {
        /*OLED_ShowChar 함수를 호출해 각 숫자를 차례로 표시*/
        /*Number1 / OLED_Pow(10, Length - i - 1) % 10으로 십진수 각 자리를 추출*/
        /*+'0'으로 숫자를 문자 형식으로 변환*/
        OLED_ShowChar(X + (i + 1) * FontSize, Y, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);
    }
}

/**
 * 함수: OLED에 십육진수 숫자 표시(십육진수, 양의 정수)
 * 매개변수: X 숫자 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 숫자 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Number 표시할 숫자, 범위: 0x00000000~0xFFFFFFFF
 * 매개변수: Length 숫자의 길이, 범위: 0~8
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16 가로 8픽셀, 세로 16픽셀
 *                 OLED_6X8 가로 6픽셀, 세로 8픽셀
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowHexNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
    uint8_t i, SingleNumber;
    for (i = 0; i < Length; i++) // 숫자의 각 자릿수를 순회
    {
        /*십육진수로 각 자리를 추출*/
        SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;

        if (SingleNumber < 10) // 한 자리 숫자가 10 미만
        {
            /*OLED_ShowChar 함수를 호출해 이 숫자를 표시*/
            /*+'0'으로 숫자를 문자 형식으로 변환*/
            OLED_ShowChar(X + i * FontSize, Y, SingleNumber + '0', FontSize);
        } else // 한 자리 숫자가 10 이상
        {
            /*OLED_ShowChar 함수를 호출해 이 숫자를 표시*/
            /*+'A'로 10 이상의 십육진수 문자로 변환*/
            OLED_ShowChar(X + i * FontSize, Y, SingleNumber - 10 + 'A', FontSize);
        }
    }
}

/**
 * 함수: OLED에 이진수 숫자 표시(이진수, 양의 정수)
 * 매개변수: X 숫자 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 숫자 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Number 표시할 숫자, 범위: 0x00000000~0xFFFFFFFF
 * 매개변수: Length 숫자의 길이, 범위: 0~16
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16 가로 8픽셀, 세로 16픽셀
 *                 OLED_6X8 가로 6픽셀, 세로 8픽셀
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowBinNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
    uint8_t i;
    for (i = 0; i < Length; i++) // 숫자의 각 자릿수를 순회
    {
        /*OLED_ShowChar 함수를 호출해 각 숫자를 차례로 표시*/
        /*Number / OLED_Pow(2, Length - i - 1) % 2로 이진수 각 자리를 추출*/
        /*+'0'으로 숫자를 문자 형식으로 변환*/
        OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(2, Length - i - 1) % 2 + '0', FontSize);
    }
}

/**
 * 함수: OLED에 부동소수점 숫자 표시(십진수, 소수)
 * 매개변수: X 숫자 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 숫자 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Number 표시할 숫자, 범위: -4294967295.0~4294967295.0
 * 매개변수: IntLength 정수부 길이, 범위: 0~10
 * 매개변수: FraLength 소수부 길이, 범위: 0~9, 소수는 반올림하여 표시
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16 가로 8픽셀, 세로 16픽셀
 *                 OLED_6X8 가로 6픽셀, 세로 8픽셀
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowFloatNum(uint8_t X, uint8_t Y, double Number, uint8_t IntLength, uint8_t FraLength, uint8_t FontSize)
{
    uint32_t PowNum, IntNum, FraNum;

    if (Number >= 0) // 숫자가 0 이상
    {
        OLED_ShowChar(X, Y, '+', FontSize); // + 기호 표시
    } else                                  // 숫자가 0 미만
    {
        OLED_ShowChar(X, Y, '-', FontSize); // - 기호 표시
        Number = -Number;                   // Number 부호 반전
    }

    /*정수부와 소수부 추출*/
    IntNum = Number;                  // 정수부를 정수 변수에 바로 대입
    Number -= IntNum;                 // Number에서 정수를 빼서, 이후 소수를 정수로 만들 때 수가 너무 커지는 오류 방지
    PowNum = OLED_Pow(10, FraLength); // 소수 자릿수에 따라 곱할 수 결정
    FraNum = round(Number * PowNum);  // 소수를 정수로 만들고 반올림하여 표시 오차 방지
    IntNum += FraNum / PowNum;        // 반올림으로 인해 올림이 발생하면 정수부에 다시 더함

    /*정수부 표시*/
    OLED_ShowNum(X + FontSize, Y, IntNum, IntLength, FontSize);

    /*소수점 표시*/
    OLED_ShowChar(X + (IntLength + 1) * FontSize, Y, '.', FontSize);

    /*소수부 표시*/
    OLED_ShowNum(X + (IntLength + 2) * FontSize, Y, FraNum, FraLength, FontSize);
}

/**
 * 함수: OLED에 한자 문자열 표시
 * 매개변수: X 한자 문자열 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 한자 문자열 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Chinese 표시할 한자 문자열, 범위: 반각 문자 없이 전부 한자 또는 전각 문자
 *           표시할 한자는 OLED_Data.c의 OLED_CF16x16 배열에 정의되어 있어야 함
 *           지정 한자를 찾지 못하면 기본 그림(물음표가 들어간 사각형)이 표시됨
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowChinese(uint8_t X, uint8_t Y, char *Chinese)
{
    uint8_t pChinese = 0;
    uint8_t pIndex;
    uint8_t i;
    char SingleChinese[OLED_CHN_CHAR_WIDTH + 1] = {0}; // UTF8 인코딩은 3바이트, +1은 \0 종료 문자

    for (i = 0; Chinese[i] != '\0'; i++) // 한자 문자열을 순회
    {
        SingleChinese[pChinese] = Chinese[i]; // 한자 문자열 데이터를 단일 한자 배열에 추출
        pChinese++;                           // 카운트 증가

        /*추출 횟수가 OLED_CHN_CHAR_WIDTH에 도달하면 완전한 한자 하나를 추출한 것*/
        if (pChinese >= OLED_CHN_CHAR_WIDTH) {
            SingleChinese[pChinese + 1] = '\0'; // 한자 뒤에 널 문자를 붙여 종료 표시
            pChinese                    = 0;    // 카운트 초기화

            /*전체 한자 글꼴 라이브러리를 순회하며 일치하는 한자 찾기*/
            /*마지막 한자(빈 문자열로 정의)를 만나면 글꼴 라이브러리에 정의되지 않은 것으로 판단하고 중단*/
            for (pIndex = 0; strcmp(OLED_CF16x16[pIndex].Index, "") != 0; pIndex++) {
                /*일치하는 한자 발견*/
                if (strcmp(OLED_CF16x16[pIndex].Index, SingleChinese) == 0) {
                    break; // 루프 탈출, 이때 pIndex는 지정 한자의 인덱스 값
                }
            }

            /*한자 글꼴 라이브러리 OLED_CF16x16의 지정 데이터를 16*16 이미지 형식으로 표시*/
            OLED_ShowImage(X + ((i + 1) / OLED_CHN_CHAR_WIDTH - 1) * 16, Y, 16, 16, OLED_CF16x16[pIndex].Data);
        }
    }
}

/**
 * 함수: OLED에 이미지 표시
 * 매개변수: X 이미지 왼쪽 상단의 가로 좌표, 범위: 0~127
 * 매개변수: Y 이미지 왼쪽 상단의 세로 좌표, 범위: 0~63
 * 매개변수: Width 이미지 너비, 범위: 0~128
 * 매개변수: Height 이미지 높이, 범위: 0~64
 * 매개변수: Image 표시할 이미지
 * 반환값: 없음
 * 설명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_ShowImage(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image)
{
    uint8_t i, j;

    /*매개변수 검사, 지정 이미지가 화면 범위를 벗어나지 않도록 보장*/
    if (X > 127) { return; }
    if (Y > 63) { return; }

    /*이미지가 위치한 영역을 지움*/
    OLED_ClearArea(X, Y, Width, Height);

    /*지정 이미지와 관련된 페이지를 순회*/
    /*(Height - 1) / 8 + 1은 Height / 8을 올림한 값*/
    for (j = 0; j < (Height - 1) / 8 + 1; j++) {
        /*지정 이미지와 관련된 열을 순회*/
        for (i = 0; i < Width; i++) {
            /*경계를 벗어나면 표시 건너뜀*/
            if (X + i > 127) { break; }
            if (Y / 8 + j > 7) { return; }

            /*현재 페이지에 이미지 내용 표시*/
            OLED_DisplayBuf[Y / 8 + j][X + i] |= Image[j * Width + i] << (Y % 8);

            /*경계를 벗어나면 표시 건너뜀*/
            /*continue를 사용하는 이유는, 다음 페이지가 경계를 벗어나도 이전 페이지의 나머지 내용은 계속 표시해야 함*/
            if (Y / 8 + j + 1 > 7) { continue; }

            /*다음 페이지에 이미지 내용 표시*/
            OLED_DisplayBuf[Y / 8 + j + 1][X + i] |= Image[j * Width + i] >> (8 - Y % 8);
        }
    }
}/**
 * 함수: OLED에서 printf 함수를 사용해 형식화된 문자열 출력
 * 매개변수: X 형식화된 문자열의 왼쪽 상단 X 좌표, 범위: 0~127
 * 매개변수: Y 형식화된 문자열의 왼쪽 상단 Y 좌표, 범위: 0~63
 * 매개변수: FontSize 글자 크기
 *           범위: OLED_8X16      8픽셀 너비, 16픽셀 높이
 *                 OLED_6X8       6픽셀 너비, 8픽셀 높이
 * 매개변수: format 화면에 표시할 형식화된 문자열, 범위: ASCII 가시 문자로 구성된 문자열
 * 매개변수: ... 형식화된 문자열의 인자 목록
 * 반환값: 없음
 * 설명: 이 함수를 호출한 뒤 실제 화면에 반영하려면 업데이트 함수를 호출해야 함
 */
void OLED_Printf(uint8_t X, uint8_t Y, uint8_t FontSize, char *format, ...)
{
    char String[30];                         // 문자 배열 정의
    va_list arg;                             // 가변 인자 목록 변수 arg 정의
    va_start(arg, format);                   // format부터 인자 목록을 arg에 받음
    vsprintf(String, format, arg);           // vsprintf로 형식화된 문자열과 인자 목록을 문자 배열에 출력
    va_end(arg);                             // arg 종료
    OLED_ShowString(X, Y, String, FontSize); // OLED에 문자 배열(문자열) 표시
}

/**
 * 함수: OLED의 지정 위치에 점 그리기
 * 매개변수: X 점의 X 좌표, 범위: 0~127
 * 매개변수: Y 점의 Y 좌표, 범위: 0~63
 * 반환값: 없음
 * 설명: 이 함수를 호출한 뒤 실제 화면에 반영하려면 업데이트 함수를 호출해야 함
 */
void OLED_DrawPoint(uint8_t X, uint8_t Y)
{
    /* 매개변수 검사, 지정 위치가 화면 밖으로 나가지 않도록 함 */
    if (X > 127) { return; }
    if (Y > 63) { return; }

    /*显存 배열의 지정 위치 Bit를 1로 설정 */
    OLED_DisplayBuf[Y / 8][X] |= 0x01 << (Y % 8);
}

/**
 * 함수: OLED의 지정 위치 점 값 얻기
 * 매개변수: X 점의 X 좌표, 범위: 0~127
 * 매개변수: Y 점의 Y 좌표, 범위: 0~63
 * 반환값: 지정 위치 점이 켜져 있는지 여부, 1: 켜짐, 0: 꺼짐
 */
uint8_t OLED_GetPoint(uint8_t X, uint8_t Y)
{
    /* 매개변수 검사, 지정 위치가 화면 밖으로 나가지 않도록 함 */
    if (X > 127) { return 0; }
    if (Y > 63) { return 0; }

    /* 지정 위치 데이터 판단 */
    if (OLED_DisplayBuf[Y / 8][X] & 0x01 << (Y % 8)) {
        return 1; // 1이면 1 반환
    }

    return 0; // 아니면 0 반환
}

/**
 * 함수: OLED 선 그리기
 * 매개변수: X0 한 끝점의 X 좌표, 범위: 0~127
 * 매개변수: Y0 한 끝점의 Y 좌표, 범위: 0~63
 * 매개변수: X1 다른 끝점의 X 좌표, 범위: 0~127
 * 매개변수: Y1 다른 끝점의 Y 좌표, 범위: 0~63
 * 반환값: 없음
 * 설명: 이 함수를 호출한 뒤 실제 화면에 반영하려면 업데이트 함수를 호출해야 함
 */
void OLED_DrawLine(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1)
{
    int16_t x, y, dx, dy, d, incrE, incrNE, temp;
    int16_t x0 = X0, y0 = Y0, x1 = X1, y1 = Y1;
    uint8_t yflag = 0, xyflag = 0;

    if (y0 == y1) // 가로선은 따로 처리
    {
        /* 0번 점 X좌표가 1번 점보다 크면 두 점 X좌표 교환 */
        if (x0 > x1) {
            temp = x0;
            x0   = x1;
            x1   = temp;
        }

        /* X좌표 순회 */
        for (x = x0; x <= x1; x++) {
            OLED_DrawPoint(x, y0); // 점 순서대로 찍기
        }
    } else if (x0 == x1) // 세로선은 따로 처리
    {
        /* 0번 점 Y좌표가 1번 점보다 크면 두 점 Y좌표 교환 */
        if (y0 > y1) {
            temp = y0;
            y0   = y1;
            y1   = temp;
        }

        /* Y좌표 순회 */
        for (y = y0; y <= y1; y++) {
            OLED_DrawPoint(x0, y); // 점 순서대로 찍기
        }
    } else // 기울기 있는 선
    {
        /* Bresenham 알고리즘으로 직선 그리기, 부동소수점 연산 없이 효율적 */
        /* 참고 문서: https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf */
        /* 참고 강좌: https://www.bilibili.com/video/BV1364y1d7Lo */

        if (x0 > x1) // 0번 점 X좌표가 1번 점보다 큼
        {
            /* 두 점 좌표 교환 */
            /* 교환해도 선 그리기에 영향 없고, 방향을 1·2·3·4사분면에서 1·4사분면으로 바꿈 */
            temp = x0;
            x0   = x1;
            x1   = temp;
            temp = y0;
            y0   = y1;
            y1   = temp;
        }

        if (y0 > y1) // 0번 점 Y좌표가 1번 점보다 큼
        {
            /* Y좌표 부호 반전 */
            /* 반전하면 선 그리기에 영향 있으나, 방향을 1·4사분면에서 1사분면으로 바꿈 */
            y0 = -y0;
            y1 = -y1;

            /* yflag 플래그 세워 뒤바뀐 좌표 기억, 실제 점 찍을 때 복원 */
            yflag = 1;
        }

        if (y1 - y0 > x1 - x0) // 기울기가 1보다 큼
        {
            /* X좌표와 Y좌표 교환 */
            /* 교환하면 선 그리기에 영향 있으나, 방향을 1사분면 0~90도에서 0~45도로 바꿈 */
            temp = x0;
            x0   = y0;
            y0   = temp;
            temp = x1;
            x1   = y1;
            y1   = temp;

            /* xyflag 플래그 세워 뒤바뀐 좌표 기억, 실제 점 찍을 때 복원 */
            xyflag = 1;
        }

        /* 아래는 Bresenham 직선 알고리즘 */
        /* 알고리즘 요구사항: 방향은 1사분면 0~45도 범위여야 함 */
        dx     = x1 - x0;
        dy     = y1 - y0;
        incrE  = 2 * dy;
        incrNE = 2 * (dy - dx);
        d      = 2 * dy - dx;
        x      = x0;
        y      = y0;

        /* 시작점 찍기, 플래그에 따라 좌표 복원 */
        if (yflag && xyflag) {
            OLED_DrawPoint(y, -x);
        } else if (yflag) {
            OLED_DrawPoint(x, -y);
        } else if (xyflag) {
            OLED_DrawPoint(y, x);
        } else {
            OLED_DrawPoint(x, y);
        }

        while (x < x1) // X축 점마다 순회
        {
            x++;
            if (d < 0) // 다음 점이 현재 점 동쪽
            {
                d += incrE;
            } else // 다음 점이 현재 점 북동쪽
            {
                y++;
                d += incrNE;
            }

            /* 각 점 찍기, 플래그에 따라 좌표 복원 */
            if (yflag && xyflag) {
                OLED_DrawPoint(y, -x);
            } else if (yflag) {
                OLED_DrawPoint(x, -y);
            } else if (xyflag) {
                OLED_DrawPoint(y, x);
            } else {
                OLED_DrawPoint(x, y);
            }
        }
    }
}

/**
 * 함수: OLED 사각형
 * 매개변수: X 사각형 왼쪽 상단 X 좌표, 범위: 0~127
 * 매개변수: Y 사각형 왼쪽 상단 Y 좌표, 범위: 0~63
 * 매개변수: Width 사각형 너비, 범위: 0~128
 * 매개변수: Height 사각형 높이, 범위: 0~64
 * 매개변수: IsFilled 사각형 채움 여부
 *           범위: OLED_UNFILLED      채우지 않음
 *                 OLED_FILLED         채움
 * 반환값: 없음
 * 설명: 이 함수를 호출한 뒤 실제 화면에 반영하려면 업데이트 함수를 호출해야 함
 */
void OLED_DrawRectangle(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, uint8_t IsFilled)
{
    uint8_t i, j;
    if (!IsFilled) // 채우지 않음
    {
        /* 위아래 X좌표 순회하며 사각형 위아래 선 그리기 */
        for (i = X; i < X + Width; i++) {
            OLED_DrawPoint(i, Y);
            OLED_DrawPoint(i, Y + Height - 1);
        }
        /* 좌우 Y좌표 순회하며 사각형 좌우 선 그리기 */
        for (i = Y; i < Y + Height; i++) {
            OLED_DrawPoint(X, i);
            OLED_DrawPoint(X + Width - 1, i);
        }
    } else // 채움
    {
        /* X좌표 순회 */
        for (i = X; i < X + Width; i++) {
            /* Y좌표 순회 */
            for (j = Y; j < Y + Height; j++) {
                /* 지정 영역에 점 찍어 사각형 가득 채우기 */
                OLED_DrawPoint(i, j);
            }
        }
    }
}

/**
 * 함수: OLED 삼각형
 * 매개변수: X0 첫 번째 꼭짓점 X 좌표, 범위: 0~127
 * 매개변수: Y0 첫 번째 꼭짓점 Y 좌표, 범위: 0~63
 * 매개변수: X1 두 번째 꼭짓점 X 좌표, 범위: 0~127
 * 매개변수: Y1 두 번째 꼭짓점 Y 좌표, 범위: 0~63
 * 매개변수: X2 세 번째 꼭짓점 X 좌표, 범위: 0~127
 * 매개변수: Y2 세 번째 꼭짓점 Y 좌표, 범위: 0~63
 * 매개변수: IsFilled 삼각형 채움 여부
 *           범위: OLED_UNFILLED      채우지 않음
 *                 OLED_FILLED         채움
 * 반환값: 없음
 * 설명: 이 함수를 호출한 뒤 실제 화면에 반영하려면 업데이트 함수를 호출해야 함
 */
void OLED_DrawTriangle(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t X2, uint8_t Y2, uint8_t IsFilled)
{
    uint8_t minx = X0, miny = Y0, maxx = X0, maxy = Y0;
    uint8_t i, j;
    int16_t vx[] = {X0, X1, X2};
    int16_t vy[] = {Y0, Y1, Y2};

    if (!IsFilled) // 채우지 않음
    {
        /* 세 점을 직선으로 연결하는 함수 호출 */
        OLED_DrawLine(X0, Y0, X1, Y1);
        OLED_DrawLine(X0, Y0, X2, Y2);
        OLED_DrawLine(X1, Y1, X2, Y2);
    } else // 채움
    {
        /* 세 점 중 최소 X, Y 좌표 찾기 */
        if (X1 < minx) { minx = X1; }
        if (X2 < minx) { minx = X2; }
        if (Y1 < miny) { miny = Y1; }
        if (Y2 < miny) { miny = Y2; }

        /* 세 점 중 최대 X, Y 좌표 찾기 */
        if (X1 > maxx) { maxx = X1; }
        if (X2 > maxx) { maxx = X2; }
        if (Y1 > maxy) { maxy = Y1; }
        if (Y2 > maxy) { maxy = Y2; }

        /* 최소~최대 좌표 사이 사각형이 채워야 할 영역 */
        /* 이 영역의 모든 점 순회 */
        /* X좌표 순회 */
        for (i = minx; i <= maxx; i++) {
            /* Y좌표 순회 */
            for (j = miny; j <= maxy; j++) {
                /* OLED_pnpoly로 점이 삼각형 안에 있는지 판단 */
                /* 안에 있으면 점 찍고, 아니면 무시 */
                if (OLED_pnpoly(3, vx, vy, i, j)) { OLED_DrawPoint(i, j); }
            }
        }
    }
}

/**
 * 함수: OLED 원 그리기
 * 매개변수: X 원 중심 X 좌표, 범위: 0~127
 * 매개변수: Y 원 중심 Y 좌표, 범위: 0~63
 * 매개변수: Radius 원 반지름, 범위: 0~255
 * 매개변수: IsFilled 원 채움 여부
 *           범위: OLED_UNFILLED      채우지 않음
 *                 OLED_FILLED         채움
 * 반환값: 없음
 * 설명: 이 함수를 호출한 뒤 실제 화면에 반영하려면 업데이트 함수를 호출해야 함
 */
void OLED_DrawCircle(uint8_t X, uint8_t Y, uint8_t Radius, uint8_t IsFilled)
{
    int16_t x, y, d, j;

    /* Bresenham 알고리즘으로 원 그리기, 부동소수점 연산 없이 효율적 */
    /* 참고 문서: https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf */
    /* 참고 강좌: https://www.bilibili.com/video/BV1VM4y1u7wJ */

    d = 1 - Radius;
    x = 0;
    y = Radius;

    /* 8분원 각 시작점 찍기 */
    OLED_DrawPoint(X + x, Y + y);
    OLED_DrawPoint(X - x, Y - y);
    OLED_DrawPoint(X + y, Y + x);
    OLED_DrawPoint(X - y, Y - x);```c
if (IsFilled) // 지정 원 채우기
    {
        /*시작점 Y좌표 순회*/
        for (j = -y; j < y; j++) {
            /*지정 영역에 점 찍기, 부분 원 채우기*/
            OLED_DrawPoint(X, Y + j);
        }
    }

    while (x < y) // X축의 각 점 순회
    {
        x++;
        if (d < 0) // 다음 점이 현재 점의 동쪽
        {
            d += 2 * x + 1;
        } else // 다음 점이 현재 점의 남동쪽
        {
            y--;
            d += 2 * (x - y) + 1;
        }

        /*각 8분원 호의 점 그리기*/
        OLED_DrawPoint(X + x, Y + y);
        OLED_DrawPoint(X + y, Y + x);
        OLED_DrawPoint(X - x, Y - y);
        OLED_DrawPoint(X - y, Y - x);
        OLED_DrawPoint(X + x, Y - y);
        OLED_DrawPoint(X + y, Y - x);
        OLED_DrawPoint(X - x, Y + y);
        OLED_DrawPoint(X - y, Y + x);

        if (IsFilled) // 지정 원 채우기
        {
            /*중간 부분 순회*/
            for (j = -y; j < y; j++) {
                /*지정 영역에 점 찍기, 부분 원 채우기*/
                OLED_DrawPoint(X + x, Y + j);
                OLED_DrawPoint(X - x, Y + j);
            }

            /*양측 부분 순회*/
            for (j = -x; j < x; j++) {
                /*지정 영역에 점 찍기, 부분 원 채우기*/
                OLED_DrawPoint(X - y, Y + j);
                OLED_DrawPoint(X + y, Y + j);
            }
        }
    }
}

/**
 * 함    수: OLED 타원 그리기
 * 매개변수: X 지정 타원의 중심 횡좌표, 범위: 0~127
 * 매개변수: Y 지정 타원의 중심 종좌표, 범위: 0~63
 * 매개변수: A 지정 타원의 횡방향 반축 길이, 범위: 0~255
 * 매개변수: B 지정 타원의 종방향 반축 길이, 범위: 0~255
 * 매개변수: IsFilled 지정 타원 채우기 여부
 *           범위: OLED_UNFILLED\t\t채우지 않음
 *                 OLED_FILLED\t\t\t채움
 * 반 환 값: 없음
 * 설    명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_DrawEllipse(uint8_t X, uint8_t Y, uint8_t A, uint8_t B, uint8_t IsFilled)
{
    int16_t x, y, j;
    int16_t a = A, b = B;
    float d1, d2;

    /*Bresenham 알고리즘으로 타원 그리기, 일부 느린 부동소수점 연산 방지, 효율 ↑*/
    /*참고 링크: https://blog.csdn.net/myf_666/article/details/128167392*/

    x  = 0;
    y  = b;
    d1 = b * b + a * a * (-b + 0.5);

    if (IsFilled) // 지정 타원 채우기
    {
        /*시작점 Y좌표 순회*/
        for (j = -y; j < y; j++) {
            /*지정 영역에 점 찍기, 부분 타원 채우기*/
            OLED_DrawPoint(X, Y + j);
            OLED_DrawPoint(X, Y + j);
        }
    }

    /*타원 호의 시작점 그리기*/
    OLED_DrawPoint(X + x, Y + y);
    OLED_DrawPoint(X - x, Y - y);
    OLED_DrawPoint(X - x, Y + y);
    OLED_DrawPoint(X + x, Y - y);

    /*타원 중간 부분 그리기*/
    while (b * b * (x + 1) < a * a * (y - 0.5)) {
        if (d1 <= 0) // 다음 점이 현재 점의 동쪽
        {
            d1 += b * b * (2 * x + 3);
        } else // 다음 점이 현재 점의 남동쪽
        {
            d1 += b * b * (2 * x + 3) + a * a * (-2 * y + 2);
            y--;
        }
        x++;

        if (IsFilled) // 지정 타원 채우기
        {
            /*중간 부분 순회*/
            for (j = -y; j < y; j++) {
                /*지정 영역에 점 찍기, 부분 타원 채우기*/
                OLED_DrawPoint(X + x, Y + j);
                OLED_DrawPoint(X - x, Y + j);
            }
        }

        /*타원 중간 부분 호 그리기*/
        OLED_DrawPoint(X + x, Y + y);
        OLED_DrawPoint(X - x, Y - y);
        OLED_DrawPoint(X - x, Y + y);
        OLED_DrawPoint(X + x, Y - y);
    }

    /*타원 양측 부분 그리기*/
    d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;

    while (y > 0) {
        if (d2 <= 0) // 다음 점이 현재 점의 동쪽
        {
            d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3);
            x++;

        } else // 다음 점이 현재 점의 남동쪽
        {
            d2 += a * a * (-2 * y + 3);
        }
        y--;

        if (IsFilled) // 지정 타원 채우기
        {
            /*양측 부분 순회*/
            for (j = -y; j < y; j++) {
                /*지정 영역에 점 찍기, 부분 타원 채우기*/
                OLED_DrawPoint(X + x, Y + j);
                OLED_DrawPoint(X - x, Y + j);
            }
        }

        /*타원 양측 부분 호 그리기*/
        OLED_DrawPoint(X + x, Y + y);
        OLED_DrawPoint(X - x, Y - y);
        OLED_DrawPoint(X - x, Y + y);
        OLED_DrawPoint(X + x, Y - y);
    }
}

/**
 * 함    수: OLED 원호 그리기
 * 매개변수: X 지정 원호의 중심 횡좌표, 범위: 0~127
 * 매개변수: Y 지정 원호의 중심 종좌표, 범위: 0~63
 * 매개변수: Radius 지정 원호의 반지름, 범위: 0~255
 * 매개변수: StartAngle 지정 원호의 시작 각도, 범위: -180~180
 *           수평 오른쪽이 0도, 수평 왼쪽이 180도 또는 -180도, 아래가 양수, 위가 음수, 시계 방향 회전
 * 매개변수: EndAngle 지정 원호의 종료 각도, 범위: -180~180
 *           수평 오른쪽이 0도, 수평 왼쪽이 180도 또는 -180도, 아래가 양수, 위가 음수, 시계 방향 회전
 * 매개변수: IsFilled 지정 원호 채우기 여부, 채우면 부채꼴
 *           범위: OLED_UNFILLED\t\t채우지 않음
 *                 OLED_FILLED\t\t\t채움
 * 반 환 값: 없음
 * 설    명: 이 함수 호출 후 실제 화면에 표시하려면 업데이트 함수를 추가로 호출해야 함
 */
void OLED_DrawArc(uint8_t X, uint8_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, uint8_t IsFilled)
{
    int16_t x, y, d, j;

    /*이 함수는 Bresenham 원 그리기 알고리즘 차용*/

    d = 1 - Radius;
    x = 0;
    y = Radius;

    /*원의 각 점을 그릴 때 지정 각도 내에 있는지 판단, 있으면 점 그리기, 없으면 무시*/
    if (OLED_IsInAngle(x, y, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y + y); }
    if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y - y); }
    if (OLED_IsInAngle(y, x, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y + x); }
    if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y - x); }

    if (IsFilled) // 지정 원호 채우기
    {
        /*시작점 Y좌표 순회*/
        for (j = -y; j < y; j++) {
            /*채우기 원의 각 점을 그릴 때 지정 각도 내에 있는지 판단, 있으면 점 그리기, 없으면 무시*/
            if (OLED_IsInAngle(0, j, StartAngle, EndAngle)) { OLED_DrawPoint(X, Y + j); }
        }
    }

    while (x < y) // X축의 각 점 순회
    {
        x++;
        if (d < 0) // 다음 점이 현재 점의 동쪽
        {
            d += 2 * x + 1;
        } else // 다음 점이 현재 점의 남동쪽
        {
            y--;
            d += 2 * (x - y) + 1;
        }

        /*원의 각 점을 그릴 때 지정 각도 내에 있는지 판단, 있으면 점 그리기, 없으면 무시*/
        if (OLED_IsInAngle(x, y, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y + y); }
        if (OLED_IsInAngle(y, x, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y + x); }
        if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y - y); }
        if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y - x); }
        if (OLED_IsInAngle(x, -y, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y - y); }
        if (OLED_IsInAngle(y, -x, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y - x); }
        if (OLED_IsInAngle(-x, y, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y + y); }
        if (OLED_IsInAngle(-y, x, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y + x); }

        if (IsFilled) // 지정 원호 채우기
        {
            /*중간 부분 순회*/
            for (j = -y; j < y; j++) {
                /*채우기 원의 각 점을 그릴 때 지정 각도 내에 있는지 판단, 있으면 점 그리기, 없으면 무시*/
                if (OLED_IsInAngle(x, j, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y + j); }
                if (OLED_IsInAngle(-x, j, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y + j); }
            }

            /*양측 부분 순회*/
            for (j = -x; j < x; j++) {
                /*채우기 원의 각 점을 그릴 때 지정 각도 내에 있는지 판단, 있으면 점 그리기, 없으면 무시*/
                if (OLED_IsInAngle(-y, j, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y + j); }
                if (OLED_IsInAngle(y, j, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y + j); }
            }
        }
    }
}

/*********************기능 함수*/

/*****************江协科技|저작권所有****************/
/*****************jiangxiekeji.com*****************/

OLED.h 파일:

#ifndef __OLED_H
#define __OLED_H

#include <stdint.h>
#include "OLED_Data.h"

/*매개변수 매크로 정의*********************/\n\n/*FontSize 매개변수 값*/\n/*이 매개변수는 판단뿐만 아니라 가로 문자 오프셋 계산에도 사용되며, 기본값은 글꼴 픽셀 너비입니다*/\n#define OLED_8X16\t\t\t\t8\n#define OLED_6X8\t\t\t\t6\n\n/*IsFilled 매개변수 값*/\n#define OLED_UNFILLED\t\t\t0\n#define OLED_FILLED\t\t\t\t1\n\n/*********************매개변수 매크로 정의*/\n\n\n/*함수 선언*********************/\n\n/*초기화 함수*/\nvoid OLED_Init(void);\n\n/*업데이트 함수*/\nvoid OLED_Update(void);\nvoid OLED_UpdateArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height);\n\n/*디스플레이 버퍼 제어 함수*/\nvoid OLED_Clear(void);\nvoid OLED_ClearArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height);\nvoid OLED_Reverse(void);\nvoid OLED_ReverseArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height);\n\n/*표시 함수*/\nvoid OLED_ShowChar(uint8_t X, uint8_t Y, char Char, uint8_t FontSize);\nvoid OLED_ShowString(uint8_t X, uint8_t Y, char *String, uint8_t FontSize);\nvoid OLED_ShowNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize);\nvoid OLED_ShowSignedNum(uint8_t X, uint8_t Y, int32_t Number, uint8_t Length, uint8_t FontSize);\nvoid OLED_ShowHexNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize);\nvoid OLED_ShowBinNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize);\nvoid OLED_ShowFloatNum(uint8_t X, uint8_t Y, double Number, uint8_t IntLength, uint8_t FraLength, uint8_t FontSize);\nvoid OLED_ShowChinese(uint8_t X, uint8_t Y, char *Chinese);\nvoid OLED_ShowImage(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image);\nvoid OLED_Printf(uint8_t X, uint8_t Y, uint8_t FontSize, char *format, ...);\n\n/*그리기 함수*/\nvoid OLED_DrawPoint(uint8_t X, uint8_t Y);\nuint8_t OLED_GetPoint(uint8_t X, uint8_t Y);\nvoid OLED_DrawLine(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1);\nvoid OLED_DrawRectangle(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, uint8_t IsFilled);\nvoid OLED_DrawTriangle(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t X2, uint8_t Y2, uint8_t IsFilled);\nvoid OLED_DrawCircle(uint8_t X, uint8_t Y, uint8_t Radius, uint8_t IsFilled);\nvoid OLED_DrawEllipse(uint8_t X, uint8_t Y, uint8_t A, uint8_t B, uint8_t IsFilled);\nvoid OLED_DrawArc(uint8_t X, uint8_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, uint8_t IsFilled);\n\n/*********************함수 선언*/\n\n#endif\n\n\n/*****************강협기술 | 저작권 소유****************/\n/*****************jiangxiekeji.com*****************/\n\n```\n\n**OLED_Data.c 파일:**\n\n```c
#include "OLED_Data.h"

/**
  * 데이터 저장 형식:
  * 세로 8점, 상위 비트가 아래쪽, 왼쪽에서 오른쪽으로, 위에서 아래로
  * 각 Bit는 한 픽셀에 대응
  * 
  *      B0 B0                  B0 B0
  *      B1 B1                  B1 B1
  *      B2 B2                  B2 B2
  *      B3 B3  ------------->  B3 B3 --
  *      B4 B4                  B4 B4  |
  *      B5 B5                  B5 B5  |
  *      B6 B6                  B6 B6  |
  *      B7 B7                  B7 B7  |
  *                                    |
  *  -----------------------------------
  *  |   
  *  |   B0 B0                  B0 B0
  *  |   B1 B1                  B1 B1
  *  |   B2 B2                  B2 B2
  *  --> B3 B3  ------------->  B3 B3
  *      B4 B4                  B4 B4
  *      B5 B5                  B5 B5
  *      B6 B6                  B6 B6
  *      B7 B7                  B7 B7
  * 
  */

/*ASCII 글꼴 데이터*********************//*너비 8픽셀, 높이 16픽셀*/
const uint8_t OLED_F8x16[][16] =
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//   0
	0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,// ! 1
	0x00,0x16,0x0E,0x00,0x16,0x0E,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// " 2
	0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
	0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,// # 3
	0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
	0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,// $ 4
	0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
	0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,// % 5
	0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
	0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,// & 6
	0x00,0x00,0x00,0x16,0x0E,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ' 7
	0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
	0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,// ( 8
	0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
	0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,// ) 9
	0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
	0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,// * 10
	0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
	0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,// + 11
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,// , 12
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,// - 13
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,// . 14
	0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
	0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,// / 15
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,// 0 16
	0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// 1 17
	0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
	0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,// 2 18
	0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
	0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,// 3 19
	0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
	0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,// 4 20
	0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
	0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,// 5 21
	0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
	0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,// 6 22
	0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,// 7 23
	0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
	0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,// 8 24
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,// 9 25
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,// : 26
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,// ; 27
	0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
	0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,// < 28
	0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
	0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,// = 29
	0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
	0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,// > 30
	0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
	0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,// ? 31
};? 31
	0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
	0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,// @ 32
	0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
	0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,// A 33
	0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,// B 34
	0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
	0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,// C 35
	0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,// D 36
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,// E 37
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,// F 38
	0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
	0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,// G 39
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,// H 40
	0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// I 41
	0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
	0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,// J 42
	0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
	0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,// K 43
	0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,// L 44
	0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
	0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,// M 45
	0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,// N 46
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,// O 47
	0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
	0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,// P 48
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,// Q 49
	0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,// R 50
	0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
	0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,// S 51
	0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,// T 52
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,// U 53
	0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
	0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,// V 54
	0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
	0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,// W 55
	0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
	0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,// X 56
	0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,// Y 57
	0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,// Z 58
	0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,// [ 59
	0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,// \ 60
	0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
	0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,// ] 61
	0x00,0x20,0x10,0x08,0x04,0x08,0x10,0x20,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ^ 62
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,// _ 63
	0x00,0x02,0x04,0x08,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ` 64
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,// a 65
	0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
	0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,// b 66
	0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,// c 67
	0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,// d 68
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,// e 69
	0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// f 70
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,// g 71
	0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,// h 72
	0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// i 73
	0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
	0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,// j 74
	0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,// k 75
	0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// l 76
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,// m 77
	0x00,0x80,0x80,0x00,0x80,0x80,0x00,0x00,
	0x00,0x20,0x3F,0x21,0x00,0x20,0x3F,0x20,// n 78
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,// o 79
	0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
	0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,// p 80
	0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
	0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,// q 81
	0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,// r 82
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,// s 83
	0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
	0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,// t 84
	0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,// u 85
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,// v 86
	0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
	0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,// w 87
	0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,// x 88
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,// y 89
	0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,// z 90
	0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
	0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,// { 91
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,// | 92
	0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
	0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,// } 93
	0x00,0x80,0x40,0x40,0x80,0x00,0x00,0x80,
	0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,// ~ 94
};/*너비 6픽셀, 높이 8픽셀*/
const uint8_t OLED_F6x8[][6] = 
{
	0x00,0x00,0x00,0x00,0x00,0x00,//   0
	0x00,0x00,0x00,0x2F,0x00,0x00,// ! 1
	0x00,0x00,0x07,0x00,0x07,0x00,// " 2
	0x00,0x14,0x7F,0x14,0x7F,0x14,// # 3
	0x00,0x24,0x2A,0x7F,0x2A,0x12,// $ 4
	0x00,0x23,0x13,0x08,0x64,0x62,// % 5
	0x00,0x36,0x49,0x55,0x22,0x50,// & 6
	0x00,0x00,0x00,0x07,0x00,0x00,// ' 7
	0x00,0x00,0x1C,0x22,0x41,0x00,// ( 8
	0x00,0x00,0x41,0x22,0x1C,0x00,// ) 9
	0x00,0x14,0x08,0x3E,0x08,0x14,// * 10
	0x00,0x08,0x08,0x3E,0x08,0x08,// + 11
	0x00,0x00,0x00,0xA0,0x60,0x00,// , 12
	0x00,0x08,0x08,0x08,0x08,0x08,// - 13
	0x00,0x00,0x60,0x60,0x00,0x00,// . 14
	0x00,0x20,0x10,0x08,0x04,0x02,// / 15
	0x00,0x3E,0x51,0x49,0x45,0x3E,// 0 16
	0x00,0x00,0x42,0x7F,0x40,0x00,// 1 17
	0x00,0x42,0x61,0x51,0x49,0x46,// 2 18
	0x00,0x21,0x41,0x45,0x4B,0x31,// 3 19
	0x00,0x18,0x14,0x12,0x7F,0x10,// 4 20
	0x00,0x27,0x45,0x45,0x45,0x39,// 5 21
	0x00,0x3C,0x4A,0x49,0x49,0x30,// 6 22
	0x00,0x01,0x71,0x09,0x05,0x03,// 7 23
	0x00,0x36,0x49,0x49,0x49,0x36,// 8 24
	0x00,0x06,0x49,0x49,0x29,0x1E,// 9 25
	0x00,0x00,0x36,0x36,0x00,0x00,// : 26
	0x00,0x00,0x56,0x36,0x00,0x00,// ; 27
	0x00,0x08,0x14,0x22,0x41,0x00,// < 28
	0x00,0x14,0x14,0x14,0x14,0x14,// = 29
	0x00,0x00,0x41,0x22,0x14,0x08,// > 30
	0x00,0x02,0x01,0x51,0x09,0x06,// ? 31
	0x00,0x3E,0x49,0x55,0x59,0x2E,// @ 32
	0x00,0x7C,0x12,0x11,0x12,0x7C,// A 33
	0x00,0x7F,0x49,0x49,0x49,0x36,// B 34
	0x00,0x3E,0x41,0x41,0x41,0x22,// C 35
	0x00,0x7F,0x41,0x41,0x22,0x1C,// D 36
	0x00,0x7F,0x49,0x49,0x49,0x41,// E 37
	0x00,0x7F,0x09,0x09,0x09,0x01,// F 38
	0x00,0x3E,0x41,0x49,0x49,0x7A,// G 39
	0x00,0x7F,0x08,0x08,0x08,0x7F,// H 40
	0x00,0x00,0x41,0x7F,0x41,0x00,// I 41
	0x00,0x20,0x40,0x41,0x3F,0x01,// J 42
	0x00,0x7F,0x08,0x14,0x22,0x41,// K 43
	0x00,0x7F,0x40,0x40,0x40,0x40,// L 44
	0x00,0x7F,0x02,0x0C,0x02,0x7F,// M 45
	0x00,0x7F,0x04,0x08,0x10,0x7F,// N 46
	0x00,0x3E,0x41,0x41,0x41,0x3E,// O 47
	0x00,0x7F,0x09,0x09,0x09,0x06,// P 48
	0x00,0x3E,0x41,0x51,0x21,0x5E,// Q 49
	0x00,0x7F,0x09,0x19,0x29,0x46,// R 50
	0x00,0x46,0x49,0x49,0x49,0x31,// S 51
	0x00,0x01,0x01,0x7F,0x01,0x01,// T 52
	0x00,0x3F,0x40,0x40,0x40,0x3F,// U 53
	0x00,0x1F,0x20,0x40,0x20,0x1F,// V 54
	0x00,0x3F,0x40,0x38,0x40,0x3F,// W 55
	0x00,0x63,0x14,0x08,0x14,0x63,// X 56
	0x00,0x07,0x08,0x70,0x08,0x07,// Y 57
	0x00,0x61,0x51,0x49,0x45,0x43,// Z 58
	0x00,0x00,0x7F,0x41,0x41,0x00,// [ 59
	0x00,0x02,0x04,0x08,0x10,0x20,// \ 60
	0x00,0x00,0x41,0x41,0x7F,0x00,// ] 61
	0x00,0x04,0x02,0x01,0x02,0x04,// ^ 62
	0x00,0x40,0x40,0x40,0x40,0x40,// _ 63
	0x00,0x00,0x01,0x02,0x04,0x00,// ` 64
	0x00,0x20,0x54,0x54,0x54,0x78,// a 65
	0x00,0x7F,0x48,0x44,0x44,0x38,// b 66
	0x00,0x38,0x44,0x44,0x44,0x20,// c 67
	0x00,0x38,0x44,0x44,0x48,0x7F,// d 68
	0x00,0x38,0x54,0x54,0x54,0x18,// e 69
	0x00,0x08,0x7E,0x09,0x01,0x02,// f 70
	0x00,0x18,0xA4,0xA4,0xA4,0x7C,// g 71
	0x00,0x7F,0x08,0x04,0x04,0x78,// h 72
	0x00,0x00,0x44,0x7D,0x40,0x00,// i 73
	0x00,0x40,0x80,0x84,0x7D,0x00,// j 74
	0x00,0x7F,0x10,0x28,0x44,0x00,// k 75
	0x00,0x00,0x41,0x7F,0x40,0x00,// l 76
	0x00,0x7C,0x04,0x18,0x04,0x78,// m 77
	0x00,0x7C,0x08,0x04,0x04,0x78,// n 78
	0x00,0x38,0x44,0x44,0x44,0x38,// o 79
	0x00,0xFC,0x24,0x24,0x24,0x18,// p 80
	0x00,0x18,0x24,0x24,0x18,0xFC,// q 81
	0x00,0x7C,0x08,0x04,0x04,0x08,// r 82
	0x00,0x48,0x54,0x54,0x54,0x20,// s 83
	0x00,0x04,0x3F,0x44,0x40,0x20,// t 84
	0x00,0x3C,0x40,0x40,0x20,0x7C,// u 85
	0x00,0x1C,0x20,0x40,0x20,0x1C,// v 86
	0x00,0x3C,0x40,0x30,0x40,0x3C,// w 87
	0x00,0x44,0x28,0x10,0x28,0x44,// x 88
	0x00,0x1C,0xA0,0xA0,0xA0,0x7C,// y 89
	0x00,0x44,0x64,0x54,0x4C,0x44,// z 90
	0x00,0x00,0x08,0x7F,0x41,0x00,// { 91
	0x00,0x00,0x00,0x7F,0x00,0x00,// | 92
	0x00,0x00,0x41,0x7F,0x08,0x00,// } 93
	0x00,0x08,0x04,0x08,0x10,0x08,// ~ 94
};
/*********************ASCII 글꼴 데이터*/

/*한자 글꼴 데이터*********************/

/*같은 한자는 한 번만 정의하면 되며, 한자는 순서에 관계없음*/
/*반드시 전부 한자 또는 전각 문자여야 하며, 반각 문자는 포함하지 않음*/

/*너비 16픽셀, 높이 16픽셀*/
const ChineseCell_t OLED_CF16x16[] = {
	",",
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x58,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

	"。",
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x18,0x24,0x24,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

	"你",
	0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00,
	0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00,

	"好",
	0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x80,0x82,0x82,0xE2,0x92,0x8A,0x86,0x80,0x00,
	0x40,0x22,0x15,0x08,0x16,0x61,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,

	"世",
	0x20,0x20,0x20,0xFE,0x20,0x20,0xFF,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x47,0x44,0x44,0x44,0x47,0x40,0x40,0x40,0x00,0x00,

	"界",
	0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0xFE,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00,
	0x08,0x08,0x04,0x84,0x62,0x1E,0x01,0x00,0x01,0xFE,0x02,0x04,0x04,0x08,0x08,0x00,
	/*위의 형식에 따라 이 위치에 새 한자 데이터 추가*/
	//...

	/*지정된 한자를 찾지 못했을 때 표시되는 기본 그림(물음표가 들어 있는 사각형), 배열의 가장 끝에 위치해야 함*/
	"",
	0xFF,0x01,0x01,0x01,0x31,0x09,0x09,0x09,0x09,0x89,0x71,0x01,0x01,0x01,0x01,0xFF,
	0xFF,0x80,0x80,0x80,0x80,0x80,0x80,0x96,0x81,0x80,0x80,0x80,0x80,0x80,0x80,0xFF,
};

/*********************한자 글꼴 데이터*/


/*이미지 데이터*********************/

/*테스트 이미지(내부에 다이오드 기호가 있는 사각형), 너비 16픽셀, 높이 16픽셀*/
const uint8_t Diode[] = {
	0xFF,0x01,0x81,0x81,0x81,0xFD,0x89,0x91,0xA1,0xC1,0xFD,0x81,0x81,0x81,0x01,0xFF,
	0xFF,0x80,0x80,0x80,0x80,0x9F,0x88,0x84,0x82,0x81,0x9F,0x80,0x80,0x80,0x80,0xFF,
};

/*위의 형식에 따라 이 위치에 새 이미지 데이터 추가*/
//...

/*********************이미지 데이터*/


/*****************江协科技|저작권所有****************/
/*****************jiangxiekeji.com*****************/

OLED_Data.h 파일:

#ifndef __OLED_DATA_H
#define __OLED_DATA_H

#include <stdint.h>

/*중문자 바이트 너비*/
#define OLED_CHN_CHAR_WIDTH			3		//UTF-8 인코딩 형식은 3, GB2312 인코딩 형식은 2

/*글꼴 기본 단위*/
typedef struct 
{
	char Index[OLED_CHN_CHAR_WIDTH + 1];	//한자 인덱스
	uint8_t Data[32];						//글꼴 데이터
} ChineseCell_t;

/*ASCII 글꼴 데이터 선언*/
extern const uint8_t OLED_F8x16[][16];
extern const uint8_t OLED_F6x8[][6];

/*한자 글꼴 데이터 선언*/
extern const ChineseCell_t OLED_CF16x16[];

/*이미지 데이터 선언*/
extern const uint8_t Diode[];
/*위의 형식에 따라 이 위치에 새 이미지 데이터 선언 추가*/
//...

#endif


/*****************江协科技|저작권所有****************/
/*****************jiangxiekeji.com*****************/

추천 읽기