あっ、LEDキャンドル(蝋燭)を×24倍に拡大したら、だいぶ明るくなってしまったので、LED暖炉(またはLED焚き火)と言う事で、着地させてみました。
暖炉の炎🔥には、心をリラックスさせる効果があるみたいです。キャンプをした時の焚き火の炎とかもそうですけど、何も考えずにぼ〜っと眺めていると、時間が経つのを忘れてしまいますよね。実際には、一瞬として同じ形になることの無い炎の形を眺めることとか、薪がパチッと音を立てながら燃える様とか、炎から感じられる心地よい温かさとか、そう言う物が諸々セットになってのリラックス効果なのだろうな、とは思います。
今回作るLED暖炉では、その内の「炎の瞬き」だけをエミュレートするので、ちょっと、諸々足りないかも?です。
あと、形が形だけに、暖炉とか焚き火と言うよりも、ガスコンロの炎の方がピッタリマッチしてしまいます……。開き直って、それはそれでまたをかし。
「#4-6 LEDキャンドル」では、炎の揺らめきや瞬きを表現するため、1/fゆらぎ乱数列を使って、RGB LED(1個、以下図)の明るさを制御しました。
この時は、RGB LEDの明るさをRGB LEDに流す電流の平均値で制御するため、Arduinoからデューティを制御したPWMを出力して、駆動しました。
今回は、円形配置RGB LEDモジュール(下図)を使って、LEDキャンドルを×24倍化しようと思います。
「#11-2 円形配置RGB LED(WS2812B)を使う」の様に、円形配置RGB LEDを構成するWS2812Bというチップ(単体のRGB LED)は、デジタル信号で0〜255の数値を送信すると指定した明るさで勝手に光ってくれるので、PWM波形を作って駆動する必要がありません。
1/fゆらぎ乱数列とは、周波数fに反比例(1/f)する頻度を持って変化する乱数列の事を指します。周波数に関係するということは、時間軸に縛られているということの裏返しなので、作成した1/fゆらぎ乱数列(rand_1_f[ ]テーブル)は、以下の様な条件を満たして使わないと、1/fゆらぎ乱数になりません。
「#4-6 LEDキャンドル」で作成した4,096個の乱数列rand_1_f[ ]テーブルは、1系列の乱数列だということです。1系列なので、1個の炎のエミュレートにしか使用できません。つまり、LEDキャンドル(蝋燭1本分)が関の山。
順当に考えると、Excelを使って複数の1/fゆらぎ乱数列(テーブル)を生成して使うということになるのでしょう。
ただ、こちらをご覧になって実際に生成してみるとわかりますが、Excelを使ったフォワードとインバースFFT(4,096点)にはめちゃくちゃ時間がかかります。しかも、4,096点のuint8配列は4Kバイトのメモリ領域を占めます。PROGMEMを使ってFlashメモリ上に領域を確保するのを基本としても、Arduino UNOではFlashメモリは32Kバイトしかありませんので、複数持つにしても、限度があります。
今回、RGB LED(WS2812B)が24ピクセルあるからと言って、24系列の1/fゆらぎ乱数テーブルを確保しようとすると、4Kバイト×24ピクセル=96Kバイトと、全くメモリが足りません。
そこで、以下の様な手法を使って、1/fゆらぎの特徴を残したままで、複数の乱数系列に見えるよう、偽装して用います。
1)〜3)は独立して設定可能なので、一様乱数を使ってどのパタンを使うかを選択して使います。
1/fゆらぎ乱数テーブルrand_1_f[ ]は4,096個の乱数を収めたテーブルなのですが、FFTによって作り出したテーブルなので、最後のデータrand_1_f[4,095]と最初のデータrand_1_f[0]は繋がっています。
つまり、テーブル[0]から[1]、[2]、……と参照して行って、[4,095]でオーバフロウした後に再び[0]に戻って[1]、[2]、……と続け
ても、1/fゆらぎを崩すことなく、使用することが出来るようになっています。ひっくり返すと、この乱数テーブルrand_1_f[ ]は、4,096と言う回数で繰り返しています。
今回は24ピクセルのRGB LEDについて、更にRGB各原色毎に発光強度を制御したいと思っているので、都合、24ピクセル×3原色=72系列の1/fゆらぎ乱数列が必要となります。
上の記憶領域をまとめると、(2バイト(rgb_ptr)+2バイト(rgb_ofs)+1バイト(rgb_cnt))×24ピクセル×3原色=360バイトの配列が必要となります。
「#11-2 円形配置RGB LED(WS2812B)を使う」の通り、WS2812Bへのデータ送信用に確保するデータ領域が、24ピクセル×3原色×(1バイト(ピクセルバッファ)+8バイト(デューティバッファ))=648バイトなので、合わせて約1Kバイトと、Arduino UNOのRAM容量の半分ほどが埋まる計算ですが、まあ、何とかなるでしょう。
前節で検討した1/fゆらぎ乱数テーブルの参照方法を手順に落とし込んでおきます。
※実はこの手順だと、乱数テーブルが1周(4,096個参照)した時に乱数系列を変化させてしまっているため、この瞬間だけは「1/fゆらぎ」から外れてしまっています。……見なかったことに……。
以上の様に、1系列の1/fゆらぎ乱数列を複数系列の乱数列に偽装して使用するために、一様乱数random()関数を使うので、randomSeed()関数を使って乱数の種を指定しておく必要があります。
ユーザI/Fのあるアプリケーションで、「スタート」ボタンとか、何らかのトリガを外部から取得可能な場合には、トリガがかかった時点のmicros()関数値などを乱数の種にすることか可能です。
randomSeed (micros ());
今回はそのような外部からのトリガが存在せず、Arduinoが起動してそのまま動作を開始するので、
void setup (void)
{
randomSeed (micros ());
}
等としてしまうと、毎回micros()関数が似たような値を返してしまい、ランダムになりません。
そこで、「#6-10 真の乱数を生成する」で作ったtrue_random()関数を使って、Arduinoのフローティングアナログ端子からランダムな挙動を取り込んで、乱数の種を取得します。
const int ana_TRAND=0; // noise source
void setup (void)
{
randomSeed (true_random ());
}
定数ana_TRANDにフローティング状態になっているアナログ端子番号を指定しておく必要があります。ここでは、アナログ端子A0を乱数ソースとして使うような設定としてありますので、A0端子をVccに吊ったりGNDに落としたりしないようにしてください。
さて、全ピクセルを1/fゆらぎ乱数で瞬かせてみたのだけど、全くそれらしく見えませんでした……。それはそうですよね?暖炉や焚き火の炎は、ロウソクをN個灯したのとは違いますから(1^^;)。LEDキャンドルを×24個並べただけでは、単にロウソクを24本立てただけ……、基本的な事でした。やって見るまでわからなかったなんて。
「炎っぽい」モデリングは、この両極端の間にあると考えてみました。そこで、暖炉の炎、薪などを燃やした時の炎を思い浮かべてみたところ、以下の様な特徴を抽出しました。
全体としては、1つのLEDキャンドルの様な、方向性の同じ乱数系列を使って、少しだけ、個々のランダム性が見られるのが良いのではないか?
例えば、基本的には全てのピクセルを同じ乱数系列で光らせます。その開始タイミングを、個々に一様乱数でばらつかせれば、全体として1つの大きな炎でありながら、部分的には異なる瞬きをすることで、大きな1つの炎をエミュレートできるのではないか?と考えました。
一様乱数で開始タイミングを変化させてしまうことで、時間が経過すると全てがバラバラのタイミングになってしまい、最終的に「24個のLEDキャンドル」になってしまうのではないか?と言う心配はあったのですが、実際にやって見ると、そうはなりませんでした。
random()関数の返す乱数が一様なら、全てのピクセルが平均値でタイミングがずれて行くので、全体としては平均値を中心とした正規分布となるはず。つまり、完全にバラバラになって、「24個のLEDキャンドル」にはならない、と言う事みたいです。
円形配置RGB LEDの最大の問題点は、「明るすぎる」ことです。これに尽きる。炎は確かに、明るく回りを照らすけど、蛍光灯や電球ほどの明るさは無いので、辺りをぼんやりと照らす程度にとどまります。これに対して円形配置RGB LEDは明るすぎます。
円形配置RGB LEDに搭載されているWS2812BというLEDチップは、デジタル信号で0〜255までの数値を送信すると、それに対応した明るさで光ってくれるので、1/fゆらぎ乱数列から計算した明るさをWS2812Bへ送信したら終了です。
だけど、あまりに明るすぎるので、最大輝度を4とか5とかに抑えてしまうと1/fゆらぎ乱数列で変化する範囲が0〜5程度に制限されてしまって、瞬きの自由度が小さすぎます。
最大輝度をもう少し明るくして、1/fゆらぎ乱数による明度の変化量を大きくしたい。
そこで、円形配置RGB LEDを何か半透明な物質で覆って明度を抑えると共に、物質の乱反射によってピクセル同士の光を適切にミキシングする効果も狙ってみようと思い立ちました。
本当は、何か難燃性の半透明な物質(例えばすりガラス)で手軽に使えるものが手元にあれば良かったのですが、丁度良いものが見当たりませんでしたので、筆者は紙を使いました。
WS2812B自体は、明るく光らせた状態で指で触れてみても、ほとんど熱を感じませんので、大丈夫だとは思いますが、念のため、WS2812Bと紙を接触させない、熱がこもらない様な工夫をする、事に気を付けてみてください。
紙を透過する分、RGB LEDの明るさは減衰するので、256段階の100くらいで明るく光らせたとしても、紙の厚さなどでちょうど良い光量に調整することが出来ます。色の付いた紙を使うと、LEDの発光色に加えて紙の色でも光の色合いを調整することが出来ます。
炎には色々な色が有ります。どのような色の炎であってもエミュレートできる様、RGBの発光強度を個別に制御可能にしておきたいと思います。RGB各発光強度の上限値rgb_max<RGB>および、下限値rgb_min<RGB>で制御します。
例えばこの時、発光強度は、rgb_min+(rgb_max−rgb_min)×rnd_1_f/255等となります。
WS2812Bは発色が綺麗なので、赤〜黄色っぽい暖色系にかけての炎の色を綺麗に再現できるでしょうし、敢えて青白い色に制御してガスコンロなどの炎をエミュレートするのも良いかもしれません。炎に拘らず、ゆらゆらと揺れる光を見つめることで心が落ち着くなら、緑の若葉が萌ゆるイメージも良いですね。
さて、色だけでなく、試行錯誤の過程で見出した、いくつかの炎のモデリングが有ったので、その内のいくつかをプログラムから設定して光り方を変えられるようにしておきます。
完成したプログラムをリンクしておきます。
完成プログラム:led_fire.ino
コンパイルしてUNOに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
コマンド仕様:
円形配置RGB LEDとArduino UNOを以下ネットリストに従って接続してください。
Arduino UNO | 接続 | 円形配置RGB LED |
5V | → | 5V |
GND | ⇔ | GND |
D3(OC1B) | → | DI |
× | DO |
以下、実験中の様子です。
実験サンプル:【 】内はシリアルモニタからの送信データ
【mod,0】
OK: mode 0
今回のプログラム(led_fire.ino)は、起動時の発光強度が0に設定されているので、そのままだと円形配置RGB LEDは光りません。【mod】コマンドでいずれかのプリセットモードに設定するか、【col】コマンドで発光強度を手動設定すると、その時点で光り始めます。
【col,0,0,0】
OK: color (0~0, 0~0, 0~0)
【col】コマンドは、RGB LEDの発光色を制御するコマンドですが、RGB三原色全てを0に設定すると、円形配置RGB LEDを強制的に消灯することが出来ます。
【mod,1】
OK: mode 1
【mod,2】
OK: mode 2
↑等、実験中の様子を動画に落としておきました(↓)。
切り込みを入れた紙のシェードは、炎に見立てて揺らめかせるため団扇であおぎました……。
動画で見ると、ちょっと色合いが白っぽく見えているのですが、実物はもう少し橙〜黄色っぽい色になっています。色合いに関しては、実物でご確認いただく方が良いかと思われます。
筆者手元の円形配置RGB LEDモジュールに関しては、「#11-2 円形配置RGB LED(WS2812B)を使う」にて、消費電流値を計測済みなので、【col】コマンドで設定する発光強度から消費電流値を概算して、USB給電可能な範囲内で使用しました。
モジュールの個体差が多少はあるはずですので、各自、お手元のモジュールで電流値を計測するか、もしくは筆者計測結果を使うのであれば、十分にマージンを持って電源設計してお使いください。
特にLED行灯モードなど間接照明として使おうとすると、どうしても明るさを上げたくなると思います。くれぐれも、余裕を持った電源設計をするようにしてください。
LEDキャンドル → LED暖炉と、言う事で、単純に「キャンドルを×24倍化したら終わり?」と、舐めていたのだけれども、そうはいきませんでした。そりゃあ、そうか……。