STM32로 SHT3x 시리즈 온습도 센서 읽기, 표준 라이브러리와 HAL 라이브러리

STM32에서 SHT3x 시리즈(SHT30, SHT31, SHT35) 온습도 센서 데이터를 읽고 0.96인치 OLED 화면에 표시하기

아래에 두 가지 코드를 제공합니다. 하나는 표준 라이브러리를 사용한 하드웨어 I2C 버전이고, 다른 하나는 HAL 라이브러리를 사용한 소프트웨어 시뮬레이션 IIC 버전입니다.

제가 사용하는 마이크로컨트롤러는 STM32F103C8T6이고, 온습도 센서는 SHT30입니다.

STM32 소프트웨어 I2C로 AM2320 온습도 센서 데이터 읽기: https://blog.zeruns.com/archives/695.html
STM32 하드웨어 I2C로 SHTC3 온습도 센서 읽기: https://blog.zeruns.com/archives/692.html
STM32 마이크로컨트롤러로 AHT10 온습도 센서 데이터 읽기: https://blog.zeruns.com/archives/693.html

전자/마이크로컨트롤러 기술 교류 그룹: 2169025065

구현 효과도


I2C 프로토콜 소개

I2C 통신 프로토콜(Inter-Integrated Circuit)은 Philips 회사에서 개발했으며, 핀 수가 적고 하드웨어 구현이 간단하며 확장성이 뛰어나고 USART, CAN 등의 통신 프로토콜에 필요한 외부 송수신 장치(레벨 변환 칩)가 필요 없어 현재 시스템 내 여러 집적회로(IC) 간의 통신에 널리 사용되고 있습니다.

I2C는 데이터 버스 SDA(Serial Data Line) 하나만 있으며, 이는 직렬 데이터 버스로 한 번에 한 비트씩 데이터를 전송하므로 직렬 통신에 속하며 반이중 통신을 사용합니다.

반이중 통신: 양방향 통신이 가능하지만 두 방향에서 동시에 진행할 수 없으며 번갈아가며 진행해야 하므로, 실제로는 방향을 전환할 수 있는 단방향 통신으로 이해할 수 있으며, 같은 시간에는 한 방향으로만 전송되고 데이터 라인 하나만 필요합니다. I2C 통신 프로토콜을 물리 계층과 프로토콜 계층으로 나누면, 물리 계층은 통신 시스템의 기계적, 전자적 기능 부분의 특성(하드웨어 부분)을 규정하여 물리 매체에서 원본 데이터의 전송을 보장합니다. 프로토콜 계층은 주로 통신 논리를 규정하고 송수신 양쪽의 데이터 패킹, 언팩킹 표준을 통일합니다(소프트웨어 계층).

I2C 물리 계층

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

(1) 이는 다중 장치를 지원하는 버스입니다. "버스"는 여러 장치가 공유하는 신호선을 의미합니다. 하나의 I2C 통신 버스에는 여러 I2C 통신 장치를 연결할 수 있으며, 여러 통신 마스터 및 여러 통신 슬레이브를 지원합니다.

(2) 하나의 I2C 버스는 두 개의 버스 라인만 사용하며, 하나는 양방향 직렬 데이터 라인 SDA(Serial Data Line), 다른 하나는 직렬 클록 라인 SCL(Serial Clock Line)입니다. 데이터 라인은 데이터를 나타내는 데 사용되고, 클록 라인은 데이터 송수신 동기화에 사용됩니다.

(3) 버스는 풀업 저항을 통해 전원에 연결됩니다. I2C 장치가 유휴 상태일 때 고임피던스 상태를 출력하며, 모든 장치가 유휴 상태이고 고임피던스 상태를 출력할 때, 풀업 저항이 버스를 높은 전위로 당깁니다.

I2C 통신 시 마이크로컨트롤러 GPIO 포트는 오픈 드레인 출력으로 설정해야 하며, 그렇지 않으면 단락이 발생할 수 있습니다.

