16.picoで温湿度&気圧ロガー Arduino


16.picoで温湿度&気圧ロガー Arduino

ENV2のセンサーで温湿度&気圧を測定し24Hの推移グラフを表示します。

必要ハード

・pico
・センサー : M5Stack用ENV2 (I2C1)
・モニタ : Pico-ResTouch-LCD-3.5 (SPI1)

M5Stack用ENV2センサー

詳しくは、"08.Wio Terminalで温湿度・気圧表示"を参照ください。

ENV2の特性

・温湿度センサIC SHT30 (I2C:0x44)
 (0〜60)±0.2℃, (10〜90)±2%RH
 Adafruit SHT31 Library by Adafruit Ver2.2.0使用

・絶対大気圧&温度センサIC BMP280 (I2C:0x76)
 (300〜1100)±1hPa, (0-65)±1℃
 Adafruit BMP280 Library by Adafruit Ver2.6.3使用

ENV2の接続

(ENV2)---(線色)---(pico)
I2C_SCL(白)--(黒) 5pin I2C1_SCL GP3
I2C_SDA(黄)--(青) 4pin I2C1_SDA GP2
Vcc(5V)(赤)--(赤)36pin 3V3out
GND----(黒)--(黒) 3pin GND

ここで
・I2C0 = Wire
・I2C1 = Wire1
です。

温湿度・気圧の過去測定値

横浜の観測史上1~10位の値
https://www.data.jma.go.jp/obd/stats/etrn/view/rank_s.php?prec_no=46&block_no=47670&year=&month=&day=&view=

ただし、下記*の4点は2021年の月平均値です。
https://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s1.php?prec_no=46&block_no=47670&year=2021&month=&day=&view=

気温

日最高 : 37.4℃ (2016/8/9)
日最低 : -8.2℃ (1927/1/24)

相対湿度

月平均最大 : 82% (2021/7)*
日最小 : 8% (2003/2/28)

海面気圧

月平均最高 : 1018.4hPa (2021/10)*
日最低 : 953.8hPa (1917/10/1)

現地気圧

月平均最高 : 1013.4hPa (2021/10)*
月平均最低 : 1004.5hPa (2021/5)*

* 横浜地方気象台 (横浜市中区山手町)

スケッチ説明

全体
左から、pico、センサー、wifi(動作はさせていません)、モニタです。
拡大
窓を開け風が時々強い部屋で測定したので、データが上下に動いています。フィルタ計算をした方が良いかも。
・モニタは480x320で、グラフ表示範囲は360x250です。
・delay(240000)=240秒=4分で1画面4分x360px=24Hとなります。
・測定精度は、温度±0.2℃, 湿度±2%RH, 気圧±1hPa なので、湿度と気圧は整数、温度は測定値を10倍して整数で計算し、表示時に10で割ります。
・y軸値は初期測定値をセンターとします。
・初期y軸の刻み幅は、
温度:Δ0.1℃で2.5px (軸初期max-min=10℃)
 24H以内に初期値から5℃以上変化するとy軸の値が変わります。
湿度:Δ1%で2.5px (軸0-100%固定表示)
気圧:Δ1hPaで2.5px (軸初期max-min=100hPa)

データ配列

buf[j][i]に測定値を入れます。jは、0:温度,1:湿度,2:気圧で、iは時間軸(横)の各データ値です。測定前の初期値は-999とし、表示はしません。

データ更新

サンプリングスピード1回/4分で1画面24Hです。横軸の右端が現在点、左端が24H前です。tft.fillScreen で1画面全体を書き換えるとそのたびにちらつくので tft.drawFastVLine で1列づつ書換え、流れるようにデータが移動しているように見えます。

気温(緑)→湿度(空色)→気圧(薄紫)の順にプロットしているので、気温と湿度が同じ座標位置ならば先の気温の方のプロットが消えます。データ値は保存してあるので軸が変われば表示します。

スケッチ


// 温湿度気圧の測定値を24Hグラフで表示 モニタ480x320,ENV2必要
// 入力変動値によって軸のy値を一部自動調整
// tft.setFreeFont(&FreeMono{-/Bold}{9/12/18/24}pt7b);

#include <Wire.h>                 // I2C通信(センサー)使用
#include <Adafruit_SHT31.h>       // 温湿度センサIC使用(0~60)±0.2℃,(10~90)±2%
Adafruit_SHT31 sht3x = Adafruit_SHT31(&Wire1); //
#include <Adafruit_BMP280.h>      // 気圧センサIC使用 (300~1100)±1hPa
Adafruit_BMP280 bme = Adafruit_BMP280(&Wire1); //
#include <SPI.h>                  // SPI通信(モニタ)使用
#include <TFT_eSPI.h>             // ハードウェア固有のライブラリ
TFT_eSPI tft = TFT_eSPI();        // カスタムライブラリを呼出す
#define GFXFF 1                   // GFXFFフォント使用
const int xx = 360, yy = 250;     // xx,yy:表示範囲
const int x00 = 110, y00 = 35;    // x00,y00:グラフ枠原点
const int x01 = x00 + 1, y01 = y00 + yy; //
const int y55 = yy / 2 + y00 + 1; // 枠縦の中央y
int buf[3][xx + 1];               // データ保持用 0:温度,1:湿度,2:気圧
int Dmin[3], Dmax[3], Gy[3], Gynext[3]; // 最小値,最大値,グラフのy値
char TXTbuf[20];                  // txt用バッファ
int xpos, ypos;                   // x,y値

