GPSタイムサーバーの構築

最終更新日:2020年4月17日

※このプロジェクトではBluePillの内部時計を使用しています。BluePillのVBT端子を使えば、内部時計のバッテリーバックアップも可能です。その場合、充電可能な電池を接続することをお勧めします。 LIRxxxx等です。CRxxxxを接続するとCRxxxx側に電流が流れ、爆発の恐れがあります。逆止回路もご検討ください。

※amazonジャパンで販売されていた『Crosstour GPSアクティブアンテナ空中車ダッシュカム』というGPSモジュールを購入しました。端子は3.5mmステレオプラグです。確認したところ、これは使えます。9600bpsです。流れてくるセンテンスは$GPGGA/$GPRMC、、でした。モジュールによってどのGPSを捕らえるように設定されているかまちまちであることが判りました。
問題はこのモジュールから$GPZDAのセンテンスが流れてこないことです。
タイムサーバとして使うなら$GxRMCを解析して日時情報を取得するのが正解なのかと思います。

※このtimeserverの精度ですが、文字列情報をもとにしてしまっているため最大1秒の遅れが出るかなと思っています。
1PPSの信号が取得出来るGPSに変更して接点による補正をすれば1msecオーダに出来るはずですが。。


    GPSタイムサーバー

    工場に設置するPC装置はインターネットに接続することなく運用されることが多いです。そんなときに問題になるのが各PCの時計がずれることです。検査結果等の突き合わせが出来ません。
    市場ではTSV-400GPというGPSタイムサーバーが存在することは判っています。他にもたくさん有ると思います。

    そこで、Localのタイムサーバを検討しました。シリアルで接続するGPSレシーバは電波受信可能な箇所に貼り付けておくだけで、BluePill内部時計を校正し、PCからは起動タイミングや正時タイミングでSNTPでタイムサーバにアクセスする事で時刻統一が可能となります。

    GPSのchipはいくつかあるようです。ライブラリが存在するモノを選ぶ必要があります。
    スイスのublox社のChipを使った商品が流通しています。カーナビのGPSなんかはここのGPSを使ったモノが多々ありますし、Arduino用のモジュールも出ています。メーカからライブラリも出ているようなので、これをターゲットにして置きたいです。
    https://github.com/bolderflight/UBLOX
    安価に提供されているGPSモジュールに『UBX-M8030』というchipが搭載されているモノが見受けられます。このchipもこのドライバの対象のようです。
    目的が単に時刻取得で、GPSのごく一部の機能しか利用しないこともあり、安価に越したことはありません。
    ライブラリもとても単純化されており、これが使えるモノであれば何でも良さそうです。
    ※U-BLOX・GPSモジュールを購入した直後ですと内部がどのように設定されているか定かではありません。また、ボーレートも9600bpsのままかもしれません。U-BLOXのサイトから設定ツール『u-center_vxx.xx.exe』をダウンロードし、一旦クリアしてから必要な点のみ設定する必要があります。この設定内容について、ちゃんと把握できていません。https://github.com/bolderflight/UBLOXの説明だと、UBX-NAV-PVTが設定されていればうまくゆくような、そんな気がします。

    タイムサーバのソフトウエアですが、WIZNETのExampleにはNTPクライアントのコードしか提供されていません。しかしフォーラムには先人がトライしているモノが多々紹介されており、不可能ではないと考えました。
    https://forum.arduino.cc/index.php?topic=197870.0
    このリンク先のコードを参照してレタッチしてみました。GPSはU-BLOXのchipが搭載されているモジュールを前提にライブラリを読み替えています。

    検証方法自体はWindowsPCのインターネット時刻設定で確認出来ます。実際にビルドしてみました。内部時計を初期化してアクセスしてみたところ、とても反応の早い結果が得られました。期待した通りです。

    ソースを掲載します。追加が必要なライブラリは、

    /* 
     *  2020/2/28 T.Wanibe
     *  起動時のLED&KEYの表示を変更しました。GPSの受信が曖昧な内は『HELLO』表示とし、受信後に時刻表示するように
     *  変更しました。
     *  新たにGPSモジュールを購入しました。Crosstour CR900向けの3.5mmステレオジャックタイプです。
     *  こちらからは$GPのセンテンスが流れてきます。ところが、$GPZDAが流れてこないのです。多分設定で限定した
     *  センテンスしか流さないように流さないようにしているものと考えました。
     *  と云う事は、$GxRMCを解析して日時情報を求めるのが正解なのだと痛感しました。修正します。
     *  LED2が点灯したら内部時計設定完了です。
     *  設定画面がほしいので再度挑戦
     *  ver2のコードで問題を確認したため修正します。タイムゾンーンを単に+9してしまうとAM9に日付が変わります。
     *  これはダメです。正しいLocalタイム対応に修正します。※内部時計はLocalTimeに変更
     *  内部時計はGMTのまま扱い、LED&KEYに表示するときに対策します。そうしないとタイムサーバとしては問題ありです。
     *  このコードはGPSタイムサーバを構築したくて検証中のコードです
     *  NMEA0183のフォーマットを極力単純化して時刻取得するようにした。
     *  まずはNTPサーバを構築して内部時間を返すものを検討
     *  当初ヘッダが$GPGGAに対応するようにしていたが、購入したGPSが$GNGGAのコードしか出さなかったので修正した。
     *  $GNxxxはロシア版GPSであるGLONASSのセンテンスとのようです。
     *  $ GNGGA、185824.00,2605.0247881、N、08014.3097082、W、4,14,0.7、-24.343、M ,,, 0.59,0402 * 36
     *  185824.00           協定世界時(UTC)での時刻。 hhmmss.uu
     *  2605.0247881        緯度。dddmm.mmmm
     *  N                   N = 北緯、South = 南緯
     *  08014.3097082       経度。dddmm.mmmm
     *  W                   E = 東経、W = 西経
     *  4                   位置特定品質
     *  14                  使用衛星数
     *  0.7                 水平精度低下率
     *  -24.343             アンテナの海抜高さ
     *  M                   [m]
     *  0.59
     *  0402                差動基準地点ID
     *  * 36                チェックサム
     *  GNGGAには日付情報が無いためGNRMCを使うべきとあった。
     *  $GNRMC,001903.000,A,3430.61495,N,13316.67709,E,0.00,100.54,120220,,,A,V*00
     *  RMC(時刻、位置、日付)$__RMC,hhmmss.ss,a, ddmm.mm ,a, ddmm.mm ,a,x.x,x.x,mmddyy,a,a,*hh<CR><LF>
     *  $    センテンスの開始
     *  __RMC   アドレスフィールド( 5 文字)、 __ は「 GN 」「 GP 」など
     *  hhmmss.ss       時刻( UTC )  hh 時 mm 分 ss.ss 秒
     *  a       ステータス
     *  A=data valid
     *  V=navigation receiver warming
     *  ddmm.mm 経度  dd 度 mm.mm 分
     *  a       北緯( N )または南緯( S )
     *  ddmm.mm 経度  dd 度 mm.mm 分
     *  a       東経( E )または西経( W )
     *  x.x     対地速度( knot )
     *  x.x     対地コース(進路)(度)
     *  mmddyy  日付  yy 年 mm 月 dd 日
     *  x.x     偏差
     *  a       偏差の方向  W または E
     *  a       モード
     *  A :単独
     *  D :ディファレンシャルモード
     *  E : Estimated (dead reckoning),
     *  M :マニュアル入力
     *  S :シミュレーター入力
     *  N :無効
     *  *hh     チェックサム( $ から * の間)
     *  <CR><LF>        センテンスの終了
     *  センテンス長が固定長ではないようなので一寸パスします。
     *  結局GGA起動時のみ$GNZDAもチェックして日付情報を登録します。
     *  $GNZDA
     *  ZDA(時刻、日付)$__ZDA,x.x,x.x,x.x,*hh<CR><LF>
     *  $    センテンスの開始
     *  __ZDA   アドレスフィールド( 5 文字)、 __ は「 GP 」など
     *  hhmmss.ss       時刻( UTC )  hh 時 mm 分 ss.ss 秒
     *  dd      日
     *  mm      月
     *  yyyy    西暦
     *  hh      時(ローカルタイム)
     *  mm      分(ローカルタイム)
     *  *hh     チェックサム( $ から * の間)
     *  <CR><LF>        センテンスの終了
     * 最大131072バイトのフラッシュメモリのうち、スケッチが57048バイト(43%)を使っています。
     * 最大20480バイトのRAMのうち、グローバル変数が4896バイト(23%)を使っていて、ローカル変数で15584バイト使うことができます。
     */
    #include <SPI.h>                                        // needed for Arduino versions later than 0018
    #include <Ethernet3.h>
    #include <EthernetUdp3.h>                               // UDP library from: bjoern@cs.stanford.edu 12/30/2008
    //#include <UBLOX.h>
    #include <TM1638.h>
    #include <RTClock.h>
    #include <EEPROM.h>
    #include <avr/pgmspace.h>
    #include <TextFinder.h>                                 //WebSetting
    // Time Server Port
    #define NTP_PORT        123
    #define vers            "NTP GPS STM32miniShield"
    #define debug           true
    //PIN
    #define W550io_Rst      PA8                             //NIC_Reset
    #define SPI1_NSS_PIN    PA4                             //NIC_ChipSelec
    #define UpdateInterval  86400                           //更新間隔
    #define dataPin         PB4                             //LED&KEY
    #define clockPin        PB3                             //LED&KEY
    #define strobePin       PA15                            //LED&KEY
    #define BUILTIN_LED     PC13
    #define LED1            PB8
    #define LED2            PB9
    #define SERIAL_RX_BUFFER_SIZE 256
    #define HttpPort        80                              //HTTP
    #define DELIMITER       ","
    // Time Server MAC address
    byte mac[]              = {0x00,0x08,0xDC,0x54,0x4D,0xD1};      //Wiznet
    byte ip[]               = {192, 168, 0, 197};
    byte dns_server[]       = {192, 168, 0, 1};
    byte gateway[]          = {0, 0, 0, 0};
    byte subnet[]           = {255, 255, 255, 0};
    static const int NTP_PACKET_SIZE = 48;
    byte packetBuffer[NTP_PACKET_SIZE];                     //データを送受信するためのバッファ
    int             year    = 2020;
    byte            month   = 2;
    byte            day     = 11;
    byte            hour    = 13;
    byte            minute  = 10;
    byte            second  = 00;
    byte            hundredths      = 00;
    unsigned long   gDate, gTime, age;
    uint32_t        timestamp, tempval;
    // LM: GPS message parsing
    const char      EOL     = 10;                           // End-of-line
    const int       MSGLEN  = 67;                           // GNRMCメッセージの長さ、66文字の印刷可能文字+ 改行コード
    String          tmpMsg  = "";
    String          gnrmcMsg        = "";
    char            chkHader[]      = "RMC";                //このバージョンから3文字に変更
    // LM: Date/time handling
    String          sUTD    = "";                           // UT Date
    String          sUTC    = "";                           // UT Time
    String          HTTP_req= "";                           // stores the HTTP request
    const int       CENTURY = 2000;
    // LM: Alternative debug
    const boolean   MYDEBUG = true;
    char            STRBUF[50];
    int             timezone        = 9;                    // change to your timezone
    const uint8_t   daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };//const or compiler complains
    const unsigned long seventyYears = 2208988800UL;        // to convert unix time to epoch
    long            gLoopCount = 3540;                      //1時間毎に更新予定 最初は1分後に更新したくて初期値を調整
    //オブジェクトコンストラクタ
    EthernetServer  webServer(HttpPort);
    EthernetClient  client;
    TM1638          LedAndKey(dataPin, clockPin, strobePin);//データピンPB4、クロックピンPB3、およびストローブピンPA15でモジュールを定義する
    RTClock         rtclock (RTCSEL_LSE);                   //RTC初期化
    tm_t            tmm;
    EthernetUDP     Udp;                                    //イーサネットUDPインスタンス
    //UBLOX           gps(Serial1,9600);                      //115200bpsでUBLOX・GPSは通信できるのか要確認
    const int       TD = 9;                                 //TimeZone 日本は+9です。
                                                            // これは、HTMLコードを「流す」ためのバッファーです。
                                                            // ””を含む最長の文字チェーン+1と同じ大きさでなければなりません。
    char    buffer[100];
                                                            // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。
                                                            // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を
                                                            // 使用する必要があります。そうしないと機能しません。
    const byte      ID = 0x92;
    //----------------------------------
    void LANSetup(){
            int idcheck = EEPROM.read(0);
            Serial.print(F("LocalID = 0x"));Serial.println(idcheck,HEX);
            if (idcheck == ID){
                    //idがIDと同じ値の場合、
                    //これは、このスケッチがシールドを設定するために使用されたことを意味します。
                    //EERPOMの値を読み取ってシールドを設定します。
                    for (int i = 0; i < 6; i++){
                            mac[i] = EEPROM.read(i+1);
                    }
                    for (int i = 0; i < 4; i++){
                            ip[i] = EEPROM.read(i+7);
                    }
                    for (int i = 0; i < 4; i++){
                            subnet[i] = EEPROM.read(i+11);
                    }
                    for (int i = 0; i < 4; i++){
                            gateway[i] = EEPROM.read(i+15);
                    }
            }else{
                    //idが一致しない場合、初期値を書き込む事にします。
                    for (int i = 0 ; i < 6; i++){
                            EEPROM.write(i + 1,mac[i]);
                    }
                    for (int i = 0 ; i < 4; i++){
                            EEPROM.write(i + 7, ip[i]);
                    }
                    for (int i = 0 ; i < 4; i++){
                            EEPROM.write(i + 11, subnet[i]);
                    }
                    for (int i = 0 ; i < 4; i++){
                            EEPROM.write(i + 15, gateway[i]);
                    }
                    // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                    EEPROM.write(0, ID); 
            }
            Ethernet.begin(mac, ip);
    //      Ethernet.begin(mac);                            //DHCPの場合
    //      Ethernet.begin(mac, ip, subnet);                //SubnetMaskを意識した場合。
    //      Ethernet.begin(mac, ip, subnet, gateway);       //gatewayを意識した場合。
    }
    //--------------------------
    void SetWebPage( EthernetClient client){
            client.print(F("<!DOCTYPE HTML PUBLIC \"\"><html><HEAD><META http-equiv=\"Content-Type\" charset=UTF-8\">"));
            client.print(F("<META http-equiv=\"Content-Style-Type\"><TITLE>"));
            client.print(vers);
            client.print(F(" Setup Page</TITLE></HEAD>"));
            client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\"><BLOCKQUOTE><BLOCKQUOTE>"));
            client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">"));
            client.print(F("<tr><td>&nbsp;"));
            client.print(vers);
            client.print(F(" Setup Page</td></tr></table><br>"));
            //
            client.print(F("<script>function hex2num (s_hex) {eval(\"var n_num=0X\" + s_hex);return n_num;}</script>"));
            client.print(F("<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\"><table><tr><td>MAC:</td><td>"));
            client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\""));
            client.print(mac[0],HEX);
            client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\""));
            client.print(mac[1],HEX);
            client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\""));
            client.print(mac[2],HEX);
            client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\""));
            client.print(mac[3],HEX);
            client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\""));
            client.print(mac[4],HEX);
            client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\""));
            client.print(mac[5],HEX);
            //
            client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2"));
            client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4"));
            client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6"));
            client.print(F("\"></td></tr><tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\""));
            client.print(ip[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\""));
            client.print(ip[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\""));
            client.print(ip[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\""));
            client.print(ip[3],DEC);
            //
            client.print(F("\"></td></tr><tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\""));
            client.print(subnet[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\""));
            client.print(subnet[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\""));
            client.print(subnet[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\""));
            client.print(subnet[3],DEC);
            //
            client.print(F("\"></td></tr><tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\""));
            client.print(gateway[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\""));
            client.print(gateway[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\""));
            client.print(gateway[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\""));
            client.print(gateway[3],DEC);
            client.print(F("\"></td></tr><tr><td><br></td></tr><tr><td><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" "));
            //
            client.print(F("Onclick=\"document.getElementById('T2').value "));
            client.print(F("= hex2num(document.getElementById('T1').value);"));
            client.print(F("document.getElementById('T4').value = hex2num(document.getElementById('T3').value);"));
            client.print(F("document.getElementById('T6').value = hex2num(document.getElementById('T5').value);"));
            client.print(F("document.getElementById('T8').value = hex2num(document.getElementById('T7').value);"));
            client.print(F("document.getElementById('T10').value = hex2num(document.getElementById('T9').value);"));
            client.print(F("document.getElementById('T12').value = hex2num(document.getElementById('T11').value);\""));
            //
            client.print(F("></td><td></td></tr></form></table></BLOCKQUOTE></BLOCKQUOTE></BODY></html>"));
    }
    //--------------------------
    void checkWebPage( EthernetClient client)
    {
            Serial.println(F("new webClient"));
            if (client) {
                    TextFinder  finder(client );
                    while (client.connected()) {
                            //digitalWrite(LED, HIGH);
                            if (client.available()) {
                                    //この部分はすべてのテキスト検索を行います。
                                    if( finder.find("GET /") ) {
                                            // 「setup」とい語が見つかった場合は、さらに探してください。
                                            // その単語が見つからない場合は、検索を停止して先に進みます。
                                            // これにより、後でスケッチに独自のWebページを配置できます。
                                            if (finder.findUntil("setup", "\n\r")){
                                                    // 「SBM」という単語が見つかった場合は、さらに探し続けます。
                                                    // その言葉が見つからない場合は、探して停止します。
                                                    // SUBMITボタンが押されていない、何も押されていないことを意味します
                                                    // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。
                                                    if (finder.findUntil("SBM", "\n\r")){
                                                            byte SET = finder.getValue();
                                                            // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、
                                                            // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。
                                                            while(finder.findUntil("DT", "\n\r")){
                                                                    int val = finder.getValue();
                                                                    // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。
                                                                    if(val >= 1 && val <= 6) {
                                                                            mac[val - 1] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。
                                                                    if(val >= 7 && val <= 10) {
                                                                            ip[val - 7] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。
                                                                    if(val >= 11 && val <= 14) {
                                                                            subnet[val - 11] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。
                                                                    if(val >= 15 && val <= 18) {
                                                                            gateway[val - 15] = finder.getValue();
                                                                    }
                                                            }
                                                            // すべてのデータを取得したので、EEPROMに保存できます
                                                            for (int i = 0 ; i < 6; i++){
                                                                    EEPROM.write(i + 1,mac[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 7, ip[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 11, subnet[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 15, gateway[i]);
                                                            }
                                                            // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                                                            EEPROM.write(0, ID); 
                                                            // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。
                                                            //ハードウェアリセットボタンを使用する必要があります。
                                                    }
                                                    // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。
                                                    client.println("HTTP/1.1 200 OK");
                                                    client.println("Content-Type: text/html");
                                                    client.println();
                                                    //
                                                    SetWebPage(client);
                                                    
                                                    break;
                                            }
                                    }
                                    client.println("HTTP/1.1 200 OK");
                                    client.println("Content-Type: text/html");
                                    client.println();
                                    // put your own html from here on
                                    client.print("IT WORKS: go to ");
                                    client.print(ip[0],DEC);
                                    for (int i= 1; i < 4; i++){
                                            client.print(".");
                                            client.print(ip[i],DEC);
                                    }
                                    client.print("/setup");
                                    // put your own html until here 
                                    break;  
                            }
                    }
                    //digitalWrite(LED, LOW);
                    delay(1);
                    client.stop();
            }     
    }
    //---------------------
    void processNTP() {
            //利用可能なデータがある場合は、パケットを読み取ります
            int packetSize  =       Udp.parsePacket();
            if(packetSize){
                    Serial.println(packetSize);
                    Udp.read(packetBuffer,NTP_PACKET_SIZE);
                    IPAddress       Remote  = Udp.remoteIP();
                    int             PortNum = Udp.remotePort();
                    //if (!getGPSdata())  return;
                    packetBuffer[0]         = 0b00100100;           // LI, Version, Mode
                    packetBuffer[1]         = 1 ;                   // stratum
                    packetBuffer[2]         = 6 ;                   // ポーリング最小
                    packetBuffer[3]         = 0xFA;                 // 精度
                    packetBuffer[7]         = 0;                    // ルート遅延
                    packetBuffer[8]         = 0;
                    packetBuffer[9]         = 8;
                    packetBuffer[10]        = 0;
                    packetBuffer[11]        = 0;                    // ルート分散
                    packetBuffer[12]        = 0;
                    packetBuffer[13]        = 0xC;
                    packetBuffer[14]        = 0;
                    //gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
                    //crack(sUTD, sUTC);
                    timestamp = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second);
                    //timestamp = numberOfSecondsSince1900Epoch(tmm.year,tmm.month,tmm.day-1,tmm.hour,tmm.minute,tmm.second);
    #if debug
                    Serial.print(timestamp);Serial.print(F(":Time synchronization Request received\n"));
                    //print_date(gps);
    #endif
                    tempval                 = timestamp;
                    packetBuffer[12]        = 71;                  //"G";
                    packetBuffer[13]        = 80;                  //"P";
                    packetBuffer[14]        = 83;                  //"S";
                    packetBuffer[15]        = 0;                   //"0";
                    // reference timestamp
                    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]        = 0;
                    packetBuffer[21]        = 0;
                    packetBuffer[22]        = 0;
                    packetBuffer[23]        = 0;
                    //copy originate timestamp from incoming UDP transmit timestamp
                    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];
                    //receive 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;
                    //
                    packetBuffer[36]        = 0;
                    packetBuffer[37]        = 0;
                    packetBuffer[38]        = 0;
                    packetBuffer[39]        = 0;
                    //transmitt 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;
                    packetBuffer[44]        = 0;
                    packetBuffer[45]        = 0;
                    packetBuffer[46]        = 0;
                    packetBuffer[47]        = 0;
                    // Reply to the IP address and port that sent the NTP request
                    Udp.beginPacket(Remote, PortNum);
                    Udp.write(packetBuffer,NTP_PACKET_SIZE);
                    Udp.endPacket();
            }
    }
    //---------------NTP since 1900/01/01
    static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) {
            int leapAdjustment = 0;
            if (y >= 1970) {
                    y -= 1970;
                    leapAdjustment  = 2;                            // LM: 1970 was NOT a leap year!
            }
            uint16_t days = d;
            for (uint8_t i = 1; i < m; ++i)         days += pgm_read_byte(daysInMonth + i - 1);
    /*
            if (m > 2 && y % 4 == 0)                ++days;
    */
            if (m > 2 && (y + leapAdjustment) % 4 == 0)     ++days; // LM: Weak but okay for the present
            days += 365 * y + (y + 3) / 4 - 1;
            return days*24L*3600L + h*3600L + mm*60L + s + seventyYears;
    }
    //------------------
    bool readSensor(void){
            if (Serial1.available()) {
                    Serial1.readStringUntil('\n');                  //読み捨てて先頭から読めるようにする
                    String nmea = Serial1.readStringUntil('\n');
    #if debug
                    Serial.println(nmea);
    #endif
                    String  nmeames = nmea.substring(3,6);           // $GP***
                    int     count   = 0;
                    while(!nmeames.equals(chkHader)){
                           nmea     = Serial1.readStringUntil('\n');
                           nmeames  = nmea.substring(3,6);          // $GP***
                           digitalWrite(LED1, count++%2);
    #if debug
                           Serial.print(nmea);Serial.print(F("\t"));Serial.println(nmeames);
    #endif
                    }
                    int     nextPos = 0;
                    if ( nmeames.equals(chkHader)){                  // get UTC
                            nextPos         = nmea.indexOf(DELIMITER);//int indexOf(char ch, unsigned int fromIndex) const;
                            String shh      = nmea.substring(nextPos+1,nextPos+3);
                            String smm      = nmea.substring(nextPos+3,nextPos+5);
                            String sss      = nmea.substring(nextPos+5,nextPos+7);
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip UTC
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip Status
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 緯度
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 北緯か南緯か
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 経度
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 東経か西経か
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 地表における移動の速度[knot]
                            nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 地表における移動の真方位[deg]
                            String sdd      = nmea.substring(nextPos+1,nextPos+3);
                            String smn      = nmea.substring(nextPos+3,nextPos+5);
                            String syy      = nmea.substring(nextPos+5,nextPos+7);
                            Serial.println("20" + syy + smn + sdd + shh + smm + sss);
                            tmm.hour        = shh.toInt();
                            hour            = tmm.hour;
                            tmm.minute      = smm.toInt();
                            minute          = tmm.minute;
                            tmm.second      = sss.toInt();
                            second          = tmm.second;
                            tmm.day         = sdd.toInt();
                            day             = tmm.day -1;
                            tmm.month       = smn.toInt();
                            month           = tmm.month;
                            tmm.year        = syy.toInt() + 2000 - 1970;
                            year            = syy.toInt() + 2000;
                            digitalWrite(LED1, LOW);
                            return true;
                    }else{
                            digitalWrite(LED1, HIGH);
                            return false;
                    }
            }
    }
    //------------------GPSにアクセスし、取得に成功したら内部時間を更新
    bool ReadGPS(){
            if(readSensor()) {
                    if(gLoopCount>3600){
                            rtclock.setTime(tmm);                   // 時刻の設定 一時間毎に更新
                            time_t Now = rtclock.getTime() + TD * 3600;
                            rtclock.setTime(Now);
                            gLoopCount = 0;
                            digitalWrite(LED2, HIGH);
                    }
                    hundredths = 0;                                 // LM: GPS time is always acquired on the second (not used)
                    age = 0;                                        //     Not used in this adaptation
            }else{
                    if(gLoopCount%100)      Serial.print(F("."));
                    else{
                            Serial.println(F("."));
                            digitalWrite(LED2, LOW);
                    }
            }
    }
    //------------------
    void setup() {
            Serial.begin(115200);                           //シリアル通信を開ます。
            delay(1000);
            Serial.println(F("Serial interface started"));
            pinMode(BUILTIN_LED,OUTPUT);
            pinMode(LED1,OUTPUT);digitalWrite(LED1, LOW);
            pinMode(LED2,OUTPUT);digitalWrite(LED2, LOW);
            // start Ethernet and UDP:(Ethernet3API)
            Ethernet.setCsPin(SPI1_NSS_PIN);
            Ethernet.setRstPin(W550io_Rst);
            //
            pinMode(W550io_Rst, OUTPUT);
            digitalWrite(W550io_Rst, LOW);
            delay(10);
            digitalWrite(W550io_Rst, HIGH);
            Serial.print(F("NIC_Reset\n"));
            LANSetup();                                             //Ethernet.begin(mac,ip);
            Serial.println(F("Ethernet interface started"));
            Serial.print(F("server is at "));Serial.println(Ethernet.localIP());
            webServer.begin();
            Udp.begin(NTP_PORT);
            //
    #if debug
            Serial.print("Version:");
            Serial.println(vers);
    #endif
            //GPS受信機との通信を開始する
            Serial1.begin(9600);                    //9600bps固定の場合
            ReadGPS();                              //GPS取得に成功したら内部時計を更新しています。
            rtclock.getTime(tmm);
            //sprintf(STRBUF,"%02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
            //LedAndKey.setDisplayToString(STRBUF,0,2);
            LedAndKey.setDisplayToString(" HELLO  ",0,0);
            Serial.println(F("EndSetUp"));
    }
    //-------------------- 
    void loop() {
            //参考にしたコードはNTPdは常に動作させ、クライアントから時刻同期要求が有ったら
            //GPSにアクセスするというモノでした。
            //この修正版は、NTPdは常に動作させ、要求があったらBluePillの内部クロックを
            //返します。
            //また、NTPdの周回待ちにGPSにアクセスし、値を取得したら内部時間を更新します。
            delay(500);
            ReadGPS();
            delay(500);
            client = webServer.available();
            if(client)      checkWebPage(client);
            client.stop();                          // コネクションを閉じる。
            processNTP();
            gLoopCount++;
            digitalWrite(BUILTIN_LED,gLoopCount%2);  //毎秒LEDの点滅処理を実施
            rtclock.getTime(tmm);
            byte keys = LedAndKey.getButtons();
            switch(keys){
                    case 0x01:
                            sprintf(STRBUF,"%04u%02u%02u",tmm.year + 1970,tmm.month,tmm.day);
                            LedAndKey.setDisplayToString(STRBUF,0,0);
                            delay(1000);
                            break;
                    case 0x02:
                            sprintf(STRBUF,"  %02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
                            LedAndKey.setDisplayToString(STRBUF,0,0);
                            break;                        
                   default:
                            if(digitalRead(LED2)){
                                    sprintf(STRBUF,"  %02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
                                    LedAndKey.setDisplayToString(STRBUF,0,0);
                            }
            }
    }


    GPSモジュールメーカがいくつかあります。一寸調査し記録に残しておきます。

      メーカ コメント
    @ U-BLOX スイスに本社があるとのこと。APMフライトコントローラの設計上のGPSに採用されている
    A JRCモビリティ どの程度普及しているのか判らない
    B アルプス 車載用GNSS
    C STマイクロ Teseo-LIV3Fと云う商品があるとのことだが、、、
    D 古野電気 高いので対象外
    E Garmin 結構流通しているようです。
    https://www8.garmin.com/manuals/webhelp/gpsmap_touch/JA-JP/
    GUID-16CE378B-2135-4CEF-B6B3-651C6E810CD3.html
    にNMEA-0183の設定方法が記載されています。
    F SkyTraq 不明
    G MYK 横浜にある創業10年程度の会社
    H 太陽誘電 秋月で提供されているモジュールが太陽誘電製です。GYSFDMAXB

    タイムサーバで使う分にはGPSモジュールがどのメーカのモノであっても NMEA-0183 のフォーマットで出力してくれるモノであればU-BLOXで無くてもそのまま使えるはずです。
    U-BLOX以外のメーカが NMEA-0183 に見合う出力を出すためにどのような設定をすればいいのか、、この点は敷居が高いです。

    そこで考えました。既設定のまま使えるモジュールがいい!

    カーナビでGPSアンテナをオプションにしている商品があります。例えばAKEEYOのドライブレコーダが該当します。
    https://akeeyo.com/
    amazonでも扱っています。
    このカーナビにはオプションでGPSアンテナが用意されていますが、これが利用できます。

    RXの端子がないため設定は出来ません。また、u-centerに接続しても自動でu-bloxG7なんて認識もしません。しかし、通電して暫くすると電波を捕らえて、NMEA-0183データが流れてきます。
    ちなみにVCCは3.3Vで行いましたが大丈夫でした。接続方法ですが、『オーディオジャック+ピッチ変換基板のセット』を介した接続になります。

    実際に取得したデータを調べたところ、『$GPGGA』が有りません。代わりに『$GNGGA』です。また、この『$GNGGA』には日付情報が無いことを確認しました。TimeServerとしては大問題です。そこで『$GNZDA』を使う事にしました。
    また、ライブラリを使うよりも自分で String関数で文字処理した方がリーズナブルだと判断しました。
    GPSが電波を捕らえるまで時間が掛かることが有ります。この点はシリアルモニタを使って判断することになります。


    新たに購入した Crosstour GPSモジュールは 1000円以下で購入でき安価でした。また、pinアサインもakeeyoと同じ、9600bpsで受信できました。ただ、流れてくるセンテンスは『$GPGGA』と『$GPRMC』のみでした。このことから、

      本プロジェクトでは 『$GxRMC』から日時取得する としました。

    このコードを実行するに当たり追加が必要なライブラリは、

※2020/4/17:3/19版にタイミングの問題があり修正 同期を掛けたタイミングによってTimeZone分ずれる問題があったのを修正。

/* 
 *  2020/4/17 T.Wanibe
 *  アンテナが外れた時にLED2が消灯するようにした。TimeServerとしては内部時計の値を返します。
 *  起動時のLED&KEYの表示を変更しました。GPSの受信が曖昧な内は『HELLO』表示とし、受信後に時刻表示するように
 *  変更しました。
 *  新たにGPSモジュールを購入しました。Crosstour CR900向けの3.5mmステレオジャックタイプです。
 *  こちらからは$GPのセンテンスが流れてきます。ところが、$GPZDAが流れてこないのです。多分設定で限定した
 *  センテンスしか流さないように流さないようにしているものと考えました。
 *  と云う事は、$GxRMCを解析して日時情報を求めるのが正解なのだと痛感しました。修正します。
 *  LED2が点灯したら内部時計設定完了です。
 *  設定画面がほしいので再度挑戦
 *  ver2のコードで問題を確認したため修正します。タイムゾンーンを単に+9してしまうとAM9に日付が変わります。
 *  これはダメです。正しいLocalタイム対応に修正します。※内部時計はLocalTimeに変更
 *  内部時計はGMTのまま扱い、LED&KEYに表示するときに対策します。そうしないとタイムサーバとしては問題ありです。
 *  このコードはGPSタイムサーバを構築したくて検証中のコードです
 *  NMEA0183のフォーマットを極力単純化して時刻取得するようにした。
 *  まずはNTPサーバを構築して内部時間を返すものを検討
 *  当初ヘッダが$GPGGAに対応するようにしていたが、購入したGPSが$GNGGAのコードしか出さなかったので修正した。
 *  $GNxxxはロシア版GPSであるGLONASSのセンテンスとのようです。
 *  $ GNGGA、185824.00,2605.0247881、N、08014.3097082、W、4,14,0.7、-24.343、M ,,, 0.59,0402 * 36
 *  185824.00           協定世界時(UTC)での時刻。 hhmmss.uu
 *  2605.0247881        緯度。dddmm.mmmm
 *  N                   N = 北緯、South = 南緯
 *  08014.3097082       経度。dddmm.mmmm
 *  W                   E = 東経、W = 西経
 *  4                   位置特定品質
 *  14                  使用衛星数
 *  0.7                 水平精度低下率
 *  -24.343             アンテナの海抜高さ
 *  M                   [m]
 *  0.59
 *  0402                差動基準地点ID
 *  * 36                チェックサム
 *  GNGGAには日付情報が無いためGNRMCを使うべきとあった。
 *  $GNRMC,001903.000,A,3430.61495,N,13316.67709,E,0.00,100.54,120220,,,A,V*00
 *  RMC(時刻、位置、日付)$__RMC,hhmmss.ss,a, ddmm.mm ,a, ddmm.mm ,a,x.x,x.x,mmddyy,a,a,*hh<CR><LF>
 *  $    センテンスの開始
 *  __RMC   アドレスフィールド( 5 文字)、 __ は「 GN 」「 GP 」など
 *  hhmmss.ss       時刻( UTC )  hh 時 mm 分 ss.ss 秒
 *  a       ステータス
 *  A=data valid
 *  V=navigation receiver warming
 *  ddmm.mm 経度  dd 度 mm.mm 分
 *  a       北緯( N )または南緯( S )
 *  ddmm.mm 経度  dd 度 mm.mm 分
 *  a       東経( E )または西経( W )
 *  x.x     対地速度( knot )
 *  x.x     対地コース(進路)(度)
 *  mmddyy  日付  yy 年 mm 月 dd 日
 *  x.x     偏差
 *  a       偏差の方向  W または E
 *  a       モード
 *  A :単独
 *  D :ディファレンシャルモード
 *  E : Estimated (dead reckoning),
 *  M :マニュアル入力
 *  S :シミュレーター入力
 *  N :無効
 *  *hh     チェックサム( $ から * の間)
 *  <CR><LF>        センテンスの終了
 *  センテンス長が固定長ではないようなので一寸パスします。
 *  結局GGA起動時のみ$GNZDAもチェックして日付情報を登録します。
 *  $GNZDA
 *  ZDA(時刻、日付)$__ZDA,x.x,x.x,x.x,*hh<CR><LF>
 *  $    センテンスの開始
 *  __ZDA   アドレスフィールド( 5 文字)、 __ は「 GP 」など
 *  hhmmss.ss       時刻( UTC )  hh 時 mm 分 ss.ss 秒
 *  dd      日
 *  mm      月
 *  yyyy    西暦
 *  hh      時(ローカルタイム)
 *  mm      分(ローカルタイム)
 *  *hh     チェックサム( $ から * の間)
 *  <CR><LF>        センテンスの終了
 * 最大131072バイトのフラッシュメモリのうち、スケッチが57048バイト(43%)を使っています。
 * 最大20480バイトのRAMのうち、グローバル変数が4896バイト(23%)を使っていて、ローカル変数で15584バイト使うことができます。
 */
#include <SPI.h>                                        // needed for Arduino versions later than 0018
#include <Ethernet3.h>
#include <EthernetUdp3.h>                               // UDP library from: bjoern@cs.stanford.edu 12/30/2008
//#include <UBLOX.h>
#include <TM1638.h>
#include <RTClock.h>
#include <EEPROM.h>
#include <avr/pgmspace.h>
#include <TextFinder.h>                                 //WebSetting
// Time Server Port
#define NTP_PORT        123
#define vers            "NTP GPS STM32miniShield 3.4"
#define debug           true
//PIN
#define W550io_Rst      PA8                             //NIC_Reset
#define SPI1_NSS_PIN    PA4                             //NIC_ChipSelec
#define UpdateInterval  86400                           //更新間隔
#define dataPin         PB4                             //LED&KEY
#define clockPin        PB3                             //LED&KEY
#define strobePin       PA15                            //LED&KEY
#define BUILTIN_LED     PC13
#define LED1            PB8
#define LED2            PB9
#define SERIAL_RX_BUFFER_SIZE 256
#define HttpPort        80                              //HTTP
#define DELIMITER       ","
// Time Server MAC address
byte mac[]              = {0x00,0x08,0xDC,0x54,0x4D,0xD1};      //Wiznet
byte ip[]               = {192, 168, 0, 197};
byte dns_server[]       = {192, 168, 0, 1};
byte gateway[]          = {0, 0, 0, 0};
byte subnet[]           = {255, 255, 255, 0};
static const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];                     //データを送受信するためのバッファ
int             year    = 2020;
byte            month   = 2;
byte            day     = 11;
byte            hour    = 13;
byte            minute  = 10;
byte            second  = 00;
byte            hundredths      = 00;
unsigned long   gDate, gTime, age;
uint32_t        timestamp, tempval;
// LM: GPS message parsing
const char      EOL     = 10;                           // End-of-line
const int       MSGLEN  = 67;                           // GNRMCメッセージの長さ、66文字の印刷可能文字+ 改行コード
String          tmpMsg  = "";
String          gnrmcMsg        = "";
char            chkHader[]      = "RMC";                //このバージョンから3文字に変更
// LM: Date/time handling
String          sUTD    = "";                           // UT Date
String          sUTC    = "";                           // UT Time
String          HTTP_req= "";                           // stores the HTTP request
const int       CENTURY = 2000;
// LM: Alternative debug
const boolean   MYDEBUG = true;
char            STRBUF[50];
int             timezone        = 9;                    //TimeZone 日本は+9です。
const uint8_t   daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };//const or compiler complains
const unsigned long seventyYears = 2208988800UL;        // to convert unix time to epoch
long            gLoopCount = 3540;                      //1時間毎に更新予定 最初は1分後に更新したくて初期値を調整
//オブジェクトコンストラクタ
EthernetServer  webServer(HttpPort);
EthernetClient  client;
TM1638          LedAndKey(dataPin, clockPin, strobePin);//データピンPB4、クロックピンPB3、およびストローブピンPA15でモジュールを定義する
RTClock         rtclock (RTCSEL_LSE);                   //RTC初期化
tm_t            tmm;
EthernetUDP     Udp;                                    //イーサネットUDPインスタンス
//UBLOX           gps(Serial1,9600);                      //115200bpsでUBLOX・GPSは通信できるのか要確認
                                                        // これは、HTMLコードを「流す」ためのバッファーです。
                                                        // ””を含む最長の文字チェーン+1と同じ大きさでなければなりません。
char    buffer[100];
                                                        // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。
                                                        // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を
                                                        // 使用する必要があります。そうしないと機能しません。
const byte      ID = 0x91;
bool            GpsEnable = false;
//----------------------------------
void LANSetup(){
        int idcheck = EEPROM.read(0);
        Serial.print(F("LocalID = 0x"));Serial.println(idcheck,HEX);
        if (idcheck == ID){
                //idがIDと同じ値の場合、
                //これは、このスケッチがシールドを設定するために使用されたことを意味します。
                //EERPOMの値を読み取ってシールドを設定します。
                for (int i = 0; i < 6; i++){
                        mac[i] = EEPROM.read(i+1);
                }
                for (int i = 0; i < 4; i++){
                        ip[i] = EEPROM.read(i+7);
                }
                for (int i = 0; i < 4; i++){
                        subnet[i] = EEPROM.read(i+11);
                }
                for (int i = 0; i < 4; i++){
                        gateway[i] = EEPROM.read(i+15);
                }
        }else{
                //idが一致しない場合、初期値を書き込む事にします。
                for (int i = 0 ; i < 6; i++){
                        EEPROM.write(i + 1,mac[i]);
                }
                for (int i = 0 ; i < 4; i++){
                        EEPROM.write(i + 7, ip[i]);
                }
                for (int i = 0 ; i < 4; i++){
                        EEPROM.write(i + 11, subnet[i]);
                }
                for (int i = 0 ; i < 4; i++){
                        EEPROM.write(i + 15, gateway[i]);
                }
                // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                EEPROM.write(0, ID); 
        }
        Ethernet.begin(mac, ip);
//      Ethernet.begin(mac);                            //DHCPの場合
//      Ethernet.begin(mac, ip, subnet);                //SubnetMaskを意識した場合。
//      Ethernet.begin(mac, ip, subnet, gateway);       //gatewayを意識した場合。
}
//--------------------------
void SetWebPage( EthernetClient client){
        client.print(F("<!DOCTYPE HTML PUBLIC \"\"><html><HEAD><META http-equiv=\"Content-Type\" charset=UTF-8\">"));
        client.print(F("<META http-equiv=\"Content-Style-Type\"><TITLE>"));
        client.print(vers);
        client.print(F(" Setup Page</TITLE></HEAD>"));
        client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\"><BLOCKQUOTE><BLOCKQUOTE>"));
        client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">"));
        client.print(F("<tr><td>&nbsp;"));
        client.print(vers);
        client.print(F(" Setup Page</td></tr></table><br>"));
        //
        client.print(F("<script>function hex2num (s_hex) {eval(\"var n_num=0X\" + s_hex);return n_num;}</script>"));
        client.print(F("<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\"><table><tr><td>MAC:</td><td>"));
        client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\""));
        client.print(mac[0],HEX);
        client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\""));
        client.print(mac[1],HEX);
        client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\""));
        client.print(mac[2],HEX);
        client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\""));
        client.print(mac[3],HEX);
        client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\""));
        client.print(mac[4],HEX);
        client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\""));
        client.print(mac[5],HEX);
        //
        client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2"));
        client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4"));
        client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6"));
        client.print(F("\"></td></tr><tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\""));
        client.print(ip[0],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\""));
        client.print(ip[1],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\""));
        client.print(ip[2],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\""));
        client.print(ip[3],DEC);
        //
        client.print(F("\"></td></tr><tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\""));
        client.print(subnet[0],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\""));
        client.print(subnet[1],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\""));
        client.print(subnet[2],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\""));
        client.print(subnet[3],DEC);
        //
        client.print(F("\"></td></tr><tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\""));
        client.print(gateway[0],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\""));
        client.print(gateway[1],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\""));
        client.print(gateway[2],DEC);
        client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\""));
        client.print(gateway[3],DEC);
        client.print(F("\"></td></tr><tr><td><br></td></tr><tr><td><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" "));
        //
        client.print(F("Onclick=\"document.getElementById('T2').value "));
        client.print(F("= hex2num(document.getElementById('T1').value);"));
        client.print(F("document.getElementById('T4').value = hex2num(document.getElementById('T3').value);"));
        client.print(F("document.getElementById('T6').value = hex2num(document.getElementById('T5').value);"));
        client.print(F("document.getElementById('T8').value = hex2num(document.getElementById('T7').value);"));
        client.print(F("document.getElementById('T10').value = hex2num(document.getElementById('T9').value);"));
        client.print(F("document.getElementById('T12').value = hex2num(document.getElementById('T11').value);\""));
        //
        client.print(F("></td><td></td></tr></form></table></BLOCKQUOTE></BLOCKQUOTE></BODY></html>"));
}
//--------------------------
void checkWebPage( EthernetClient client)
{
        Serial.println(F("new webClient"));
        if (client) {
                TextFinder  finder(client );
                while (client.connected()) {
                        //digitalWrite(LED, HIGH);
                        if (client.available()) {
                                //この部分はすべてのテキスト検索を行います。
                                if( finder.find("GET /") ) {
                                        // 「setup」とい語が見つかった場合は、さらに探してください。
                                        // その単語が見つからない場合は、検索を停止して先に進みます。
                                        // これにより、後でスケッチに独自のWebページを配置できます。
                                        if (finder.findUntil("setup", "\n\r")){
                                                // 「SBM」という単語が見つかった場合は、さらに探し続けます。
                                                // その言葉が見つからない場合は、探して停止します。
                                                // SUBMITボタンが押されていない、何も押されていないことを意味します
                                                // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。
                                                if (finder.findUntil("SBM", "\n\r")){
                                                        byte SET = finder.getValue();
                                                        // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、
                                                        // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。
                                                        while(finder.findUntil("DT", "\n\r")){
                                                                int val = finder.getValue();
                                                                // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。
                                                                if(val >= 1 && val <= 6) {
                                                                        mac[val - 1] = finder.getValue();
                                                                }
                                                                // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。
                                                                if(val >= 7 && val <= 10) {
                                                                        ip[val - 7] = finder.getValue();
                                                                }
                                                                // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。
                                                                if(val >= 11 && val <= 14) {
                                                                        subnet[val - 11] = finder.getValue();
                                                                }
                                                                // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。
                                                                if(val >= 15 && val <= 18) {
                                                                        gateway[val - 15] = finder.getValue();
                                                                }
                                                        }
                                                        // すべてのデータを取得したので、EEPROMに保存できます
                                                        for (int i = 0 ; i < 6; i++){
                                                                EEPROM.write(i + 1,mac[i]);
                                                        }
                                                        for (int i = 0 ; i < 4; i++){
                                                                EEPROM.write(i + 7, ip[i]);
                                                        }
                                                        for (int i = 0 ; i < 4; i++){
                                                                EEPROM.write(i + 11, subnet[i]);
                                                        }
                                                        for (int i = 0 ; i < 4; i++){
                                                                EEPROM.write(i + 15, gateway[i]);
                                                        }
                                                        // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                                                        EEPROM.write(0, ID); 
                                                        // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。
                                                        //ハードウェアリセットボタンを使用する必要があります。
                                                }
                                                // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。
                                                client.println("HTTP/1.1 200 OK");
                                                client.println("Content-Type: text/html");
                                                client.println();
                                                //
                                                SetWebPage(client);
                                                
                                                break;
                                        }
                                }
                                client.println("HTTP/1.1 200 OK");
                                client.println("Content-Type: text/html");
                                client.println();
                                // put your own html from here on
                                client.print("IT WORKS: go to ");
                                client.print(ip[0],DEC);
                                for (int i= 1; i < 4; i++){
                                        client.print(".");
                                        client.print(ip[i],DEC);
                                }
                                client.print("/setup");
                                // put your own html until here 
                                break;  
                        }
                }
                //digitalWrite(LED, LOW);
                delay(1);
                client.stop();
        }     
}
//---------------------
void processNTP() {
        //利用可能なデータがある場合は、パケットを読み取ります
        //https://blog.goo.ne.jp/hiro239415/e/c426a545863921a13a9d6a70b7ae4484
        //NTP_Packet (48Byte)
        int packetSize  =       Udp.parsePacket();
        if(packetSize){
                Serial.println(packetSize);
                Udp.read(packetBuffer,NTP_PACKET_SIZE);
                IPAddress       Remote  = Udp.remoteIP();
                int             PortNum = Udp.remotePort();
                //if (!getGPSdata())  return;
                packetBuffer[0]         = 0b00100100;           // 閏秒警告なしLI, 4:SNTPサーバ(Version), 4:サーバ(Mode)
                packetBuffer[1]         = 1 ;                   // 1:一次基準源(GPS等)(stratum
                packetBuffer[2]         = 6 ;                   // ポーリング64秒デフォルト値
                packetBuffer[3]         = 0xFD;                 // 精度 -3
                packetBuffer[7]         = 0;                    // ルート遅延
                packetBuffer[8]         = 0;
                packetBuffer[9]         = 8;
                packetBuffer[10]        = 0;
                packetBuffer[11]        = 0;                    // ルート分散
                packetBuffer[12]        = 0;
                packetBuffer[13]        = 0xC;
                packetBuffer[14]        = 0;
                //gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
                //crack(sUTD, sUTC);
                //timestamp = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second);
                rtclock.getTime(tmm);
                timestamp = numberOfSecondsSince1900Epoch(tmm.year+1970,tmm.month,tmm.day-1,tmm.hour,tmm.minute,tmm.second,timezone);
#if debug
                Serial.print(timestamp);Serial.print(F(":Time synchronization Request received\n"));
                //print_date(gps);
#endif
                tempval                 = timestamp;
                packetBuffer[12]        = 71;                  //"G";
                packetBuffer[13]        = 80;                  //"P";
                packetBuffer[14]        = 83;                  //"S";
                packetBuffer[15]        = 0;                   //"0";
                // reference timestamp
                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]        = 0;
                packetBuffer[21]        = 0;
                packetBuffer[22]        = 0;
                packetBuffer[23]        = 0;
                //Receive Timestamp 受信タイムスタンプ = サーバに要求が届いた時間
                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];
                //Transmit 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;
                //
                packetBuffer[36]        = 0;
                packetBuffer[37]        = 0;
                packetBuffer[38]        = 0;
                packetBuffer[39]        = 0;
                //transmit_timestamp_seconds      
                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      
                packetBuffer[44]        = 0;
                packetBuffer[45]        = 0;
                packetBuffer[46]        = 0;
                packetBuffer[47]        = 0;
                // Reply to the IP address and port that sent the NTP request
                Udp.beginPacket(Remote, PortNum);
                Udp.write(packetBuffer,NTP_PACKET_SIZE);
                Udp.endPacket();
        }
}
//---------------NTP since 1900/01/01
static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s,int timezone) {
        int leapAdjustment = 0;
        if (y >= 1970) {
                y -= 1970;
                leapAdjustment  = 2;                            // LM: 1970 was NOT a leap year!
        }
        uint16_t days = d;
        for (uint8_t i = 1; i < m; ++i)         days += pgm_read_byte(daysInMonth + i - 1);
/*
        if (m > 2 && y % 4 == 0)                ++days;
*/
        if (m > 2 && (y + leapAdjustment) % 4 == 0)     ++days; // LM: Weak but okay for the present
        days += 365 * y + (y + 3) / 4 - 1;
        return days*24L*3600L + (h-timezone)*3600L + mm*60L + s + seventyYears;
}
//------------------
bool readSensor(void){
        if (Serial1.available()) {
                Serial1.readStringUntil('\n');                  //読み捨てて先頭から読めるようにする
                String nmea = Serial1.readStringUntil('\n');
#if debug
                Serial.println(nmea);
#endif
                String  nmeames = nmea.substring(3,6);           // $GP***
                int     count   = 0;
                while(!nmeames.equals(chkHader)){
                       nmea     = Serial1.readStringUntil('\n');
                       nmeames  = nmea.substring(3,6);          // $GP***
                       digitalWrite(LED1, count++%2);
#if debug
                       Serial.print(nmea);Serial.print(F("\t"));Serial.println(nmeames);
#endif
                }
                int     nextPos = 0;
                if ( nmeames.equals(chkHader)){                  // get UTC
                        nextPos         = nmea.indexOf(DELIMITER);//int indexOf(char ch, unsigned int fromIndex) const;
                        String shh      = nmea.substring(nextPos+1,nextPos+3);
                        String smm      = nmea.substring(nextPos+3,nextPos+5);
                        String sss      = nmea.substring(nextPos+5,nextPos+7);
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip UTC
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip Status
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 緯度
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 北緯か南緯か
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 経度
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 東経か西経か
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 地表における移動の速度[knot]
                        nextPos         = nmea.indexOf(DELIMITER,nextPos+1);    //skip 地表における移動の真方位[deg]
                        String sdd      = nmea.substring(nextPos+1,nextPos+3);
                        String smn      = nmea.substring(nextPos+3,nextPos+5);
                        String syy      = nmea.substring(nextPos+5,nextPos+7);
                        Serial.println("20" + syy + smn + sdd + shh + smm + sss);
                        tmm.hour        = shh.toInt();
                        hour            = tmm.hour;
                        tmm.minute      = smm.toInt();
                        minute          = tmm.minute;
                        tmm.second      = sss.toInt();
                        second          = tmm.second;
                        tmm.day         = sdd.toInt();
                        day             = tmm.day -1;
                        tmm.month       = smn.toInt();
                        month           = tmm.month;
                        tmm.year        = syy.toInt() + 2000 - 1970;
                        year            = syy.toInt() + 2000;
                        digitalWrite(LED1, LOW);
                        digitalWrite(LED2, HIGH);
                        if (tmm.year < 30)      return false;
                        return true;
                }else{
                        digitalWrite(LED1, HIGH);
                        //digitalWrite(LED2, LOW);
                        return false;
                }
        }
}
//------------------GPSにアクセスし、取得に成功したら内部時間を更新
bool ReadGPS(){
        if(readSensor()) {
                if(gLoopCount>3600 | !GpsEnable){
                        rtclock.setTime(tmm);                   // 時刻の設定 一時間毎に更新
                        time_t Now = rtclock.getTime() + timezone * 3600;
                        rtclock.setTime(Now);
                        gLoopCount = 1;
                        digitalWrite(LED2, HIGH);
                        GpsEnable      = true;
                }
                hundredths = 0;                                 // LM: GPS time is always acquired on the second (not used)
                age = 0;                                        //     Not used in this adaptation
        }else{
                delay(100); 
                if(gLoopCount%100)      Serial.print(F("."));
                else{
                        Serial.println(F("."));
                        GpsEnable      = false; 
                        //digitalWrite(LED2, LOW);
                }
        }
}
//------------------
void setup() {
        Serial.begin(115200);                           //シリアル通信を開ます。
        delay(1000);
        Serial.println(F("Serial interface started"));
        pinMode(BUILTIN_LED,OUTPUT);
        pinMode(LED1,OUTPUT);digitalWrite(LED1, LOW);
        pinMode(LED2,OUTPUT);digitalWrite(LED2, LOW);
        // start Ethernet and UDP:(Ethernet3API)
        Ethernet.setCsPin(SPI1_NSS_PIN);
        Ethernet.setRstPin(W550io_Rst);
        //
        pinMode(W550io_Rst, OUTPUT);
        digitalWrite(W550io_Rst, LOW);
        delay(10);
        digitalWrite(W550io_Rst, HIGH);
        Serial.print(F("NIC_Reset\n"));
        LANSetup();                                             //Ethernet.begin(mac,ip);
        Serial.println(F("Ethernet interface started"));
        Serial.print(F("server is at "));Serial.println(Ethernet.localIP());
        webServer.begin();
        Udp.begin(NTP_PORT);
        //
#if debug
        Serial.print("Version:");
        Serial.println(vers);
#endif
        //GPS受信機との通信を開始する
        Serial1.begin(9600);                    //9600bps固定の場合
        ReadGPS();                              //GPS取得に成功したら内部時計を更新しています。
        rtclock.getTime(tmm);
        //sprintf(STRBUF,"%02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
        //LedAndKey.setDisplayToString(STRBUF,0,2);
        LedAndKey.setDisplayToString(" HELLO  ",0,0);
        Serial.println(F("EndSetUp"));
}
//-------------------- 
void loop() {
        //参考にしたコードはNTPdは常に動作させ、クライアントから時刻同期要求が有ったら
        //GPSにアクセスするというモノでした。
        //この修正版は、NTPdは常に動作させ、要求があったらBluePillの内部クロックを
        //返します。
        //また、NTPdの周回待ちにGPSにアクセスし、値を取得したら内部時間を更新します。
        //delay(500);
        ReadGPS();
        //delay(500);
        client = webServer.available();
        if(client)      checkWebPage(client);
        client.stop();                          // コネクションを閉じる。
        processNTP();
        gLoopCount++;
        digitalWrite(BUILTIN_LED,gLoopCount%2);  //毎秒LEDの点滅処理を実施
        rtclock.getTime(tmm);
        byte keys = LedAndKey.getButtons();
        switch(keys){
                case 0x01:
                        sprintf(STRBUF,"%04u%02u%02u",tmm.year + 1970,tmm.month,tmm.day);
                        LedAndKey.setDisplayToString(STRBUF,0,0);
                        delay(1000);
                        break;
                case 0x02:
                        sprintf(STRBUF,"  %02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
                        LedAndKey.setDisplayToString(STRBUF,0,0);
                        break;                        
               default:
                        if(GpsEnable){
                                sprintf(STRBUF,"G %02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
                                LedAndKey.setDisplayToString(STRBUF,0,0);
                        }else{
                                sprintf(STRBUF,"C %02u%02u%02u",tmm.hour,tmm.minute,tmm.second);
                                LedAndKey.setDisplayToString(STRBUF,0,0);
                                //LedAndKey.setDisplayToString(" HELLO  ",0,0); 
                                Serial.println(gLoopCount);
                        }
        }
}

このコードを実行すると、Setup()が完了すると LED&KEY に 『HELLO』と表示されます。
GPSアンテナを捕らえると、LED1が点滅します。安定して電波を捕らえ、時刻をBluePill内部時計に登録したら、LED2が点灯します。また、このタイミングで LED&KEY に時刻表示をします。また、表示更新するタイミングでビルトインLED(PC13)を点滅させます。
LED2が点灯しているタイミングで、ネットワーク上のPCから時刻同期を求められたらBluePill内部時計値を返します。
GPSを確保しているかどうかを7セグLEDの一桁目にG/Cの違いで表示するように変更しました。、また、GPSを捕らえたらすぐに『HELLO』を消すように修正しました。
確認しているPCは、

です。
肝心のPLC側の時刻同期検証は出来ておりません。ただ、想定だけはしており、今後機会があれば検証してゆきます。
※仕事柄OMRONのPLC、SYSMACしか検証できないと思っています。


箱に入れて見ました。GPSはAKEEYOのものを接続しています。

ヒマラヤ化学工業所のCUBEという
PP製のケースに入れてみました。
もっとコンパクトな入れ物も有るのでしょうが、この程度でもいいのかな?

100×82×67.5 蓋付きケースです

側面にLAN、USB(マイクロB)、
φ3.5ステレオジャックを装備しています。PPなので加工は楽と云えば楽なのですが、フライス盤を持っていないのでそれなりに大変です。

内部へのアクセスは容易です。

気になっている点として、PPが
静電気に対してどの程度の物か
という事です。
壊れないことを祈りながら運用を
続けます。

部品表

@ ケース \110 ヒマラヤ化学工業所のCUBEセリアで\110です。
加工をどうするかですね。カッタナイフとハンドドリルでなんとかなります。
A STM32miniShield \1650  
B NIC \2442 USR-ES1が入手出来れば安価に出来ます。
AliExpressから入手すれば600円程度でも入手可能です。
C GPS \998 AmazonJapanでCrosstour GPSモジュールを入手しました。
AliExpressから入手すれば500円程度でも入手可能です。
D LED&KEY \850 AmazonJapanだと2個で850円というのがありました。
AliExpressから入手すれば200円程度でも入手可能です。
E φ3.5ステレオジャック \200 秋月で扱っている丸信のジャックの方がお勧めです。
http://akizukidenshi.com/catalog/g/gC-09630/
F JST-XH5Pハウジング \10×2 秋月から購入するのが一番リーズナブルだと思います。
コンタクトは100個入りの場合です。10個入りもあるのですが、結構失敗します。
工具はIZOKEE 圧着ペンチをお勧めします。
G JST-XH4Pハウジング \5×1
H JST-XHコンタクト \200
I ACアダプタ \1100 秋月 AD-B50P250
http://akizukidenshi.com/catalog/g/gM-10507/
J USBケーブル \500 TOPK https://ja.aliexpress.com/item/32912994331.html
合計 \8100  


オブジェクト自体はベクター殿のストレージをお借りしています。

https://www.vector.co.jp/vpack/browse/person/an051501.html



免責事項

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

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