PIC AVR 工作室別館 arduinoの館->TopPage->標準機能->アナログ入力

arduinoのアナログ入力について

arduinoのアナログ入力は、AT-MEGA168が内蔵しているADコンバーター機能を使って実現 されています。このアナログ入力機能の概要と使い方、そして使用する際の注意点をまとめていきたいと思います。

arduinoのアナログ入力機能

アナログ入力ピンについて

arduinoの14番~19番ピンの6本のピンは、アナログ入力端子として割り当てられています。 (0番~13番のデジタルI/Oと同様に使うことも出来ます)

アナログ入力ピンとして使う場合は、デジタルピンとは別のピン番号で指定をします。14番~19番は それぞれアナログピン0番~5番として割り当てられているので、0とか3とか5とかといった具合に指定します。 arduinoのボード上にはanalog inの0~5とシルクがプリントされているので迷うことは無いかと思います。 ちなみに、これらのアナログ入力ピンはAT-MEGA168のPC0~PC5端子に該当します。

これらの端子に各種センサーなどを繋ぎ簡単なスケッチを書くことで、入力電圧を計測することができます。

ちなみに、arduino-miniやnanoでは8本がアナログ入力に使用可です。

アナログ入力の命令について

アナログ入力を行う関数はarduinoの標準機能に取り込まれています。以下の簡単な関数の式で利用することができます。

変数 = analogRead(ピン番号)  (戻り値はint型)

ピン番号に指定したアナログ入力端子からの入力電圧を数値化して返す関数です。ピン番号に指定するのは 0~5のアナログピン番号です。arduinoのアナログ入力機能は10ビット幅(0~1023)の 分解能で値を読み取ることが出来ます。また、arduinoのアナログ入力機能は、デフォルトで内蔵5Vの参照電圧を使用している ので、0V~5Vの入力電圧はそれぞれ0~1023の値となって取り込まれることになります。

(厳密にいうと上限の電圧は、5V-5V/1024 ≒ 4.995Vまでです)

入力する電圧がおよそ0.0049V(=5V/1024)上昇するごとに取り込む値が1増える計算になります。 例えば、AD変換で得た値が100であれば、100×0.0049V=0.49Vが入力されたという計算です。

参照電圧について

上記に、参照電圧はデフォルトで内蔵5Vが適用されると書きましたが、AT-MEGA168と同様に アナログ参照電圧は以下の命令によって3種類から選択することが出来ます。ただし、外部からの参照電圧入力を 使用する場合は注意が必要です。(詳しくは後述の注意点をご参照)

analogReference(DEFAULT);
 … デフォルトの5V参照電圧(Avcc)を指定

analogReference(INTERNAL);
  … 内蔵の1.1V参照電圧を指定(※)

analogReference(EXTERNAL);
  … analog referenceピンの外部参照電圧を指定[要注意!]

(※:1.1VはMEGA168の場合 MEGA8の場合は2.56V)

内蔵1.1Vを使った場合、入力に使える電圧は0V~1.1Vとなります。(厳密には1.1Vではなく 1.1V-1.1V/1024 ≒ 1.099Vです)外部参照電圧を使う場合も同様です。

なお、この場合アナログ入力端子に1.1Vを超えた電圧を与えても、IC内部の機器が壊れるということでは ありません。正確な数値が得られないだけです。でも5V(Avccの電圧)を超えてはいけません。

サンプルスケッチ 3軸加速度センサー

秋月で販売されている 3軸加速度センサーを繋いで、加速度をシリアルケーブルでPC宛に送るスケッチです。

int ledPin = 3;
int analogpin0 = 0;
int analogpin1 = 1;
int analogpin2 = 2;
int data = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("start");
}

void loop() {
  data = analogRead(analogpin0);
  Serial.print("X=");
  Serial.print(data);
  Serial.print(",");

  data = analogRead(analogpin1);
  Serial.print("Y=");
  Serial.print(data);
  Serial.print(",");

  data = analogRead(analogpin2);
  Serial.print("Z=");
  Serial.println(data);

  analogWrite(ledPin, data/4);
  delay(100);
}

写真は、reduino-nanoと秋月製3軸加速度センサーを繋いで実験中のものです。

