STM32でハードウェアI2Cを使用してSHTC3温湿度センサーを読み取る

STM32でハードウェアI2Cを使ってSHTC3温湿度センサからデータを読み取り、0.96インチOLEDディスプレイに表示する。

使用したのはSTM32F103C8T6で、プログラムはST標準ライブラリで書いている。

電子/マイコン技術交流グループ:2169025065

実装効果図


I2Cプロトコル概要

I2C通信プロトコル(Inter-Integrated Circuit)はPhilips社によって開発されたもので、ピン数が少なく、ハードウェアの実装が簡単で、拡張性が高く、USARTやCANなどの通信プロトコルのような外部送受信デバイス(レベル変換チップ)が不要なため、現在ではシステム内の複数の集積回路(IC)間の通信で広く使用されている。

I2Cにはデータバスが1本だけで、SDA(Serial Data Line)と呼ばれるシリアルデータバスであり、1ビットずつデータを送信するシリアル通信で、半二重通信を採用している。

半二重通信:双方向の通信が可能だが、同時に双方向で行うことはできず、交互に行う必要がある。ある意味で切り替え可能な単一方向通信とも言え、同一時刻には必ず一方向の転送のみであり、データラインは1本で済む。
I2C通信プロトコルは物理層とプロトコル層に分けられる。物理層は通信システムにおける機械的・電子的機能部分(ハードウェア部分)の特性を規定し、物理メディアでの生データの転送を保証する。プロトコル層は主に通信ロジックを規定し、送受信側のデータパッケージングおよびアンパッキングの標準(ソフトウェアレベル)を統一する。

I2C物理層

I2C通信デバイス間の一般的な接続方法

(1) 複数デバイスをサポートするバスである。「バス」とは複数のデバイスが共有する信号線のこと。1つのI2C通信バスでは、複数のI2C通信デバイスを接続でき、複数の通信マスターおよび複数の通信スレーブをサポートする。

(2) 1つのI2Cバスは2本のバスラインのみを使用する。1本は双方向シリアルデータラインSDA(Serial Data Line)、もう1本はシリアルクロックラインSCL(Serial Clock Line)。データラインはデータを表すために使用され、クロックラインはデータ送受信の同期に使用される。

(3) バスはプルアップ抵抗を介して電源に接続される。I2Cデバイスがアイドル状態のときはハイインピーダンスを出力し、すべてのデバイスがアイドルでハイインピーダンスを出力しているときは、プルアップ抵抗によってバスがハイレベルに引き上げられる

I2C通信時、マイコンのGPIOピンはオープンドレイン出力に設定する必要があり、そうでないとショートする可能性がある。

STM32のI2Cに関する詳細情報や使用方法については以下の記事を参照:https://url.zeruns.com/JC0Ah

ここでは詳細は説明しない。

SHTC3温湿度センサ

SHTC3データシートダウンロード先:https://url.zeruns.com/WpLDy

データシートを見ると、SHTC3は温度と湿度を検出できるセンサであることがわかる。
温度範囲:-40℃~125℃
湿度範囲:0%~100%
動作電圧:1.6V~3.6V
通信方式:I2C
クロック周波数:3つのモードがあり、それぞれ0~100kHz、0~400kHz、0~1000kHz

以下の重要な情報を見つける:

温湿度デバイスアドレスと読み書きコマンド

実際の使用では、SHTC3のデバイスアドレスは読み書きデータ/コマンド方向ビットと組み合わせて1バイトとして同時に送信する必要がある。バイトの最下位ビットが読み書きデータ/コマンド方向ビットで、上位7ビットがSHTC3のデバイスアドレスである。

I2Cを介してSHTC3にデータまたはコマンドを書き込む場合、I2Cスタート信号の後に「1110 0000」、つまり0xE0をSHTC3に送信する必要がある。上位7ビット「1110 000」のデバイスアドレスでアドレッシングし、最下位ビット「0」で次は書き込み操作であることをSHTC3に通知する。

