今回は、Arduinoを使い、自分自身に供給されている電源(Vcc)の電圧を計測してみます。
ACアダプタとか、USBケーブルなどから電源を供給して使っている間は、自分自身に供給されている電源電圧Vccを測定しても、あまり意味は無いのかもしれません。しかし、電池などのバッテリで駆動しているときにはどうでしょうか?例えば、気温や湿度、気圧、位置情報などを定期的に記録するような用途(データロガ)で、バッテリ駆動したArduinoのシステムを携行する場合を考えます。
始めの内はバッテリが元気なので、問題なくデータを取得、記録できるでしょう。時間が経って、バッテリ出力が低下してくると、どうでしょうか?Arduino本体もそうですし、周りに接続したセンサ類も、一定の電力(電圧)が供給されないと、正常に計測できなくなってしまうかもしれません。
SDカードなどの外部メモリにデータを書き込むにせよ、定格電圧が供給できなかったせいで、誤ったデータを書き込んでしまって、メモリの内容を破壊してしまうことにもなりかねません。
つまり、バッテリで駆動する場合、「多少電圧が低下しても、動いている間はそのまま使えば良い」という、気楽な場合もあれば、「周辺モジュールが正常動作しない可能性のある電圧に低下したら、まだArduinoが動作可能でも、処理を停止しなければならない」という、シビアな使い方も存在するということです。
Arduino UNOやPro Mini等に搭載されているATmega328というMCUは、定格で1.8〜5.5[V]の範囲で動作することが可能です。ただし、電源電圧(Vcc)によって動作周波数の制限があります(以下図)。
横軸がVcc[V]、縦軸が最大動作周波数fmax[MHz]です。
Arduino UNOでは、ATmega328は16MHzで動作しています。10MHz@2.7V → 20MHz@4.5Vまでがリニアなので、16MHz@3.78Vが定格となります。電源電圧は接続した負荷によって変動するため、10%程度のマージンを見ることが多いので、それに従うと、16MHzでの正常動作電圧(TYP)は、約4.2Vと言ったところでしょうか?
もし、Vcc<3.78Vに低下しても、暫くは「実力」で動作するでしょうが、仮に電源電圧低下(Vcc<3.78V)によって誤動作したとしても、「定格外使用」なので、「ご愁傷様、あなたの責任ですね」ってことです。
そういう事態になった時に、ちゃんと電源電圧の低下を検知して、誤動作しないようにMCUをスリープさせて停止したり、ユーザに向けて、「バッテリ電圧が低下してますよ、このまま使い続けると誤動作するかもしれませんよ」と、アラームを出したり、何らかの手立てを講じるのが良いでしょう。
電圧の測定と言えば、AD変換器ですが、ちょっと待ってくださいね。
ATmega328に搭載されたAD変換器は、普通に使うと、AVcc(=Vcc=5V)をフルスケールとした相対的な電圧値を測定するユニットなので、そもそも基準であるVccそのものを測定することはできません。
AD変換器がフルスケール(基準)とする電圧をリファレンス電圧(以下Vrefと呼びます)と言って、ATmega328の場合、analogReference()関数を使って、以下の3種類から選択が可能です。
Vref設定 | 説明 | |
analogReference (DEFAULT); | DEFAULT=1 |
Vref=AVcc(=Vcc=5V) 3.3V動作のArduinoでは、Vref=AVcc=3.3V |
analogReference (INTERNAL); | INTERNAL=3 |
Vref=Vbg(内蔵のバンドギャップ基準電圧) ATmega328では、Vbg=1.1V(TYP) ATmega32U4では、Vbg=2.56V(TYP) |
analogReference (EXTERNAL); | EXTERNAL=0 | Vref=AREF(外部端子AREFに印可した電圧) |
EXTERNALについて補足しておきます。ATmega328の外部端子AREFに、基準となる電圧値を入力することが可能です。EXTERNALは、AREFに外部から印可した電圧を基準としてAD変換を行うモードです。ただし、AREFに外部から電圧を印加した場合、ATmega328内部に持っている、他の基準電圧(DEFAULTおよびINTERNAL)を選択できなくなってしまうので、注意してください。
ATmega328が内部に持っている基準電圧を使う場合には、AREF端子にはコンデンサだけを接続して、電圧を印加しないことが必要です。Arduino UNOの回路図では、AREF端子には0.1μFのコンデンサが接続されており、ピンソケットに引き出されているので、ユーザが外部から電圧を印加することも可能になっています。
※その他、使用上の注意事項については、Arduino HP ⇒ REFERENCE ⇒ analogReference()の項目をご覧ください。
EXTERNAL以外は、プログラム中で切り替えて使用可能ですが、切り替える度にAREF端子へ接続した外部コンデンサに電荷をチャージ(/ディスチャージ)する必要があり、その間、AD変換結果が安定しません。「Arduino HP ⇒ REFERENCE ⇒ analogReference()」にも、「切り替えた後、数回は正確ではない」と、記されています。切り替えて使う場合は、要注意です。
今回注目すべきは、Vrefに「INTERNAL」を選択した場合です。ATmega328は、半導体ウェハを構成する材料である、シリコン(Si)のバンドギャップ電圧から作り出したバンドギャップ基準電圧Vbgを内蔵しており、この値Vbg=1.1V(TYP)をAD変換器のVrefとして使用することができるのです。
この電圧Vbgは、シリコンのバンドギャップ電圧から生成されており、その時の電源電圧Vccなどとは無関係に、常にVbg=1.1V(TYP)を指しています。今回の用途では、ここ、重要で、VbgはVccとは無関係に決まっているので、VbgをVrefに使うと、Arduinoに印可されているVccの電圧値を測定できる可能性が出てきました。
世の中には、温度や製造ばらつきなどの影響をなるべく受けない様に生成した基準電圧の作り方が存在します。その一つがバンドギャップ基準電圧です。シリコン(Si)のバンドギャップ電圧から作るらしいです。詳しく知りたい方は、以下から検索してみてください。
バンドギャップ基準電圧の検索(別ウインドウが開きます)
調べてみると、外付け回路で作り込もうとすると結構骨が折れるみたいです。しかし、幸いなことにATmega328は、この電圧発生回路を内蔵しています。しかも、AD変換器のVrefとして選択可能になっていました。
そこで、Vrefを内蔵バンドギャップ基準電圧Vbg=1.1V(TYP)に設定して、AD変換してみます。
analogReference (INTERNAL);
uint16 anlg=analogRead (0);
これで、アナログ端子A0の電圧をVref(=Vbg=1.1V(TYP))をフルスケールとして計測可能です。この場合、10ビットAD変換器の最大出力値である1,023は、Vbgを表しています。正確には、A0=anlg×Vbg/1,024[V]です。
つまり、VrefにVbgを選択した場合、1.1Vを超える電圧を測定することはできません。
また、データシートを見ると、Vbgの値は、1.0V(MIN)〜1.2V(MAX)という幅を持って保証されています。約±10%と、結構大きめなので、精密な測定には使えないかもしれませんね。ただ、AVcc(=Vcc)をVrefに使う場合だって、そもそも、Vccってそんなに安定(※)しているんだっけ?と言うことなので、たぶん、似たり寄ったりですよ(きっと)。
※「安定」:時間変動の話、だけではなく、製造ばらつきや温度変化などの話も含めて、です。
ATmega32U4では、Vbgの電圧値が異なっているので、注意しましょう。
MIN | TYP | MAX | ||
ATmega328 | Vbg | 1.0V | 1.1V | 1.2V |
ATmega32U4 | 2.4V | 2.56V | 2.8V |
どちらのMCUでも、VrefにVbgを選択する場合は、"analogReference (INTERNAL);"です。
データシートでAD変換器のスペックを確認すると、アナログ入力電圧Ain≦Vrefである必要があるようです。データシートの回路図(ブロック図)を見る限り、「Ain≦AVccで良いのではないのか?」と、思えるのですが……(スペックがそうなっているので仕方がありません)。
バンドギャップ基準電圧VbgをVrefとして使う場合、Vref=Vbg=1.0V(MIN)なので、Ain≦Vref=1.0V(MIN)を満たしておく必要があります。つまり、Vcc(=5V)は直結できません。そこで、Vccを抵抗で分圧して、1V以下に落としてからアナログ端子A0に入力します(下図)。
抵抗は、A0に入力する電圧がVbg=1.0V(MIN)より低くなるように決めればOKです。
上の回路図では、抵抗に流れる定常電流は、I=5V/(5kΩ+1kΩ)≒830μA。A0に入力される最大電圧は、A0(MAX)=5V×1kΩ/(5kΩ+1kΩ)≒0.83Vで、Vbgを下回り、かつ余裕あり。
analogRead()した値は、フルスケールがVbg=1.1V(TYP)であることと、R1:R2に分圧された電圧であることを考慮して、Vccに換算します。
Vccの抵抗分圧比をR1:R2
バンドギャップ電圧をVbg(=1.1V(TYP))とすると、
A0=analogRead()
電源電圧Vcc=A0×Vbg/1,024×(R1+R2)/R2
完成プログラムをリンクしておきます。
完成プログラム:meas_vcc.ino
プログラムの中身については、analogReference()を設定して、抵抗比の換算をしているだけですので、特に解説することはありません。いつもの様に、シリアルモニタから以下のコマンドを受け付けるようになっています。
実験してみましょう。本来なら、本当に、Arduinoへ供給する電源電圧を変更しながら測定したいところなのですが、電源電圧を可変するのはちょっと骨です。電源電圧を生成するために降圧電源モジュールが必要なのですが、持ち合わせがありません。
そこで、先日購入した昇圧電源モジュールを使って、電圧を測定してみます。
※別な目的で購入した昇圧電源モジュールです。その、本来の目的については、また別の機会に(「#6-3 1/fゆらぎ扇風機」をご参照ください)、お話しします。
購入した昇圧電源モジュールは、5V以上の電圧しか出力できないため、前節で示した回路図の抵抗分圧比を5:1 → 10:1程度に変更して、降圧した電圧をArduinoのアナログ端子へ入力します。以下、今回の実験用回路図です。
抵抗値は、10kΩ:1kΩとしました。昇圧電源モジュールから、5〜11[V]を出力すると、Arduinoのアナログ端子A0に約0.45〜1[V]が印加される計算です。昇圧電源モジュールの出力電圧値は、モジュールに搭載されたLED電圧計の指示値が正しいと仮定して(神様とする)実験します。
すなわち、今回は、電源電圧(Vcc)そのものを変更しながら測定するのではなく、アナログ端子に入力した電圧が、内蔵バンドギャップ基準電圧を用いて正しく測定できるかどうか?を確かめる実験となります。従って、Arduino UNOには、普通にUSBから電源を供給します。
実験サンプル:【 】内はシリアルモニタからの送信データ
【vref,1】 …… Vref=Vbgを選択
OK: Vref=1100[mV] INTERNAL (Vbg)
【rdiv,10000,1000】 …… 抵抗分圧比を設定
OK: rdiv 10000:1000 [ohm]
【meas,-1】 …… 測定開始
Vcc=5010[mV] (424=455[mV]), Vref=1100[mV]
Vcc=5021[mV] (425=456[mV]), Vref=1100[mV]
Vcc=5010[mV] (424=455[mV]), Vref=1100[mV]
Vcc=5010[mV] (424=455[mV]), Vref=1100[mV]
Vcc=5010[mV] (424=455[mV]), Vref=1100[mV]
Vcc=5021[mV] (425=456[mV]), Vref=1100[mV]
:
シリアルモニタに送られてくる測定値は、抵抗分圧比を考慮した測定電圧値(Vcc)、測定時のAD変換器出力値(10ビットデジタルとその電圧換算値、丸カッコ内)、測定に使用したVref、となっています。
測定値をグラフにしてみます。
横軸には昇圧電源モジュールのLED表示値その物(入力電圧)、縦軸には抵抗分圧を考慮した入力電圧の測定値を取っています。手持ちのArduino、UNO/Pro Mini/Microで測定した結果をプロットしてあります。
3本の赤い線の内、真ん中の破線は理想的な場合の測定値、上下の点線は、VbgがMINだった時とMAXだった時に測定される値です。以下、雑感です。
電源電圧を測るのに必要な電流値について考えてみましょう。抵抗分圧した分、Vcc→GNDへ定常電流が流れてしまい、バッテリ駆動アプリケーションにとっては致命的とも思えます。
しかし、5kΩ+1kΩなら、定常電流は5V/6kΩ≒830μA程度。例えば、1,000mAh程度のバッテリの場合、放電しきるのに約1,200時間(約50日)かかります。もう1桁抵抗値を上げて50kΩ+10kΩとすれば、定常電流値は1桁低下して83μA程度なので、放電に要する時間は10倍となります。
※ちなみに、単三型eneloopの電流容量は、公称1,900mAh(MIN)です。単4型だと750mAh(MIN)。
それでも電流消費が気になる場合は、抵抗分圧回路をVcc直結ではなく、ArduinoのデジタルI/Oポートに変更する方法が考えられます(下図)。
電源電圧を測定する時だけ、I/OポートをHIGHに駆動して、分圧後の電圧をAD変換器で読み取る方法です。計測が終了したら、I/OポートをLOW出力(またはHIGH-Z、入力状態と)すれば、定常電流は流れなくなります。
ただし、以下の様な欠点も容易に想像できます。
バッテリ駆動のアプリケーションを考える時、高々数100μAの定常電流より、もっと電流を抑えるべき回路は山ほどあるはずなので、そっちに集中した方が良いような気がします。
USBから電源をもらうにせよ、ACアダプタであるにせよ、給電する側の出力電圧精度次第では、電源電圧Vccが本来あるべき電圧を指示していないかもしれません。その場合、VccをフルスケールとしてAD変換した値は、電源電圧の誤差をそのまま、引き継いでしまうことになります。
このような場合に、今回のテクニックを使って、自分自身の電源電圧Vccを測定し、AD変換値を校正することが可能です。以下、その手順。
Vccの測定(mvlt_A0)は、最初に1回だけ、setup()関数で実行すれば良い。
自分自身の電源電圧を測定する道筋は立ちました。バッテリ駆動で使うようなアプリケーションがあれば、応用可能……。これから探します(1^^;)。