クルマのバッテリー電圧・車室内のCO2濃度をリアルタイムにグラフ表示

#経緯
最近のクルマには、電気系装備の充実やアイドリングストップ機能等への対応のため、○ソでか重い12Vバッテリーが載っています。一方で、ウチのクルマのモニターでは、色んな情報を表示してくれるものの、バッテリー電圧だけは表示してくれません。
ディーラーで「最近のバッテリーって急にダメになるんですよねぇ」と言われ、バッテリーの電圧が妙に気になり始めました。イイお値段しますし。
そもそも運転中どんな時に上下するのか、寒くなるとどのくらい電圧下がるのか、「推移」をグラフで見たいなと、小さなMaker魂に火がつきました。
そんな時、背後の押し入れから声が。

液晶モジュール：「買って満足してないで使えや」

**データ可視化はとても楽しいですが、運転には十分に注意しましょう（自戒）**

#製作の方針
車載したかったので、
　① 本末転倒にならない程度に省電力・小型であること
　② 電源ON/OFFに気を遣わなくて良いこと
から、これまでにも使ったことのあるESP8266(ESPr-Developer)を選びました。とても使いやすいArduinoです。

バッテリー電圧は分圧（12～14V → 0～1V）してアナログ値として取り込み、またネタとして車室内の環境も計測することにしました。使ったセンサーは以下の二つ。
　・MH-Z19C：　二酸化炭素濃度
　・BME280：　温度、湿度、気圧


#つくったもの

| 完成品（車載状態） | 単体 画面拡大 | 内部機器の配置 |
|:-:|:-:|:-:|
| ![IMG_20211214_075707-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272621/e890b705-6e99-b568-0dc5-871ed40e43e9.jpeg) | ![IMG_20211230_152935-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272621/69c76095-3b92-f653-4ab5-928c98a90da1.jpeg) | ![IMG_20211230_150541-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272621/51b7c98d-4784-04e0-7aeb-13ad95cb812c.jpeg) |
| 丁度良いスペースが！  | 【Point】グラフ縦軸は各数値の変化に自動で追従  | ESP8266発熱を考慮してBME280を離して配置  |

#材料
・ESPr-Develper(ESP8266) Rev.2　：千石電商
・TFT液晶モジュール(2.8インチ, 240x320, ILI9341制御)　：Amazon
・MH-Z19C　：秋月電子
・BME280　：秋月電子
・OBD2コネクタ　：Amazon
・抵抗（470Ω x 1, 10kΩ x 1）
・押しボタンスイッチ（モーメンタリ x 1）
・ピンヘッダ（メス）
・ケーブル類
・ジュラコンスペーサー（25mm x 4、5mm x 4）
・M3x15 六角穴付きボルト x 8本
・端子台
・基板（をTFTと同程度のサイズにカット）
・プラ板（背面絶縁カバー用）←100均タッパのフタから切り出し
・ギボシ端子（オスx2、メスx2）→ 本体からOBD2コネクタへの配線途中に使用
・ブレッドボード（試作確認用）他、工作工具

総額：8,000円くらい（今回、追加購入したのはMH-Z19Cのみ）

#配線・接続
配線は下図の様にしました。最後にフラットケーブルにまとめるため、一般的な？配置から数箇所ピンを変更しています。
　・ESPr-DeveloperにはUSBで5V給電
　・バッテリー電圧は分圧（12～14V → 0～1V）してTOUTからアナログ値として取り込み
　・TFT液晶モジュールとBME280は3.3V、MH-Z19Cは5V駆動
　・そしてピン数ギリギリの巻

![接続図2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272621/d4ceab8e-5134-348c-dc1a-1f79201827c3.jpeg)


| ESPr | TFT |
|:-:|:-:|
| 3v3  | VCC  |
| 3v3  | LED  |
| IO14  | SCK  |
| IO12  | SDO  |
| 13  | SDI  |
| 15  | CS  |
| 2  | RESET  |
| 0  | DC/RS  |
| GND  | GND  |