X軸の信号をアナログ0から、Y軸の信号をアナログ1から、Z軸の信号をアナログ2から入力し、 それをSerial.printでPC宛に送っています。また、デジタル3番ピンは抵抗を介してLEDを 接続してあり、Z軸からの入力データがLEDの明るさとなって現れるようになっています。

参照電圧はデフォルトの5Vのままです。秋月の3軸加速度センサーは、5V電源の場合 加速度0でおよそ2.5V出力となるので、アナログ入力の値はおよそ500程度になります。 加速度が±2Gの範囲内まで計測でき、およそ0V~5Vの範囲内で変動することになります。

もう一つサンプルスケッチ 温度センサーLM35

arduinoに温度センサーLM35を繋いで温度を計ってみます。(一部、for文内の不等号がhtmlタグと混同されてしまうので 全角不等号に換えてあります。半角に置き換えてお読みください)

int ledPin = 3;
int analogpin0 = 0;
int data = 0;
int summary = 0;
int i;
double t_value;
int t_1;
int t_2;

void setup() {
  analogReference(INTERNAL);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("start");
}

void loop() {
  digitalWrite(ledPin, HIGH);
  delay(1800);
  summary = 0;
  for(i=0;i<50;i++){
    data = analogRead(analogpin0);
    summary = summary + data;
    delay(2);
  }

  Serial.println(summary/50);
  t_value =(double)(summary)
     / 50.0 * 1.1 / 1024.0 * 100.0;
  t_1 = (int)(t_value);
  t_2 = (int)((t_value - (double)t_1)
     * 100.0);

  Serial.print(t_1);
  Serial.print(".");
  Serial.println(t_2);
  digitalWrite(ledPin, LOW);

  delay(100);
}

色々ゴチャゴチャと不要な部分が多いスケッチですが、大枠としては参照電圧に内蔵の1.1Vを選択し、 アナログ入力から取り込んだ値を実際の温度に換算して、PC宛にシリアル通信しているだけです。

LM35は温度0℃の時に0Vを出力、1℃上昇するごとに出力電圧が10mVずつ高くなるというICで、 計算がとても簡単です。今回は1.1Vの内蔵参照電圧を適用しているので、アナログ入力の値に(1.1V/1024)を掛けて 電圧値に戻し、それをさらに100倍して温度に換算しています。

写真はreduino-nanoとブレッドボードで実際に実験中の様子です。3ピンのトランジスターのようなものがLM35です。

アナログ入力の注意点

プルアップ抵抗について

アナログ入力機能を使う場合、該当の入力ピンはプルアップ抵抗をオフにしてください。

arduinoのアナログ入力ピンは、先述のとおりデジタル入出力ピンとしても使用することが出来ます。 そして、AVRの入力端子は、各ピンごとにプルアップ抵抗を使用/不使用を選択することができます。 プルアップ抵抗を使用する場合は、そのピンを入力モードに設定し、かつそのピンにデジタル出力機能で HIGHを出力することで設定されます。

このプルアップ状態(=出力値がHIGH)のままアナログ入力を行うと、外部機器からの入力電圧とプルアップ抵抗を介した 電圧が混ざりあって正しい電圧が測定できません。出力をLOWに戻してプルアップ抵抗を無効にしてください。

(なお、デフォルトでは無効になっています。意図的に有効にしていなければ問題ありません)

参照電圧について (要注意! きちんと守らないと故障の恐れ!)

analog referenceピンから外部参照電圧を入力して使用する場合、注意が必要です。analogReference(EXTERNAL); のモードで使用したい場合のことです。もし注意を怠ると大事なarduinoを破壊する恐れがあります。

2019.4.7追記

居酒屋ガレージ日記さんのブログ記事で、analog referenceの内部回路とArduino標準機能について内部処理を詳細に調査された結果、 このページで記述していた内容に誤りがあることが判りました。

正しくは、取り消し部分で記載しているような保護抵抗を使わなくても、 analog reference内部のトランジスタ部分はショートしないように、 Arduinoの標準機能の内部処理により上手く制御されており、普通に(使用方法を間違えずに)利用すれば、 安全に利用することが可能です。また、安全に利用するための方法を強調文字にて追記しておきます。

