今回は、太陽光をエミュレートした照明を作ります。
太陽光のエミュレートと言っても、光のスペクトルを太陽光に合わせたりはできないので、エミュレートするのは「明るさ」だけです。太陽光のモデルは、だいぶ簡略化したモデルを使います。
用途としては以下の様な感じです。
日の出と日の入りの時刻は、国立天文台のHPで閲覧することが出来ます。
国立天文台HPを検索する(別ウインドウが開きます)
日の出/日の入り時刻は、地域によっても違うし、標高、季節、年でも変動します。以下図は、国立天文台HPから「一番下の一覧 ⇒ 一般の方向け ⇒ 暦計算ウェブサイト ⇒ 各地のこよみ ⇒ 東京」で調べた2021年の日の出/日の入り時刻をグラフ化した物です。
横軸に1年(2021年)の月日、縦軸に時刻(第一(左)軸)、と百分率(第二(右)軸)となっていて、日の出(青線)、日の入り(橙線)、太陽が真南に来る時刻を南中(赤線)としてプロットしています。昼%(緑破線)は、1日の中で昼(日の出から日の入りまでの時間)となる割合を百分率にしたものです。
日の出は太陽が水平線から顔を出す時刻で、日の入りは太陽が完全に水平線下に沈む時刻を指すため、元来、「昼」と定義される時間の方が、わずかに「夜」よりも長くなります。
よく観察すると、日の出が一番早くなる日付と、日の入りが一番遅くなる日付は少しずれていることが解ります。その間に、昼の長さが一番長くなる夏至の日があります。昼の割合(昼%)は夏至の日に最大となって、60%超が昼となります。
太陽光をエミュレートする時には、日の出〜南中〜日の入りの各時刻を知る必要があります。
計算で求めることもできるのでしょうが、調べるのが一苦労なので、上グラフのデータをテーブル化して使おうと思います。365日分のデータをテーブルにするのは全く無駄なので、各月毎に12個のテーブルにします。
12か月分のテーブルDT[12]を作ります。DT[12]には、各月初日(某月1日)のデータを記憶することとします。
この時、某月D日のデータを求めるには、以下式を使います。
DT(D日)=(DT[当月]×(M−D+1)+DT[次月]×(D−1))/M
…… ただし、M=当月の日数
Mは「西向く士」と、当年がうるう年の場合には、2月の日数を29日とすることで求められます。
上式は、当月のテーブル値と、次月のテーブル値を線形補間する式となっています。前節グラフの通り、日の出/日の入り時刻は比較的なだらかな曲線なので、月毎のデータを線形補間しても大きな違和感はありません。
なお、テーブルデータDT[12]は、整数型(int16)を使います。時刻は、当日午前0時からの累積分で表すこととします。
例)日の出時刻が6:51の場合
6時×60分/時+51分=411分
この方式だと、最大値が24×60−1=1,439分となりますので、int16で十分保持することが可能です。
なお、日の出/日の入り時刻等のデータは、年によって若干の違いがあります。地球の公転周期と1年の長さが一致していないことや、その他諸々の影響があるためだと思われます。日本の祝日である秋分の日と春分の日が毎年1〜2日前後することなどからもお解りでしょう。
しかし、その差はわずかなので、前節で参照した2021年東京のデータで代表して適用してしまいます。
つまり、時計に設定した年が1910年だろうと、2100年だろうと、2021年東京のデータを使うという事です。補間したり、簡易的なモデルを適用している今回のアプリケーションでは、十分合理的な制限だと思われます。
太陽が水平面から何度の高さにあるのか?を高度と呼びます。
南中時の高度については、前節で国立天文台から参照したデータ中に含まれています。南中時の高度も季節や年等で変化するので、日の出/日の入り時刻と一緒にテーブルにしておきます。テーブルに記憶する値は、南中時の高度[°]×10倍の固定小数とします。
例)南中高度=31.4°の場合
記録するデータ=314 …… int16で十分記録可能。
南中時以外の時刻における太陽の高度は、以下のモデルで求めます。
太陽の動きを簡略化して円運動にモデリングしているので、天文学的で正確なエミュレートではないことに留意してください。
太陽光は、地球の大気で吸収・散乱されることで色を持ちます。波長の短い光ほど、大気での吸収・散乱が大きくなります。このため、青い光が大きく減衰して、次に緑の光が減衰します。最後まで減衰し難くて残るのが赤い光です。
日の出/日の入り時刻の周辺では、太陽光が通過する大気の層が厚くなるため、青・緑が減衰して橙色〜赤色の光に見え、朝焼けや夕焼けが見られます。
この辺の吸収・散乱モデルもまじめに計算するのは大変なので、単純に、以下の様な「色」としてユーザが定義可能としておきます。
日の出時刻の色: Cr(R,G,B)
南中時刻の色: Cm(R,G,B)
日の入り時刻の色: Cs(R,G,B)
R,G,Bは0〜255まで256段階の強度で、0で真っ暗、255で最大輝度となるような色と定義します。この色情報は、今回照明として使用する円形配置RGB LEDの発光強度に使用します。
また、Cr,Cm,Csは、太陽が天頂に位置したと仮定した時の色を表すこととします。
本来、太陽が水平線近くにある場合、地面(≒水平面)に対して角度が浅いため、地面を照らす光は暗くなります。南中時には太陽が高い位置に上っているので、明るくなります。
この明暗については、別途太陽の高度を計算した上でモデルによって計算するため、本来赤道直下でしかあり得ない、太陽が天頂にあったと仮定した場合の色をCr,Cm,Csで指定します。
これにより、時刻によって太陽光の色が変化します。現在時刻Tにおける太陽光の色Cは、以下のモデルで計算します。
時刻Tにおける太陽の高度をθとする。
日の出時刻T1では、θ=0°
南中時刻T2では、θ=南中時高度(テーブルから参照する)
日の入り時刻T3では、θ=0°
南中前の時刻(T<T2): C=(1−sinθ)×Cr+sinθ×Cm
南中後の時刻(T≧T2): C=(1−sinθ)×Cs+sinθ×Cm
面倒なら、Cr=Cm=Cs=白色光(かつ同じ明るさ)としておくと、朝焼け・夕焼けがエミュレートされないだけで、一通り機能します。発光色を制御できない照明機器を使う場合も同様です。
まず、今回照明として使用する円形配置RGB LED(下図)の設置方法を固定します。
太陽は東から昇って西に沈むので、時間帯によって光が入射する角度が変化します。今回は、明るさだけをエミュレートしますので、明るさ(光の強さ)を制御し易い様に、明かりを照らす対象物に対して、真上から光を照射することとします。
部屋の照明で言えば、シーリングライトの様な天井灯に相当します(下図)。
この場合、必要なのは太陽の高度θの場合の、鉛直方向の強度L(R,G,B)となります。
L=C×sinθ
円形配置RGB LEDを強度Lで光らせたら目的達成となります。
円形配置RGB LEDに搭載されているのはWS2812BというRGB LEDチップが24個です。WS2812Bの制御方法については「#11-2 円形配置RGB LED(WS2812B)を使う」で解説してあります。
WS2812Bは5V定格なので、今回は久々にArduino UNOを使います。
以下の様にして、時計機能を実装します。時計機能は「#5-9 JJYのエミュレーション」でも実装しているので、そちらもご参照ください。
時計の実装:
ここで、日カウンタclk_dyのオーバフロウ判定には、「その月の日数」が必要となります。
月の日数:
一番最初の割り込みに戻って、100ms間隔の割り込みに必要なコンペアマッチレジスタ値Aは、0x61a7=24,999 @(fcpu/64)です。Aを変動させると、1秒の長さを変更することが可能となります。
例)A=249に設定すると
割り込みは1ms毎に発生して、時間は×100倍速で進む。
……結果として、1日(24時間)が14分24秒に短縮される。
Aの下限値には制限を持たせておきます。Aを小さくし過ぎると、割り込み頻度が高くなり過ぎるためです。割り込み頻度の最高値を1ms間隔と決めると、上の例で示したA=249のケースが下限値となります。
一方、上限値は?と言うと、コンペアマッチレジスタAは16ビット幅なので、設定可能な最大値はA=65,535であり、この時の割り込み周期=(A+1)/(fcpu/64)≒262msとなります。元々100msで割り込みをかけていたところ、それが262msに引き伸ばされるので、時間が262ms/100ms=2.62倍遅く進みます(1日が約2.6日に引き伸ばされる)。
この様に、時を引き延ばす側については×2.6倍程度と、あまり自由度は無いのですが、必要十分でありましょう。
以下に完成したプログラムをリンクしておきます。
完成プログラム:sun_light.ino
コンパイルしてUNOに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
シリアルモニタから受け付けるコマンド仕様:
【mset】コマンドで現在時刻を設定するまでは、機能が停止しています。【mset】コマンドを実行すると、その時刻の太陽に合わせた明るさで円形配置RGB LEDが光ります。
Arduino UNOと円形配置RGB LED、電源モジュールを以下表の様に結線してください。
Arduino UNO | 接続 | 円形配置RGB LED | 接続 | 電源モジュール |
5V | ← | 5V | ||
GND | ⇔ | GND | ⇔ | GND |
D3(OC2B) | → | DI | ||
× | DO |
円形配置RGB LEDは照明用として高輝度で光らせると、ものすごく電流を喰います。円形配置RGB LEDへ供給する5Vは、UNOの5Vピンソケットからではなく、大電流の供給が可能なACアダプタなどの電源から供給してください。
以下、実験中の様子です。
円形配置RGB LEDですが、本当は照明対象物の真上に設置します。上図の状態は、LEDの光らせ方をデバッグするための配置となっています。
電源には、写真の様に、ACアダプタ(9V)から5Vに降圧するタイプの電源モジュールを使いました。手元の電源モジュールには、レギュレータチップAMS1117(マークより推定)が載っていて、データシートによると出力制限電流Ilim=900mA(MIN)〜1,100mA(TYP)〜1,500mA(MAX)のスペックが張られています。ギリギリですが、何とかなるでしょう(?)。
実験サンプル:
【mset,2021,9,23,5,40,0】
OK: mset 2021/09/23 05:40:00
【time】
OK: 2021/09/23 05:40:12
2021年秋分の日(9/23)の5:40に時刻を合わせました。この日の東京の日の出時刻は5:29なので、円形配置RGB LEDがうっすら光るはずです。
イヤ、嘘。実はWS2812Bは「暗く光る」ことは不得意なので、最低輝度である"1"の値で光らせても、かなり明るいです。そこで、24個のピクセルを持つ円形配置RGB LEDの特徴を活かし、24個の組み合わせで明るさを制御しています。他のパラメータはデフォルトのままで上の時刻に設定すると、円形配置RGB LEDのWS2812Bが飛び飛びに数個のピクセルだけ光るはずです。
時間が経つと、太陽が昇るのに合わせて、だんだん光るピクセル数が増えて行き、最終的には全体が光ります。
【ssec,10】
OK: ssec=10[ms]
時間を縮めて、日の出〜日の入りまでの明るさの変化を確かめてみましょう。上の設定では、1秒が10ミリ秒に相当するため、×100倍速で時間が進んで行きます。日の出〜日の入り12時間ほどが7分とちょっとで経過します。
以下、動画にしてみました。
動画にするには7分はまだ長すぎたので、上の動画は更に時間を短縮してあって、日の出から日の入りまで1分弱です。
【colr,64,32,16】
OK: colr=(64, 32, 16)
【colm,64,64,64】
OK: colr=(64, 64, 64)
【cols,64,32,16】
OK: colr=(64, 32, 16)
日の出/日の入り、南中時の色指定コマンドを使って、デフォルトの色のまま、輝度を上げてみました。円形配置RGB LEDは、とにかく電流を喰うデバイスなので、輝度を上げて明るく照明する場合には消費電流にご注意ください。
手元の環境だと、輝度値を64以上に上げると不安定になって、円形配置RGB LEDが消灯してしまったり、UNOにリセットがかかってしまうことがありました。消灯したのは、レギュレータチップAMS1117の電流制限に引っかかって、OFFしてしまったものと推定しています(計算上はまだ余裕があるはずなのだけど……)。
被写体を照らしてみた映像がこちら(↓)です。
朝焼け感や夕焼け感をうまく出すことは出来ませんでした……。太陽が傾いて全体的に暗くなる時間帯なので、LEDの発光色だけを赤色に設定しても暗くなる方が勝ってしまって、うまく色が出ないようです。もう少し研究が必要かも?です。
久しぶりにUNOで工作してみました。油断していたらRAMが枯渇して警告が出ていました。RAM、2Kバイトしか搭載してないのね。C言語だと、つい、32ビット演算に頼ったり、float型使ってみたりと、気にせずプログラミングできるのも、ちょっと過保護に思ってしまいました(1^−^;)。