/* アクアライトコントローラー 機能 ・LEDをPWM制御することで滑らかに明るさ変化。指数関数変化による自然な明るさの変化を実現 ・2色(白と赤)のLEDの独立調光 ・季節による日の出・日の入り時刻の変化に自動追従して点灯。 ・日の出・日の入り時刻は各々±4.0Hrのオフセットが設定可能。(液晶画面から設定可) ・日の出前、日没後に弱く点灯させることでインテリアライトとして使用可能。(点灯時刻は液晶画面から設定可) ・消灯前に真っ赤に光らせるアクセントライト機能あり(プログラムにより調節可能) ・マニュアル調光機能付き ・設定値はEEPROMで記憶し、電源を切っても消えない。 ・日の出・日の入り時刻は日付と経緯度から計算。(経緯度の変更は再コンパイル) ・リアルタイムクロックRTC8564BNと秋月のI2C液晶(16文字2行)を使用 ・パラメーターの設定などの操作は液晶とタクトスイッチで可能。・RTC8564.hライブラリは要修正 2017/10/29版 ラジオペンチ http://radiopench.blog96.fc2.com/ */ #include // 数学ライブラリ https://www.arduino.cc/en/Math/H #include // Wireライブラリ #include #include // N. MitsunagaさんのI2C液晶ライブラリ http://n.mtng.org/ele/arduino/i2c.html #include // なんでも作っちゃうかもさんのRTCライブラリ http://arms22.blog91.fc2.com/blog-entry-232.html #define M_PI 3.14159265358979323846 // πの値(Arduinoの浮動小数点は32ビットなので7桁くらいしか使われない) #define DEG(a) ((a) * 180 / M_PI) // ラジアンを度に変換するマクロ #define RAD(a) ((a) * M_PI /180) // 度をラジアンに変換するマクロ I2CLiquidCrystal lcd(35, true); // コントラスト(0-63),液晶電源(true=5V, false=3.3V) char dtString[20] = "yyyy/mm/dd hh:mm:ss"; // テンプレート用文字列で初期化 byte RtcADR = 0x51; int DecB = 8; // −ボタン int IncB = 9; // +(Select)ボタンのピンNo. int EntB = 10; // EnterボタンのピンNo. int ManSW = 12; // マニュアル制御スイッチ int NightOutPin = 11; // 夜間固定出力ピン(T2からT1の間 HIGH) int LedPin = 13; // LED float T1; // 日の入り時刻(単位:時) float T2; // 日の入り時刻(単位:時) float Longitude = 139.7414; // 経度(東経を入力、値は東京) float Latitude = 35.6581; // 緯度(北緯を入力) int mNumNow; // 分で数えた現在時刻 int mNumT1, mNumT2; // 分で数えた日の出・日の入り時刻 int sNumNow; // 秒の値 (0-59) unsigned long sNumToday; // 本日の秒連番 (max=60*60*24=86400) int T1offset; // T1(日の出時刻)のオフセット(単位:分) int T2offset; // T2(日没時刻)のオフセット(単位:分) int FixOnTime; // 固定ONタイマー設定値(単位:分) int FixOffTime; // 固定OFFタイマー設定値(単位:分) int ManWhite, ManRed; // マニュアルモードの明るさ設定値(0.003から1.0) // LEDのアナログ点灯用変数 float WhiteNow = 0.003; // 0.003は最小値 1/255=0.0039→0.003 float WhitePow = 0.003; // 白LED明るさ設定値 float WhiteSlope = 1.0; // 白LED明るさ変化率 float RedNow = 0.003; // 最小値指定 float RedPow = 0.003; // 赤LED明るさ設定値 float RedSlope = 1.0; // 赤LED明るさ変化率 int WhitePin = 5; // 白LEDピン(PWM可能ピンのみ指定可能) int RedPin = 6; // 赤LEDピン(   〃        ) void setup() { pinMode(2, INPUT_PULLUP); // RTCの /INT信号を接続 RTC側はオープンドレインなのでプルアップ pinMode(NightOutPin, OUTPUT); // Night On 出力 (日没から日の出までHigh) // pinMode(TimerOutPin, OUTPUT); // プログラム出力 pinMode(LedPin, OUTPUT); // 動作表示LED pinMode(DecB, INPUT_PULLUP); // Decボタン pinMode(IncB, INPUT_PULLUP); // Incボタン pinMode(EntB, INPUT_PULLUP); // Enterボタン pinMode(ManSW, INPUT_PULLUP); // マニュアルスイッチ Serial.begin(115200); Serial.println("Aqua Light Controller V1.0"); readEEPROM(); // EEPROMから設定値を読み出す lcd.begin(16, 2); // 16文字2行のI2C液晶を使用 Rtc.begin(); Rtc.available(); // RTC convString(); // RTCの時刻(BCD)を読んで文字列に変換しdtStringに格納 calcT1T2(); // 日の出、日の入り時刻を計算(初期値用) if (digitalRead(EntB) == LOW ) { // 起動時にEntボタンが押されていたら dispLoc(); // 経緯度を表示 for (;;) { // 無限ループに入る(リセットで抜ける) } } if ((digitalRead(DecB) == LOW) && (digitalRead(IncB) == LOW)) { // 起動時にDecとIncが両方押されていたら lcd.clear(); lcd.print("Clock set mode"); while ((digitalRead(DecB) == LOW) || (digitalRead(IncB) == LOW)) { // DecかIncのいずれかが押されている間は待つ } delay(30); ClockSet(); // RTCの日付・時刻を修正 Rtc.available(); // RTC convString(); // 再度RTCの時刻(BCD)を読んで文字列に変換しdtStringに格納 calcT1T2(); // 日の出、日の入り時刻を再計算 } if (digitalRead(DecB) == LOW) { // 起動時にDecボタンが押されていたら solarTimerSetting2(); // ソーラータイマーのオフセット設定 } if (digitalRead(IncB) == LOW) { // 起動時にIncボタンが押されていたら fixTimerSetting2(); // Fixタイマー(スモールタイマー)の動作条件設定 } lcd.clear(); // 時計合わせ後などで表示が残っていることがあるためクリア lcd.print("Aqua Light Start"); // 開始メッセージ lcd.setCursor(0, 1); lcd.print("V1.0 2017/10/27"); // 2行目にバージョン表示 delay(1000); catchUpLedPow(sNumToday); // 現在時刻までプログラムを進める nightLED(); // ソーラータイマー出力(夜間だけ出力するタイマー) userTimer(); // 特別な動作が必要なら使う SetRtcTimer(); // NB8564の設定(1秒周期でINT発生) もっと前に設定する必要があるかも } void loop() { // メインループ ManualCont(); // マニュアル調整 waitPin2Low(); // RTCからの1秒パルスを待つ(割込み、スリープ無し) digitalWrite(LedPin, HIGH); // 動作中表示LED Rtc.available(); // RTC convString(); // RTCの時刻(BCD)を読んで文字列に変換しdtStringに格納 Serial.print(sNumToday); Serial.print(", "); // 現在の秒連番を出力 if ((mNumNow == 0) && (sNumNow == 0)) { // 日付が変わっていたら(分と秒がゼロだったら) calcT1T2(); // 新しい日付の日の出、日の入り時刻を計算 Serial.print("New T1,T2 = "); Serial.print(T1); Serial.print(", "); Serial.println(T2); } if (sNumNow == 0) { // 毎分0秒のタイミングで行う処理 nightLED(); // 夜間表示LED userTimer(); // 特別な動作が必要ならここを使う Serial.print(dtString); Serial.print(", "); Serial.println(mNumNow); } AquaLight(); // アクアライトコントロール disp(); // 液晶表示 Serial.flush(); // シリアルの送信が完了するまで待つ digitalWrite(LedPin, LOW); // 動作表示LED OFF } void catchUpLedPow(unsigned long m) { // LEDの出力を現在時刻の状態にセット for (unsigned long i = 0; i <= m; i++) { // 現在時刻(秒)まで LedContProgram(i, 0); // 照明制御プログラムを簡易モードで読む } WhiteNow = WhitePow; // 明るさ目標値設定(スロープは無視) RedNow = RedPow; WhiteLedOut(); // LEDの明るさ設定(初回分) RedLedOut(); } void ManualCont() { // LEDの明るさをマニュアルで設定 if (digitalRead(ManSW) == LOW) { // スイッチがマニュアルになっていたら以下を実行 lcd.clear(); lcd.print("Manual Power Set"); // マニュアル設定表示 lcd.setCursor(0, 1); lcd.print("W=NNN% R=NNN%"); // 2行目表示テンプレート lcdDispNNN(ManWhite, 4, 1); // 白LED現在値表示 analogWrite(WhitePin, ManWhite * 255 / 100); lcdDispNNN(ManRed, 11, 1); // 赤LED現在値表示 analogWrite(RedPin, ManRed * 255 / 100); while (digitalRead(ManSW) == LOW) { // スイッチが押されている間は ManWhite = lcdInputNNN(ManWhite, 4, 1, WhitePin); // 白LED設定と出力 ManRed = lcdInputNNN(ManRed, 11, 1, RedPin); // 赤LED } EEPROM.write(10, ManWhite); // マニュアルの設定値を保存 EEPROM.write(11, ManRed); catchUpLedPow(sNumToday); // プログラムの状態を最新に合わせる } } void AquaLight() { LedContProgram(sNumToday, 1); // 第二引数が1ならスロープまで計算、0なら出力だけセット WhiteLedOut(); RedLedOut(); // Serial.print(RedNow, 3); Serial.print(", "); Serial.print(RedSlope, 4); Serial.print(", "); Serial.print(RedPow, 3); Serial.print(", "); // Serial.print(WhiteNow, 3); Serial.print(", "); Serial.print(WhiteSlope, 4); Serial.print(", "); Serial.println(WhitePow, 3); } void disp() { // 運転中の液晶表示 if (digitalRead(IncB) == LOW) { // Incボタンが押されていたら dispFix2(); // 固定タイマーの設定状態表示 } else { if (digitalRead(DecB) == LOW) { // Decボタンが押されていたら dispSolar2(); // ソーラータイマーオフセット値表示 } else { // 何も押されていなければ dispClockNorm2(); // 時計表示、タイマー出力表示(1行目) dispSunRiseSet(); // 日の入り・日の出時刻表示(2行目) } } } void dispFix2() { // スモール点灯時刻表示 lcd.setCursor(0, 0); lcd.print("small ON --:-- "); // 固定文字表示 lcdDisp(FixOnTime / 60, 11, 0, 0); // 時 lcdDisp(FixOnTime % 60, 14, 0, 0); // 分 lcd.setCursor(0, 1); lcd.print(" OFF --:-- "); // 固定文字表示 lcdDisp(FixOffTime / 60, 11, 1, 0); // 時 lcdDisp(FixOffTime % 60, 14, 1, 0); // 分 lcd.noCursor(); // カーソルを消す } void dispSolar2() { // ソーラータイマー設定値表示 lcd.clear(); lcd.setCursor(0, 0); lcd.print("SunON h:mm sNRNh"); // 固定文字表示 lcd.setCursor(0, 1); lcd.print(" OFF hh:mm sNRNh"); // 固定文字表示 // 1行目 lcdDisp(mNumT1 / 60, 6, 0, 0); // 日の出時刻の時 lcd.setCursor(5, 0); lcd.print(" "); // 上位はゼロなのでスペースを埋める lcdDisp(mNumT1 % 60, 9, 0, 0); //       分 lcdDispSNRN(T1offset / 6, 14, 0, -1); // オフセット値を0.1h単位で表示 lcd.setCursor(15, 0); lcd.print("h"); // 2行目 lcdDisp(mNumT2 / 60, 6, 1, 0); // 日の入り時刻時 lcdDisp(mNumT2 % 60, 9, 1, 0); //       分 lcdDispSNRN(T2offset / 6, 14, 1, -1); // オフセット値を0.1h単位で表示 lcd.setCursor(15, 1); lcd.print("h"); lcd.noCursor(); // カーソルを消す } void dispClockNorm2() { // 液晶にRTCの内容を表示 int x; lcd.clear(); lcd.setCursor(0, 0); if (digitalRead(EntB) == HIGH) { // Enterボタンが押されていなければ for (int n = 11; n <= 18; n++) { // hh/mm/ss 表示(通常表示) lcd.print(dtString[n]); } lcd.print(" w10+r20"); // LED出力表示 // WhitePow=1.0;RedPow=1.0; if (WhiteNow >= 1.0) { lcd.setCursor(10, 0); lcd.print("Mx"); } else { x = WhiteNow * 100; lcdDisp(x, 11, 0, 0); } if (RedNow >= 1.0) { lcd.setCursor(14, 0); lcd.print("Mx"); } else { x = RedNow * 100; lcdDisp(x, 15, 0, 0); } } else { // EntBボタンが押されていたら for (int n = 0; n <= 16; n++) { // yyyy/mm/dd hh:mm 表示(西暦表示) lcd.print(dtString[n]); } } } void calcT1T2() { // 日の出・日の入り時刻を計算 int mm, dd, n, i; mm = (dtString[5] & 0xf) * 10 + (dtString[6] & 0xf); dd = (dtString[8] & 0xf) * 10 + (dtString[9] & 0xf); n = dayOfYear(mm, dd); // 日付連番を求める T1 = SunRiseTime(Longitude, Latitude, n); // 経緯度、日付連番から日の出時刻を求める i = (T1 * 60.0) + 0.5; // T1を分の値で計算(四捨五入) mNumT1 = i; // T2 = SunSetTime(Longitude, Latitude, n); // 経緯度、日付連番から日の入り出時刻を求める i = (T2 * 60.0) + 0.5; // T2を分の値で計算(四捨五入) mNumT2 = i; } void dispSunRiseSet() { // 日の出・日の入り時刻表示 lcd.setCursor(0, 1); lcd.print("Solar "); // 2行目の先頭から書き始める lcd.print(mNumT1 / 60); lcd.print(":"); // 時: lcd.print((mNumT1 % 60) / 10); // 分の10の位 lcd.print((mNumT1 % 60) % 10); // 分の1の位 lcd.print("-"); lcd.print(mNumT2 / 60); lcd.print(":"); // 時: lcd.print((mNumT2 % 60) / 10); // 分の10の位 lcd.print((mNumT2 % 60) % 10); // 分の1の位 } void solarTimerSetting2() { lcd.clear(); lcd.print("Sol. Time Offset"); while (digitalRead(DecB) == LOW) { // DecBが押されていたら離されるまで待つ } delay(30); // チャッタ消しのために待つ lcd.clear(); // 日の入りON動作時刻のオフセット設定 lcd.setCursor(0, 0); lcd.print("ON time offset "); // 固定文字表示 lcd.setCursor(0, 1); lcd.print("SunRize sNRN hr"); // 固定文字表示 T1offset = 6 * lcdInputSNRN(T1offset / 6, 11, 1, -40, 40); // T1のオフセット入力(範囲は±4.0H) EEPROM.write(0, (T1offset >> 8)); // 値をEEPROMに保存(MSB) EEPROM.write(1, (T1offset & 0x00ff)); // (LSB) lcd.clear(); // 日の出OFF時刻のオフセット設定 lcd.setCursor(0, 0); lcd.print("OFF time offset "); // lcd.setCursor(0, 1); lcd.print("SunSet sNRN hr"); // T2offset = 6 * lcdInputSNRN(T2offset / 6, 11, 1, -40, 40); //T2のオフセット入力(範囲は±4.0H) EEPROM.write(2, (T2offset >> 8)); // EEPROMへ保存 EEPROM.write(3, (T2offset & 0x00ff)); } void fixTimerSetting2() { // スモールライト点灯時刻の設定 int hh, mm; lcd.clear(); lcd.print("small light set."); // 開始メッセージ while (digitalRead(IncB) == LOW) { // IncBが押されていたら離されるまで待つ } delay(30); lcd.clear(); lcd.print("small ON hh:mm"); lcd.setCursor(0, 1); lcd.print(" OFF hh:mm"); lcdDispFixTime2(FixOnTime); // 現在の設定値を表示して lcdDispFixTime3(FixOffTime); // 現在の設定値を表示して hh = FixOnTime / 60; hh = lcdInput(hh, 11, 0, 0, 23); // 時の値の入力、値は0-23でサーキュレート mm = FixOnTime % 60; mm = lcdInput(mm, 14, 0, 0, 59); // 分の値の入力、値は0-59でサーキュレート FixOnTime = hh * 60 + mm; // 固定ON時刻の設定(分単位の値) EEPROM.write(4, (FixOnTime >> 8)); EEPROM.write(5, (FixOnTime & 0x00ff)); hh = FixOffTime / 60; hh = lcdInput(hh, 11, 1, 0, 23); // HHの入力 mm = FixOffTime % 60; mm = lcdInput(mm, 14, 1, 0, 59); // mmの入力 FixOffTime = hh * 60 + mm; // 固定OFF時刻の設定(分単位の値) EEPROM.write(6, (FixOffTime >> 8)); // EEPROMへ保存 EEPROM.write(7, (FixOffTime & 0x00ff)); } int lcdInput(int d, int x, int y, int minV, int maxV) { // 2桁の値の液晶表示と値の入力 // +−ボタンで値を変更、Enterボタンで確定 // d=初期値、x, y=液晶座標、 minV,maxV=下限上限、戻り値=入力決定値 // 下限値(minV)が負なら符号付き表示(sNN) 正なら符号無し表示(NN)を行う // minVが正なら値をサーキュレート、minVが負なら値は上下限でストップ lcdDisp(d, x, y, minV); // 値を表示 while (digitalRead(EntB) == LOW) { // もしEntBが押されていたら離されるまで待つ // delay(30); } delay(30); while (digitalRead(EntB) == HIGH) { // EntBが押されていなければ以下の処理を繰り返す if (digitalRead(IncB) == LOW) { // +ボタンが押されていたら delay(30); d++; // dをインクリメント if (d > maxV ) { // 上限を超えていたら if (minV < 0) { // minVがマイナスの場合は d = maxV; // 上限で抑える } else { // そうでなければ d = minV; // 値を下限に変更(サーキュレート) } } lcdDisp(d, x, y, minV); // 指定フォーマットで表示 while (digitalRead(IncB) == LOW) { // +ボタンが離されるまで待つ } delay(30); } if (digitalRead(DecB) == LOW) { // −ボタンが押されていたら delay(30); d--; // dをデクリメント if (d < minV ) { // 下限を下回っていたら if ( minV < 0) { // minVがマイナスの場合は d = minV; // 下限で抑える } else { // そうでなければ d = maxV; // 値を上限に変更(サーキュレート) } } lcdDisp(d, x, y, minV); // 指定フォーマットで表示 while (digitalRead(DecB) == LOW) { // −ボタンが離されるまで待つ } delay(30); } } delay(30); lcd.noCursor(); // 入力が終わったのでカーソルを消す return d; // 入力された値を返す } int lcdInputSNRN(int d, int x, int y, int minV, int maxV) { // sN.N形式の値の液晶表示と値の入力(EX1.2) // +−ボタンで値を変更、Enterボタンで確定 // d=初期値、x, y=液晶座標、 minV,maxV=下限上限、戻り値=入力決定値 // 下限値(minV)が負なら符号付き表示(sNN) 正なら符号無し表示(NN)を行う // minVが正なら値をサーキュレート、minVが負なら値は上下限でストップ lcdDispSNRN(d, x, y, minV); // 値を表示 while (digitalRead(EntB) == LOW) { // もしEntBが押されていたら離されるまで待つ // delay(30); } delay(30); while (digitalRead(EntB) == HIGH) { // EntBが押されていなければ以下の処理を繰り返す if (digitalRead(IncB) == LOW) { // +ボタンが押されていたら delay(30); d++; // dをインクリメント if (d > maxV ) { // 上限を超えていたら if (minV < 0) { // minVがマイナスの場合は d = maxV; // 上限で抑える } else { // そうでなければ d = minV; // 値を下限に変更(サーキュレート) } } lcdDispSNRN(d, x, y, minV); // 指定フォーマットで表示 while (digitalRead(IncB) == LOW) { // +ボタンが離されるまで待つ } delay(30); } if (digitalRead(DecB) == LOW) { // −ボタンが押されていたら delay(30); d--; // dをデクリメント if (d < minV ) { // 下限を下回っていたら if ( minV < 0) { // minVがマイナスの場合は d = minV; // 下限で抑える } else { // そうでなければ d = maxV; // 値を上限に変更(サーキュレート) } } lcdDispSNRN(d, x, y, minV); // 指定フォーマットで表示 while (digitalRead(DecB) == LOW) { // −ボタンが離されるまで待つ } delay(30); } } delay(30); lcd.noCursor(); // 入力が終わったのでカーソルを消す return d; // 入力された値を返す } int lcdInputNNN(int d, int x, int y, int pin) { // 0-100の値の液晶表示と値の入力 // +−ボタンで値を変更、Enterボタンで確定 // d=初期値、x, y=液晶座標 lcdDispNNN(d, x, y); // 値を表示 while (digitalRead(EntB) == LOW) { // もしEntBが押されていたら離されるまで待つ // delay(30); } delay(30); while (digitalRead(EntB) == HIGH) { // EntBが押されていなければ以下の処理を繰り返す if (digitalRead(IncB) == LOW) { // +ボタンが押されていたら delay(30); d++; // dをインクリメント if (d > 100 ) { // 上限を超えていたら d = 100; } lcdDispNNN(d, x, y); // 指定フォーマットで表示 while (digitalRead(IncB) == LOW) { // +ボタンが離されるまで待つ } delay(30); } if (digitalRead(DecB) == LOW) { // −ボタンが押されていたら delay(30); d--; // dをデクリメント if (d < 0) { // 下限を下回っていたら d = 0; } } lcdDispNNN(d, x, y); // 指定フォーマットで表示 while (digitalRead(DecB) == LOW) { // −ボタンが離されるまで待つ } analogWrite(pin, d * 255 / 100); // 指定LEDを指定明るさで点灯 delay(30); if (digitalRead(ManSW) == HIGH) { // マニュアルスイッチがOFFになっていたら return d; // 現在の値を返して抜ける } } delay(30); lcd.noCursor(); // 入力が終わったのでカーソルを消す return d; // 入力された値を返す } void lcdDisp(int d, int x, int y, int minV) { // 指定位置に2桁の値を表示 // minVが正ならNN形式、負ならsNN形式 if (minV < 0) { // 負の値がある場合 lcd.setCursor(x - 2, y); // 2文字左にカーソルを移動して if (d >= 0) { // 値が正なら lcd.print("+"); // +を表示 } else { // それ以外(つまり負)なら lcd.print("-"); // −を表示 d = abs(d); } } lcd.setCursor(x - 1, y); // 1文字左から if (d < 10) { // 値が10以下なら lcd.print("0"); // 0を書き } lcd.print(d); // 値を表示 lcd.setCursor(x, y); // 1桁目にカーソル移動 lcd.cursor(); // カーソル表示 } void lcdDispSNRN(int d, int x, int y, int minV) { // 指定位置にNRN形式の値を表示(-9.9〜+9.9) // minVが正なら+NRN、負なら-NRN形式 if (minV < 0) { // 負の値がある場合 lcd.setCursor(x - 3, y); // 3文字左にカーソルを移動して if (d >= 0) { // 値が正なら lcd.print("+"); // +を表示 } else { // それ以外(つまり負)なら lcd.print("-"); // −を表示 d = abs(d); } } lcd.setCursor(x - 2, y); // 2文字左から lcd.print(d / 10); // 1桁目を表示 lcd.print("."); // 小数点 lcd.print(d % 10); // 小数点以下1桁目 lcd.setCursor(x, y); // 1桁目にカーソル移動 lcd.cursor(); // カーソル表示 } void lcdDispNNN(int d, int x, int y) { // 指定位置から右詰めゼロサプレスで3桁表示 lcd.setCursor(x, y); // 指定位置 lcd.print(d % 10); // 1桁目 lcd.setCursor(x - 1, y); d = d / 10; if ( d == 0) { lcd.print(" "); } else { lcd.print(d % 10); // 2桁目 } lcd.setCursor(x - 2, y); d = d / 10; if ( d == 00) { lcd.print(" "); } else { lcd.print(d % 10); // 3桁目 } lcd.setCursor(x, y); // 1桁目にカーソル移動 lcd.cursor(); // カーソル表示 } void lcdDispFixTime2(int x) { // 固定タイマー設定時刻の表示 int hh, mm; hh = x / 60; mm = x % 60; lcd.setCursor(10, 0); // 書き始め位置はここ if (hh < 10) { lcd.print("0"); } lcd.print(hh); lcd.print(":"); if (mm < 10) { lcd.print("0"); } lcd.print(mm); } void lcdDispFixTime3(int x) { // 固定タイマー設定時刻の表示 int hh, mm; hh = x / 60; mm = x % 60; lcd.setCursor(10, 1); // 書き始め位置はここ if (hh < 10) { lcd.print("0"); } lcd.print(hh); lcd.print(":"); if (mm < 10) { lcd.print("0"); } lcd.print(mm); } void readEEPROM() { // EEPROMからタイマーの設定値を読み出す。不当な値は修正 T1offset = (EEPROM.read(0) << 8) | EEPROM.read(1); if ((T1offset < -240) || (240 < T1offset)) { // ±240分(4Hr)の範囲外なら EEPROM.write(0, 0); EEPROM.write(1, 0); // 異常値とみなしてゼロにする } T2offset = (EEPROM.read(2) << 8) | EEPROM.read(3); if ((T2offset < -240) || (240 < T2offset)) { // ±240分(4Hr)の範囲外なら EEPROM.write(2, 0); EEPROM.write(3, 0); // ゼロにする } FixOnTime = (EEPROM.read(4) << 8) | EEPROM.read(5); if ((FixOnTime < 0) || (1439 < FixOnTime)) { // マイナスか1439(23*60+59)以上なら EEPROM.write(4, 0); EEPROM.write(5, 0); // ゼロにする } FixOffTime = (EEPROM.read(6) << 8) | EEPROM.read(7); if ((FixOffTime < 0) || (1439 < FixOffTime)) { // マイナスか1439(23*60+59)以上なら EEPROM.write(6, 0); EEPROM.write(7, 0); // ゼロにする } // FixOnMode = EEPROM.read(8); // 1固定なので使わないが互換性維持のためリザーブ // FixOffMode = EEPROM.read(9); // 同上 ManWhite = EEPROM.read(10); // マニュアルの場合のLED明るさ(白) if (ManWhite > 100) { EEPROM.write(10, 50); } ManRed = EEPROM.read(11); // マニュアルの場合のLED明るさ(赤) if (ManRed > 100) { EEPROM.write(11, 50); } Serial.print(T1offset); Serial.print(", "); Serial.println(T2offset); Serial.print(FixOnTime); Serial.print(", "); Serial.println(FixOffTime); // Serial.print(FixOnMode); Serial.print(", "); Serial.println(FixOffMode); Serial.print(ManWhite); Serial.print(", "); Serial.println(ManRed); Serial.println(); } void convString() { // RTCを読んで時刻文字列を作る。時刻の分連番を計算 byte x; dtString[0] = '2'; dtString[1] = '0'; x = Rtc.years(); dtString[2] = upper2chr(x); dtString[3] = lower2chr(x); x = Rtc.months(); dtString[5] = upper2chr(x); dtString[6] = lower2chr(x); x = Rtc.days(); dtString[8] = upper2chr(x); dtString[9] = lower2chr(x); x = Rtc.hours(); dtString[11] = upper2chr(x); dtString[12] = lower2chr(x); mNumNow = ((x & 0xF0) >> 4 ) * 600 + (x & 0x0F) * 60; // 分の連番計算 x = Rtc.minutes(); dtString[14] = upper2chr(x); dtString[15] = lower2chr(x); mNumNow += ((x & 0xF0) >> 4) * 10 + (x & 0x0F); // 分の連番計算 x = Rtc.seconds(); dtString[17] = upper2chr(x); dtString[18] = lower2chr(x); sNumNow = ((x & 0xF0) >> 4) * 10 + (x & 0x0F); // 秒の値 (secndNumberNow) sNumToday = mNumNow * 60UL + sNumNow; // 累積秒数 } void nightLED() { // 夜間表示LED if ((mNumNow <= mNumT1) || (mNumT2 <= mNumNow)) { // T2からT1の間はON digitalWrite(NightOutPin, HIGH); } else { digitalWrite(NightOutPin, LOW); } } void userTimer() { // ユーザー定義のタイマー // 必要に応じて処理を書く } char upper2chr(byte x) { // 上位4ビットをAsciiコードに変換 return (x >> 4) + 0x30; } char lower2chr(byte x) { // 下位4ビットをAsciiコードに変換 return (x & 0x0f) + 0x30; } void SetRtcTimer() { // RTCの設定 Wire.beginTransmission(RtcADR); Wire.write(0x0e); // タイマー停止 Wire.write(0x00); Wire.endTransmission(); Wire.beginTransmission(RtcADR); Wire.write(0x01); // コントロールレジスタ-2の設定 Wire.write(0x11); // TI/TP=on, TIE=on Wire.endTransmission(); Wire.beginTransmission(RtcADR); Wire.write(0x0f); // タイマー設定値 Wire.write(0x01); // 1回毎 Wire.endTransmission(); Wire.beginTransmission(RtcADR); Wire.write(0x0e); // タイマースタート Wire.write(0x82); // TEビットon, カウンタソースクロックは1秒を設定 Wire.endTransmission(); } void ClockSet() { // 時計合わせ int nen, tsuki, dayCount; lcd.setCursor(0, 0); lcd.print("Clock set!"); // モード開始メッセージ lcd.setCursor(0, 1); lcd.print("Sel, Ent"); // SElectで選択、Enterで決定 while (digitalRead(EntB) == LOW) { // EnterスイッチがOFFになるまで待つ } delay(30); Rtc.available(); // 現在の時刻を取得 convString(); // RTCの時刻(BCD)を文字列に変換してdtStringに格納 dtString[17] = '0'; dtString[18] = '0'; // 但し秒はゼロに設定 dispClockAdj(); // 時刻合わせモードで表示 //データ位置と値の範囲を指定して数値設定プログラムを呼ぶ setV(3, 15, 35); // 年(2015〜2035年まで) setV(6, 1, 12); // 月 // 月の大小と閏年を反映した日数で呼ぶ nen = (dtString[2] & 0x0f) * 10 + (dtString[3] & 0x0f); tsuki = (dtString[5] & 0x0f) * 10 + (dtString[6] & 0x0f); dayCount = 31; // 普通の月は31日まで if ((tsuki == 4) | (tsuki == 6) | (tsuki == 9) | (tsuki == 11)) { dayCount = 30; // 4,6,9,11月は30日まで } if (tsuki == 2) { // 2月は dayCount = 28; // 普通は28日まで if ((nen % 4) == 0) { // うるう年だったら dayCount = 29; // 29日まで } } setV(9, 1, dayCount); // 日 setV(12, 0, 23); // 時 setV(15, 0, 59); // 分を合わせたら秒はゼロで時計あわせ updateClock(); // RTCに日時を設定 } // IncB,(DecB) ボタンが押される毎にdtStringの指定位置の値を増加(減少)、液晶を更新 // Eentボタンが押されたら値を確定。 // 本来はlcdInput()関数を使うべきだが、昔作ったものをそのまま流用したためこうなっている void setV(int p, int minV, int maxV) { // p:ポインタ、minV:最小値、maxV:最大値 int xx; char upper; char lower; lcd.setCursor(p % 11, p / 11); // 液晶のカーソルを移動 lcd.cursor(); // カーソル表示 xx = (dtString[p - 1] & 0x0f) * 10 + (dtString[p] & 0x0f); //文字列から初期値を計算。BCDなので上位は10倍 while (digitalRead(EntB) == LOW) { // EntBがOFFになるまで待つ delay(30); } while (digitalRead(EntB) == HIGH) { // EntBが押されていなければ以下の処理行う if (digitalRead(IncB) == LOW) { // IncBが押されたなら delay(30); xx++; // xxをインクリメント if (xx >= maxV + 1) { // 上限超えてたら下限へサーキュレート xx = minV; } upper = (xx / 10) | 0x30; // 上位をASCIIへ変換 lower = (xx % 10) | 0x30; // 下位をASCIIへ変換 lcd.setCursor((p - 1) % 11, (p - 0) / 11); lcd.print(upper); // 表示更新 lcd.print(lower); lcd.setCursor((p - 0) % 11, (p - 0) / 11); // 液晶のカーソルを元の位置に戻す dtString[p - 1] = upper; // データ文字列更新 dtString[p] = lower; while (digitalRead(IncB) == LOW) { // IncBが離されるまで待つ } delay(30); } if (digitalRead(DecB) == LOW) { // DecBが押されたなら delay(30); xx--; // xxをデクリメント if (xx <= minV - 1) { // 下限以下なら上限へサーキュレート xx = maxV; } upper = (xx / 10) | 0x30; // 上位をASCIIへ変換 lower = (xx % 10) | 0x30; // 下位をASCIIへ変換 lcd.setCursor((p - 1) % 11, (p - 0) / 11); lcd.print(upper); // 表示更新 lcd.print(lower); lcd.setCursor((p - 0) % 11, (p - 0) / 11); // 液晶のカーソルを元の位置に戻す dtString[p - 1] = upper; // データ文字列更新 dtString[p] = lower; while (digitalRead(DecB) == LOW) { // DecBが離されるまで待つ } delay(30); } } delay(30); } void updateClock() { // RTCに値をセット byte data[7]; int yy, mm, dd, ww; yy = (dtString[2] & 0xf) * 10 + (dtString[3] & 0xf) + 2000; mm = (dtString[5] & 0xf) * 10 + (dtString[6] & 0xf); dd = (dtString[8] & 0xf) * 10 + (dtString[9] & 0xf); if ( mm <= 2) { // ツェラーの式で曜日を求める mm += 12; yy--; } ww = (yy + yy / 4 - yy / 100 + yy / 400 + (13 * mm + 8) / 5 + dd) % 7; for (int n = 0; n <= 15; n++) { // デバッグ用 Serial.print(dtString[n]); } Serial.print(" Week= "); Serial.println(ww); // print week No. delay(200); data[0] = 0x00; // 00秒にリセット data[1] = ((dtString[14] & 0xf) << 4) | (dtString[15] & 0x0f); // 分 data[2] = ((dtString[11] & 0xf) << 4) | (dtString[12] & 0x0f); // 時 data[3] = ((dtString[8] & 0xf) << 4) | (dtString[9] & 0x0f); // 日 data[4] = ww; // 曜日 data[5] = ((dtString[5] & 0xf) << 4) | (dtString[6] & 0x0f); // 月 data[6] = ((dtString[2] & 0xf) << 4) | (dtString[3] & 0x0f); // 年 Rtc.sync(data); } void dispClockAdj() { // 時刻合わせモードの表示 lcd.clear(); lcd.setCursor(0, 0); // 1行目に、 for (int n = 0; n <= 9; n++) { // yyyy/mm/dd lcd.print(dtString[n]); } lcd.setCursor(0, 1); // 2行目に、 for (int n = 11; n <= 16; n++) { // hh:mm lcd.print(dtString[n]); } lcd.print("--"); // ssにはゼロが入る } /* 日の出・日の入り時刻の計算方法は以下のサイトを参考にさせて頂きました。ありがとうございます。 http://k-ichikawa.blog.enjoy.jp/etc/HP/js/sunRise/srs.html http://www.iot-kyoto.com/satoh/2016/01/22/post-99/ */ float SunRiseTime(float x, float y, int n) { // 日の出時刻を求める関数 float d, e, t; y = RAD(y); // 緯度をラジアンに変換 d = dCalc(n); // 太陽赤緯を求める e = eCalc(n); // 均時差を求める // 太陽の時角幅を求める(視半径、大気差などを補正 (-0.899度) ) t = DEG(acos( (sin(RAD(-0.899)) - sin(d) * sin(y)) / (cos(d) * cos(y)) ) ); return ( -t + 180.0 - x + 135.0) / 15.0 - e; // 日の出時刻を返す } float SunSetTime(float x, float y, int n) { // 日の入り時刻を求める関数 float d, e, t; y = RAD(y); // 緯度をラジアンに変換 d = dCalc(n); // 太陽赤緯を求める e = eCalc(n); // 均時差を求める // 太陽の時角幅を求める(視半径、大気差などを補正 (-0.899度) ) t = DEG(acos( (sin(RAD(-0.899)) - sin(d) * sin(y)) / (cos(d) * cos(y)) ) ); return ( t + 180.0 - x + 135.0) / 15.0 - e; // 日の入り時刻を返す } float dCalc(int n) { // 近似式で太陽赤緯を求める float d, w; w = (n + 0.5) * 2 * M_PI / 365; // 日付をラジアンに変換 d = + 0.33281 - 22.984 * cos(w) - 0.34990 * cos(2 * w) - 0.13980 * cos(3 * w) + 3.7872 * sin(w) + 0.03250 * sin(2 * w) + 0.07187 * sin(3 * w); return RAD(d); // 赤緯を返す(単位はラジアン) } float eCalc(int n) { // 近似式で均時差を求める float e, w; w = (n + 0.5) * 2 * M_PI / 365; // 日付をラジアンに換算 e = + 0.0072 * cos(w) - 0.0528 * cos(2 * w) - 0.0012 * cos(3 * w) - 0.1229 * sin(w) - 0.1565 * sin(2 * w) - 0.0041 * sin(3 * w); return e; // 均一時差を返す(単位は時) } int dayOfYear(int mm, int dd) { // 月日から年間の経過日数を求める int daySum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; //前月末の通算日(うるう年は無視) return daySum[mm - 1] + dd - 1; // 経過日数を返す(1月1日=0) } void dispLoc() { // 経緯度表示 lcd.clear(); lcd.print("Long. E "); lcd.print(Longitude, 4); // 東経の値 lcd.setCursor(0, 1); lcd.print("Lati. N "); lcd.print(Latitude, 4); // 北緯の値 } 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(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, int exec) { // 白LEDの動作パラメーター設定(明るさ, 通過サイクル数) if (x > 1.0) { // 上限をクランプ x = 1.0; } if (x < 0.003) { // 下限をクランプ x = 0.003; } WhitePow = x; // 明るさ設定値 if (exec == 1) { WhiteSlope = pow(WhitePow / WhiteNow, 1.0 / y); // 係数 = 全変化率^(1/サイクル数) } } void RedSet(float x, float y, int exec) { // 赤LED動作パラメーター設定(明るさ, 通過サイクル数) if (x > 1.0) { // 上限をクランプ x = 1.0; } if (x < 0.003) { // 下限をクランプ x = 0.003; } RedPow = x; if (exec == 1) { RedSlope = pow(RedPow / RedNow, 1.0 / y); // 係数 = 全変化率^(1/サイクル数) } } void waitPin2Low() { // RTCの*INTを読む while (digitalRead(2) == HIGH) { // LOWになるまで待つ } while (digitalRead(2) == LOW) { // LOWが終わって再度HIGHになるまで待つ } } void LedContProgram(unsigned long x, int e ) { // 明るさ変化パターン設定プログラム // if文:イベント開始時刻(累積秒)。RedSetとWhiteSetの引数は下記 // 第一引数:明るさ設定値(0.003から1.0の値を設定)。 // 第二引数:明るさ遷移時間(秒)明るさは等比級数で変化 // 第三引数:実行(Execute)フラグ。ゼロならスロープ計算無し(高速実行される) const float p = 0.75; // LED電流調整係数(1.0以下の値を入れる) // 夜明け if (x == (FixOnTime - 5UL) * 60UL) { // small ON 時刻の5分前 RedSet(0.10, 60, e); // 1分スロープで赤だけ10%点灯(真っ赤な朝焼け) } if (x == FixOnTime * 60UL ) { // small ON 時刻 RedSet(0.003, 300, e); // 5分スロープで赤消灯 WhiteSet(0.07, 60, e); // 1分スロープで白7%(夜明け前) } // 日の出から日中まで(3時間かけて増光) // 日照の変化パターンに似せるため1時間置きに目標値を設定(指数関数で一気に変えてはダメ) if (x == (mNumT1 + T1offset) * 60UL ) { // 日の出時刻なら RedSet(0.45 * p, 3600, e); // 1時間スロープで赤45% WhiteSet(0.45 * p, 3600, e); // 1時間スロープで白45% } if (x == (mNumT1 + T1offset + 60) * 60UL ) { // 日の出時刻 +1Hrなら RedSet(0.83 * p, 3600, e); // 1時間スロープで赤83% WhiteSet(0.83 * p, 3600, e); // 1時間スロープで白83% } if (x == (mNumT1 + T1offset + 120) * 60UL ) { // 日の出時刻 +2Hrなら RedSet(1.0 * p, 3600, e); // 1時間スロープで赤100%点灯 WhiteSet(1.0 * p, 3600, e); // 1時間スロープで白100%点灯 } // 日中から夕暮れ(3時間かけて減光)日没後はスモールライト点灯 if (x == (mNumT2 + T2offset - 180) * 60UL ) { // 日没時刻 3Hr前なら RedSet(0.83 * p, 3600, e); // 1時間スロープで赤83% WhiteSet(0.83 * p, 3600, e); // 1時間スロープで白83% } if (x == (mNumT2 + T2offset - 120) * 60UL ) { // 日没時刻 2Hr前なら RedSet(0.45 * p, 3600, e); // 1時間スロープで赤45% WhiteSet(0.45 * p, 3600, e); // 1時間スロープで白45% } if (x == (mNumT2 + T2offset - 60) * 60UL ) { // 日没時刻 1Hr前なら RedSet(0.003, 3600, e); // 1時間スロープで赤消灯 WhiteSet(0.07, 3600, e); // 1時間スロープで白だけ7%点灯に移行(夕暮れ) } // 消灯前の演出 if (x == FixOffTime * 60UL) { // small OFF 時刻 RedSet(0.10, 300, e); // 5分スロープで赤10%点灯(夕焼け開始) } if (x == (FixOffTime + 10UL) * 60UL) { // small OFF 時刻10分後(真っ赤な夕焼け開始) WhiteSet(0.003, 600, e); // 10分スロープで白消灯し、真っ赤にする(日没20分後) } if (x == (FixOffTime + 25UL) * 60UL) { // small OFF 時刻25分後 RedSet(0.003, 300, e); // 5分スロープで赤消灯(夜間) } }