ArduinoとSCD30、OLED、u8g2で作る日本語表示のCO2濃度・温湿度計

Arduino Uno Rev3とSCD30センサモジュール、SSD1309搭載の2.42インチOLED(有機ELディスプレイ)で作ったCO2濃度・温湿度計の表示画面をu8g2.drawXBMP()で日本語表記するスケッチ(プログラム)作成時のメモです。リビングの窓を全開にしている時のCO2濃度は500ppm以下でした。

ArduinoとSCD30、OLED、u8g2で作る日本語表示のCO2濃度・温湿度計
OLEDは黒色がくっきりで表示が鮮明、斜めからも良く見えます
目次

準備したパーツ

昨年作ったCO2濃度・温湿度計の「ESP32-DevkitC」を「Arduino Uno Rev3」に置き換えました。ArduinoのI2C信号レベルに合わせて5Vから3.3Vへの双方向電圧レベル変換とu8g2ライブラリ対応のスケッチ修正を行います。

あわせて読みたい
ESP32-DevKitCと二酸化炭素センサ SCD30モジュールを使ってCO2濃度をLCDに表示:環境モニタ(5) 窓を全開にして換気するとCO2濃度が500ppm台に下がるので、換気のタイミングを計るのに重宝しています。ESP32-DevKitCと各種センサで構築中の環境モニタにSensirion社の...

OLEDの仕様、集めたパーツ

ネット通販で集めたOLEDの仕様です。サイズ、発光色の異なる2個で試しました。

#サイズ
発光色
解像度接続方式(I2Cアドレス)ドライバーIC電源電圧(V)
12.42インチ
黄色
128×64I2Cに設定変更(0x3C)
※デフォルトはSPI
SSD13093.3
20.96インチ
青色
128×64I2C(0x3C)SSD13063.3〜5.0

OLED以外のパーツです。モジュールの電源電圧とI2Cの電圧レベルは3.3Vで統一です。ArduinoのI2C電圧レベルは5Vなので双方向電圧レベル変換モジュールを入れて3.3Vにしています。

#パーツ個数
1Arduino Uno Rev31
2I2Cバス用双方向電圧レベル変換モジュール(PCA9306)
プルアップ抵抗付き(1kΩx4個)
1
3SCD30 CO2センサ モジュール
※別途ピンヘッダ(1×4)を準備してはんだ付け
1
4ピンヘッダ 1列タイプ 40ピン
※40ピンから4ピンをニッパでカット
1
6サンハヤト ニューブレッドボード SAD-1011
7ジャンパーワイヤ(オス-オス、メス-オス)適量
8USBケーブル(USB A オス to B オス、PC接続用)1
(手持ち)

大型のOLEDは消費電流も増えます。Arduino Unoのバージョンによっては3.3V出力が不足するかもしれません。Uno Rev3は3.3VのレギュレータとしてLP2985-33DBVRを搭載しているのでマージンがあったものと思います。

Sensirion(センシリオン)社のSCD30センサ

CO2センサを選ぶにあたってTVOC(Total Volatile Organic Compounds 総揮発性有機化合物の総称)測定がメインのセンサ(VOCガス濃度からCO2濃度を概算)もあるので迷いましたが、CO2 濃度測定の精度が高いSensirion社の 非分散型赤外線(NDIR)ベースの SCD30センサモジュールを使っています。
このモジュールには基板内に同じSensirion社のSHT31センサを補正用として内蔵しているので湿度や温度も測定できますが、SCD30センサモジュールとしての動作温度は0℃~+50℃の仕様です。

SCD30 CO2 センサ モジュール基板
SCD30 CO2 センサ モジュール基板
補正用のSHT31センサ 付近の拡大
補正用のSHT31センサ 付近の拡大

2.42インチOLED(有機ELディスプレイ)をSPI接続からI2C接続に変更

購入した2.42インチOLEDはSPI接続がデフォルトでした。転送速度はSPIが高速ですが、時計やCO2濃度・温湿度測定といった用途なので配線数が少ないI2C接続に変更しています。

