[Micropython]三角波と正弦波によるLED点滅の違い

## はじめに
つい先日、NeopixelのLEDを使ったIoT機器(エンタメ要素の方が強い)をつくる機会がありました。
そのときにLED点滅時の明度の制御を三角波と正弦波でそれぞれ行ったときの違いを調べてみました。その時の備忘録となります。
(実際には、点滅時の明度の変化を階段状に変化させたところ機械的な感じがしたので別な方法を探った結果です)

## TL;DR
* NeoPixelのLED点滅時の明度の制御を三角波と正弦波でそれぞれ実装
* 三角波と正弦波を表現するのにMicropythonの三角関数使って実装
* 点滅の動画を比較した結果、正弦波の方が「味のある点滅」になった

## 正弦波について
正弦波を点滅に適用するので以下の関数を使用します。
```math
f_{sin}(x) = -\frac{1}{2}(\cos2x\pi)+\frac{1}{2}
```
この関数をグラフで表すしたのがこちらになります。
(グラフは[geogebra](https://www.geogebra.org/)にて作成。以下同様)

<img width="640" src="https://qiita-image-store.s3.amazonaws.com/0/3763691/1339c2e5-9c8c-a595-1604-e5ca6c4c6e9b.png">

Pythonのコードはこちら。

```python
import math
def sinwave(x):
    return (-math.cos(2*math.pi*x)+1)/2
```

## 三角波
```math
f_{tri}(x) = \frac{1}{\pi}\cos^{-1}(\cos2x\pi)
```
この関数をグラフで表すしたのがこちらになります。

<img width="640" src="https://qiita-image-store.s3.amazonaws.com/0/3763691/c06a43f1-5929-10f8-d11c-4517a4f61ecc.png">

三角波であればコードを組むことで表現できますが、せっかくなので正弦波と対比させる意味で$\cos^{-1}$(アークコサイン)を使っています。
Pythonのコードはこちら。

```python
import math
def trianglewave(x):
    return math.acos(math.cos(2*math.pi*x))/(math.pi)
```

### LEDの明度について
実際のLEDの点滅は、LEDが点灯しているときのRGBの値に対して前述の$f_{sin}(x)$や$f_{tri}(x)$が返す値を掛け算するかたちで実現します。例えば、正弦波で点滅させたいときは以下のようなコードになります。

```python
_R = 0
_G = 127
_B = 255
blightness = sinwave(x)　# xは0〜1までの値
led[0] = (int(_R*blightness), int(_G*blightness), int(_B*blightness)) # LEDのRGBを設定
led.write() # LEDに反映
```

## 実装したコード
こちらは今回実装した最終的なコードになります。

```python:main.py
import math
import neopixel
import machine
import utime

_BLINK_PERIOD = const(2000) # 点滅周期 2.0sec
_TIMER_PERIOD = const(20) # タイマー周期 20msec

_LED_R = const(0)
_LED_G = const(255)
_LED_B = const(255)

count = 0
led = neopixel.NeoPixel(machine.Pin(10), 1) # NeoPixel LED 1個

# 点滅処理(タイマーのコールバックに指定)
def blink(timer):
    global count
    if count < _BLINK_PERIOD:
        count = count + _TIMER_PERIOD
    else:
        count = 0
    # 明るさの算出
    blight = sinwave(count/_BLINK_PERIOD)　# 正弦波で点滅
    # blight = trianglewave(count/_BLINK_PERIOD) # 三角波で点滅
    led[0] = (int(_LED_R*blight), int(_LED_G*blight), int(_LED_B*blight))　# 明るさを色に反映
    led.write()
    
# 正弦波
def sinwave(x):
    return (-math.cos(2*math.pi*x)+1)/2

# 三角波
def trianglewave(x):
    return math.acos(math.cos(2*math.pi*x))/(math.pi)

def main():
    t1 = machine.Timer(0)　# タイマー生成
    t1.init(period=int(_TIMER_PERIOD),callback=blink)　# blink関数をコールバック関数として指定
    while True:
         utime.sleep(1)
 
if __name__ == '__main__':
    main()
```

_BLINK_PERIODで指定される点滅周期を_TIMER_PERIOD毎にLEDに反映していきます。
ESP32系のMicropythonで、NeoPixelの制御ライブラリが含まれているので、以下のようなポート指定だけですぐに制御できるようになります。

```python
import neopixel
import machine
led = neopixel.NeoPixel(machine.Pin(pin), num) # pin:使用するPin num:LEDの個数
```

## 比較結果
以下の動画は「三角波」と「正弦波」の違いをできるだけ位相が同じになるように比較したものです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/I59HGxS0b6k" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy" allowfullscreen></iframe>

動画は常速のあと、1/2倍速、1/4倍速になっていますが、正直なところパッと見通常速だと違いが分かりませんが、「正弦波」の方がなんかメリハリが効いていて「味がある」感じがします。言い換えれば、三角波の方は「機械的」なのかもしれません。

動画からこの波形の差を感じていただけると幸いです。

<img width="640" src="https://qiita-image-store.s3.amazonaws.com/0/3763691/8f6f3761-6102-8ad0-7c07-43a34c316e44.png">

## 最後に
点滅周期が長くなるともっと光り方の「表情」見えてくるのだと思います。
LEDの点滅やイルミネーションの光らせ方に「味」を求める方はお試しください。