機器モニタ用システムの製作(1)

ソーラパネルで発電し、バッテリーに充電。充電した電気で夜間LEDを点灯する。天気が良ければずっとこれを繰り返す。そんな機器を作ろうと思っています。どこから始めようかと思ったのですが、パネルやバッテリー等の機器を評価できる測定器から始める事にしました。

製作したい機器の最終形態が分からないので測定器への要求も当然未定なのですが、とりあえず

  • CPUはESP32を使用する。
    • 特別な理由は無いのですが、今手持ちにこのCPUが有るから。
  • 測定したデータを保存出来るようにする。
    • ESP32は内部メモリにデータを保持出来ますが、測定値のように書き換える頻度が高い場合、内部メモリでは不利と思い今回はSDカードをサポートする事にしました。
  • 測定時間は専用のRTCを使用する。
    • ESP32はRTCを持っていますが、専用のRTCを使用してボタン電池でバックアップすれば、本体の電源を切っても時間は保持され便利です。
  • 電源は12Vのバッテリーからも取れるようにする。
    • ソーラパネル等の評価は外でも行う可能性が高い。
    • その場合ESP32の電源をバッテリーからとれるようにすると便利
  • 電圧測定用にADコンバーターを持つ
    • ESP32はAD変換機能が有るのでそれを利用する。
  • 温度湿度も測定出来るようにする。
    • 使用する予定のRTCがI2Cなので、I2Cで動作する温度湿度を測定出来る素子をついでにサポートする。

以上をもとに、以下の回路を書いてみました。

  • 電源は12Vのバッテリーから3.3Vを取る
  • 電圧測定用ADコンバーター
    • ESP32は、0〜3.6Vを12ビットでAD変換します。
    • 100Kと22Kの抵抗を直列につないだ間の電圧は、全体の22/(100+22)となります。ここの電圧を3.6Vとすると、全体は約20Vになります。
    • つまりこの回路でMAX20Vまで測定出来事になります。
    • AD変換器は3つ用意し、1つはバッテリー電圧測定用に固定しました。
  • 温度湿度の測定
    • AM2320を使うを使用しました。
    • これは温度と湿度を測定出来る素子です。
    • 今回は使用していませんが温度だけなら、ADT7410を使う等の使用を考慮して、”I2C”用のコネクタを4個用意しました。
  • LED
    • ESP32に電源が供給された時に点灯するもの。動作確認用に2個のLEDのを配線しています。
    • これらのLEDはバッテリー駆動時の消費電力を考えてソケットに差し込んで取り外し出来る様にしています。

実際の配線は下記の様になりました。

選んだ基板が小さかったので縦に積み上げて実装しています。一番右が完成品です。

次回は制御用のソフトの説明です。ESP32はArduino IDEを使用してソフトを書きます。まずはメインのスケッチ。HTTPサーバを上げて、Web画面からハードを操作します。

