08.T-Watchの時刻合わせ


08.T-Watchの時刻合わせ

説明

注意:この機種は技適が通っていません。中国で動作確認したスケッチとメモを日本で整理してブログ発信しています。

時刻を手動修正します。
年月日時分の各項目を年から順に数字をup/downしてSETで確定します。すると次の項目へ移動し、最後に分を確定すると時計へ戻ります。これは、前回のスケッチに追加します。

off状態
↓電源ボタン2秒(通常起動)
時計画面
↓タッチ
メニュー画面
↓1行2列目をタッチ
時刻修正画面
↓SETを5回押す。時刻等を修正する場合は、SETを押す前に+-をタッチ。デモではSETのみタッチしています
時計画面

各RTC用ICによる精度

Copilotの回答より

・DS3231:±2ppm(0~40℃), ±3.5ppm(-40~85℃)
 内蔵温度補償により、温度変化に対して高い精度
 バックアップ電流:1.5uA(典型的)
 30日(=30日x24Hx60分x60秒)x±2/1000000=±5秒/月

・PCF8563:±5ppm(0~40℃)
 低消費電力で、バッテリー駆動のデバイスに適しています
 内蔵温度補償無し
 バックアップ電流:0.25uA(典型的)
 30日(=30日x24Hx60分x60秒)x±5/1000000=±13秒/月

・DS1307:±20ppm(0~40℃)
 低コストで広く使用されています
 30日(=30日x24Hx60分x60秒)x±20/1000000=±52秒/月

T-WatchのRTC

T-WatchにはRTC機能があるので、一度時刻を合わせればバッテリーが無くなるまで電源offでも時刻は動作しています。RTCで使用しているICのPCF8563は、いろいろなメーカが作っています。
https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf

https://www.digikey.jp/ja/htmldatasheets/production/97864/0/0/1/pcf8563

・発振器内蔵
・バックアップ電流:0.25uA(250nA)typ(3.0V,25℃時) p28より
・温度が一定で、電源電圧のみ変動時の相対発振周波数変動は、
 0.2ppm(ΔVdd=200mV,25℃) p34より
 これは、
 30日(=30日x24Hx60分x60秒)x0.2ppm(=0.2/100万)=0.5秒
 30日で0.5秒の精度です。

時刻の精度は、環境により発振周波数が一定であることです。

参考 他機種のRTCは、
BM8563:M5CoreInk, M5StickC PLUS
 無し:M5Stack Basic, M5Atom Lite
です。

RTCの精度の資料

外部温度センサーを使用したPCF8563による時計精度の向上
https://www.nxp.com.cn/docs/en/application-note/AN10652.pdf
NXPのアプリケーションノート AN10652 2007/11/2改の4-5頁の一部を自動翻訳すると、

上記の式(1)には、温度の関数として周波数応答に影響を与える3つの変数がある。放物線係数B、ターンオーバー温度T0、室温オフセットfoffである。
水晶振動子メーカーはこれらのパラメーターを指定しており、典型的な値はT0=25℃、∆T0=±5℃、foff=30ppmです。
係数Bは、1つのタイプの様々な結晶に対して非常に小さな広がりを持ちますが、温度の関数としての周波数偏差の放物線的性質に最も大きな影響を与えます。
ターンオーバー温度T0の変動は偏差曲線を左右にシフトさせ、室温でのオフセットの変動は上下にシフトさせます。実際には、室温でのT0とオフセットのばらつきの組み合わせにより、室温での精度は±30ppmとなり、これは年間約15分の時間偏差に相当する。さらに温度の影響もある。Bの代表的な値は-0.035ppm/℃²~-0.04ppm/℃²である。実際のアプリケーションでは、B=-0.04を使用すると、通常の32.768kHz音叉型水晶振動子を使用して製造されたクロックは、室温ではT0とfoffの変動から生じる周波数偏差を持つだけですが、室温より10℃高い(or 低い)温度では年間2分、室温より20℃高い(or 低い)温度では年間8分追加で損失することを意味します。実時間とppmの関係をより感覚的に理解するために、1s/day速く動きすぎる時計の精度は1/(3600*24)=11.57ppmであり、1s/weekの偏差は1.65ppmを意味する。

スケッチ

"icon9.h"も同一ホルダに必要です。