STM32의 I2C 관련 정보 및 사용 방법에 대한 자세한 내용은 다음 문서를 참조하세요: https://url.zeruns.com/JC0Ah

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

SHT3x 온습도 센서

SHT3x 데이터시트 다운로드 주소:

원본: https://url.zeruns.com/uWNnP 추출 코드: icqu

중국어 버전(기계 번역): https://url.zeruns.com/n9b9t 추출 코드: vedy

데이터시트를 검토하면 SHT30은 온도와 습도를 감지할 수 있는 센서임을 알 수 있습니다.
온도 범위: -40℃~125℃
습도 범위: 0%~100%
작동 전압: 2.4V~5.5V
통신 방식: I2C
클록 주파수: 0~1000kHz

다음과 같은 몇 가지 주요 정보를 찾을 수 있습니다.

SHT3x 장치 주소 및 읽기/쓰기 명령

SHT3x의 주소는 두 번째 핀을 높거나 낮은 전위에 연결하여 설정할 수 있습니다. 제가 타오바오에서 구입한 모듈의 센서 2핀은 저항을 통해 VCC에 연결되어 있으므로 기본값은 0x44입니다.

실제 사용 과정에서 SHT3x의 장치 주소는 읽기/쓰기 데이터/명령 방향 비트와 함께 하나의 바이트를 구성하여 동시에 전송되어야 하며, 바이트의 최하위 비트는 읽기/쓰기 데이터/명령 방향 비트이고 상위 7비트는 SHT3x의 장치 주소입니다.

I2C를 통해 SHT3x에 데이터 또는 명령을 쓰려면 I2C 시작 신호 후에 “1000 1000”, 즉 0x88을 SHT3x에 전송해야 합니다. 상위 7비트 “1000 100”(0x44)의 장치 주소로 주소 지정하는 것 외에도 최하위 비트 "0"을 통해 SHT3x에 다음이 쓰기 데이터 또는 명령 작업임을 알립니다.

I2C를 통해 SHT3x에서 데이터를 읽으려면 I2C 시작 신호 후에 “1000 1001”, 즉 0x89를 SHT3x에 전송해야 합니다. 상위 7비트 "1000 100"의 장치 주소로 주소 지정하는 것 외에도 최하위 비트 "1"을 통해 SHT3x에 다음이 데이터 읽기 작업임을 알립니다.
간단히 말해서 0x88은 데이터 쓰기를 나타내고 0x89는 데이터 읽기를 나타냅니다. 그러나 STM32 하드웨어 I2C를 사용할 때는 0x88만 입력하면 되며, 최하위 비트는 표준 라이브러리에서 처리합니다.

온습도 데이터 읽기

다양한 명령에 따라 데이터 획득 방식이 다르며, 단일 측정 및 주기 측정 모드가 있고, Clock Stretching Enable과 Disable의 차이가 있습니다.

Clock Stretching은 클록 스트레칭을 의미합니다. Clock Stretching Enable 명령을 사용하면 측정 명령을 전송한 후 SHT3x가 온습도 데이터를 측정하는 과정에서 SHT3x는 I2C의 클록 라인 SCL을 낮게 유지하여 마스터가 SHT3x에 명령을 보내는 것을 금지합니다. SHT3x가 온습도 데이터 측정을 완료할 때만 SHT3x가 클록 라인 SCL을 해제합니다.

Clock Stretching Disable 명령을 사용하면 SHT3x가 데이터를 측정하는 과정에서 SHT3x는 I2C의 클록 라인 SCL을 낮게 유지하지 않습니다. 다만 마스터가 SHT3x 데이터 측정 과정 중에 명령이나 데이터를 보낼 때 읽기 명령을 보내면 SHT3x는 부정 응답 신호를 반환하며, 측정이 완료된 후 읽기 명령을 보낼 때만 응답 신호를 반환합니다.

주기 측정 모드를 사용하면 센서가 자동으로 측정하고 데이터를 저장할 수 있으며, 초당 0.5/1/2/4/10회 측정하도록 설정할 수 있으며, 읽기 명령 0xE000을 통해 최신 측정 결과를 읽을 수 있습니다.