本来、AT-MEGA168の外部参照電圧はデフォルトで「外部参照モード」、つまりAREFピンからの入力電圧 を使用することになっているのですが、arduinoの場合はデフォルトで内蔵5V参照モードに設定されることになっています。

この内蔵5V参照モードになっている状態でanalog referenceピンに外部参照電圧のICなどを直接接続すると、 内蔵5V参照の線とanalog reference用の線がIC内部でショートすることになります。これは、内蔵5Vモードだけでなく 内蔵1.1V参照モードでも同様です。しかも、arduinoは電源投入後(もしくはリセット後)、ブートローダーが 約10秒間プログラムのアップロード待ち状態になるので、ショートしつづけるものと思われます。

(もしかしたらブートローダーの実行中は外部参照モードのままかもしれません。ブートローダやその他のプログラムソースを 眺めないとハッキリとは断言できません)

追記:この図のに登場するFETが、プログラムでの制御方法によってはショートする可能性があります。 以前は、AREF端子に保護抵抗をつける必要があると記述していましたが、Arduino内部のソフトの処理を追ってみると、 ショートしないロジックになっていることが判りました。

以下にAtmelのAT-MEGA168データシートから内部回路抜粋にに補記したものを記します。 (縮小表示しているので、別画面で大きく表示するなどしてください)

図の左端にあるAREFという端子がarduinoのanalog referenceピンに繋がっています。

内蔵5Vや1.1Vの参照電圧を有効に設定するとIC内部では赤い矢印で示したFETトランジスタが 通電状態となるのですが、その時にAREF端子に電圧がかかっていると、その先の交差点でショートしてしまうわけです。

なお、この件はAT-MEGA168のデータシートにも注意事項として載せられています。

Arduinoのアナログ入力機能は、標準で「内部5V参照」が使われます。 そのため、単純に考えれば、AREF端子に外部参照用の電圧源を接続すると、このFETが内部でショートしてしまうはずですが、 Arduinoのアナログ入力処理は、以下のようにな流れで処理を行っています。

・Arduino電源投入直後、アナログ入力回路の動作は「外部参照モード」になっている
・Arduino内部の初期化処理で、アナログ入力のモードを表す内部変数に、「内部5V参照」をセット
・つまり、直接SFRに設定せず、一旦「変数」に保持しておくだけなのがミソ
 (この時点では、ハード処理的にはまだ「外部参照モード」→FETはオフなのでショートしない)
・ユーザアプリ側で、analogReference(EXTERNAL)により、「外部参照モード」に変更
・直接SFRに設定されず、同様に内部変数にだけ「外部参照モード」をセットする
 (この時点で、やはりハード的には変更が行われない→FETはオフのまま)
・ユーザアプリ側で、analogRead()でADCピンから読み込みを実行
・上記の内部変数を元に、実際にSFRを書き換えて、アナログ入力のモードを切替える
 (内部変数が「外部参照モード」なら、結果的にSFRも「外部参照モード」でショートしない)
 (ちなみに「内部5V参照」を使う場合、この時点でようやくSFRが「内部5V参照」に変更)

こうした処理タイミングのため、外部参照電圧をAREFに接続した状態でも、内部のFETはショートしないことになります。

ただし、「やってはいけないこと」があるので、注意。

外部参照電圧をAREF端子に接続した状態で、初期化処理でanalogReference(EXTERNAL)を実行せずに、 analogRead()でアナログ入力処理を行ってしまうと、analogRead()実行の時点で参照電圧関係のSFRが「内部5V参照」(デフォルト値) に変更されてしまい、AREFに繋いだ外部参照電圧と内部参照5Vがショートすることになります。

このため、AREF端子に外部参照電圧を接続する際は、必ず初期化処理内にて(というかanalogRead()を実行する前に) あらかじめanalogReference(external)を実行しておく必要があります。

詳しいことは、 居酒屋ガレージさんのブログに記載されています。

