【電気・電子】Raspberry PiでRGB LEDを点灯させて、PWM信号を勉強しました

電気・電子

 先回は、Raspberry Piとキットを使ってLチカ(LEDをチカチカ点灯させること)をしました。Lチカでは、制限抵抗(器)を入れてLEDが壊れないように電流を調整する必要があることを知りました。

 今回は、RGB LEDを点灯させる課題です。1個のLEDでR(赤)、G(緑)、B(青)の3つの色を発光させるLEDを使います。RGBというのは光の三原色と言われています。この3つの光の色で色を表現する方法です。

 この光の三原色の図を一度は見たことがあると思います。赤と緑の重なった部分は黄に、緑と青の重なった部分はシアン、赤と青の重なった部分はマゼンタ、赤、緑、青が重なったところが白になります。また、光のない部分は黒になります。

 よく、デジタルテレビで約1677万色の色を再現できます、などの文言を聞いたり見たりしたことがあると思います。光の三原色では、RGBとも単色として表現していますが、RGBの各色を0~255の値(デジタル)で表し、256諧調で表現するとその組み合わせが256×256×256=16,777,216色となり、上述の約1677万色になります。例えば、白色は、(R、G、B)=(255、255、255)になり、黒色は、(0、0、0)となり、紫色は、(125、0,125)となります。

 数字だけではイメージが浮かばないかもしれませんね。RGBの値を指定するとその色を示してくれるものがあります。Windowsに付属しているペイントツールでも確認することができます。

RGB LEDについて

 さて、今回使用するRGB LEDについて確認してみます。

 今回のRGB LEDは、赤、緑、青に発光する3つのLEDが1つのパッケージに格納されていて、3本のマイナス電極(カソード)を1本の電極に繋げて共用(コモン)しています。これをカソードコモンタイプといいます。本来なら6本の端子が必要なところをカソードを共用(コモン)にすることで2本減らすことができて、1つのパッケージにすることができています。

 ちなみに、プラス電極(アノード)を共用(コモン)にしたアノードコモンタイプ(上図)もあります。なんか不思議に見えますね。これだと常に3つとも3.3Vの電圧がかかっているので点灯しっぱなしに見えますね。アノードコモンタイプの場合、マイナス電極(カソード)側の電圧を変化させることで1つひとつ点灯を制御することができます。例えば、R(赤)だけ点灯させたいときは、1ピンをLow(0V)にして、3,4ピンをHigh(3.3V)にしてあげます。電流は電圧の高いところから低いところに流れるので、2→1ピンに電流が流れて点灯しますが、2→3ピン、2→4ピンは、電圧がどちらも3.3Vなので電流は流れず点灯しません。(図では、抵抗器を省略しています。)

RGB LEDのLチカ

 RGB LEDをRaspberry PiのGPIOにつなげるための回路図です。R(赤)を点灯させるには、GPIO17ピンをHigh(+3.3V)にしてあげます。同様にG(緑)ならGPIO18 ピンをHigh(+3.3V)に、B(青)ならGPIO27ピンをHigh(+3.3V)にすれば、それぞれ点灯させることができます。

 RGB LEDの点灯の組み合わせを表にしてみました。「1」は端子をHigh(+3.3V)にして点灯し、「0」は端子をLow(0V)にして消灯することを表します。全て「0」の場合を便宜上、消灯を意味する黒としています。黒く光るわけではないです。これは、光の三原色の色の組み合わせを表していることがわかります。

RGB Lチカの回路の構築

 Raspberry Piのブレッドボードに回路図通りにRGB LED、抵抗器(220Ω)3個とジャンパーケーブルを使って配線しました。ジャンパーケーブルの色をLEDの色のピンに合わせました。

RGB Lチカのプログラム

 前回のLチカのプログラムを流用して、Pythonプログラムを作成しました。

import RPi.GPIO as GPIO
import time
# 辞書型でピン定義
pins = {'Red':17, 'Green':18, 'Blue':27}

def setup():
    global p_R, p_G, p_B
    GPIO.setmode(GPIO.BCM)
    # 全てのピンをHIGH(3.3v)出力で初期化する
    for i in pins:
        GPIO.setup(pins[i], GPIO.OUT, initial=GPIO.HIGH)