데이터시트에서 알 수 있듯이 하나의 측정 주기는 2단계로 구성됩니다:

  1. 측정 명령 전송
  2. 측정 완료 후 데이터 읽기.

위의 측정 명령 및 읽기 명령은 데이터시트에서 조회할 수 있습니다.

요약하면 다음과 같습니다:

  1. 측정 명령 전송: 먼저 쓰기 명령(0x88)을 전송하고, 다음으로 웨이크업 명령 상위 바이트(0x2C)를 전송한 후, 웨이크업 명령 하위 바이트(0x0D)를 전송합니다.
  2. 데이터 읽기 및 측정 완료 대기: 읽기 명령(0x89)을 전송하고 슬레이브가 SCL 버스를 해제할 때까지 대기합니다.
  3. 데이터 수신: 연속으로 6바이트 데이터를 수신합니다. 이 6바이트 중 첫 번째-두 번째 바이트는 온도 값이고, 세 번째 바이트는 온도 체크섬입니다. 네 번째-다섯 번째 바이트는 습도 값이고, 여섯 번째 바이트는 습도 체크섬입니다. 마지막 바이트를 수신한 후 부정 응답 신호를 전송합니다.
  4. 데이터 처리: 데이터에 대해 CRC 체크섬을 확인하고 처리합니다.

데이터 계산

SHT3x 데이터시트에서 알 수 있듯이

예를 들어, 수집된 습도 값이 0x6501이면 십진수로 변환하면 25857입니다.
그러면: 습도 = 100 * 25857 / (65536 - 1) = 39.45 (단위: %)
수집된 온도 값이 0x6600이면 십진수로 변환하면 26112입니다.
그러면: 온도 = -45 + 175 * 26112 / (65536 - 1) = 24.72 (단위: ℃)

필요한 부품

STM32 최소 시스템 보드: https://s.click.taobao.com/AJEGiNu
SHT3x 모듈: https://s.click.taobao.com/55xGiNu
OLED 모듈: https://s.click.taobao.com/0dlG0Ou
듀폰 선: https://s.click.taobao.com/xAkAJRu
브레드보드: https://s.click.taobao.com/ShJAJRu
ST-LINK V2: https://s.click.taobao.com/C8ftZRu

프로그램

여기서는 표준 라이브러리 버전의 main.c, SHT3x.c 및 oled.c 세 가지 주요 코드만 제시합니다. 나머지는 아래 링크의 압축 파일을 다운로드하세요.

표준 라이브러리 버전: https://url.zeruns.com/a49EX 추출 코드: 8nn5

HAL 라이브러리 버전: https://url.zeruns.com/p3og4 추출 코드: v9wc

SHT3x 및 OLED 모듈의 SCL은 PB6에, SDA는 PB7에 연결합니다.

VSCode를 사용하여 Keil 대신 STM32 및 51 마이크로컨트롤러 개발 구현: https://blog.zeruns.com/archives/690.html

main.c

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


uint16_t numlen(uint16_t num);

