14.picoでNTP時計 Arduino


14.picoでNTP時計 Arduino

モニタへ時刻表示します。起動時にwifi接続し、1度だけNTPサーバーから時刻を取得します。その後RTCで時刻更新します。

ハード

・pico
・wifi : Pico-ESP8266
・モニタ : Pico-ResTouch-LCD-3.5

参照

・RTCの内蔵時計(RP2040_RTCライブラリ)で、"13.picoでRTCサンプル動作 Arduino"参照
・NTPの時刻取得(ESP8266_AT_WebServe)で、"12.picoでNTPサンプル動作 Arduino"参照
・モニタ表示(TFT_eSPIライブラリ)で、"09.picoでモニタ表示 Arduino編"参照

カラー

TFT_eSPI Ver2.4.72
https://www.arduino.cc/reference/en/libraries/tft_espi/

Bodmer/TFT_eSPI
https://github.com/Bodmer/TFT_eSPI/blob/master/TFT_eSPI.h
より、25色定義されています。

0x0000 0, 0, 0 TFT_BLACK 黒
0x000F 0, 0, 128 TFT_NAVY 濃紺
0x001F 0, 0, 255 TFT_BLUE 青
0x03E0 0, 128, 0 TFT_DARKGREEN 深緑
0x03EF 0, 128, 128 TFT_DARKCYAN 暗いシアン
0x07E0 0, 255, 0 TFT_GREEN 緑
0x07FF 0, 255, 255 TFT_CYAN 水色に近い青緑
0x7800 128, 0, 0 TFT_MAROON 栗色
0x780F 128, 0, 128 TFT_PURPLE 赤紫
0x7BE0 128, 128, 0 TFT_OLIVE 暗い緑みの黄
0x7BEF 128, 128, 128 TFT_DARKGREY 濃い灰色
0x867D 135, 206, 235 TFT_SKYBLUE 空色
0x9A60 150, 75, 0 TFT_BROWN 茶
0x915C 180, 46, 226 TFT_VIOLET 青紫
0xB7E0 180, 255, 0 TFT_GREENYELLOW 黄緑
0xC618 192, 192, 192 TFT_SILVER 銀
0xD69A 211, 211, 211 TFT_LIGHTGREY 淡い灰色
0xF800 255, 0, 0 TFT_RED 赤
0xF81F 255, 0, 255 TFT_MAGENTA 赤紫
0xFC9F 255, 146, 255 TFT_Lighter pink より淡いピンク
0xFDA0 255, 180, 0 TFT_ORANGE 橙
0xFE19 255, 192, 203 TFT_PINK ピンク
0xFEA0 255, 215, 0 TFT_GOLD 金
0xFFE0 255, 255, 0 TFT_YELLOW 黄
0xFFFF 255, 255, 255 TFT_WHITE 白

0000-0xFFFFの16bit 65536色でRGB565は、15-11の5bitは赤, 10-5の6bitは緑, 4-0の5bitは青となります。通常の24bitカラー RGB888 のRとBの下位3bit、Gの下位2bitを切り捨てています。RGの場合、0~111(2)→0(2), 1000(2)→1(2), FF(16)→1F(16)となります。
しかし、ここではなぜか10が反転しています。

時分表示の色の変化

スケッチで時計の時分表示を256色変化させています。256を3等分し、刻みは3にします。R(赤)G(緑)B(青)の1周85ループで、1色を0で固定し、他の1色を255から3まで下げ、残りの色を0から252まで上げていきます。周の切替わりは色が大きく変わらないようにします。

___1周目_, 2周目_, 3週目
(R)255→3, 0_____, 0→252
(G)0_____, 0→252, 255→3
(B)0→252, 255→3, 0

ただし、RGB888で計算していますが、実際はRGB565なので、RとBの3,6の時は0で、Gの3の時も0のはずです。

使用している通信

Serial(=SerialUSB) : PC
Serial1(=UART 0) : ESP8266(GP0-1)
Serial2(=UART 1) :  -
SPI0 :  -
SPI1 : モニタ(GP8-13,15)
I2C0 :  -
I2C1 :  -
SD : (GP5,18-22)
Touch : (GP16-17)
空き : GP2-4,6-7,14,23-28

