Kpです。
Arduinoに関してWebを見ていると、こんな記述を時たま見かけます。
analogRead()するピンを、pinMode()で設定してはならない。
Webで見かけるならまだ我慢できたのですが、実験実習の指導書にまで同じことを書かれると話は別です。
ここでは簡単に、なぜanalogRead()とpinMode()が併用できるかを説明します。
Arduinoはたくさん存在しますが、すべて同じような仕様ですから、今回は最も一般的と思われるArduino UNO(ATmega328)基準で書きたいと思います。
データシートはこちら(2016/02/28 閲覧)
pinMode()が何をしているのか
実はこれが判れば簡単にこの問題を説明することができます。
pinMode()が操作するレジスタは、以下の2つです。
- PORTx
- DDRx
xにはB,C,Dのいずれかが入ります。誰しも一度は耳にしたことがあるポートというやつです。全てのピンにこれらレジスタの1ビットが割り当てられています。利便性のため、以下ではPORTxのnビット目をPORTxn、DDRxのそれをDDRxnと記述します。(この略称はデータシート基準です。P75に書いてあります。)
では、pinMode()の実装を読んでみます。ただ、ピン番号からレジスタを特定する仕組みなどを説明していると話がかなり複雑になるので、ここでは擬似的なコードで説明します。
void pinMode(uint8_t pin, uint8_t mode) { ポートとビットを特定する魔法 if (そんなピンはない) return; PORTxとDDRxを特定する魔法 if (mode == INPUT) { noInterrupts(); DDRxn = 0; PORTxn = 0; interrupts(); } else if (mode == INPUT_PULLUP) { noInterrupts(); DDRxn = 0; PORTxn = 1; interrupts(); } else { noInterrupts(); DDRxn = 1; interrupts(); } }
pinMode()の実装は、だいたいこんな感じです。(Arduino1.6.7現在)
実際のソースコードはもう少し複雑なので、興味がある方は是非読んでみてください。Arduino内部のwiring_digital.cに記述されています。(インデントのタブとスペースが統一されていなかったり、これだけ重要な関数なのにコメントに疑問が残されていたり、なかなかアレです。)
pinMode()がそれぞれのモードに対してどんな値をレジスタに設定するかを表にまとめました。
モード | DDRxn | PORTxn |
---|---|---|
INPUT | 0 | 0 |
INPUT_PULLUP | 0 | 1 |
OUTPUT | 1 | 不定 |
なるほど、データシートP77のTable 14-1と矛盾しません。では、これらのレジスタの初期値はどうなっているのでしょうか。
データシートP91の14.4節に、それぞれの初期値が記述されています。これによると、DDRxn、PORTxnとも、初期値は全て0です。すなわち、
全てのピンはpinMode(*, INPUT)された状態で初期化されている。
ということが判ります。
まとめ
今回の調査で、pinMode()に関する2つの興味深い性質が明らかになりました。
- digitalRead()するピンにpinMode()による設定は必要ない。
- analogRead()とpinMode(*, INPUT)は併用できる。
初期状態がINPUTということは、digitalRead()でさえも何の設定もなしに使えるということです。しかし、だからといってsetup()からpinMode(*, INPUT)を全て削除するべきでしょうか。そのようなことは誰だってしたくないでしょう。なぜなら、それは可読性を著しく低下させるからです。逆に考えると、pinMode()はプログラムの可読性を向上させます。どのピンが、どういう用途に使われるかはっきりさせてくれます。
そこで、こう提案したいと思います。
analogRead()するピンはpinMode()でINPUTに設定すべし!
おまけ
アナログピンを入出力両方で使えないとできないことをやってみます。
回路定数は適当でいいのですが、C=47μF、R=120kΩで実験してみました。
Arduinoには次のスケッチを書き込みます。
void setup() { Serial.begin(9600); } void loop() { pinMode(A0, OUTPUT); digitalWrite(A0, HIGH); Serial.println("Charging..."); delay(1000); pinMode(A0, INPUT); int value; while ((value = analogRead(A0)) > 200) { Serial.println(value); } }
コンデンサを充電した後、放電する様子が確認できます。
このブログの本旨であるロボカップジュニアとあまり関係がない記事なので最後にひっそりと書きますが、この記事の執筆者は管理人ではなくその元相方です。
コメント