マイコン工作 3


3.ESP-WROOM-02で気象観測



私は兼業農家で米を作っています。今年の異常気象は天体観測だけでなく、農作物への影響も大きく、星を見られない鬱憤をこんな物で晴らしています。
農作物は、日照も大事ですが、積算温度という1日平均気温の累積気温も大事です。米の場合は、花が咲いて穂が出始め(出穂といいます)てから
積算温度で1000℃〜1100℃で刈取時期を迎えます(平均気温25℃なら約40日)。
このため、気温の測定も必要なので、気温ロガー的な物を作成しました。これにぴったりなのが、ESP-WROOM-02というWifiモジュールです。


液晶パネルの温度は、SHT31・BME280・STTS751の順に表示しています。


ESP-WROOM-02に温度センサを取り付け、Wifiでサーバに5分毎に測定データを送信しています。
最初はraspberry piに温度センサ(BME280)を付けて測定していましたが、温度計の値よりかなり大きな値になったので複数のセンサで試しましたが結果は同じ。

原因はraspberry piの上にセンサ基板をセットしたため、センサにraspberry piのCPUチップの熱が影響したようでした。

そこでESP-WROOM-02に変更したのが上記の写真のものです。ところが、センサによって測定値が異なる結果が出てしまいました。

BME280 測定精度 温度:−40〜+85℃、 ±1℃


SHT31 測定精度 温度:−40℃〜+125℃、±0.3℃(@0℃〜90℃)


DHT11 測定精度 温度:±2℃(@25℃)


MPL115A1 測定精度 温度:不明 大気圧精度:±1kPa(±10hPa)


STTS751 温度測定精度 ±2℃


1ケ月間表示した場合はこんな感じです


左からBME280 SHT31 DHT11 MPL115A1 STTS751 DS18B20です。DS18B20以外は秋月電子で購入。DS18B20は中華製の防水仕様のもの。
 DS18B20は、中華製ですがチップの測定精度が±0.5℃(−10℃〜+85℃)でステンレスパッケージで防水なので、 たんぼの気温や水温の測定に使用する予定です。
アマゾンで1本220円(秋月で販売しているチップ本体が250円)というのも魅力で、4本購入して測定しましたが、 多少のばらつきはありますが測定精度もまずまずでした。

積算温度の収集を目標にしているので、測定精度が1℃違えば40日で40℃ずれる事になるので精度を重視しています。

BME280とSHT31はほぼ同様の測定結果で、手元のアナログ温度計ともほぼ一致していました。
価格はBME280が1080円、SHT31が950円。カタログ性能の測定精度はSHT31のほうが優れていますが、BME280は大気圧の測定も出来るので、BME280のほうがお勧めかも知れません。
一番安いのがSTTS751の100円です。精度をあまり問題にしないのであれば、これもお勧めですが、小さいので工作が大変です。
SHT31の精度を信じると、他のセンサの測定誤差は25〜30℃以外で大きくなる傾向がありました。

ESP−WROOM−02でarduino開発環境でPGを作成し、Wifiでサーバ(linux(centos6)で構築)に飛ばしたデータをgnuplotというオープンソースを利用してweb上でグラフ表示出来るようにしています。

ESP−WROOM−02はWifiの中継機能も利用出来るので、何れ、ESP−WROOM−02をたんぼやビニールハウスに持ち込んで、
農業IOTの真似事でも出来ればいいな〜、と考えています。
ESP−WROOM−02を10個まとめて買うと1個当たり400円。たんぼにばら撒けば、かなり遠くまでWifiが利用出来ます。

プログラムはこんな感じ(一部表示のため加工しています)
#include "I2CLiquidCrystal.h"
extern "C" {
#include "spi.h"
#include "spi_register.h"
#include "user_interface.h"
}

#include "SPI.h"
#include "Wire.h"
#define STTS751_ADRS  0b0111001
#include "ESP8266WiFi.h"
#include "time.h"
#include "stdio.h"
#include "AE_SHT31.h"

#define TARGET_HOST "xxx.xxx.x.xx"
#define TARGET_PORT 80

//---------------------------------------------------//
// AE-SHT31    ------     Arduino ESP                //
//  1. V+      ------        3.3V or 5.0V            //
//  2.SDA      ------        IO4                     //
//  3.SCL      ------        IO5                     //
//  4.ADR      ------       (Open = 0x45)            //
//  5.GND      ------        GND                     //
//---------------------------------------------------//
// SHT31のアドレスを設定
AE_SHT31 SHT31 = AE_SHT31(0x44);

char ssid[] = "xxxxxxxx";			//  your network SSID (name)
char pass[] = "yyyyyyyy";		// your network password
unsigned int localPort = 2390;			// local port to listen for UDP packets

