今回はESP32でヒーターのオンオフを行い保温器の温度を管理します。AC100Vのオンオフには、ソリッド・ステート・リレーを使っているんですが今回はちょっと状況が違う様です。
ソリッドステートリレー(SSR)使うぞ
秋月電子通商のHP、”ソリッド・ステート・リレー(SSR)キット 25A(20A)タイプ”に概要が書いて有ります。そこに流せる電流について、”放熱しない場合は2Aぐらい(200W)”と有ります。今回使用するヒーターは最大 100V 3A 約300W です。つまり放熱板を付けないと必要な電流を流せない事になります。今まではLED電球のオンオフが主だったので全く放熱の事は考えていませんでした。取り敢えず手持ちの放熱板を使用する事にしました。大きさ的に十分か分かりません。実際に電流を流して放熱板がどれくらい熱くなるか確認して見ます。また、念の為電源ラインにヒューズを入れました。ヒーターに流れる電流が最大3Aなので5Aのヒューズを使いました。
回路の変更
以下の機能を追加しました。
- ヒューズを付けたSSRを追加。
- 保温器の温度に合わせて、SSRをオンオフするポート追加。
- SSRがオンの時に点灯、オフの時に消灯するLEDを追加。(ヒーターのオンオフ確認用)
全体の回路と実物は以下の通り。
プログラムの変更
プログラムは、前回から メインのMonitor_00.inoとHTMLのnemu.htmlを変更しています。
Monitor_00.ino
ヒーターとLEDのオンオフは、ESP32のGPIO13,12を使って下記の様に行っています。(プログラムでは343から354行で実行)
- オン: 測定温度が設定温度のー1%より低い場合。
- オフ: 測定温度が設定温度の+1%より高い場合。
また保温器内の温度サンプリング時間ですが測定時間を使用しています。
Monitor_00.ino
#include "Arduino.h"
#include "SD.h"
#include <Wire.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include "AM2320.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
*/
// External Interrupt
// Only use GPIO:0,2,4,12-15,25-27,32-39
#define RTC_INT GPIO_NUM_4
// DS3231 Flg
#define every_s 0x0f //Alarm once per second
#define match_s 0x0e //Alarm when seconds match
#define match_ms 0x0c //Alarm when min, sec match
#define match_hms 0x08 //Alarm when hour, min, sec match
#define match_dhms 0x00 //Alarm when date, h, m, s match
#define match_whms 0x00 //Alarm when DoW, h, m, s match
#define alarm1 1
#define alarm2 2
int bootCount = 0;
int st_h = 0;
int st_m = 0;
int st_s = 0;
int target_tmp = 0;
// LED Driver Ccontroll
#define LED_ctl 12
// Power control
#define Power_ctl 13
WebServer server(80);
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");
}
server.on("/", handleRoot);
server.onNotFound(handleWebRequests);
server.begin();
Serial.println("HTTP server started");
Init_LCD();
LCD_CLS(0);
LCD_Print_Str(0,0,"LCD OK",1,2);
}
void loop()
{
server.handleClient();
if(int_flg) measure_data();
}
void handleRoot() {
String buf,cmd;
int a,b,fl,fl_sleep;
File dataFile;
float h_data[2];
uint8_t para[7];
fl=1; fl_sleep=0;
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(alarm1);
bootCount = 0;
int_flg = 0;
Serial.println("Stop Monitoring");
break;
case 4: // Start Logger
buf=server.arg("4");
get_h_m_s_tmp(buf);
Serial.println("Start Monitoring");
Serial.println("buf:" + buf);
dataFile = SD.open("/data0.txt", FILE_WRITE);
dataFile.close();
dataFile = SD.open("/data1.txt", FILE_WRITE);
dataFile.close();
bootCount = 0;
measure_data();
attachInterrupt(RTC_INT, Handle_int, FALLING);
break;
case 80: // Send parameter
buf=get_current_time() + ",";
AM2320(h_data);
buf += ("Temp:" + String(h_data[1]) + "'c / Humi:" + String(h_data[0]) +"%,");
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;
Serial.println("buf:" + buf);
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 = "N:" + String(bootCount) ;
buf.toCharArray(c_buf, 50);
LCD_Print_Str(0,32,c_buf,1,2);
}
void set_next(byte cntl)
{
int a,b,c;
get_DS3231(tm_data);
a = tm_data[6] + st_s;
b = c = 0;
if(a > 59)
{
a -= 60;
b ++;
}
b += (tm_data[5] + st_m);
c = tm_data[4] + st_h;
if(b > 59)
{
b -= 60;
c ++;
}
if(c > 23) c -= 24;
Clock.setA1Time(0, c, b, a, cntl, 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=0 / SAT=6
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 measure_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]) +",");
Serial.println("N:" + String(bootCount) + "/T:" + String(h_data[1]) + "/H:" + String(h_data[0]) );
disp_temp(h_data);
dataFile = SD.open("/data0.txt", FILE_APPEND);
dataFile.println(buf);
dataFile.close();
t_temp = target_tmp * 1.01;
if(h_data[0] > t_temp)
{
digitalWrite(Power_ctl,LOW);
digitalWrite(LED_ctl,LOW);
}
t_temp = target_tmp * 0.99;
if(h_data[0] < t_temp)
{
digitalWrite(Power_ctl,HIGH);
digitalWrite(LED_ctl,HIGH);
}
bootCount ++;
set_next(match_hms);
Clock.checkIfAlarm(alarm1);
Clock.turnOnAlarm(alarm1);
int_flg = 0;
}
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(".html")) dataType = "text/html";
delay(5);
dataFile = SD.open(path.c_str(), "r");
server.streamFile(dataFile, dataType);
dataFile.close();
delay(5);
}
menu.html
以前の画面に温度設定欄を追加しました。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel='stylesheet' type='text/css' href='./menu.css' >
<title>ESP32 Monitor</title>
</head>
<body>
<center>
<div style='font-size:35px'><b><i><u>ESP32 Monitor</u></i></b><br><br></div>
<div class="menu">
<div class="input-group">
<label>Time:</label>
<div id="0" ></div>
</div>
<div class="input-group">
<label>Temp & Humi:</label>
<div id="1" ></div>
</div>
<div class="input-group">
<label>Count:</label>
<div id="2" ></div>
</div>
<form method='get'>
<button type='submit' name='2' onclick='onBtn_back(2)'>Set Time</button><br>
<button type='submit' name='1' onclick='onBtn_back(1)'>Update</button>
<div>
Traget Temp: <input type='number' max='150' min='0' id='6' class='inp_box' />('C)
<br><br>
<input type='number' max='23' min='0' id='3' class='inp_box' />(H)
<input type='number' max='59' min='0' id='4' class='inp_box' />(M)
<input type='number' max='59' min='0' id='5' class='inp_box' />(S)
</div>
<button type='submit' name='4' onclick='onBtn_back(4)'>Start</button>
<button type='submit' name='3' onclick='onBtn_back(3)'>Stop</button><br>
<a style="color:#00ffff;cursor: pointer;" href='./data0.txt' download='data00.txt'>Download00</a>
<a style="color:#ffff00;cursor: pointer;" href='./data1.txt' download='data01.txt'> Download01</a><br><br>
</form>
</div>
</center>
<script>
var para=['0','0','0','0','0','0','0','0'];
var url = "http://esp32monitor.local/?80=";
var xhr = new XMLHttpRequest();
var a,b,str;
xhr.open('GET', url);
xhr.send();
xhr.onreadystatechange = function()
{
if(xhr.readyState === 4 && xhr.status === 200)
{
console.log( xhr.responseText );
b=0;
for(a = 0; a < 7; a ++)
{
para[a]='';
while( xhr.responseText[b] != ',')
{
para[a] += xhr.responseText[b];
b ++;
}
b ++;
}
for(a = 0; a < 3; a ++) document.getElementById(String(a)).innerHTML = para[a];
for(a = 3; a < 7; a ++) document.getElementById(String(a)).value=para[a];
}
}
function onBtn_back(btno){
var a;
var str = "";
for(a = 3; a < 7; a ++) str += (document.getElementById(String(a)).value + ",");
document.getElementsByName(btno)[0].value=str;
}
history.pushState(null,null,'/');
</script>
</body>
</html>
測定条件と結果
前回の状態では60℃以下は難しいと思われたので、今回は設定温度を50℃として測定条件を以下の様に設定しました。
- 目標温度:50℃
- 測定間隔:60,30,10,5秒の4種類
- 測定時間:各約60分間
測定結果は以下の様になりました。
なんと全て前回より悪い結果になりました。専用の温度測定素子を使って温度測定しヒータを制御しているのに何でサーモスタットに負けるのか。とはいえ、この結果から下記の事が分かります。
- 全てオーバーシュートが大きく、何回も起こる。
- サンプリング時間(測定時間)が短い方がオーバーシュートの量が小さい。
- でも、10と5秒は殆ど同じ。
サンプリング60SECを測定した所で余りにも結果が悪かったのでプログラムが正しく動作しているか確認しました。LCDに表示される温度とLEDの点灯を見ていたのですが、ヒータは確かに50℃近辺でオンオフしていました。プログラムは想定通りに動いていました。
保温器の温度とヒーターのオンオフをずっと見ていて温度を上手く制御出来ない理由が何となく分かって来ました。
最初にヒーターをオンした後、保温器内の温度は徐々に上がって行きます。温度が50℃過ぎた時点で確かにヒータはオフしますが、温度はその後更に上がり続けます。温度はピークまで行くと今度は下がり始め、室温が50℃より低くなった時点でヒーターが再びオンになります。
ヒータがオンになったので室温が直ぐに上がるかと言うとそうでは無いのです。ヒータがオンになっても室温は下がり続け、更に若干下がった後に室温が上がり始めました。
ここまでの現象を見て、ヒータのオンオフと室温の上下にタイムラグが有る事が分かりました。多分ヒーターのサーモスタットはヒーターの直ぐ近くに有り、センサはヒータから離れかつ缶の中に入っている。これがタイムラグの原因と思われます。
- 温度を上げる場合
- 缶の外から缶内を温めているので、室温が設定温度に成った時点で缶の外の温度はそれよりかなり高い。
- よってその時点でヒーターをオフしても室温度は上がり続ける。
- 温度が下がる場合
- 缶内が設定温度に成った時点で、缶外はその温度より低い。
- この時点でヒータをオンしても缶外を先ず温め、その後缶内の温度が上がるので缶内の温度は直ぐには上がらない
つまり今回の缶内に置いているセンサー(青色)を、缶の外でヒータの近く(赤色)に置けばサーモスタット同じ程度に温度を管理出来のではないか。またセンサーがヒータのそばに有れば(赤色の位置)オーバーシュートも防げる気がします。
次回は
センサーをヒータに上に置いてもう一度トライです。