void setEVN2() {                   // センサーEVN2初期設定
  Wire1.setSDA(2);                 // GP2 i2c1(Wire1)
  Wire1.setSCL(3);                 // GP3 i2c1(Wire1)
  Wire1.begin();                   // Wireライブラリ初期化 ()はマスタとしてバスに接続
  while (!sht3x.begin(0x44)) {     // 接続するまでループ
    Serial.println("ENV2 未接続");  // SHT31が見つからない
  }
  while (!bme.begin(0x76)) {       // 接続するまでループ
    Serial.println("ENV2 未接続");  // BMP280が見つからない
  }
}

void setGamen() {                // 画面初期設定関数
  tft.init();                    // TFT_eSPIライブラリを初期化
  tft.setRotation(1);            // 回転 0:縦,1:左が下,2:逆さま,3:右が下 (CNを上にして)
  tft.invertDisplay(1);          // なぜか色反転しないとだめ
  tft.fillScreen(TFT_BLACK);     // 画面クリア(黒)
  //tft.setCursor(0,0);          // カーソル(x,y)
  //tft.setTextColor(TFT_GREEN, TFT_BLACK); // 緑字 黒地
  tft.setTextDatum(TL_DATUM);    // 文字の基準位置(左上) default
}
void printTitle() {
  xpos = x00 + 19; ypos = 4;                  // 表示位置
  tft.setFreeFont(&FreeMono12pt7b);           // フォント設定
  tft.setTextColor(TFT_GREEN, TFT_BLACK);     // 緑字 黒地
  tft.drawString("Temp", xpos, ypos, GFXFF);  // 表示 4文字
  tft.setTextColor(TFT_SKYBLUE, TFT_BLACK);   // 明るい青色字 黒地
  xpos += 5 * 14;                             // 右へ移動
  tft.drawString("Hum(0-100%)", xpos, ypos, GFXFF); // 表示 12文字
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);   // 薄紫字 黒地
  xpos += 12 * 14;                            // 右へ移動
  tft.drawString("Pres", xpos, ypos, GFXFF);  // 表示 4文字
  tft.setTextColor(TFT_WHITE, TFT_BLACK);     // 白字 黒地
  xpos += 5 * 14;                             // 右へ移動
  tft.drawString("24H", xpos, ypos, GFXFF);   // 表示
}

void setup() {
  Serial.begin(115200);               // システムモニタ初期設定
  setGamen();                         // 表示画面の初期設定関数へ
  printTitle();                       // タイトル表示
  setEVN2();                          // センサー初期設定
  while (!Serial && millis() < 5000); // モニタを開いてなく起動5秒以内なら待つ
  for (int j = 0; j < 3; j++) {
    for (int i = 0; i <= xx; i++) buf[j][i] = -999;    // 初期値(-999)代入
  }
  tft.drawRect(x00, y00, xx + 2, yy + 2, TFT_YELLOW);  // 枠表示
  tft.drawFastHLine(x00 - 5, y55, 5, TFT_YELLOW);      // 中央水平線左
  tft.drawFastHLine(x00 + xx + 2, y55, 5, TFT_YELLOW); // 中央水平線右
  //tft.setTextSize(1);                                  // 文字サイズ倍率(整数)
}