int main(void)
{
	IWDG_Configuration();	//초기화 독립 워치독
	OLED_Init();	 		//OLED 화면 초기화
	SHT3x_Init();	 		//SHT3x 초기화
	
	/*OLED_ShowString(1, 1, "T:");
	OLED_ShowString(3, 1, "H:");*/
	
	uint8_t Chinese[]={0,1};
  	OLED_ShowChinese(1,1,Chinese,2);//온도
  	uint8_t Chinese1[]={2,3};
	OLED_ShowChinese(3,1,Chinese1,2);//습도
  	uint8_t Chinese2[]={4};
	OLED_ShowChinese(1,11,Chinese2,1);//℃

	uint32_t a=0;
	uint16_t err_count=0;
	
	while (1)
	{
		a++;
		OLED_ShowNum(2, 14, a, 2);
		if(a==99)a=0;

		float Temp,Hum;	 		//온습도 데이터를 저장할 변수 선언

``````c
if(ReadSHT3x(&Hum,&Temp))	//온습도 데이터 읽기
		{
			if(Temp>=0)
			{
				char String[10];
				sprintf(String, "+%.2f", Temp);//문자열을 형식화하여 문자열 변수에 출력
				OLED_ShowString(1, 5, String);	//온도 표시

				sprintf(String, " %.2f%%", Hum);	//문자열을 형식화하여 문자열 변수에 출력
				OLED_ShowString(3, 5, String);	//습도 표시
			}else
			{
				char String[10];
				sprintf(String, "%.2f", Temp);//문자열을 형식화하여 문자열 변수에 출력
				OLED_ShowString(1, 5, String);	//온도 표시
				
				sprintf(String, " %.2f%%", Hum);	//문자열을 형식화하여 문자열 변수에 출력
				OLED_ShowString(3, 5, String);	//습도 표시
			}
		}
		else
		{
			err_count++;
			OLED_ShowNum(1,14, err_count, 2);	//오류 횟수 카운트 표시
		}
		
	/*
	https://blog.zeruns.com
	*/

		Delay_ms(200);	//200밀리초 지연

		IWDG_FeedDog();	//개에게 먹이 주기(워치독, 1초 이상 먹이를 주지 않으면 자동 리셋)
	}
}

/**
  * @brief  정수 길이 계산
  * @param  num 길이를 계산할 정수
  * @retval 길이 값
  */
uint16_t numlen(uint16_t num)
{
    uint16_t len = 0;        // 초기 길이는 0
    for(; num > 0; ++len)    // num이 0보다 큰지 판단, 그렇지 않으면 길이 +1
        num /= 10;	         // 나눗셈을 사용하여 연산, num이 1보다 작을 때까지
    return len;              // 길이 값 반환
}

SHT3x.c

#include "stm32f10x.h"
#include "Delay.h"

/*SHT3x 주소*/
#define SHT3x_ADDRESS 0x44<<1	//슬레이브 주소는 7비트이므로 1비트 왼쪽 시프트

/*어느 I2C를 사용할지 설정*/
#define I2Cx I2C1

/*
https://blog.zeruns.com
*/

/**
  * @brief  CRC 검증, CRC 다항식: x^8+x^5+x^4+1, 즉 0x31
  * @param  DAT 검증할 데이터
  * @retval 검증 코드
  */
uint8_t SHT3x_CRC_CAL(uint16_t DAT)
{
	uint8_t i,t,temp;
	uint8_t CRC_BYTE;

	CRC_BYTE = 0xFF;
	temp = (DAT>>8) & 0xFF;

	for(t = 0; t < 2; t++)
	{
		CRC_BYTE ^= temp;
		for(i = 0;i < 8;i ++)
		{
			if(CRC_BYTE & 0x80)
			{
				CRC_BYTE <<= 1;
				CRC_BYTE ^= 0x31;
			}
			else
			{
				CRC_BYTE <<= 1;
			}
		}

		if(t == 0)
		{
			temp = DAT & 0xFF;
		}
	}

	return CRC_BYTE;
}

/*시작 신호 전송*/
void SHT3x_I2C_START(){
    while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));//버스가 유휴 상태일 때까지 대기
	I2C_GenerateSTART(I2Cx, ENABLE);//시작 신호 전송
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//EV5 이벤트 감지
}

/*정지 신호 전송*/
void SHT3x_I2C_STOP(){
    I2C_GenerateSTOP(I2Cx, ENABLE);//정지 신호 전송
}

/**
  * @brief  두 개의 바이트 데이터 전송
  * @param  MSB 상위 8비트
  * @param  LSB 하위 8비트
  * @retval 없음
  */
void SHT3x_WriteByte(uint8_t MSB,uint8_t LSB)
{
	SHT3x_I2C_START();  //시작 신호 전송
	
	I2C_Send7bitAddress(I2Cx, SHT3x_ADDRESS, I2C_Direction_Transmitter);    //장치 쓰기 주소 전송
	while(I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);  //EV6 이벤트 감지

    I2C_SendData(I2Cx, MSB);//상위 8비트 데이터 전송
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//EV8 이벤트 감지
   
	I2C_SendData(I2Cx, LSB);//하위 8비트 데이터 전송
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//EV8 이벤트 감지
	
	I2C_GenerateSTOP(I2Cx, ENABLE);//정지 신호 전송
	
}