//---------------------------------------------------//
// AE-BME280   ------     Arduino ESP                //
//  1. V+        ------        3.3V                  //
//  2. GND        ------        GND                  //
//  4. SDA        ------        IO4                  //
//  5. ADR        ------        GND=0x76 VDD=0x77    //
//  6. SCL        ------        IO5                  //
//---------------------------------------------------//
// BME280のアドレスを設定
#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;

WiFiClient client;
I2CLiquidCrystal lcd(30, (bool)false);

void setup()
{
  Wire.begin(4, 5);
  Serial.begin(115200);
  lcd.begin(16, 2);
  WiFi.begin(ssid, pass);
  delay(1000);
  int err = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    lcd.print(".");
    err = err + 1;
  //  if (err > 20) {
  //    ESP.deepSleep(1 * 1000 * 1000 , WAKE_RF_DEFAULT);  // 1秒sleep
  //  }
  }
  Serial.println("");

  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Wire.beginTransmission(STTS751_ADRS);
  Wire.write(0x03);
  Wire.write(0b10001100);			// EVENT停止、12bit解像度指定
  Wire.endTransmission();
  spi_init(HSPI);

  // SHT31をソフトリセット
  SHT31.SoftReset();
  // 内蔵ヒーター 0:OFF 1:ON
  SHT31.Heater(0);

  // BME280 setup
  //  uint8_t osrs_t = 1;             //Temperature oversampling x 1
  uint8_t osrs_t = 4;             //Temperature oversampling x 1
  //  uint8_t osrs_p = 1;             //Pressure oversampling x 1
  uint8_t osrs_p = 4;             //Pressure oversampling x 1
  //  uint8_t osrs_h = 1;             //Humidity oversampling x 1
  uint8_t osrs_h = 4;             //Humidity oversampling x 1
  uint8_t mode = 3;               //Normal mode
  //  uint8_t t_sb = 5;               //Tstandby 1000ms
  uint8_t t_sb = 3;               //Tstandby 250ms
  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;

  writeReg(0xF2, ctrl_hum_reg);
  writeReg(0xF4, ctrl_meas_reg);
  writeReg(0xF5, config_reg);
  readTrim();
  getsens();
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("deepSleep start");
  ESP.deepSleep(50 * 1000 * 1000 , WAKE_RF_DEFAULT);  // 60秒sleep
  Serial.println("deepSleep end");
  //  ESP.deepSleep(300 * 1000 * 1000 , WAKE_RF_DEFAULT);  // 300秒sleep
}