書込みが出来ない

現象

書込みが出来なくなり、シリアルポートも開けなくなってしまいました。

表示

・Resetting COM15 (COM15のリセット)
・スケッチの書き込み中にエラーが発生しました
・Converting to uf2, output size: 102912, start address: 0x2000 (uf2に変換、出力サイズ:102912、開始アドレス:0x2000)(uf2ファイルはできている模様)
・No drive to deploy.(展開するドライブがありません。)

対策

ドライブが無いというので、
1.シリアルモニタを閉じる。
2.picoのBOOTSELスイッチを押しながらUSBを接続し、スイッチを離す。
3.スケッチをマイコンボードに書込む。
4....NEW.UF2 が表示されたら早くシリアルモニタを開く。
5.「シリアルポートが開けません。」と表示されたら、再度シリアルモニタを開く。
これで、今のところ対処できています。

スイッチ一覧

pico

●BOOTSEL (プッシュSW) CN近く
押しながらUSBケーブルを接続するとpicoがドライブになる。 (FLASHのChip Select端子をopen→1kΩでGNDに接続)

モニタ

Pico-ResTouch-LCD-3.5
●RUN (プッシュSW) CNを上にして左面中央に
USBの抜き差しの代わり (picoのRUNをGNDに接続)
 1.電池基板のRESET SWを押し続けたまま
 2.picoのBOOTSEL SWを押し続け
 3.1.を離し、2.を離す。

wifi

Pico ESP8266
●IO0 (プッシュSW)
ESP8266に書込時に使用 (ESP88266のGP0をGNDに接続 プルアップ)

●RST (プッシュSW)
ESP8266に書込時に使用 (ESP8266のRSTをGNDに接続 プルアップ)

* ESP8266へ書込みは、IO0ボタンを押したまま、ESP8266のRESETボタンを押す。

電池基板

●RESET(プッシュSW) 基板裏面でCNと反対側
モニタのRUNと同じにUSBの抜き差しの代わり (picoのRUNをGNDに接続)

●POWER(スライドSW)
(電池の+側と昇圧ICのEN端子間に接続)

スケッチ

"**********"の2ヶ所は自分のssidとそのパスワードを記入してください。

// tokei.ino  (Udpは1日480回(=3分に1回)以上受信しない事)
#ifndef defines_h                    // 定義がされてなければ
#define defines_h                    // 定義する
#include <ESP8266_AT_WebServer.h>    // ESP8266のAT_WebServerを使用
#include <RP2040_RTC.h>              // RP2040_RTCを使用
char ssid[] = "**********";          // 自分のSSID
char pass[] = "**********";          // そのパスワード
#define DEBUG_ESP8266_AT_WEBSERVER_PORT Serial  // 定義
#define _ESP_AT_LOGLEVEL_  0         // デバックレベル(0-4)
#define SHIELD_TYPE  "ESP8266-AT & ESP8266_AT_WebServer Library" // 定義
#define EspSerial  Serial1           // picoは1と2がある
#endif                               // defines_h

#include "ESP8266_AT_Udp.h"          // ESP8266ATコマンド用UDP
#include <SPI.h>                     // SPI通信LCD使用
#include <TFT_eSPI.h>                // ハードウェア固有のライブラリ
int status = WL_IDLE_STATUS;//WiFi.begin()が呼び出された時に割当てられる一時的ステータス
char timeServer[]  = "ntp.nict.jp"; // 情報通信研究機構のNTPサーバー
unsigned int localPort    = 2390;   // UDPパケットを受信するローカルポート(ESP8266側)
const int NTP_PACKET_SIZE = 48;     // NTPタイムスタンプは 最初の48バイトに含まれる
const int UDP_TIMEOUT     = 2000;   // UDPパケットの到着タイムアウト(mS)
byte packetBuffer[48];              // 入出力パケットを保持するバッファ
char buf[32];                       // 時刻表示文字用
ESP8266_AT_UDP Udp;                 // UDPでパケットを送受信するためのUDPインスタンス
TFT_eSPI tft = TFT_eSPI();          // カスタムライブラリを呼出す
int i = 0, c = 0, R, G, B, hour12;  // i&c:色計算,RGB=0-255,hour12:12時間表示
time_t      timep;                  // timepをtime_t型変数に定義
struct tm   *time_inf;              //

