なんでも作っちゃう、かも。

Arduino/Make/フィジカルコンピューティング/電子工作あたりで活動しています。スタバの空きカップを使ったスタバカップアンプなど製作。最近はもっぱらArduinoと3Dプリンタの自作に興味があります。

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

Posted by arms22 on 2011年07月28日 6  0

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


Ads by Google

6 Comments

says..."管理人のみ閲覧できます"
このコメントは管理人のみ閲覧できます
2012.01.12 19:01 | | # [edit]
arms22 says..."Re: Arduinoで遊ぼう - NTPを使ってインターネット経由で時刻をあわせる"
こんにちは。Arduino-1.0からAPIが大きく変わったため、
サンプルスケッチはコンパイルできない状態になります。
Arduinoの古いバージョン(0023以下)でお試しいただけますか?
時間があるときにサンプルスケッチ、修正すると思います。
2012.01.13 15:13 | URL | #- [edit]
says..."管理人のみ閲覧できます"
このコメントは管理人のみ閲覧できます
2015.04.10 15:30 | | # [edit]
arms22 says..."Re: こんにちは"
もちろんOKです。
2015.04.15 21:08 | URL | #- [edit]
says..."管理人のみ閲覧できます"
このコメントは管理人のみ閲覧できます
2015.05.07 15:52 | | # [edit]
says..."管理人のみ閲覧できます"
このコメントは管理人のみ閲覧できます
2015.06.24 15:14 | | # [edit]

Leave a reply






管理者にだけ表示を許可する

該当の記事は見つかりませんでした。