#include "Arduino.h"
#include "SD.h"                          
#include <Wire.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include "AM2320.h"
#include <DS3231.h>
// SD Card select PIN
#define sd_ss           5
/*
#define sd_sck            18
#define sd_mosi           23
#define sd_miso           19
*/
// AD Conversion R
#define ad_r1           100
#define ad_r2           22
// AD Conversion IO
#define ad_00           36
#define ad_01           39
#define ad_02           34
// Pilot Lamp 
#define Pilot_Lamp      13
// 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 led_stat;
WebServer server(80);
const char *SSID = "XXXXXXXX";
const char *PASSWORD = "YYYYYYYY";
DS3231 Clock;
byte tm_data[10];           //Year,Month,Date,DoW,Hour,Minute,Secon
void setup() 
{
    pinMode(Pilot_Lamp, OUTPUT);
    digitalWrite(Pilot_Lamp, LOW);
    Serial.begin(115200);
    delay(500);
    
    Wire.begin();
    SPI.begin();
    SD.begin(sd_ss);
    init_DS3231();
    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("esp32solar")) {
        Serial.println("MDNS responder started");
    }
    server.on("/", handleRoot);
    server.onNotFound(handleWebRequests); 
    server.begin();
    Serial.println("HTTP server started");
    
    digitalWrite(Pilot_Lamp, HIGH);
}
void loop() 
{
  server.handleClient();
}
void handleRoot() {
  String buf,cmd;
  int a,b,fl;
  File dataFile;
  float h_data[2];
  uint8_t para[7];
    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)
                dataFile = SD.open("/Set_Time.html", FILE_READ);
                server.streamFile(dataFile,"text/html");
                dataFile.close();
                fl=0;
                break;
      case 80:  // Send Solar parameter
                buf=get_current_time() + ",";
                AM2320(h_data);
                buf += ("Temp:" + String(h_data[1]) + "'c / Humi:" + String(h_data[0]) +"%,");
                buf += (String(get_volt(ad_00)) + "v / " + String(get_volt(ad_01)) + "v / " + String(get_volt(ad_02)) + "v,");
                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 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);
}
//#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();
}
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] - 1] + "/");
    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);
}
float get_volt(int no)
{
  float d_vol;
  
    d_vol = analogRead(no);
    
    d_vol *= 3.6; d_vol /= 4095;
    d_vol *= ((ad_r1 + ad_r2) / ad_r2); 
    d_vol *= 1.1;
    return(d_vol);
}
  • 7行:#include “AM2320.h” AM2320用のヘッダー
  • 8行:#include <DS3231.h> DS3231用のヘッダー。RTC DS3231を参考にライブラリーをインクルードして下さい。
  • 44行: ルーターのSSID
  • 45行: ルーターのパスワード
  • 83行: DNSを使ってサーバーのホスト名を、”esp32solar”としています。

下記の2つのファイルも本体と同じフォルダにセーブして下さい。

ファイル名:AM2320.cpp /AM2320本体スケッチ

#include "AM2320.h"
void AM2320(float * h_t_data) 
{
 uint8_t data[8];
 int flg;
  Wire.beginTransmission(AM2320_ADR);
  Wire.endTransmission();
  delay(10);
  Wire.beginTransmission(AM2320_ADR);
  Wire.write(0x03);         
  Wire.write(0x00);         
  Wire.write(0x04);            
  Wire.endTransmission();
  delay(10);
  Wire.requestFrom(AM2320_ADR,8); 
  if (Wire.available() >= 8) 
  {
    for (uint8_t i=0; i<8; i++) data[i] = Wire.read();
    h_t_data[0] = ((float)((data[2] << 8) | data[3]))/10;
     
    flg = 1;
    if(data[4] & 0x80) flg = -1;
    data[4] &= 0x7f;
    h_t_data[1] = ((float)((data[4] <<8 ) | data[5]))/10 * flg; 
  }
}

ファイル名:AM2320.h /AM2320用ヘッダーファイル。

#include <Wire.h>
// AM2320 I2C Address
#define AM2320_ADR    0x5c        // FIX
//------------------------------------------------
// Get Humidity & Temperature 
// float * h_t_data:  Pointer to array of floating point variables
//                    1st: Humidity  2nd:Temperature
//------------------------------------------------
void AM2320(float * h_t_data) ;

Web画面用HTMLは下記の3ファイルです。これらのファイルは、SDカードに保存して下さい。

ファイル名: 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 Solar</title>
    </head>
    <body>
	<center>
   	    <div style='font-size:35px'><b><i><u>ESP32 Solar</u></i></b><br><br></div>
    	<div class="menu">
		    <div class="input-group"> 
		         <label>Time:</label>
			<div id="0" >20</div>
		    </div>
		    <div class="input-group"> 
			    <label>Temp & Humi:</label>
			    <div id="1" >Temp: 20.5'C / Humi: 20.3%</div>
		    </div> 
		    <div class="input-group"> 
			    <label>Voltage 0/1/2:</label>
			    <div id="2" >12.5V / 12.5V / 12.5V</div>
		    </div>
   	        <form method='get'>
   	            <button type='submit' name='1' >Update</button>
   	            <button type='submit' name='2' >Set Time</button>
   	        </form>
    	    </div>
	</center>
	<script>
	    var para=['0','0','0'];
	    var url = "http://esp32solar.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 < 3; 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];
   	      }
	    }
	</script>
    </body>