def setRGBLed(v_R,v_G,v_B):
    # 各ピンに指定の値(1(HIGH) or 0(LOW))を指定してHIGH時に点灯させる
    GPIO.output(17, v_R)
    GPIO.output(18, v_G)
    GPIO.output(27, v_B)

    print("R = %d, G = %d, B = %d"%(v_R, v_G, v_B))

def main():
    while True:
        setRGBLed(1,0,0) #赤
        time.sleep(1.0)
        setRGBLed(0,1,0) #緑
        time.sleep(1.0)
        setRGBLed(0,0,1) #青
        time.sleep(1.0)
        setRGBLed(1,1,0) #黄
        time.sleep(1.0)
        setRGBLed(1,0,1) #マゼンタ
        time.sleep(1.0)
        setRGBLed(0,1,1) #シアン
        time.sleep(1.0)
        setRGBLed(1,1,1) #白
        time.sleep(1.0)
        setRGBLed(0,0,0) #黒
        time.sleep(1.0)

def destroy():
    # GPIOをクリーンアップする
    GPIO.cleanup()

if __name__ == '__main__':
    setup()
    try:
        main()
    except KeyboardInterrupt:
        destroy()

 このプログラムは上述のRGB LEDの点灯組み合せ表の通り、3つのピンに1か0の値を設定して1秒ごとにLEDを光らせています。値が1の色を光らせて、2つ以上値が1の場合に色が合成されて光ります。

 プログラムを実行させて、ターミナルにRGBの値(赤枠部)を変化させて動画のようにLEDが光ります。

 RGB LEDを使って7色を点灯させることができました。はて?どうやって1677万色もの色を光らせることができるのでしょうか?

1677万色を光らせてみる

 1677万色というのは、256×256×256=16,777,216色で求められました。R、G、Bのそれぞれを0~255の階調で光らせればよいことになります。先ほどは、R、G、Bの各LEDに対して0(0v)、1(3.3v)にして消灯と点灯を切り替えました。LEDの特性では、順方向電流の大きさによって輝度が変わります。順方向電流が小さいと暗く、大きいと明るくなります。この0~1を0~255に分解してあげればよさそうです。

 図にしてみました。R(赤)の明るさを半分の125にするには、電圧を1.62vにしてあげる感じです。ところが、GPIO(汎用入出力ポート)はデジタル信号の出入り口で、ピンの状態を高電圧(’HIGH’ または’1’)または低電圧(’LOW’ または’0’)にしかできません。そのため、1.62vという中間の電圧にすることができません。

 そこで、疑似的にアナログな中間の電圧を作り出す手法があります。それがパルス幅変調(PWM)信号による手法で、PWM(Pulse Width Modulation)信号は、デジタル信号を用いてアナログ信号をエミュレート(模倣)する手法です。PWMでは、一定の周期でオンとオフを繰り返す信号が生成されます。この周期のうちオンの時間(デューティサイクルまたはデューティ比)を調整することで、平均的な電圧や電流を制御します。例えば、デューティサイクルが50%なら信号は半分の時間オン、半分の時間オフとなり、平均的には供給電圧の50%に相当する出力となります。PWMはモーター制御やLEDの明るさ調整などに使用されます。

 平均的な電圧というのは、図では実効電圧を指します。人間の目では200Hz(ヘルツ)以下、周期でいうと0.005(=1/200)秒以上のオン、オフはフリッカー(ちらつき)として認識されます。また、デジタルカメラやビデオの撮影では、カメラのフレームレートとPWM周波数の関係で、低い周波数のフリッカーは映像に干渉し、ちらつきが発生することがあります。したがって、フリッカーを回避してLEDを調光するには1000Hz以上のPWM周波数(周期では0.001秒)が推奨されています。商業用やプロフェッショナルな照明では、さらに高い数キロヘルツ(例えば10kHz)を使用することもあります。

 ちなみに、LEDを照明に使用する場合、フリッカーは健康に影響するため、以下のフリッカー規則が示されています。
