ESP32 (Arduino IDE)でNTPサーバーから情報を取得する
サークル”オタク語り”アドベントカレンダー2023 12/8
※コードは適当なので参考までに
こんにちは。
趣味の電子工作でESP32を用いてNTPと通信し時刻合わせをする時計を製作したので、NTPサーバーとの通信に関して覚え書き的にブログを書こうと思います。
ESP32 (Arduino IDE)においては、configTime()関数*1がありますが、今回RTCへの書き込み(秒が変わるピッタリのタイミングで書き込みする必要がある)や、うるう秒情報の取得を行いたかったので、パケット情報を全て取得することに挑戦しました。
NTPについて
NTPについての詳細な解説は他の詳しい人に任せて、ざっくりと説明します。
NTPは機器の時刻を合わせるためのプロトコルです。NTPサーバーにアクセスすると、今の正確な時刻を教えてくれます。実際には通信に時間が少しかかりますので、機器からサーバーの通信とサーバーから機器の通信にかかる時間が同じとして補正を行います。
NTPのパケットについて
NTPでは、サーバーにパケット(データ)を送信することでリクエストを行い、同様のパケットを受信します。
送受されるパケットはRFC5905*2で下記のように定義されています。
NTPパケットの内容

0:警告なし
1:次の9:00(日本時間)に+1秒のうるう秒
2:次の9:00(日本時間)に-1秒のうるう秒
3:不明
VN:バージョン 3bit整数
今はver. 4
Mode:モード 3bit整数
0:予約
1:対称アクティブ
2:対称パッシブ
3:クライアント
4:サーバー
5:ブロードキャスト
6:NTPコントロールメッセージ
7:私的利用のため予約
Stratum:Stratum 8bit整数
数字が小さいほど階層が高く(基準の時計に近く)なります。
Poll:ポーリング間隔 8bit符号付き整数、log2(x)
NTPリクエストする間隔。底を2とした対数が取られています。
Precision:精度 8bit符号付き整数、log2(x)
時刻の精度を表します。底を2とした対数が取られています。
Root Delay:ルート遅延 32bit固定小数点
基準クロックからの通信に要する合計時間
Root Dispersion:ルート分散 32bit固定小数点
基準クロックからの通信時間の合計分散
Reference Timestamp:参照時間 64bit固定小数点
サーバーの時刻を最後に同期した時刻
Origin Timestamp:起点時間 64bit固定小数点
クライアントがパケットを送信した時間
Receive Timestamp:受信時間 64bit固定小数点
サーバーがパケットを受信した時間
Transmit Timestamp:送信時間 64bit固定小数点
サーバーがパケットを送信した時間
実際に通信してみた
今回の仕様
今回はNICTが管理するNTPサーバー(Stratum1)のntp.nict.jpにリクエストを送信することにします。
またNTPのモードに関して、NTPサーバーは互いを参照しあい時刻を同期するといった機能があったりしますが、今回は時刻を取得するのみで、ESP32から同期用時刻情報を送信することはないので、Server / Clientモードを使います。 つまり、Mode3の空パケットをリクエストとして送信します。
NTPサーバーのUDPにおけるポート番号は基本123ですので、そのように設定します。
コード(Arduino IDEで書き込み)
#include <WiFi.h> #include <WiFiUdp.h> //WiFi char WiFissid[32] = "My_SSID"; // enter your WiFi SSID char WiFipass[32] = "My_pass"; // enter your WiFi password WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP byte mac[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //write default MAC address unsigned int localPort = 8888; // local port to listen for UDP packets const char NTPServer[] = "ntp.nict.jp"; // NTP server const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets void connectWiFi() { Serial.println("WiFi Connecting..."); WiFi.mode(WIFI_STA); WiFi.begin(WiFissid, WiFipass); //WiFi begin try unsigned int startTime = millis(); while (1) { if (WiFi.status() == WL_CONNECTED) { Serial.println("WiFi Connected"); WiFi.macAddress(mac); break; } if (millis() - startTime >= 5000) { Serial.println("WiFi connection timed out"); break; } delay(1); } } //NTP bool NTP() { // Initialize values needed to form NTP request memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0 packetBuffer[0] = 0b00100011; // LI, Version, Mode Udp.begin(localPort); Udp.beginPacket(NTPServer, 123); // NTP requests are to port 123 unsigned long time_send = millis(); Udp.write(packetBuffer, NTP_PACKET_SIZE); //send request Udp.endPacket(); while (Udp.parsePacket() == 0) { if (millis() - time_send >= 5000) { return 0; } } Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer // the timestamp starts at byte 40 of the received packet // this is NTP time (seconds since Jan 1 1900): Serial.print("LI:"); Serial.println(packetBuffer[0] >> 6); Serial.print("VN:"); Serial.println((packetBuffer[0] >> 3) & 0x07); Serial.print("Mode:"); Serial.println(packetBuffer[0] & 0x07); Serial.print("Stratum:"); Serial.println(packetBuffer[1]); Serial.print("Poll:"); Serial.println(pow(2, (int8_t)packetBuffer[2])); Serial.print("Precision:"); Serial.println(pow(2, (int8_t)packetBuffer[3])); Serial.print("Root Delay:"); Serial.println(FixedPoint32toFloat(&packetBuffer[4])); Serial.print("Root Dispersion:"); Serial.println(FixedPoint32toFloat(&packetBuffer[8])); Serial.print("Reference Timestamp:"); Serial.println(FixedPoint64toFloat(&packetBuffer[16])); Serial.print("Origin Timestamp:"); Serial.println(FixedPoint64toFloat(&packetBuffer[24])); Serial.print("Receive Timestamp:"); Serial.println(FixedPoint64toFloat(&packetBuffer[32])); Serial.print("Transmit Timestamp:"); Serial.println(FixedPoint64toFloat(&packetBuffer[40])); return 1; } //type conversion double FixedPoint64toFloat(byte *b0) { // combine the four bytes (two words) into a long integer unsigned long integer = word(b0[0], b0[1]) << 16 | word(b0[2], b0[3]); unsigned long decimal = word(b0[4], b0[5]) << 16 | word(b0[6], b0[7]); double retval = integer + (double)decimal / (pow(2, 32)); return retval; } double FixedPoint32toFloat(byte *b0) { // combine the four bytes (two words) into a long integer unsigned long integer = word(b0[0], b0[1]); unsigned long decimal = word(b0[2], b0[3]); double retval = integer + (double)decimal / (pow(2, 16)); return retval; } void setup() { Serial.begin(115200); connectWiFi(); } void loop() { NTP(); delay(1000*60*5); }
実行結果(Serial Monitor)
LI:0 VN:4 Mode:4 Stratum:1 Poll:1.00 Precision:0.00 Root Delay:0.00 Root Dispersion:0.00 Reference Timestamp:3910875529.00 Origin Timestamp:0.00 Receive Timestamp:3910875529.16 Transmit Timestamp:3910875529.16
上記のように、時刻を取得することができました。(時刻を変換すれば、僕がいつこのブログを書いたか分かります)
おわりに
今回はESP32でNTPサーバーから時刻を取得する方法について、共有させていただきました。プログラミングに関しては素人なので、指摘があればいただけるとありがたかったりします。
ところで、記事をほったらかしにしている間に、なんとうるう秒の廃止が決定してしまいました。
www.gizmodo.jp
今回NTPパケット取得から始めたモチベーションが時計へのうるう秒の実装なので、なんとも言えない気分です。
まぁ、サーバーとかを直で叩く方法とかについてだいぶ詳しくなれたので良しとしましょう。
時刻を合わせる方法について
サークル”オタク語り”アドベントカレンダー2023 12/6
こんにちは。
ブログを開設したものの、”オタク語り”のアドベントカレンダー専用になっているドメです。
アドベントカレンダーが開催決定する前から書きたい記事があったのですが、色々忙しく優先度が下がってしまい、下書きのまま放置していました。今年のアドベントカレンダーをいい機会として、書き上げたいと思います。
今回は、その公開したい内容があまりにも細かい・専門的内容すぎるので、”オタク語り”の意図に沿ってその足掛かりになるような記事を書きたいと思います。
はじめに
さて、皆さんがいつも使っている時計は何でしょうか。腕時計・置時計・スマホの時計などいろいろあると思います。その時計に、自動時刻合わせ機能はついているでしょうか。
我々の生活は常に時刻に縛られています。学校は時間割通りに動くし、電車は秒単位でダイヤが組まれています。また、ネットワーク機器はかなり高精度で時刻を保持する必要があります。身近な機器はどのようにして時刻を合わせているのでしょうか。
今回は、現代における(太陽を参考にするとかいう原始的な方法は置いておいて)機器の時刻合わせの方法を3つ紹介します。
標準電波JJYによる時刻合わせ*1
いわゆる「電波時計」と呼ばれるもので採用されている方式です。
日本には、時刻合わせ用に2つの標準電波が飛んでいます。1つ目が福島県のおおたかどや山標準電波送信所から送信されている40kHzの電波、2つ目が佐賀県/福島県のはがね山標準電波送信所から送信されている60kHzの電波です。これらはコールサインから標準電波JJYと呼ばれています。kHzというとても低い周波数(長波)を採用することでより遠くまで電波を送信できるようにしています。どちらの送信所にもセシウム原子時計が設置されており、正確な時刻情報を備えた電波を送信しています。
標準電波は、01のAMで送信されています。基本的に1秒に1データを送信し、0秒を起点とした1分60データでその分の時刻が取得できるようになっています。詳しくはNICTのページ*2をご覧ください。
受信側は、AM用のアンテナ・回路(AMラジオと同じようなもの)を用意し、得られたデータを解析すればよいです。日本全国、電波を受け取れるところであればどこでも時刻合わせができるので、時刻合わせの方法として広く採用されています。ただし基本的に回路構成が日本限定のものが多い(海外にも標準電波はあるが、周波数などが違う)ので、日本以外で使うことはできません。
GPSによる時刻合わせ
2つ目は、位置情報取得で有名なGPSを利用する方法です。
GPSは基本的に、GPSから送出される電波に記録されている時刻と自分の時刻を比較し、衛星で送信されてから受信するまでにかかった時間をもとに、衛星との距離を算出し、自分の位置を割り出します。
基本的にGPSによる位置の取得は、4つの衛星のデータをもとに行われます。位置の決定だけなら3つの衛星で十分(厳密にいえば2点に絞られる)ですが、自分の時刻がGPS衛星の時刻と同じとは限らないので、位置座標3変数と時刻変数を決定するために4つの衛星のデータから決定するといった方式になっているはずです。よって、GPSの基本的な通信を行えば、自身の時刻合わせができることになります。GPS衛星にも原子時計が搭載されています。
受信側はGPS電波を受信する設備が必要ですが、世界中どこでも使うことができます。最近は高級めの腕時計に採用されていたりしますね。
NTPによる時刻合わせ
最後に、NTPによる時刻合わせを紹介します。
NTPとは、Network Time Protocolの略で、インターネットを通した時刻合わせを行うためのプロトコルです。
NTPサーバーという正確な時刻を刻むサーバーに対し機器から時刻の照会を行います。この時、機器からサーバーに照会を送るのにかかった通信時間とサーバーから機器への返答にかかった通信時間が同じであるとし、照会から返答到着までの時間、返答中に記載されている照会の到着・送信時間をもとに、時刻合わせを行います。
インターネットに接続された機器であればなんでも時刻合わせを行うことができます。逆に、インターネット環境がないと時刻合わせを行うことができません。日本では先ほどの標準電波も管理しているNICTという機関が原子時計に接続したツヨツヨNTPサーバーを用意してくれており、なるべく負荷をかけないようにしながら個人利用することができます*3。
Windowsとか、スマホとか、最近のインターネットに接続して使用するデバイスはほぼこの方式で時刻合わせを行っているのではないでしょうか。
おわりに
時刻を合わせる方法の中で代表的なもの(?)をご紹介しました。数時間で書き上げたつたない記事ですみません。
次回、12/8公開予定の記事ではNTPを利用した時刻取得に関して、ESP32を用いて実際に接続をするという技術解説に挑戦しようと思っています。
追記:12/8公開の記事のリンク
結晶構造モデル 作ってみた
サークル”オタク語り”アドベントカレンダー 12/5
こんにちは。
今回は結晶構造モデルを作ってみたので、ブログに残したいと思います。
結晶構造とは?
結晶構造は、結晶固体内の原子の並び方の事を言います。高校の化学で習う、体心立方格子・面心立方格子・六方最密構造も結晶構造の一つです。

