「えだまめ」しているラズパイ

30年ぶりに半田ごて握ってラズパイ勉強中。

WROOM-02で静電容量式水分計を作る (5)正10分おきに起動して測定する

◯やりたいこと

(4)まででハードウエアの構成がほぼ決まったので動作仕様を決めてスケッチの作成をします。動作仕様は以下の通り。
1. 測定結果をWiFiで定期的(正10分)に送信する。
2. 時刻はNTP(Network Time Protocol)を利用する。
3. 電池動作とするためデータ送信時以外はスリープして省電力化を図る。
4. 1回の起動で1秒ごとに10回測定しその平均値を測定値とする。

◯やったこと

・ハードウエアの追加

WROOM-02(ESP8266)は省電力スタンバイモードとしていくつかのスリープモードを持ちますが、1番電力を消費しないのがディープスリープモード。今回はこのディープスリープモードを利用して必要時以外は極力電池を消費しないエコな水分計を作成します。
ディープスリープからWROOM-02を起動させるにはIO16に出力される起動用のリセットパルスでリセットをかける必要がありますので、あらかじめIO16とリセット端子を接続しておきます。

1KΩの抵抗をはさんであるのはIO16が出力設定でH出力時にリセットスイッチでショートするのを防ぐための電流制限抵抗です。

・スケッチの作成

動作仕様に基づきスケッチを作成しました。

/**
*
*    WiFi 容量式水分計 WR02_CSMS v0.0.9b
*
*      Module : ESP-WROOM-02(ESP8266)
*      Pin Function
*        IO0  : SCL(pull_up)
*        IO1  : TX
*        IO2  : SDA(pull_up)
*        IO3  : RX
*        IO4  : -
*        IO5  : -
*        IO12 : -
*        IO13 : -
*        IO14 : -
*        IO15 : -
*        IO16 : Reset出力(OnBord)
*        TOUT : -
*
**/
// Standerd Library
#include               <Arduino.h>
#include               <time.h>
#include               <Wire.h>

// Include Library
/* WiFi (ESP8266) */
#include               <ESP8266WiFi.h>
#include               <WiFiClient.h>

// Global Value
/* WiFi */
#define CONN_SSID     "SSID"
#define CONN_PASS     "PASSWORD"

/* My ID,IP,MAC */
#define MY_ID         63

IPAddress ip          (192,168,  1,MY_ID);  // センサーIPアドレス
IPAddress gateway     (192,168,  1, 1);     // ルーターIPアドレス
IPAddress dnServer    (192,168,  1, 1);
IPAddress netmask     (255,255,255, 0);

/* UART */
#define TX            1         // D1
#define RX            3         // D3

/* I2C */
#define I2C_SDA       2         // D2
#define I2C_SCK       0         // D0
#define I2C_CLK       50000     // fSCL=50KHz

/* ADC */
#define ADC_ADDR      0x68

/* NTP DATA */
#define JST           (3600 * 9)
#define DAYLIGHTOFFSET_JST (0)
#define NTP_SERVER1   "ntp.nict.jp"         // NTP1
#define NTP_SERVER2   "ntp.jst.mfeed.ad.jp" // NTP2

void setup() {
  // シリアル 開始
  Serial.begin(9600);

  // I2C 開始
  Wire.begin   (I2C_SDA, I2C_SCK);  // set I2C pins, default clock is 100kHz
  Wire.setClock(I2C_CLK);           // set I2C clock(fSCL)

  // WiFi スタティック接続 (IP,GateWay,SubnetMask,DNS)
  WiFi.config(ip, gateway, netmask, gateway);
  if (!wifi_connect()) wifi_end();

  // NPT 接続
  configTzTime("JST-9", NTP_SERVER1, NTP_SERVER2);
  if (!ntp_connect()) wifi_end();
}

void loop() {
  // 現在時刻取得
  time_t tmUnix    = time(NULL);           // unix
  struct tm* tmNow = localtime(&tmUnix);   // unix

  // 正10分(or11分)でなければsleep
  if ((tmNow->tm_min % 10)>2) wifi_end();

 // 変数設定
  float   adcd = 0;
  float   ratio= 0;
  uint8_t cunt = 0;
  byte    conf = 0;

  // 測定(10回平均値)
  for (uint8_t i=0; i<10; i++) {
    // ADC コマンド送信 (ワンショット測定,16Bit,ゲイン1倍)
    Wire.beginTransmission(ADC_ADDR);
    Wire.write(0b10001000);
    Wire.endTransmission();
    delay(10);

    // ADC data,config 受信
    unsigned long timeout = millis();
    while(millis() - timeout < 1000) {
      // データ(3Byte)要求
      Wire.requestFrom(ADC_ADDR, 3);

      // 3Byte未確定は3回まで待つ
      for (uint8_t j=0; j<3; j++) {
        if(Wire.available() >= 3) {
          // ADC i2cデータ -> 数値変換
          adcd +=(Wire.read()<< 8)+ Wire.read();
          conf  = Wire.read();
          cunt += 1;
          break;
        }
        else delay(100);
      }

      // 確定データか確認
      if (conf>=0x80) break;
      delay(250);
    }
    delay(1000);
  }
  // 平均値計算
  if (cunt!=0) adcd = (adcd/cunt)*2.048/32768;

  // プローブ乾燥時:2.048V 水中時:0.800V で比率計算
  ratio = 0;
  if (adcd >= 0.800)
    ratio = ((adcd-2.048)/(2.048-0.800))*100;
    if (ratio<0) ratio *= -1;
  
  // 結果表示
  data = String(MY_ID)+ ","
       + String(adcd,3)+ ","
       + String(ratio,0);
  Serial.print("-> "+ data+ "%");

/* 
* 〜 ここにWiFiでデータを転送するスケッチを作成  〜
*/
  
  // 計測終了 sleep
  wifi_end();
}

///// WiFi Control /////
/* WiFi接続開始処理 -> True:WiFi接続完了, False:WiFi接続失敗 */
boolean wifi_connect() {
  // WiFi 接続サイクルスタート(接続->確認->再接続) 3トライ
  for(int i = 0; (i < 2); i++) {
    // WiFi接続 接続が記憶されていない場合接続情報を記憶させ次回自動接続にする。
    if (WiFi.SSID() != CONN_SSID) {  
      WiFi.persistent(true);
      WiFi.mode(WIFI_STA);
      WiFi.setAutoConnect(true);
      WiFi.begin(CONN_SSID, CONN_PASS); 
    }

    // 5秒間 0.5秒毎に接続確認
    unsigned long timeout = millis();
    while(millis() - timeout < 10000) {
      // 接続できた場合 Trueリターン
      if(WiFi.status() == WL_CONNECTED) return true;
      // 未接続は 500ms待ちしてループ
      delay(500);
    }
  }
  // WiFi 接続不可 エラーリターン
  return false;
}

/* WiFi 終了・切断処理 */
void wifi_end() {
  // 現在時刻取得
  time_t tmUnix    = time(NULL);           // unix
  struct tm* tmNow = localtime(&tmUnix);   // unix

  // WiFi切断
  if (WiFi.status() == WL_CONNECTED) {
    WiFi.persistent(false);
    WiFi.disconnect(); // ステーションモード維持,ssid・passwordをクリアしない
    delay(10);
  }

  // 現在から正10分までの時間を計算
  int minNow = tmNow->tm_min % 10;
  int minSlp = 10 - minNow;
  int secSlp = 0;

  // 30秒過ぎは起動を30秒早める
  if (tmNow->tm_sec>30) {
    minSlp = minSlp-1; 
    secSlp = 30;
  }
  
  ESP.deepSleep((minSlp*63+ secSlp)*1000*1000);
  delay(500);
}

///// NTP Control /////
/* NTP 同期処理 */
boolean ntp_connect() {
  // NTP サーバー同期確認 (max 5sec)
  unsigned long timeout = millis();
  while (millis() - timeout < 10000) {
    // 現在時刻読込
    time_t timeNow   = time(NULL);
    struct tm* tmNow = localtime(&timeNow);

    // NPT同期完了は正常リターン
    if(tmNow->tm_year>=100) return true;

    // 同期未了は0.25秒おきに再確認
    delay(250);
  }
  // 接続できなかったので エラーリターン
  return false;
}


大まかな流れとしては以下の通りとなっています。

1. 起動・初期設定
2. (setup)
 UARTスタート
 I2Cスタート
 WiFi接続
 NTP確認
3. (loop)
 現在時刻読込
 正10分でなければスリープ(11分もok)
 10回測定し平均値を求める
 測定データを出力(今回は単純にUART出力しています)
 スリープ

起動して測定して終了するだけの簡単な一本道スケッチですが、ポイントはその終わり方(wifi_end()のdeepSleep処理)です。終了時の時刻から次の正10分までの時間を求めその時間分スリープさせる方式をとっています。
ESP8266の内部時計はかなりラフで、10分単位になると分単位で簡単にずれてしまいます。私の場合は1分あたり63秒で計算すると実時間に近づきましたがさすがにこれを計測時間のあてにはできません。そこでNTPとの連携で正10分の記録ができる様いろいろといじくってみました。
一回の起動で3回NTPにアクセスしますので1時間あたり18回、1時間平均20回以下のアクセスというNTPの要求事項はクリアしています。

◯やってみて

しばらく走らせてみましたが、どうやら無事動作してくれているようです。

d1が測定電圧、d2が測定回数で測定開始から順調に正10分・測定回数10回で動作しています。
測定電圧が微妙に上昇しているのは水温の変化によるもので

プローブが出力する最低電圧がどれくらいになるのかテストしてみたものです。
ちなみにドライアイス入りでも実験。

当たり前ですがドライアイス入りでもキチンとデータを測定してくれました。

これでスケッチも無事動作しましたので次回いよいよ本体の製作に入ります。