| ESPr | BME280 |
|:-:|:-:|
| 3v3  | VDD  |
| GND  | GND  |
| TXD(1)  | SDI  |
| IO5  | SCK  |

温度の補正は、部屋で他のいくつかの温度計と比較して「えいやあ」で temp-3.4 の補正としました。気温0℃付近の場合、ちょっと低くなり過ぎかもしれません。
センサーに直射日光が当たると、面白いくらい敏感に反応します。

| ESPr | R | OBD2(12V BATT) |
|:-:|:-:|:-:|
| TOUT  | 10 KΩ  | IG  |
| TOUT  | 470 Ω  | GND  |
| GND  | 470 Ω  | -  |
| GND  | -  | GND  |

バッテリー電圧の取得はACCかIG電源から取得します。OBDコネクタでどのピンになるかはメーカーや車によって違うのでよく調べましょう。ヒューズBoxから取得でも良いと思いますが、バッテリー直はNG。

| ESPr | - | MH-Z19C |
|:-:|:-:|:-:|
| VOUT(5V)  | -  | Vin  |
| GND  | -  | GND  |
| GND  | Switch  | HD  |
| RXD(3)  | -  | Rx  |
| IO4  | -  | Tx  |

MH-Z19C(HD) と ESPr-Developer(GND) との間の押しボタンスイッチは大気校正に使います（7秒以上長押し）。


#プログラム環境
Windows10
Arduino IDE 1.8.13

#考察
・可視化楽しい！
・ILI9341液晶を初めて使いましたが発色もよくて良いと思います
・CO2は気にし出すとキリがない＆部屋の換気は思ったよりできてた
・BME280の温度ってあんまり正確じゃないんですね
・TOUTの電圧、解放時に0.2V位を示すのなんでや
・個人的には許せる描写速度だけど、さらに速いと素敵かも
・気圧のF値だけでは降雨予測は難しい
・坂道で気圧が上下するの楽しい
・ちょっと運転席の窓開けたくらいじゃ案外換気できない（外気導入最強）
・工作はキレイなケースを準備するのが一番難しい
・部屋置きしてもGood

**データ可視化はとても楽しいですが、運転には十分に注意しましょう（二回目）**

#考察（補足）
■一番のお目当てだったバッテリー電圧の観察ですが、14.5-14.7v位で低かったり不安定だったりということもなく、元気なことが確認できました。普段使っていませんがアイドリングストップ前後の挙動なんかも今後みたいと思います。
■部屋で気圧の変化を眺めていたら「局地的な天気予報できるんじゃないか」という気がして、直近60個ともう1区間前の60個の分散（データばらつき）の比：F値（厳密には少し違います）を算出表示してみました。
定置観測では通常一桁(~5くらい)で、雨降り前後では気圧が不安定になりF値が一桁後半から二桁になること "も" ありましたが毎回という訳でもなく、自然の偉大さをかみしめています。サンプリング周期や、比較するデータ数もいじってみましたがなかなか難しそうです（スマホ見た方が早いとか言わない）。
■CO2の計測には、当初、安価簡便という理由でCCS811を使いましたが、VOC（揮発性有機炭素）をすべて拾ってくれるので、見事におっさん加齢臭センサーで「ちょっと使えないなぁ」という印象でした。
■少し書き換えれば、色々なデータの推移・傾向をみるのに有効と思います。閃いた方はぜひ使ってみてください。

#プログラム
概要は以下の通り
　1）初期化
　2）各センサーで計測
　3）計測データを二次元配列：Data[12][250]に格納
　4）格納したデータを基に加工・グラフ描写
　5）一定時間：update_sec ごとに 2）～4）を繰り返し

■データ構成／二次元配列：Data[12][250]
・直近データが[x][0]に入り、過去のデータは1個ずつ後ろにずらす
・当初、0~3行に気圧データの加工が集中しているのは、気圧の変化に注目していた名残

