Loggerの製作-02-測定回路

今回のLoggerの目的は、畑の温度を測定する。要求される項目は、

  • CPUにEPS8266を使用する。
  • センサーはADT7410を使用する。
  • ESP8266とのインターフェイスはESP8266をAPモードで立ち上げたWebサーバを使用する。
  • データの保管は、SIPFFSを使用しESP8266内に保管する。
  • 電源は最終的にはバッテリ(乾電池)を使用する。
  • 長時間測定と乾電池駆動から極力電気を使わない設計にする。
  • ESP8266おDeep Sleepモードを使用してパワーセーブを行う。

です。これらを考慮して回路は下記の様に設計しました。

  • ADT7410との接続
    • インタフェイスはI2Cですが、ArduinoとESP8266のSPECでは使用するピンが違います。
      • Arduino: SCL→IO5 SDA→IO4
      • ESP8266: SCL→IO14 SDA→IO2
    • 今回は、Arduinoに合わせています。Wire.begin()で使用出来ます。
    • ESP8266ケースを使用する場合、Wire.begin(SDA,SCL) の様に各ポートを指定して使用します。
  • IO16(17番ピン)Deep Sleepモード終了検出端子
    • 終了時このポートがLowになります。
    • この信号をRST端子に繋ぐ事により復帰はBootから始まります。
  • Tout(16番ピン)
    • 0から1Vまでの電圧を10ビットでAD変換。
    • 電池の残量測定。
  • IO14(3番ピン)AP/Log用スイッチ
    • Setupでこの端子を見て、Deep SleepモードかAP Webモードか判断します。
  • IO13(5番ピン) APモード用LED
    • Deep SleepモードAP Webモード判断用LED。
    • APモードの時に点灯
  • 左上(電源部)
    • 乾電池と外部電源(3.3V)を3点スイッチで切り替えて使用。
    • 開発中は外部電源。測定は乾電池を使用する。
    • Nju7223は3端子レギュレータ

実際の配線とケースへの実装は

  • 17 x 14 x 4cm の透明なプラスチックの容器です。
  • 今は、ADT7410は1個ですが、通信がI2Cなので右側端に端子を追加しています。
  • 蓋がついているので外で使用しても大丈夫と期待。

先ずはADT7410

ADT7410を使う”で説明していますが、キーポイントは、

  • ホストとの通信は、I2C
  • ハードの関係からアドレスは、0x48, 0x49, 0x4A, 0x4B の4つが指定可能
  • 13ビットモードならば電源投入のみで設定無しで温度データが測定可能
  • 測定モードは連続を使用
  • シャットダウン機能を使用し省電力を図る

です。先ずは、読み込み用スケッチ。

#include <Wire.h>

void setup() {
  float f_data;
  
    delay(1000);
    Serial.begin(115200);
    Serial.println();

    Wire.begin(); 
    Serial.println("I2C started");
    
    f_data = get_Temp(0x48);          
    Serial.println("tenp:" + String(f_data));
}

float get_Temp(int addr)
{
  short int data;
  float f_data;
  
    Wire.requestFrom(addr, 2);
    data = Wire.read() << 8;  
    data |= Wire.read();      
    data >>= 3;                          
    f_data = (float)data  / 16.0;          
    return f_data;
}

void loop() {
}
  • 11行:I2Cの開始。今回はArduino仕様を使用したので、”Wire.begin();” のみでOK。 ESP8266仕様なら、”Wire.begin(2,14);” となる。
  • 14行:読み込み用関数。引数はI2Cアドレス。今回は0x48。
  • 17行:読み込み用関数本体。
    • 22行:2バイト読み込みをI2Cに申請。
    • 23行:最初の1バイトは測定値上位バイト
    • 24行:次の1バイトは測定値下位バイト。
    • 25行:有効は部分は上位13ビット(下位3ビットは無効)なので3ビット右にシフト。
    • 26行:最後の変換(13ビット長の場合16で割る)して
    • 27行:本体に戻す

これだけでシリアルモニタに温度の値が表示されます。

次は測定モード変更用スケッチ。

