最終更新日:2022/4/20
RP2040をNTPサーバとして構築します。
初期時刻については起動時に外部NTPサーバにNTPクライアントとしてアクセスして時刻を所得し、内部RTCに登録します。
以後はTimer割込でRTCの値を取得し、4-7セクLEDに表示します。RPiPicoをGroveShieldに載せて使おうとしているため、pin配置にて注意が必要です。
SPI0にNICを接続しますので、TM1637の接続はGPIO20,21はちょっと避け、GPIO18,19に接続します。
Grove | Groveケーブル配色 | XH4P | ||
@ | GND | B | GND | |
A | VCC | C | 5V | |
B | GPIO19 | A | DIO | |
C | GPIO18 | @ | SCK |
/* * 2022年4月20日 T.Wanibe * 2.5.1 ではNTPサーバー機能を追加する 内部RTCはLocalTimeとし 問い合わせに対してはUTCに変換する必要がある * 2.5.0.1起動時にNTP(192.168.0.199)にアクセスして内部RTCにセットして、その後TM1637LEDに表示しています。 * * 実行環境はPiPicoをGroveShieldに接続した状態で、SPIポートにW5500を接続して使います。CS=GPIO5です。 * 固定IPとします。192.168.0.207 MACアドレスはダブらないように注意します。 * アクセスするNTPサーバはLocalにある192.168.0.199です。 * 最大2093056バイトのフラッシュメモリのうち、スケッチが73384バイト(3%)を使っています。 * 最大262144バイトのRAMのうち、グローバル変数が11884バイト(4%)を使っていて、ローカル変数で250260バイト使うことができます。 */ #define TIMER_INTERRUPT_DEBUG 1 #define _TIMERINTERRUPT_LOGLEVEL_ 4 #include <stdio.h> #include <TimeLib.h> #include "TM1637.h" #include "RPi_Pico_TimerInterrupt.h" #include <SPI.h> #include <Ethernet.h> #include <EthernetUdp.h> extern "C" { #include <hardware/watchdog.h> }; #define CLK 18 #define DIO 19 #define TIMER0_INTERVAL_MS 1000 #define vers "NTP PiPicoShield 2.5.1" uint32_t delay_ms = 8000; //8秒 最大8.3秒 const char FVirsion[] = "2.5.1"; byte mac[] = {0x00,0x08,0xDC,0x54,0x4D,0xD7}; //Wiznet byte ip[] = {192, 168, 0, 207}; byte dns_server[] = {192, 168, 0, 1}; byte gateway[] = {0, 0, 0, 0}; byte subnet[] = {255, 255, 255, 0}; unsigned int localPort = 8888; // local port to listen for UDP packets //const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server const char timeServer[] = "192.168.0.199"; const int NTPport = 123; const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets byte fTogle1 = LOW; byte fTogle2 = LOW; const int timeZone = 9; int TimeZoneSec = timeZone * 3600; float unit = 2.33E-7; unsigned long baseMilis = millis(); unsigned long miliTime; struct tm *local; //STM32dinoとRP2040の構造体が異なる time_t currentTime,recieveTime; int currentHour,currentMin,currentSec; int8_t TimeDisp[] = {0x00,0x00,0x00,0x00}; const unsigned long seventyYears = 2208988800UL; char buff[80]; char buf1[32]; const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };//const or compiler complains uint32_t timestamp, tempval,microVal,fractions; //内部カウンタ unsigned long gNTPqueryCount = 0; //オブジェクト初期化 TM1637 tm1637(CLK,DIO); EthernetUDP Udp; // A UDP instance to let us send and receive packets over UDP RPI_PICO_Timer ITimer0(0); //-------------タイマ割込で呼ばれるルーチン クロック表示 bool TimerHandler0(struct repeating_timer *t){ currentTime = now(); //local = localtime(¤tTime); //sprintf(buff,"\n%04d/%02d/%02d %02d:%02d:%02d", local->tm_year,local->tm_mon+1,local->tm_mday,local->tm_hour,local->tm_min,local->tm_sec); if(Serial) Serial.println(currentTime); currentTime %= 86400; currentHour = currentTime / 3600; currentMin = (currentTime % 3600)/60; TimeDisp[0] = currentHour/ 10; TimeDisp[1] = currentHour % 10; TimeDisp[2] = currentMin / 10; TimeDisp[3] = currentMin % 10; if(fTogle1) tm1637.point(POINT_OFF); else tm1637.point(POINT_ON); tm1637.display(TimeDisp); fTogle1 = !fTogle1; return true; } //--------------------- bool processNTP() { //利用可能なデータがある場合は、パケットを読み取ります //https://blog.goo.ne.jp/hiro239415/e/c426a545863921a13a9d6a70b7ae4484 //NTP_Packet (48Byte) bool req = false; int packetSize = Udp.parsePacket(); if(packetSize){ digitalWrite(LED_BUILTIN,HIGH); //要求があったときにBuiltInLEDを点灯 miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); sprintf(buf1,"■%d\t%d\n",packetSize,fractions); if(Serial) Serial.print(buf1); Udp.read(packetBuffer,NTP_PACKET_SIZE); IPAddress Remote = Udp.remoteIP(); int PortNum = Udp.remotePort(); //受信タイムスタンプ = サーバが受け取った時刻を予めセットしておく JSTなのでGMTに変換 recieveTime = now() - TimeZoneSec; timestamp = recieveTime + seventyYears; miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); tempval = timestamp; packetBuffer[32] = (tempval >> 24) & 0XFF; tempval = timestamp; packetBuffer[33] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[34] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[35] = (tempval) & 0xFF; // microVal = fractions; packetBuffer[36] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[37] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[38] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[39] = microVal & 0xFF; //ヘッダーセット packetBuffer[0] = 0b00100100; // 閏秒警告なしLI, 4:SNTPサーバ(Version), 4:サーバ(Mode) packetBuffer[1] = 1 ; // 1:一次基準源(GPS等)(stratum packetBuffer[2] = 5 ; // ポーリング64秒デフォルト値 6(64秒)->5(32秒) packetBuffer[3] = 0xF6; // 精度 -3(0xFD)->-10(0xF6)に変更 msec packetBuffer[4] = 0; // ルート遅延 packetBuffer[5] = 0; packetBuffer[6] = 8; packetBuffer[7] = 0; packetBuffer[8] = 0; // ルート分散 packetBuffer[9] = 0; packetBuffer[10] = 0xC; packetBuffer[11] = 0; timestamp = now() - TimeZoneSec + seventyYears; if(Serial){ Serial.print(timestamp);Serial.print(F(":Time synchronization Request received\n")); } tempval = timestamp; microVal = fractions; packetBuffer[12] = 71; //"G"; ReferenceID packetBuffer[13] = 80; //"P"; ReferenceID packetBuffer[14] = 83; //"S"; ReferenceID packetBuffer[15] = 0; //"0"; ReferenceID // リファレンス時刻をセット packetBuffer[16] = (tempval >> 24) & 0XFF; tempval = timestamp; packetBuffer[17] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[18] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[19] = (tempval) & 0xFF; packetBuffer[20] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[21] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[22] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[23] = microVal & 0xFF; //オリジナル時間をコピー packetBuffer[24] = packetBuffer[40]; packetBuffer[25] = packetBuffer[41]; packetBuffer[26] = packetBuffer[42]; packetBuffer[27] = packetBuffer[43]; packetBuffer[28] = packetBuffer[44]; packetBuffer[29] = packetBuffer[45]; packetBuffer[30] = packetBuffer[46]; packetBuffer[31] = packetBuffer[47]; //送信時刻をセット timestamp = now() - TimeZoneSec + seventyYears; miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); tempval = timestamp; packetBuffer[40] = (tempval >> 24) & 0XFF; tempval = timestamp; packetBuffer[41] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[42] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[43] = (tempval) & 0xFF; //transmit_timestamp_fractions microVal = fractions; packetBuffer[44] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[45] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[46] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[47] = microVal & 0xFF; //NTP要求を送信したIPアドレスとポートに応答します Udp.beginPacket(Remote, PortNum); Udp.write(packetBuffer,NTP_PACKET_SIZE); Udp.endPacket(); digitalWrite(LED_BUILTIN,LOW); //処理完了で消灯 sprintf(buf1,"●%d\t%d\t%d\t%d\n",packetBuffer[20],packetBuffer[21],packetBuffer[22],packetBuffer[23]); if(Serial) Serial.print(buf1); req = true; gNTPqueryCount++; } return req; } //-----------------指定されたアドレスのタイムサーバーにNTP要求を送信します void sendNTPpacket(const char * address) { // バッファ内のすべてのバイトを0に設定します memset(packetBuffer, 0, NTP_PACKET_SIZE); // NTP要求を形成するために必要な値を初期化します(パケットの詳細については、上記のURLを参照してください) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // ポーリング64秒デフォルト値 packetBuffer[3] = 0xEC; // 精度 // ルート遅延とルート分散の場合は8バイトのゼロ packetBuffer[12] = 0x31; // '1' packetBuffer[13] = 0x4E; // 'N' packetBuffer[14] = 0x31; // '1' packetBuffer[15] = 0x34; // '4' //すべてのNTPフィールドに値が指定されているため、タイムスタンプを要求するパケットを送信できます。 Udp.beginPacket(address, NTPport); // NTP要求はポート123に送信されます Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } //-----------------受信したNTPパケットを解析してRTCにセットします bool parseNTPpacket(){ // 返信が利用可能かどうかを確認するのを待ちます if (Udp.parsePacket()) { // パケットを受信しました。そこからデータを読み取ります Udp.read(packetBuffer, NTP_PACKET_SIZE); // パケットをバッファに読み込みます // タイムスタンプは、受信したパケットのバイト40から始まり、4バイトです。 unsigned long secsSince1900; // 位置40から始まる4バイトを長整数に変換します secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; Serial.print("Seconds since Jan 1 1900 = "); Serial.println(secsSince1900); // エピックタイムに変換します。(local) unsigned long epoch = secsSince1900 - seventyYears + TimeZoneSec; Serial.print("Unix time = "); Serial.println(epoch); setTime(epoch); //RTC登録(LocalTime) } return true; } //----------------- void setup() { pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, fTogle2); //SPI0のpinアサインを設定 SPI.setSCK(2); SPI.setRX(4); SPI.setTX(3); SPI.setCS(5); // Ethernet.init(pin)を使用してCSピンを設定できます //Ethernet.init(10); // Most Arduino shields Ethernet.init(5); // MKR ETH shield //Ethernet.init(0); // Teensy 2.0 //Ethernet.init(20); // Teensy++ 2.0 //Ethernet.init(15); // ESP8266 with Adafruit Featherwing Ethernet //Ethernet.init(33); // ESP32 with Adafruit Featherwing Ethernet //シリアル通信を開き、ポートが開くのを待ちます。 Serial.begin(115200); /* while (!Serial) { ; // シリアルポートが接続するのを待ちます。 ネイティブUSBポートにのみ必要 //シリアルモニタを開かないと先に進まないので注意が必要 } */ // start Ethernet and UDP Ethernet.begin(mac,ip); //FIXIP サンプルスケッチはDHCPでした Udp.begin(NTPport); if(Serial){ Serial.print(F("server is at "));Serial.println(Ethernet.localIP()); } //最初にNTPサーバから時刻を取得する sendNTPpacket(timeServer); // NTPパケットをタイムサーバーに送信します delay(1000); parseNTPpacket(); tm1637.init(); tm1637.set(BRIGHT_TYPICAL); //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7; //マイクロ秒単位の間隔 if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 500, TimerHandler0)){ if(Serial){ Serial.print(F("Starting ITimer0 OK, millis() = ")); Serial.println(millis()); } }else{ if(Serial){ Serial.println(F("Can't set ITimer0. Select another freq. or timer")); } } watchdog_reboot(0,0,delay_ms); if(Serial){ Serial.println(F("EndSetup")); } } //----------------- void loop() { //1秒のループ WDT8秒 if (watchdog_caused_reboot()){ if(Serial) Serial.printf("Rebooted by Watchdog!\n"); }else{ if(Serial) Serial.printf("Clean boot\n"); } watchdog_update(); delay(1000); bool req = processNTP(); //NTP要求確認 Ethernet.maintain(); digitalWrite(LED_BUILTIN, fTogle2); fTogle2 = !fTogle2; }
免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。
商標・登録商標
Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。