あわせて読みたい
2.42インチOLED(有機ELディスプレイ)をSPI接続からI2C接続に変更 ネット通販で購入したSSD1309搭載の2.42インチOLED(有機ELディスプレイ)の接続インターフェースを基板裏面のランドパターンを使ってSPIからI2Cに変更した際のメモです...

ブレッドボード上で結線

モジュール間の結線は、I2Cの2線(SCL、SDA)と電源の2線(3.3V、GND)の4線なので容易です。Arduino Uno Rev3とブレッドボード上に配置した双方向電圧レベル変換モジュール、SCD30 CO2 センサ モジュール、OLEDを4色に色分けしたジャンパーワイヤでつないでいきます。

それぞれのOLED単体でも動作しますが、今回両方のOLEDを同じI2CアドレスのままI2Cバスにつないで、一つのスケッチで同時表示しています。

CO2 濃度・温湿度計の結線図
CO2 濃度・温湿度計の結線図
ArduinoとSCD30、OLED、u8g2で作る日本語表示のCO2濃度・温湿度計
CO2 濃度と温湿度を日本語形式で表示

スケッチ:記号や漢字はHEXデータをu8g2.drawXBMP()で描画

SCD30センサライブラリは、SparkFunサイトのライブラリ「sparkfun/SparkFun_SCD30_Arduino_Library(version 1.0.16)」を利用させていただきました。 サイトの「Code」プルダウンから「SparkFun_SCD30_Arduino_Library-main.zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」でダウンロードしたzipファイルを指定します。

U8g2lib.hは、Arduino IDEメニューの ツール –> ライブラリの管理 からライブラリマネージャを開いて「U8g2」で検索してインストールします。
U8g2の詳細は下記にあります。今回使ったOLEDのドライバーIC:SSD1306とSSD1309もサポートされています。
https://github.com/olikraus/u8g2
https://github.com/olikraus/u8g2/wiki/u8g2reference

u8g2.setFont()で使うフォント見本は、
https://github.com/olikraus/u8g2/wiki/fntlistall
を参考にしました。今回は英数字の表示用フォントとして「u8g_font_helvR08」と「u8g_font_fub17」を使っています。u8g_font_helvR08は、細かすぎて画像作成が難しかった「(ppm)」表示に使っています。

CO2 濃度・温湿度計で使った記号や漢字は「CO2、(℃)、(%)、温、湿、度」と少ないので、メモリ消費を抑えるために利用する特定の文字のみです。これらの記号や漢字は画像(BMPファイル)から変換した32×16ピクセルと16×16ピクセルのHEXデータをu8g2.drawXBMPで描画します。グラフィック型なので CO2 のように「2」を下付き画像で表示できます。