/**
  * @brief  데이터 읽기
  * @retval 읽은 바이트 데이터
  */
uint8_t SHT3x_ReadData()
{
    while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//EV7 이벤트 감지
	return I2C_ReceiveData(I2Cx);//데이터 읽기 및 반환
}

/*SHT3x 소프트웨어 리셋*/
void SHT3x_SoftReset(void)                    
{
    SHT3x_WriteByte(0x30,0xA2);    //SHT3x 재초기화
}

/*핀 초기화*/
void SHT3x_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//I2C1 클록 활성화
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//GPIOB 클록 활성화
 
	/*STM32F103 칩의 하드웨어 I2C1: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;               //GPIO 구성 구조체 정의
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//출력 모드를 오픈 드레인 출력으로 설정, 풀업 저항 필요
	GPIO_Init(GPIOB, &GPIO_InitStructure);              //GPIO 초기화
	
	I2C_DeInit(I2Cx);	//주변 장치 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 = 400000;	//I2C 전송 속도, 400K, 사용하는 칩 매뉴얼에서 지원하는 속도 확인.	
	I2C_Init(I2Cx, &I2C_InitStructure);         //I2C 초기화

	I2C_Cmd(I2Cx, ENABLE);  //I2C 활성화

	//SHT3x_WriteByte(0X27,0X21); //주기 데이터 수집 모드 (초당 10회, Medium Repeatability)

	Delay_us(300);//300마이크로초 지연
}

/**
  * @brief  SHT3x 데이터 읽기
  * @param  *Hum 습도
  * @param  *Temp 온도
  * @retval 1 - 읽기 성공; 0 - 읽기 실패
  */
uint8_t ReadSHT3x(float *Hum,float *Temp)
{
    uint16_t HumData,TempData,HumCRC,TempCRC;//읽은 데이터를 저장할 변수 선언
    
    //SHT3x_WriteByte(0XE0,0X00);   //명령 전송, 데이터 획득, 주기 데이터 수집 모드 사용

    SHT3x_WriteByte(0X2C,0X0D); //단일 측정 명령 전송 (클록 스트레칭 활성화, Medium Repeatability)
	
    SHT3x_I2C_START();//시작 신호 전송
    
	I2C_Send7bitAddress(I2Cx,SHT3x_ADDRESS,I2C_Direction_Receiver);//장치 읽기 주소 전송
	
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//EV6 이벤트 감지

    TempData = SHT3x_ReadData();//온도 상위 8비트 데이터 읽기
    TempData=TempData<<8;       //8비트 왼쪽 시프트
	TempData |= SHT3x_ReadData();//온도 하위 8비트 데이터 읽기
    TempCRC = SHT3x_ReadData(); //온도 CRC 검증 데이터 읽기
	
    HumData = SHT3x_ReadData(); //습도 상위 8비트 데이터 읽기
    HumData=HumData<<8;         //8비트 왼쪽 시프트
	HumData |= SHT3x_ReadData();//습도 하위 8비트 데이터 읽기
    I2C_AcknowledgeConfig(I2Cx,DISABLE);    //응답 신호 비활성화
    HumCRC = SHT3x_ReadData();  //습도 CRC 검증 데이터 읽기

    SHT3x_I2C_STOP();   //정지 신호 전송
    I2C_AcknowledgeConfig(I2Cx,ENABLE); //응답 신호 활성화
	

    if( SHT3x_CRC_CAL(HumData)==HumCRC && SHT3x_CRC_CAL(TempData)==TempCRC ){   //수신한 데이터에 대해 CRC 검증 수행
       *Hum = (float)HumData*100/(65536-1);        //수신한 16비트 이진 데이터를 10진 습도 데이터로 변환
       *Temp = (float)TempData*175/(65536-1)-45;   //수신한 16비트 이진 데이터를 10진 온도 데이터로 변환
       return 1;
    }
    return 0;
}