void loop() {
  for (int j = 0; j < 3; j++) {
    Dmin[j] = 9999;                    // 最低値初期値
    Dmax[j] = -99;                     // 最高値初期値
    for (int i = 0; i < xx; i++) {     // データ全てについて
      buf[j][i] = buf[j][i + 1];       // 1つ左へずらす
      if (buf[j][i + 1] != -999) {     // 初期値なら除外
        if (buf[j][i] < Dmin[j]) Dmin[j] = buf[j][i]; // 最低値を見つける
        if (buf[j][i] > Dmax[j]) Dmax[j] = buf[j][i]; // 最高値を見つける
      }
    }
  }
  buf[0][xx] = (int)(sht3x.readTemperature() * 10); // 温度を読取る 小数点1桁を整数化
  buf[1][xx] = (int)(sht3x.readHumidity());         // センサーの湿度を読取る
  buf[2][xx] = (int)(bme.readPressure() / 100);     // 圧力を読取る 単位変換
  for (int j = 0; j < 3; j++) {
    if (buf[j][xx] < Dmin[j]) Dmin[j] = buf[j][xx]; // 小さかったら最低値に
    if (buf[j][xx] > Dmax[j]) Dmax[j] = buf[j][xx]; // 大きかったら最高値に
  }
  if (Dmax[0] - Dmin[0] < 100) {  // y軸差が小さすぎたら大きくする
    Dmin[0] -= 50; Dmax[0] += 50; // ±5℃ 1データ変化2.5px刻み
  }
  if (Dmax[1] - Dmin[1] < 100) {
    Dmin[1] = 0; Dmax[1] = 100;   // 0-100% 2.5px刻み
  }
  if (Dmax[2] - Dmin[2] < 100) {
    Dmin[2] -= 50; Dmax[2] += 50; // ±50hPa 2.5px刻み
  }

  xpos = 0; ypos = y00 - 25;                  // 軸最大値表示位置
  tft.setTextColor(TFT_GREEN, TFT_BLACK);     // 緑字 黒地
  sprintf(TXTbuf, "%5.1f'C", Dmax[0] / 10.0); // 文字列作成
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);  // 表示
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);   // 薄紫字 黒地
  sprintf(TXTbuf, "%4dhPa", Dmax[2]);         // 文字列作成
  ypos += tft.fontHeight(GFXFF);              // フォント高さを取得し下に移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);  // 表示

  ypos = y55 - 45;                                   // 軸中央値表示位置
  tft.setTextColor(TFT_GREEN, TFT_BLACK);            // 緑字 黒地
  sprintf(TXTbuf, "%5.1f'C", (Dmax[0] + Dmin[0]) / 20.0); // 文字列作成
  ypos += tft.fontHeight(GFXFF);                     // フォント高さを取得し下に移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);         // 表示
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);          // 薄紫字 黒地
  sprintf(TXTbuf, "%4dhPa", (Dmax[2] + Dmin[2]) / 2);// 文字列作成
  ypos += tft.fontHeight(GFXFF);                     // フォント高さを取得し下に移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);         // 表示

  ypos = y01 - 43;                            // 軸最小値表示位置
  tft.setTextColor(TFT_GREEN, TFT_BLACK);     // 緑字 黒地
  sprintf(TXTbuf, "%5.1f'C", Dmin[0] / 10.0); // 文字列作成
  ypos += tft.fontHeight(GFXFF);              // フォント高さを取得し下に移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);  // 表示
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);   // 薄紫字 黒地
  sprintf(TXTbuf, "%4dhPa", Dmin[2]);         // 文字列作成
  ypos += tft.fontHeight(GFXFF);              // フォント高さを取得し下に移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);  // 表示

  xpos = x00 + 45; ypos = y01 + 10;              // データ表示位置 下
  tft.setTextColor(TFT_GREEN, TFT_BLACK);        // 緑字 黒地
  sprintf(TXTbuf, "%4.1f'C", buf[0][xx] / 10.0); // 文字列作成
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);     // 表示
  tft.setTextColor(TFT_SKYBLUE, TFT_BLACK);      // 明るい青色字 黒地
  sprintf(TXTbuf, "%3d%%", buf[1][xx]);          // 文字列作成
  xpos += 8 * 11;                                // 右へ移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);     // 表示
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);      // 薄紫字 黒地
  sprintf(TXTbuf, "%4dhPa", buf[2][xx]);         // 文字列作成
  xpos += 6 * 11;                                // 右へ移動
  tft.drawString(TXTbuf, xpos, ypos, GFXFF);     // 表示
  for (int i = 0; i < xx; i++) {                 // グラフ表示
    for (int j = 0; j < 3; j++) {
      if (buf[j][i] != -999) {                   //-999は表示しない
        Gy[j] = (int)((float)(buf[j][i] - Dmin[j]) / (Dmax[j] - Dmin[j]) * yy); //グラフのy座標
        Gynext[j] = (int)((float)(buf[j][i + 1] - Dmin[j]) / (Dmax[j] - Dmin[j]) * yy); //
      }
    }
    if (buf[0][i] != -999) {                                // -999は表示しない
      tft.drawFastVLine(x01 + i, y00 + 1, yy, TFT_BLACK);   // 1列消す
      tft.drawPixel(x01 + i, y01 - Gynext[0], TFT_GREEN);   // 気温プロット
      tft.drawPixel(x01 + i, y01 - Gynext[1], TFT_SKYBLUE); // 湿度プロット
      tft.drawPixel(x01 + i, y01 - Gynext[2], TFT_MAGENTA); // 気圧プロット
    }
  }
  delay(240000); // 500→1画面3分,10k(10s)→1H,60k(1分)→6H,120k(2分)→12H,240k(4分)→24H
}
* フラッシュメモリ(1Mbyte)のうち、スケッチが10%使用。RAM(262kbyte)のうち、グローバル変数が5%使用し、ローカル変数で248kbyte使用可能。(1000byte=1kbyteで計算)。