</html>

ファイル名: Set_Time.html /DS3231の時間設定画面用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 Solar</title>
    </head>
    <body>
	<center>
            <div style='font-size:35px'><b><i><u>Set Time</u></i></b><br><br></div>
            <div class="menu">
		<div class="input-group"> 
		   <label>Year:</label>
		   <div >
			20<input type='number' max='99' min='0' id='100' class='inp_box' onchange='onChg_Set_Year()' />
		   </div>
		</div>
		<div class="input-group"> 
		   <label>Month/Day/Weekday:</label>
			  <div>
				<input type='number' max='12' min='1' id='101' class='inp_box' />(M)
				<input type='number' max='31' min='1' id='102' class='inp_box' />(D)
                           	<select id="103" style="width:65px">
 	                              <option value='1' selected >Sun.</option>
        	                       <option value='2'>Mon.</option>
        	                       <option value='3'>Tue.</option>
        	                       <option value='4'>Wen.</option>
        	                       <option value='5'>Thu.</option>
        	                       <option value='6'>Fry.</option>
        	                       <option value='7'>Sat.</option>
        	                   </select>
			  </div>
		</div> 
		<div class="input-group"> 
			<label>Hour/Minute/Second:</label>
			<div>
				<input type='number' max='23' min='0' id='104' class='inp_box' />(H)
				<input type='number' max='59' min='0' id='105' class='inp_box' />(M)
				<input type='number' max='59' min='0' id='106' class='inp_box' />(S)
			</div>
		</div>
               <form method='get' class="input-group">
                   <button type='submit' name='99' value='2' >Back</button>
                   <button type='submit' name='100' value='2'  onclick='onBtn_Set_Time()' >Set</button>
               </form>
         </div>
    </center>
	<script>
		function onChg_Set_Year() {
 		var a;
 			a=Number(document.getElementById("100").value);
			if(a < 10) document.getElementById("100").value="0" + String(a);
		}
		function onBtn_Set_Time() {
		var a;
		var str="";
			for(a=100; a<107; a ++) str += (document.getElementById(String(a)).value + ",");
			document.getElementsByName('100')[0].value=str;
		}
		var para=['0','0','0','0','0','0','0'];
	      var url = "http://esp32solar.loacl/?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 ++; 
		        	}
				if(Number(para[0]) < 10) 
					para[0] += ("0" + para[0]);
			
			        for(a=0; a<7; a++)
				{
					document.getElementById(String(a + 100)).value=para[a];
		        	  if(a == 3) 
					document.getElementById("103").selectedIndex=Number(para[a]);
				}
	   		}
				onChg_Set_Year();			
		}
	</script>
    </body>
</html>

ファイル名: menu.css /CSSファイル

@charset "UTF-8";
body {
        font-family: Arial,Helvetica,sans-serif;
        background: #181818;
        color: #EFEFEF;
        font-size: 16px
}
.menu {
        width: 420px;
        background: #363636;
        padding: 15px;
        border-radius: 10px;
        margin-top: -30px;
        margin-right: 10px;
}
.input-group {
        display: flex;
        flex-wrap: nowrap;
        line-height: 22px;
        margin: 5px 0px;
}
.input-group label {
        padding-right: 10px;
        min-width: 38%;
	text-align: left
}
button {
        display: block;
        margin: 10px;
        line-height: 35px;
        cursor: pointer;
        color: #fff;
        background: #228b22;
        border-radius: 10px;
        font-size: 16px;
	width:200px;
}
.inp_box {
		width:32px;
        font-size: 16px;
		text-align: right;
}

すべての準備が出来たら実行して下さい。パイロットLEDが点灯したらサーバーの準備完了です。PC ブラウザーに、”esp32solar.local”と入力すると、以下の画面が表示されます。

現在の時間と温度、湿度が表示されます。また電圧はバッテリーを使用しているので、0番のみ電圧を表示されています。

Updateボタンを押すと値が更新されます。またSet Timeボタンを押すと現在の時間の設定が出来ます。今回は、RTCをボタン電池でバックアップしているので本体の電源を落としてもRTCのデータは保存されます。

<<Before