目次
はじめに
ESP32-C3-DevKitM-1 上のLED(写真の基板の左側・青緑点灯)は、WS2812互換のデジタル通信インタフェースを搭載しています。フルカラーを再現できるメリットのある反面、通常のLEDのように、GPIOのHレベルで点灯、Lレベル出力で消灯といった制御が出来ません。
本開発ボード上のLEDの点灯させるには、R(赤)、G(緑)、B(青)のそれぞれのLED素子の輝度値をデジタル信号で送信する必要があります。本稿では、その制御方法について解説します。
デジタルPWM変調
このLEDに搭載されているWS2812互換デジタル通信インタフェースは、デジタルPWMの変調方式に対応しています。PWMとは、パルス幅変調のことで、入力値に応じたパルス幅を情報として付与する方式です。
下図では、入力値が0のときに幅300nsのパルスを、入力値が1のときに幅600nsのパルスを送信します。
ここでPWMをご存知の方は、少し疑問を感じたかもしれません。PWMには、例えば256段階などの多値の入力によるデジタル→アナログ変換のような使い方も多く、また、LEDの輝度の調整にも使われているからです。
本稿のLEDでは、PWMをデジタル→アナログ変換用に使うのではなく、WS2812互換のデジタル通信インタフェースとして使う点に注意してください。情報をパルス幅に重畳させる原理が同じPWMであって、使い方は異なります。
なお、パルス位置で情報を示すPPMとも言います。また、周期が一定で4等分の位置で情報を示す場合はPSKと呼びます。WS2812のデータシート記載の図例ではPSKのようにも見えますが、非パルス(Lレベルの)区間の仕様から、デバイスの実装上はPWMまたはPPMです。
300nsへの挑戦
幅300nsのパルスを生成するには少なくともGPIOを1.7MHzで制御しなければなりません。GPIO制御による通信インタフェースとしては、高速な方です。とはいえ、1ビットにつき必ず1パルスが含まれるので、パルス幅の精度や伝送速度の精度は、あまり厳しくありません。速度とLED側の受信回路規模の簡略化を両立させた方式と言えるでしょう。
制御するESP32マイコン側も、正確なパルス幅である必要はありません。とはいえ、GPIO制御で300nsのパルス幅を生成するには工夫も必要です。ここでは、パルス幅の待ち時間を空ループで生成し、その空ループの回数は、100回分の空ループが要する所要時間から求めるようにしました。
下図は、PWM/PPM波形の測定結果の一例です。普段、使わない高速な信号のため、プローブの調整を失念していました。
デジタルPWM変調部のプログラム
下記リストは、引数r、g、bに、それぞれ、赤、緑、青のLED素子の輝度値(0~255)を入力したときに、WS2812互換デジタル通信インタフェース用の送信信号を生成する関数です。THがPWM/PPM変調のパルス幅です。TLはパルスのあとの待ち時間です。WS2812では580ns~1000nsの範囲内と定められています。後半のwhile文が、パルス持続時間を生成する空ループ処理部です。
void led(int r,int g,int b){ _led_reset(); volatile int TH, TL; uint32_t rgb = (g & 0xff) << 16 | (r & 0xff) << 8 | (b & 0xff); noInterrupts(); for(int b=23;b >= 0; b--){ if(rgb & (1<<b)){ TH = T1H_num; TL = T1L_num; }else{ TH = T0H_num; TL = T0L_num; } digitalWrite(PIN_LED,HIGH); while(TH>0) TH--; digitalWrite(PIN_LED,LOW); while(TL>0) TL--; } interrupts(); }
空ループの回数を求める事前処理部
上記プログラムの空ループ回数は、プログラム起動時に、測定します。とはいえ、Arduinoのタイマー関数microsだと、1μs単位でしか測れず、600nsを測定するだけの分解能がありません。そこで、目的の空ループを、さらに100回、繰り返すのに要する時間を求め、100で除算しました。
下記リストは、待ち時間(ns)から空ループの回数を求める関数です。
int _led_delay(int ns){ volatile uint32_t i; uint32_t target, counts=0; delay(1000); noInterrupts(); delay(1); do{ i = ++counts; target = micros() + ns / 10; while(i>0) i--; }while(micros() < target); interrupts(); return (counts + 50)/100; }
もうひと工夫
以上の処理で目的のデジタルPWM/PPM変調が得られるはずでしたが、2点の誤算がありました。一点目は、while関数を実行時の遅延時間です。初期化時に実測して減算するようにしました(2021/7/8更新)。
もう一つの誤算は、WS2812の仕様です。ESP32-C3-DevKitM-1に実装されているSK68XXMINIとWS2812との間に若干の違いがありました。とはいえ、もともと広い仕様なので、ほとんど違いはありませんし、どちらの仕様を適用しても問題なく動作します。
なお、本稿ではPWMと書きましたが、プログラムではPSKっぽくしてみました。オシロスコープをお持ちの方は、そのあたりも確認してみて下さい。
3種類のサンプル・プログラム
この仕組みを利用した3種類のサンプル・プログラムを用意しました。
led01_onoff.ino
LEDのON/OFFを繰り返すLチカ用プログラムです。ちょっとだけ色を付けさせていただきました。プログラムの内容が理解出来たら、色を変更してみて下さい。
https://github.com/bokunimowakaru/esp32c3/tree/master/led_ws2812/led01_onoff
led02_dimmer.ino
LEDの輝度が心臓の鼓動のように明暗を繰り返すプログラムです。周期は、一般的な心拍数に合わせました。プログラムの内容が理解出来たら、少し早くしてみて下さい。自分で改造したプログラムを実行するときは、心拍数も上がるはずです。
https://github.com/bokunimowakaru/esp32c3/tree/master/led_ws2812/led02_dimmer
led03_color.ino
フルカラーLEDの特長を活かし、12色の色相を連続で変化させるプログラムです。同時に、輝度も変化させ、最大輝度のときに30°の倍数の位置になるようにしました。0°の前後(赤紫→赤)の変化がぎこちない気がします。理由をご存知の方は教えていただけないでしょうか?
https://github.com/bokunimowakaru/esp32c3/tree/master/led_ws2812/led03_color
おわりに
WS2812互換の通信インタフェースにLEDの制御をRISC-V搭載の最新ESP32マイコンで行ってみました。また、デジタルPWM/PPMやGPIOによる通信インタフェースについても説明しました。手間はかかりますが、フルカラーによる表現力を手に入れた喜びを感じていただければ幸いです。
なお、Adafruit NeoPixel というライブラリを利用しても、同じようなことが出来ます。WS2812で最も使われているライブラリであり、そもそもWS2812を広めたのがAdafruit社だと言っても過言ではないでしょう。
関連記事
by bokunimo.net
「RISC-V搭載ESP32ボード上の WS2812で手間のかかるLチカ」への4件の返信
[…] RSIC-Vを搭載した 最新 ESP32-C3 のベンチマーク評価 RISC-V搭載ESP32ボード上の WS2812で手間のかかるLチカ […]
プログラムで、WS2812のシリアル信号を作ってしまうとは、大変ですね。
自分は今、マイコンラズパイpicoにもてあそばれています、この苦労を見て、PICOのPIOで、WS2812を動かすと、picoの良いところ(簡単に動かせる)を実感できる気がします。
今は、いろいろなやり方が多すぎて、目が回り気味です。
このような情報をどんどん公開してもらえると嬉しいです。
コメント、どうもありがとうございます。
NeoPixelをダウンロードすれば済む話ですが、自分で作ると動作原理(の一部)が分かるようになります。
LEDの点灯ひとつでも、「少し回り道して仕組みを理解する」といった楽しみを、記事で体験してもらえればなぁと、思いました。
もちろん、Picoでも、似たような遠回りネタを考えています。そのうちブログか紙で提供したいと思っています。
[…] RISC-V搭載ESP32ボード上の WS2812で手間のかかるLチカ […]