公式サイトのanalogReferenceの説明ページ、 Warningという項目に回避方法が記載されています。簡単に要約すると、参照電圧生成のICとarduinoのanalog reference端子 の間に5kΩの抵抗を挟んでください、とのことです。5kΩの抵抗を挟むことで電源投入時にはショートが回避でき、 その後各ユーザーが作成するスケッチの中でanalogReference(EXTERNAL);を選択すればFETトランジスタがオフになり、外部からの参照電圧 だけが参照されるようになるということのようです。(元々、内部の参照電圧回路自体はインピーダンスが高く電流が 殆ど流れ込まないので、5kΩ程度の抵抗をつけても参照電圧には影響がないようです)

ちなみに、なぜわざわざこのような仕組みになっているのかなぁ、と考えてみました。

MEGA168のデータシートによると、外部電圧参照モードに限らず、内蔵5Vでも内蔵1.1Vでも、 AREFピン(arduinoではanalog referenceピン)とGNDピンの間に0.1μF程度のコンデンサーを 繋いでおくことで参照電圧から入るノイズを減らすことが可能と書かれています。

つまり、限られたピン数で最大限の機能と性能を盛り込んでいる苦労がこの辺りに見え隠れしているわけです。

なお、ノイズ対策については下記を併せてご覧ください。

ノイズ対策について

ノイズ対策は大きくは2つの観点に分けられると思います。

一点は上記の通り、analog referenceピンとGNDピンの間に0.1μF程度のパスコンを挿入すること。 これによって参照電圧に乗っているノイズを軽減することが出来ます。

もう一つは、マイコンを使ってAD変換を行う時に良く用いられる方法で、AD変換開始から完了までの間 CPUコアを休止状態にして、CPUコアが発するノイズを止めるという方法です。

マイコン等の内部のデジタル回路は、信号がオン→オフやオフ→オンに切り替わる極短い瞬間、信号の出力回路部分(バッファ回路) の所で極一瞬、電源端子とGND端子が短絡に近い状態になります(バッファ回路の避けられない特性で、 この瞬間にに貫通電流と呼ばれる大きな電流が電源からGNDに向かって流れます)。

CPUコアが動作している間は1クロック毎にたくさんのデジタル回路から生じるこの貫通電流がノイズ元となって電源回路 を走り回ります。そしてこれがAD変換の値も少し狂わせてしまいます。

この対策として、大抵のマイコンには「CPUを休止モード」にする命令と、「AD変換が完了したら割り込みを発生させて 休止状態から回復」する機能が搭載されています。

MEGA168にもその機能は載っているのですが、arduinoの内部でその機能が使われているのか、 それとも通常どおりCPUはフル回転なのかは、ライブラリのソースプログラムを見ないとなんともいえません。 (目下のところ調査未済)

AD変換中CPUを休止モードにしてしまうと、その他のタイマー割り込みなどタイミングクリティカルな処理に 影響を及ぼすことがあるので、よほど必要でないとCPUの休止モードは用いないようです。なので、arduinoについても なんとなく用いられていないのでは?という気がするのですが…。

そうそう。もちろん、アナログ入力ピンに接続する測定対象物側のノイズ対策もキッチリやっておかないと、 元も子もありません。例えば温度センサーICなら、その電源端子にパスコンをきちんと入れるなどの対応を 行いたいところです。ちなみに上記の写真にはそのパスコンが入ってません。悪い子ちゃんの例です。

内蔵参照電圧の誤差について

内蔵参照電圧(5V、1.1V)は、厳密に5V、1.1Vの値ではなく、若干の誤差が含まれています。

5V参照についてはMEGA168のAvcc端子の電圧が用いられるので、この端子に掛かる電圧の品質に因るの ですが、arduinoの場合はデジタル用の電源Vccとアナログ用の電源Avccが直接接続されているので、 デジタル回路側のノイズがアナログ回路に流れ込みやすい形になっているようです。

また、1.1Vの参照電圧はMEGA168内部で作り出されるのですが、データシートによると 1.1V±0.1Vという幅で誤差が生じるようです。つまり、1割近い誤差です! よほど小さい電圧幅の測定が必要でない限り、内蔵5V参照を使って測定した方がマシかもしれません。 5Vだって約0.005V単位で測定できるのですから。

いずれにしても、厳密な計測には少々足りないような気がしますが、限られたボード面積、限られたチップサイズ、 少ない部品で最大限の機能ということから考えると、arduinoのパッケージではこれで充分ではないかという気はしますが、 いかがでしょう?