// 08timeSet.ino
// 時計の時刻合わせ 手動 void apptimeSet()を追加
#include "icon9.h"  // 3x3アイコンpng1枚
// 1:目覚まし時計,2:時計+手,3:袋,4:時計+wifi
// 5:太陽,6:歯車,7:トレイ,8:風車,9:戻る
#define LGFX_AUTODETECT                  // 機種の自動認識
#include <LovyanGFX.hpp>                 // ヘッダをinclude
#include <LGFX_AUTODETECT.hpp>           // クラス"LGFX"を用意
static LGFX lcd;                         // LGFXのインスタンスを作成
#define LILYGO_WATCH_2020_V3             // T-Watch2020v3を使用
#define LILYGO_WATCH_HAS_MOTOR           // 振動モータを使用
#include <LilyGoWatch.h>                 // LilyGoWatchを使用
TTGOClass *ttgo;                         //
AXP20X_Class *power;                     // 電源管理 AXP202
PCF8563_Class *rtc;                      // PCF853 RTC(Real-Time Clock)
extern const unsigned char img[];        // 表示png画像1枚
int8_t Mcase = 0;                        // メニュー番号
int16_t x, y;                            // タッチ位置変数
uint8_t mmonth, dday, week, hh, mm, ss;  // 時分秒等の変数 0-255
uint16_t yyear;                          // 年は16bit整数 0-65535
char dayWeek[7][4] = { "土", "日", "月",
                       "火", "水", "木", "金" };  // 曜日
uint8_t timeSetNo = 1;                            // 1:年,2:月,3:日,4:時,4:分
uint8_t touchNo;                                  // 1:上,2:SET,3:下

void SetFont() {                            // Font等初期化関数
  ttgo = TTGOClass::getWatch();             // ttgoに略します
  ttgo->begin();                            // 本体の初期化
  lcd.init();                               // LCDの初期化
  lcd.setRotation(2);                       // 画面回転 0-3 (4-7で上下反転)
  lcd.setBrightness(128);                   // バックライト輝度 0-255(実際は256通りの輝度ではない)
  lcd.fillScreen(TFT_BLACK);                // 全画面黒
  lcd.setFont(&fonts::lgfxJapanGothic_28);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
  lcd.setTextColor(TFT_GREEN, TFT_BLACK);   // (緑字,黒地)
  lcd.setTextSize(1);                       // 文字サイズの倍数
  lcd.setTextWrap(true);                    // 自動折返し
}

void printBT() {                                // バッテリー残量表示関数
  lcd.setCursor(185, 0);                        // カーソル位置
  lcd.setTextSize(0.7);                         // 文字サイズ(倍)
  uint8_t per = power->getBattPercentage();     // 電池%測定
  if (power->isChargeing()) {                   // 充電中の時
    lcd.setTextColor(TFT_CYAN, TFT_BLACK);      // (水字,黒地)
    lcd.print("充");                            // 画面表示
  } else {                                      // 充電中でない時
    lcd.print(" ");                            // 画面表示
    if (per <= 20) {                            // 0-20%時
      lcd.setTextColor(TFT_RED, TFT_BLACK);     // (赤字,黒地)
    } else if (per <= 40) {                     // 21-40%
      lcd.setTextColor(TFT_YELLOW, TFT_BLACK);  // (黄字,黒地)
    } else if (per <= 80) {                     // 41-80%時
      lcd.setTextColor(TFT_GREEN, TFT_BLACK);   // (緑字,黒地)
    } else {                                    // 81-100%時
      lcd.setTextColor(TFT_WHITE, TFT_BLACK);   // (白字,黒地)
    }
  }
  lcd.printf("%3d%%", per);  // 電池%表示
  lcd.setTextSize(1);        // 文字サイズを戻す
}

void RTCread() {                             // RTCのデータを読込む
  RTC_Date tnow = ttgo->rtc->getDateTime();  // 現在のデータを取得
  yyear = tnow.year;                         // 年
  mmonth = tnow.month;                       // 月
  dday = tnow.day;                           // 日
  hh = tnow.hour;                            // 時
  mm = tnow.minute;                          // 分
  //ss = tnow.second;                        // 秒 使用しない

  uint16_t yearA = yyear;  //ツェラーの公式 年月日から曜日weekを求める
  uint8_t monthA = mmonth;
  if (monthA < 3) {
    monthA += 12;
    yearA--;
  }
  uint16_t year1 = yearA / 100;
  uint16_t year2 = yearA % 100;
  week = (dday + (int)(26 * (monthA + 1) / 10) + year2 + (int)(year2 / 4)
          + (5 * year1) + (int)(year1 / 4))
         % 7;
}