I2Cを介してSHTC3からデータを読み取る場合、I2Cスタート信号の後に「1110 0001」、つまり0xE1をSHTC3に送信する必要がある。上位7ビット「1110 000」のデバイスアドレスでアドレッシングし、最下位ビット「1」で次は読み取り操作であることをSHTC3に通知する。
簡単に言うと、0xE0はデータ書き込み、0xE1はデータ読み取りを表す。ただし、STM32のハードウェアI2Cを使用する場合は0xE0のみを入力すればよく、最下位ビットは標準ライブラリが処理する。

温湿度データの読み取り

わかることは、異なるコマンドでは取得するデータの順序が異なるだけでなく、Clock Stretching EnableとDisableの違いもある。

Clock Stretchingはクロックストレッチの意味。Clock Stretching Enableコマンドを使用する場合、測定コマンドを送信した後、SHTC3が温湿度データを測定している間、SHTC3はI2CのクロックラインSCLをプルダウンし、これによってホストがSHTC3にコマンドを送信することを禁止し、SHTC3が温湿度データの測定を完了したときにのみSCLを開放する。

Clock Stretching Disableコマンドを使用する場合、SHTC3がデータを測定している間はSCLをプルダウンせず、測定中にホストがコマンドまたはデータを送信してもSHTC3は応答しない。ホストはSHTC3からの応答信号の有無で測定が完了したかどうかを判断できる。

データシートによると、1つの測定サイクルは4つのステップに分かれる:

  1. ウェイクアップコマンドを送信
  2. 測定コマンドを送信
  3. 測定完了後のデータを読み取る
  4. スリープコマンドを送信

ウェイクアップコマンドとスリープコマンドはデータシートで確認できる:

まとめ:

  1. SHTC3をウェイクアップ:書き込み命令(0xE0)を送信し、次にウェイクアップ命令上位(0x35)、ウェイクアップ命令下位(0x17)を送信
  2. ウェイクアップ待機:データシートによる最大ウェイクアップ時間は240μsで、これ以上待てばよい
  3. 収集命令を送信:書き込み命令(0xE0)を送信し、次に収集命令の上位と下位を送信。収集命令は複数あり、必要に応じて選択
  4. データ受信:読み取り命令(0xE1)を送信し、6バイトのデータを連続で受信。収集命令が温度優先の場合、6バイトのうち1~2バイト目が温度値、3バイト目が温度チェックサム、4~5バイト目が湿度値、6バイト目が湿度チェックサム。湿度優先の場合は前後3バイトが逆になる
  5. スリープ移行:書き込み命令を送信し、スリープ命令を送信してスリープ状態に入る

データの計算

SHTC3データシートによると:

例:収集した湿度値が0x6501で、10進数に変換すると25857
則:湿度 = 100 × 25857 / 65536 = 39.45(単位:%)
収集した温度値が0x6600で、10進数に変換すると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の3つの主要なコードのみを掲載する。他は以下のリンクからダウンロードしてほしい。

完全なプロジェクトファイル:https://url.zeruns.com/EXCvo

SHTC3および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 "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  2バイトデータ送信
  * @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アドレス、不要なら適当でOK
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	// ACKビット有効
	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アドレス、不要なら適当でOK
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	// ACKビット有効
	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);
		}
	}
}

/**
  * @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  OLED1文字表示
  * @param  Line 行位置、範囲:1~4
  * @param  Column 列位置、範囲:1~16
  * @param  Char 表示する1文字、範囲: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数値表示(10進、正数)
  * @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数値表示(10進、符号付き)
  * @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数値表示(16進、正数)
  * @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数値表示(2進、正数)
  * @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);	//ディスプレイOFF
	
	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ピンHW設定
	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);	//全体表示ON/OFF設定

	OLED_WriteCommand(0xA6);	//正/反転表示設定

	OLED_WriteCommand(0x8D);	//チャージポンプ設定
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//ディスプレイON
		
	OLED_Clear();				//OLED画面クリア
}

部分内容参考以下两篇文章:

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

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

おすすめ記事

「いいね!」 1