この記事は、Arduino Advent Calendar 2020 - Qiitaの17日目の記事です。
さて、今年もクリスマスシーズンがやってきましたね🎄
今日は、クリスマスといえば欠かせない、キャンドルにまつわる自由研究の成果についてご報告します。
ことの経緯
LEDキャンドルにゆらぎ機能を追加したい
先日Aliexpressで、なかなか造形が良くて、とても安いLEDキャンドルを見つけました。
何に使おうと思ったんだか忘れてしまったんですが、ともかくこれを3つ買っていました。
実物も写真通りの可愛さですが、LEDは点滅しないタイプでした。
点滅しないのもそれはそれで良いんですが、LEDで炎のゆらぎを再現する技術には前々から興味があったので、ゆらぎ機能を自作してみようと思います。
予備知識
LEDキャンドルの種類について
さて、LEDキャンドルにも色々な種類があります。
僕が知っているのはこの4種類で、今回買ったのは①のタイプ、今回作るのは②のタイプです。
※名前は勝手に付けてみました。
①固定点灯タイプ
- 今回買ったのがこれ、要はただのLEDランプ、形だけキャンドルになっているタイプです。
- ゆらぎが邪魔になる用途の場合はこれもアリ。
②調光ゆらぎタイプ
- 一番よく見かけるタイプです、雑貨屋や100円ショップでも売ってます。
- LED自体にICが内蔵されている、自己点滅タイプのLEDを使っているものが多い印象です。
- akizukidenshi.com
- むかし、COBのIC使ったタイプのLEDを外してスピーカーを繋いでみたら、中国民謡みたいな音楽が流れたことあるけど、もうないかなぁ。
③ルミナラ社タイプ
- ディズニーランドのホーンテッドマンションのキャンドルとして有名な、おそらく今一番リアルなLEDキャンドルです。
- 特許公報で詳しい仕組みが見られます。
- patents.google.com
- ルミナラ社の特許は何本か出ているのでひととおりよんでみまたんですが、もうLEDキャンドルやり尽くされてるなって思いました。作るのが目的じゃなければ、買っちゃったほうがいいと思います。
④マトリックスアニメーションタイプ
- これはキャンドルというより、焚き火です。円筒状に並んだLEDマトリックスで、炎のアニメーションを表示するタイプです。すごいメラメラするので、けっこう使い所を選びます。
さらに、②のタイプの中でも、明るさの変化にランダム値をつかうもの、一定のパターンをループするものなど、いろんな種類があります。
今回は「間欠カオス法」で「1/fゆらぎ」を再現するという手法を使ってみます。
1/fゆらぎと間欠カオス
1/fゆらぎとは、ろうそくの炎や星の瞬きをはじめとした色々な自然現象に見られるゆらぎです。
おおざっぱに言うと、明るさがランダムに変化し、明滅が速くなるほど光量が小さくなります。
このゆらぎを作るのに、簡単な計算で1/fゆらぎを作り出せるという、「間欠カオス」という方法がよく使われるそうです。
式の出典を探してみたものの、見つけられなかったんですが、式のかたち的にテント写像の兄弟のようなものだと思います。
ガンマ補正について
あと、アナログ値でLEDを光らせると言えばこれ、人が見た時に光量の変化が一定になるよう、ガンマ補正をかけておきます。
ガンマ補正の掛け方については、この記事でやってた通りです。
実験
Arduinoで試作します、以降写真はM5StickCとDigisparkが入り混じりますが、アナログ出力が2ch取れるマイコンなら、なんでも大丈夫です。
途中のソースコードは説明用の抜粋版です。実際動かせるコードは記事の末尾に載せておきます。
光量0〜100%のゆらぎ
まずは間欠カオスの計算結果を、そのまま0〜256にマップして、アナログ出力してみました。
if (value < 0.5) { value = value + 2.0 * value * value; } else if (value >= 0.5) { value = value - 2.0 * (1.0 - value) * (1.0 - value); } analogWrite(led_pin, pgm_read_byte(&(gamma8[255 * value])));
その結果がこちら。
変化量が大きいうえ、消灯する瞬間もあるので、まるで風前の灯火という感じです。
キャンドルのケースを被せてみるとこう。風前の灯火という感じ。 pic.twitter.com/crkgt1NqcM
— アツユキ (@aaa_tu) 2020年11月23日
ホラーな演出にはちょうど良いかもしれませんが、ちょっとクリスマス向きではありません。
光量80〜100%のゆらぎ
そこで、明るさを80〜100%に拘束して、暗くなりすぎないように調整してみます。
if (value < 0.5) { value = value + 2.0 * value * value; } else if (value >= 0.5) { value = value - 2.0 * (1.0 - value) * (1.0 - value); } output_value = 205 + (value * 50); analogWrite(led_pin, pgm_read_byte(&(gamma8[output_value])));
そこで、明るさがゼロにならないように拘束したのがこちら。よくあるLEDキャンドルはこんな感じかな?
— アツユキ (@aaa_tu) 2020年11月24日
しかしまだ風が吹いてる感がある。 pic.twitter.com/Zjk4AtOPPc
どうでしょうか、さっきより大人しい印象になりました。よく見るLEDキャンドルはこんな感じですよね?
仮説と検証
なぜ不自然に見えるのか
しかし個人的には、この光り方にもまだ不満がありました。
ここまでの実験から、そもそも、定点で光量だけが変わるってところに、不自然さを感じるんじゃないかという仮説を立てました。
いまのゆらぎを、実際のキャンドルに当てはめると、このようになると思います。
なんだか、見るからに不自然な印象です。
対して、さきほど立てた仮説をもとに考えた、自然なゆらぎがこちら。
光量の変化ではなく、光源の揺れを表現できれば、より自然な炎に見せることができるはずです。
そのためにはさて、どうするか。
逆位相のLEDを追加しよう
そして思いついたのがこの方法です、LEDをひとつ追加して逆位相で光らせることによって、擬似的に光源の揺れを作り出す案です。
アナログ出力を2つ使い、明るさの変化を反比例させてみます。
int value_1 = max_value - dimming_range + (value * dimming_range); int value_2 = max_value - (value * dimming_range);
そして、その結果がこちら。
明滅のリアリティもさることながら、影のゆらぎがすばらしい。良いものができた。
— アツユキ (@aaa_tu) 2020年11月24日
以上、昨晩の進捗でした。 pic.twitter.com/9XYXRjAmNb
LEDを1つ追加しただけですが、炎っぽさが格段に増していると思いませんか?とくに影のゆらぎが素晴らしい。
簡単な工夫ですが、予想以上の効果を出すことに成功しました!
ATtiny85で作る
最終的にはキャンドル型のケースに納めたいので、8ピンの小さなマイコン、「ATtiny85」に移植します。
同サイズでリーズナブルなATtiny13とか、さらに小さいATtiny10とかもありますが、僕のスキルではコードサイズを収められなかったので85を使います。
ATtinyをArduino環境で使うには
Arduino環境でATtinyシリーズを開発する方法は、ここを参考にしました。
書き込みには、自作キーボード用にストックしてあったArduino pro micro互換機を、Arduino as ISP化して使います。
材料
- ATtiny85×1個
- 暖色のLED×2個
- 黄色やオレンジ、お好みで。
- 100Ω程度の電流制限抵抗×1個
- 常に明滅するので、これくらい入れておけばたぶん大丈夫、気になる人はちゃんと計算したほうが良いと思います。
回路図
ソースコード
// ガンマテーブル const int PROGMEM gamma8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175, 177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255 }; const int led_pin_1 = 0; const int led_pin_2 = 1; float value = 0.1; const int max_value = 255; const int dimming_range = 90; // magic number const float threshold = 0.065; // magic number void setup() { pinMode(led_pin_1,OUTPUT); pinMode(led_pin_2,OUTPUT); } void loop() { if (value < 0.5) { value = value + 2.0 * value * value; } else if (value >= 0.5) { value = value - 2.0 * (1.0 - value) * (1.0 - value); } if (value <= (0.0 + threshold) || (1.0 - threshold) <= value) { value = random(0.0 + (threshold * 1000),1000 - (threshold * 1000)) / 1000.0; } int value_1 = max_value - dimming_range + (value * dimming_range); int value_2 = max_value - (value * dimming_range); analogWrite(led_pin_1, pgm_read_byte(&(gamma8[value_1]))); analogWrite(led_pin_2, pgm_read_byte(&(gamma8[value_2]))); delay(70); }
組み立て
試作なので、裏返したICの足に直接部品を載せていく、「デッドバグスタイル」で組み立ててみました。
完成
電源を電気二重層コンデンサにしてみた。
— アツユキ (@aaa_tu) 2020年11月27日
1Fで3分くらいしかもたないので要改良だけど、収まりはかなり良い感じ。 pic.twitter.com/CNp8DpJ3M0
今後の展望
- 縦揺れ対応
- 今回のは洋ロウソク風の横揺れなので、和ロウソク風の縦揺れバージョンも作りたい。
- センサーの追加
- 空きピンがアナログ入力に使えるので、少しインタラクティブな機能を追加したい。
それでは少し早いですが、メリークリスマス!
このゆらぎ回路をLEDランタンに組み込んだ話を書きました↓ www.creativity-ape.com