google-site-verification: google3bd66dd162ef54c7.html
FC2ブログ

ArduinoでLEDの光量を滑らかに変化させる

 水草水槽のLED照明をタイマーを使って自動的にコントロールしたいと思っています。単純なON/OFFでは面白くないので、光量を滑らかに変化させてやることにします。真っ暗な状態からいきなり100%で点灯させると、水槽内の生物の健康に悪いかも知れません。また白色と赤色のLEDがあるので、パワーの比率を変えることで調色を行い、夜明けや夕暮れ時の雰囲気を作ってみたいと思います。

 ハードは以前作ったソーラータイマースイッチを改造するとして、これにいきなりLEDの制御プログラムを組み込むのはデバッグが大変になってしまいます。ということで、まずはLEDのコントロール部分だけのプログラムを作ってみました。

▼テスト中
LEDのPWM点灯テスト

▼回路図
LED調光テスト回路
 Arduino UNOを使い、白と赤の二つのLEDを抵抗を通して接続しただけです。

 作成したプログラムは以下の通りです。これだけでデモプログラムとして動くようになっています。
// LEDの明るさを滑らかに変化させるテストプログラム
// 明るさを指数変化させることで人間の感覚に合わせた。
// 新しい明るさと変化時間を指定すれば自動的にPWMでLEDの明るさを調節
// 2017/10/18 ラジオペンチ http://radiopench.blog96.fc2.com/
// 20171018LedAnalogDriveTest

unsigned long LC = 0; // ループカウンタ

float WhiteNow = 0.003; // 0.003は最小値 1/255=0.0039→0.003
float WhitePow = 0.0; // 白LED明るさ設定値
float WhiteSlope = 1.0; // 白LED明るさ変化率

float RedNow = 0.003; // 最小値指定
float RedPow = 0.0; // 赤LED明るさ設定値
float RedSlope = 1.0; // 赤LED明るさ変化率

int WhitePin = 5; // 白LEDピン(PWM可能ピンのみ指定可能)
int RedPin = 6; // 赤LEDピン(   〃        )

void setup() {
Serial.begin(115200);
}

void loop() {
timerDemo1(); // 動作パラメーター設定(demo1,demo2あり)
WhiteLedOut(); // 白LED制御ルーチン
RedLedOut(); // 赤LED制御ルーチン
LC++; // ループカウンタインクリ
Serial.flush();
delay(10); // ループ回転速度調整(Demo1=10,Demo2=1)
}

void WhiteLedOut() { // 赤色LED、指定光量まで指定変化率で明るさを変える
int WhiteBin; // 赤LEDのポートバイナリ設定値

WhiteBin = WhiteNow * 255; // ポート設定値計算
analogWrite(WhitePin, WhiteBin); // PWM出力

WhiteNow = WhiteNow * WhiteSlope; // 次回の光量を計算次回の光量を計算(slopeの値に従い毎回更新)
if (WhiteSlope > 1.0) { // 上昇中で
if (WhiteNow > WhitePow) { // 目標値を超えていたら
WhiteNow = WhitePow; // 目標値で止める
}
}
if (WhiteSlope < 1.0) { // 降下中で
if (WhiteNow < WhitePow) { // 目標値より下なら
WhiteNow = WhitePow; // 目標値で止める
}
if (WhiteNow < 0.003) { // 下限は
WhiteNow = 0.003; // 0.003で抑える(1/255=0.0039)
}
}
// Serial.print(LC);Serial.print(", ");
Serial.print(WhiteBin);
}

void RedLedOut() { // 赤色LED、指定光量まで指定変化率で明るさを変える
int RedBin; // 赤LEDのポートバイナリ設定値

RedBin = RedNow * 255; // ポート設定設定計算
analogWrite(RedPin, RedBin); // PWM出力

RedNow = RedNow * RedSlope; // 次回の光量を計算(slopeの値に従い毎回更新)
if (RedSlope > 1.0) { // 上昇中で
if (RedNow > RedPow) { // 目標値を超えていたら
RedNow = RedPow; // そこで止める
}
}
if (RedSlope < 1.0) { // 降下中で
if (RedNow < RedPow) { // 目標値より下なら
RedNow = RedPow; // 目標値で止める
}
if (RedNow < 0.003) { // 下限は
RedNow = 0.003; // 0.003で抑える(1/255=0.0039)
}
}
Serial.print(", "); Serial.println(RedBin);
}

void WhiteSet(float x, float y) { // 白LEDの動作パラメーター設定(明るさ, 通過サイクル数)
if (x > 1.0) { // 上限をクランプ
x = 1.0;
}
if (x < 0.003) { // 下限をクランプ
x = 0.003;
}
WhitePow = x; // 明るさ設定値
WhiteSlope = pow(WhitePow / WhiteNow, 1.0 / y); // 係数 = 全変化率^(1/サイクル数)
}

