最終更新日:2023年3月1日
※このプロジェクトではBluePillの内部時計を使用しています。BluePillのVBT端子を使えば、内部時計のバッテリーバックアップも可能です。その場合、充電可能な電池を接続することをお勧めします。 LIRxxxx等です。CRxxxxを接続するとCRxxxx側に電流が流れ、爆発の恐れがあります。逆止回路もご検討ください。
※amazonジャパンで販売されていた『Crosstour GPSアクティブアンテナ空中車ダッシュカム』というGPSモジュールを購入しました。端子は3.5mmステレオプラグです。確認したところ、これは使えます。9600bpsです。流れてくるセンテンスは$GPGGA/$GPRMC、、でした。モジュールによってどのGPSを捕らえるように設定されているかまちまちであることが判りました。
問題はこのモジュールから$GPZDAのセンテンスが流れてこないことです。
タイムサーバとして使うなら$GxRMCを解析して日時情報を取得するのが正解なのかと思います。※このtimeserverの精度ですが、文字列情報をもとにしてしまっているため最大1秒の遅れが出るかなと思っています。
1PPSの信号が取得出来るGPSに変更して接点による補正をすれば1msecオーダに出来るはずですが。。※コードの修正をしました。->
主な変更点は
- ユーティリティー頁を追加し、GPSモジュールから読み取ったセンテンスを分析表示するようにしました。
これによりGPSモジュールが電波を捕らえているかどうか確認が出来ます。- TimeZoneを変更出来るようにしました。NTPはUTCで扱いますが、表示はLocalにしているためTimeZoneを容易に変更出来る必要があります。
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が搭載されているモジュールを前提にライブラリを読み替えています。
※このスケッチには大きな問題があることが判りました。それは秒以下の値を0に固定しているのです。これによる同期ミスが発生します。これを解決する対策を講じる必要があります
検証方法自体はWindowsPCのインターネット時刻設定で確認出来ます。実際にビルドしてみました。内部時計を初期化してアクセスしてみたところ、とても反応の早い結果が得られました。期待した通りです。
NTPサーバとしての情報を追加しておきます。
NTP、SNTP のフォーマットについてはRFC-1305、RFC-2030を確認してください。
クライアントから要求されたNTPパケットに対して、サーバ側は情報を追加して返すことになります。
NTP/SNTPでは1900年1月1日0時(UTP)が基準で、64ビットの符号無し固定小数点で扱います。
上位32bitが整数部で1900年1月1日0時からの相対時間を秒単位で表現します。
下位32bitは秒以下の少数位を表しています。
このプログラムではUTP123の受信パケットの先頭48バイトを解析して必要な情報を付加して返信しています。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
0 |
LI | VN | MODE | 階層 | ポーリング間隔 | 精度 | |||||||||||||||||||||||||||
1 |
|
|
||||||||||||||||||||||||||||||||
2 |
|
|||||||||||||||||||||||||||||||||
3 |
|
|||||||||||||||||||||||||||||||||
4 |
|
|||||||||||||||||||||||||||||||||
5 |
||||||||||||||||||||||||||||||||||
6 |
|
|||||||||||||||||||||||||||||||||
7 |
||||||||||||||||||||||||||||||||||
8 |
|
|||||||||||||||||||||||||||||||||
9 |
||||||||||||||||||||||||||||||||||
10 |
|
|||||||||||||||||||||||||||||||||
11 |
||||||||||||||||||||||||||||||||||
12 |
|
|||||||||||||||||||||||||||||||||
13 |
|
|||||||||||||||||||||||||||||||||
14 | ||||||||||||||||||||||||||||||||||
15 | ||||||||||||||||||||||||||||||||||
16 |
SNTP メッセージのセット内容(サーバ・クライアント・モード)
クライアント リクエスト | サーバ 応答 | クライアント 処理 | |
閏秒指示子 | 0 | 0 | |
バージョン番号 | 1〜4 | 4 | |
モード | 3 | 4 | |
階層 | 0 | 1 | |
ポーリング間隔 | 0 | 6 | 無視 |
精度 | 0 | −9 | 無視 |
ルート遅延 | 0 | 0x0080 | 無視 |
ルート分散 | 0 | 0x00C0 | 無視 |
参照識別子 | 0 | 0x71808300 | 無視 |
参照タイムスタンプ | 0 | TimeStamp | 無視 |
開始タイムスタンプ | 0 | クライアントの送信タイムスタンプコピー | クライアントからのリクエスト送信時間がセットされている。 |
受信タイムスタンプ | 0 | TimeStamp | サーバの受信時間がセットされている。 |
送信タイムスタンプ | TimeStamp | TimeStamp | サーバの送信時間がセットされている。 |
ソースを掲載します。追加が必要なライブラリは、
/* * 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バイト(418)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4896バイト(218)を使っていて、ローカル変数で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> ")); 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』から日時取得する としました。
このコードを実行するに当たり追加が必要なライブラリは、
※2021/1/25:v3.7.4 バグ修正のためスケッチをUpdateしました。
※2020/4/17:3/19版にタイミングの問題があり修正 同期を掛けたタイミングによってTimeZone分ずれる問題があったのを修正。
※2020/12/23 NTPプロトコルでは時刻を1900年1月1日0時0分0秒からの経過秒数を64bit符号なし固定小数点で表現します。
上位32bitが整数部、下位32bitが小数点以下となります。 この下位32bitをこれまで0で扱っていました。RMCの値も少数位は.000だったのでいいかなと思っていましたが、クライアントからの要求の場合は下位32bitの時差が気になってしまいます。また、偶にエラーになることが判りました。修正しました。
/* * 2021/1/25 T.Wanibe * v3.7.4の修正点: * ・ntpパケット内の経過時刻表記を修正 * v3.7.3の修正点: * ・isnan()を使用するコードに変更 * v3.7.2の修正点: * ・WDTを組み込みました。 * ソースコードなのかライブラリなのか、数日間動いた時点でMainLoopが止まってしまう様です。しかし、時計はちゃんと進んでいるため割込は動いています。 * これを打開するにはWDTをMainLoopに組み込むべきだと解釈しました * ・WebServerへのアクセスで不用意なURLの場合にきちんと404を出すように見直した。 * v3.7.1の修正点: * setup2画面(ユーティリティ)の充実 * 少数位の時刻の精度見直し * TimeZone追加 * v3.7.0の修正点: * NTPtoolを使って調査したところ、STM32miniGPSNTPサーバは1秒以上時刻ズレが発生することが判った。 * 何度かNTPtoolを実行して判ったこととして少数位の時刻を無視していることが原因と判ったので修正した。 * v3.6.3の修正点: * setupのSubmit実行でマイコンの再起動を実行する様に変更 * v3.6.2の修正点: * :点滅の間隔を1秒に変更 * v3.6.1の修正点: * /setup頁への移行リンク追加 * v3.6の修正点: * @正常運転中アンテナを外したところ時計表示が更新されなかった。バグと思われるので内部時計で更新されるように修正 * AアンテナかGPSの受信が出来ないときのLED挙動を変更 * LED1:アンテナ受信 未受信点灯 受信消灯 * LED2: * BUILTIN_LED:実行中点滅 * NTP Time Serverの時刻表示をTM1637経由で4桁7セグLED版に変更しました。 * アンテナが外れた時に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バイトのフラッシュメモリのうち、スケッチが79192バイト(60%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が5656バイト(27%)を使っていて、ローカル変数で14824バイト使うことができます。 */ #include <libmaple/iwdg.h> #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 <TM1637.h> #include <RTClock.h> #include <EEPROM.h> #include <avr/pgmspace.h> #include <TextFinder.h> //WebSetting // Time Server Port #define ON 1 #define OFF 0 #define NTP_PORT 123 #define vers "GPS NTP STM32miniShield 3.7.3" #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 CLK PB3 //pins definitions for TM1637 and can be changed to other ports #define DIO PB4 #define BUILTIN_LED PC13 #define LED1 PB8 #define LED2 PB9 #define SERIAL_RX_BUFFER_SIZE 256 #define HttpPort 80 //HTTP #define DELIMITER "," #define debug 1 #define iwdg_init_ms(N) iwdg_init(IWDG_PRE_256,((N)/6)) //設定時間をmsecとするためにマクロを用意する。 // Time Server MAC address byte mac[] = {0x00,0x08,0xDC,0x54,0x4D,0xD6}; //Wiznet byte ip[] = {192, 168, 0, 199}; 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]; //データを送受信するためのバッファ int8_t TimeDisp[] = {0x00,0x00,0x00,0x00}; unsigned char ClockPoint = 1; unsigned char Update; unsigned long halfsecond = 0; 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,microVal,fractions; // 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]; float timezone = 9.0; //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 = 3; //1時間毎に更新予定 最初は1分後に更新したくて初期値を調整 //オブジェクトコンストラクタ EthernetServer webServer(HttpPort); EthernetClient client; //TM1638 LedAndKey(dataPin, clockPin, strobePin); //データピンPB4、クロックピンPB3、およびストローブピンPA15でモジュールを定義する TM1637 tm1637(CLK,DIO); RTClock rtclock (RTCSEL_LSE); //RTC初期化 tm_t tmm; EthernetUDP Udp; //イーサネットUDPインスタンス char buffer[100]; // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。 // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を // 使用する必要があります。そうしないと機能しません。 const byte ID = 0x96; bool GpsEnable = false; char buf1[32]; const char FVirsion[] = "3.7.3"; char MacAddressStr[20]; char IPAddressStr[20]; char SubnetMaskStr[20]; char GatewayStr[20]; char NTPserverStr[20]; char DateTimeStr[20]; char Status = 'V'; float unit = 2.33E-7; String nmea; unsigned long baseMilis = millis(); unsigned long miliTime; String setenceStr = "$GPRMC"; String StatusStr = "V = 警告"; String DateTime2Str = "2020年12月24日 8時25分59秒.000"; String DateStr; String TimeStr; String latitudeStr = "北緯 35度41.1493分"; String latitudeNSStr; String longitudeStr = "東経 139度45.3994分"; String longitudeEWStr; String VeloStr = "00.0"; String DirStr = "0.0度"; String ModeStr = "A = 自律方式"; String MagDegStr; String MagDevStr; String NavStr; double latitude; double longitude; float gDayBuf[24][4]; //24時間分4要素データ typedef struct{ uint32_t UTC; long LAT; //10000倍した緯度 long LON; //10000倍した経度 int DIR; //100倍した値0〜3600 int SPD; //速度(ノット単位)を100倍したもの }RMC; RMC gRmc; //---------------------------------- 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); } timezone = float(EEPROM.read(19)) / 10.0; }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]); } EEPROM.write(19,char(timezone * 10)); // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。 EEPROM.write(0, ID); } sprintf(MacAddressStr,"%02x.%02x.%02x.%02x.%02x.%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); sprintf(IPAddressStr,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); sprintf(SubnetMaskStr,"%d.%d.%d.%d",subnet[0],subnet[1],subnet[2],subnet[3]); sprintf(GatewayStr,"%d.%d.%d.%d",gateway[0],gateway[1],gateway[2],gateway[3]); 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.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // client.print(F("<!DOCTYPE HTML PUBLIC \"\">\n<html>\n<HEAD>\n\t<META http-equiv=\"Content-Type\" charset=Shift_JIS\">\n")); client.print(F("\t<META http-equiv=\"Content-Style-Type\">\n\t<TITLE>")); client.print(vers); client.print(F(" Setup Page</TITLE>\n</HEAD>\n")); client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\">\n<BLOCKQUOTE><BLOCKQUOTE>\n")); client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">\n")); client.print(F("<tr><td>")); client.print(vers); client.print(F(" Setup Page</td></tr></table><br>\n")); // client.print(F("<script>\n\tfunction hex2num (s_hex) {\n\t\teval(\"var n_num=0X\" + s_hex);\n\t\treturn n_num;\n\t}\n</script>\n")); client.print(F("<tbody>\n<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\">\n<table>\n<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>\n<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>\n<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>\n<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 COLSPAN='2'>\n<HR ALIGN=CENTER></td></tr>")); //1行空けたい // client.print(F("<tr><td>TimeZone:</td><td><input type=\"text\" size=\"3\" maxlength=\"5\" name=\"DT19\" value=\"")); client.print(timezone,2); client.print(F("\"></td></tr>\n<tr><td><br></td></tr><tr><td COLSPAN='2'><P ALIGN=RIGHT><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" ")); // client.print(F("Onclick=\"")); client.print(F("document.getElementById('T2').value = hex2num(document.getElementById('T1').value);\n")); client.print(F("\t\tdocument.getElementById('T4').value = hex2num(document.getElementById('T3').value);\n")); client.print(F("\t\tdocument.getElementById('T6').value = hex2num(document.getElementById('T5').value);\n")); client.print(F("\t\tdocument.getElementById('T8').value = hex2num(document.getElementById('T7').value);\n")); client.print(F("\t\tdocument.getElementById('T10').value = hex2num(document.getElementById('T9').value);\n")); client.print(F("\t\tdocument.getElementById('T12').value = hex2num(document.getElementById('T11').value);\"")); // client.print(F("></td></tr></tbody>\n</table>\n</BLOCKQUOTE></BLOCKQUOTE>\n</form>\n</BODY>\n</html>\n")); } //-------------------------- 2021/1/5 void SetUtilPage( EthernetClient client){ client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // client.print(F("<!DOCTYPE HTML PUBLIC \"\">\n<html>\n<HEAD>\n\t<META http-equiv=\"Content-Type\" charset=\"UTF-8\">\n")); client.print(F("\t<META http-equiv=\"Content-Style-Type\">\n\t<TITLE>")); client.print(vers); client.print(F(" Utility Page</TITLE>\n")); client.print(F("\t<script type='application/javascript' SRC='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.bundle.js'></script>\n")); client.print(F("\t<script type='application/javascript' SRC='https://cdn.rawgit.com/chartjs/Chart.js/master/samples/utils.js'></script>\n")); client.print(F("\t<style>\n\t\tcanvas{\n")); client.print(F("\t\t\t-moz-user-select: none;\n")); client.print(F("\t\t\t-webkit-user-select: none;\n")); client.print(F("\t\t\t-ms-user-select: none;\n\t\t}\n")); client.print(F("\t\thtml { font-family: Helvetica; display: inline-block; margin: 0px auto;}\n")); client.print(F("\t\tbody{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n")); client.print(F("\t\tp {font-size: 24px;color: #444444;margin-bottom: 10px;}\n")); client.print(F("\t</style>\n</HEAD>\n")); client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\" BGCOLOR=\"#ffffff\">\n")); client.print(F("<FORM>\n<BLOCKQUOTE><BLOCKQUOTE>\n")); client.print(F("<P><table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\" CELLSPACING=\"2\">\n")); client.print(F("<tr><td>")); client.print(vers); client.print(F(" Utility Page</td></tr></table><br>\n")); // client.print(F("<TABLE WIDTH=\"100%\" BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">FirmwareVirsion:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(FVirsion); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">MacAddress:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(MacAddressStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">IPAddress:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(IPAddressStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\">SubnetMask:</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(SubnetMaskStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"25%\" NOWRAP>Data acquisition time(Local):</TD>\n\t<TD WIDTH=\"20%\"></TD>\n\t<TD WIDTH=\"55%\">")); client.print(DateTimeStr); client.print(F("</TD>\n</TR></TABLE></P>\n")); client.print(F("<P><TABLE WIDTH=\"750\" BORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"0\" HEIGHT=\"101\">\n")); client.print(F("\t<CAPTION ALIGN=\"TOP\"><P ALIGN=LEFT>GPS センテンス</CAPTION>")); client.print(F("\t<TR>\n\t\t<TD COLSPAN=\"2\" BGCOLOR=\"#000000\">\n\t\t<BLOCKQUOTE>\n")); client.print(F("\t\t\t<PRE><FONT COLOR=\"#ffffff\" SIZE=\"+1\">")); client.print(nmea); Serial.println(nmea); //$GPRMC,021227.000,A,3459.9060,N,13652.6560,E,0.09,344.80,241220,,,A*6D //$GNRMC,072815.000,V, , , , , , ,241220,,,N,V*27 //RMCは要素数が固定されていないようなので注意が必要 analsysRMC(); client.print(F("</FONT></PRE>\n\t\t</BLOCKQUOTE>")); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">ステータス:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(StatusStr); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">日時(UTC):</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(DateTime2Str); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">位置情報:緯度</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(latitudeNSStr); client.print(latitudeStr); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">位置情報:経度</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(longitudeEWStr); client.print(longitudeStr); client.print(F("</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">移動の速度:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(VeloStr); client.print(F("[knot]</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">移動の真方位:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(DirStr); client.print(F("[度]</TD>\n\t</TR>\n\t<TR>\n\t\t<TD WIDTH=\"150\" HEIGHT=\"20\">モード:</TD>\n\t\t<TD WIDTH=\"600\">")); client.print(ModeStr); client.print(F("</TD>\n\t</TR>\n</TABLE>\n<CENTER>\n")); client.print(F("\t<div style='width:75%;'><canvas id='canvas' width='750' height='400' style='display: block; width: 750px; height: 400px;'></canvas></div>\n")); client.print(F("</CENTER></BLOCKQUOTE></BLOCKQUOTE>\n</form>\n")); client.print(F("<script>\n")); client.print(F("\t\tvar randomScalingFactor = function() {\n\t\t\treturn 18 + Math.random() * 10;\n\t\t};\n")); client.print(F("\t\tvar data_00 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][0]) ? String('\0') : String(gDayBuf[i][0],6)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_01 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][1]) ? String('\0') : String(gDayBuf[i][1],6)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_02 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][2]) ? String('\0') : String(gDayBuf[i][2],2)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar data_03 = [")); for(int i = 0; i<24 ;i++){ client.print(isnan(gDayBuf[i][3]) ? String('\0') : String(gDayBuf[i][3],2)); client.print(F(",")); } client.print(F("];\n")); client.print(F("\t\tvar config = {")); client.print(F("\t\t\ttype: 'line',\n\t\t\tdata: {\n")); client.print(F("\t\t\t\tlabels: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12' , '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],\n")); client.print(F("\t\t\t\tdatasets: [{\n")); client.print(F("\t\t\t\t\tlabel: '緯度[度]',\n")); client.print(F("\t\t\t\t\tdata: data_00,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.red,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tcubicInterpolationMode: 'monotone',\n")); client.print(F("\t\t\t\t\tyAxisID: 'y1'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '経度[度]',\n")); client.print(F("\t\t\t\t\tdata: data_01,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.blue,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y2'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '速度[knot]',\n")); client.print(F("\t\t\t\t\tdata: data_02,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.orange,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y3'\n")); client.print(F("\t\t\t\t}, {\n")); client.print(F("\t\t\t\t\tlabel: '真方位[度]',\n")); client.print(F("\t\t\t\t\tdata: data_03,\n")); client.print(F("\t\t\t\t\tborderColor: window.chartColors.yellow,\n")); client.print(F("\t\t\t\t\tbackgroundColor: 'rgba(0, 0, 0, 0)',\n")); client.print(F("\t\t\t\t\tfill: false,\n")); client.print(F("\t\t\t\t\tyAxisID: 'y4'\n")); client.print(F("\t\t\t\t}]\n\t\t\t},\n")); client.print(F("\t\t\toptions: {\n\t\t\t\tresponsive: true,\n")); client.print(F("\t\t\t\ttitle: {\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\ttext: '今日のGPS位置情報'\n\t\t\t\t},\n")); client.print(F("\t\t\t\tlegend: {\n\t\t\t\t\tdisplay: true\n\t\t\t\t},\n")); client.print(F("\t\t\t\ttooltips: {\n\t\t\t\t\tenabled: true,\n\t\t\t\t\tmode: 'index'\n\t\t\t\t},\n")); client.print(F("\t\t\t\tscales: {\n\t\t\t\t\txAxes: [{\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '時刻 [時]'\n\t\t\t\t\t\t}\n\t\t\t\t\t}],\n")); client.print(F("\t\t\t\t\tyAxes: [{\n\t\t\t\t\t\tid:'y1',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.red,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '緯度[度]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.red,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: -120,\n\t\t\t\t\t\t\tsuggestedMax: 120\n\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y2',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.blue,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '経度[度]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.blue,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: -200,\n\t\t\t\t\t\t\tsuggestedMax: 200\n\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y3',\n\t\t\t\t\t\tposition: 'right',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.orange,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '速度[knot]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.orange,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: 0,\n\t\t\t\t\t\t\tsuggestedMax: 200\n\t\t\t\t\t\t\t}\n")); client.print(F("\t\t\t\t\t},{\n\t\t\t\t\t\tid:'y4',\n\t\t\t\t\t\tposition: 'right',\n\t\t\t\t\t\tdisplay: true,\n")); client.print(F("\t\t\t\t\t\tscaleLabel: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.yellow,\n")); client.print(F("\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tlabelString: '真方位[度]'\n\t\t\t\t\t\t},\n")); client.print(F("\t\t\t\t\t\tticks: {\n\t\t\t\t\t\t\tfontColor: window.chartColors.yellow,\n")); client.print(F("\t\t\t\t\t\t\tsuggestedMin: 0,\n\t\t\t\t\t\t\tsuggestedMax: 400\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n")); client.print(F("\t\t\t\t}\n\t\t\t}\n\t\t};\n")); client.print(F("\t\twindow.onload = function() {\n\t\t\tvar ctx = document.getElementById('canvas').getContext('2d');\n")); client.print(F("\t\t\twindow.myLine = new Chart(ctx, config);\n\t\t};\n")); client.print(F("</script>\n")); client.print(F("</BODY></html>")); } //-------------------------- void checkWebPage( EthernetClient client) { Serial.println(F("new webClient")); boolean restart = false; 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")){ long cord = finder.getValue(); sprintf(buf1,"setupIn=%d",cord); Serial.println(buf1); switch(cord){ case 1: // 「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(); } //「DT」のvalが19の場合、対応する値はtimezoneです。 timezone = 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]); } EEPROM.write(19, char(timezone * 10)); // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。 EEPROM.write(0, ID); sprintf(MacAddressStr,"%02x.%02x.%02x.%02x.%02x.%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); sprintf(IPAddressStr,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); sprintf(SubnetMaskStr,"%d.%d.%d.%d",subnet[0],subnet[1],subnet[2],subnet[3]); sprintf(GatewayStr,"%d.%d.%d.%d",gateway[0],gateway[1],gateway[2],gateway[3]); // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。 //ハードウェアリセットボタンを使用する必要があります。 restart = true; }else{ } // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。 SetWebPage(client); break; case 2: // 新規にユーティリティ頁を作りました。 Serial.println(F("utilIn")); rtclock.getTime(tmm); sprintf(DateTimeStr,"%04u/%02u/%02u %02u:%02u:%02u",tmm.year+1970, tmm.month, tmm.day,tmm.hour, tmm.minute, tmm.second); SetUtilPage(client); break; default: PrintResponse404(client ); break; } }else{ pageIntroduction(client); } } break; } PrintResponse404(client ); } delay(1); client.stop(); if(restart) nvic_sys_reset(); } } //---------------------------- void pageIntroduction( EthernetClient client){ client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // put your own html from here on sprintf(buf1,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); client.print("<HTML>\n<HEAD></HEAD>\n<BODY>\n<P>IT WORKS: go to <B><A HREF=\"https://"); client.print(buf1);client.print("/setup1\">"); client.print(buf1);client.print("/setup1</A></B></P>\n"); client.print("<BLOCKQUOTE>\n\t<BLOCKQUOTE>\n\t\t<P>or</P>\n\t</BLOCKQUOTE>\n</BLOCKQUOTE>\n"); client.print("<P>IT WORKS: go to <B><A HREF=\"https://"); client.print(buf1);client.print("/setup2\">"); client.print(buf1);client.print("/setup2</A></B></P>\n</BODY>\n</HTML>\n"); // put your own html until here client.stop(); } //-------------------------- void PrintResponse404( EthernetClient client ) { client.println(F("HTTP/1.1 404 Not Found")); client.println(F("Content-Type: text/html")); client.println(F("Connnection: close")); client.println(); client.stop(); } //--------------------- void processNTP() { //利用可能なデータがある場合は、パケットを読み取ります //https://blog.goo.ne.jp/hiro239415/e/c426a545863921a13a9d6a70b7ae4484 //NTP_Packet (48Byte) int packetSize = Udp.parsePacket(); if(packetSize){ digitalWrite(BUILTIN_LED,HIGH); //要求があったときにBuiltInLEDを点灯 miliTime = millis() - baseMilis; fractions = uint32_t(float(miliTime % 1000) / unit); sprintf(buf1,"■%d\t%d\n",packetSize,fractions); Serial.print(buf1); 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] = 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; //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,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; microVal = fractions; packetBuffer[12] = 71; //"G"; ReferenceID packetBuffer[13] = 80; //"P"; ReferenceID packetBuffer[14] = 83; //"S"; ReferenceID packetBuffer[15] = 0; //"0"; ReferenceID // 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] = (microVal >> 24) & 0xFF; microVal = fractions; packetBuffer[21] = (microVal >> 16) & 0xFF; microVal = fractions; packetBuffer[22] = (microVal >> 8) & 0xFF; microVal = fractions; packetBuffer[23] = microVal & 0xFF; //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 送信タイムスタンプ = サーバからクライアントに送られた時間 rtclock.getTime(tmm); timestamp = numberOfSecondsSince1900Epoch(tmm.year+1970,tmm.month,tmm.day,tmm.hour,tmm.minute,tmm.second,timezone); 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; //transmit_timestamp_seconds rtclock.getTime(tmm); timestamp = numberOfSecondsSince1900Epoch(tmm.year+1970,tmm.month,tmm.day,tmm.hour,tmm.minute,tmm.second,timezone); 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; // Reply to the IP address and port that sent the NTP request Udp.beginPacket(Remote, PortNum); Udp.write(packetBuffer,NTP_PACKET_SIZE); Udp.endPacket(); digitalWrite(BUILTIN_LED,LOW); //処理完了で消灯 sprintf(buf1,"●%d\t%d\t%d\t%d\n",packetBuffer[20],packetBuffer[21],packetBuffer[22],packetBuffer[23]); Serial.print(buf1); } } //---------------NTP since 1900/01/01 retouch2020/12/25 static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s,float timezone) { //2036年問題が存在する。unsigned long intの桁溢れが発生 //桁溢れが発生したら受信側が対応する必要がある。サーバ側では64bit化するべきだが動かなくなる。 //timezoneをfloat扱いにした 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; long offsetTime = long((float(h)- timezone) * 3600.0); return days*24L*3600L + offsetTime + mm*60L + s + seventyYears; } //------------------ bool readSensor(void){ if (Serial1.available()) { Serial1.readStringUntil('\n'); //読み捨てて先頭から読めるようにする nmea = Serial1.readStringUntil('\n'); #if debug Serial.println(nmea); #endif //$GNRMC,022757.000,A,3430.62587,N,13316.66175,E,0.63,144.24,231220,,,A,V*02 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(LED2, count++%2); #if debug Serial.print(nmea);Serial.print(F("\t"));Serial.println(nmeames); #endif if(count>30){ digitalWrite(LED1, HIGH); break; } } 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.year = syy.toInt() + 2000 - 1970; //Serial.println(tmm.year); if ((tmm.year < 31) || (tmm.year > 100)){ //2001~2079をサポート return false; }else{ 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; year = syy.toInt() + 2000; digitalWrite(LED1, LOW); digitalWrite(LED2, HIGH); return true; } }else{ digitalWrite(LED1, HIGH); return false; } } } //------------------GPSにアクセスし、取得に成功したら内部時間を更新 bool ReadGPS(){ if(readSensor()) { if(gLoopCount>60 | !GpsEnable){ rtclock.setTime(tmm); // 時刻の設定 一時間毎に更新 time_t Now = rtclock.getTime() + int(timezone * 3600.0); rtclock.setTime(Now); baseMilis = millis(); //20201224追加 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%60) Serial.print(F(".")); else{ Serial.println(F(".")); GpsEnable = false; } } if(gLoopCount > 0x7FFFFFF0) gLoopCount = 1; } //----------------- 割込CallBack void TimeUpdate(void){ rtclock.getTime(tmm); if(ClockPoint) tm1637.point(POINT_ON); else tm1637.point(POINT_OFF); TimeDisp[0] = tmm.hour / 10; TimeDisp[1] = tmm.hour % 10; TimeDisp[2] = tmm.minute / 10; TimeDisp[3] = tmm.minute % 10; ClockPoint = (~ClockPoint) & 0x01; tm1637.display(TimeDisp); } //----------------- int getHourTime(void){ rtclock.getTime(tmm); return tmm.hour; } //----------------- RMC文字列を解析してクラスタRMCにセットする void analsysRMC(void){ //$GPRMC,021227.000,A,3459.9060,N,13652.6560,E,0.09,344.80,241220,,,A*6D //$GNRMC,072815.000,V, , , , , , ,241220,,,N,V*27 //RMCは要素数が固定されていないようなので注意が必要 int stPosi = 0; int endPosi; char State; //センテンス endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ setenceStr = nmea.substring(stPosi,endPosi); Serial.print(F("setenceStr="));Serial.println(setenceStr); } stPosi = endPosi+1; //測位時刻(UTC時分秒) UTC時刻:09時24分03秒307 endPosi = nmea.indexOf(',',stPosi); Serial.print(F("endPosi="));Serial.println(endPosi); if(!(endPosi<0)){ TimeStr = nmea.substring(stPosi,endPosi); Serial.print(F("TimeStr="));Serial.println(TimeStr); } stPosi = endPosi+1; //測位状態 A:単独測位中またはDGPS測位中 V:未測位 単独測位中/DGPS測位中 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ StatusStr = nmea.substring(stPosi,endPosi); Serial.print(F("StatusStr="));Serial.println(StatusStr); } stPosi = endPosi+1; //緯度。dddmm.mmmm (10進) 緯度 43.07328 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ latitudeStr = nmea.substring(stPosi,endPosi); Serial.print(F("latitudeStr="));Serial.println(latitudeStr); } stPosi = endPosi+1; //N:北緯、S:南緯 北緯 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ latitudeNSStr = nmea.substring(stPosi,endPosi); Serial.print(F("latitudeNSStr="));Serial.println(latitudeNSStr); } stPosi = endPosi+1; //経度。dddmm.mmmm (10進) 経度 141.270980 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ longitudeStr = nmea.substring(stPosi,endPosi); Serial.print(F("longitudeStr="));Serial.println(longitudeStr); } stPosi = endPosi+1; //E:東経、W:西経 東経 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ longitudeEWStr = nmea.substring(stPosi,endPosi); Serial.print(F("longitudeEWStr="));Serial.println(longitudeEWStr); } stPosi = endPosi+1; //速度(000.0〜270.0、単位:ノット) 速度 0 Knot endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ VeloStr = nmea.substring(stPosi,endPosi); Serial.print(F("VeloStr="));Serial.println(VeloStr); } stPosi = endPosi+1; //真方位による進行方向(000.0〜359.9、単位:度) 240.3度 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ DirStr = nmea.substring(stPosi,endPosi); Serial.print(F("DirStr="));Serial.println(DirStr); } stPosi = endPosi+1; //ddmmyy 日付(日、月、年) 日付 2017年7月15日 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ DateStr = nmea.substring(stPosi,endPosi); Serial.print(F("DateStr="));Serial.println(DateStr); } stPosi = endPosi+1; //磁気偏差(000.0〜180.0、単位:度) 0(なし) endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ MagDegStr = nmea.substring(stPosi,endPosi); Serial.print(F("MagDegStr="));Serial.println(MagDegStr); } stPosi = endPosi+1; //磁気偏差 E:磁気偏差が東より W:磁気偏差が西より なし endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ MagDevStr = nmea.substring(stPosi,endPosi); Serial.print(F("MagDevStr="));Serial.println(MagDevStr); } stPosi = endPosi+1; //測位mode N:未測位 A:単独測位 D:DGPS測位 endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ ModeStr = nmea.substring(stPosi,endPosi); Serial.print(F("ModeStr="));Serial.println(ModeStr); }else{ ModeStr = nmea.substring(stPosi,stPosi+1); Serial.print(F("ModeStr="));Serial.println(ModeStr); } stPosi = endPosi+1; //ナビゲーション状態 S:Safe C:Caution U:Unsafe V:無効(RAIM 機能 OFF) endPosi = nmea.indexOf(',',stPosi); if(!(endPosi<0)){ NavStr = nmea.substring(stPosi,endPosi); Serial.print(F("NavStr="));Serial.println(NavStr); }else{ NavStr = nmea.substring(stPosi,stPosi+1); Serial.print(F("NavStr="));Serial.println(NavStr); } Status = StatusStr.charAt(0); switch(Status){ case 'A': StatusStr = "A = 有効"; digitalWrite(LED1, LOW); break; case 'V': StatusStr = "V = 警告"; digitalWrite(LED1, HIGH); break; default: StatusStr = ""; digitalWrite(LED1, HIGH); break; } DateTime2Str = "20" + DateStr.substring(4,6) + "年" + DateStr.substring(2,4) + "月" + DateStr.substring(0,2) + "日 " + TimeStr.substring(0,2) + "時" + TimeStr.substring(2,4) + "分" + TimeStr.substring(4,6) + "秒" + TimeStr.substring(7,10); State = latitudeNSStr.charAt(0); switch(State){ case 'N': latitudeNSStr = "北緯 "; break; case 'S': latitudeNSStr = "南緯 "; break; default: latitudeNSStr = ""; break; } stPosi = 0; endPosi = latitudeStr.indexOf('.',stPosi); if(endPosi>0){ String latitudeStr1 = latitudeStr.substring(stPosi,endPosi-2); String latitudeStr2 = latitudeStr.substring(endPosi-2,endPosi); String latitudeStr3 = "." + latitudeStr.substring(endPosi+1); latitude = latitudeStr1.toFloat() + (latitudeStr2.toFloat()/60.0) + (latitudeStr3.toFloat()/60.0); if(State == 'S') latitude = -latitude; latitudeStr = latitudeStr1 + "度" + latitudeStr2 + "分" + latitudeStr3; } State = longitudeEWStr.charAt(0); switch(State){ case 'E': longitudeEWStr = "東経 "; break; case 'W': longitudeEWStr = "西経 "; break; default: longitudeEWStr = ""; break; } stPosi = 0; endPosi = longitudeStr.indexOf('.',stPosi); if(endPosi>0){ String longitudeStr1 = longitudeStr.substring(stPosi,endPosi-2); String longitudeStr2 = longitudeStr.substring(endPosi-2,endPosi); String longitudeStr3 = "." + longitudeStr.substring(endPosi+1); longitude = longitudeStr1.toFloat() + (longitudeStr2.toFloat()/60.0) + (longitudeStr3.toFloat()/60.0); if(State == 'W') longitude = -longitude; longitudeStr = longitudeStr1 + "度" + longitudeStr2 + "分" + longitudeStr3; } State = ModeStr.charAt(0); switch(State){ case 'N': ModeStr = "N = データなし"; break; case 'A': ModeStr = "A = Autonomous(自律方式)"; break; case 'D': ModeStr = "D = Differential(干渉測位方式)"; break; case 'E': ModeStr = "E = Estimated(推定)"; break; case 'M': ModeStr = "M = マニュアル入力"; break; case 'S': ModeStr = "S = シミュレーター入力"; break; default: ModeStr = State; break; } gRmc.UTC = timestamp; gRmc.LAT = long(latitude * 10000.0); gRmc.LON = long(longitude * 10000.0); gRmc.SPD = int(VeloStr.toFloat() * 100.0); gRmc.DIR = int(DirStr.toFloat() * 100.0); } //------------------ void setup() { Serial.begin(115200); //シリアル通信を開ます。 delay(1000); Serial.println(F("Serial interface started")); pinMode(BUILTIN_LED,OUTPUT); pinMode(LED1,OUTPUT);digitalWrite(LED1, HIGH); pinMode(LED2,OUTPUT);digitalWrite(LED2, LOW); digitalWrite(BUILTIN_LED,LOW); //LED消灯 //TM1637 init Timmer Setup tm1637.set(); tm1637.init(); Timer1.pause(); // Timer1.setPrescaleFactor(7200); // 72MHz100uSEC Timer1.setOverflow(5000); // 500mSEC Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,TimeUpdate); // 割り込みサーブルーチンの宣言:TimeUpdate Timer1.setCount(0); // 0 Timer1.refresh(); Timer1.resume(); // 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); for(int i = 0;i<24;i++){ for(int j = 0;j<4;j++){ gDayBuf[i][j] = NAN; } } //sprintf(STRBUF,"%02u%02u%02u",tmm.hour,tmm.minute,tmm.second); //LedAndKey.setDisplayToString(STRBUF,0,2); //LedAndKey.setDisplayToString(" HELLO ",0,0); iwdg_init_ms(5000); //ウォッチドッグをオンにして、オーバーフロー時間を設定します。 Serial.println(F("EndSetUp")); } //-------------------- int count = 0; int preSet = -1; void loop() { //このLoopはscantime=1秒程度です。そのため5秒経っても戻らなかったらリセットすることにします。 iwdg_feed(); //ウォッチドッグタイマーをリセットするには、ドッグ操作をフィードします //参考にしたコードはNTPdは常に動作させ、クライアントから時刻同期要求が有ったら //GPSにアクセスするというモノでした。 //この修正版は、NTPdは常に動作させ、要求があったらBluePillの内部クロックを //返します。 //また、NTPdの周回待ちにGPSにアクセスし、値を取得したら内部時間を更新します。 //delay(500); ReadGPS(); //delay(500); client = webServer.available(); if(client) checkWebPage(client); client.stop(); // コネクションを閉じる。 processNTP(); int h = getHourTime(); //返値0-23 if((Status == 'A') && (preSet != h)){ //1時間おきに時刻を確認し、データをメモリに確保 analsysRMC(); preSet = h; gDayBuf[h][0] = float(gRmc.LAT) / 10000.0; gDayBuf[h][1] = float(gRmc.LON) / 10000.0; gDayBuf[h][2] = float(gRmc.SPD) / 100.0; gDayBuf[h][3] = float(gRmc.DIR) / 100.0; //次回分のデータをNULL化 h = (h+1) % 24; gDayBuf[h][0] = NAN; gDayBuf[h][1] = NAN; gDayBuf[h][2] = NAN; gDayBuf[h][3] = NAN; } gLoopCount++; //digitalWrite(BUILTIN_LED,gLoopCount%2); //毎秒LEDの点滅処理を実施 //rtclock.getTime(tmm); }このコードを実行すると、Setup()が完了すると LED&KEY に 『HELLO』と表示されます。
GPSアンテナを捕らえると、LED1が点滅します。安定して電波を捕らえ、時刻をBluePill内部時計に登録したら、LED2が点灯します。また、このタイミングで LED&KEY に時刻表示をします。また、表示更新するタイミングでビルトインLED(PC13)を点滅させます。
LED2が点灯しているタイミングで、ネットワーク上のPCから時刻同期を求められたらBluePill内部時計値を返します。
GPSを確保しているかどうかを7セグLEDの一桁目にG/Cの違いで表示するように変更しました。、また、GPSを捕らえたらすぐに『HELLO』を消すように修正しました。
確認しているPCは、
- Windows7SP1・PC
![]()
- Windows10・PC
- ubuntu 18.04.1LTS ※標準でntpdateコマンドが登録されていないので追加する必要があります。
設定をするにはパーミションの問題があって出来ませんでしたが、問い合わせ(-q)は出来ました。
root権限(sudo)で実行すれば成功しました。
![]()
- Rasibianというラズベリーパイ用OSで試しました。基本ubuntuと同じですが。
ただ、この回は一寸別の意味でうまくいきませんでしたが、問い合わせは出来ています。
![]()
です。
肝心のPLC側の時刻同期検証は出来ておりません。ただ、想定だけはしており、今後機会があれば検証してゆきます。
※仕事柄OMRONのPLC、SYSMACしか検証できないと思っています。
- SYSMAC、NJシリーズは時刻同期について記事がありました。
https://www.myomron.com/index.php?action=kb&print=1556
![]()
- SYSMAC、CJ2シリーズですが、EtherNet/IPモジュール側で時刻同期を取るそうです。
マニュアルの 12-1 Automatic Clock Adjustment に記載があります。
![]()
箱に入れて見ました。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 秋月で扱っている丸信のジャックの方がお勧めです。
https://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
https://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社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。