Arduinoのアナログ出力
前項で、Arduino にはアナログ出力が可能な端子がいくつかあると書きました。しかし実は、これは正確な言い方ではありません。
たとえば照明の明るさを調節する場合、古典的な方法は電圧を変化させることです。しかし、Arduino の出力端子は、電圧を変化させることができません。オンならば 5V ( 3.3V 電源の場合は 3.3V )、オフならば 0V の2段階にしか設定できないのです。
ではアナログ出力とは何なのか?
実は Arduino のアナログ出力とは、オン/オフを高速で切り替えることにより実現されているのです。たとえば LED なら、オン/オフを高速で繰り返すと、人間の目には点滅として識別できず、平均すると『暗く点灯している』ように見えるのです。オンになっている時間とオフになっている時間が同じなら、『ずっとオンの場合の半分』の明るさ…人間が『半分』と感じるかどうかは別に議論する必要がありますが、少なくとも光のエネルギーを点滅の間隔より充分長い時間で平均すると『ずっとオン』の場合の半分になります。半分の時間しか光っていないのですから当然です。
この『オン/オフのうち、オンの部分の比率』をデューティ比といいます。0% ならずっとオフ、100% ならずっとオン、50% ならオンとオフが半々ということです。Arduino では、出力端子のデューティ比を変化させることで、出力端子に接続した素子( LED など)に供給する平均の電力を変化させることができるのです。
製作と実行
回路と配線
回路は前回の LED のオン/オフとまったく同じです。
プログラム
以下のソースコードを入力して下さい。
#define LED_PIN 3
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int i;
for(i=0; i<=255; i++){
analogWrite(LED_PIN, i);
delay(10);
}
for(i=255; i>=0; i--){
analogWrite(LED_PIN, i);
delay(10);
}
}
プログラムの解説
変数の宣言
Arduino のスケッチでは、C言語と同様に変数を扱うことができます。変数は使用前に宣言が必要で、
型名 変数名 [ , 変数名 … ];
の書式で宣言します。主な型名には以下のようなものがあります。
bool | 論理型。true(真)または false(偽)のいずれかの値をとる。 |
char | 文字型。1バイト文字を表す。値は’A’のように単引用符で表す。 |
byte | 1バイト符号なし整数(0~255)を表す。 |
int | Arduino UNOでは16ビット符号付き整数値(-32,768~32,767) Arduino DUEでは32ビット符号付き整数値(-2,147,483,648~2,147,483,647) |
float | 32ビット浮動小数点数値(-3.4028235E+38~3.4028235E+38) |
変数が使用できるのは、宣言されたブロック内(波括弧に囲まれた範囲)だけです。今回の例では 変数を loop() 関数の先頭で宣言しているので、loop() 関数の中だけで使用できます。
for() 文で繰り返す
for() 文は処理を繰り返すためのものです。書式は以下の通りです。
for(式1; 式2; 式3){ 繰り返したい処理 }
実行の順番は、
- 式1を実行する
- 式2の結果が false なら終了
- 繰り返したい処理を実行する
- 式3を実行する
- 2に戻る
となります。通常、
式1にはカウンタに使用する変数などの初期化
式2には繰り返しの継続条件(比較など結果が真偽値になる式)
式3にはカウンタ変数のインクリメント(増加)など
を書くことが多いです。例えば
for(i=0; i<=255; i++){
処理1;
}
ならば、変数iをカウンタとして使用して、i=0 から 255 まで 1ずつ増加させながら処理1を繰り返します。
analogWrite() でアナログ量を出力する
前項で登場した digitalWrite() とよく似た関数ですが、こちらはアナログ値( PWM )を出力します。書式は以下の通りです。
void analogWrite(int pin, int value)
analogWrite()関数には、2つの引数 pin, value があります。
pin には、出力したいピン(端子)の番号を整数値で指定します。
value には、0 ~ 255 の整数値を指定します。出力は PWM で、0 を指定するとデューティ比が 0%、 255 を指定するとデューティ比が 100% となります。
全体の動作
for(i=0; i<=255; i++){
analogWrite(LED_PIN, i);
delay(10);
}
この部分の動作は、LED_PIN(= D3 )端子に対して、10 ms 間隔で 0、1、2、… 255 までの値を順に指定していきます。出力は PWM なので、デューティ比が 0% から 100% までだんだん上がっていくことになります。D3 には LED が接続されているので、LED が消灯からだんだん明るくなっていきます。明るさが最大になるまでは 10 ms × 256 = 2560 ms ≒ 約2.6秒かかります(実際には analogWrite() やループのカウント処理にも時間がかかるのでもう少し長いかもしれません)。
for(i=255; i>=0; i--){
analogWrite(LED_PIN, i);
delay(10);
}
こちらもほとんど同じですが、for ループのカウンタ変数 i が 255 から 0 まで減少していきます。つまり、約2.6秒かけて LED がだんだん暗くなっていきます。
これが loop() 関数の中で繰り返されるので、
『約5秒周期で、LED がゆっくり明るくなったり暗くなったりする』
という動作をするはずです。
コンパイル&実行
それでは、USB ケーブルを PC と Arduino に接続し、Arduino IDE の『 → 』ボタンをクリックしてスケッチをコンパイル&実行してみましょう。
上手く行ったでしょうか。単純なオン/オフ制御とちがい、LED がだんだん明るくなったり、だんだん暗くなったりしているのが判ると思います。
人間の感覚にあわせてちょっと改良
上手く行くと欲が出てきます。
上の動画をよく見ると、前半は急激に明るくなりますが、後半は明るさがほとんど変化していないように見えます。プログラムでは一定時間ごとに1ずつ値が増えているのに、なぜこうなるのでしょうか。
これは、『人間の感覚は対数的である』ことが影響していると思われます。
人間の目には、『明るさ』の変化が、
『 1 → 2 』『 2 → 3 』『 3 → 4 』『 4 → 5 』
という風に、一定の値ずつ加算していく『等差数列(一次関数)』的な変化ではなく、
『 1 → 2 』『 2 → 4 』『 4 → 8 』『 8 → 16 』
という風に、一定の値を掛けて増えていく『等比数列(指数関数)』的な変化をすると『同じ間隔で変化している』ように感じるということです。
先のプログラムでは常に1ずつ加算/減算していました。これでは、明るくなればなるほど『1の増減』に対する『明るさの変化』が相対的に小さくなってしまうのです。
というわけで、ものは試しです。早速プログラムを書き換えてみましょう。
#define LED_PIN 3
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int i;
for(i=0; i<=8; i++){
analogWrite(LED_PIN, (1<<i)-1);
delay(300);
}
for(i=8;i>=0;i--){
analogWrite(LED_PIN, (1<<i)-1);
delay(300);
}
}
analogWrite() で出力する値を、次の式で求めるように変更しました。
(1<<i)-1
は、2i-1(iは変数です。虚数単位ではありません)を表します。具体的には、
0, 1, 3, 7, 15, 31, 63, 127, 255
となっています。これは、等比数列
1, 2, 4, 8, 16, 32, 64, 128, 256
より1ずつ小さい値になっています。そうすればちょうど Arduino の出力端子に指定可能な最小値0と最大値 255 になるので…。
この改造でループ回数が i = 0 ~ 8 の 9回だけと少なくなったので、1回の点滅にかかる時間が先ほどの例とだいたい同じになるように、delay() の時間を 300 ms に変更しました。
では実行してみましょう。
明るさの変化が 9段階なのでカクカクしていますが、こちらの方が『一定の速さでだんだん明るく/暗くなっていく』感じがします。
どうやら『明るさ』や『音の大きさ』などが人間の感覚で『一定のペースで増減する』ようなプログラムを作る場合は、変化量を等比数列的にするとよい、といえそうです。
まとめ
- Arduino は、PWM によって擬似的なアナログ出力をすることができる
- アナログ(PWM)出力には analogWrite() 関数を使用する
- LEDの明るさなど人間の感覚に関わるものは、等比数列的(指数関数的)に変化させた方が自然な増減に感じる
コメント