これらの構造は原子の種類が一種類で単純ですが、原子の種類が増えると構造も複雑になり種類も多くなります*1。

さて今回は、私の研究対象でもあるペロブスカイト構造の結晶構造モデルを作ってみたいと思います。

ペロブスカイト構造は、緑色・水色・赤色で示された三種類の原子からなる構造です。緑色をAサイト、水色をBサイトと呼び、赤色には基本酸素原子が入るので組成はABO3で表されます。
製作!
今回の製作方法について軽く説明します。このブログを書こうと思ってモデルを作ったわけではないので、製作途中の写真等はありません。すみません。
用意した材料
- 直径2cmの発泡スチロール球
20mm 発泡スチロール真球 1袋(50個) サン・ホーム 【通販モノタロウ】 - 直径3mmの白プラスチック棒
近所のホームセンターで購入 - 直径3mmの透明アクリル棒
https://www.yodobashi.com/product/100000001006261057/
工程
①まず、発泡スチロールに色を塗りました。塗料はクレオス水性ホビーカラーを使用しました。
②プラスチックの棒をええ感じの長さにニッパーで切りました。
③塗装した発泡スチロールに、転がっていたええ感じの太さのケガキ針でええ感じの穴をあけ、プラスチックの棒を差して組み上げました。
④完成!

