Arduinoで遊ぼう - NTPを使ってインターネット経由で時刻をあわせる

2012/1/14 サンプルスケッチをArduino 1.0用に書き直しました。

NTP同期型キャラクタLCD時計

今日はインターネット経由で自動的に時刻を合わせる時計の作り方を紹介するよ。

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


SNTPパケット


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サンプル回路図
キャラクタ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)
小林 茂
オライリージャパン
売り上げランキング: 8320


コメント

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

Re: Arduinoで遊ぼう - NTPを使ってインターネット経由で時刻をあわせる

こんにちは。Arduino-1.0からAPIが大きく変わったため、
サンプルスケッチはコンパイルできない状態になります。
Arduinoの古いバージョン(0023以下)でお試しいただけますか?
時間があるときにサンプルスケッチ、修正すると思います。
Secret

Ads by Google
最近の記事
カテゴリ
Arduino (99)
電子工作 (105)
スタバッテルミン (5)
スタバカップアンプ (17)
電光掲示板 (7)
イベント (49)
太陽電池 (12)
ニキシー管 (19)
ARM (8)
PIC (20)
USBデバイス (7)
V850 (17)
Xfind (6)
プログラミング (4)
Android (3)
未分類 (21)
カメラ (6)
本 (18)
mbed (1)
Amazon.co.jp
最近のトラックバック
以前の記事
リンク
プライバシーポリシー
当サイトでは、第三者配信による広告サービスを利用しています。このような広告配信事業者は、ユーザーの興味に応じた商品やサービスの広告を表示するため、当サイトや他サイトへのアクセスに関する情報 (氏名、住所、メール アドレス、電話番号は含まれません) を使用することがあります。このプロセスの詳細やこのような情報が広告配信事業者に使用されないようにする方法については、ここをクリックしてください。