void Adt_ope_mode(int data)
{
    data <<= 5;
    Wire.beginTransmission(0x48);
    Wire.write(0x03);
    Wire.write(data);
    Wire.endTransmission();
}
  • 4行:通信の開始をI2Cに依頼
  • 5行:レジスタ番号0x3のコンフィグレーションレジスタを指定
  • 6行:ビット6,5に値を書き込にモードを指定します。
    • 0:連続モード
    • 1:ワンショット
    • 2:インターバル
    • 3:シャットダウン
  • 7行:通信終了

今回使用するモードは、0と3です。

ちょっと分からなかったDeep Sleep

Deep Sleepに関する資料が少なく、良く分からなかったのですが、Webで調べると大体下記のよう

  • このモードに入るとRTC関係以外の回路の電源が切られ、ESP8266の消費電力が非常に小さくなる。
  • 関数は、deepSleep(uint32_t time_us, RFMode mode = RF_DEFAULT)
    • 1番目の引数:uint32_t time_usはスリープしている時間。単位はマイクロ秒。計算すると約71分30秒位がスリープ出来るMAX時間
    • 2番目の引数:引数としてRF_DEFAULT、RF_CAL、RF_NO_CAL、RF_DISABLEDが有るようだが効果が分からない。この引数を省力するとRF_DEFAULTになる模様
  • 設定値の時間になると、IO16(17番ピン)がLowに落ちる。この端子をESP8266のRSTに繋ぐと設定時間にリセットがかかり、EPS8266はBOOTから起動する。
  • RTC関係の回路としてRTC Memoryがある。これはDeep Sleepに入っても値を保持する。
  • 読み込み用の関数:bool rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size);
  • 書込用の関数:bool rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size);
  • 引数は全て、uint32_t型。

取り合えすDeep Sleepに入るスケッチ

void setup() {
  
    delay(100);
    Serial.begin(115200);
    Serial.println();
    
    Serial.println("Enter Deep Sleep");
    ESP.deepSleep(10 * 1000 * 1000);
    delay(100);
}

void loop() {
}
  • 8行:ここで、時間の設定を行っています。単位がマイクロ秒なので、10 * 1000 * 1000 で10秒
  • 9行:8行でDeepSleepにはいるのであればこの行はいらない。Webに直ぐに移行しない場合を考慮してdelay()を入れる方が安全と有りました。

IO16(17番ピン)をRSTに繋いで実行して下さい。10秒毎にBootして、”Enter Deep Sleep”とモニタに表示されます。

ただこれでは無限ループです。無限ループから抜ける為にBOOT後、IO14端子を調べて分岐します。

RTC Memoryですが、今回は、

  • 現在の測定回数。
  • Sleepの時間。

の2つを保存しています。

用に使用しています。

データは何処に保管?

長時間の測定を想定しています。データの保管は、SIPFFSを使用します。ESP8266のSPIFFSのインストールは、”Arduino IDE に ESP8266 SPIFFS ファイルシステムアップローダーをインストールする方法”を参考にしました。

SPIFFSを使用して、データ保管ファイル、”data.txt” と ”data1.txt”の2つを用意しました。これらのファイルを追加書込モードで開き、測定の度に測定の回数と温度データを保管しています。2つのファイルの中身は同じです。もし仮にどちらかのファイルの保管中に電池が切れても、もう一方は残るだろうと2つ作っています。ちなみにファイルのオープンモードは

  • “r”:読み込み
  • “w”:書き込み
  • “a”:追記
  • “r+”:読み込み+書き込み
  • “w+”:書き込み+読み込み
  • “a+”:追記+読み込み

が有ります。

電池の本数

最終的にバッテリー駆動でこの回路を動かしたいと思っています。単3電池(1.5V)で、3.3Vを出そうとすると多分2本使う事になりますが、それでは直ぐに寿命が来ます。電池を何本が直列に繋ぎ、3端子レギュレータで3.3Vを作った方が時間は伸びると思われます。では何本使うか。

  • 電池には終止電圧というものが有りその値は約1V。
  • ESP8266が約3Vまで起動すると仮定すると電池を寿命まで使うとして最低3本
  • 3本の新品の電池を直列に繋ぐと約4.5V。これは3端子レギュレータで3.3Vに減圧。
  • この時レギュレータには電圧降下が有り、それを約0.5Vとすると、3本では終始電圧の時レギュレータが3Vを出力出来ない
  • 4本の場合、終始電圧が4V。これは電圧降下を考慮しても3Vは十分に出力出来る。終始電圧以下でも若干使用可か
  • そこで乾電池の数を4本としばらつきを考えてMAX6.5V(普通は6V)とすると、TOUTは約0.644V。EPS8266が3Vまで動いたとすると

