Arduinoで遊ぼう - NTPを使ってインターネット経由で時刻をあわせる
Posted by arms22 on 2011年07月28日

2012/1/14 サンプルスケッチをArduino 1.0用に書き直しました。
今日はインターネット経由で自動的に時刻を合わせる時計の作り方を紹介するよ。
一般的にインターネット経由で時刻を合わせる時はSNTPを使う。SNTPはNTPの簡易版で時刻を取得する目的に特化した通信プロトコルだ。クライアントはNTPサーバにUDP(ポート123番)を使って要求パケットを送る。そうするとNTPサーバから時刻情報が入った応答パケットが送られてくる。後は応答パケットから時刻情報を取り出し、キャラクタ液晶に時間を表示すればインターネット時計の完成だ。
SNTPパケット

(via ネットで時刻を合わせるプロトコル---SNTP・その3(第66回))
これはSNTPパケットの構造を表している。最後の2つのフィールドはオプションで前半部分の48バイトが正味のパケットとなる。このパケットはクライアントからの要求パケット、NTPサーバからの応答パケットの両方に使われる。要求パケットと応答パケットを同じ構造にすることでNTPサーバの処理負荷を軽減することができる。NTPサーバは送られてきた要求パケットの中身を書き換えて、そのまま送り返すだけだからね。
Transmit TimestampというフィールドにはNTPサーバが応答データを送信した時刻が格納されている。NTPサーバからクライアントまでの通信による遅れはあるけども、Transmit Timestampが現在の時刻情報となる。
時刻情報は1900年1月1日0:00(UTC)からの秒数=64ビットの符号無し固定小数点数(整数部32ビット、小数部32ビット)で表現される。64ビットのフィールドは4294967296秒=約136年でオーバーフローする。つまり今の仕様のままだと2036年の何処かで時間が1900年に巻き戻ってしまうのだ※1。
※1...RFC2030を読むと一応対策があるみたい。MSB(最上位ビット)が1の場合、時刻は1968年~2036年の間にあって、1900年1月1日0:00UTCから計算する。MSBが0の場合、時刻は2036年~2104年の間にあって、2036年2月7日6時28分16秒UTCから計算するとある。
ハードウェア
時刻の表示にキャラクタ液晶を使います。インターネットの接続にはイーサーネットシールドが必要なので、Arduinoにイーサーネットシールドを接続してからキャラクタ液晶の配線を行ってください。

