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/
オライリージャパン
売り上げランキング: 8320
Arduino EthernetとArduino MEGA ADK
Posted by arms22 on 2011年07月18日

Arduinoチームから新しいボードのご紹介。Arduino EthernetとArduino MEGA ADKです。
Arduino Ethernet, ADK Available for purchase
http://arduino.cc/blog/2011/07/13/arduino-ethernet-adk-available-for-purchase/
Arduino Ethernetはつい先日、SparkFunから発売されたEthernet Proと同じコンセプトのボードでEthernetシールドとArduino Unoが合体したようなボードだ。Arduino EthernetはEthernet ProとおなじくUSBシリアル変換のチップがついていないのでスケッチの書込みには別途、USBシリアル変換ボードが必要だ。Arduino EthernetはPoE(Power over Ethernet)に対応している。PoEはイーサーネットケーブルを介して電源を供給する仕組みで、PoEを利用するには別途、PoEモジュールをつける必要がある。
Arduino Ethernet
http://arduino.cc/en/Main/ArduinoBoardEthernet
続いてArduinoチームオフィシャルのADKボード、Arduino MEGA ADKです。USBホストシールドとArduino MEGAが合体したようなボードで、Androidにつないでナニかしたい時に使います。折角、USBホストの機能がついているのだからAndroid以外でも使いたいところ。安価なPC用USB機器が使えると楽しそうだ。USBマウスとかキーボード、Bluetooth、カメラ、etc
Arduino MEGA ADK
http://arduino.cc/en/Main/ArduinoBoardADK
最後はAndroidでナニかしたい時に便利なセンサーキット。はんだづけ不要でLEDやボタン、リレー、タッチセンサ、スライドボリュームなどを追加できる。
ADK Sensor Kit
http://store.arduino.cc/ww/index.php?main_page=product_info&cPath=2_23&products_id=140
Ethernet Pro
Posted by arms22 on 2011年07月08日

SparkFunの新製品「Ethernet Pro」。こいつはArduino Unoとイーサーネットシールドを合体したような製品で、イーサーネットシールドのWiznet W5100チップとATmega328が搭載されている。USBシリアルのチップは搭載されていないので、スケッチの書き込みには別途、USBシリアルアダプタが必要だ。またEthernet ProにはArduino Unoと同じブートローダ「Optiboot」が書き込まれているので、ボードタイプはArduino Unoを選択して、スケッチを書き込む。
これでArduino Unoとイーサーネットシールドをスタックさせて、ものすごく高いタワーを作る必要がなくなったね!
スイッチサイエンス/商品詳細 Ethernet Pro
http://www.switch-science.com/products/detail.php?product_id=650
SparkFun - Ethernet Pro
http://www.sparkfun.com/products/10536
Arduinoで遊ぼう - キャラクタLCDにビッグなフォントを表示する
Posted by arms22 on 2011年07月06日

2016/8/3
BigFontライブラリ更新。Arduino 1.6.xに対応しました。
2011/12/18
BigFontライブラリ更新。Arduino 1.0に対応しました。
(16x2行超小型キャラクタLCDに表示している様子)
ArduinoブログでキャラクタLCDにビッグなフォントを表示する Phi_Big_Font ライブラリが紹介されていたので試してみたよ。
Big Fonts On LCD Library Turns To Second Version
http://arduino.cc/blog/2011/05/27/big-fonts-on-lcd-library-turns-to-second-version/
Phi_Big_Font ライブラリ
http://liudr.wordpress.com/libraries/phi_big_font/
Phi_Big_Font ライブラリはキャラクタLCDに、通常の6倍もの大きさの文字を表示するArduino用のソフトウェアライブラリだ。このライブラリは1つの文字を表示する為に縦に2行、横に3文字を使う(文字と文字の間に1文字の空白が入る)。16文字×2行のキャラクタLCDなら1行に4文字、20文字×4行なら2行に5文字の文字を表示することができる。表示できる文字の種類は0~9、a~zとA~Zの英数字で、記号類は表示できない。
少し離れたところからキャラクタLCDの普通サイズの文字を読むと小さく読み辛い。しかし、このライブラリを使えば文字が大きく表示され、離れた場所からでも読める。車やバイクの速度計、距離センサ、あるいはガイガーカウンタなどの表示にベストなライブラリだ。
(20x4行キャラクタLCDに表示している様子)
一般的なキャラクタLCDはCGRAM(Character Generator RAM)と呼ばれる8文字分の文字パターンを書き込めるRAM領域が用意されている。Phi_Big_Font ライブラリはこのCGRAMに独自の文字パターンを書き込み、それらを組み合わせて大きな文字を表示している。
BigFontライブラリ
Phi_Big_FontライブラリのAPIは昔ながらのCスタイルなので基本機能はそのままに、Arduinoスタイルのライブラリに仕上げてみた。
BigFontライブラリ
http://github.com/arms22/BigFont/releases/tag/v1.0.0
ライブラリのインストールは上記URLからZIPファイルをダウンロードして、Arduino IDEのメニューからスケッチ→ライブラリをインクルード→.ZIP形式のライブラリをインストールでダウンロードしたZIPファイルを選んでください。
主な関数
- attach(lcd)
BigFontライブラリにキャラクタLCDをアタッチします。 - clear()
画面をクリアし、カーソルをホームに移動します。 - home()
カーソルをホームに移動させます。 - setInvert(invert)
文字を反転させます。 - setCursor(col,row)
指定した位置にカーソルを移動させます。 - print()
ビッグなフォントで文字を表示します。
回路図

キャラクタ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をつなぐ
サンプルスケッチ
#include <LiquidCrystal.h>
#include <BigFont.h>
// キャラクタLCDクラス(RS=>7, E=>6, D4=>5, D5=>4, D4=>3, D3=>2)
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
// ビッグフォントクラス
BigFont bigf;
void setup() {
// キャラクタLCDを初期化
lcd.begin(16, 2);
// 通常フォントで表示
lcd.print("Hello, World!");
// キャラクタLCDをビッグフォントクラスにアタッチ
bigf.attach(&lcd);
// 3秒待つ
delay(3000);
}
void loop() {
// カーソルをホームポジションに移動
bigf.home();
// ビッグフォントでランダム値を表示
bigf.print(random() % 10000);
// 200ミリ秒待つ
delay(200);
}
Arduinoで遊ぼう - キャラクタLCDモジュール
http://arms22.blog91.fc2.com/blog-entry-203.html
武蔵野電波のプロトタイパーズ - 第16回「液晶ゲームを作ってみよう」
http://pc.watch.impress.co.jp/docs/column/musashino_proto/20091001_318437.html