Edited at

ArduinoでLEDの明るさを「線形に」変える

More than 1 year has passed since last update.

この記事は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待つことにします.


led.ino

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に書き込んで実行したものがこの動画です.

何かがおかしいですね…等速で明るさを変えたいのに,起動直後に一気に明るくなり,明るさが最大付近に留まっている時間がやたら長い気がします.

ここまでの中で,何か見落としていることがあったのでしょうか…?


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の特性に依存する定数ですが,ここではスケッチ内で定数として宣言し,後で実験して良さそうな値に調整できるようにしておきます.

以上の考え方を用いて,改めてスケッチを書いてみましょう!


led.ino

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の明るさが変化しているように見えますね!


明日のU-TOKYO AP Advent Calendar 2017はyuinitykさんの遺伝的アルゴリズムを使ったお話です.





  1. 蛇足ですが,この図を書くのにFritzingというフリーソフトを使いました.綺麗な回路のイラストを書くのにおすすめ.