/* RTC DS3231 と 0.96インチOLEDを使った時計 * Ent + Reset:時刻合わせ * Inc + Reset:エージングレジスタ設定 * 動作はピン2からの割り込みで行い、割り込み待ちの時はパワーダウンモードで省電流化 * 2019/7/31 ラジオペンチ http://radiopench.blog96.fc2.com/ */ #include // DS3231のライブライ #include // OLED表示ライブラリ #include // 割込み待ち期間はスリープさせるために使用 #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define xS 8 // 画面のX方向シフト量 #define timeIrqPin 2 #define incPin 5 // (+) Inc. button #define decPin 6 // (−) Dec. button #define entPin 7 // Enter button #define ledPin 13 // LED pin Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); DS3231 rtc(SDA, SCL); // DS3231の設定 Time t; // Timeクラス構造体 char buff[10]; // 文字列操作バッファ String ymd = "yyyy/mm/dd"; String hms = "hh:MM:ss"; String dayOfWeek = "sun"; float rtcTemp; // RTCの温度センサ void setup() { pinMode(timeIrqPin, INPUT_PULLUP); // RTC割り込み入力(宣言不要だが明示のために定義) pinMode(incPin, INPUT_PULLUP); // + pinMode(decPin, INPUT_PULLUP); // - pinMode(entPin, INPUT_PULLUP); // enter pinMode(ledPin, OUTPUT); Serial.begin(115200); rtc.begin(); rtc.setSQWRate(SQW_RATE_1); // DS3231から1秒パルスを、 rtc.setOutput(OUTPUT_SQW); // SQWピンに出力 rtc.enable32KHz(true); // 32kHzも出力 oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); // アドレス 0x3C (0x78) oled.setTextColor(WHITE); // 白文字で描く oled.setTextSize(2); // 2倍角文字で表示 if (digitalRead(entPin) == LOW) { // 起動時にEnt.ボタンが押されていたら clockAdjust(); // OLED画面と+, -, Ent.ボタンで時刻合わせ。 } if (digitalRead(incPin) == LOW) { // 起動時にInc.(+)ボタンが押されていたら agingAdjust(); // エージング値の調整。(時計の歩度の微調整) } // adjClock(); // 決め打ちで時刻合わせする場合はこっちを使う set_sleep_mode(SLEEP_MODE_PWR_DOWN); // スリープはパワーダウンモードで行う } void loop() { waitExtIRQ(); // RTCからの1秒パルスをスリープ(パワーダウンモード)で待つ digitalWrite(ledPin, HIGH); t = rtc.getTime(); // Time変数の値を取得(注:下記と別関数なので内容の同一性が保証出来ていない) ymd = rtc.getDateStr(FORMAT_LONG, FORMAT_BIGENDIAN, '/'); // 年月日を文字列(yyyy/mm/dd)で取得 hms = rtc.getTimeStr(FORMAT_LONG); // 時刻を文字列(hh:mm:dd 形式)で取得 dayOfWeek = rtc.getDOWStr(FORMAT_SHORT); rtcTemp = rtc.getTemp(); // 温度を取得 Serial.print(ymd); Serial.print(", "); Serial.print(hms); Serial.print(", "); Serial.println(rtcTemp); oled.clearDisplay(); // 0.96インチOLEDに表示 oled.setCursor(0 + xS, 0); oled.print(ymd); // 年月日表示 oled.setCursor(24 + xS, 16); oled.print("("); oled.print(dayOfWeek); oled.print(")"); // 曜日を表示 oled.setCursor(12 + xS, 32); oled.println(hms); // 時刻表示 oled.setCursor(24 + xS, 48); oled.print(rtcTemp, 1); // 温度表示 oled.print("C"); oled.display(); // OLEDに表示(データーを転送) Serial.flush(); digitalWrite(ledPin, LOW); } void clockAdjust() { // OLEDとボタンスイッチで時刻を合わせる oled.clearDisplay(); oled.setCursor(0, 0); oled.println("Time adj."); // 時刻合わせ開始表示 oled.display(); while (digitalRead(entPin) == LOW) { // entボタンが離されるまで待つ } t = rtc.getTime(); // Time変数の値((year,mon,date,hour,min,sec)を取得 ymd = rtc.getDateStr(FORMAT_LONG, FORMAT_BIGENDIAN, '/'); // 年月日を文字列(yyyy/mm/dd)で取得 hms = rtc.getTimeStr(FORMAT_LONG); // 時刻を文字列(hh:mm:dd 形式)で取得 oled.clearDisplay(); // 画面を消して oled.setCursor(0 + xS, 0); oled.println(ymd); // 現在の年月日を表示 Serial.println(ymd); hms[6] = '-'; // 秒の桁を--に差し替え hms[7] = '-'; oled.setCursor(12 + xS , 24); // 2行目に oled.println(hms); // 時刻表示 oled.display(); // x, y座標, 値, ステップ, 下限, 上限を指定して時計の設定値を入力 t.year = oledRW(24 + xS, 0, t.year - 2000, 1, 10, 49) + 2000; // 年の値を入力 t.mon = oledRW(60 + xS, 0, t.mon, 1, 1, 12); // 月の入力 if (t.mon == 2) { // 2月で if ((t.year % 4) == 0 ) { t.date = oledRW(96 + xS, 0, t.date, 1, 1, 29); // 閏年なら29日まで } else { t.date = oledRW(96 + xS, 0, t.date, 1, 1, 28); // 閏年でなければ28日まで } } else if ((t.mon == 4) || (t.mon == 6) || (t.mon == 9) || (t.mon == 11)) { t.date = oledRW(96 + xS, 0, t.date, 1, 1, 30); // 4,6,9,11月なら30日まで } else { t.date = oledRW(96 + xS, 0, t.date, 1, 1, 31); // それ以外なら31日まで } t.hour = oledRW(12 + xS, 24, t.hour, 1, 0, 23); // 時 t.min = oledRW(48 + xS, 24, t.min, 1, 0, 59); // 分 rtc.setDate(t.date, t.mon, t.year); // 年月日を書き込み rtc.setTime(t.hour, t.min, 0); // 時分秒を書き込み rtc.setDOW(zeller(t.year, t.mon, t.date)); // ツェラーの式で曜日を書き込み delay(10); } int oledRW(int x, int y, int d, int stepD, int minD, int maxD) { // OLEから値を入力 // OLEDの指定位置に2桁右詰めで変数の値を表示。ボタン操作で値を増減し、 // Ent入力で値を確定し戻り値として返す。表示位置の左上をx, y 座標で指定 // 操作位置は下線で表示。値は上下限の範囲でサーキュレート。文字サイズは2倍角(12x16画素) // 引数:x座標、y座標、変更したい変数、変更ステップ量、下限値、上限値 oledDisp2Chr(x, y, d); // 画面の指定位置に数値を下線付きで2桁表示する while (digitalRead(entPin) == LOW) { // enterボタンが押されていたら離されるまで待つ } delay(30); while (digitalRead(entPin) == HIGH) { // enterボタンが押されるまで以下を実行 if (digitalRead(incPin) == 0) { // + ボタンが押されていたら d = d + stepD; // x を指定ステップ増加 if (d > maxD) { // 上限超えたら下限へサキュレート d = minD; } oledDisp2Chr(x, y, d); // 画面の指定位置に数値を2桁表示(下線付き) while (digitalRead(incPin) == 0) { // + ボタンが離されるまで待つ } delay(30); } if (digitalRead(decPin) == 0) { // - ボタンが押されていたら d = d - stepD; // x を指定ステップ減らす if (d < minD) { // 下限以下なら上限へサーキュレート d = maxD; } oledDisp2Chr(x, y, d); // 画面の指定位置に数値を2桁表示(下線付き) while (digitalRead(decPin) == 0) { // - ボタンが離されるまで待つ } delay(30); } } oled.drawFastHLine(x, y + 15, 24, BLACK); // アンダーラインを消す(画面への反映は次の表示) delay(30); return d; // 戻り値 } void oledDisp2Chr(int x, int y, int val) { // OLEDの指定場所に2桁の値を表示 oled.fillRect(x, y, 24, 16, BLACK); // 指定座標から2文字分消す(黒塗り) oled.drawFastHLine(x, y + 15, 24, WHITE); // 下線を引く sprintf(buff, "%02d", val); // データーを10進2桁0フィル文字列に変換 oled.setCursor(x, y); // カーソルを指定位置に oled.print(buff); // 数値を書き込み oled.display(); // 画面に表示 } int zeller(int y, int m, int d) { // ツェラーの式で曜日を計算 if (m <= 2) { m += 12; y--; } // DS3231の仕様(月曜日=1、日曜日=7)に合わせるために補正している return 1 + (y + y / 4 - y / 100 + y / 400 + (13 * m + 8 ) / 5 + d - 1) % 7; } void agingAdjust() { // エージングレジスタの設定(時計の歩度調整) int8_t agingV; oled.clearDisplay(); oled.setCursor(0, 0); oled.println(F("Set Aging")); // 開始メッセージ oled.display(); while (digitalRead(incPin) == LOW) { // entボタンが離されるまで待つ } oled.println(F(" Offset=")); agingV = readAging(); // エージングレジスタの値を読み出し oled.fillRect(36, 32, 48, 16, BLACK); // 指定座標から4文字分黒塗りして消す oled.setCursor(36, 32); oled.print(agingV); // 初期値を表示 oled.display(); while (digitalRead(entPin) == LOW) { // enterボタンが押されていたら離されるまで待つ } delay(30); while (digitalRead(entPin) == HIGH) { // enterボタンが押されるまで以下を繰り返す if (digitalRead(incPin) == 0) { // + ボタンが押されていたら agingV++; // 値を増加 if (agingV > 120) { // 上限で止める agingV = 120;; } oled.fillRect(36, 32, 48, 16, BLACK); // 指定座標から4文字分黒塗りで消す oled.setCursor(36, 32); oled.print(agingV); oled.display(); while (digitalRead(incPin) == 0) { // + ボタンが離されるまで待つ } delay(30); } if (digitalRead(decPin) == 0) { // - ボタンが押されていたら agingV--; // 値を減らす if (agingV < -120) { // 下限で止める agingV = -120; } oled.fillRect(36, 32, 48, 16, BLACK); // 指定座標から4文字分黒塗りで消す oled.setCursor(36, 32); oled.print(agingV); oled.display(); while (digitalRead(decPin) == 0) { // - ボタンが離されるまで待つ } delay(30); } } writeAging(agingV); // エージングレジスタの値を書き換え rtcConv(); // 変更を即時反映させる(これやらないと反映は64秒後) delay(10); } byte readAging() { // エージングレジスタの値を読み出す(ライブラリにこの機能は無い) byte x; Wire.beginTransmission(0x68); // I2Cアドレス指定 Wire.write(0x10); // Aging offset レジスタを指定 Wire.endTransmission(); Wire.requestFrom(0x68, 1); // RTCから1バイトデーターリクエスト x = Wire.read(); // データ読み出し return x; } void writeAging(byte x) { // エージングレジスタに値を書き込み(ライブラリにこの機能は無い) Wire.beginTransmission(0x68); // I2Cアドレス指定 Wire.write(0x10); // Aging offset レジスタを指定 Wire.write(x); // データ書き込み Wire.endTransmission(); } void rtcConv() { // 強制温度補正を実行(エージングの値を即時反映) byte x; Wire.beginTransmission(0x68); // RTCアドレスを指定して Wire.write(0x0E); // コントロール レジスタ指定 Wire.endTransmission(); Wire.requestFrom(0x68, 1); // 1バイト読み出し x = Wire.read(); // データ読み出し x = x | 0b00100000; // CONVビットを1にして Wire.beginTransmission(0x68); Wire.write(0x0E); // コントロール レジスタ指定 Wire.write(x); // 書き込み Wire.endTransmission(); delay(20); // ちょっと待って x = x & 0b11011111; // CONVビットを0にして Wire.beginTransmission(0x68); Wire.write(0x0E); // コントロール レジスタ Wire.write(x); // 書き込み Wire.endTransmission(); } void adjClock() { // 時刻の決め打ち設定 // rtc.setDOW(WEDNESDAY); // 曜日の設定 rtc.setDate(26, 6, 2019); // 日、月、年の設定 rtc.setTime(20, 19, 0); // 時、分、秒の設定 } void waitExtIRQ() { ADCSRA &= ~(1 << ADEN); // ADENビットをクリアしてADCを停止(120μA節約) attachInterrupt(0, rtcIRQ, FALLING); // Pin2のネガエッジで割込み sleep_mode(); // 指定したモードでスリープ detachInterrupt(0); // ここでリープから復活 ADCSRA |= (1 << ADEN); // ADC動作再開 } void rtcIRQ() { // IRQ0(pin2)割込み処理 }