oled.c

#include "stm32f10x.h"
#include "OLED_Font.h"

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

/*어느 I2C를 사용할지 설정*/
#define I2Cx I2C1

/*
https://blog.zeruns.com
*/

/*핀 초기화*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//I2C1 클록 활성화
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//GPIOB 클록 활성화
 
	/*STM32F103 칩의 하드웨어 I2C: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//출력 모드를 오픈 드레인 출력으로 설정, 풀업 저항 필요
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2Cx);	//주변 장치 I2C 레지스터를 기본값으로 재설정
	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(I2Cx, &I2C_InitStructure);

	I2C_Cmd(I2Cx, ENABLE);
}

void I2C_WriteByte(uint8_t addr,uint8_t data)
{
	
	while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
	
	//시작 신호 전송
	I2C_GenerateSTART(I2Cx, ENABLE);
	//EV5 이벤트 감지
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	//장치 쓰기 주소 전송
	I2C_Send7bitAddress(I2Cx, OLED_ADDRESS, I2C_Direction_Transmitter);
	//EV6 이벤트 감지
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//장치 내부 주소 전송
	I2C_SendData(I2Cx, addr);
	//EV8_2 이벤트 감지
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	
	I2C_SendData(I2Cx, data);//데이터 전송
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	//정지 신호 전송
	I2C_GenerateSTOP(I2Cx, ENABLE);
}
 
/**
  * @brief  OLED 명령 쓰기
  * @param  Command 쓸 명령
  * @retval 없음
  */
void OLED_WriteCommand(unsigned char Command)//명령 쓰기
{
	I2C_WriteByte(0x00, Command);
}
 
/**
  * @brief  OLED 데이터 쓰기
  * @param  Data 쓸 데이터
  * @retval 없음
*/
void OLED_WriteData(unsigned char Data)//데이터 쓰기
{
	I2C_WriteByte(0x40, Data);
}

/**
  * @brief  OLED 커서 위치 설정
  * @param  Y 왼쪽 위 모서리를 원점으로, 아래 방향 좌표, 범위: 0~7
  * @param  X 왼쪽 위 모서리를 원점으로, 오른쪽 방향 좌표, 범위: 0~127
  * @retval 없음
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);	 			//Y 위치 설정
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//X 위치 하위 4비트 설정
	OLED_WriteCommand(0x00 | (X & 0x0F));	 		//X 위치 상위 4비트 설정
}

/**
  * @brief  OLED 화면 지우기
  * @param  없음
  * @retval 없음
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}
``````c
/**
  * @brief  OLED 부분 화면 지우기
  * @param  Line 행 위치, 범위: 1~4
  * @param  start 열 시작 위치, 범위: 1~16
  * @param  end 열 시작 위치, 범위: 1~16
  * @retval 없음
  */
void OLED_Clear_Part(uint8_t Line, uint8_t start, uint8_t end)
{  
	uint8_t i,Column;
	for(Column = start; Column <= end; Column++)
	{
		OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);	//상반부에 커서 위치 설정
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00);			//상반부 내용 표시
		}
		OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//하반부에 커서 위치 설정
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00);		//하반부 내용 표시
		}
	}
}

/**
  * @brief  OLED 한 문자 표시
  * @param  Line 행 위치, 범위: 1~4
  * @param  Column 열 위치, 범위: 1~16
  * @param  Char 표시할 문자, 범위: ASCII 가시 문자
  * @retval 없음
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//상반부에 커서 위치 설정
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//상반부 내용 표시
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//하반부에 커서 위치 설정
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//하반부 내용 표시
	}
}

/**
  * @brief  OLED 중국어 문자 표시
  * @param  Line 행 위치, 범위: 1~4
  * @param  Column 열 위치, 범위: 1~16
  * @param  Chinese 표시할 중국어 문자의 글꼴 배열 위치
  * @retval 없음
  */