作ってみた感想
思ったよりうまくできました。単位格子を白い棒、原子と原子の間の棒を透明にしたのは名案だったと思います。やはり構造を実際に手に取ってみるととても分かりやすいです。今回はBサイトを中心にしてモデルを製作しましたが、Aサイトを中心にしたものを同時に作ると、さらに理解が深まると思います。
ただよくよく見てみると、立方体ではない上持つと歪みます。やはり発泡スチロールの限界を感じました。反省点を下に書き出してみました。
反省点
- 球に手で穴をあけるとどうしても位置がずれてしまう
- 球内で差し込んだ棒同士が干渉してしまう
- 球と棒を固定できないため、形が崩れまくる
- 組んでいる途中に球が傷ついてしまう
結晶構造モデルを作ろうという方がいらっしゃいましたら、是非反面教師にしてください。
また、他にも結晶構造モデルを手軽に作ろうとされている方がいらっしゃいます。スーパーボールを用いたモデル作成の方法なども公開されています*2。参考にしてみてください。
ここまで読んでいただきありがとうございました。今後は空間充填モデルの製作もしてみたいと考えています。
令和4年度 技術士一次受験記
サークル”オタク語り”アドベントカレンダー 12/2
こんにちは。
今回は、11/27に受験してきた技術士一次試験の感想を書きたいと思います。
そもそも、技術士とは?
技術士は、国家資格の一つです。
「技術士」は、国によって科学技術に関する高度な知識と応用能力が認められた技術者で、科学技術の応用面に携わる技術者にとって最も権威のある国家資格です。
私もよく知らずに受験したので、詳しいことは調べてみてください。
私の頭の中では、技術士は博士の企業版のような位置づけだと思っています。いずれ博士か技術士(並べていいものか分かりませんが)どちらかは取得したいなぁと思っています。
技術士は一次試験と二次試験があり、二次試験は受験資格として実務経験が必要です。この実務経験を積むときに、一次試験を合格した状態かそうでないかで扱いが変わってくるそうなので、一次は早めに受けることにしました。
技術士(試験)には技術部門というものがあり、専門分野を選択して試験を受けることになります。技術士になった時には、”博士(~学)”のように”技術士(~部門)”と技術部門を添えて表示することが多いみたいです。
今回私は、大学での分野に近い金属部門で受験することにしました。
受験勉強
技術士一次試験で受験する科目は、基礎科目・適正科目・専門科目の3つになります。
基礎科目は全技術部門の基礎の基礎が出題される感じで、大学受験・大学1回生を真面目に頑張った人ならなんとなく解けるレベルだと思います。全部門共通です。
適正科目は一般的な資格試験の法規分野に近いです。法律以外にも、ハラスメントやリスクマネジメントについて出題されます。こちらも全部門共通です。
専門科目は、選択した技術部門の知識が出題されます。レベルは大学の学部レベルよりちょっと詳しめで、金属を扱う企業で実際に働いている人ならわかる感じです。幅広く出題されますが、25/35問選択回答なのでなんとかなる感じです。
基礎と適正については、参考書*1で勉強しました。基礎については過去問1年分で満足し、適正は4年分解きました。過去問は技術士会のホームページで公開されています。
基礎は過去問1回目で7割を超えたので、十分だと判断しました。適正の覚えものがちょっとだけ大変でした。でもそんな量は多くないので、直前勉強で何とかしました。
専門については、これといった参考書がありません。実力勝負です。といっても過去問対策が少しできるので、4年分くらい解いて対策をしました。
金属部門はやはり鉄鋼の話がメインだと感じました。私の専門は鉄ではないので、鉄鋼に関しては簡単な問題は抑えつつ、ほかの問題を得点源にしようと決めました。1問の中でも色々な材料・加工に触れたりするため、間違い直しをするのがちょっと難しかったです。
受験当日の感想
本番の試験は、専門・適正・基礎の順に行われます。専門が2時間で、適正・基礎が1時間ずつです。当日知ってびっくりしたのですが、技術士試験は途中退出する(途中で解答を提出する)ことができません。あと科目合格もないので、必ず全科目受験する必要があります。
専門は計算も少なく1時間ほどで一通り解き終わったので、とても暇でした。25/35問選択回答で26問以上マークすると失格なので、マーク数をひたすら確認していました。過去問はいい感じで解け、初見の問題もうまく対応することができたと思います。コロナ対策で扉と窓が解放されていたので、足元がとても寒かったです。
昼ご飯を食べて適正科目ですが、予想通り30分ほどで解き終わりました。全問回答なので見直し以外にやることもなく、20分くらい寝ていたと思います。
最後、基礎です。すぐ終わると思いきや、かなり焦りました。結構難しく感じ、過去問1年分しかしなかった自分を恨みました。材料系の問題が多めに出題されたのに、解けない自分が悲しくなりました。何とか耐えたと思います。
試験後、基礎で落としたら笑うなぁと思いながらTwitterを開くと、どうやら今年の基礎は難化だったようです。ちょっと安心しました。
感想を総括すると、暇・寒い・基礎難しい??でした。
翌日(自己採点)
試験日の翌日、正答が公開されました。自己採点の結果、基礎8割・適正8割・専門6割でした。マークミス等なければ合格になると思います。基礎は意外と取れていましたね。難しかったとはいえ、答えにはたどり着けていたようです。逆に専門は意外と取れていませんでした。知識不足・準備不足ですね...
おわりに
結構長くなってしまいましたが、読んでいただいてありがとうございました。
金属部門で受験しましたが、おそらく金属関係の進路には進まないと思います。二次試験はこれからの進路に関係する部門で受けようと思います。これからも頑張ります。