| 項目             | Data[x][y] | y=1 | 2 | 3 | … | 247 | 248 | 249 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 気圧（生データ）  | [0][0]  | [0][1]  | [0][2]  | …  |   |   |   |   |
| 気圧（平均値との差分）      | [1][0]  | [1][1]  | …  |   |   |   |   |   |
| 気圧（差分^2）     | [2][0] | …  |   |   |   |   |   |   |
|  気圧（グラフ描写用）| [3][0]  |   |   |   |   |   |   |   |
| 温度（生データ）  | [4][0]  |   |   |   |   |   |   |   |
| 温度（グラフ描写用）  | [5][0]  |   |   |   |   |   |   |   |
| 湿度（生データ）  | [6][0]  |   |   |   |   |   |   |   |
| 湿度（グラフ描写用）  | [7][0]  |   |   |   |   |   |   |   |
| 電圧（生データ）  | [8][0]  |   |   |   |   |   |   |   |
| 電圧（グラフ描写用）  | [9][0]  |   |   |   |   |   |   |   |
| CO2（生データ）  | [10][0]  |   |   |   |   |   |   |   |
| CO2（グラフ描写用）  | [11][0]  |   |   |   |   |   |   |   |

<details><summary>コードはこちら（折りたたみ表示）</summary><div>