//-------------------------------------------------// 0
void appWatch() {             // Mcase=0 時計
  lcd.fillScreen(TFT_BLACK);  // 全画面黒
  RTCread();                  //
  do {
    lcd.setCursor(5, 40);                     // カーソル位置
    lcd.setTextColor(TFT_GREEN, TFT_BLACK);   // (字,地)
    lcd.setFont(&fonts::Font7);               // Font 7セグ
    lcd.print(ttgo->rtc->formatDateTime());   // 時:分:秒の文字列表示
    lcd.setFont(&fonts::lgfxJapanGothic_28);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    printBT();                                // バッテリー残量表示関数へ
    if (ttgo->getTouch(x, y)) {               // タッチしたら
      ttgo->motor->onec();                    // モータを振動(0.2秒程度)
      delay(250);                             // 250mS待つ 連続:-200 不連続:250-
      Mcase = 10;                             // メニューへ
    }
  } while (Mcase == 0);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 10
void appMenu() {                                  // Mcase=10 アイコンメニュー
  lcd.drawPng((std::uint8_t *)img, 34644, 7, 7);  // png表示 (配列名,サイズ,x,y)
  delay(1);                                       // 1mS待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたらx,yを読取る
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      if (y < 80) {              // 1行目なら
        if (x < 80) {            // 1行1列目なら
          Mcase = 1;             // 1へ
        } else if (x < 160) {    // 1行2列目なら
          Mcase = 2;             // 2へ
        } else {                 // 1行3列目なら
          Mcase = 3;             // 3へ
        }
      } else if (y < 160) {    // 2行目なら
        if (x < 80) {          // 2行1列目なら
          Mcase = 4;           // 4へ
        } else if (x < 160) {  // 2行2列目なら
          Mcase = 5;           // 5へ
        } else {               // 2行3列目なら
          Mcase = 6;           // 6へ
        }
      } else {                 // 3行目なら
        if (x < 80) {          // 3行1列目なら
          Mcase = 7;           // 7へ
        } else if (x < 160) {  // 3行2列目なら
          Mcase = 8;           // 8へ
        } else {               // 3行3列目なら
          Mcase = 9;           // 9へ
        }
      }
    }
  } while (Mcase == 10);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 1
