STM32 하드웨어 I2C로 SHTC3 온습도 센서 읽기

STM32의 하드웨어 I2C를 이용해 SHTC3 온습도 센서 데이터를 읽고 0.96인치 OLED 화면에 표시하기.

나는 STM32F103C8T6를 사용하며, 프로그램은 ST 표준 라이브러리로 작성했다.

전자/마이콘 기술 교류 그룹: 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)이라는 직렬 클럭선, 총 2개의 버스선만 사용한다. 데이터선은 데이터를 나타내고, 클럭선은 데이터 송수신 동기화에 사용된다.

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

I2C 통신 시 MCU의 GPIO 핀은 오픈 드레인 출력으로 설정해야 하며, 그렇지 않으면 단락이 발생할 수 있다.

STM32 I2C에 대한 더 자세한 정보와 사용법은 다음 글을 참고: https://url.zeruns.com/JC0Ah

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

SHTC3 온습도 센서

SHTC3 데이터시트 다운로드: https://url.zeruns.com/WpLDy

데이터시트를 살펴보면 SHTC3은 온도와 습도를 측정할 수 있는 센서로,

온도 범위: -40℃ ~ 125℃
습도 범위: 0% ~ 100%
동작 전압: 1.6 V ~ 3.6 V
통신 방식: I2C
클럭 주파수: 0~100 kHz, 0~400 kHz, 0~1000 kHz 세 가지 모드

다음과 같은 핵심 정보를 찾을 수 있다.

온습도 장치 주소 및 읽기/쓰기 명령

실제 사용 시 SHTC3의 장치 주소는 읽기/쓰기 방향 비트와 함께 한 바이트로 전송되며, 하위 1비트가 읽기/쓰기 방향 비트이고 상위 7비트가 SHTC3의 장치 주소다.

I2C로 SHTC3에 데이터나 명령을 쓰려면 I2C 시작 신호 후 “1110 0000”, 즉 0xE0을 SHTC3에 전송한다. 상위 7비트 “1110 000”로 장치를 찾고, 하위 1비트 “0”으로 이후 쓰기 동작임을 알린다.

I2C로 SHTC3에서 데이터를 읽으려면 I2C 시작 신호 후 “1110 0001”, 즉 0xE1을 SHTC3에 전송한다. 상위 7비트로 장치를 찾고, 하위 1비트 “1”로 읽기 동작임을 알린다.
요컨대 0xE0은 쓰기, 0xE1은 읽기다. 단, STM32 하드웨어 I2C 사용 시 0xE0만 입력하면 되며, 하위 비트는 표준 라이브러리가 처리한다.

온습도 데이터 읽기

다른 명령에 따라 데이터 순서와 Clock Stretching Enable/Disable 여부가 다르다.

Clock Stretching은 클럭 스트레칭이다. Clock Stretching Enable 명령 사용 시 측정 명령 전송 후 SHTC3이 온습도 데이터를 측정하는 동안 I2C 클럭선 SCL을 당겨(풀다운) 마스터가 명령을 보내지 못하도록 하고, 측정이 끝나야 SCL을 놓는다.

Clock Stretching Disable 명령 사용 시 측정 중 SCL을 당기지 않으며, 측정 중 마스터가 명령을 보내도 SHTC3은 응답하지 않는다. 마스터는 SHTC3의 응답 유무로 측정 완료를 판단할 수 있다.

데이터시트에 따르면 한 측정 주기는 다음 4단계로 이루어진다:

  1. 깨우기 명령 전송
  2. 측정 명령 전송
  3. 측정 완료 후 데이터 읽기
  4. 잠자기 명령 전송

깨우기 및 잠자기 명령은 데이터시트에서 확인.

요약:

  1. SHTC3 깨우기: 쓰기 명령(0xE0) → 깨우기 명령 상위(0x35) → 하위(0x17)
  2. 깨우기 대기: 데이터시트상 최대 240 µs이므로 그보다 길게 대기
  3. 측정 명령 전송: 쓰기 명령(0xE0) → 측정 명령 상위·하위 전송(명령은 용도에 따라 선택)
  4. 데이터 수신: 읽기 명령(0xE1) 전송 후 6바이트 연속 수신. 온도 먼저 저장 명령 시 1~2바이트가 온도 값, 3바이트가 온도 CRC, 4~5바이트가 습도 값, 6바이트가 습도 CRC. 습도 먼저 명령 시 앞뒤 3바이트가 바뀐다.
  5. 수면 진입: 쓰기 명령 후 잠자기 명령 전송