経済産業省「電気用品安全法」施行令別表第二第9号(2012):繰り返し周波数が100Hz以上で光出力に欠落部がない又は 繰り返し周波数が500Hz以上
IEEE 1789-2015 “Recommended Practices for Modulating Current in High-Brightness LEDs for Mitigating Health Risks to Viewers”(視聴者の健康リスクを軽減するための高輝度 LED の電流変調に関する IEEE 推奨プラクティス)

 最初に戻って、PWM信号を使って1.62vの電圧を出力するための条件を整理します。
 PWM周波数は、フリッカーを抑えるために1000Hzとする。デューティサイクルは49%(=125/255×100)となります。これにより、実効電圧は1.62v(≒3.3v×0.49)になります。

PWM信号による1677万色 RGB Lチカのプログラム

 PWM信号による1677万色を発光させるRGB LチカのPythonプログラムを作成しました。

import RPi.GPIO as GPIO
import time
# 辞書型でピン定義
pins = {'Red':17, 'Green':18, 'Blue':27}

def setup():
    global p_R, p_G, p_B
    GPIO.setmode(GPIO.BCM)
    # 全てのピンをHIGH(3.3v)出力で初期化する
    for i in pins:
        GPIO.setup(pins[i], GPIO.OUT, initial=GPIO.HIGH)
    # GPIOのPWM周波数を1000Hzに設定する
    p_R = GPIO.PWM(pins['Red'], 1000)
    p_G = GPIO.PWM(pins['Green'],1000)
    p_B = GPIO.PWM(pins['Blue'], 1000)
    # GPIOのPWM信号を開始する
    p_R.start(0)
    p_G.start(0)
    p_B.start(0)

# デューティサイクルを算出する(0~255の値を0~100%に換算する)
def calcDutyCycle(x, in_min, in_max, out_min, out_max):
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

# 色値を設定する
def setColor(R_val, G_val, B_val):
    # 色値(0~255)をデューティサイクル(0~100)に変換する
    R_val = calcDutyCycle(R_val, 0, 255, 0, 100)
    G_val = calcDutyCycle(G_val, 0, 255, 0, 100)
    B_val = calcDutyCycle(B_val, 0, 255, 0, 100)

    # PWM信号のデューティサイクルを変更して色値を設定する
    p_R.ChangeDutyCycle(R_val)
    p_G.ChangeDutyCycle(G_val)
    p_B.ChangeDutyCycle(B_val)

    print ("R = %.2f  G = %.2f, B = %.2f"%(R_val, G_val, B_val))

def main():
    while True:
        # 1677万色すべてを点灯させる
        for B_val in range(256):
            for G_val in range(256):
                for R_val in range(256):
                    setColor(R_val,G_val,B_val)
                    time.sleep(1.0)

def destroy():
    # PWM信号を停止する
    p_R.stop()
    p_G.stop()
    p_B.stop()
    # GPIOをクリーンアップする
    GPIO.cleanup()

if __name__ == '__main__':
    setup()
    try:
        main()
    except KeyboardInterrupt:
        destroy()

 このプログラムでは、R、G、Bの各色に対して0~255の値を設定して、256×256×256のループを回してRGB LEDを点灯させていくものです。GPIOライブラリには、PWM信号用のメソッドが用意されているので簡単に点灯させることができました。見やすくするために1秒間ずつ点灯させていますが、1677万色を表示しきるには約194日間かかってしまいます。ふぅ~。

        for B_val in range(0,256,80):
            for G_val in range(0,256,80):
                for R_val in range(0,256,80):
                    setColor(R_val,G_val,B_val)
                    time.sleep(1.0)

 なので、0~255を80スキップずつして1分程度で回せるようにしました。

 絞りが甘いのでLEDが明るくなりすぎています。壁に反射した色を見ていただくとよいかと思います。

まとめ

 今回は、RGB LEDのLチカを行いました。単純にONするだけでは7色しか表現できませんが、PWM信号を使うことで1677万色から好きな色を点灯させることができました。また、PWM信号は、デジタル信号を用いてアナログ信号をエミュレートすることができ、LEDの調光だけでなくモーター制御などにも使えることがわかりました。

タイトルとURLをコピーしました