今回はシリアルRGB LED(WS2812B)を使います。今まで手持ちのWS2812B搭載モジュールだと、下図左側の円形配置RGB LEDモジュールがありました。
今回新たに上図右側のRGB LEDパネルを入手しましたので、これを使います。
今回のテーマは、上図の様なRGB LEDモジュールに使われているWS2812BというRGB LED素子を「暗く光らせる」ことです。
長さ5.5cmほどの小さなモジュールです。シリアルRGB LED(WS2812B)が8個並んでいます。
WS2812Bは、シリアル通信でRGBの各輝度を指定して光るLED素子です。端子は電源、GND、DI、DOの4種類です。改めて写真(下図)を見てみると、裏面の写真向かって左側からシリアルデータDIが入って来て、右側からシリアルデータDOが出て行く構成です。
※電源端子に「4-7VDC」と言うシルク印刷が見えますが、手元のWS2812BデータシートではVDD=3.5〜5.3[V]が規定されていました。「4-7VDC」は嘘っぽい……。
円形配置RGB LEDに比べてピクセル数が少なく、手ごろな8ピクセルが一列に並んでいるので、ちょっとした用途に使い易いかな?と思って購入しました。
複数のRGB LEDパネルがある場合は、DOと次段RGB LEDパネルのDIをカスケード接続すると、ピクセル数を増やして使用可能となります。
※円形配置RGB LEDと同じWS2812Bが使われているため、RGB LEDパネルのDOを円形配置RGB LEDのDIにカスケードすることも可能です。
基板のフットプリント👣にピンヘッダをはんだ付けして、使い易くしておきましょう。詳しくはこちらをご覧ください。
WS2812Bの制御方法については、「#11-2 円形配置RGB LED(WS2812B)を使う」に説明がありますので、省略します。
改めて、WS2812Bの特徴についてまとめておきます。
明るいです。最低輝度の1でも、部屋の中で使うには十分な明るさです。昼間の太陽の下で表示に使う場合等、255まで輝度を上げられるのはGoodです。
ただ、明るすぎる……。
最低輝度の1でも十分に明るいため、「ぼんやり暗く光らせる」ことが出来ません。今回は、この部分を対策しようと思っています。次節以降で検討します。
WS2812Bは、「電圧を加えると、流れた電流に応じた明るさで光る」ような、アナログ制御(※)ではなく、シリアル通信で発光色強度をデジタルに設定する素子です。通信方法については、「#11-2 円形配置RGB LED(WS2812B)を使う」の記事でご確認ください。
※デジタル回路が入っているため、電源電圧を低下させたり、PWMでON/OFF制御したりすると、デジタル回路が正常動作しなくなってしまいます。
本節で注目しておくのは、通信時間です。
今回使うRGB LEDパネルは8ピクセルなので、通信時間はtRES+1.25μs×8ビット×3原色×8ピクセル=290μsです。次節で使うので頭に入れておいてください。
普通のLEDをPWMで駆動すると、PWMのデューティに従って、明るさが変化します。本HPでも度々使っています。Arduino UNOを使う場合は、analogWrite()関数でPWMを出力できるので、これを使うことが多いです(下図上側、バイナリPWM)。
WS2812Bはデジタル制御なので、PWM波形を接続して明るさを制御することはできません。そこで、デジタルPWMとでも言うべき方法を使ってみようと思います。
上図下側(デジタルPWM)が、その様子です。
WS2812Bの定格電圧がVdd=3.5〜5.3[V]なので、5V定格のArduino UNOを使って実験します。
UNOの16ビットタイマユニット(TC1)を使い、PWM↑のタイミング検出にオーバフロウ割り込みOVF、PWM↓のタイミング検出にコンペアマッチ割り込みOC1Aを使います。
TC1のカウンタは0〜255の8ビット、カウントクロックはfcpu/1,024とします。この時、PWM周期は16,384μsで、デューティは0〜255の間で設定可能となります(設定コード部分は↓)。
// TC1 setting
// OC1A pwm output, fast pwm 8 bit mode 5, fcpu/1024, TOIE1, OCIE1A
TCCR1A=0x81;
TCCR1B=0x08 + 0x05;
noInterrupts ();
TIMSK1=0x03;
TIFR1=0x03;
OCR1A=255;
interrupts ();
analogWrite()関数が出力するPWM周期は2,048μsなのですが、今回は敢えて少し長めの周期(256/(fcpu/1,024)=16,384μs)を選択してあります。WS2812Bへのデータ送信にtRES+1.25μs×8ビット×3原色×8ピクセル=290μsが必要となるため、長い周期の方が有利だったからです(→こちらもチェック!)。
もう一度概念図を示して、詳細に検討を進めます。
バイナリPWMと異なり、WS2812Bはシリアル信号線によるデジタル通信が必要で、データを送信するのにtRES+1.25μs×8ビット×3原色(RGB)×8ピクセル=290μsの時間がかかります。その様子が、図中「transfer」欄の通信a,b,cなどとして示してあります。
PWM↑とPWM↓のタイミングで発生した割り込み処理ルーチン内で、WS2812Bにデータを送信(約290μs)するため、実際にON/OFFするタイミングは、バイナリPWMの変化点から少し遅れます。
PWMのデューティを小さくしていくと、通信aと通信bが接近して、さらに小さくすると重なってしまいます。重なっても同時に通信(処理)することはできないため、ぴったりくっついてしまったところが最小のデューティ値となります。
デューティが大きくなる側でも同様に、通信bと通信cがくっつくところがデューティの最大値となります。
このままだと、デューティ0でも完全には消灯されず、デューティ255でも最大輝度に到達しないことから、各割り込みサービスルーチン(ISR)内で互いに相手の割り込みフラグをチェックして、自身の処理を実行すべきかどうか、判定することとしました(↓)。
ISR (TIMER1_OVF_vect)内の処理: OFF(OC1A)優先
// check OCF1A
if (OCR1AL<128 && (TIFR1&0x02))
return;
// led on
rgb_send ();
ISR (TIMER1_COMPA_vect)内の処理: ON(OVF)優先
// check TOV1
if (OCR1AL>=128 && (TIFR1&0x01))
return;
// led off
rgb_send_all0 ();
この処理によって、デューティ0では消灯、デューティ255では最大輝度で光る様になります。
WS2812Bは明るく光る分、消費する電流も大きくなっています。明るさと電流の消費量については、こちらに実験結果があります。
今回のデジタルPWMでは、非常に短い周期(16,384μs)でWS2812BがON/OFFを繰り返すため、WS2812Bへ流れる電流値が増/減します。電流の増/減によって、電源ラインへノイズが発生し、UNOやその他周辺回路のデジタル信号に悪影響を及ぼして、誤動作の原因(EMSノイズ)となる場合があります。
また、電流の増/減によってEMIノイズも発生するため、そちらのノイズ対策も必要となります。
各自、適切に電源設計およびノイズ設計してお使いください。
※本ページの様に、個人の電子工作として楽しむ分には、あまり気にしなくても動けばOKです(1^_^)。念のため、パスコンを配置しておいたら十分だと思います。
以下に、完成したプログラムをリンクしておきます。
完成プログラム:digital_pwm.ino
UNOへコンパイル&書き込むと、シリアルモニタから以下コマンドを受け付けるようになっています。
シリアルモニタコマンド仕様:
Arduino UNOとRGB LEDパネルを以下の様に接続してください。
Arduino UNO | 接続 | RGB LEDパネル |
5V | → | VCC(4-7VDC) |
GND | ⇔ | GND |
D3(OC2B) | → | DI |
× | DO |
※この他、RGB LEDパネルのVcc-GND間にパスコンを接続しました。また、D9(OC1A)端子にバイナリPWMを出力してあるので、ここにLEDを接続すると、RGB LEDパネルの明るさ設定(【duty】コマンド)に合わせて光ります。
以下図に実験中の様子を示します。
実験サンプル:【 】内はシリアルモニタからの送信データ
【pix,-1,1,1,1】
OK: pixel #8=(1, 1, 1)
上のコマンドによってRGB LEDパネルが最低輝度RGB=(1,1,1)で光ります。これがWS2812B本来の最低輝度で、これより暗く光ることはできません。ずいぶん明るいでしょう?
【pix,-1,4,4,4】
OK: pixel #8=(4, 4, 4)
輝度を×4倍に上げました。さっきより明るく光りましたか?最大255まで上げることが出来るので、まだまだ明るく光らせることが出来ます。
でも、今回のテーマは、暗く光らせることです。
【duty,16】
OK: duty=16/255
デューティを16に設定しました。これで輝度(明るさ)が×16/255=約1/16倍に低下した筈です。最初のRGB=(1,1,1)に対して、×4倍のRGB=(4,4,4)が、更に×1/16倍なので、都合×4/16=1/4倍の暗さで光っています。
デューティを小さくして行くと、もっと暗く光ります。ただし、【duty,1】以下では完全に消灯してしまいました。割り込みOVFとOC1Aがほぼ同時に発生することで通信時間(約290μs)が確保できなくなって、消灯処理が優先されたためです。
最後に、デモを実行してみましょう。
【demo】
OK+ RGB=(1,1,1)
OK+ RGB=(L,L,L)
OK+ gradually darken
OK+ fire fly
OK+ traffic signal
OK+ blue
OK+ yellow
OK+ red
OK+ knight rider
OK: demo finished
デモの内容を以下の通り説明します。
デジタルPWMを使って、テーマであった「暗く光らせる」ことが必要となるアプリケーションをデモンストレーションしてみました。
スマホのカメラで、前節のデモ動画を撮影したところ、デジタルPWMによるRGB LEDパネルの表示が点滅して写ってしまいました。
少し長めに設定したデジタルPWMの周期16,384μs≒61Hzが、フルモーション(30FPS)ビデオのフレームレートに近くてかつ、ほぼ整数倍の値となっているためと考えられます。カメラなどで撮影することを考えると、もう少し短いPWM周期に設定した方が良かったみたいです。
UNO(ATmega328)の16ビットタイマ(TC1)を使う場合、現在使っているカウントクロックfcpu/1,024を、もう一段速くするとfcpu/256となって、周期4,096μsとなります。
WS2812Bへのデータ送信時間290μsに対して周期が短くなることで、発光可能な最小デューティ値が上昇しますが、そこを許容できるのであれば選択肢の一つです。
リンクした動画は、スマホカメラのシャッタ速度を調整して、点滅しないように撮影したものです(PWM周期は16,384μsのまま)。
今回のお買い物は、以下の通りです。
SPI-TFTなどと一緒に購入しておいた部品です。
今回のデジタルPWMを使うと、円形配置RGB LEDを使った「#11-6 LED暖炉」や、「#18-6 お日様照明」等のアプリケーションも、もう少し違ったアプローチが出来たかもしれません。