void getsens()
{
  //STTS751 start
  byte valueHigh, valueLow;
  int16_t temp;
  // 整数部をセンサーから取得
  Wire.beginTransmission(STTS751_ADRS);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(STTS751_ADRS, 1);
  valueHigh = Wire.read();

  // 整数部をPCへ出力
  Serial.print("STTS751=");
  Serial.print(valueHigh);
  Serial.print(".");

  // 少数部をセンサーから取得
  Wire.beginTransmission(STTS751_ADRS);
  Wire.write(0x02);
  Wire.endTransmission();
  Wire.requestFrom(STTS751_ADRS, 1);
  valueLow = Wire.read();

  // 少数部をPCへ出力
  temp = (valueLow >> 4) * 625;
  Serial.print(temp / 1000);
  temp %= 1000;
  Serial.print(temp / 100);
  temp %= 100;
  //  Serial.print(temp / 10);
  temp %= 10;
  //    Serial.print(temp);
  Serial.print("℃");
  //STTS751 end

  //SHT31 start
  // SHT31から温湿度データを取得
  SHT31.GetTempHum();
  // SHT31.Temperature() より温度データ取得
  Serial.print(" SHT31temp=");
  Serial.print(SHT31.Temperature());
  // SHT31.Humidity() より相対湿度データ取得
  Serial.print("℃ Humidity=");
  Serial.print(SHT31.Humidity());
  Serial.print("% ");
  //SHT31 end

  //MCP3008 start
  uint32 val0 = check(0);
  float ADC_Valuef;
  ADC_Valuef = ((float) val0 * (3.3 / 1024.0)) / (3.05 / 13.3); // 10K+3.3Kの分圧回路
  Serial.print(ADC_Valuef);
  Serial.print("V");
  //MCP3008 end

  // BME280 start
  double temp_act = 0.0, press_act = 0.0, hum_act = 0.0;
  signed long int temp_cal;
  unsigned long int press_cal, hum_cal;

  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;
  Serial.print(" BME280=");
  Serial.print(temp_act);
  Serial.print("℃ PRESS=");
  Serial.print(press_act);
  Serial.print("hPa HUM=");
  Serial.print(hum_act);
  Serial.println("%");
  // BME280 end

  lcd.clear();
  lcd.setCursor(0, 0);
  //STTS751/SHT31/BME280 気温 lcd write
  lcd.print(SHT31.Temperature(), 1); //SHT31気温
  lcd.print(char(0xDF));
  lcd.print(temp_act, 1);            //BME280気温
  lcd.print(char(0xDF));
  lcd.print(valueHigh);              //STTS751気温
  lcd.print(".");
  temp = (valueLow >> 4) * 625;
  lcd.print(temp / 1000);
  lcd.print(char(0xDF));
  lcd.setCursor(0, 1);
  //SHT31/BME280 湿度 lcd write
  lcd.print(SHT31.Humidity(), 1);    //SHT31湿度
  lcd.print(" ");
  lcd.print(hum_act, 1);             //BME280湿度
  lcd.print("% ");
  lcd.print(press_act, 1);           //BME280気圧
  lcd.print("hPa ");

  if (!client.connect(TARGET_HOST, TARGET_PORT)) {
    Serial.println("STTS751 Could not connect.");
    return;
  }

  client.print("GET /xxx/xxxxxxx.php?temp=");
  client.print(valueHigh);            //気温
  client.print(".");
  temp = (valueLow >> 4) * 625;
  client.print(temp / 1000);
  temp %= 1000;
  client.print(temp / 100);
  client.print("&ps=");
  client.print("0.0");                  //気圧
  client.print("&volt=");
  client.print(ADC_Valuef, 2);          //電圧
  client.print("&hum=");
  client.print("0.0");                  //湿度
  client.print("&heat=");
  float heatIndex = 0.0;		//不快指数
  client.print(heatIndex, 2);
  client.print(" HTTP/1.0");
  client.print("\r\n\r\n");

  delay(100);

  //  Serial.println("STTS751");
  while (client.available()) {
    //    Serial.print(client.readStringUntil('\r'));
    String string751 = client.readStringUntil('\r');
    delay(100);
  }

  if (!client.connect(TARGET_HOST, TARGET_PORT)) {
    Serial.println("SHT31 Could not connect.");
    return;
  }
  client.print("GET /xxx/xxxx.php?temp=");
  client.print(SHT31.Temperature(), 2);
  client.print("&ps=");
  float ans = 0.0;		//気圧
  client.print(ans, 2);
  client.print("&volt=");
  client.print(ADC_Valuef, 2);
  client.print("&hum=");
  client.print(SHT31.Humidity(), 2);
  client.print("&heat=");
  client.print(heatIndex, 2);   //不快指数
  client.print(" HTTP/1.0");
  client.print("\r\n\r\n");

  delay(100);

  //  Serial.println("SHT31");
  while (client.available()) {
    //    Serial.print(client.readStringUntil('\r'));
    String string31 = client.readStringUntil('\r');
    delay(100);
  }

  if (!client.connect(TARGET_HOST, TARGET_PORT)) {
    Serial.println("BME280 Could not connect.");
    return;
  }
  client.print("GET /msp02/BME280rcv.php?temp=");
  client.print(temp_act, 2);            //気温
  client.print("&ps=");
  client.print(press_act, 2);           //気圧
  client.print("&volt=");
  client.print(ADC_Valuef, 2);          //電圧
  client.print("&hum=");
  client.print(hum_act, 2);             //湿度
  client.print("&heat=");
  client.print(heatIndex, 2);          //不快指数
  client.print(" HTTP/1.0");
  client.print("\r\n\r\n");

  delay(100);

  //  Serial.println("BME280");
  while (client.available()) {
    //    Serial.print(client.readStringUntil('\r'));
    String string280 = client.readStringUntil('\r');
    delay(100);
  }

  delay(100);
  WiFi.disconnect(true);
}

//MCP3008
uint32 check(int channel) {
  uint8 cmd = (0b11 << 3) | channel;

  const uint32 COMMAND_LENGTH = 5;
  const uint32 RESPONSE_LENGTH = 12;

  uint32 retval = spi_transaction(HSPI, 0, 0, 0, 0, COMMAND_LENGTH, cmd, RESPONSE_LENGTH, 0);

  retval = retval & 0x3FF; // mask to 10-bit value
  return retval;
}

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();
}

void readData()
{
  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];
}

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);
}



Web側はPHPで
  date_default_timezone_set('Asia/Tokyo');
  $today = date("Y/m/d H:i:s");

  // 日時,温度,湿度,気圧,電圧,不快指数
      $temp = $_GET['temp'];
      $ps = $_GET['ps'];
      $volt = $_GET['volt'];
      $hum  = $_GET['hum'];
      $heat = $_GET['heat'];

      $fp = fopen("xxx.txt", "a");
      fwrite($fp, $today);
      fwrite($fp, ",");
      fwrite($fp, $temp);
      fwrite($fp, ",");
      fwrite($fp, $hum);
      fwrite($fp, ",");
      fwrite($fp, $ps);
      fwrite($fp, ",");
      fwrite($fp, $volt);
      fwrite($fp, ",");
      fwrite($fp, $heat);
      fwrite($fp, "\n");
      fclose($fp);



画像掲示板へ

マイコン工作へ