これより、乾電池4本使用する事にしました。

ちょっと便利なバッテリーチェッカー

乾電池4本。1本が約1.5V。ばらつきを考えて、MAX 6.5Vと仮定。レギュレータの電圧降下を約0.5Vとすると、今回の電源の動作範囲として6.5から3.5Vを想定します。使用中バッテリーが切れるのは困るので電池残量を測定したい所です。ESP8266のTout(16番ピン)は0から1Vの電圧を10ビット分解能でAD変換する事が出来ます。ここの利用してバッテリー残量を測定します。

この回路で、入力が6.5から3.5Vまで触れた場合、TOUTの電圧は約0.644から0.347Vになります。上記の回路で、

  • 1Vを10ビットの分解能で変換します。つまり1カウントは、1/1024(V)。
  • つまりバッテーリーの電圧 B(v)は、NをAD変換後の値として
    • B = N * ( 1/1024 ) x (100 / 11 )
    • B = N * 9.09/1024 。

となります。先ずはこの値でスケッチを実行し、値の分かっている電圧を掛けてこの値を調整します。今回は、調整後の値は、9.82でした。

最後はインターフェイス

ESP8266でAPモードのサーバを上げサーバーにスマホでアクセスすることを前提にしています。サーバーにアクセスすると、この様な画面が表示されます。

スマホの画面縦型にちょうど良い大きさです。

  • 温度の測定。
  • 測定間隔時間の設定。
  • 測定の開始。
  • 測定データのダウンロード

が出来ます。

今回のスケッチ

今回のスケッチは以下の通り

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <FS.h>

#define Adt_On    0
#define Adt_Off   3
#define Mic_Min   60000000

/* Set these to your desired credentials. */
const char *ssid = "ESP8266";
const char *password = "12345678";

ESP8266WebServer server(80);

String index_Dt =
                  "<!DOCTYPE html>\n<html>\n<head>\n"
                  "<title>ESP8266 Logger</title>\n"
                  "<style type='text/css'>\n"
                  "button.bt1 {background:#90ee90;font-size:50px;padding:10px;margin:20px;border-radius:30px;box-shadow:4px 4px #555;}\n"
                  "</style>\n</head>\n"
                  "<body>\n<center>\n"
                  "<div style='width:550px; background-color:#d8a373; border-style:solid; border-radius:30px; border-color:#6b3f31'>\n"
                  "<p style='font-size:70px'><b><i><u>Temp Logger</u></i></b></p>\n"
                  "<div style='font-size:50px'>\n"
                  "<span>---Temp---</span><br>\n"
                  "<span id='t_48'>48: XX.X</span><br>\n"
                  "<span id='t_49'>49: XX.X</span><br>\n"
                  "<span id='t_4a'>4a: XX.X</span><br>\n"
                  "<span id='t_4b'>4b: XX.X</span><br>\n"
                  "<span id='p_volt'>Battery: X.XXV</span><br>\n"
                  "<form method='get'>\n"
                  "<button type='submit' name='10' class='bt1'>Measure</button><br><br>\n"
                  "<input type='number' max='71' id='d_time' style='font-size:50px;width:100px;'/>\n"
                  "<button type='submit' name='1' id='t1' onclick='onBtnStart()' class='bt1'>Start</button><br>\n"
                  "</form><br>\n"
                  "<a style='font-size:50px' href='./data.txt' download='data.txt'>Download</a><br><br>\n"
                  "<a style='font-size:50px' href='./data1.txt' download='data1.txt'>Backup</a><br><br>\n"
                  "</div></div>\n</center>\n<script>\nfunction onBtnStart() {\n"
                  "document.getElementById('t1').value =document.getElementById('d_time').value;\n}\n";
                  
/* Just a little test message.  Go to http://192.168.4.1 in a web browser
   connected to this access point to see it.
*/
// Arduino I2C  SCL:A5  SDA:A4

