RP2040にW5500とTM1637を接続する(NTPサーバ)

最終更新日: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(&currentTime);
        //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;
}


戯言(nonsense)に戻る


問い合わせ頁の表示


免責事項

本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。

本ソフトウエアの著作権は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社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。