void sendNTPpacket(char *ntpSrv) {  // 指定タイムサーバーにNTPリクエストを送信する関数
  memset(packetBuffer, 0, 48);//バッファの全byteを0 (メモリのポインタ,セット値,セットサイズ)
  //NTPリクエストの形成に必要な値を初期化(パケットの詳細は上記URL参照)
  packetBuffer[0] = 0b11100011; // LI=0b11(時刻同期無し),Ver=0b100(現在),Mode=0b011(Client)
  packetBuffer[1] = 0;          // 階層=0(取扱わず)
  packetBuffer[2] = 6;          // パケット送出最大間隔 2^6=64秒
  packetBuffer[3] = 0xEC;       // 精度 符号付EC(16)=-20(10) 2^-20=0.95uS
  //                               ルート遅延[4]-[ 7]の4byte(32bit)は0
  //                               ルート拡散[8]-[11]の4byte(32bit)は0
  packetBuffer[12] = 49;        // 参照識別子32bit [49,78,40,52]?
  packetBuffer[13] = 0x4E;      //
  packetBuffer[14] = 49;        //
  packetBuffer[15] = 52;        // これで、全NTPフィールドを設定完了
  Udp.beginPacket(ntpSrv, 123); // 接続開始し UDPデータをリモート接続に書込む。NTPのリクエストは123番ポート
  Udp.write(packetBuffer, 48);  // UDP送信データをリモート接続に書込む
  Udp.endPacket();              // パケットを終了して送信
}

void setGamen() {
  tft.init();                // TFT_eSPIライブラリを初期化
  tft.setRotation(1);        // 回転 0:縦,1:左が下,2:逆さま,3:右が下 (CNを上にして)
  tft.invertDisplay(1);      // なぜか色反転しないとだめ
  tft.fillScreen(TFT_BLACK); // 黒地
}

void setWifi() {
  Serial.println(BOARD_NAME);               // "RASPBERRY_PI_PICO"と表示
  Serial.println(SHIELD_TYPE); // "ESP8266-AT & ESP8266_AT_WebServer Library"と表示
  Serial.println(ESP8266_AT_WEBSERVER_VERSION);//"ESP8266_AT_WebServer v1.5.4"と表示
  EspSerial.begin(115200);                  // ESPモジュールのシリアル初期化
  WiFi.init(&EspSerial); // ESPモジュールの初期化 "[ESP_AT] Use ES8266-AT Command"と表示
  if (WiFi.status() == WL_NO_SHIELD) {      // wifi状態でシールドが無ければ
    Serial.println("WiFiシールドがありません"); // 表示
    while (true);                           // 永久ループ
  }
  WiFi.begin(ssid, pass);                   // ネットに接続
  while (WiFi.status() != WL_CONNECTED) {   // WiFiに接続して無ければ
    delay(500);                             // 0.5秒待つ
    Serial.print(".");                      // 表示
  }
  //Serial.println(WiFi.localIP());         // ESP8266のIP 表示させると止まる時がある???
  Udp.begin(localPort);                     // WiFiUDPライブラリとネット設定を初期化
}