```cpp:DrawGraph.ino
//所々入っているdelay()は不要なものもあるかも

#include <Wire.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_GFX.h>
#include <MHZ19_uart.h>

// Analog入力用
extern "C"{
  #include "user_interface.h"
  }

#define TFT_RST 2
#define TFT_DC 0
#define TFT_CS 15

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

const int tx_pin = 3;
const int rx_pin = 4;

MHZ19_uart mhz19;

#define BME280_ADDRESS 0x76
unsigned long int hum_raw,temp_raw,pres_raw;
signed long int t_fine;

uint16_t dig_T1;
 int16_t dig_T2;
 int16_t dig_T3;
uint16_t dig_P1;
 int16_t dig_P2;
 int16_t dig_P3;
 int16_t dig_P4;
 int16_t dig_P5;
 int16_t dig_P6;
 int16_t dig_P7;
 int16_t dig_P8;
 int16_t dig_P9;
 int8_t  dig_H1;
 int16_t dig_H2;
 int8_t  dig_H3;
 int16_t dig_H4;
 int16_t dig_H5;
 int8_t  dig_H6;

float p_temp_c = 0;
float p_humid = 0;
float p_press = 0;
float p_tout_cal = 0;
float p_F_ratio = 0;
float p_varS = 0;
float p_CO2 = 0;

const int Si = 60;  //Short Data 長さ：5sec x 60 = 300sec分
const int Sip = 120;  //Short Data Past 1ブロック前データ
const int Li = 250;  //Long Data 長さ：5sec x 250 = 1250sec分

float Data[12][Li] = {0};
//[0][i]---Pressure/raw -> avg
//[1][i]---Pressure/sbn
//[2][i]---Pressure/sbn^2 -> Variance
//[3][i]---Pressure/for drawGraph
//[4][i]---Temp/raw
//[5][i]---Temp/graph
//[6][i]---Hum/raw
//[7][i]---Hum/graph
//[8][i]---Batt/raw
//[9][i]---Batt/graph
//[10][i]---CO2/raw
//[11][i]---CO2/garph

float sum_avgS = 0.0;
float sum_varS = 0.0;
float avgS =0.0;
float varS = 0.0;

float sum_avgSp = 0.0;
float sum_varSp = 0.0;
float avgSp =0.0;
float varSp = 0.0;

float F_ratio = 0.0;

char buf[16];  //電圧右寄せ表示用
char bufCO2[16];  //CO2

float Vctr = 0;

float Tmax_d = 0;
float Tmin_d = 0;
float Hmax_d = 0;
float Hmin_d = 0;
float Pmax_d = 0;
float Pmin_d = 0;
float Vmax_d = 0;
float Vmin_d = 0;
float Cmax_d = 0;
float Cmin_d = 0;

float Tctr_d = 0;
float Hctr_d = 0;
float Pctr_d = 0;
float Vctr_d = 0;
float Cctr_d = 0;

int update_sec = 5;  //更新sec

void setup() {
  uint8_t osrs_t = 1;             //Temperature oversampling x 1
  uint8_t osrs_p = 1;             //Pressure oversampling x 1
  uint8_t osrs_h = 1;             //Humidity oversampling x 1
  uint8_t mode = 3;               //Normal mode
  uint8_t t_sb = 5;               //Tstandby 1000ms
  uint8_t filter = 0;             //Filter off 
  uint8_t spi3w_en = 0;           //3-wire SPI Disable

  uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
  uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
  uint8_t ctrl_hum_reg  = osrs_h;

  Serial.begin(115200);
  mhz19.begin(rx_pin, tx_pin);
  mhz19.setAutoCalibration(false);  //false
  delay(100);
  
  Wire.begin(1,5);
  writeReg(0xF2,ctrl_hum_reg);
  writeReg(0xF4,ctrl_meas_reg);
  writeReg(0xF5,config_reg);
  readTrim();
  delay(100);
  
  tft.begin();
  tft.fillScreen(ILI9341_BLACK);
  tft.setRotation(3);

  tft.setCursor(0, 0);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(80, 0);
  tft.println("degC");
  tft.setTextColor(ILI9341_CYAN);
  tft.setCursor(80, 20);
  tft.println("%");
  tft.setTextColor(ILI9341_GREEN);
  tft.setCursor(80, 40);
  tft.println("hPa");
  
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_LIGHTGREY);
  tft.setCursor(290, 17);
  tft.println("V");

  updateVaxis();
}

void loop() {
  float temp_act=0.0, press_act=0.0, hum_act=0.0, tout_cal=0.0, CO2=0.0;
  signed long int temp_cal;
  unsigned long int press_cal,hum_cal;
    
  while(CO2 < 1){
    CO2 = mhz19.getPPM();  //稀にデータ取得に失敗するのでループ
    delay(500);
    }

  Wire.begin(1,5);
  
  readBME280();
  
  temp_cal = calibration_T(temp_raw);
  hum_cal = calibration_H(hum_raw);
  press_cal = calibration_P(pres_raw);
      
  temp_act = temp_cal / 100.0 - 3.4;  //温度補正
  hum_act = hum_cal / 1024.0;
  press_act = press_cal / 100.0;
  
  int tout = analogRead(A0);  //1024=1v
  tout_cal = (float)tout/1024*(10000+470)/470 * 14.7/15.2 ;  //抵抗で分圧:*14.7/15.2は実測比補正
  if (tout_cal < 0){
    tout_cal = 0.0;
    }
  delay(100);
    
//  Serial.print("TEMP: ");
//  Serial.print(temp_act);
//  Serial.print(" DegC  PRESS: ");
//  Serial.print(press_act);
//  Serial.print(" hPa  HUM: ");
//  Serial.print(hum_act);
//  Serial.print(" %  Batt: ");
//  Serial.print(tout_cal);
//  Serial.print(" v CO2: ");
//  Serial.print(CO2);
//  Serial.println(" ppm");
//  delay(100);  //

  //全体 データIN前の準備：古いデータを後ろにずらす
  for (int i=Li-1; i>0; i--){
    Data[0][i] = Data[0][i-1];
    Data[4][i] = Data[4][i-1];
    Data[6][i] = Data[6][i-1];
    Data[8][i] = Data[8][i-1];
    Data[10][i] = Data[10][i-1];
    }

  //new data in
  Data[0][0] = press_act;
  Data[4][0] = temp_act;
  Data[6][0] = hum_act;
  Data[8][0] = tout_cal;
  Data[10][0] = CO2;

  //max/min設定
  updateVaxis();

  //グラフ表示用の計算：実測値を縦60-240の座標に換算
  for (int i=0; i<Li; i++){
    Data[3][i] = (240-60)/(Pmax_d-Pmin_d)*(-Data[0][i]+Pmax_d)+60;  //pressure
    Data[5][i] = (240-60)/(Tmax_d-Tmin_d)*(-Data[4][i]+Tmax_d)+60;  //temp
    Data[7][i] = (240-60)/(Hmax_d-Hmin_d)*(-Data[6][i]+Hmax_d)+60;  //hum
    Data[9][i] = (240-60)/(Vmax_d-Vmin_d)*(-Data[8][i]+Vmax_d)+60;  //batt
    Data[11][i] = (240-60)/(Cmax_d-Cmin_d)*(-Data[10][i]+Cmax_d)+60;  //CO2
    }

  //軸用数値のクリア  _d: for Display
  Tmax_d = 0;
  Tmin_d = 0;
  Hmax_d = 0;
  Hmin_d = 0;
  Pmax_d = 0;
  Pmin_d = 0;
  Vmax_d = 0;
  Vmin_d = 0;
  Cmax_d = 0;
  Cmin_d = 0;

  //0-Si区間の平均算出：avgS
  for (int i=0; i<Si; i++){
    sum_avgS = sum_avgS + Data[0][i];
    }
    avgS = sum_avgS/Si;

  //全体 sbn=Data[1][i]からsbn^2算出
  for (int i=0; i<Si; i++){
    Data[1][i] = Data[0][i] - avgS;
    Data[2][i] = pow(Data[1][i], 2);  //^2
    }

  //varS
  for (int i=0; i<Si; i++){
    sum_varS = sum_varS + Data[2][i];
    }
    varS = sum_varS/Si;

  //Si-Sip区間の平均値算出：avgSp S_Past
  for (int i=Si; i<Sip; i++){
    sum_avgSp = sum_avgSp + Data[0][i];
    }
    avgSp = sum_avgSp/(Sip-Si);

  //Si-Sip区間の分散
  for (int i=Si; i<Sip; i++){
    Data[1][i] = Data[0][i] - avgSp;
    Data[2][i] = pow(Data[1][i], 2);  //^2
    }
    
  //varSp
  for (int i=Si; i<Sip; i++){
    sum_varSp = sum_varSp + Data[2][i];
    }
    varSp = sum_varSp/(Sip-Si);

  //F-value; F_ratio v1=v2=59, 10%:1.40 / 5%:1.54 / 1%:1.85
  F_ratio = varS/varSp;

  //TFT数値表示の準備：前回と数字が違っていたら黒塗りで消去
  if (temp_act != p_temp_c || hum_act != p_humid ||
      press_act != p_press|| tout_cal != p_tout_cal ||
      F_ratio != p_F_ratio || varS != p_varS || CO2 != p_CO2) {
    // Clear
    tft.setCursor(0, 0);
    tft.setTextColor(ILI9341_BLACK);
    tft.setTextSize(2);
    tft.println(p_temp_c); 
    tft.setCursor(0, 20);
    tft.println(p_humid);
    tft.setCursor(0, 40);
    tft.println(String(p_press, 1));
    tft.setTextSize(2);
    tft.setCursor(235, 40);
    tft.print("F:");
    tft.println(p_F_ratio);
  }
   
  // TFT数値表示
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.println(temp_act);
  tft.setCursor(0, 20);
  tft.println(hum_act);
  tft.setCursor(0, 40);
  tft.println(String(press_act, 1));

  //Volt
  tft.setTextSize(4);
  tft.setCursor(160, 5);
  tft.setTextColor(ILI9341_BLACK);  //vはここで黒塗り
  tft.println(dtostrf(p_tout_cal, 5, 2, buf));
  tft.setCursor(160, 5);
  tft.setTextColor(ILI9341_WHITE);
  tft.println(dtostrf(tout_cal, 5, 2, buf));  //5桁.2桁右詰め

  //CO2
  tft.setTextSize(2);
  tft.setCursor(130, 40);
  tft.setTextColor(ILI9341_BLACK);  //ここで黒塗り
  tft.println(dtostrf(p_CO2, 4, 0, bufCO2));
  tft.setCursor(130, 40);
  tft.setTextColor(ILI9341_WHITE);
  tft.println(dtostrf(CO2, 4, 0, bufCO2));  //4桁.0桁右詰め

  //F表示
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.setCursor(235, 40);
  tft.print("F:");
  tft.println(F_ratio);

  //単位再描写
  tft.setCursor(0, 0);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(80, 0);
  tft.println("degC");
  tft.setTextColor(ILI9341_CYAN);
  tft.setCursor(80, 20);
  tft.println("%");
  tft.setTextColor(ILI9341_GREEN);
  tft.setCursor(80, 40);
  tft.println("hPa");
  tft.setTextColor(ILI9341_MAGENTA);
  tft.setCursor(185, 40);
  tft.println("ppm");

  //グラフ部黒塗り更新
  tft.fillRect(60, 60, 260, 180, ILI9341_BLACK);  //x1,y1,sx,sy

  //グラフ線の描写：　色は _RED, PINK, ORANGE, PURPLE, OLIVE, BLUE, MAGENTA, GREENYELLOW などあり
  for (int i=0; i<Li-11; i++){
    if (Data[0][i+1] != 0){
      tft.drawLine(309-i, Data[3][i], 309-(i+1), Data[3][i+1], ILI9341_GREEN);
      tft.drawLine(309-i, Data[5][i], 309-(i+1), Data[5][i+1], ILI9341_YELLOW);
      tft.drawLine(309-i, Data[7][i], 309-(i+1), Data[7][i+1], ILI9341_CYAN);
      tft.drawLine(309-i, Data[9][i], 309-(i+1), Data[9][i+1], ILI9341_WHITE);
      tft.drawLine(309-i, Data[11][i], 309-(i+1), Data[11][i+1], ILI9341_MAGENTA);
      }
    }

  //flame&scale表示
  tft.drawLine(60, 60, 60, 240, ILI9341_DARKGREY);
  tft.drawLine(240, 230, 300, 230, ILI9341_LIGHTGREY);
  tft.drawLine(240, 228, 240, 232, ILI9341_LIGHTGREY);
  tft.drawLine(300, 228, 300, 232, ILI9341_LIGHTGREY);
  tft.setCursor(256, 220);
  tft.setTextColor(ILI9341_LIGHTGREY);
  tft.setTextSize(1);
  tft.print(update_sec);
  tft.println(" min");

  p_temp_c = temp_act;
  p_humid = hum_act;
  p_press = press_act;
  p_tout_cal = tout_cal;
  p_F_ratio = F_ratio;
  p_varS = varS;
  p_CO2 = CO2;

  sum_avgS = 0;
  avgS = 0;
  sum_varS = 0;
  varS = 0;

  sum_avgSp = 0;
  avgSp = 0;
  sum_varSp = 0;
  varSp = 0;

  delay(update_sec * 1000);
}

//---------------------------------

void readBME280() {
  int i = 0;
  uint32_t data[8];

  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0xF7);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS,8);
  
  while(Wire.available()){
    data[i] = Wire.read();
    i++;
    }

  pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
  temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
  hum_raw  = (data[6] << 8) | data[7];
  }

void updateVaxis() {
  float temp_act=0.0, press_act=0.0, hum_act=0.0, tout_cal=0.0, CO2=0.0;
  signed long int temp_cal;
  unsigned long int press_cal,hum_cal;

  Wire.begin(1,5);
  readBME280();
  temp_cal = calibration_T(temp_raw);
  hum_cal = calibration_H(hum_raw);
  press_cal = calibration_P(pres_raw);
  delay(100);

  int tout = analogRead(A0);  //1024=1v
  Vctr = (float)tout/1024*(10000+470)/470 * 14.7/15.4;  //抵抗で分圧: *14.7/15.4は実測値から補正
  if (Vctr < 0){
    Vctr = 0.0;
    }

  while(CO2 < 1){
    CO2 = mhz19.getPPM();
    delay(500);
    }

  //縦軸調整用：最初はありえない数字にして更新させる
  float Tmax = -100;
  float Tmin = 100;
  float Hmax = 0;
  float Hmin = 100;
  float Pmax = 0;
  float Pmin = 2000;
  float Vmax = 0;
  float Vmin = 1000;
  int Cmax = 0;
  int Cmin = 5000;

  //Liまでの区間の配列のmax+a/min-aをセットする
  //T
  for (int i=0; i<Li; i++){
    if (Data[4][i] != 0 && Tmax < Data[4][i]){
      Tmax = Data[4][i];
      }
    if (Data[4][i] != 0 && Tmin > Data[4][i]){
      Tmin = Data[4][i];
      }
    }

  //H
  for (int i=0; i<Li; i++){
    if (Data[6][i] != 0 && Hmax < Data[6][i]){
      Hmax = Data[6][i];
      }
    if (Data[6][i] != 0 && Hmin > Data[6][i]){
      Hmin = Data[6][i];
      }
    }

  //BATT
  for (int i=0; i<Li; i++){
    if (Data[8][i] != 0 && Vmax < Data[8][i]){
      Vmax = Data[8][i];
      }
    if (Data[8][i] != 0 && Vmin > Data[8][i]){
      Vmin = Data[8][i];
      }
    }

  //P
  for (int i=0; i<Li; i++){
    if (Data[0][i] != 0 && Pmax < Data[0][i]){
      Pmax = Data[0][i];
      }
    if (Data[0][i] != 0 && Pmin > Data[0][i]){
      Pmin = Data[0][i];
      }
    }

  //CO2
  for (int i=0; i<Li; i++){
    if (Data[10][i] != 0 && Cmax < Data[10][i]){
      Cmax = Data[10][i];
      }
    if (Data[10][i] != 0 && Cmin > Data[10][i]){
      Cmin = Data[10][i];
      }
    }

  //グラフ縦軸余白調整
  Tmax_d = Tmax + 1;
  Tmin_d = Tmin - 0.5;
  Hmax_d = Hmax + 10;
  Hmin_d = Hmin - 10;
  Pmax_d = Pmax + 1;
  Pmin_d = Pmin - 1.5;
  Vmax_d = Vmax + 0.5;
  Vmin_d = Vmin - 1.5;
  Cmax_d = Cmax + 150;
  Cmin_d = Cmin - 100;

  Tctr_d = (Tmax_d + Tmin_d)/2;
  Hctr_d = (Hmax_d + Hmin_d)/2;
  Pctr_d = (Pmax_d + Pmin_d)/2;
  Vctr_d = (Vmax_d + Vmin_d)/2;
  Cctr_d = (Cmax_d + Cmin_d)/2;

  //グラフ軸のクリア
  tft.fillRect(0, 60, 60, 240, ILI9341_BLACK);  //x1,y1,sx,sy

  tft.setTextSize(1);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(0, 60);
  tft.println((int)Tmax_d);
  tft.setCursor(0, 140);
  tft.println((int)Tctr_d);
  tft.setCursor(0, 220);
  tft.println((int)Tmin_d);

  tft.setTextColor(ILI9341_CYAN);
  tft.setCursor(15, 60);
  tft.println((int)Hmax_d);
  tft.setCursor(15, 140);
  tft.println((int)Hctr_d);
  tft.setCursor(15, 220);
  tft.println((int)Hmin_d);

  tft.setTextColor(ILI9341_GREEN);
  tft.setCursor(35, 60);
  tft.println((int)Pmax_d);
  tft.setCursor(35, 140);
  tft.println((int)Pctr_d);
  tft.setCursor(35, 220);
  tft.println((int)Pmin_d);

  tft.setTextColor(ILI9341_MAGENTA);
  tft.setCursor(2, 70);
  tft.println((int)Cmax_d);
  tft.setCursor(2, 150);
  tft.println((int)Cctr_d);
  tft.setCursor(2, 230);
  tft.println((int)Cmin_d);

  tft.setTextColor(ILI9341_LIGHTGREY);
  tft.setCursor(30, 70);
  tft.println((float)Vmax_d);
  tft.setCursor(30, 150);
  tft.println((float)Vctr_d);
  tft.setCursor(30, 230);
  tft.println((float)Vmin_d);  
}

void readTrim() {
  uint8_t data[32],i=0;
  
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0x88);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS,24);
  
  while(Wire.available()){
    data[i] = Wire.read();
    i++;
    }
  
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0xA1);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS,1);
  data[i] = Wire.read();
  i++;

  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0xE1);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS,7);

  while(Wire.available()){
    data[i] = Wire.read();
    i++;
    }

  dig_T1 = (data[1] << 8) | data[0];
  dig_T2 = (data[3] << 8) | data[2];
  dig_T3 = (data[5] << 8) | data[4];
  dig_P1 = (data[7] << 8) | data[6];
  dig_P2 = (data[9] << 8) | data[8];
  dig_P3 = (data[11]<< 8) | data[10];
  dig_P4 = (data[13]<< 8) | data[12];
  dig_P5 = (data[15]<< 8) | data[14];
  dig_P6 = (data[17]<< 8) | data[16];
  dig_P7 = (data[19]<< 8) | data[18];
  dig_P8 = (data[21]<< 8) | data[20];
  dig_P9 = (data[23]<< 8) | data[22];
  dig_H1 = data[24];
  dig_H2 = (data[26]<< 8) | data[25];
  dig_H3 = data[27];
  dig_H4 = (data[28]<< 4) | (0x0F & data[29]);
  dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F);
  dig_H6 = data[31];
}

void writeReg(uint8_t reg_address, uint8_t data) {
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(reg_address);
  Wire.write(data);
  Wire.endTransmission();
}

signed long int calibration_T(signed long int adc_T) {
  signed long int var1, var2, T;

  var1 = ((((adc_T >> 3) - ((signed long int)dig_T1<<1))) * ((signed long int)dig_T2)) >> 11;
  var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T>>4) - ((signed long int)dig_T1))) >> 12) * ((signed long int)dig_T3)) >> 14;

  t_fine = var1 + var2;
  T = (t_fine * 5 + 128) >> 8;
  return T; 
}

unsigned long int calibration_P(signed long int adc_P) {
  signed long int var1, var2;
  unsigned long int P;

  var1 = (((signed long int)t_fine)>>1) - (signed long int)64000;
  var2 = (((var1>>2) * (var1>>2)) >> 11) * ((signed long int)dig_P6);
  var2 = var2 + ((var1*((signed long int)dig_P5))<<1);
  var2 = (var2>>2)+(((signed long int)dig_P4)<<16);
  var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((signed long int)dig_P2) * var1)>>1))>>18;
  var1 = ((((32768+var1))*((signed long int)dig_P1))>>15);
  if (var1 == 0) {
    return 0;
    }
  P = (((unsigned long int)(((signed long int)1048576)-adc_P)-(var2>>12)))*3125;
  if(P<0x80000000) {
    P = (P << 1) / ((unsigned long int) var1);
    } else {
      P = (P / (unsigned long int)var1) * 2;
      }
  var1 = (((signed long int)dig_P9) * ((signed long int)(((P>>3) * (P>>3))>>13)))>>12;
  var2 = (((signed long int)(P>>2)) * ((signed long int)dig_P8))>>13;
  P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4));
  return P;
}

unsigned long int calibration_H(signed long int adc_H) {
  signed long int v_x1;

  v_x1 = (t_fine - ((signed long int)76800));
  v_x1 = (((((adc_H << 14) -(((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) + 
            ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) * 
            (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) + (( signed long int)2097152)) * 
            ((signed long int) dig_H2) + 8192) >> 14));
  v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4));
  v_x1 = (v_x1 < 0 ? 0 : v_x1);
  v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1);
  return (unsigned long int)(v_x1 >> 12);   
}
```
</div></details>