void RedSet(float x, float y) { // 赤LED動作パラメーター設定(明るさ, 通過サイクル数)
if (x > 1.0) { // 上限をクランプ
x = 1.0;
}
if (x < 0.003) { // 下限をクランプ
x = 0.003;
}
RedPow = x;
RedSlope = pow(RedPow / RedNow, 1.0 / y); // 係数 = 全変化率^(1/サイクル数)
}

// 以下は動作テスト用のデーター
void timerDemo1() { // 動作デモ1(タイマー割込み周期=10msで実行)
if (LC == 0) RedSet(1.0, 50); // 100%まで 0.5秒
if (LC == 50) RedSet(0.0, 100); // 0%まで 1 秒
if (LC == 100) RedSet(0.5, 20); // 50%まで 0.2秒
if (LC == 200) RedSet(0.0, 200); // 0%まで 2 秒

if (LC == 20) WhiteSet(1.0, 100); // 100%まで 1 秒
if (LC == 150) WhiteSet(0.0, 20); // 0%まで 0.2秒
if (LC == 180) WhiteSet(0.2, 20); // 70%まで 0.2秒
if (LC == 300) WhiteSet(0.0, 80); // 0%まで 0.8 秒

if (LC > 400) {
// while (1);
LC = 0;
}
}

void timerDemo2() { // アクアリウム照明の模擬パターン (割込み周期を1msにすると1000倍速でデモ)
if (LC == 14400) { // 4:00
RedSet(0.2, 600); // 600秒かけて赤20%(朝焼け)
}
if (LC == 16200) { // 4:30
RedSet(0.1, 600); WhiteSet(0.1, 600); // 600秒かけて赤白10%点灯(夜明け待ち)
}
if (LC == 19800) { // 5:30 
RedSet(1.0, 600); WhiteSet(1.0, 600); // 600秒かけて赤白フル点灯(日中)
}
if (LC == 63000UL) { // 17:30
RedSet(0.1, 600); WhiteSet(0.1, 600); // 600秒かけて赤白10%点灯(夕暮れ、消灯待ち)
}
if (LC == 73800UL) { // 20:30 
RedSet(0.2, 600); WhiteSet(0.0, 600); // 600秒かけて赤20%、白消灯(真っ赤な夕焼け)
}
if (LC == 75600UL) { // 21:00
RedSet(0.0, 600); // 600秒かけて全消灯(夜間)
}
if (LC > 86400UL) { // 24:00
LC = 0;
}
}
プログラムのポイント
・ちょっと長いですが、100行目までがメインのプログラムで、以降はデモ用の点灯パターン定義です。

・PWMで使用出来るピンには制限があるので、その中から選びます。他にも制約条件があることがあるので要注意。

・メインのループは24から31行までで、単純な構造になっているので判り易いはずです。

・メインループの中でWhiteLedOut() 33行とRedLedOut() 57行を毎回呼び出して光量の設定を行っています。デモプログラムなので、ループを回るスピードは30行目のdelay(10)でやっていますが、ここはタイマー割り込みなどにした方がいいでしょう。もちろんこのままでも大丈夫です。なおループを回るスピードがLED点灯プログラムの時間単位になっています。

・発光パターンは102行目以降のtimerDemo1()で指定していますが、ポイントはRedSet()とWhiteSet()で、この関数の中で、明るさの変化率を計算しています。なお、PWMの値は0-255の整数しか使えずこれでは細かい変化が表現出来ないので、明るさは浮動小数点で計算・記憶し、最後に整数化することでPWMを行っています。

・明るさの変化はべき乗で計算(pow関数)していますが、消灯状態をゼロで表現すると計算がやっかいになります。そこで浮動小数点で 0.003という値にして誤魔化しています。この値は255倍しても1以下なのでLEDが点灯せず、事実上ゼロと見なすことが出来ます。このアイディアでプログラムの構造が少し簡単になりました。

▼Demo1のLEDの光量変化
LED点灯カーブ(指数変化)
 これはArduino IDEのシリアルプロッタに書いたLEDの光量変化グラフです。ちゃんと指数で変化しています。また変化中に新しいプログラム設定が入れば、それに従った変化になっています。

◆まとめ
 ArduinoでPWMを使ってLEDの明るさを変化させることはよく行われていますが、この記事のように指数変化(等比級数)させている例は少ないと思います。もしPWMのパラメーターを一定値で変化(等差級数)させた場合、光量が少ない時は明るさが大きく変わり、光量が大きくなるとほとんど明るさが変わらないように見えます。これは人間の感覚が刺激を対数圧縮しているためですが、これを打ち消してあたかも自然な光量変化をしているように見せるためには、この記事のように指数変化させる必要があります。

 これでシンプルなインターフェイスでLEDの光量をプログラムできるようになりました。今後は、ソーラータイマースイッチに組み込んでアクアライトコントローラーに作り替えて行きたいと思います。ただ、このプログラムは増築を重ねた旅館のようにグチャグチャの構造になっているので、修正はかなり面倒そうです。地道にやっていきたいと思います。

関連記事

コメントの投稿

管理者にだけ表示を許可する

カレンダー
08 | 2018/09 | 10
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 - - - - - -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード