Groveで広がるArduinoの世界-Senser Kit-① Grove Light Sensor
Light Sensorは明るさを0~1023の値で表すセンサです。
(裏面)
を取り付け、右上にある
Light Sensorを利用します。
●ディジタルは'1'と'0'の世界というけれど
コンピュータは2進法で動いています。1と0でいろいろなものを表現します。Arduinoでは、C++言語なので、次のように数値を表現します。
数値 | 2進表記 | 16進表記 |
---|---|---|
0 | 0b0 | 0x00 |
8 | 0b00001000 | 0x08 |
11 | 0b00001011 | 0x0b |
256 | 0b11111111 | 0xff |
'0'とは何でしょうか。電池でいえばマイナス端子を指します。ArduinoではGND=Ground(グラウンド)を指します。Arduinoの予約語ではLowです。ブール型ではfalseです。
'1'とは何でしょうか。マンガン単三電池ではプラス端子の1.5Vを指します。エネループであれば1.2Vです。Arduinoでは5Vで、予約語ではHighです。ブール型ではtrueです。
Arduino UNOで使われているマイコンはAtmega328です。入力ピンの'0'と判断される電圧は-0.5~0.3Vcc=1.5Vです。つまり、入力が1.5V以下ならばLowです。
入力ピンの'1'と判断される電圧は-0.6Vcc(=3.0V)~Vcc+0.5=5.5Vです。つまり、入力が3.0V以上ならばHighです。
では、1.5~3.0Vの間の電圧が入力されたら、どうなるでしょうか。類推ですが、電源の5Vの半分以上、つまり2.5V以上はHighになるでしょう。どちらにしろ、ディジタル入力は、しっかりと1.5V以下もしくは3.0V以上の電圧を入力することがトラブルの原因を避ける意味で賢明です。
出力ピンのLowは、sink時=電流をマイコンが吸い込むとき20mAで0.7Vを維持します(電源電圧は5V)。電流が増えると、電圧が上がっていきます。
source時=マイコンから電流が流れだすとき、0mAでは3.0Vですが、電流が増えるほど電圧は下がっていき、20mAのときには2.1Vになります(電源電圧は3V時、5Vのデータはない)。
マイコンの端子名の記号の上にバーが書かれていることがあります。バーが書けないときは、/CSのようにスラッシュをつけることも便宜上あります。これは、負論理を意味します。
'1'は普通はtrueです。負論理では'0'がtrueと逆になります。
●アナログ
人が日常目にするもの、感じるものをアナログと呼びます。マイコンはアナログを直接扱えないので、アナログ-ディジタル変換を行ってから様々な処理をプログラムで行います。
アナログ-ディジタル変換は、
- アナログ-ディジタル・コンバータ
- A-Dコンバータ
- ADC
などとも呼ばれます。3文字熟語は、ほかの用語と混同されることもあるので、「A-Dコンバータ」を使うのが望ましいです。
A-Dコンバータは、それ自体がICになっていることも多いのですが、Arduinoなどで使われるマイコンに内蔵されていることもあります。Raspberry Piのマイコンには内蔵されていません。
Arduino UNOのA0~A5のピンがアナログ入力です。同時に使えます。入力範囲は、デフォルトでは0~5Vです。得られるディジタル値は、0~1023です。1/1024の分解能になります。
●光センサ
Arduino.ccにあるこの製品Senser Kit-Light Sensorの解説ページは、次のURLです。
レッスン光センサ (下記の回路図を転載)
解説があります。内容はCdS(光を当てると抵抗値が減少する)を使っている説明ですが、実際の光センサとは異なります。ここで利用している光センサは、明るさによって電流が変化するダイオード+アンプで構成されたデバイスです。
下記のURLは、製造元のSeeedの解説ページです。
Grove - Light Sensor (下記の回路図を転載)
ここで使われている光センサは、強い光が当てられると電流が増える性質をもっています。OPアンプは増幅率1のボルテージ・フォロワと呼ばれる回路です。
A点をArduino UNOのA3端子へつなげればよいはずです。上記のページにOPアンプが搭載されていない写真があります。昔はその回路だったのだろうと思いますが、なんらかの不都合が生じました。想像ですが、光が弱いときなど、Arduino UNOではおかしな値になったのでしょう。
このマイコンAtmega328は、A-Dコンバータ・ブロックの入力部分にあるサンプリング&ホールド回路にバッファが入っていないため、つなぐセンサなどの回路の出力インピーダンスは10kΩ以下が望ましいとデータシートに書かれています。A点のインピーダンスが10kΩ以上になったら、コンデンサに充電するときに誤差が生じるようです。したがって、同じ光が当たっている状態で何回も測定すると、ディジタル値がふらふらしてしまったのかもしれません。それを防ぐために、ボルテージ・フォロワ回路が追加され、SIG信号は数Ωという低いインピーダンスになり、安定な測定ができるようになったと想像します。
回路図で抵抗R1は10kΩですが、購入したボードでは683と書かれていたので68kΩのようです。
●接続
Senser Kitのままだと、Arduino UNOのアナログ入力A3に配線されています。赤線の部分を切り離したときは、次のように接続します。なお、Groveとは、写っている4ピンのコネクタの名称です。古くからSeeedがDexter Industriesのキット類を扱っていたころからのコネクタです。2番は何もつなぎません。
Grove Light Sensor | Arduino UNOの端子 |
---|---|
1番 SIG | A3 |
3番 Vcc | 5V |
4番 GND | GND |
実際は、付属のGrove-Groveコネクタでつなぐだけです。
●スケッチ
サンプルを動かしてみましょう。
int light_sensor = A3;
void setup() {
Serial.begin(9600); //begin Serial Communication
}
void loop() {
int raw_light = analogRead(light_sensor); // read the raw value from light_sensor pin (A3)
int light = map(raw_light, 0, 1023, 0, 100); // map the value from 0, 1023 to 0, 100
Serial.print("Light level: ");
Serial.println(light); // print the light value in Serial Monitor
delay(1000); // add a delay to only read and print every 1 second
}
内容を見ていきます。
int light_sensor = A3;
先頭のintは型を表しています。符号付き整数なので、値の範囲は-32768から32767までです。使っているマイコンによっては16ビットでなく32ビットのときもあります。絶対に符号付き16ビット整数型が使いたいときは、int16_tと記述します。
void setup() {
Serial.begin(9600); // begin Serial Communication
}
setup()関数の中には、一度だけ実行してほしいことを記述します。ここでは、シリアルモニタが使いたいので初期化し、その通信速度は9600bpsだと伝えています。
void loop()が、電源を切るまで永久に繰り返す処理を記述するところです。1回だけ何か仕事をしたら終わりにしたいときには、setup()に記述し、このloop()内は何も記述しないでおきます。
voidとは、この関数の戻り値の型を示していて、何も戻り値とかはなくって、実行するだけという意味です。C言語では、昔、型は厳密ではありませんでした。トラブルを避けるために、変数や関数のすべてに型をつけるようにという方針になったため、何もないという型が用意されたようです。
関数は、何か実行したら、値を戻すという使われ方が一般的です。
int raw_light = analogRead(light_sensor);
analogRead(light_sensor)は、analogRead(A3)と同じです。A3端子の電圧を読み込みます。A3端子といっても、A3端子とGND間の電圧です。10ビットのA-Dコンバータが内蔵されているので、得られる値は0~1023の数値です。
その読み取った値をraw_lightに代入します。代入された変数raw_lightはint型だと宣言しています。まとめて1行で書いていますが、次のように記述しても同じです。
int raw_light;
raw_light = analogRead(light_sensor);
intなので、raw_light は0~1023までの数値しか取りえないですが、値の範囲は-32768から32767までだと宣言しています。これがもう一つ小さい数値を扱うbyte型を使うと、符号なしの0~255の範囲になるので、足りません。
変数の型は、マイコンのメモリに何バイトで領域を確保するかということをコンパイラに伝えます。Arduino UNOで使われているマイコンは8ビット・マイコンなので、変数が8ビット=1バイトや16ビット=2バイトというのは、効率よくプログラムが実行できます。
このスケッチでは使われていませんが、float型は実数で、メモリの中では4バイトの領域がとられ、IEEE 754 binary32という単精度浮動小数点数の形式で収納されます。使用可能な値の範囲は、3.4028235E+38から-3.4028235E+38までを表せます。Arduino UNOで使われているマイコンはfloatの計算をする演算ユニットを内蔵していないので、実行にはとても時間がかかります。
int light = map(raw_light, 0, 1023, 0, 100);
0~1023の範囲のデータを、0~100という101段階に変換しています。なんだか中途半端な変換ですね。rawという単語は、「生」という意味で使われています。mapはArduino独自の関数で、整数値だけを扱います。
Serial.print("Light level: ");
シリアルコンソールにLight level:と表示します。改行しません。シリアルコンソールの右下にある速度が9600bpsでないときは、表示が出ないかもしくは文字化けします。9600が選ばれていないときは、9600を選び、一度シリアルコンソールを閉じてもう一度開きます。
Serial.println(light);
0~100の範囲の光強度の値を表示します。改行します。
このままだと、loop()関数なので、loop()の最初に戻ってから実行します。つまり、ものすごいスピードで、
Light level: xx
Light level: xx
Light level: xx
Light level: xx
...
がシリアルコンソールに表示されますが、Light level: xxの表示にかかる時間よりシリアルコンソールにデータを転送するスピードが遅くて間に合わなくなり、止まったようになります。
なので、
delay(1000);
という待ち時間を入れます。引数の単位は1msですから1秒です。整数です。
●実行中の様子
シリアルコンソールには光強度が0~100の間で表示されます。センサを手のひらで覆ったとき、机の上に放置したとき、電球の直下にもっていったときなど、どのように値が変化するかを確かめましょう。
電球などでは、luxという単位が使われます。絶対値を表示するのですが、このプログラムは相対値の表示になります。
照度計と比較してみましょう。40W相当のLED電球を使います。Arduinoで2種類のデータを記録します。
raw_light |
light |
照度計[lux] | |
---|---|---|---|
手で覆う | 12 | 1 | 3 |
10cm上に手をかざす | 354 | 34 | 52 |
20cm上に手をかざす | 510 | 49 | 155 |
環境の光だけ | 708 | 69 | 373 |
電球を60cm離れた上に | 708 | 69 | 483 |
電球を20cm離れた上に | 708 | 69 | 1108 |
200luxの明るさ以上では、センサが飽和しているようです。逆に、それ以下の明るさであれば、ある程度の分解能があります。海外のドラマで、本を読んでいるときに、やけに暗いところでという印象があります。家庭内の明るさが200lux付近だから、その明るさを上限としているのかもしれません。
シリアルコンソールを終了します。メニューのツールからシリアルプロットを選びます。手のひらを30cmほど上からだんだんセンサに近づけている様子です。