void OLED_ShowWord(uint8_t Line, uint8_t Column, uint8_t Chinese)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i]);	
	}
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8 + 8);
	for (i = 1; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i+8]);	
	}
	OLED_SetCursor((Line - 1) * 2 +1, (Column - 1) * 8);
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i+16]);
	}
	OLED_SetCursor((Line - 1) * 2 +1, (Column - 1) * 8 + 8);
	for (i = 1; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i+16+8]);
	}
}

/**
  * @brief  OLED 중국어 문자열 표시
  * @param  Line 행 위치, 범위: 1~4
  * @param  Column 열 위치, 범위: 1~16
  * @param  Chinese[] 표시할 중국어 문자의 글꼴 배열 위치, 배열에 각 문자의 위치 저장
  * @param	Length 표시할 중국어의 길이, 범위: 1~8
  * @retval 없음
  */
void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t *Chinese,uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)
	{
		OLED_ShowWord(Line, Column + i*2,Chinese[i]);
	}
}

/**
  * @brief  OLED 문자열 표시
  * @param  Line 시작 행 위치, 범위: 1~4
  * @param  Column 시작 열 위치, 범위: 1~16
  * @param  String 표시할 문자열, 범위: ASCII 가시 문자
  * @retval 없음
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED 거듭제곱 함수
  * @retval 반환값은 X의 Y 거듭제곱과 같음
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED 숫자 표시 (십진법, 양수)
  * @param  Line 시작 행 위치, 범위: 1~4
  * @param  Column 시작 열 위치, 범위: 1~16
  * @param  Number 표시할 숫자, 범위: 0~4294967295
  * @param  Length 표시할 숫자의 길이, 범위: 1~10
  * @retval 없음
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED 숫자 표시 (십진법, 부호 있는 숫자)
  * @param  Line 시작 행 위치, 범위: 1~4
  * @param  Column 시작 열 위치, 범위: 1~16
  * @param  Number 표시할 숫자, 범위: -2147483648~2147483647
  * @param  Length 표시할 숫자의 길이, 범위: 1~10
  * @retval 없음
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED 숫자 표시 (십육진법, 양수)
  * @param  Line 시작 행 위치, 범위: 1~4
  * @param  Column 시작 열 위치, 범위: 1~16
  * @param  Number 표시할 숫자, 범위: 0~0xFFFFFFFF
  * @param  Length 표시할 숫자의 길이, 범위: 1~8
  * @retval 없음
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED 숫자 표시 (이진법, 양수)
  * @param  Line 시작 행 위치, 범위: 1~4
  * @param  Column 시작 열 위치, 범위: 1~16
  * @param  Number 표시할 숫자, 범위: 0~1111 1111 1111 1111
  * @param  Length 표시할 숫자의 길이, 범위: 1~16
  * @retval 없음
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED 초기화
  * @param  없음
  * @retval 없음
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//전원 공급 지연
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//포트 초기화
	
	OLED_WriteCommand(0xAE);	//디스플레이 끄기
	
	OLED_WriteCommand(0xD5);	//디스플레이 클록 분주비/발진기 주파수 설정
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//멀티플렉스 비율 설정
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//디스플레이 오프셋 설정
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//디스플레이 시작 행 설정
	
	OLED_WriteCommand(0xA1);	//좌우 방향 설정, 0xA1 정상 0xA0 좌우 반전
	
	OLED_WriteCommand(0xC8);	//상하 방향 설정, 0xC8 정상 0xC0 상하 반전

	OLED_WriteCommand(0xDA);	//COM 핀 하드웨어 구성 설정
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//명암 제어 설정
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//사전 충전 기간 설정
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//VCOMH 선택 해제 레벨 설정
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//전체 디스플레이 켜기/끄기 설정

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

	OLED_WriteCommand(0x8D);	//충전 펌프 설정
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//디스플레이 켜기
		
	OLED_Clear();				//OLED 화면 지우기
}

추천 읽을거리

1개의 좋아요