ESP-WROOM-02 + BME280 + SSD1306 で 熱中症指数(WBGT)計を作ってみた

# はじめに

このところ急に暑くなってきました。
これからの時期は熱中症予防が大事になりますが、WBGT値に気を付けるようにとか言われますね。WBGTを正確に測定するには専用の器具が必要になりますが、簡易推計表があるようなので、WBGT計を作ってみました。

# 部品

- SWITCH SCIENCE ESPr Developer (ESP8266搭載ボード)
- AE-BME280 (I2C 温度・湿度・気圧系)
- SSD1306 (I2C ミニLCD)
- ブレッドボード
- 赤LED 5mm
- カーボン抵抗 330Ω

# 回路

![BME280_SSD1306.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570192/3056dc3e-4b83-7d76-276e-5337b8aaf4c8.png)

LED書き忘れてしまいました(^^;
[IO2 - 330Ω - LED - GND] という感じでつないでます。なくても動くのでご容赦ください。
なお、本来はIO4とIO5の先にプルアップ抵抗を入れる必要がありますが、今回使用したESPr Developerにはプルアップ抵抗が搭載されているので、このようにつないでいます。

# ブレッドボードでの配線

![IMG_0493.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570192/a3c18324-e87f-8d21-8cdc-599df3b95706.jpeg)

# 開発環境

- Windows 10 Pro 1909
- Arduino IDE 1.8.12

# スケッチ

ちょっと、だらけたソースになっちゃったかな。。。

```c++
/*
 * WBGT計
 */

#include "ESP8266WiFi.h"
/***********************************************
 * BME280関連の定義
 ***********************************************/
#include <Wire.h>
// I2Cアドレス
#define BME280_ADDRESS 0x76

// センサーから読みだした値
unsigned long int hum_raw, temp_raw, pres_raw;
signed long int t_fine;

// 補正後の確定値
double temp_act = 0.0, press_act = 0.0, hum_act = 0.0;

// ConfレジスタとCtrlレジスタの設定値をセットする
uint8_t osrs_t = 1;   // Temperature oversampling (= x1)
                      // 2⇒x2, 3⇒x4, 4⇒x8, 5～7⇒x16
uint8_t osrs_p = 1;   // Pressure oversampling
uint8_t osrs_h = 1;   // Humidity oversampling
uint8_t mode = 3;     // Normal=3, Sleep=0, Force=1,2
uint8_t t_sb = 5;     // 計測の間隔(Standbyにいる時間) 
                      // 0=>0.5ms, 1=62.5ms, 2=>125ms,3=>250ms,4=>500ms,5=>1000ms,6=>10ms,7=20ms
uint8_t filter = 0;   // IIR Filterの設定値(0=>off, 1=>2, 2=>4, 3=>8, 4～7=>16
uint8_t spi3w_en = 0; // SPIの3線モードをディセーブルにする(使わない)

// ビットマッピング
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;

// レジスタのアドレス
#define CTRL_HUM_REG  0xF2
#define CTRLREG   0xF4
#define CONFREG   0xF5

// 読み込まれるデータのマッピング
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;
uint8_t   dig_H1;
int16_t   dig_H2;
uint8_t   dig_H3;
int16_t   dig_H4;
int16_t   dig_H5;
int8_t    dig_H6;


/***********************************************
 * SSD1306(Display)関連の定義
 ***********************************************/
#include "SSD1306Wire.h"
// I2Cアドレス
const int I2C_DISPLAY_ADDRESS = 0x3c;
// I2C用のPIN
const int SDA_PIN = 4;
const int SDC_PIN = 5;
// SSD1306との通信用オブジェクト
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);

/****
 * 初期化処理
 */
void setup() {
  Serial.begin(115200);

  /**
   * Display(SSD1306)の初期化
   */
  initDisplay();

  /**
   * BME280初期化
   */
  initBME280();

}

/*****
 * メインループ
 */
void loop() {

  // BME280センシング
  doBME280();

  // WBGTを算出する
  int wbgt = calcWbgt(temp_act, hum_act);

  // 測定結果を表示する
  displaydata(temp_act, hum_act, press_act, wbgt);

  // 10秒おきに実行
  delay(10000);
}

/**
 * WBGTを算出する
 */
int calcWbgt(float temp, float hum){
  const int mat[20][17] = {
    { 29, 30, 31, 32, 33, 34, 35, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44},
    { 28, 29, 30, 31, 32, 33, 34, 35, 35, 36, 37, 38, 39, 40, 41, 42, 43},
    { 28, 28, 29, 30, 31, 32, 33, 34, 35, 35, 36, 37, 38, 39, 40, 41, 42},
    { 27, 28, 29, 29, 30, 31, 32, 33, 34, 35, 35, 36, 37, 38, 39, 40, 41},
    { 26, 27, 28, 29, 29, 30, 31, 32, 33, 34, 34, 35, 36, 37, 38, 39, 39},
    { 25, 26, 27, 28, 29, 29, 30, 31, 32, 33, 33, 34, 35, 36, 37, 38, 38},
    { 25, 25, 26, 27, 28, 29, 29, 30, 31, 32, 33, 33, 34, 35, 36, 37, 37},
    { 24, 25, 25, 26, 27, 28, 28, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36},
    { 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, 31, 31, 32, 33, 34, 34, 35},
    { 22, 23, 24, 24, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33, 33, 34},
    { 21, 22, 23, 24, 24, 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33},
    { 21, 21, 22, 23, 24, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 31, 32},
    { 20, 21, 21, 22, 23, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, 30, 31},
    { 19, 20, 21, 21, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30},
    { 18, 19, 20, 20, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27, 28, 28, 29},
    { 18, 18, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28},
    { 17, 18, 18, 19, 19, 20, 21, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27},
    { 16, 17, 17, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26},
    { 15, 16, 17, 17, 18, 18, 19, 19, 20, 21, 21, 22, 22, 23, 24, 24, 25},
    { 15, 15, 16, 16, 17, 17, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24}
  };
  int ti, hi;
  // 温度は40度-20度の降順の表になっている
  ti = 40 - (int)temp;
  ti = (ti < 0) ? 0 : ti;
  ti = (ti >= 20) ? 19 : ti;
  // 湿度は20%-100%の5%刻みになっている
  hi = (((int)hum) / 5) - 4;
  hi = (hi < 0) ? 0 : hi;
  hi = (hi >= 17) ? 16 : hi;

  return mat[ti][hi];
}

/**
 * 測定結果の表示
 */
void displaydata(
  float bme_temp,
  float bme_hum,
  float bme_pres,
  int wbgt
  ){

  // Displayに表示する文字列を生成する
  char bmestr[64];
  char bmestr2[64];
  char bmestr3[64];
  char bmestr4[64];
  sprintf(bmestr, "temp: %2.2f[°C]", bme_temp);
  sprintf(bmestr2, "humd: %2.2f[%%]", bme_hum);
  sprintf(bmestr3, "pres: %2.2f[hpa]", bme_pres);
  sprintf(bmestr4, "WGBT: %d[°C]", wbgt);

  // Displayに表示する
  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.drawString(10, 0, bmestr);
  display.drawString(10, 10, bmestr2);
  display.drawString(10, 20, bmestr3);
  display.setFont(ArialMT_Plain_16);
  display.drawString(10, 36, bmestr4);
  display.display();
}

/*****************************************************
 * BME280関連
 *****************************************************/
// 初期化
void initBME280(){
  // I2Cインタフェースを開始する
  Wire.begin();
  // LEDのPINをセット
  pinMode(LED_BUILTIN, OUTPUT);
  // Ctrlレジスタを書き込む
  writeI2C(CTRL_HUM_REG, ctrl_hum_reg);
  writeI2C(CTRLREG, ctrl_meas_reg);
  writeI2C(CONFREG, config_reg);

  // trimming parameterを読み込む
  readTrim();
}
// 計測
void doBME280(){
  // 補正後の値
  signed long int temp_cal;
  unsigned long int press_cal, hum_cal;
  char s[20], t[20];

  // データを読み込む
  readData();

  // 補正を適用する
  temp_cal = calibration_T(temp_raw);
  press_cal = calibration_P(pres_raw);
  hum_cal = calibration_H(hum_raw);
  temp_act = (double)temp_cal / 100.0;
  press_act = (double)press_cal / 100.0;
  hum_act = (double)hum_cal / 1024.0;

  // センサーをsleepさせる
  bme280_sleep();
  // 測定結果をデバッグ用にコンソール出力  Serial.print("BME280#TEMP : ");
  Serial.print(temp_act);
  Serial.print(" DegC  PRESS : ");
  Serial.print(press_act);
  Serial.print(" hPa  HUM : ");
  Serial.print(hum_act);
  Serial.println(" %");    
  // タイミングをとってセンサーをNormalに戻す
  delay(900);
  bme280_awake();
  delay(100);
}

// センサーをsleep状態にする
void bme280_sleep(){
  // LEDを消灯する
  digitalWrite(LED_BUILTIN,0);
  // sleepモードにする
  mode=0;
  ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
  // CONFIGレジスタに書き込む
  writeI2C(CONFREG,config_reg);
  // trimming parameterを読み込む
  readTrim(); 
}
void bme280_awake(){
  // LEDを点ける
  digitalWrite(LED_BUILTIN,1);  
  // normalモードにする
  mode=3;
  ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
  // CONFIGレジスタに書き込む
  writeI2C(CONFREG,config_reg);
  // trimming parameterを読み込む
  readTrim(); 
}

/*
 *  trimming parameterを読み込む
 *  ※ConfigRegisterをセットした後にこれを実行する
 */
void readTrim(){
  // 読み込み用のバッファ
  uint8_t data[32];
  uint8_t* pdata = &data[0];
  int i = 0;

  // データ転送(読み込み)
  pdata = readI2C(0x88, 24, pdata, 0);
  pdata = readI2C(0xA1, 1, pdata, 0);
  pdata = readI2C(0xE1, 7, pdata, 1000);

  // 取り出したデータをマッピングに従って取り出す
  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 readData(){
  uint8_t data[32];
  readI2C(0xF7, 8, &data[0], 0);

  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];

}

// I2Cライブラリによってデータを転送(書き込み)する
void writeI2C(uint8_t reg_address, uint8_t data){
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(reg_address);
  Wire.write(data);
  Wire.endTransmission();
}

// I2Cライブラリによってデータを転送(読み込み)する
uint8_t* readI2C(uint8_t reg, int size, uint8_t* pdata, int dly){
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(reg);
  Wire.endTransmission();
  // 必要な場合はwait
  if(dly > 0){
    delay(dly);
  }
  Wire.requestFrom(BME280_ADDRESS, size);
  while(Wire.available()){
    *pdata = Wire.read();
    ++pdata;
  }
  return pdata; 
}
/**
 * Calibration処理(この関数はBME280の仕様書に記載されている)
 */
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; 
}
/**
 * Calibration処理(この関数はBME280の仕様書に記載されている)
 */
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;
}
/**
 * Calibration処理(この関数はBME280の仕様書に記載されている)
 */
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);   
}
/*********
 * Display初期化
 */
void initDisplay(){
  // initialize dispaly
  display.init();
  display.clear();
  display.display();
  //display.flipScreenVertically();
  // フォントサイズを設定する
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_LEFT);  // TEXT_ALIGN_CENTER: Xpos=128/2=64
  display.setContrast(255);
  display.display();  
}
```

# 参考

[日本気象学会　日常生活における熱中症予防指針](http://seikishou.jp/pdf/news/shishin.pdf)

[環境省　熱中症予防情報サイト](https://www.wbgt.env.go.jp/doc_observation.php)