ESP32 電源電圧を自己測定して過放電防止 (前編)

 届いてすぐに ESP32 を NiMH×2 で動かしてみました。(当時の記事)
 公称動作電圧 2.2V〜 とされる部分には少々異議ありながらも、概ね問題なく動作することを確認しました。
 ただし、動作下限電圧を下回ってフリーズしてもなお電池の消費は止まらず、電池に残る電気を吸い尽くそうとしていることが判明しましたので、過放電に弱い NiMH を保護してやらないと電池が何本あっても足りません。


 さくら IoT Platform 向けに作った シンプルなハイサイド動作な過放電防止回路 を改良して投入してもいいのですが、ESP32 には ADC がいっぱい載ってるし、わずか 4μA しか消費しない究極の Deep Sleep mode たる Hibernatioin mode も 2/23 から普通に使えるようになった ので、自身で電源電圧を測定してフリーズする前に Hibernatioin mode に移行して過放電を食い止める、という技が使えそうです。
(NiMHの自然な自己放電と同程度のオーダーぽいので、自然分と合わせて自己放電が2倍速になる程度、ってイメージ)


http://dl.ftrans.etr.jp/?975860e4519842e0afd027d644b9f14908f561b7.png


 緑の枠線の部分が今回の追加分です。
 IO34 を ADC として使います。


 最初は IO34 を 3V3 に直結して試してみたのですが、電源電圧に関わらず常にフル電圧(12ビット時で4095に近い数値)が出力されてしまいました。
 どれくらい差がないといけないのか(電源電圧に対して、計れる上限は何ボルト下なのか)仕様書に書かれているべきと思うんですが見つけれず。。


 要分圧ということで、10kΩ+30kΩで 3/4 に分圧して ADC させたところ、概ね近い数字になったので、そうしてあります。
 分圧せざるを得ないのは仕方ないとして、なんで 3V3 じゃなくて IO21 から分圧してるの?って思われた方、お目が高い!


 3V3 と GND の間で分圧させたほうが簡単なんですが、10kΩ+30kΩ・2.0V時で計算すると 2.0V÷40000Ω=0.00005A=0.05mA=50μA も食ってしまうんですよ! 停止後もずっと。
 せっかく 4μA まで落とせる Hibernation mode を使うことが出来るというのに、分圧抵抗がその10倍を消費し続けては本末転倒なんです。
(それを思うと、ESP32 の 4μA という値がいかに驚異的かおわかり頂けると思います。)


 低電圧を検知して Hibernation mode に移行する際に、IO21 を落とせば 50μA の消費を抑制することができます。
(説明用にソースでは LOW に落とすよう書いてますが、実際には Hibernation mode になるとオープンになる気がする)


※なぜ IO21 なのか?という疑問ですが、単に IO34 の真向かいにあったから、という理由のみです。

#include "esp_deep_sleep.h"
#include "driver/adc.h"

const int RA = 10000;   // 10k ohm
const int RB = 30000;   // 30k ohm

void setup() {
  // put your setup code here, to run once: 
  Serial.begin(115200, SERIAL_8N1);
  
  pinMode(21, OUTPUT);
  digitalWrite(21, HIGH);

  // ADC 関係の ardino-esp32 が出来てないぽい気がするので core を利用
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_11db);  // IO34

  // DeepSleep の準備
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
}

void loop() {
  // put your main code here, to run repeatedly:
  static int seq = 0;
  
  float value = adc1_get_voltage(ADC1_CHANNEL_6);
  float voltage = value / 4095 * 3.6 * (RA + RB) / RB;

  Serial.print("SEQ:"); Serial.print((String)seq++);
  Serial.print("  VALUE:"); Serial.print((String)value);
  Serial.print("  VOLTAGE:"); Serial.print((String)voltage);
  Serial.println("");

  if(voltage < 2.30)
  {
    Serial.println("good night!");
    digitalWrite(21, LOW);                // なくていい
    esp_deep_sleep_start();
  }
  
  delay(1000);
}

※このサンプルだと、一瞬でも 2.30V を切ると過放電防止モードに突入します


 IO34 を ADC で用いるときは ADC1_CHANNEL_6 と呼ぶそうです。
 いっぱい ADC が付いてますが、SENSOR_VP/SENSOR_VN のほうが精度が良いらしいものの、電測ごときに精度は要らんだろ、ってことで(精度が落ちるらしい) IO34 のほうを使ってます。


 弄っていて気がついた点としては、テスターの実測値と比較して ADC の結果が割と非線形な気が・・・
 電測くらいの目的では特に問題ないですが、ちゃんと ADC したい人は SENSOR_VP/SENSOR_VN のほうを試すとか、校正する手段(スプライン補正とか)を用意するとか、なにがしか対策が必要な気がします。


http://dl.ftrans.etr.jp/?d596b346c11e4c80ae1ac610bb4f8f2a21b41434.jpg http://dl.ftrans.etr.jp/?291088083d144e56ab6f4175b68d5c1a202e71f5.jpg
拙作ブレークアウトボード を使った実験の様子(装着した3端子を無効化するため基板を切断してます)


 今回の例では、最初に電圧測定用の IO21 を ON にして、ずっと放置させてますが

  1. 普段は OFF
  2. 電測直前に ON
  3. 僅かに delay させた後に電測
  4. OFF に戻す

ってやると、さらに省エネになりますね。
 各自でアレンジしてみてくださいませ〜



※前編と銘打ってますが、更に究極なものを用意してます。(たったいま実機でテスト中)
 本稿は 4μA もの超低電力モードながらも RTC だけは生きていて、指定時間後に再起動ということはできる状態です。

    esp_deep_sleep_enable_timer_wakeup(30 * 60 * 1000 * 1000);
    esp_deep_sleep_start();

とすると、Hibernation mode に移行するものの、30分後に再起動がかかります。


 後編では、「指定時間後に再起動」を切り捨てる前提で、再起動させるには外部からのアクション(タクトスイッチ等)が必要とはなりますものの、4μA よりも更に低電力なモードに移行させ、究極の過放電防止を実現する予定です。


(追記)2017/02/28
 コメントのやりとりをまとめて、別記事にしました。
 ESP32 電池(NiMH×2)で動かすときは 40MHz でコンパイルせよ をどうぞ


(追記)2017/03/02
 RTC をも停止させ、実測 0.2〜0.3μA で過放電の進行を防止する究極の回路例を記事にしました。
 後編 をどうぞ