”PWM調光機能付き定電流LEDドライバ(NJW4616)”の様に、LEDを点灯させバッテリーの電圧の変化を、”ESP32″でモニターして見ます。
ハードの準備
まずは、LEDドライバ(NJW4616)のおさらい。
- 全体にかける電圧は12V
- LEDユニットを上図の様に配線
- LEDに流す電流は60mA。計算からRs=3.3Ω
- INPUTの電圧を、High->点灯 Low->消灯
モニター中バッテリーの電圧が11V以下になったらバッテリーの完全放電を防止する為にLEDを消灯する仕様にしたいと思いっています。EN/PWM端子にESP32のGPIOを繋ぎ、LEDのON OFFをしようとしたのですが、INPUTの電圧に問題が有りそうです。下記はNJW4616のSPECの抜き出しです。
表の項目”EN/PWM端子ON電圧2”の欄に入力電圧が5Vより大きい場合、OFF->ONになる最小の電圧が3.5V と有ります。ESP32のHIGHレベルは3.3Vです。最小値より小さい値です。ESP32のHIGHの電圧を上げる事を考えたのですが部品を追加するのも面倒なので実際に駆動出来ないかやってみたら出来ました。多分ギリギリセーフな値だったのでしょう。
全体の回路はこの様にしました。前回の回路に左下のNJW4616部分を追加したのみです。
測定は一定の間隔で行う予定です。そして測定間隔の間は消費電力を抑える為、ESP32をDeepSleepモードにする予定です。DeepSleep中もLEDをオンしたいので、EN/PWM端子がその間HIGHになっている必要が有ります。今回はこの端子にGPIO32につないでいます。GPIO32はDeepSleep時ハイインピーダンスになるのでESP32の電源(3.3V)でプルアップしています。これでDeepSleep時でもHIGHを保てます。もちろんGPIO32をLOWにすればLEDを消灯出来ます。
RTC DS3231からの割り込み信号(SQW)は、GPIO35と接続しています。バッテリー電圧はGPIO36でモニターします。
ソフトの準備
Web画面に下記の機能を追加しました。
- LEDのオン・オフ ボタン
- 測定時間設定機能
- 測定開始ボタン
- 測定結果保存ファイルのダウンロード機能
こんな感じです。
Web関係のソフトは、”menu.html”,”SetTime.html”,”menu.css”の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' onclick='onBtn_back(1)'>Update</button>
<button type='submit' name='2' onclick='onBtn_back(2)'>Set Time</button>
<button type='submit' name='3' id="3" onclick='onBtn_back(3)'>LED ON</button>
<div>
<input type='number' max='23' min='0' id='4' class='inp_box' />(H)
<input type='number' max='59' min='0' id='5' class='inp_box' />(M)
</div>
<button type='submit' name='4' onclick='onBtn_back(4)'>Start</button><br>
<a style="color:#00ffff;cursor: pointer;" href='./data.txt' download='data0.txt'>Download00</a>
<a style="color:#ffff00;cursor: pointer;" href='./data1.txt' download='data.txt'> Download01</a><br><br>
</form>
</div>
</center>
<script>
var para=['0','0','0','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 < 6; a ++)
{
para[a]='';
while( xhr.responseText[b] != ',')
{
para[a] += xhr.responseText[b];
b ++;
}
b ++;
}
for(a = 0; a < 4; a ++) document.getElementById(String(a)).innerHTML = para[a];
for(a = 4; a < 6; a ++) document.getElementById(String(a)).value=para[a];
}
}
function onBtn_back(btno){
var a;
var str = "";
for(a = 4; a < 6; a ++) str += (document.getElementById(String(a)).value + ",");
document.getElementsByName(btno)[0].value=str;
}
// history.pushState(null,null,'/');
</script>
</body>
</html>
Set_Time.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 ++;
}
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
@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;
}
本体スケッチ。測定のプロセスは以下の通り。
- Web画面で測定間隔を入力。
- 入力された値をRCTに設定しESP32はDeepSleepモードに入る。
- 設定時間になるとRTCがESP32に割り込みセットを掛ける。
- 割り込みのかかったESP32はバッテリー電圧の測定を行いSDカードに保存。
- 電圧が11.5V以下ならLEDを消灯して、11.5V以上ならRTCに時間を設定してDeepSleepモードに入る。
前回はスケッチと追加のファイル2つ有りましたが、今回はスケッチを1つにまとめました。
#include "Arduino.h"
#include "SD.h"
#include <Wire.h>
#include <WebServer.h>
#include "esp_deep_sleep.h"
#include <ESPmDNS.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
// AM2320 I2C Address
#define AM2320_ADR 0x5c // FIX
// LED Driver Ccontroll
#define LED_ctl 32
// External Interrupt
// Only use GPIO:0,2,4,12-15,25-27,32-39
#define RTC_INT GPIO_NUM_35
// 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
// 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
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR int st_h = 0;
RTC_DATA_ATTR int st_m = 0;
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()
{
File dataFile;
pinMode(LED_ctl, OUTPUT);
digitalWrite(LED_ctl, HIGH);
Wire.begin();
SPI.begin();
SD.begin(sd_ss);
if(esp_sleep_get_wakeup_cause() != 2)
{
Serial.begin(115200);
pinMode(RTC_INT, INPUT);
pinMode(Pilot_Lamp, OUTPUT);
digitalWrite(LED_ctl, LOW);
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");
}
digitalWrite(Pilot_Lamp, HIGH);
server.on("/", handleRoot);
server.onNotFound(handleWebRequests);
server.begin();
Serial.println("HTTP server started");
led_stat=0;
}
else
{
Serial.begin(115200);
delay(500);
Serial.println("times:" + String(bootCount));
measure_data();
if(get_volt(ad_00) > 11.5)
{
set_next(match_hms);
Clock.checkIfAlarm(alarm1);
Serial.println("Enter ReSleep");
}
else
{
digitalWrite(LED_ctl, LOW);
dataFile = SD.open("/data0.txt", FILE_APPEND);
dataFile.println("Measure Stop!!");
dataFile.close();
dataFile = SD.open("/data1.txt", FILE_APPEND);
dataFile.println("Measure Stop!!");
dataFile.close();
Clock.turnOffAlarm(alarm1);
}
esp_sleep_enable_ext0_wakeup(RTC_INT, LOW);
esp_deep_sleep_start();
delay(500);
}
}
void loop()
{
server.handleClient();
}
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
buf=server.arg("1");
get_h_m(buf);
break;
case 99: // Back or Cansel
break;
case 2: //Set TIME (send url data)
buf=server.arg("2");
get_h_m(buf);
dataFile = SD.open("/Set_Time.html", FILE_READ);
server.streamFile(dataFile,"text/html");
dataFile.close();
fl=0;
break;
case 3: // LED ON/OFF
buf=server.arg("3");
get_h_m(buf);
led_stat = ! led_stat;
digitalWrite(LED_ctl, led_stat);
break;
case 4: // Start Logger
buf=server.arg("4");
get_h_m(buf);
server.send(204, "text/plain", "");
Serial.println("Enter Start");
dataFile = SD.open("/data0.txt", FILE_WRITE);
dataFile.close();
dataFile = SD.open("/data1.txt", FILE_WRITE);
dataFile.close();
bootCount = 0;
measure_data();
set_next(match_hms);
Clock.checkIfAlarm(alarm1);
Clock.turnOnAlarm(alarm1);
esp_sleep_enable_ext0_wakeup(RTC_INT, LOW);
Serial.println("Enter Sleep");
digitalWrite(Pilot_Lamp, LOW);
esp_deep_sleep_start();
delay(500);
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,");
cmd = "LED ON,";
if(led_stat) cmd = "LED OFF,";
buf += cmd;
buf += (String(st_h) + "," + String(st_m) + ",");
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 set_next(byte cntl)
{
int a,b;
get_DS3231(tm_data);
a = tm_data[5] + st_m;
b = tm_data[4] + st_h;
if(a > 59)
{
a -= 60;
b ++;
}
if(b > 23) b -= 24;
Clock.setA1Time(0, b, a, tm_data[6], 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(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();
}
//#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 measure_data()
{
String buf;
File dataFile;
float h_data[2];
buf= String(bootCount) + ",";
buf += get_current_time() + ",";
AM2320(h_data);
buf += ("T/H," + String(h_data[1]) + "," + String(h_data[0]) +",");
buf += ("/0/1/2," + String(get_volt(ad_00)) + "," + String(get_volt(ad_01)) + "," + String(get_volt(ad_02)));
dataFile = SD.open("/data0.txt", FILE_APPEND);
dataFile.println(buf);
dataFile.close();
dataFile = SD.open("/data1.txt", FILE_APPEND);
dataFile.println(buf);
dataFile.close();
bootCount ++;
}
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);
}
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;
}
}
これでバッテリーの減り具合を測定したいと思います。