この記事はU-TOKYO AP Advent Calendar 2017の11日目の記事です.僕はみなさん一度は触ったことがあるであろうArduinoの初歩的な内容を寄稿します.
はじめに
この記事が役立ちそうな読者は次の2パターンに大別できます.
- Arduinoで
digitalWrite()
関数を使い,Lチカを取り敢えずやってみたArduino初心者 -
analogWrite()
関数を使ってLED輝度を制御してみたものの,思い通りにならないと嘆いている人
Arduinoを初めて触る方は,先にLチカを試しておきましょう.ばりばりLED光らせてるぜ!という方にも,この記事の最後に書かれている情報だけは有用かもしれません.
回路を組み立てる
LEDと抵抗,そしてArduinoを下図のように組みます1.Lチカのときにも気を付けたように,LEDが壊れるほどの大きな電流が流れないような抵抗を選びましょう.
この際デジタルピンの番号は後述するように,"~"が付いているものを選ぶ必要があります.後に述べるスケッチをそのまま用いたければ,図の通り11番ピンを選んでおいてください.
LED輝度を変える方法
analogWrite()関数を使う
digitalWrite()
関数では出力電圧をON/OFFの2値で制御しました.しかしArduinoはそれだけではなく,多段階に電圧を変えることもできます.これを行うには,analogWrite()
関数を使う必要があります.
この関数はanalogWrite(pin, value)
という形で使います.pin
はint型の値で,電圧を出力するデジタルピン番号を指定します.例えばArduino Unoでは,3,5,6,9,10,11番ピンがこの関数に対応しています.Arduinoのボードをよく見ると,それらの数字の横には"~"マークが付いているはずです.
またvalue
は0から255の範囲をとるint型の値で,0を指定すると0V,255を指定するとArduinoの電源電圧が出力されます.Arduino Unoであれば,value
に255を指定したときの出力電圧は5Vです. 一般にvalue
に $v$ を指定したとき,出力電圧は $5.0 \times \frac{v}{255}$ Vになるというわけです.
PWM制御について
前パラグラフを読んで「Arduinoのデジタルピンに繋ぐ…?デジタルなんだからON/OFFしかできないやんけ!!!」と思った人はかなり鋭いところを突いています.実はArduinoでは,__PWM制御(Pulse Width Modulation)__という技術を使い,ONとOFFだけで出力電圧を多段階制御しています.
PWM制御では,電圧ON/OFFのスイッチングをとても早い周期で行い,1周期の間でONになっている時間を調節することで,出力電圧を調節します.特に電圧がONになっている時間を1周期の時間で割った値を,__デューティー比__と呼びます.この値が1ならば電圧ONのときの電圧値がそのままかかり,0ならば常に電圧OFFなので電圧は全くかからないというわけです.そして,このデューティー比が例えば0.5ならば,電圧ONのときの電圧値のちょうど半分だけの電圧が得られます.
さて,analogWrite()
関数の第二引数value
をもう一度見てみましょう.もうお分かりですね…!このvalue
は,PWM制御のデューティー比を指定するための値だったのです!value
として $v$ を取ったとき,デューティー比は$\frac{v}{255}$となり,これから計算される出力電圧は先ほどの式に一致します.
ちなみにvalue
が0から255までの整数値を取れることから,Arduinoではデューティー比を8bit精度で制御できることがわかります.
さあ実装!
ここまでくればArduinoで自在にLEDの輝度を操ることができますね!試しに消灯しているLEDを一定の速さで徐々に明るくするスケッチを書いてみましょう.
素直な実装…?
analogWrite()
関数の第二引数を0から255まで一定の速さで変化させれば,LEDにかかる電圧,ひいては電流も一定の速さで変化し,一定の速さで徐々にLEDが明るくなっていく気がします.この方針で実装してみましょう.ここでは第二引数をインクリメントする度に,50ms待つことにします.
const int LED_PIN = 11;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
for(int i = 0; i <= 255; i++){
analogWrite(LED_PIN, i);
delay(50);
}
analogWrite(LED_PIN, 0);
delay(1000);
}
これをArduinoに書き込んで実行したものがこの動画です.
analogWrite()関数のよくある使い方 pic.twitter.com/8FLXXjapZK
— らでぃっしゅ@そつろん (@radi_bow) 2017年12月7日
何かがおかしいですね…等速で明るさを変えたいのに,起動直後に一気に明るくなり,明るさが最大付近に留まっている時間がやたら長い気がします.
ここまでの中で,何か見落としていることがあったのでしょうか…?
Weber-Fechnerの法則を反映する
実は,人間に対する物理的刺激量 $R$ とそれによって生じる心理的感覚量 $E$ について,次の関係式が近似的に成立することが知られています.
E = k \log R
ここで $k$ は定数です.これを__Weber-Fechnerの法則__と呼びます.今考えている状況では,$R$ はLEDの輝度に,$E$ は人が知覚する光の強さにあたります.
この法則を考慮に入れて,「人が知覚する光の強さ」が等速で変化するようにしてみましょう.そのためには,LEDが出せる最大の輝度を $C$,知覚される最大の光の強さを $L$ として,その $r ;(0 \leq r \leq 1)$ 倍だけの強さの光 $Lr$ を人に感じさせたいときの,第二引数の値 $v$ を求めれば良さそうです.PWM制御のデューティー比が $d$ のときのLED自体の輝度は $Cd$ と表せるので,Weber-Fechnerの法則より
L = k \log (C\cdot 1)\\
\therefore k = \frac{L}{\log C}
が成立します.したがって,やはりWeber-Fechnerの法則より導かれる
Lr = k \log (C d)
に $k$ の式と $d = \frac{v}{255}$を代入して
Lr = \frac{L}{\log C} \log \left(C \frac{v}{255}\right)\\
\therefore v = \exp(\log 255 - (1-r)\log C)
となります.$C$ は用いているLEDの特性に依存する定数ですが,ここではスケッチ内で定数として宣言し,後で実験して良さそうな値に調整できるようにしておきます.
以上の考え方を用いて,改めてスケッチを書いてみましょう!
const int LED_PIN = 11;
const double C = 255.0; // 用いるLEDに依存する定数
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
for(int i = 0; i <= 255; i++){
analogWrite(LED_PIN, getDuty(i/255.0));
delay(50);
}
analogWrite(LED_PIN, 0);
delay(1000);
}
int getDuty(double ratio){
return round(exp(log(255.0) - (1 - ratio) * log(C)));
}
これをArduinoに書き込んで実行するとこの動画のようになります.
LEDの点灯の仕方をいい感じに調整するとこうなる pic.twitter.com/FQNwDhkjoP
— らでぃっしゅ@そつろん (@radi_bow) 2017年12月7日
ちゃんと一定の速さでLEDの明るさが変化しているように見えますね!
明日のU-TOKYO AP Advent Calendar 2017はyuinitykさんの遺伝的アルゴリズムを使ったお話です.