void handleRoot() {
  String buf,cmd;
  double inter_time;
  float d_temp; 
  int a,b,fl;
  File dataFile;
  char temp[16];
  uint32_t offset,m_data;

    fl=0;
    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1:   // Start Log
                cmd=server.arg("1");
                m_data=cmd.toInt();
                offset=0;             // interval time
                ESP.rtcUserMemoryWrite(offset, &m_data, sizeof(m_data)) ;
                offset=4; m_data=0;   // measurement No.
                ESP.rtcUserMemoryWrite(offset, &m_data, sizeof(m_data)) ;

                fl=1;
                break;
    }
                
    buf=index_Dt;
    
    for(a=0x48; a<0x4C; a++)
    {
      dtostrf(get_Temp(a), 5, 1,temp);
      buf += ("document.getElementById('t_" + String(a,HEX) + "').innerHTML=\"");
      cmd=temp;
      buf += (String(a,HEX) + ": " + cmd +  " 'C\";\n");
    }
    
    dtostrf(Battery_chk(), 5, 2,temp);
    cmd=temp;
    buf += ("document.getElementById('p_volt').innerHTML=\"Battery: " + cmd +  " V\";\n");

    if(fl)
      if(digitalRead(14))
      {
        buf += "alert('Switch is AP position');";
        fl=0;
      }
    
    buf += "history.pushState(null,null,'/');\n</script>\n</body>\n</html>";

    server.send(200, "text/html", buf);
    
    if(fl)
    {
      Serial.println("Start");
      SPIFFS.remove("/data.txt");
      SPIFFS.remove("/data1.txt");
      get_save_Temp();
      Adt_ope_mode(Adt_Off);
      
      offset=0;             // interval time
      ESP.rtcUserMemoryRead(offset, &m_data, sizeof(m_data)) ;
      inter_time = m_data * Mic_Min;
      ESP.deepSleep(inter_time);
      delay(500);
    }
}

void setup() {
  String buf;
  File dataFile;
  uint32_t offset,m_data;
  double inter_time;
  
    delay(500);
    Serial.begin(115200);
    Serial.println();

    Wire.begin(); 
    Serial.println("I2C started");

    SPIFFS.begin();
    Serial.println("SPIFFS started");

    Adt_ope_mode(Adt_On);
    delay(500);
    
    pinMode(14,INPUT);
    if(digitalRead(14)){
      Serial.print("Configuring access point...");
      // You can remove the password parameter if you want the AP to be open. 
      WiFi.softAP(ssid, password);

      IPAddress myIP = WiFi.softAPIP();
      Serial.print("AP IP address: ");
      Serial.println(myIP);
      server.on("/", handleRoot);
      server.onNotFound(handleWebRequests); //Set setver all paths are not found so we can handle as per URI
      server.begin();
      Serial.println("HTTP server started");

      pinMode(13,OUTPUT);
      digitalWrite(13, HIGH);
    }
    else{
      get_save_Temp();
      Serial.println("Measured");
      
      Adt_ope_mode(Adt_Off);
      
      offset=0;             // interval time
      ESP.rtcUserMemoryRead(offset, &m_data, sizeof(m_data)) ;
      inter_time = m_data * Mic_Min;
      ESP.deepSleep(inter_time);
      delay(500);
    }
}

float Battery_chk()
{
  float d_temp; 
  int a;

    d_temp=0;
    for(a=0; a<5; a++)
    {
      d_temp += (system_adc_read() * 9.822 / 1024);
      delay(100);
    }
    
    return d_temp/5;
}

void Adt_ope_mode(int data)
{
  int a;

    data <<= 5;
    for(a=0x48; a<0x4c; a++)
    {
      Wire.beginTransmission(a);
      Wire.write(0x03);
      Wire.write(data);
      Wire.endTransmission();
    }
}

void loop() {
  
  server.handleClient();

}

void handleWebRequests()
{
  String dataType = "text/plain";
  String path;
  File dataFile;
   
    path = server.uri();
    if(path.endsWith(".txt")) dataType = "text/plain";
    else if(path.endsWith(".css")) dataType = "text/css";
    else if(path.endsWith(".js")) dataType = "application/javascript";
    else if(path.endsWith(".png")) dataType = "image/png";
    else if(path.endsWith(".gif")) dataType = "image/gif";
    else if(path.endsWith(".jpg")) dataType = "image/jpeg";
    delay(5);
    
    dataFile = SPIFFS.open(path.c_str(), "r");
    server.streamFile(dataFile, dataType);
    dataFile.close();
    delay(5);
}