void app1() {                              // 1.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー1");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 1);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 2
void apptimeSet() {                        // 2.時刻合わせ 手動
  RTCread();                               // RTCデータを読込む
  lcd.setTextSize(1.6);                    // 文字サイズの倍数
  lcd.setCursor(20, 150);                  // カーソル位置 タッチ表示用
  lcd.setTextColor(TFT_GREEN, TFT_BLACK);  // (字,地)
  lcd.print("+ SET -");                  // 表示
  timeSetNo = 1;                           // 年から始める

  do {
    delay(100);                              // 100mS待つ
    lcd.setTextSize(1.6);                    // 文字サイズの倍数
    lcd.setCursor(10, 10);                   // カーソル位置 年月日
    if (timeSetNo == 1) {                    // 年(変更場所)
      lcd.setTextColor(TFT_RED, TFT_BLACK);  // (字,地)
    } else {
      lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    }
    lcd.printf("%4d", yyear);                // 表示 年
    lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    lcd.print("/");                          // 表示
    if (timeSetNo == 2) {                    // 月
      lcd.setTextColor(TFT_RED, TFT_BLACK);  // (字,地)
    } else {
      lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    }
    lcd.printf("%02d", mmonth);              // 表示 月
    lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    lcd.print("/");                          // 表示
    if (timeSetNo == 3) {                    // 日
      lcd.setTextColor(TFT_RED, TFT_BLACK);  // (字,地)
    } else {
      lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    }
    lcd.printf("%02d", dday);                // 表示 日
    lcd.setCursor(60, 70);                   // カーソル位置 時分
    if (timeSetNo == 4) {                    // 時
      lcd.setTextColor(TFT_RED, TFT_BLACK);  // (字,地)
    } else {
      lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    }
    lcd.printf("%02d", hh);                  // 表示 時
    lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    lcd.print(":");                          // 表示
    if (timeSetNo == 5) {                    // 分
      lcd.setTextColor(TFT_RED, TFT_BLACK);  // (字,地)
    } else {
      lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
    }
    lcd.printf("%02d", mm);  // 表示 分
    lcd.setTextSize(1);      // 文字サイズの倍数

    if (ttgo->getTouch(x, y)) {  // タッチしたらx,yを読取る
      delay(100);                // 100mS待つ
      if (x < 80) {              // 左側
        touchNo = 1;             // +
      } else if (x < 160) {      // 中央
        touchNo = 2;             // SET
      } else {                   // 右側
        touchNo = 3;             // -
      }
      switch (timeSetNo) {            // する動作の場所
        case 1:                       // 年
          if (touchNo == 1) {         // +
            yyear++;                  // 1up
          } else if (touchNo == 3) {  // -
            yyear--;                  // 1down
          } else {                    // SET
            timeSetNo++;              // 次の設定
          }
          break;
        case 2:                       // 月
          if (touchNo == 1) {         // +
            mmonth++;                 // 1up
          } else if (touchNo == 3) {  // -
            mmonth--;                 // 1down
          } else {                    // SET
            timeSetNo++;              // 次の設定
          }
          break;
        case 3:                       // 日
          if (touchNo == 1) {         // +
            dday++;                   // 1up
          } else if (touchNo == 3) {  // -
            dday--;                   // 1down
          } else {                    // SET
            timeSetNo++;              // 次の設定
          }
          break;
        case 4:                       // 時
          if (touchNo == 1) {         // +
            hh++;                     // 1up
          } else if (touchNo == 3) {  // -
            hh--;                     // 1down
          } else {                    // SET
            timeSetNo++;              // 次の設定
          }
          break;
        case 5:                                                       // 分
          if (touchNo == 1) {                                         // +
            mm++;                                                     // 1up
          } else if (touchNo == 3) {                                  // -
            mm--;                                                     // 1down
          } else {                                                    // SET
            ttgo->rtc->setDateTime(yyear, mmonth, dday, hh, mm, 30);  // RTCに書込む
            Mcase = 0;                                                // 時計画面へ
          }
          break;
        default:  // それ以外
          break;
      }
    }
  } while (Mcase == 2);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 3
void app3() {                              // 3.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー3");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 3);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 4
void app4() {                              // 4.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー4");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 4);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 5
void app5() {                              // 5.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー5");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 5);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 6
void app6() {                              // 6.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー6");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 6);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 7
void app7() {                              // 7.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー7");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 7);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 8
void app8() {                              // 8.未作成
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
  lcd.print("メニュー8");                  // 表示
  delay(1000);                             // 1秒待つ
  do {
    delay(100);                  // 0.1秒待つ
    if (ttgo->getTouch(x, y)) {  // タッチしたら
      ttgo->motor->onec();       // モータを振動(0.2秒程度)
      delay(250);                // 250mS待つ 連続:-200 不連続:250-
      Mcase = 0;                 // 時計画面へ
    }
  } while (Mcase == 8);  // Mcaseが変わるまでループ
}

//-------------------------------------------------// 9
void appReturn() {  // 9.時計画面へ戻る
  Mcase = 0;        // 時計画面へ
}

//-------------------------------------------------//
void setup() {
  SetFont();            // Font等初期化関数へ
  power = ttgo->power;  // 簡単に書けるようにオブジェクトを受取る
  ttgo->motor_begin();  // 振動モータの初期設定
}
void loop() {
  switch (Mcase) {  // 表示状態
    case 0:
      appWatch();  // 0.時計へ
      break;
    case 1:
      app1();  // 1.未作成
      break;
    case 2:
      apptimeSet();  // 2.時刻合わせ
      break;
    case 3:
      app3();  // 3.未作成
      break;
    case 4:
      app4();  // 4.未作成
      break;
    case 5:
      app5();  // 5.未作成
      break;
    case 6:
      app6();  // 6.未作成
      break;
    case 7:
      app7();  // 7.未作成
      break;
    case 8:
      app8();  // 8.未作成
      break;
    case 9:
      appReturn();  // 9.時計画面へ戻る
      break;
    case 10:
      appMenu();  // 10.メニュー画面へ
      break;
    default:  // それ以外
      break;
  }
  delay(100);                 // 100mS待つ
  lcd.fillScreen(TFT_BLACK);  // 全画面黒
}
* flash memory(6.5Mbyte)のうち、スケッチが13%使用。RAM(4.5Mbyte)のうち、global変数が0%使用、local変数で4.5Mbyte使用可能。(1000byte=1kbyteで計算)