4pin RGB LED アノード コモン・カソード コモン どちらでも 同じに扱う

# RGB LED

今回 扱うLEDは、NeoPixel(WS2812他)等のマイコン内蔵フルカラーLEDではありません。
（下の写真のような）Red、Green、Blue 各色のピンと共通ピンの合計4ピンあるRGBLEDです。

<img width="200" alt="800px-RGB_LED.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/63ed2f59-64ed-e524-fd49-45d789ab0ed3.jpeg" />

アノード(+)コモン と、カソード(-)コモンの2タイプがあります。


<img width="150" alt="anode-common.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/e49521fc-591a-df37-ccdc-dff2dabdc86d.png" />

<img width="150" alt="cathord-common.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/c82bfa85-4e0e-2dd4-a226-c2aa850f3862.jpeg" />



一番足の長いピンが共通ピンですが、R・G・Bピンの並びは、RGBLEDの製品により異なる場合があるので、データシートで確認します。

:::note
R・G・B のそれぞれのピンに電流制限抵抗を入れる必要があります。RGBLED製品の諸元をもとに必要な抵抗値を計算で求められますが、3.3V〜5Vで駆動する場合は 500Ω〜1KΩ 程度の抵抗を使えばほぼ問題ありません。
:::

## 点灯させる論理のおさらい

これまでの説明の通り、R・G・B の各色は独立していることが分かります。

_※折りたたんであります。「LEDを点灯させる論理」を確認したい方は、次の「<b><i>ここに表示</i></b>」をクリックしてください。_

<details><summary><b><i>ここに表示</i></b></summary><div>

## On/Off制御（デジタル制御）

各色を`光る`、`消す`で制御すると、次の8色の発光パターンが作れます。
（下表で、`1：光る`、`0：消す`を表す）

|	R	|	G	|	B	|	色	|
|	:-:	|	:-:	|	:-:	|	:-:	|
|	0	|	0	|	0	|（全消灯）|
|	0	|	0	|	1	|	青	|
|	0	|	1	|	0	|	緑	|
|	0	|	1	|	1	|	水	|
|	1	|	0	|	0	|	赤	|
|	1	|	0	|	1	|	紫	|
|	1	|	1	|	0	|	黄	|
|	1	|	1	|	1	|	白	|


これを実現するには、

- アノードコモン の場合

    + `1：光る` `V-`(GND)に接続
    + `0：消す` `V+`(VDD)に接続<br>　

- カソードコモン の場合

    + `1：光る` `V+`(VDD)に接続
    + `0：消す` `V-`(GND)に接続<br>　


と、なります。
アノードコモンとカソードコモンでは、逆の理論になっています。
（カソードコモンの方が、電気の流れと論理が一致していて、分かり易い気がします）

タイトルでいう「同じに扱う」にはなっていませんが、`反転`するだけです。

整数の`0`と`1`で表すなら、カソードコモンを基準に`0`か`1`に決定した値を`i`とすると、アノードコモンの場合の値は、`(1-i)`です。
Boolean変数`x`なら、`(Not x)`で、言語により`(!x)`かも知れません。

`Arduino`における[`digitalWrite()`](https://www.arduino.cc/reference/en/language/functions/digital-io/digitalwrite/)がこれに当たります。R・G・B のそれぞれのピンに対して実行します。

- カソードコモンの場合<br>`digitalWrite(pin#, i)` or `digitalWrite(pin#, x)`<br>　
- アノードコモンの場合<br>`digitalWrite(pin#, 1 - i)` or `digitalWrite(pin#, !x)`

:::note warn
ただし、最初から`digitalWrite(pin#, 0)`がOn、`digitalWrite(pin#, 1)`がOffを意味する`負論理`のマイコンが存在します。今回の記事を`負論理`のマイコンに適用する場合は、さらに`Not`が必要になります。ややこしい・・・
:::

## PWM制御（アナログ制御）

他方、各R・G・BのOn・Off制御ではなく、各R・G・Bを16階調や256階調で制御することで、4096色（$16^3$）や1677万色（$256^3$）の発光パターンを作れます。

これは、各R・G・Bに印加する電圧を変化させることで実現します。この方法としては、PWM（Pulse Width Modulation）電圧変換が一般的です。

PWM電圧変換とは、一定電圧（VDD）の入力からパルス列のオンとオフの一定周期を作り（周期を周波数で示すことが多い）、オンの時間幅（デューティ比）を変化させ、出力電圧を制御することです。出力電圧はデューティ比に該当する平均化された大きさとなります。
例えば、デューティ比を50%とすれば、VDDの半分の電圧を出力することができます。

デューティ比の指定方法は、使用するマイコンと使用する言語によって異なりますが、どんなマイコンでもPWM制御は持っていますし、デューティ比も変えられます。
PWMの周波数は、数百Hzで十分です。高性能のマイコンなら数KHzでも可

例えば、関数`PWM(0〜1023)`でデューティ比が指定できると仮定すると、256階調とするなら、$\bigl(\frac{1023}{255}\bigr)$≒4倍した値を引数に渡せばよいことになります。

スカイブルーという色のRGB値が`(R:135, G:206, B:235)`だとすると、以下の擬似コードになります。

```python
Red.PWM(135 * 1023 / 255)
Green.PWM(206 * 1023 / 255)
Blue.PWM(235 * 1023 / 255)
```

ただし、これは、カソードコモン での場合です。
アノードコモン の場合は、`反転論理`ですから、値を反転する必要があります。
PWM関数の引数でカソードコモンでの値を`i`とするなら、アノードコモン の場合は、`(1023-i)`となります。


```python:アノードコモンの場合
Red.PWM(1023 - 135 * 1023 / 255)
Green.PWM(1023 - 206 * 1023 / 255)
Blue.PWM(1023 - 235 * 1023 / 255)
```

</div></details>


今回は検証用に [WOKWI版](https://qiita.com/nak435/items/583468cbf79a69c8fb67) Raspbery Pi Pico ＆ Micropython を使いますが、どんなマイコンであれ、使える言語に差があっても、考え方はどれも同じです。


<img width="300" alt="rgbled2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/cfbacf5d-160c-aceb-38c3-c8b80746ae17.png" />

:::note warn
Picoは、どのGPIOピンでもPWMを使用できますが、全部で8チャネルしかありません。
GPIO0〜15とGPIO16以降でチャネルが重複しているので、PWMで使用するGPIOピンを選ぶときは注意が必要です。

参考サイト
https://www.denshi.club/parts/2021/07/raspberry-pi-pico-18-pwm.html

:::

# 実際に試す

上の写真のように、カソードコモンとアノードコモンのRGBLEDを、Raspbery Pi Picoに接続します。

## On/Off制御

アノードコモンとカソードコモンの差を吸収するために、クラス化しました。インスタンス生成時に、コモンタイプを指定します。

```python
from machine import Pin
import utime

class RGBLED:
    def __init__(self, red_pin, green_pin, blue_pin, anode_common=False):
        self.red = Pin(red_pin, Pin.OUT)
        self.green = Pin(green_pin, Pin.OUT)
        self.blue = Pin(blue_pin, Pin.OUT)
        self.anode_common = anode_common

    def rgb(self, r, g, b):
        if self.anode_common:
            r = 1 - r
            g = 1 - g
            b = 1 - b
        self.red.value(r)
        self.green.value(g)
        self.blue.value(b)

a_led = RGBLED(28, 27, 26, anode_common=True) #アノードコモン
c_led = RGBLED(3, 2, 1)                       #カソードコモン

Illumination = [
    (0, 0, 1), #青
    (0, 1, 0), #緑
    (0, 1, 1), #水
    (1, 0, 0), #赤
    (1, 0, 1), #紫
    (1, 1, 0), #黄
    (1, 1, 1), #白
    (0, 0, 0)  #全消灯
]
while True:
    for r, g, b in Illumination:
        a_led.rgb(r, g, b)
        c_led.rgb(r, g, b)
        utime.sleep(1)
```

タネを明かすと、これが、タイトルに書いた『同じに扱う』ということでした。
下のURLをクリックすると、WOKWI シミュレータが開きます。 ![rgbled4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/8061afa5-b5ef-8cdd-d74f-142e69b73c80.png)をクリックすると、コードを実行してRGBLEDが光る様子を見られます。

https://wokwi.com/projects/400622865743012865



# PWM制御

こちらも、アノードコモンとカソードコモンの差を吸収するために、クラス化しました。
MicroPythonでは、PWMのでデューティ比を0〜65535で指定するため、256階調にするために、$\bigl(\frac{65535}{255}\bigr)$≒256倍します。

```python
from machine import Pin, PWM
import utime

class RGBLED:
    def __init__(self, red_pin, green_pin, blue_pin, anode_common=False):
        self.red = PWM(Pin(red_pin), freq=200)
        self.green = PWM(Pin(green_pin), freq=200)
        self.blue = PWM(Pin(blue_pin), freq=200)
        self.anode_common = anode_common

    def pwm(self, r, g, b):
        r = int(r * 65535 / 255)
        g = int(g * 65535 / 255)
        b = int(b * 65535 / 255)
        if self.anode_common:
            r = 65535 - r
            g = 65535 - g
            b = 65535 - b
        self.red.duty_u16(r)
        self.green.duty_u16(g)
        self.blue.duty_u16(b)

a_led = RGBLED(28, 27, 26, anode_common=True) #アノードコモン
c_led = RGBLED(3, 2, 1)                       #カソードコモン

Illumination = [
    (64, 154, 227),
    (128, 0, 128),
    (50, 150, 50),
    (255, 30, 30),
    (0, 128, 255),
    (99, 199, 0),
    (128, 128, 128),
    (255, 100, 0)
]
while True:
    for r, g, b in Illumination:
        a_led.pwm(r, g, b)
        c_led.pwm(r, g, b)
        utime.sleep(1)
```

下のURLをクリックすると、WOKWI シミュレータが開きます。

https://wokwi.com/projects/400624832297102337



実は、MicroPythonのPWMには `論理反転`する引数`invert`がはじめから用意されており、これを使うと、自力で`論理反転`する処理が不要になります。
この場合は、以下のコードとなります。

```python
from machine import Pin, PWM
import utime

class RGBLED:
    def __init__(self, red_pin, green_pin, blue_pin, anode_common=False):
        self.red = PWM(Pin(red_pin), freq=200, invert=anode_common)
        self.green = PWM(Pin(green_pin), freq=200, invert=anode_common)
        self.blue = PWM(Pin(blue_pin), freq=200, invert=anode_common)
        self.anode_common = anode_common

    def pwm(self, r, g, b):
        r = int(r * 65535 / 255)
        g = int(g * 65535 / 255)
        b = int(b * 65535 / 255)
        self.red.duty_u16(r)
        self.green.duty_u16(g)
        self.blue.duty_u16(b)

a_led = RGBLED(28, 27, 26, anode_common=True) #アノードコモン
c_led = RGBLED(3, 2, 1)                       #カソードコモン

Illumination = [
    (64, 154, 227),
    (128, 0, 128),
    (50, 150, 50),
    (255, 30, 30),
    (0, 128, 255),
    (99, 199, 0),
    (128, 128, 128),
    (255, 100, 0)
]
while True:
    for r, g, b in Illumination:
        a_led.pwm(r, g, b)
        c_led.pwm(r, g, b)
        utime.sleep(1)

```

下のURLをクリックすると、WOKWI シミュレータが開きます。

https://wokwi.com/projects/400826454683858945


# まとめ

自分が持っていない`コモン`タイプの制作例を真似するときは、`“反転”`するだけで使えるようになりました。