데이터 계산

SHTC3 데이터시트에 따르면

예: 수신된 습도 값이 0x6501이면 십진수로 25857
습도 = 100 × 25857 / 65536 = 39.45(%)
온도 값이 0x6600이면 십진수로 26112
온도 = -45 + 175 × 26112 / 65536 = 24.72(℃)

필요한 부품

STM32 최소 시스템 보드: https://s.click.taobao.com/bqMwZRu
SHTC3 모듈: https://s.click.taobao.com/WxACJRu
OLED 모듈: https://s.click.taobao.com/aNlvZRu
두본선: https://s.click.taobao.com/xAkAJRu
브레드보드: https://s.click.taobao.com/ShJAJRu
ST-LINK V2: https://s.click.taobao.com/C8ftZRu

프로그램

main.c, shtc3.c, oled.c 세 주요 코드만 공개하며, 나머지는 아래 압축 파일을 내려받으세요.

전체 프로젝트 파일: https://url.zeruns.com/EXCvo

SHTC3와 OLED 모듈의 SCL은 PB6, SDA는 PB7에 연결.

Keil 대신 VSCode로 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 "SHTC3.h"

uint16_t numlen(uint16_t num);

int main(void)
{
	IWDG_Configuration();	// 초기화: 와치독
	OLED_Init();			// 초기화: OLED 화면
	SHTC3_I2C_Init();		// 초기화: SHTC3
	
	OLED_ShowString(1, 1, "T:");
	OLED_ShowString(2, 1, "H:");
	OLED_ShowString(4, 1, "err_count:");

	uint32_t a=0;
	uint16_t err_count=0;
	
	while (1)
	{
		a++;
		OLED_ShowNum(3, 1, a, 9);
		if(a==999999999)a=0;

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

		if(ReadSHTC3(&Hum,&Temp))	// 온습도 데이터 읽기
		{
			if(Temp>=0)
			{
				char String[10];
				sprintf(String, "%.2fC", Temp);	// 형식화된 문자열 출력
				OLED_ShowString(1, 3, String);	// 온도 표시
				/*
				OLED_ShowNum(1,3, (uint8_t)Temp, numlen((uint8_t)Temp));// 온도 정수부
				OLED_ShowChar(1, 3+numlen((uint8_t)Temp), '.');			// 소수점
				OLED_ShowNum(1,3+numlen((uint8_t)Temp)+1, (uint8_t)(Temp*100)%100, 2);	// 소수부
				OLED_ShowChar(1, 3+numlen((uint8_t)Temp)+1+2, 'C');		// 단위
				*/
```sprintf(String, "%.2f%%", Hum);	//格式化字符串输出到字符串变量
				OLED_ShowString(2, 3, String);	//显示湿度
				/*
				OLED_ShowNum(2,3, (uint8_t)Hum, numlen((uint8_t)Hum));	//显示湿度整数部分
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum), '.');			//显示小数点
				OLED_ShowNum(2,3+numlen((uint8_t)Hum)+1, (uint8_t)(Hum*100)%100, 2);	//显示湿度小数部分
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum)+1+2, '%');			//显示符号
				*/
			}else
			{
				char String[10];
				sprintf(String, "-%.2fC", Temp);//格式化字符串输出到字符串变量
				OLED_ShowString(1, 3, String);	//显示温度
				/*
				OLED_ShowChar(1, 3, '-');			//显示负号
				OLED_ShowNum(1,3+1, (uint8_t)Temp, numlen((uint8_t)Temp));	//显示温度整数部分
				OLED_ShowChar(1, 3+1+numlen((uint8_t)Temp), '.');			//显示小数点
				OLED_ShowNum(1,3+1+numlen((uint8_t)Temp)+1, (uint8_t)(Temp*100)%100, 2);	//显示温度小数部分
				OLED_ShowChar(1, 3+1+numlen((uint8_t)Temp)+1+2, 'C');		//显示符号
				*/

				sprintf(String, "%.2f%%", Hum);	//格式化字符串输出到字符串变量
				OLED_ShowString(2, 3, String);	//显示湿度
				/*
				OLED_ShowNum(2,3, (uint8_t)Hum, numlen((uint8_t)Hum));	//显示湿度整数部分
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum), '.');			//显示小数点
				OLED_ShowNum(2,3+numlen((uint8_t)Hum)+1, (uint8_t)(Hum*100)%100, 2);	//显示湿度小数部分
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum)+1+2, '%');			//显示符号
				*/
			}
		}
		else
		{
			err_count++;
			OLED_ShowNum(4,11, err_count, numlen(err_count));	//显示错误次数计数
		}
	/*
	https://blog.zeruns.com
	*/

		Delay_ms(100);	//延时100毫秒

		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;              // 返回长度的值
}

SHTC3.c

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

/*SHTC3地址*/
#define SHTC3_ADDRESS 0xE0

/*设置使用哪一个I2C*/
#define I2Cx I2C1

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

/**
  * @brief  CRC校验,CRC多项式为:x^8+x^5+x^4+1,即0x31
  * @param  DAT 要校验的数据
  * @retval 校验码
  */
uint8_t SHTC3_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 SHTC3_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 SHTC3_I2C_STOP(){
    I2C_GenerateSTOP(I2Cx, ENABLE);//发送停止信号
}

/**
  * @brief  发送两个字节数据
  * @param  MSB 高8位
  * @param  LSB 低8位
  * @retval 无
  */
void SHTC3_WriteByte(uint8_t MSB,uint8_t LSB)
{
	SHTC3_I2C_START();  //发送起始信号
	
	I2C_Send7bitAddress(I2Cx, SHTC3_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 SHTC3_ReadData()
{
    while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//检测EV7事件
	return I2C_ReceiveData(I2Cx);//读取数据并返回
}

/*软件复位SHTC3*/
void SHTC3_SoftReset(void)                    
{
    SHTC3_WriteByte(0x80,0x5D);    //重置SHTC3
}

/*引脚初始化*/
void SHTC3_I2C_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

	SHTC3_WriteByte(0X35,0X17);//唤醒SHTC3
	
	Delay_us(200);
}

/**
  * @brief  读取SHTC3数据
  * @param  *Hum 湿度
  * @param  *Temp 温度
  * @retval 1 - 读取成功;0 - 读取失败
  */
uint8_t ReadSHTC3(float *Hum,float *Temp)
{
    uint16_t HumData,TempData,HumCRC,TempCRC;//声明变量存放读取的数据
    
    /*SHTC3_WriteByte(0X35,0X17);//唤醒SHTC3
	
	Delay_us(300);*/
    
    SHTC3_WriteByte(0X5C,0X24);//发送指令,先读取湿度数据,时钟拉伸(测量数据期间拉低SCL时钟线,占用总线)
	
    SHTC3_I2C_START();//发送起始信号
    
	I2C_Send7bitAddress(I2Cx,SHTC3_ADDRESS,I2C_Direction_Receiver);//发送设备读地址
	
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//检测EV6事件
	
    HumData = SHTC3_ReadData(); //读取湿度高8位数据
    HumData=HumData<<8;         //左移8位
	HumData |= SHTC3_ReadData();//读取湿度低8位数据
    HumCRC = SHTC3_ReadData();  //读取湿度CRC校验数据

    TempData = SHTC3_ReadData();//读取温度高8位数据
    TempData=TempData<<8;       //左移8位
	TempData |= SHTC3_ReadData();//读取温度低8位数据
    TempCRC = SHTC3_ReadData(); //读取温度CRC校验数据

    SHTC3_I2C_STOP();   //发送停止信号
		
	//SHTC3_WriteByte(0XB0,0X98);//发送休眠指令

    if( SHTC3_CRC_CAL(HumData)==HumCRC && SHTC3_CRC_CAL(TempData)==TempCRC ){   //对接收到数据进行CRC校验
       *Hum = (float)HumData*100/65536;        //将接收的16位二进制数据转换为10进制湿度数据
       *Temp = (float)TempData*175/65536-45;   //将接收的16位二进制数据转换为10进制温度数据
       return 1;
    }
    else{
        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);
}
 
``````c
/**
  * @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);
		}
	}
}

/**
  * @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  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 화면 지우기
}

일부 내용은 다음 두 글을 참고했습니다:

https://blog.csdn.net/mj475002864/article/details/114027993

https://blog.csdn.net/k666499436/article/details/124686559

추천 읽기

1개의 좋아요