ブログ/こばさんの wakwak 山歩き

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

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

| 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 で過放電の進行を防止する究極の回路例を記事にしました。

 後編 をどうぞ

keyneekeynee 2017/02/27 20:26 こばさん、こんばんは。

rtc.hのコメントを見ますと、
ADC @0dB のフルスケール電圧は 1.1V です。
ADC @2.5dB では 1.5V , ADC @6dBでは 2.2V , ADC @11dBでは 3.9V です。
また、コメントの注意書きには @11dB のときの最大電圧はVDD_Aで制限されるとのことです。

データシートよりはオープンソースのコメントを読んだ方が色々詳しく書いてあったりします。
オープンソースのコメントを読んでいると、これは明らかにESP32内蔵のROM (ESP-WROOM-32のSPI Flashではなく)のFWで対策すべき内容があったりします。SPI FlashがDeep sleep直後に読込できない問題が特定のSPI Flash Chip にあるらしく、sdkconfig でCONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAYを設定できるようになっています。まさに私のところのチップはこれを設定してあげて libesp32.a を更新してあげる必要がありました。

keyneekeynee 2017/02/27 20:31 先の訂正
誤) rtc.h -> 正) adc.h
です。

こばさんこばさん 2017/02/27 21:56 いつもいつもありがとうございます。
あまりにお詳しいので中の人なのかと思っていたんですが(笑)
ソースのコメントにもヒントがいっぱいあるんですね。

> ADC @11dBでは 3.9V です。
> @11dB のときの最大電圧はVDD_Aで制限されるとのことです。

いちお
http://esp-idf.readthedocs.io/en/latest/api/peripherals/adc.html
は読みました。

>ADC_ATTEN_11db = 3
> The input voltage of ADC will be reduced to about 1/3.6
> 11dB attenuation (ADC_ATTEN_11db) gives full-scale voltage 3.9V
>
>Note
> The full-scale voltage is the voltage corresponding to a maximum reading
> (depending on ADC1 configured bit width, this value is: 4095 for 12-bits,
> 2047 for 11-bits, 1023 for 10-bits, 511 for 9 bits.)
>Note
> At 11dB attenuation the maximum voltage is limited by VDD_A,
> not the full scale voltage.

「フルスケールは 3.9V だが、11db時は VDD_A までに制限される」
この部分はヨシとして「reduced to about 1/3.9」じゃなくて「reduced to about 1/3.6」なんですよね。
3.9V-3.6V の差 0.3V は どこへ!?って


私の誤解力では行間が読めないのですが、ESP-WROOM-32 の場合は、内部で VDD_A が 3V3 に繋がってるので、読み替えると

・入力させてよいのは 3V3 まで。(壊れるから、3V3 は超えないでね)
・測定できるのは、3V3 - 0.3V まで。(3V3 が 3V だったら 2.7V まで測れる)

って風なのでしょうか。

keyneekeynee 2017/02/27 23:24 アッテネータで11dB減衰させますので10^(11/20)=3.548
つまり1/3.6なわけです。
リファレンス電圧が1.1Vなのでフルスケールは3.6*1.1=3.9Vですね。

データシートにははてななところが処々あります。
こばさんは気をつけてらっしゃると思いますが、電源電圧を超過した電圧をポートに入力しないようご注意くださいませ。入力トレラントではないと思われ。
特にUART RXとUSBシリアルTXの電圧は同じにするか、電圧変換するなどしてあげた方が安全です。

こばさんこばさん 2017/02/28 07:32 そういう計算でしたか。なるほど。。
(よく分かってなかったかもしれない)

手持ちUSBシリアルでは RXD/TXD は常に 3.3V らしいので、低電圧下では確かに仕様オーバーですね・・・
絶対定格の 3.6V を超えさえしなければ早々壊れまいと油断してました。。
(今のところ大丈夫な模様ですが)

何はともあれ、ありがとうございます。

keyneekeynee 2017/02/28 20:02 そうそう、analogRead(34)でも読めますね。

それと2.2V動作が厳しいとのことですが、小ネタを提供しますね。
Flash Frequency=80MHz では確かに厳しかったです。
自分のところでは、Flash Frequency=40MHzにすれば 2.05VまでSNTPサンプルが動きましたよ。
SPI FlashからのReadはこの電圧が下限っぽいです。
キャッシュ内で動作できるようなプログラムなら、2.05V以上で起動後1.55Vまで電圧落としてもHaltせずに動作し続けます。

こばさんこばさん 2017/03/01 00:18 こんばんは。
analogRead、最初に軽く試して NG だった気がしたもんで、adc1_get_voltage 使っちゃいましたが、いまはもう使えるんですか。
とはいえ、もし、adc1_config_width と adc1_config_channel_atten の Arduino 風な記述方法がないんなら、analogRead じゃなくて adc1_get_voltage 使った方が見通しはいいですね。

> Flash Frequency=40MHzにすれば 2.05VまでSNTPサンプルが動きましたよ。

なんとクロックダウンですか。
それは Arduino IDE のボード情報、80MHz/40MHz のトコを切り替えるだけでいいんですか?って質問しようとする前に試してみたら、確かに低電圧の粘りが大幅にアップ!

上のサンプルのとおりの変換で、2.3V以下(実測は2.4Vくらい)で落とさないと Halt 連発していたのに、そこの閾値を緩めた上で今 2.27V(実測で2.38Vくらい) という過去にない電圧で動いてます!!!(感動)

公称値2.2V〜ってのは 40MHz時 の条件と思えば間違えてもいないのか。
NiMH×2を前提にするときは、40MHz にしといたほうが良さそうですね!


ところで SNTP ってことは WiFi 経由ですよね
(多少は飛距離が縮むのかもしれないものの) 2.05V で電波が飛ぶなんて、ほんとに驚異的!!

むしろLチカのほうが大変な世界になろうとは・・・(笑)