最大32256バイトのフラッシュメモリのうち、スケッチが20312バイト(62%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が1577バイト(77%)を使っていて、ローカル変数で471バイト使うことができます。
スケッチが使用できるメモリが少なくなっています。動作が不安定になる可能性があります。

記号や漢字のBMPファイルは、Windows標準アプリのペイント3Dで100×100ピクセル程度で作った後、キャンパスを32×16、16×16ピクセルに変更して圧縮します。そのBMPファイルをHEXデータに変換します。HEXデータへの変換にはProgramResource.netサイトの「nfBmptoHex.exe」を利用させていただきました。

OLED画面の表示スペースは、CO2濃度は5桁、温湿度は整数部3桁、少数以下1桁です。

arduino_scd30_oled.ino  ※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <U8g2lib.h>    // U8g2ライブラリ
#include <SparkFun_SCD30_Arduino_Library.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0,5,4) ;
SCD30 airSensor;

// 漢字画像(BMP)から変換したHEXデータ
const unsigned char co2_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xE1, 0x07, 0x00, 0x0C, 0x31, 0x0C, 0x00, 0x04, 0x18, 0x08, 0x00, 0x06, 0x08, 0x18, 0x00, 0x06, 0x08, 0x18, 0x00, 0x06, 0x08, 0x98, 0x07, 0x06, 0x08, 0x98, 0x0E, 0x06, 0x18, 0x18, 0x0C, 0x0C, 0x18, 0x0C, 0x04, 0xF8, 0xF1, 0x07, 0x06, 0xF0, 0xC0, 0x01, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x00, };
const unsigned char onn_bmp[] PROGMEM = {0x00, 0x00, 0xE2, 0x1F, 0x24, 0x20, 0x24, 0x20, 0xE0, 0x3F, 0x22, 0x20, 0xE4, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0x94, 0x24, 0x94, 0x24, 0x96, 0x24, 0x92, 0x24, 0xFA, 0x7F, 0x00, 0x00, };
const unsigned char doo_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x01, 0xF8, 0x7F, 0x44, 0x08, 0x44, 0x08, 0xF4, 0x7F, 0x44, 0x08, 0xC4, 0x0F, 0x04, 0x00, 0xF4, 0x1F, 0x64, 0x10, 0xC4, 0x18, 0x00, 0x07, 0x82, 0x0F, 0x78, 0x70, 0x00, 0x00, };
const unsigned char stu_bmp[] PROGMEM = {0x00, 0x00, 0xC0, 0x3F, 0x24, 0x20, 0x28, 0x20, 0xE0, 0x3F, 0x22, 0x20, 0xE4, 0x3F, 0x00, 0x05, 0x20, 0x25, 0x20, 0x25, 0x28, 0x25, 0x04, 0x25, 0x44, 0x05, 0x06, 0x0D, 0xF0, 0x7F, 0x00, 0x00, };
const unsigned char pac_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x58, 0x8A, 0x4C, 0x8A, 0x46, 0x8B, 0x82, 0x71, 0x81, 0x81, 0x8E, 0xC3, 0x92, 0x42, 0x93, 0x22, 0x53, 0x34, 0x4C, 0x00, 0x00, 0x00, 0x00, };
const unsigned char dig_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0x3A, 0x40, 0xAA, 0x47, 0x7A, 0x48, 0x63, 0x80, 0x21, 0x80, 0x21, 0x80, 0x23, 0xC0, 0x62, 0x50, 0xC2, 0x4C, 0x82, 0x47, 0x04, 0x20, 0x00, 0x00, };

void setup(void) {
  u8g2.begin();
  u8g2.setContrast(0);  // 明るさも多少変わる(0~255)@2022-05-19追加
  u8g2.enableUTF8Print();
  airSensor.begin();  // SCD30センサ初期化
}

void loop(void) {
  u8g2.firstPage();
  do {
  u8g2.setBitmapMode(false /* solid */);
  u8g2.setDrawColor(1);
// BMP画像の表示
  u8g2.drawXBMP( 0,  3, 32, 16, co2_bmp);
    u8g2.setFont(u8g_font_helvR08);
    u8g2.setCursor(32,14);
    u8g2.print("(ppm)");
  u8g2.drawXBMP( 0, 24, 16, 16, onn_bmp);
  u8g2.drawXBMP(16, 24, 16, 16, doo_bmp);
  u8g2.drawXBMP(34, 24, 16, 16, dig_bmp);
  u8g2.drawXBMP( 0, 48, 16, 16, stu_bmp);
  u8g2.drawXBMP(16, 48, 16, 16, doo_bmp);
  u8g2.drawXBMP(34, 48, 16, 16, pac_bmp);
// SCD30データの表示
  u8g2.setFont(u8g2_font_fub17_tf);
  if (airSensor.dataAvailable()){
    u8g2.setCursor(64, 18);
  u8g2.print(airSensor.getCO2());
    u8g2.setCursor(64, 40);
  u8g2.print(airSensor.getTemperature(),1);
    u8g2.setCursor(64, 64);
  u8g2.print(airSensor.getHumidity(),1);
  }
      } while ( u8g2.nextPage() );
  delay(2000);
}
よかったらシェアしてね!
  • URLをコピーしました!
目次