/* ソーラータイマースイッチ (日の出・日の入りタイマー) 機能 ・日の出・日の入り時刻の変化に追従してスイッチを操作(ソーラータイマー機能) ・日の出・日の入り時刻は各々±99分のオフセットが設定可能。 ・一定時刻でON/OFFする機能あり(FIXタイマー機能)。ソーラータイマーとFIXタイマーは同時使用可能。 仕組み  ・日の出・日の入り時刻は日付と経緯度から計算。(経緯度の変更は再コンパイル) ・リアルタイムクロックRTC8564BNと秋月のI2C液晶(16文字2行)を使用 ・待機時はCPUをパワーダウンモードでスリープさせ省エネ。(RTCからPin2に1秒周期で割込み) ・パラメーターの設定などの操作は液晶とタクトスイッチで可能。・RTC8564.hライブラリは要修正 2016/7/21版 ラジオペンチ http://radiopench.blog96.fc2.com/ */ #include // 待機中にスリープさせるために使用 #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(30, 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 TimerOutPin = 11; // タイマー出力ピン int NightOutPin = 12; // 夜間固定出力ピン(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) int T1offset; // T1(日の出時刻)のオフセット(単位:分) int T2offset; // T2(日没時刻)のオフセット(単位:分) int FixOnTime; // 固定ONタイマー設定値(単位:分) int FixOffTime; // 固定OFFタイマー設定値(単位:分) int FixOnMode, FixOffMode; // タイマー使用フラグ(0:不使用、1:使用) boolean OutFlag = false; // タイマー出力フラグ(true=ON, false=OFF) byte OnMark[8] = { // タイマー出力表示ビットマップ(LCD外字で定義) B00000, B01110, B11111, B11011, B11111, B01110, B00000, }; 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ボタン readEEPROM(); // EEPROMから設定値を読み出す Serial.begin(115200); lcd.begin(16, 2); // 16文字2行のI2C液晶を使用 lcd.createChar(1, OnMark); // 外字の1にTimerOnマークを定義 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ボタンが押されていたら solarTimerSetting(); // ソーラータイマー動作条件設定(オフセットの設定) } if (digitalRead(IncB) == LOW) { // 起動時にIncボタンが押されていたら fixTimerSetting(); // Fixタイマーの動作条件設定 } catchUpOutFlag(mNumNow); // 出力フラグを現在時刻の状態にキャッチアップさせる digitalWrite(TimerOutPin, OutFlag); // フラグに応じてメインタイマーを出力 solarTimer(); // ソーラータイマー出力(夜間だけ出力するタイマー) userTimer(); // 特別な動作が必要なら使う SetRtcTimer(); // NB8564の設定(1秒周期でINT発生) もっと前に設定する必要があるかも set_sleep_mode(SLEEP_MODE_PWR_DOWN); // スリープモードの設定(パワーダウンモード) } void loop() { // メインループ waitExtIRQ(); // RTCからの1秒パルスを待つ(この関数の中でスリープ) digitalWrite(LedPin, HIGH); // 動作中表示LED Rtc.available(); // RTC convString(); // RTCの時刻(BCD)を読んで文字列に変換しdtStringに格納 if ((mNumNow == 0) && (sNumNow == 0)) { // 日付が変わっていたら(分と秒がゼロだったら) calcT1T2(); // 新しい日付の日の出、日の入り時刻を計算 Serial.print("New T1,T2 = "); Serial.print(T1); Serial.print(", "); Serial.println(T2); } if (sNumNow == 0) { // 毎分0秒のタイミングで updateOutFlag(mNumNow); // メインタイマーの出力フラグ更新 digitalWrite(TimerOutPin, OutFlag); // メインタイマー出力 solarTimer(); // ソーラータイマー出力(夜間だけ出力するタイマー) userTimer(); // 特別な動作が必要ならここを使う Serial.print(dtString); Serial.print(", "); Serial.print(mNumNow); Serial.print(", "); Serial.println(OutFlag); } disp(); // 液晶表示 Serial.flush(); // シリアルの送信が完了するまで待つ digitalWrite(LedPin, LOW); // 動作表示LED OFF } void catchUpOutFlag(int m) { // 過去1日分をシミュレートして出力フラグをキャッチアップさせる for (int i = m; i < 1440; i++) { // 昨日分を再現(1440 = 24Hr * 60) updateOutFlag(i); // フラグの状態セット } for (int i = 0; i <= m; i++) { // 今日の状態を再現 updateOutFlag(i); // フラグの状態セット } } void updateOutFlag(int i) { // 時刻に応じて出力フラグを設定 if ((FixOnMode == 1) && (i == FixOnTime)) { // FIX ON がUSEで時刻が一致 OutFlag = true; // フラグON } if ((FixOffMode == 1) && (i == FixOffTime)) { // FIX OFF がUSEで時刻が一致 OutFlag = false; // フラグOFF } if (i == (mNumT1 + T1offset)) { // T1+オフセットと時刻が一致 OutFlag = false; // 夜明けなのでフラグOFF } if (i == (mNumT2 + T2offset)) { // T2+オフセットと時刻が一致 OutFlag = true; // 日没なのでフラグON } } void disp() { // 運転中の液晶表示 if (digitalRead(IncB) == LOW) { // Incボタンが押されていたら dispFix(); // 固定タイマーの設定状態表示 } else { if (digitalRead(DecB) == LOW) { // Decボタンが押されていたら dispSolar(); // ソーラータイマーオフセット値表示 } else { // 何も押されていなければ dispClockNorm(); // 時計表示、タイマー出力表示(1行目) dispSunRiseSet(); // 日の入り・日の出時刻表示(2行目) } } } void dispFix() { // 固定タイマー(FixTimer)設定値表示 lcd.clear(); lcd.setCursor(0, 0); lcd.print("FIX OFF --:--"); // 固定文字表示 lcd.setCursor(0, 1); lcd.print(" ON --:--"); // 固定文字表示 // 1行目 if (FixOffMode != 0) { // 固定Offタイマー使用なら lcdDisp(FixOffTime / 60, 9, 0, 0); // 時 lcdDisp(FixOffTime % 60, 12, 0, 0); // 分を上書き表示 } // 2行目 if (FixOnMode != 0) { // 固定Onタイマー使用なら lcdDisp(FixOnTime / 60, 9, 1, 0); // 時 lcdDisp(FixOnTime % 60, 12, 1, 0); // 分を上書き表示 } lcd.noCursor(); // カーソルを消す } void dispSolar() { // ソーラータイマー設定値表示 lcd.clear(); lcd.setCursor(0, 0); lcd.print("Sol.ON hh:mm sNN"); // 固定文字表示 lcd.setCursor(0, 1); lcd.print(" OFF hh:mm sNN"); // 固定文字表示 // 1行目 lcdDisp(mNumT2 / 60, 8, 0, 0); // 日の入り時刻時 lcdDisp(mNumT2 % 60, 11, 0, 0); //       分 lcdDisp(T2offset, 15, 0, -1); // オフセット値(分)を符号付きで表示 // 2行目 lcdDisp(mNumT1 / 60, 8, 1, 0); // 日の出時刻時 lcdDisp(mNumT1 % 60, 11, 1, 0); //       分 lcdDisp(T1offset, 15, 1, -1); // オフセット値(分)を符号付きで表示 lcd.noCursor(); // カーソルを消す } void dispClockNorm() { // 液晶にRTCの内容を表示 lcd.clear(); lcd.setCursor(0, 0); if (digitalRead(EntB) == HIGH) { // Enterボタンが押されていなければ for (int n = 5; n <= 9; n++) { // mm/dd 表示(通常表示) lcd.print(dtString[n]); } } else { // EntBボタンが押されていたら for (int n = 0; n <= 4; n++) { // yyyy/ 表示(西暦表示) lcd.print(dtString[n]); } } lcd.print(" "); // 1文字スペース for (int n = 11; n <= 18; n++) { // hh:mm:ss 表示 lcd.print(dtString[n]); } if (OutFlag == true) { // タイマー出力がHIGHなら lcd.setCursor(15, 0); lcd.write(1); // 外字でONマーク表示 } } 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 solarTimerSetting() { lcd.clear(); lcd.print("SolarTimer set."); 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("Sunset sNN min."); // 固定文字表示 T2offset = lcdInput(T2offset, 9, 1, -99, 99); // 液晶とボタンでT2のオフセット入力 EEPROM.write(0, (T2offset >> 8)); // 値をEEPROMに保存(MSB) EEPROM.write(1, (T2offset & 0x00ff)); // (LSB) lcd.clear(); // 日の出OFF時刻のオフセット設定 lcd.setCursor(0, 0); lcd.print("OFF time offset "); // lcd.setCursor(0, 1); lcd.print("Sunrise sNN min."); // T1offset = lcdInput(T1offset, 10, 1, -99, 99); // T1のオフセット入力 EEPROM.write(2, (T1offset >> 8)); // EEPROMへ保存 EEPROM.write(3, (T1offset & 0x00ff)); } void fixTimerSetting() { // 固定時刻タイマーの設定 int hh, mm; lcd.clear(); lcd.print("FixTimer set "); while (digitalRead(IncB) == LOW) { // IncBが押されていたら離されるまで待つ } delay(30); lcd.clear(); lcd.print("Fix Off Timer "); // Fix OFF タイマー設定 if (FixOffMode != 0) { // このモードを使うなら(USEなら) lcdDispFixTime(FixOffTime); // 設定時刻を表示 } FixOffMode = selMode(FixOffMode, 0, 1, FixOffTime); // 使用の有無の入力 EEPROM.write(9, FixOffMode & 0x00ff); if (FixOffMode != 0) { // USEなら以下を表示 lcdDispFixTime(FixOffTime); // 現在の設定値を表示して hh = FixOffTime / 60; hh = lcdInput(hh, 8, 1, 0, 23); // HHの入力 mm = FixOffTime % 60; mm = lcdInput(mm, 11, 1, 0, 59); // mmの入力 FixOffTime = hh * 60 + mm; // 固定OFF時刻の設定(分単位の値) EEPROM.write(6, (FixOffTime >> 8)); // EEPROMへ保存 EEPROM.write(7, (FixOffTime & 0x00ff)); } lcd.clear(); lcd.print("Fix On Timer "); // Fix ON 固定オンタイマーの設定 if (FixOnMode != 0) { // このモードを使うなら(USEなら) lcdDispFixTime(FixOnTime); // 設定時刻を表示 } FixOnMode = selMode(FixOnMode, 0, 1, FixOnTime); // 使用の有無の入力 EEPROM.write(8, FixOnMode & 0x00ff); if (FixOnMode != 0) { // USEなら以下を表示 lcdDispFixTime(FixOnTime); // 現在の設定値を表示 hh = FixOnTime / 60; hh = lcdInput(hh, 8, 1, 0, 23); // 時の値の入力、値は0-23でサーキュレート mm = FixOnTime % 60; mm = lcdInput(mm, 11, 1, 0, 59); // 分の値の入力、値は0-59でサーキュレート FixOnTime = hh * 60 + mm; // 固定ON時刻の設定(分単位の値) EEPROM.write(4, (FixOnTime >> 8)); EEPROM.write(5, (FixOnTime & 0x00ff)); } } int lcdInput(int d, int x, int y, int minV, int maxV) { // 液晶表示と値の入力 // +−ボタンで値を変更、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; // 入力された値を返す } 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 lcdDispFixTime(int x) { // 固定タイマー設定時刻の表示 int hh, mm; hh = x / 60; mm = x % 60; lcd.setCursor(7, 1); // 書き始め位置はここ if (hh < 10) { lcd.print("0"); } lcd.print(hh); lcd.print(":"); if (mm < 10) { lcd.print("0"); } lcd.print(mm); } int selMode(int m, int x, int y, int t) { // モード,表示位置(x、y),タイマーの値(分単位) lcdDispMode(m, x, y, t); // 最初の状態表示 while (digitalRead(EntB) == LOW) { // もしEntBが押されていたら離されるまで待つ } delay(30); while (digitalRead(EntB) == HIGH) { // EntBが押されていなければ以下の処理を繰り返す if (digitalRead(IncB) == LOW) { // +ボタンが押されていたら m = 1; // フラグセット } if (digitalRead(DecB) == LOW) { // −ボタンが押されていたら m = 0; // フラグリセット } lcdDispMode(m, x, y, t); // 変更後の状態表示 delay(30); } delay(30); return m; // 設定されたモードを返す } void lcdDispMode(int m, int x, int y, int t) { lcd.setCursor(x, y); if (m == 0) { lcd.print("No USE "); } else { lcd.print("USE at "); lcdDispFixTime(t); } lcd.setCursor(x, y); lcd.cursor(); // カーソルを先頭に戻して表示 } void readEEPROM() { // EEPROMからタイマーの設定値を読み出す。不当な値は修正 T2offset = (EEPROM.read(0) << 8) | EEPROM.read(1); if ((T2offset < -99) | (99 < T2offset)) { // ±99の範囲外なら EEPROM.write(0, 0); EEPROM.write(1, 0); // ゼロにする } T1offset = (EEPROM.read(2) << 8) | EEPROM.read(3); if ((T1offset < -99) | (99 < T1offset)) { // ±99の範囲外なら 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); FixOffMode = EEPROM.read(9); Serial.print(T2offset); Serial.print(", "); Serial.println(T1offset); Serial.print(FixOnTime); Serial.print(", "); Serial.println(FixOffTime); Serial.print(FixOnMode); Serial.print(", "); Serial.println(FixOffMode); 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) } void solarTimer() { // 日没から日の出までONになるタイマー if ((mNumNow < (mNumT1 + T1offset)) || ((mNumT2 + T2offset) <= 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 waitExtIRQ() { // 外部割込みモードで待機と復帰 ADCSRA &= ~(1 << ADEN); // ADENビットをクリアしてADCを停止(これで120μA節約) attachInterrupt(0, trigerd, FALLING); // Pin2のネガエッジで割込み発生するように指定 sleep_mode(); // ここで指定モードでスリープ detachInterrupt(0); // 割込み入力でここから動作再開 ADCSRA |= (1 << ADEN); // ADCの動作を再開 } void trigerd() { // 割込み発生時の処理 }