STM32マイコンでソフトI2Cを使ってAM2320温湿度センサからデータを読み取り、0.96インチOLEDディスプレイに表示する。
使用したマイコンはSTM32F103C8T6で、プログラムはST標準ライブラリで記述した。
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)はフィリップス社が開発したもので、ピン数が少なく、ハードウェア実装が簡単で拡張性が高く、USARTやCANなどの通信プロトコルの外部送受信デバイス(レベル変換チップなど)が不要なため、現在ではシステム内の複数の集積回路(IC)間の通信に広く使用されている。
I2Cにはデータ総線SDA(Serial Data Line)が1本のみで、シリアルデータ総線であり1ビットずつデータを送信するシリアル通信であり、半二重通信を採用している。
半二重通信:双方向通信が可能だが、同時に両方向で行うことはできず、交互に行わなければならない。実質的に切り替え可能な単一方向通信と考えられ、同一時刻には必ず一方向の転送しか行えないため、データ線は1本で済む。
I2C通信プロトコルは物理層とプロトコル層に分けられる。物理層は通信システムの機械的・電子的な機能部分(ハードウェア部分)を規定し、物理媒体での生データ転送を保証する。プロトコル層は主に通信ロジックを規定し、送受信側のデータパッケージング・ unpack 標準を統一する(ソフトウェアレベル)。
I2C物理層
I2C通信デバイス間の一般的な接続方法
(1) 複数デバイスをサポートするバス。「バス」とは複数のデバイスが共用する信号線を指す。1つのI2C通信バスでは複数のI2C通信デバイスを接続でき、複数の通信マスターおよび複数の通信スレーブをサポートする。
(2) I2Cバスは2本のバスラインのみを使用する。1本は双方向シリアルデータ線SDA(Serial Data Line)、もう1本はシリアルクロック線SCL(Serial Clock Line)。データ線はデータを表現し、クロック線はデータ送受信の同期に使用される。
(3) バスはプルアップ抵抗を介して電源に接続される。I2Cデバイスがアイドル状態のときはハイインピーダンスを出力し、すべてのデバイスがアイドルでハイインピーダンスを出力しているときは、プルアップ抵抗によってバスがハイレベルに引き上げられる。
I2C通信時、マイコンのGPIOピンは開放ドレイン出力に設定しなければならない。そうでないとショートする可能性がある。
より詳しいSTM32のI2C情報と使用方法については以下の記事を参照:https://url.zeruns.com/JC0Ah
ここでは詳細は説明しない。
AM2320温湿度センサ
概要
AM2320デジタル温湿度センサは、工場出荷時に校正済みのデジタル信号出力を持つ温湿度複合センサである。専用の温湿度収集技術を採用し、極めて高い信頼性と優れた長期安定性を実現している。センサは静電容量式湿度素子と高精度集積温度素子を含み、高性能マイクロプロセッサと接続されている。卓越した品質、超高速応答、高耐ノイズ性、極めて高いコストパフォーマンスを持つ。AM2320の通信方式は、シングルバスと標準I2Cの2種類を採用している。標準シングルバスインターフェースにより、システム統合が簡単かつ迅速になる。超小型、超低消費電力で、信号伝送距離は20m以上に達し、最も厳しい応用状況にも対応できる。I2C通信方式は標準的な通信タイミングを採用しており、ユーザーはI2Cバスに直接接続でき、追加配線は不要で簡単に使用できる。両通信方式とも、温度補償済みの湿度・温度・CRCチェックなどのデジタル情報を直接出力するため、ユーザーはデジタル出力を二次計算する必要も、湿度を温度補償する必要もなく、正確な温湿度情報を得られる。両通信方式は自由に切り替え可能で、ユーザーは自由に選択でき、使用が簡単で応用分野も広い。製品は4本のリード線で、接続が簡単で、特殊なパッケージ形態もユーザー要求に応じて提供される。
AM2320データシートダウンロード:https://url.zeruns.com/74o6F
データシートを見ると、以下の概要情報が得られる:
- 温度範囲:-40℃~80℃
- 温度誤差:±0.5℃
- 湿度範囲:0%~99.9%
- 湿度誤差:±3%
- 動作電圧:3.1V~5.5V
- 通信方式:I2Cまたはシングルバス
- クロック周波数:100kHz以内
以下の重要情報を見つける:
デバイスアドレスと読み書きコマンド
実際の使用では、AM2320のデバイスアドレスは読み書きデータ/コマンド方向ビットと組み合わせて1バイトとして同時に送信する必要がある。バイトの最下位ビットが読み書きデータ/コマンド方向ビットで、上位7ビットがAM2320のデバイスアドレスである。
I2CでデータまたはコマンドをAM2320に書き込む場合、I2Cスタート信号の後に「1011 1000」、つまり0xB8をAM2320に送信する。上位7ビット「1011 100」のデバイスアドレスでデバイスを指定し、最下位ビット「0」で次の操作が書き込みであることを通知する。
I2CでAM2320からデータを読み取る場合、I2Cスタート信号の後に「1011 1001」、つまり0xB9をAM2320に送信する。上位7ビットでデバイスアドレスを指定し、最下位ビット「1」で次の操作が読み取りであることを通知する。
つまり、0xB8が書き込み、0xB9が読み取りを表す。
温湿度データの読み取り
データシートより、1つの読み取りサイクルは3ステップに分かれる:
- センサを起動
- 読み取りコマンドを送信
- 返信データを読み取る
まとめると:
- センサ起動:スタート信号 + 0xB8送信 + 待機(>800μs) + ストップ信号
- 読み取りコマンド送信:START + 0xB8(SLA) + 0x03(ファンクションコード) + 0x00(開始アドレス) + 0x04(レジスタ長) + STOP
- データ受信:読み取りコマンド(0xB9)を送信し、連続で8バイト受信。受信データは:データ長 + 湿度上位バイト + 湿度下位バイト + 温度上位バイト + 温度下位バイト + CRCチェック下位バイト + CRCチェック上位バイト
- 受信データを変換処理する。
データの計算
AM2320データシートより:
例:収集した湿度値が0x01F4で、10進数に変換すると500。
→ 湿度 = 500 / 10 = 50.0(単位:%)
収集した温度値が0x00FAで、10進数に変換すると250。
→ 温度 = 250 / 10 = 25.0(単位:℃)
必要な部品
- STM32最小システムボード:https://s.click.taobao.com/M2LgRPu
- AM2320:https://s.click.taobao.com/t7jgRPu
- OLEDモジュール:https://s.click.taobao.com/w67u9Pu
- ジャンパワイヤ:https://s.click.taobao.com/anyfRPu
- ブレッドボード:https://s.click.taobao.com/7Vlu9Pu
- ST-LINK V2:https://s.click.taobao.com/dtbt9Pu
- 抵抗:https://s.click.taobao.com/vbQu9Pu
- ロジックアナライザ:https://s.click.taobao.com/y2br9Pu
プログラム
ここではmain.c、AM2320.c、OLED.cの3つの主要なコードのみを掲載する。他は以下のリンクからダウンロードしてください。
完全なプロジェクトファイル:https://url.zeruns.com/AM2320
AM2320およびOLEDモジュールのSCLはPB12、SDAはPB13に接続。 AM2320を別のIOピンに接続する場合は、プルアップ抵抗(約5kΩ)を忘れずに接続すること。
VSCodeでKeilの代わりにSTM32および51マイコン開発を行う方法:https://blog.zeruns.com/archives/690.html
main.c```c
#include “stm32f10x.h” // デバイスヘッダ
#include “Delay.h”
#include “OLED.h”
#include “AM2320.h”
#include “IWDG.h”
int main(void)
{
IWDG_Configuration(); // ウォッチドッグ初期化
AM2320_I2C_Init();
OLED_Init();
OLED_ShowString(1, 1, "T:");
OLED_ShowString(2, 1, "H:");
uint16_t i = 0;
uint16_t err_count = 0;
while (1)
{
OLED_ShowNum(4, 1, i, 5);
float Temp, Hum; // 温湿度データ格納用変数宣言
if (ReadAM2320(&Hum, &Temp)) // 温湿度データ読み取り
{
if (Temp >= 0)
{
char String[10];
sprintf(String, "+%.2fC", Temp); // 文字列変数へフォーマット出力
OLED_ShowString(1, 3, String); // 温度表示
sprintf(String, " %.2f%%", Hum); // 文字列変数へフォーマット出力
OLED_ShowString(2, 3, String); // 湿度表示
}
else
{
char String[10];
sprintf(String, "-%.2fC", Temp); // 文字列変数へフォーマット出力
OLED_ShowString(1, 3, String); // 温度表示
sprintf(String, " %.2f%%", Hum); // 文字列変数へフォーマット出力
OLED_ShowString(2, 3, String); // 湿度表示
}
}
else
{
err_count++;
OLED_ShowNum(3, 1, err_count, 5); // エラー回数カウント表示
}
Delay_ms(100);
i++;
if (i >= 99999)
i = 0;
if (err_count >= 99999)
err_count = 0;
IWDG_FeedDog(); // ウォッチドッグへの餌やり(1秒以上餌やりがないと自動リセット)
}
// blog.zeruns.com
}
### AM2320.c
```c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
/*
作者ブログ:https://blog.zeruns.com
WeChat公式アカウント:zeruns-gzh
Bilibiliホーム:https://space.bilibili.com/8320520
*/
/*AM2320アドレス*/
#define AM2320_ADDRESS 0xB8
/*ピン設定*/
#define AM2320_SCL GPIO_Pin_12
#define AM2320_SDA GPIO_Pin_13
#define AM2320_W_SCL(x) GPIO_WriteBit(GPIOB, AM2320_SCL, (BitAction)(x))
#define AM2320_W_SDA(x) GPIO_WriteBit(GPIOB, AM2320_SDA, (BitAction)(x))
#define AM2320_R_SDA() GPIO_ReadInputDataBit(GPIOB, AM2320_SDA)
#define AM2320_R_SCL() GPIO_ReadInputDataBit(GPIOB, AM2320_SCL)
/*STM32のGPIOをオープンドレイン出力モードに設定しても、
GPIO入力データレジスタを読み取ることで外部からのピン入力レベルを取得できるため、浮遊入力モードの機能も併せ持つ*/
/**
* @brief CRCチェック計算
* @param *ptr 計算対象バイトデータ(配列変数として格納)
* @param len 計算対象バイト数(配列長)
* @retval CRCチェックコード
*/
unsigned short CRC16(unsigned char *ptr, unsigned char len)
{
unsigned short crc = 0xFFFF;
unsigned char i;
while (len--)
{
crc ^= *ptr++;
for (i = 0; i < 8; i++)
{
if (crc & 0x01)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/**
* @brief I2Cスタート
* @param なし
* @retval なし
*/
void AM2320_I2C_Start(void)
{
AM2320_W_SDA(1);
Delay_us(2); // 2マイクロ秒遅延
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SDA(0);
Delay_us(3);
AM2320_W_SCL(0);
Delay_us(5);
}
/**
* @brief I2Cストップ
* @param なし
* @retval なし
*/
void AM2320_I2C_Stop(void)
{
AM2320_W_SDA(0);
Delay_us(3);
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SDA(1);
Delay_us(4);
}
/**
* @brief I2C 1バイト送信
* @param Byte 送信する1バイト
* @retval なし
*/
void AM2320_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
AM2320_W_SDA((Byte << i) & 0x80);
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SCL(0);
Delay_us(5);
}
AM2320_W_SDA(1); // SDAバス開放
}
/**
* @brief 応答信号待機
* @param なし
* @retval 1-非応答信号、0-応答信号
*/
uint8_t WaitAck(void)
{
uint8_t ret;
AM2320_W_SCL(1);
Delay_us(4);
if (AM2320_R_SDA())
{
ret = 1;
}
else
{
ret = 0;
}
AM2320_W_SCL(0);
Delay_us(5);
return ret;
}
/**
* @brief I2C 1バイト読み取り
* @param NACK 1-非応答信号、0-応答信号
* @retval 読み取ったバイトデータ
*/
uint8_t AM2320_I2C_ReadByte(uint8_t NACK)
{
uint8_t i, Byte = 0;
AM2320_W_SDA(1); // SDAバス開放
for (i = 0; i < 8; i++)
{
AM2320_W_SCL(1);
Delay_us(4);
Byte = Byte | (AM2320_R_SDA() << (7 - i));
AM2320_W_SCL(0);
Delay_us(5);
}
AM2320_W_SDA(NACK); // 応答/非応答信号送信
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SCL(0);
Delay_us(5);
AM2320_W_SDA(1); // SDAバス開放
return Byte;
}
/*センサー起床*/
void AM2320_Wake(void)
{
AM2320_I2C_Start();
AM2320_I2C_SendByte(AM2320_ADDRESS);
WaitAck();
Delay_us(1000); // 1000マイクロ秒遅延
AM2320_I2C_Stop();
}
/*ピン初期化*/
void AM2320_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOBクロック有効化
GPIO_InitTypeDef GPIO_InitStructure; // GPIO設定用構造体定義
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // オープンドレイン出力モード
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = AM2320_SCL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = AM2320_SDA;
GPIO_Init(GPIOB, &GPIO_InitStructure);
AM2320_W_SCL(1);
AM2320_W_SDA(1);
AM2320_Wake(); // センサー起床
}
/**
* @brief AM2320データ読み取り
* @param *Hum 湿度
* @param *Temp 温度
* @retval 1 - 読み取り成功;0 - 読み取り失敗
*/
uint8_t ReadAM2320(float *Hum, float *Temp)
{
uint8_t Data[8];
AM2320_I2C_Start(); // スタート信号送信
AM2320_I2C_SendByte(AM2320_ADDRESS);
if (WaitAck()) // 応答信号判定
{
AM2320_I2C_Stop(); // ストップ信号送信
Delay_us(50);
// 再読み取り試行
AM2320_I2C_Start(); // スタート信号送信
AM2320_I2C_SendByte(AM2320_ADDRESS);
if (WaitAck()) // 応答信号判定
{
Delay_us(20);
AM2320_I2C_Stop(); // ストップ信号送信
return 0;
}
else
{
Delay_us(20);// AM2320がSCLを無理やり低レベルに引っ張ってデータ送信エラーになるため、20マイクロ秒待ってAM2320がSCLを開放してから継続
AM2320_I2C_SendByte(0x03); // ファンクションコード送信
WaitAck(); // 応答信号待機
AM2320_I2C_SendByte(0x00); // 読み取りレジスタ開始アドレス送信
WaitAck(); // 応答信号待機
AM2320_I2C_SendByte(0x04); // 読み取りレジスタ長さ送信
WaitAck(); // 応答信号待機
Delay_us(20);// AM2320がSCLを無理やり低レベルに引っ張ってストップ信号送信失敗になるため、20マイクロ秒待ってAM2320がSCLを開放してから継続
AM2320_I2C_Stop(); // ストップ信号送信
}
}
else
{
Delay_us(20);// AM2320がSCLを無理やり低レベルに引っ張ってデータ送信エラーになるため、20マイクロ秒待ってAM2320がSCLを開放してから継続
AM2320_I2C_SendByte(0x03); // ファンクションコード送信
WaitAck(); // 応答信号待機
AM2320_I2C_SendByte(0x00); // 読み取りレジスタ開始アドレス送信
WaitAck(); // 応答信号待機
AM2320_I2C_SendByte(0x04); // 読み取りレジスタ長さ送信
WaitAck(); // 応答信号待機
Delay_us(20);// AM2320がSCLを無理やり低レベルに引っ張ってストップ信号送信失敗になるため、20マイクロ秒待ってAM2320がSCLを開放してから継続
AM2320_I2C_Stop(); // ストップ信号送信
}
Delay_ms(2); // 2ミリ秒遅延
AM2320_I2C_Start();
AM2320_I2C_SendByte(AM2320_ADDRESS | 0x01); // 読み取りコマンド送信
WaitAck();
Delay_us(35);
uint8_t i;
for (i = 0; i < 8; i++)
{
if (i != 7)
{
Data[i] = AM2320_I2C_ReadByte(0);
}
else
{
Data[i] = AM2320_I2C_ReadByte(1); // 最終バイト読み取り時は非応答信号送信
}
}
AM2320_I2C_Stop();
if (CRC16(Data, 6) == (Data[6] | (Data[7] << 8))) // データチェック
{
*Hum = ((((uint16_t)Data[2]) << 8) | Data[3]) / 10.0; // 湿度データ計算
if (Data[4] >> 7) // 温度値が負か判定
{
*Temp = ((((uint16_t)(Data[4] && 0x7F) << 8)) | Data[5]) / -10.0; // 負温度計算
}
else
{
*Temp = ((((uint16_t)Data[4]) << 8) | Data[5]) / 10.0; // 正温度計算
}
return 1;
}
return 0;
}
```### OLED.c
```c
#include "stm32f10x.h"
#include "OLED_Font.h"
/*ピン設定*/
#define OLED_SCL GPIO_Pin_12
#define OLED_SDA GPIO_Pin_13
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, OLED_SCL, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, OLED_SDA, (BitAction)(x))
/*ピン初期化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = OLED_SCL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = OLED_SDA;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C開始
* @param なし
* @retval なし
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param なし
* @retval なし
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2Cに1バイト送信
* @param Byte 送信する1バイト
* @retval なし
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SDA(1); //SDAバスを解放
OLED_W_SCL(1); //追加の1クロック、ACK信号は処理しない
OLED_W_SCL(0);
}
/**
* @brief OLEDにコマンドを書き込む
* @param Command 書き込むコマンド
* @retval なし
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //スレーブアドレス
OLED_I2C_SendByte(0x00); //コマンド書き込み
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLEDにデータを書き込む
* @param Data 書き込むデータ
* @retval なし
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //スレーブアドレス
OLED_I2C_SendByte(0x40); //データ書き込み
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @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に1文字表示
* @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画面クリア
}
タイミング図
実際に動作しているAM2320の命令送信とデータ読み出しのタイミング図。
おすすめ記事
- コストパフォーマンスに優れた安価なVPS/クラウドサーバーおすすめ: https://blog.vpszj.cn/archives/41.html
- NPSを使った内網穿透サーバーの構築、Webパネル付き:https://blog.vpszj.cn/archives/748.html
- Linuxでウェブサイトを構築するチュートリアル:https://blog.vpszj.cn/archives/1094.html
- マイクラサーバー構築チュートリアル:https://blog.zeruns.com/tag/mc/
- STM32とHC-SR04モジュールを使った超音波距離測定機能の実現:https://blog.zeruns.com/archives/680.html
- ESP8266開発環境の構築とプロジェクトデモ:https://blog.zeruns.com/archives/526.html













