//voltMonitor.ino V1.3 // #include #include #include #include //装置 const int monid = 11; //装置種類ID //WiFi const char *ssid = "ssid"; const char *password = "password"; const char *host = "server_name"; const char *path = "regVolt.php"; const int httpPort = 80; U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE); // Adafruit Feather ESP8266/32u4 Boards + FeatherWing OLED //ログ定義 struct config { unsigned long magic; //照合No unsigned short version; //バージョン unsigned short log_outcnt; //ログ出力間隔(分) float min_volt; //最小終止電圧 float max_volt; //最大終止電圧(デフォルト:ニッケル水素×3本) }; #define NUM_MAGIC 0x01020304 //初期化照合データ #define PIN_LOGSW 12 //ログON/OFFスイッチ #define PIN_LVUPSW 0 //終止電圧+ #define PIN_LVDOWNSW 2 //終止電圧ー #define PIN_ENERG 13 //充電スイッチ(リレーON/OFF) #define PIN_LED 15 //起動時はL #define VOL_MAX 16.0F //最大判定電圧 #define VOL_MIN 1.00F //最小判定電圧 #define VOL_STEP_H 1.00F //設定電圧間隔(高) #define VOL_STEP_M 0.10F //設定電圧間隔(中) #define VOL_STEP_S 0.01F //設定電圧間隔(小) static volatile bool tmintr; //タイマー割込み static volatile bool deintr; //ボタン割込みフラグ(多重割込み制御にも利用) static volatile bool logFlag; //ログON/OFFフラグ static volatile bool energFlag; //通電ON/OFFフラグ static volatile bool logButton; //ログ開始/終了ボタンON static volatile bool upButton; //電圧UPボタンON static volatile bool downButton; //電圧DOWNボタンON static volatile long timesec; //1/10秒カウント(ログ開始からのカウント) static volatile long minutesec; //分カウント(1/10秒) static volatile long minutescnt; //ロギング出力用分カウンタ static volatile long chargsec; //充電カウント(1/10秒) static volatile int repeatCount; //ボタンの押し続けカウント static struct config def = { //ログ定義 NUM_MAGIC, 0x0130, 3, 1.15F, 4.35F }; static bool upd_def = false; //定義変更フラグ static int limit_count; //終止電圧超過カウント(ログ自動終了カウント) static int mod_dspvolt = 1; //電圧表示モード(0:最小,1:最大) static float judg_volt[7]; //判定電圧格納(6回/分) static char ctm_str[32]; //ログ開始時間(登録する際のIDとなる) //インターバルタイマー定義 //ミリ秒をクロック数に換算 (@80MHz) #define MSEC2CLOCK(ms) ((ms)*80000L) #define INTERVAL 500 //タイマー間隔(ms) #define INTRWAIT 10 //割込み可能時間間隔(ms) //内部論理時間定義(1/10秒) #define LGINVAL (INTERVAL/100) //インターバル間隔 #define LGSEC(x) ((x)*10) //1秒値 #define LGMINUTES LGSEC(60) //1分値 // タイマ割り込みハンドラ void timer0_ISR (void) { timer0_write(ESP.getCycleCount() + MSEC2CLOCK(INTERVAL) ); //割り込み処理 tmintr = true; timesec += LGINVAL; if(logFlag) minutesec += LGINVAL; if(energFlag) chargsec += LGINVAL; } static volatile unsigned long btnintrtm; //ログON/OFFスイッチの割込み void ICACHE_RAM_ATTR logsw_intr() { if(millis() - btnintrtm <= 50) { //チャタリング防止 return; } btnintrtm = millis(); noInterrupts(); if(digitalRead(PIN_LOGSW) == LOW) { if(!deintr) { logButton = true; repeatCount = 0; } } else { deintr = true; } interrupts(); } //上限電圧アップスイッチ割込み void ICACHE_RAM_ATTR lvupsw_intr() { noInterrupts(); if(digitalRead(PIN_LVUPSW) == LOW) { if(!deintr) { upButton = true; //if(logButton) { //ログボタンが押されていたらログボタン押しをキャンセル logButton = false; //} repeatCount = 0; } } else { deintr = true; } interrupts(); } //上限電圧ダウンスイッチ割込み void ICACHE_RAM_ATTR lvdownsw_intr() { noInterrupts(); if(digitalRead(PIN_LVDOWNSW) == LOW) { if(!deintr) { downButton = true; //if(logButton) { //ログボタンが押されていたらログボタン押しをキャンセル logButton = false; //} repeatCount = 0; } } else { deintr = true; } interrupts(); } //異常終了 void halt() { for(;;) delay(INTRWAIT); } void setup() { //割込み制御 noInterrupts(); deintr = true; //タイマ割り込みの設定 timer0_isr_init(); timer0_attachInterrupt(timer0_ISR); timer0_write(ESP.getCycleCount() + MSEC2CLOCK(INTERVAL) ); //PIN初期化 pinMode(PIN_LOGSW, INPUT_PULLUP); pinMode(PIN_LED, OUTPUT); pinMode(PIN_ENERG, OUTPUT); digitalWrite(PIN_LED, LOW); digitalWrite(PIN_ENERG, LOW); //ボタン割込み設定 attachInterrupt(PIN_LOGSW, logsw_intr, CHANGE); attachInterrupt(PIN_LVUPSW, lvupsw_intr, CHANGE); attachInterrupt(PIN_LVDOWNSW, lvdownsw_intr, CHANGE); logButton = upButton = downButton = false; btnintrtm = millis(); interrupts(); //WiFi接続 WiFi.begin(ssid, password); unsigned long timeout = millis(); while(WiFi.status() != WL_CONNECTED) { if(millis() - timeout > 30000) halt(); delay(100); } //I2C初期化 Wire.begin(); u8g2.begin(); u8g2.clear(); //ログ定義の取出 EEPROM.begin(100); struct config buf; EEPROM.get(0, buf); if(buf.magic == NUM_MAGIC) { def = buf; } else { EEPROM.put(0, def); EEPROM.commit(); } logFlag = false; tmintr = false; energFlag = false; chargsec = 0; limit_count = 0; } void loop() { char sstr[16], vstr[16], mstr[16], tstr[16]; float vbat; //ADC: バッテリー電圧確認 vbat = (float)system_adc_read() * (1.0f + 15.0f) / 1024.0f; //終了判定用電圧サンプル(10秒毎) judg_volt[(minutesec%LGMINUTES)/LGSEC(10)] = vbat; //通電判定(終止電圧内でなければ通電しない) if(logFlag) { if(vbat < def.min_volt || vbat > def.max_volt) { digitalWrite(PIN_ENERG, LOW); energFlag = false; } else { digitalWrite(PIN_ENERG, HIGH); energFlag = true; } } //OLEDに状況表示 sprintf(sstr, "%dm %s", def.log_outcnt, logFlag? "ON": "OFF"); sprintf(vstr, "%2.2f", vbat); if(mod_dspvolt == 0) { sprintf(mstr, "%2.2f", def.min_volt); } else { sprintf(mstr, "%2.2f", def.max_volt); } sprintf(tstr, "%02d:%02d:%02d", timesec/LGSEC(3600), timesec%LGSEC(3600)/LGSEC(60), timesec%LGSEC(60)/LGSEC(1)); u8g2.firstPage(); do { u8g2.setFont(u8g2_font_luBS08_tr); //u8g2.setFont(u8g2_font_luBIS08_tr); u8g2.drawStr( 0, 8, sstr); u8g2.drawStr( 64, 8, tstr); u8g2.drawStr(104, 31, mod_dspvolt == 0? "min": "max"); u8g2.setFont(u8g2_font_helvB14_tr); //u8g2_font_ncenB14_tr //u8g2_font_logisoso16_tr u8g2.drawStr( 0, 31, vstr); u8g2.drawStr( 56, 31, mstr); if(vbat < def.min_volt && vbat > def.max_volt) { u8g2.drawStr(48, 31, "*"); } else { u8g2.drawStr(48, 31, " "); } } while(u8g2.nextPage()); //時間,ボタン(離すのみ)割込み待ち deintr = false; while(!tmintr && !deintr) delay(INTRWAIT); tmintr = false; //ログボタン押下チェック if(logButton) { if(repeatCount >= LGSEC(2)) { //3秒押し続けで表示切替 if(mod_dspvolt == 0) { mod_dspvolt = 1; } else { mod_dspvolt = 0; } logButton = false; //deintr = true; } else if(digitalRead(PIN_LOGSW) == HIGH) { //ボタンリリース logButton = false; if(!logFlag) { //ログ開始 logFlag = true; digitalWrite(PIN_LED, HIGH); digitalWrite(PIN_ENERG, HIGH); timesec = 0; ctm_str[0] = '\0'; minutescnt = minutesec = 0; chargsec = 0; limit_count = 0; } else { //ログ終了 logFlag = false; digitalWrite(PIN_LED, LOW); digitalWrite(PIN_ENERG, LOW); energFlag = false; } } repeatCount += LGSEC(1); } //最小最大電圧値更新チェック float volume; if(upButton) { if(digitalRead(PIN_LOGSW) == LOW) { //ログボタン押しながらはログ出力時間変更(+1分) def.log_outcnt++; upButton = false; } else if(!deintr || repeatCount == 0) { if(repeatCount < LGSEC(1)) { volume = 0; } else if(repeatCount < LGSEC(4)) { volume = VOL_STEP_S; } else if(repeatCount < LGSEC(8)) { volume = VOL_STEP_M; } else { volume = VOL_STEP_H; } if(mod_dspvolt == 0) { if((def.min_volt += volume) > def.max_volt) def.min_volt = def.max_volt; } else { if((def.max_volt += volume) > VOL_MAX) def.max_volt = VOL_MAX; } } if(deintr || digitalRead(PIN_LVUPSW) == HIGH) { //ボタンリリース if(repeatCount < LGSEC(1)) { //1秒以内だったら if(mod_dspvolt == 0) { if((def.min_volt += VOL_STEP_S) > def.max_volt) def.min_volt = def.max_volt; } else { if((def.max_volt += VOL_STEP_S) > VOL_MAX) def.max_volt = VOL_MAX; } } upButton = false; } repeatCount += LGSEC(1); upd_def = true; } if(downButton) { if(digitalRead(PIN_LOGSW) == LOW) { //ログボタン押しながらはログ出力時間変更(−1分) if(--def.log_outcnt < 1) def.log_outcnt = 1; upButton = false; } else if(!deintr || repeatCount == 0) { if(repeatCount < LGSEC(1)) { volume = 0; } else if(repeatCount < LGSEC(4)) { volume = VOL_STEP_S; } else if(repeatCount < LGSEC(8)) { volume = VOL_STEP_M; } else { volume = VOL_STEP_H; } if(mod_dspvolt == 0) { if((def.min_volt -= volume) < VOL_MIN) def.max_volt = VOL_MIN; } else { if((def.max_volt -= volume) < def.min_volt) def.max_volt = def.min_volt; } } if(deintr || digitalRead(PIN_LVDOWNSW) == HIGH) { //ボタンリリース if(repeatCount < LGSEC(1)) { //1秒以内だったら if(mod_dspvolt == 0) { if((def.min_volt -= VOL_STEP_S) < VOL_MIN) def.max_volt = VOL_MIN; } else { if((def.max_volt -= VOL_STEP_S) < def.min_volt) def.max_volt = def.min_volt; } } downButton = false; } repeatCount += LGSEC(1); upd_def = true; } //分毎に判定 if(minutesec >= LGMINUTES) { minutesec = 0; long v = 0; for(int n = 0; n < 6; n++) v += judg_volt[n]; if((v/6) < def.min_volt || (v/6) > def.max_volt) { limit_count++; } else { limit_count = 0; } //ログ定義変更ありなら格納 if(upd_def) { upd_def = false; EEPROM.put(0, def); EEPROM.commit(); } //ログ出力(通信) if(++minutescnt >= def.log_outcnt) { minutescnt = 0; //終了判定 if(limit_count >= 2) { logFlag = false; digitalWrite(PIN_LED, LOW); digitalWrite(PIN_ENERG, LOW); } //Requesting POST data WiFiClient client; String data = "ID=" + String(monid) + "&TM=" + (String)ctm_str + "&VOLT=" + String(vbat); //初回と最終はログ間隔と終止電圧を出力 if(ctm_str[0] == '\0' || !logFlag) { data += "&INVAL=" + String(def.log_outcnt) + "&MINVT=" + String(def.min_volt) + "&MAXVT=" + String(def.max_volt); } if(!client.connect(host, httpPort)) { return; } //Send request to the server client.println(String("POST ") + path + " HTTP/1.1"); client.println(String("Host: ") + host); client.println("Content-Type: application/x-www-form-urlencoded"); client.println("Content-Length: " + String(data.length())); client.println(); client.print(data); unsigned long timeout = millis(); while(!client.available()) { if(millis() - timeout > 5000) { client.stop(); return; } } //HTTPヘッダを含むデータの読み取り(1文字毎) String body = ""; while(client.available()) { body = client.readStringUntil('\r'); } if(client.connected()) client.stop(); if(ctm_str[0] == '\0') strcpy(ctm_str, body.c_str()); } } }