void getNTP() {                        // 時刻を取得しセットする
  sendNTPpacket(timeServer); //タイムサーバーにNTPパケットを送信する関数へ ここのみ
  unsigned long startMs = millis();    // 現在時間をセット
  while (!Udp.available() && (millis() - startMs) < UDP_TIMEOUT) {}//データが未到着で 時間内なら待つ
  int packetSize = Udp.parsePacket();  // 次に利用可能な処理を開始しその着信パケットサイズ
  if (packetSize) {                          // パケットがあれば
    Serial.print("受信のパケットサイズ= ");      // 表示
    Serial.println(packetSize);              // "48"と表示
    Serial.print("接続先IP= ");               // 表示
    //IPAddress remoteIp = Udp.remoteIP();   // リモートIP読込み
    //Serial.print(remoteIp);                // リモートIP表示 表示させると止まる時がある???
    Serial.print(", port= ");                // 表示
    Serial.println(Udp.remotePort());        // リモートポート表示
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // パケットを受信したのでバッファに読込む
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);//送信時刻上位16bit
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);//送信時刻下位16bit
    // [40]-[47]の64bitが送信時刻で、そのうち[40]-[43]が整数秒 [44]-[47]が小数点以下
    unsigned long secsSince1900 = highWord << 16 | lowWord;//2ワードをつなげて長整数。NTP時間
    Serial.print("NTP時間(1900/1/1からの秒数)= ");      // 表示
    Serial.println(secsSince1900);                   // NTP時間(秒)
    const unsigned long seventyYears = 2208988800UL; // 70年=(70x365+閏日17)x24x60x60秒
    unsigned long JSTtime = secsSince1900 - seventyYears + 32400; // -70年+日本時間9H
    Serial.print("JST時間(1970/1/1からの秒数)= "); // 表示
    Serial.println(JSTtime);                                      // JST(秒)を表示
    setTime(JSTtime);                                             // システム時刻を設定
  }
}

void setup() {
  Serial.begin(115200);                // システムモニタ初期設定
  setGamen();                          // 画面初期設定
  while (!Serial && millis() < 5000);  // モニタを開いてなく起動5秒以内なら待つ
  setWifi();                           // wifi初期設定
  delay(200);                          // 0.2秒待つ
  rtc_init();                          // RTCシステムを初期化
  getNTP();                            // 関数へ 時刻を取得しセット
}

void loop() {
  tft.setCursor(40, 30, 4);         // (x,y,font)フォント4(26px 96文字)
  tft.setTextColor(TFT_GREEN, TFT_BLACK); // 緑字 黒地
  tft.setTextSize(2);               // 文字サイズ倍数
  time_t t = now();                 // time_t型式に変換
  sprintf(buf, "%d/%.2d/%.2d %s  ", year(t), month(t), day(t),
          dayShortStr(weekday(t))); // 時刻文字列作成
  tft.println(buf);                 // 時刻表示
  tft.setCursor(200, 90, 4);        // (x,y,font)フォント4(26px 96文字)
  if (hour(t) > 12) {               // 午後なら
    hour12 = hour(t) - 12;          // 12時間引く
    tft.println("PM");              // 表示
  } else {                          // 午前なら
    hour12 = hour(t);               // 12時間時刻変数
    tft.println("AM");              // 表示
  }
  tft.setCursor(30, 150, 7);        // (x,y,font)フォント7(48px 7セグメント)
  i = c % 255;                      // i=0-254 255=85x3
  if (i < 85) {                              // 1周目
    B = i * 3; R = 255 - B; G = 0;           // G=0で色変化
  } else if (i < 170) {                      // 2周目
    i -= 85; G = i * 3; B = 255 - G; R = 0;  // R=0で色変化
  } else {                                   // 3周目
    i -= 170; R = i * 3; G = 255 - R; B = 0; // B=0で色変化
  }
  if ((++c) == 255) c = 0;                         // 次の色
  tft.setTextColor(tft.color565(R, G, B), TFT_BLACK); // 緑字 黒地
  tft.setTextSize(3);                              // 文字サイズ倍数

  sprintf(buf, "%.2d:%.2d", hour12, minute(t));    // 時刻文字列作成
  tft.println(buf);                                // 時刻表示
  tft.setTextSize(1);                              // 文字サイズ倍数を戻す
}
* フラッシュメモリ(2Mbyte)を、スケッチが5%使用。RAM(262kbyte)を、グローバル変数が3%使用、ローカル変数で253kbyte使用可能。(1000byte=1kで計算)