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

この記事は[U-TOKYO AP Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ut_ap "U-TOKYO AP Advent Calendar 2017")の11日目の記事です．僕はみなさん一度は触ったことがあるであろうArduinoの初歩的な内容を寄稿します．

# はじめに
この記事が役立ちそうな読者は次の2パターンに大別できます．

* Arduinoで`digitalWrite()`関数を使い，Lチカを取り敢えずやってみたArduino初心者
* `analogWrite()`関数を使ってLED輝度を制御してみたものの，思い通りにならないと嘆いている人

Arduinoを初めて触る方は，先にLチカを試しておきましょう．ばりばりLED光らせてるぜ！という方にも，この記事の最後に書かれている情報だけは有用かもしれません．

# 回路を組み立てる
LEDと抵抗，そしてArduinoを下図のように組みます[^1]．Lチカのときにも気を付けたように，LEDが壊れるほどの大きな電流が流れないような抵抗を選びましょう．

![LED回路](https://qiita-image-store.s3.amazonaws.com/0/212951/9f5e21b5-ea4b-0468-8dce-08ac0e467417.png "LED回路")

この際デジタルピンの番号は後述するように，"~"が付いているものを選ぶ必要があります．後に述べるスケッチをそのまま用いたければ，図の通り11番ピンを選んでおいてください．

# LED輝度を変える方法
## analogWrite()関数を使う
`digitalWrite()`関数では出力電圧をON/OFFの2値で制御しました．しかしArduinoはそれだけではなく，多段階に電圧を変えることもできます．これを行うには，`analogWrite()`関数を使う必要があります．

この関数は`analogWrite(pin, value)`という形で使います．`pin`はint型の値で，電圧を出力するデジタルピン番号を指定します．例えばArduino Unoでは，3,5,6,9,10,11番ピンがこの関数に対応しています．Arduinoのボードをよく見ると，それらの数字の横には"~"マークが付いているはずです．

![Arduino Uno](https://qiita-image-store.s3.amazonaws.com/0/212951/f96c3d98-5691-a1f6-fe35-28bd18143806.jpeg "Arduino Uno")

また`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のときの電圧値のちょうど半分だけの電圧が得られます．

![PWM制御のグラフ](https://qiita-image-store.s3.amazonaws.com/0/212951/ee4c533a-2237-11b6-007e-ae66b3198a7e.png "PWM制御のグラフ")

さて，`analogWrite()`関数の第二引数`value`をもう一度見てみましょう．もうお分かりですね…！この`value`は，PWM制御のデューティー比を指定するための値だったのです！`value`として $v$ を取ったとき，デューティー比は$\frac{v}{255}$となり，これから計算される出力電圧は先ほどの式に一致します．

ちなみに`value`が0から255までの整数値を取れることから，Arduinoではデューティー比を8bit精度で制御できることがわかります．

# さあ実装！
ここまでくればArduinoで自在にLEDの輝度を操ることができますね！試しに消灯しているLEDを一定の速さで徐々に明るくするスケッチを書いてみましょう．
## 素直な実装…？
`analogWrite()`関数の第二引数を0から255まで一定の速さで変化させれば，LEDにかかる電圧，ひいては電流も一定の速さで変化し，一定の速さで徐々にLEDが明るくなっていく気がします．この方針で実装してみましょう．ここでは第二引数をインクリメントする度に，50ms待つことにします．

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

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">analogWrite()関数のよくある使い方 <a href="https://t.co/8FLXXjapZK">pic.twitter.com/8FLXXjapZK</a></p>&mdash; らでぃっしゅ@そつろん (@radi_bow) <a href="https://twitter.com/radi_bow/status/938789844938919936?ref_src=twsrc%5Etfw">2017年12月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


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

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

## Weber-Fechnerの法則を反映する
実は，人間に対する物理的刺激量 $R$ とそれによって生じる心理的感覚量 $E$ について，次の関係式が近似的に成立することが知られています．

```math
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の法則より

```math
L = k \log (C\cdot 1)\\
\therefore k = \frac{L}{\log C}
```

が成立します．したがって，やはりWeber-Fechnerの法則より導かれる

```math
Lr = k \log (C d)
```

に $k$ の式と $d = \frac{v}{255}$を代入して

```math
Lr = \frac{L}{\log C} \log \left(C \frac{v}{255}\right)\\
\therefore v = \exp(\log 255 - (1-r)\log C)
```
となります．$C$ は用いているLEDの特性に依存する定数ですが，ここではスケッチ内で定数として宣言し，後で実験して良さそうな値に調整できるようにしておきます．

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

```c: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に書き込んで実行するとこの動画のようになります．

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">LEDの点灯の仕方をいい感じに調整するとこうなる <a href="https://t.co/FQNwDhkjoP">pic.twitter.com/FQNwDhkjoP</a></p>&mdash; らでぃっしゅ@そつろん (@radi_bow) <a href="https://twitter.com/radi_bow/status/938790196614541312?ref_src=twsrc%5Etfw">2017年12月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


ちゃんと一定の速さでLEDの明るさが変化しているように見えますね！
***

明日の[U-TOKYO AP Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ut_ap "U-TOKYO AP Advent Calendar 2017")はyuinitykさんの遺伝的アルゴリズムを使ったお話です．

[^1]: 蛇足ですが，この図を書くのに[Fritzing](http://fritzing.org/home/ "Fritzing")というフリーソフトを使いました．綺麗な回路のイラストを書くのにおすすめ．