float get_Temp(int addr)
{
  short int data;
  float f_data;
  
    Wire.requestFrom(addr, 2);
    data = Wire.read() << 8;  
    data |= Wire.read();      
    data >>= 3;                          
    f_data = (float)data  / 16.0;          
    return f_data;
}

void get_save_Temp()
{
  float f_data[4];
  int a,b;
  uint32_t offset,m_data;
   
    for(b=0; b<4; b++)
    {
      f_data[b]=0;
      for(a=0; a<5; a++)
      {
        f_data[b] += get_Temp(0x48 + b);
        delay(300);
      }
      f_data[b] /= 5;  
    }

    offset=4;             
    ESP.rtcUserMemoryRead(offset, &m_data, sizeof(m_data)) ;
    save_data("/data.txt",f_data,m_data);
    save_data("/data1.txt",f_data,m_data);
    m_data += 1;
    ESP.rtcUserMemoryWrite(offset, &m_data, sizeof(m_data)) ;
    
}

void save_data(String fn, float *data, uint32_t m_data)
{
  File dataFile;
  char temp[16]; 
  String str_time;
  int a;
  
    dataFile = SPIFFS.open(fn, "a");
    dataFile.print(String(m_data) + ",");
    for(a=0; a<4; a++)
    {
      dtostrf(data[a], 5, 1,temp);
      str_time=temp;
      if(a != 3) dataFile.print(str_time + ",");
      else dataFile.println(str_time);
    }
    dataFile.close();
}

簡単に使い方を説明すると

  1. 今回は、ADT7410gが4個付いているのが前提です。(各アドレスは0x48,0x49,0x4a,0x4b)
  2. 先ず、測定/APモード切替スイッチをAPモードに(IO14がHigh)にして電源をオン
  3. ESP8266がレディーになるとLEDが点灯します。
  4. スマホでWiFiを検索。検索対象は、”ESP8266″。
  5. 見つかったらパスワードを”12345678”と入力してESP8266と繋いで下さい。
  6. ESP8266をデフォルトAPモードで立ち上げると、アドレスが、”192.168.4.1”になる上です。
  7. ”192.168.4.1”にアクセスして下さい。下記のWeb画面が表示されます。
  1. 管理画面で、”Meaure”ボタンを押すと各センサーの値がTempの下に表示されます。
  2. ”Start”ボタンの横の欄に測定間隔時間を入力します。
  3. 入力は、”1分”単位で71分まで入力出来ます。
  4. モード切替スイッチを、測定モード(IO14がLow)にセットした後、”Start”ボタンを押して、測定を開始します。
  5. 先ず、現在の温度を測定し、ファイルの保存してDeep Sleepに入ります。
  6. Deep Sleepに入るとLEDが消灯します。
  7. 電源は切らないで下さい。入れたままです。

これで測定が開始します。このスケッチは、オペレータが測定を停止しなければ、無限ループ測定を行います。測定の停止は以下の様に行います。

  1. 先ず電源を切ります。
  2. モード切替スイッチを、APモードにセットします。
  3. 電源を入れます。
  4. スマホでWiFiに繋いで、”192.168.4.1”にアクセスすると、上記の管理画面が表示されます。
  5. 管理画面下にある、”Download”をクリックすると、スマホにデータがダウンロードされます。
  6. 念の為に、”Backup”もクリックしてバックアップのデータをダウンロードします。
  7. スマホのファイル管理ソフトで、ダウンロードしたファイルを開きます。こんな感じでデータがデーブされています。
0,14.5,14.7,14.8,14.9
1,14.4,14.5,14.8,14.4
2,14.4,14.4,14.5,14.6
  1. データの形式は:測定回数、0x48の値、0x49の値、0x4aの値、0x4bの値
  2. 測定回数は保存されますが、測定時間は保存していません。測定間隔と回数で後で自分で計算して求めます。

ここに、今回のスケッチを置きますー>”ESP8266_Logger“ これで実際に畑の温度を測定して見ます。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする