長時間温湿度測定するぞ(5)

やっとゴールが見えて来ました。今回は長時間の温湿度測定です。

モニター時間は5秒で良いが、保存時間は?

前回はヒーターの温度をモニター時間とデータを保存する時間を同じにしていました。測定時間が短時間なら良いのですが今回は本番と同く20時間測定する予定なので、さずがに5秒間隔でデータを保存してはデータの数が多くなってしまいます。そこでデータ保存時間を5分としました。これで20時間の測定で保存データの数は240個となります。

前回は一つの割り込み信号でデータの保存とヒーター温度のモニタを行っていました。今回はモニターを5秒。保存を5分で行うので割り込み信号が2つになります。なんと都合良く、DS3231は2つの割り込み信号(データシートにはアラーム信号と書いてある)を持っています。

DS3231にはアラーム1,2と呼ばれるアラームが有りこれを割り込み信号に使い使います。また各々アラームの特性は下記の通り。

  • アラーム1:指定した 日、時間、分、秒 が一致した時に信号発生。
  • アラーム2:指定した 日、時間、分 が一致した時に信号発生。

よってモニターの間隔は5秒なのでアラーム1を、データの保存は5分なのでアラーム2を使用することになります。

アラームは2つでも端子は1つ

アラーム信号はモジュールのINT/SQW端子をLOWにします。前回はアラームが1つだったので INT/SQW端子がLOWに成ったら割り込み処理を行っていました。しかし今回は2つのアラームが同じ端子をLOWにするので、ESP32からはどちらのアラームが発生したか判断出来ません。これに対しDS3231はライブラリーに checkIfAlarm(alarm_No) という関数を用意しています。この関数は信号を発生してアラームを確認する関数です。引数alarm_No はアラームの番号(1または2)で引数を指定してこの関数を実行すると戻り値としてアラームの発生の有無を返します。戻り値は、アラームが発生していれは、”TRUE(1)”をしていなければ、”FALSE(0)”となります。この関数で信号を発生したアラームを判断し、その後ヒーターのモニターかデータの保存処理を行います。

ヒーターの温度と缶内の温度

前回缶内とヒーターの温度の関係は下記の様になる事が分かりました。

    缶内温度 = 0.523 x ヒーター設定温度 + 12.624

この式をヒーターの設定温度に付いて解くと、下記の様になります。

    ヒーターの設定温度 = 缶内の温度 x 1.92 ー 24.14

缶内設定温度をサーバーのHPから得たら上記の換算式でヒーター設定温度に計算し直し、それを使ったヒーターを管理します。

プログラムの変更

今回変更したプログラムは、Monitor_00.inoのみです。その他は前回と同じ。

Monitor_00.ino

#include "Arduino.h"
#include "SD.h"                          
#include <Wire.h>
#include <WebServer.h>
#include <ESPmDNS.h>

#include "AM2320.h"
#include "ADT7410.h"
#include "AQM1248A.h"
#include <DS3231.h>

// SD Card select PIN
#define sd_ss             5
/*
#define sd_sck            18
#define sd_mosi           23
#define sd_miso           19
*/

// LED Driver Ccontroll
#define LED_ctl           12

// External Interrupt
// Only use GPIO:0,2,4,12-15,25-27,32-39
#define RTC_INT           GPIO_NUM_4

// Wakeup caused 
#define by_ext_RTC_IO     2
#define by_ext_RTC_CNTL   3
#define by_timer          4
#define by_touchpad       5
#define by_ULP_program    6

// Power control 
#define Power_ctl         13

// DS3231 Flg 
#define every_s         0x0f    //Alarm1 once per second
#define match_s         0x0e    //Alarm1 when seconds match
#define match_ms        0x0c    //Alarm1 when min, sec match
#define match_hms       0x08    //Alarm1 when hour, min, sec match
#define match_dhms      0x00    //Alarm1 when date, h, m, s match
#define match_whms      0x00    //Alarm1 when DoW, h, m, s match
#define alarm1          1

#define every_m         0x70    //Alarm2 once per minute
#define match_m         0x60    //Alarm2 when min match
#define match_hm        0x40    //Alarm2 when hour, min match
#define alarm2          2

int bootCount = 0;
int st_h = 0;
int st_m = 0;
int st_s = 0;
int target_tmp = 0;
int heat_flg = 0;
float heat_tmp = 0;

#define ADT7410_addr     0x4b

WebServer server(80);
/*
const char ssid[] = "esp32monitor";  // SSID
const char pass[] = "12345678";   // password
*/

const char *SSID = "Enter your SSID";
const char *PASSWORD = "Enter your password";

DS3231 Clock;
byte tm_data[10];           //Year,Month,Date,DoW,Hour,Minute,Secon
bool Century,h12,PM;
int int_flg = 0;

void Handle_int()
{
    int_flg = 1;
}

void setup() 
{
  File dataFile;

    Wire.begin();
    SPI.begin();
    SD.begin(sd_ss);

    Serial.begin(115200);

    pinMode(Power_ctl, OUTPUT);     
    digitalWrite(Power_ctl,LOW);
    pinMode(LED_ctl, OUTPUT);     
    digitalWrite(LED_ctl,LOW);

    init_DS3231();

    pinMode(RTC_INT, INPUT_PULLUP);

    Serial.println("Connecting to WiFi");
    WiFi.disconnect(true);
    WiFi.softAPdisconnect(true);
    delay(500);

    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWORD);
    delay(1000);

    // Try forever
    while (WiFi.status() != WL_CONNECTED) {
      Serial.println("...Connecting to WiFi");
      delay(1000);
    }
    Serial.println("Connected");
    Serial.println(SSID);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    if (MDNS.begin("esp32monitor")) {
        Serial.println("MDNS responder started");
    }

/*
      WiFi.softAP(ssid, pass); 
      delay(1000);

      Serial.print("AP IP address: ");            //    192.168.4.1
      Serial.println(WiFi.softAPIP());
*/

    server.on("/", handleRoot);
    server.onNotFound(handleWebRequests); 
    server.begin();
    Serial.println("HTTP server started");

    Init_LCD();
    LCD_CLS(0);

    heat_flg = 0;
    cont_heat();
    Clock.turnOnAlarm(alarm1);
    Clock.checkIfAlarm(alarm1);
    attachInterrupt(RTC_INT, Handle_int, FALLING);
}

void loop() 
{
  server.handleClient();

  if(int_flg)
  {
    if(Clock.checkIfAlarm(alarm1)) cont_heat();
    if(Clock.checkIfAlarm(alarm2)) save_data();
    
    int_flg = 0;
  }

}

void handleRoot() {
  String buf,cmd;
  int a,b,fl;
  File dataFile;
  float h_data[3];

    fl=1;
    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1:   // Update Data
                break;
                
      case 99:  // Back or Cansel
                break;      
      
      case 2:   //Set TIME (send url data)
                buf=server.arg("2");
                get_h_m_s_tmp(buf);
                
                dataFile = SD.open("/Set_Time.html", FILE_READ);
                server.streamFile(dataFile,"text/html");
                dataFile.close();
                fl=0;
                break;

      case 3:   // Stop Logger
//                detachInterrupt(RTC_INT);
                Clock.turnOffAlarm(alarm2);
                
                digitalWrite(Power_ctl,LOW);
                digitalWrite(LED_ctl,LOW);

                bootCount = 0;
                int_flg = 0;
                heat_flg = 0;
                Serial.println("Stop Recording");

                break;
                
      case 4:   // Start Logger
                buf=server.arg("4");
                get_h_m_s_tmp(buf);
                Serial.println("Start Recording");
                
                dataFile = SD.open("/data0.txt", FILE_WRITE);
                dataFile.close();
                dataFile = SD.open("/data1.txt", FILE_WRITE);
                dataFile.close();
                
                bootCount = 0;
                save_data();
                Clock.turnOnAlarm(alarm2);
                Clock.checkIfAlarm(alarm2);

                heat_tmp = target_tmp * 1.92 - 24.14;
                heat_flg = 1;
                cont_heat();

                break;
                
      case 80:  // Send parameter
                buf=get_current_time() + ",";
                AM2320(h_data);
                buf += ("Temp:" + String(h_data[1]) + "'c / Humi:" + String(h_data[0]) +"%,");
                h_data[2] = ADT7410_get_temp(ADT7410_addr);
                buf += (String(h_data[2]) +"'c,");
                buf += (String(bootCount) + ",");
                disp_temp(h_data);
                buf += (String(st_h) + "," + String(st_m) + "," + String(st_s) + "," + String(target_tmp) + ",");
                server.send(200, "text/plain", buf);
                fl=0;

                break;
              
      case 81:  // Send RTC parameter
                get_DS3231(tm_data);
                buf = "";
                for(a = 0; a < 7; a ++) buf += (String(tm_data[a]) + ",");
                server.send(200, "text/plain", buf);
                fl=0;
                break;
              
      case 100: // Set TIME
                buf=server.arg("100");
                b=0;
                for(a=0; a<7; a++)
                {
                    cmd="";
                    while( buf[b] != ',') 
                    {
                        cmd += buf[b];
                        b ++;
                    }
                    tm_data[a]=cmd.toInt(); b ++; 
                }

                set_DS3231(tm_data);
                break;
    }
    
    if(fl)
    {
      dataFile = SD.open("/menu.html", FILE_READ);
      server.streamFile(dataFile,"text/html");
      dataFile.close();
    }
}

void disp_temp(float * th_data)
{
    char c_buf[50];
    String buf; 
    
    LCD_CLS(0);
    buf = "T:" + String(th_data[1]) + "'c";
    buf.toCharArray(c_buf, 50);
    LCD_Print_Str(0,0,c_buf,1,2);

    buf = "H:" + String(th_data[0]) + "%";
    buf.toCharArray(c_buf, 50);
    LCD_Print_Str(0,16,c_buf,1,2);
    
    buf = "t:" + String(th_data[2]) + "'c";
    buf.toCharArray(c_buf, 50);
    LCD_Print_Str(0,32,c_buf,1,2);

}

void set_next(byte al_no)
{
  int a,b;
  
    get_DS3231(tm_data);
    a = tm_data[6] + 5;
    if(a > 59) a -= 60;
    if(al_no == 1) Clock.setA1Time(0, 0, 0, a, match_s, 0, 0, 0);
    else
    {
        a = tm_data[5] + st_m;
        b = tm_data[4] + st_h;
        if(a > 59) 
        {
            a -= 60;
            b ++;
        }
        if(b > 23) b -= 24;
    
        Clock.setA2Time(0, b, a, match_hm, 0, 0, 0);
    }
}

void init_DS3231()
{
    Clock.turnOffAlarm(alarm1);               // Disables alarm 1 or 2 (default is 2 if Alarm != 1);
    Clock.turnOffAlarm(alarm2);               // Disables alarm 1 or 2 (default is 2 if Alarm != 1);
    Clock.setClockMode(0);
}

void get_h_m_s_tmp(String str)
{
  int a;
  String cmd;
              
    st_h = str.toInt();
    
    a = 0; 
    while(str[a] != ',') a ++;
    a ++;
    cmd = str.substring(a);
    st_m = cmd.toInt();
    
    while(str[a] != ',') a ++;
    a ++;
    cmd = str.substring(a);
    st_s = cmd.toInt();
    while(str[a] != ',') a ++;

    a ++;
    cmd = str.substring(a);
    target_tmp = cmd.toInt();
}

//#define CLOCK_ADDRESS 0x68
void set_DS3231(byte* tm_data)
{
    Clock.setYear(tm_data[0]);          //Set the year (Last two digits of the year)
    Clock.setMonth(tm_data[1]);                
    Clock.setDate(tm_data[2]);         
    Clock.setDoW(tm_data[3]);           //Set the day of the week  SUN=1 / SAT=7
    Clock.setHour(tm_data[4]);          
    Clock.setMinute(tm_data[5]);        
    Clock.setSecond(tm_data[6]);       
}

void get_DS3231(byte* tm_data)
{
  bool Century=false;
  bool h12;
  bool PM;
    
    tm_data[0] = Clock.getYear();
    tm_data[1] = Clock.getMonth(Century);
    tm_data[2] = Clock.getDate();
    tm_data[3] = Clock.getDoW();
    tm_data[4] = Clock.getHour(h12,PM);
    tm_data[5] = Clock.getMinute();
    tm_data[6] = Clock.getSecond();
}

void cont_heat()
{
  float h_data[3];
  int a;
  
    AM2320(h_data);
    h_data[2] = ADT7410_get_temp(ADT7410_addr);

    disp_temp(h_data);
    
    if(heat_flg)
    {
        a = LOW;
        if(h_data[2] < heat_tmp) a = HIGH; 
        digitalWrite(Power_ctl,a);
        digitalWrite(LED_ctl,a);
    }
    
    set_next(1);
}

void save_data()
{
  String buf;
  File dataFile;
  float h_data[2],t_temp;
  
    buf= String(bootCount) + ",";
    buf += get_current_time() + ",";
    AM2320(h_data);
    buf += ("T/H," + String(h_data[1]) + "," + String(h_data[0]) +",");

    dataFile = SD.open("/data0.txt", FILE_APPEND);
    dataFile.println(buf);
    dataFile.close();

    buf += ("t," + String(ADT7410_get_temp(ADT7410_addr)));
    Serial.println(buf);

    bootCount ++;

    set_next(2);
}

String digit_2(byte num)
{
  String str;

    if(num < 10) str = ("0" + String(num));
    else str = String(num);

    return (str);
}

String get_current_time()
{
  uint8_t a;
  String buf;
  String w_data[8]={"Sun","Mon","Tue","Wen","Thu","Fri","Sat"};

    get_DS3231(tm_data);
    buf=String(tm_data[0] + 2000);
    buf += ("/" + digit_2(tm_data[1]));
    buf += ("/" + digit_2(tm_data[2]));
    buf += ("/" + w_data[tm_data[3]] + "/");

    for(a = 4; a < 7; a ++)
    {
        buf += digit_2(tm_data[a]);
        if(a != 6) buf += ":";
    }

    return(buf);    
}

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(".html")) dataType = "text/html";
    else if(path.endsWith(".jpg")) dataType = "image/jpeg";
    delay(5);

    dataFile = SD.open(path.c_str(), "r");
    server.streamFile(dataFile, dataType);
    dataFile.close();
    delay(5);
}
  • 45から48行
    • Alarm2用マスクの追加
    • 288から309行で次の割り込み時間を設定していますが、ここで使用される。
  • 151,152行
    • ここで割り込み信号がモニターかデータ保存かの判断を行っています。
  • 214行
    • ここで温度換算を行っています。
  • 369から388行
    • ここでヒーターの温度をモニターしてオンオフしています。
  • 390から411行
    • ここで、缶内の温度を測定してデータを保存しています。

コードは変えていませんが値の扱いを変更しました

HPのコードは変更していませんが、HPで指定する値の扱いを変更しています。

  • Target Temp:
    • 前回はヒーターの温度でしたが、今回は缶内の温度とします。
  • 時間設定部:
    • ここでデータの保存間隔を指定します。
    • データの保存にはアラーム2を使用しているので秒の値は無視されます。
    • 指定出来のは、時間と分のみです。

湿度はどうするの

もう一つの問題は湿度管理です。今は何も管理していないので高温で湿度が下がってしまいます。納豆は高湿を好むので湿度を上げる必要が有ります。しかしこの保温器はヒーターをオンオフしているだけなので湿度の管理は出来ません。とりあえず、缶の中に水を入れた皿を置いて缶内の湿度がどの様になるか測定する事にします。

測定するぞ

缶内設定温度を50℃。測定データ保存時間間隔を5分。測定時間は20時間(納豆発酵に必要な時間)で測定を行いました。結果は下記の通り。

  • 缶内温度
    • 設定温度50℃に対し若干低めの値(47から49℃位の範囲)となりました。
    • 長時間になると温度がじわじわ上がるのでは無いかとう言う懸念とは逆に若干下がりました。
    • 温度が下がった時間帯は朝方で周りの温度が低い時間帯です。
    • 缶内温度は外気温の影響を受ける様です。
  • 缶内湿度
    • 起動時湿度は温度上昇に連れて下がりますが、途中から上がる事が分かりました。
    • 温度が定常状態になった頃には約100%を示しています。
    • 測定完了して保温器の蓋を開けると、蓋にしずくが付いていました。
    • 湿度の管理は出来ませんが、水を入れれば缶内は湿度100%になる様です。

試しに納豆作って見ました

Webに有った説明を頼りに実際に作ってみました。

  • 大豆を水に浸して
  • 茹でて、軽く水切り
  • 保温器のそこに水の入った皿を入れて
  • 金網のザルを
  • 皿の上にかぶせる
  • 金網の上に茹でた大豆とスーパーで買った納豆を少し混ぜたものを置いて
    約20時間、50℃に保つ
  • その後保温器から取り出すと、見た目はちょっと頼り無いが、納豆の匂いはしました
  • かき混ぜたら、ちゃんと糸を引きました。

実際にご飯にかけて食べて見ました。すごく美味いって訳では有りませんでしたが、確かに納豆でした。大豆を柔らかく茹ですぎた様で、かき混ぜると大豆が崩れてしまいました。茹で方、保温温度、保温時間等色々試して良い条件を見つける必要が有る様です。

次は

とにかくこの保温器で納豆が出来る事を確認出来ました。ただ、現在の保温器はWebからコントロールしているので便利なようで以外と使いづらいです。温度を設定してスタートボタンを押したら保温開始といった簡単な操作にした方が断然使い易いです。次回はその様に保温器を改造します。