キャラクタLCD vs Arduino
VSS <-- GND
VDD <-- 5V
VO <-- 半固定抵抗の中点をつなぐ(両端は5VとGND)
RS <-- 7
R/W <-- GND
E <-- 6
D4 <-- 5
D5 <-- 4
D6 <-- 3
D7 <-- 2
K (バックライトLEDのカソード) <-- GND
A (バックライトLEDのアノード) <-- 100Ωの抵抗を直列に入れて5Vをつなぐ
ライブラリ
このサンプルスケッチでは時間の管理にTimeライブラリを使用しています。以下のURLからTimeライブラリをダウンロードして、Arduinoのライブラリフォルダにコピーしてください。
※Arduino 1.0からEthernetライブラリにDHCP機能が追加された為、EthernetDHCPライブラリを入れる必要はなくなりました。
Arduino Time library
http://www.arduino.cc/playground/Code/Time
サンプルスケッチ
このサンプルスケッチはEthernetライブラリのサンプルコード「UdpNtpClient」をベースに作成しています。DHCPによるIPアドレスの取得、キャラクタ液晶への時刻表示、Timeライブラリによる時間管理に対応しています。
簡単にスケッチを解説すると、setup()でSerial/Ethernet/EthernetUDP/LiquidCrystalの初期化を行い、最初の要求パケット(sendNTPpacket)を送信します。loop()では応答パケットの受信(Udp.parsePacket)、パケットからの時刻情報を取得、Timeライブラリに時刻情報をセット(setTime)、時刻情報のキャラクタ液晶への表示を行っています。
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <LiquidCrystal.h>
#include <Time.h>
// MACアドレス
byte mac[] = {
0x00, 0x50, 0xc2, 0x97, 0x22, 0xc3 };
// UDPローカルポート番号
unsigned int localPort = 8888;
// NTPタイムサーバIPアドレス(ntp.nict.jp NTP server)
IPAddress timeServer(133, 243, 238, 164);
// NTPパケットバッファサイズ
const int NTP_PACKET_SIZE= 48;
// NTP送受信用パケットバッファ
byte packetBuffer[NTP_PACKET_SIZE];
// Udpクラス
EthernetUDP Udp;
// 最後にパケットを送信した時間(ミリ秒)
unsigned long lastSendPacketTime = 0;
// キャラクタLCDクラス(RS=>7, E=>6, D4=>5, D5=>4, D4=>3, D3=>2)
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
void setup()
{
Serial.begin(115200);
Serial.println("Attempting to obtain a DHCP lease...");
lcd.begin(20, 4);
lcd.print("Starts sync...");
if ( Ethernet.begin(mac) == 0 ) {
Serial.println("Failed to configure Ethernet using DHCP");
for(;;)
;
}
Serial.println("A DHCP lease has been obtained.");
Serial.print("My IP address is ");
Serial.println(Ethernet.localIP());
Serial.print("Gateway IP address is ");
Serial.println(Ethernet.gatewayIP());
Serial.print("DNS IP address is ");
Serial.println(Ethernet.dnsServerIP());
Serial.println();
Udp.begin(localPort);
// 最初の時刻リクエストを送信
sendNTPpacket(timeServer);
lastSendPacketTime = millis();
}
void loop()
{
if ( millis() - lastSendPacketTime > 180000 ){
// NTPサーバへ時刻リクエストを送信
sendNTPpacket(timeServer);
// 時間を更新
lastSendPacketTime = millis();
}
// NTPサーバからのパケット受信
if ( Udp.parsePacket() ) {
// バッファに受信データを読み込む
Udp.read(packetBuffer, NTP_PACKET_SIZE);
// 時刻情報はパケットの40バイト目からはじまる4バイトのデータ
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// NTPタイムスタンプは64ビットの符号無し固定小数点数(整数部32ビット、小数部32ビット)
// 1900年1月1日0時との相対的な差を秒単位で表している
// 小数部は切り捨てて、秒を求めている
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = " );
Serial.println(secsSince1900);
// NTPタイムスタンプをUNIXタイムに変換する
// UNITタイムは1970年1月1日0時からはじまる
// 1900年から1970年の70年を秒で表すと2208988800秒になる
const unsigned long seventyYears = 2208988800UL;
// NTPタイムスタンプから70年分の秒を引くとUNIXタイムが得られる
unsigned long epoch = secsSince1900 - seventyYears;
Serial.print("Unix time = ");
Serial.println(epoch);
// Timeライブラリに時間を設定(UNIXタイム)
// 日本標準時にあわせるために+9時間しておく
setTime(epoch + (9 * 60 * 60));
Serial.print("JST is ");
Serial.print(year());
Serial.print('/');
Serial.print(month());
Serial.print('/');
Serial.print(day());
Serial.print(' ');
Serial.print(hour());
Serial.print(':');
Serial.print(minute());
Serial.print(':');
Serial.println(second());
Serial.println();
// LCDクリア
lcd.clear();
// 年/月/日を表示
lcd.print(year());
lcd.print('/');
lcd.print(month());
lcd.print('/');
lcd.print(day());
// 2行目3文字目にカーソルを移動
lcd.setCursor(2, 1);
// 時:分:秒を表示
lcd.print(hour());
lcd.print(':');
lcd.print(minute());
lcd.print(':');
lcd.print(second());
}
}
// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
// NTP requests are to port 123
Udp.beginPacket(address, 123);
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
日本標準時プロジェクト - NICT公開NTP FAQ
http://www2.nict.go.jp/w/w114/tsp/PubNtp/qa.html
IPv4・IPv6・OSI用簡易ネットワーク時刻プロトコル(SNTP)Version 4
http://www.geocities.co.jp/SiliconValley/6876/rfc2030j.htm
ネットで時刻を合わせるプロトコル---SNTP・その3(第66回)
http://pc.nikkeibp.co.jp/article/knowhow/20081006/1008529/
Wikipedia - Network Time Protocol
http://ja.wikipedia.org/wiki/Network_Time_Protocol
NTPサーバ一覧
http://codenight.com/ntp/
Prototyping Lab ―「作りながら考える」ためのArduino実践レシピ (Make:PROJECTS)
posted with amazlet at 12.01.14
小林 茂
オライリージャパン
売り上げランキング: 8320
オライリージャパン
売り上げランキング: 8320
Ads by Google
7 Comments
このコメントは管理人のみ閲覧できます
こんにちは。Arduino-1.0からAPIが大きく変わったため、
サンプルスケッチはコンパイルできない状態になります。
Arduinoの古いバージョン(0023以下)でお試しいただけますか?
時間があるときにサンプルスケッチ、修正すると思います。
サンプルスケッチはコンパイルできない状態になります。
Arduinoの古いバージョン(0023以下)でお試しいただけますか?
時間があるときにサンプルスケッチ、修正すると思います。
このコメントは管理人のみ閲覧できます
もちろんOKです。
このコメントは管理人のみ閲覧できます
このコメントは管理人のみ閲覧できます
このコメントは管理人のみ閲覧できます
Leave a reply
該当の記事は見つかりませんでした。