職場の机の上に正確な時刻を表示する視認性の良い時計が欲しくなり、Arduino(自作Duemilanove)で挑戦してみました。ここ半年くらい、Arduinoで色々な小物を作ってきましたが、実験やお遊び的なものに飽きてきしまい、実用的なもの作りたくなりました。
主な仕様はこんな感じです。
インターネット経由で定期的にNTPサーバから正確な時刻を取ってくるようにする。→以前にmbedでNTP時計を作ってみたときに時刻情報のバックアップ機能が問題となった(ネット接続ができないときに時刻情報が一切表示されない)ので、今回は秋月電子のRTC(Real Time Clock)で時刻情報を保持するとともに、蓄電素子でバックアップするようにしました。RTCの制御はこちら参考にさせて頂きました。蓄電素子は秋月の電気二重層コンデンサです。ダイオードで逆流防止するようにしました。
最近の市販の時計は皆、気温くらいは表示できるので、液晶の余白に室温を表示するようにする。→センサーは秋月のLM61BIZを使用。
時刻と気温だけではつまらないので、ネットから情報を取ってきていることがよくアピールできるよう、現在の気象情報(イギリスBBCのページにある外気温、外湿度、気圧)を表示する。→スイッチオンまたはリセット時、東京の気象情報をBBCから自動的に取り込んで表示します(但し1時間遅れくらいの情報)。東京の情報をBBCからわざわざとってきているのは単に気象情報をHTML(XML)で公式提供しているサイトが殆ど見つからず、見つかっても漢字が混じっているため、キャラクタLCDでは表示することができないからです。本当は天気予報も表示させたいのですが、Arduinoの容量不足でうまく行かず、諦めることにしました(ArduinoMEGAで挑戦する予定。)。気象情報の表示に少々てこずりました(文字列長が逐次変化するBBCの情報と容量制限の間の調整が面倒)。
制作費を抑えるため、Arduinoはこのサイトを参考に、ワッチップで自作する。→これだとマイコン以外はクロック とリセットボタン程度の必要最小限の部品構成で済むため、費用を節約できます。
書き込みは以前組み立てた秋月の基盤を利用する。
コンパクトに仕上げる為、ネット接続のインタフェースにはスイッチサイエンスから購入したWiz820ioを使用する。→Wiz820ioは安価かつ省スペースで、しかもブレッドボードにも直接挿すことができます。制御についてはスイッチサイエンスさんのHPを参考にしました。このページにはWiz820ioのライブラリが提供されているのでArduinoからの制御は簡単です。とくに問題なく、安定して動作してます。Wiz820ioのライブラリの詳細設定はこちらを参照させて頂きました。
コンパクト化を目指した割りには、ケース(100円ショップ(セリア))をケチったために結果的にサイズが大きくなってしまいました(ケースの2/3ぐらいが無駄なスペース)。でもこのケースは液晶(SC1602)の横幅にぴったりなのでよしとしました。
基盤は秋月のユニバーサル基盤の一番小さいのを使いました。
配線は後で取り外して再利用できるように秋月のピンソケットとブレッドボード用のジャンパ線を多用しました。ちゃんとパターンを作成すればもっとコンパクトになると思いますが、狭いマンション住まいではエッチングする気が起きません。それにこことかを利用したほうが結果的に安く上がるような気がするし。
総制作費は5000円弱でした。一番高いのがイーサネットモジュール(Wiz820io(2000円弱))、その他、液晶が800円、RTCが500円、ATmega328が250円、5.0V→3.3Vのレギュレータが100円です。それ以外は手持ちのものを使いました。
回路やスケッチはネットに公開されている先人の方々の素晴らしい作品を組み合わせただけです。このページに張ってあるリンク先や「部品名+arduino」をキーワードにググったHPを参照することで容易に同じ回路を再現できます。とくにこちらのページでは大いに勉強させていただきました。
今回の製作でArduino(Duemilanove)の限界がよく見えました(これが今回の本当の目的)。今後はメモリに余裕のあるMEGA等を使い、付加機能を充実させようと思います。
欲張って温度や照度の自動ツイート機能を付けようと思ったのですが、これ以上、プログラム行数を増やすと実行時にワークエリアが食いつぶされて動作がおかしくなってしまいました。
Arduinoのよいところは、IDEが無料で提供され、スケッチの記述が容易なところ、ライブラリが無料で入手できるところでしょうか。個人の電子工作にはこれで十分だと思います。MEGAクラスのArduinoが安価に出回るようになってくると、さらに普及が進むのではないでしょうか。今後の展開として、通信の無線化(Wifi,Zigbee)を挙げておきます。無線モジュールはこれが使えるか? Zigbee Wifiは混信に難があるからなあ^_^;
ソースコードを以下に示します(2013/1/24公開)。まだ綺麗に整理してませんが、少しずつ手直ししていきたいと思います。回路は単純なのでarduinoの取り扱いに慣れた方ならソースコードから容易に再現できると思います。
追加するライブラリは以下の3つです。
1.skRTClib
3.WIZ820io
※温度センサの計算を間違えていたので修正しました(2013/7/23)。
tempC = (float) (A_val * 500 /1024 -60)*10;
が正しいです。より厳密な校正は各自でお願いします。いくらでもネットに情報があいますので(^^;
/*
* UdpNtp sketch
* Get the time from an NTP time server
* This sketch uses the Time library
http://arduino.cc/playground/Code/Time
* and the Arduino Ethernet library, LiquidCrystal Library.
*/
#include <skRTClib.h>
#include <Wire.h>
#include <Time.h>
#include <SPI.h> // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUDP.h>
#include <LiquidCrystal.h>
#define WIZ820IO_RESET 8 //Reset is active low
boolean isDisconnect = false;
int index; //wheather info. 読み込み用
int counter; //
int cyclecounter; //LCDのサイズ
int stringsize; //wheather data のサイズ
char bufferRSS[256]; //
/*
LM61BIZを用いた温度測定(0℃~100℃)
シリアルモニターに摂氏で表示させる
*/
int A_inPin = 0; // アナログ入力ピン番号
float A_val; // アナログ入力値(0~203)
float tempC = 0; // 摂氏値( ℃ )
// LiquidCrystalインスタンスを生成
LiquidCrystal lcd(7, 6, 5, 4, 3, 2); // 使用するピンを指定
byte mac = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}; // set this to the MAC address
byte ip = { 192, 168, 11, 115}; // set this to a valid IP address
unsigned int localPort = 8888; // local port to listen for UDP packets
IPAddress timeServer(202, 234, 233, 109); // ntp_tk02.ocn.ad.jp NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in first 48 bytes of message
byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incomming/outgoing packets
time_t prevDisplay = 0; // when the digital clock was displayed
time_t prevDisplayNTP = 0; // Time when RTC_time were updated by NTP
time_t prevDisplayTEMP; // when the temprature was displayed
EthernetClient client;
// A UDP instance letjus send and receive packets over UDP
EthernetUDP Udp;
// RTC8564
int RTC8564_ADDRESS=0x51;
void setup()
{
//RTC8564
Serial.begin(9600);
Wire.begin();
Wire.beginTransmission(RTC8564_ADDRESS);
Wire.write(0x00);
Wire.write(0x00);
Wire.endTransmission();
//WIZ8210IO
pinMode(WIZ820IO_RESET, OUTPUT);
digitalWrite(WIZ820IO_RESET, LOW); //Put WIZ820IO into hardware reset
//Initialize WIZ820IO
delay(10);
digitalWrite(WIZ820IO_RESET, HIGH); //Bring up WIZ820IO
//Ethernet.begin(mac,ip);
lcd.begin(16,2);
lcd.setCursor(0,0);
lcd.print("Now Connecting");
lcd.setCursor(0,1);
lcd.print(" Ethernet ...");
Ethernet.begin(mac); //get IPaddress from DHCP server
if ( Ethernet.begin(mac) == 0 ) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Can't connect!");
delay(2000);
lcd.setCursor(0,1);
lcd.print("Use RTC value");
delay(1000);
}
else{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Connect Success !");
delay(1000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Captured IP is");
lcd.setCursor(1,1);
lcd.print(Ethernet.localIP());
if (client.connect("open.live.bbc.co.uk",80)) {
Serial.println("connected");
client.println("GET /weather/feeds/en/1850147/observations.rss HTTP/1.0\r\n\r\n");
delay(3000); //とても重要
//気象情報はここから
index = 0;
counter = 0;
while (client.available()) {
char c = client.read();
Serial.print(c);
if (counter > 1090) { //ここから200バイトをバッファに格納
bufferRSS[index] = c;
if (index < 200) {
index++; }
}
counter++;
}
if (!client.connected() && !isDisconnect) {
isDisconnect = true;
Serial.println();
Serial.println("disconnecting.");
index = 0 ; //バッファの内容出力
while (index < 200) {
Serial.print(bufferRSS[index]);
index++;
}
String s = bufferRSS;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Temperature:");
index = s.indexOf("Temperature:") + 13;
while (index < s.indexOf("Wind Direction:") - 13) {
lcd.print(bufferRSS[index]);
index++;
}
char temp_str[2];
sprintf(temp_str,"%cC",0b11011111);
lcd.setCursor(14,0);
lcd.print(temp_str);
lcd.print(" Tokyo Weather from BBC ");
delay(2000);
lcd.setCursor(0,1);
index = s.indexOf("Humidity") ;
while (index < s.indexOf(", Visibility") ) {
lcd.print(bufferRSS[index]);
index++;
}
delay(2000);
for (int xi = 0; xi <24; xi++) {
lcd.scrollDisplayLeft();
delay(500); //これが重要!
}
Serial.println();
client.stop();
}
// 気象情報はここまで
client.println();
} else {
Serial.println("connection failed");
}
Udp.begin(localPort);
setSyncProvider(getNtpTime);
while(timeStatus()== timeNotSet);
int ans ;
ans = RTC.begin(0,year()-2000,month(),day(),weekday()-1,hour(),minute(),second());
ans = RTC.sTime(year()-2000,month(),day(),weekday()-1,hour(),minute(),second());
//wait until the time is set by the sync provider
}
}
void loop()
{
printTime();
if (now() > prevDisplayNTP + 86400)
{
prevDisplayNTP = now();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Time Adjusting..");
Udp.begin(localPort);
setSyncProvider(getNtpTime);
while(timeStatus()== timeNotSet);
int ans ;
ans = RTC.begin(0,year()-2000,month(),day(),weekday()-1,hour(),minute(),second());
ans = RTC.sTime(year()-2000,month(),day(),weekday()-1,hour(),minute(),second());
lcd.clear();
}
if (now() > prevDisplayTEMP + 10)
{
prevDisplayTEMP = now();
char temp_str[5];
A_val = analogRead( A_inPin );
// tempC = A_val / 2.8; //for LM35DZ
// tempC = A_val / 8.462 *10 ; //for LM61BIZ
tempC = (float) (A_val * 500 /1024 -60)*10; //for LM61BIZ
// 温度は10倍の値になっているので,整数と小数とを分けて表示する。0b11011111は,「°」
sprintf(temp_str, "%3d.%1d%cC",(int)tempC /10, (int)tempC % 10, 0b11011111);
lcd.setCursor(8,1);
lcd.print(temp_str);
}
}
void printTime(){
Wire.beginTransmission(RTC8564_ADDRESS);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(RTC8564_ADDRESS,9);
Serial.print(Wire.read(),HEX);
Serial.print(":");
Serial.print(Wire.read(),HEX);
Serial.print(":");
byte r_sec=Wire.read();
byte r_minute=Wire.read() & 0x7F;
byte r_hour=Wire.read() & 0x03F;
byte r_date=Wire.read() & 0x03F;
byte r_day_of_week=Wire.read() & 0x07;
byte r_month=Wire.read() & 0x1F;
byte r_year=Wire.read();
// Serial.print(r_year,HEX);
// Serial.print("/");
// Serial.print(r_month,HEX);
// Serial.print("/");
// Serial.print(r_date,HEX);
// Serial.print(" ");
// Serial.print(r_hour,HEX);
// Serial.print(":");
// Serial.print(r_minute,HEX);
// Serial.print(":");
// Serial.print(r_sec,HEX);
// Serial.print(" smtwtfs= ");
// Serial.println(r_day_of_week,HEX);
lcd.setCursor(0,0);
lcd.print("20");
char out_str[16];
sprintf(out_str,"%02x/%02x/%02x ", r_year,r_month,r_date);
lcd.print(out_str);
switch(r_day_of_week){
case 0:
lcd.print("Sun");
break;
case 1:
lcd.print("Mon");
break;
case 2:
lcd.print("Tue");
break;
case 3:
lcd.print("Wed");
break;
case 4:
lcd.print("Thr");
break;
case 5:
lcd.print("Fri");
break;
case 6:
lcd.print("Sat");
break;
default:
lcd.print("error");
}
lcd.setCursor(0,1);
sprintf(out_str,"%02x:%02x:%02x", r_hour,r_minute,r_sec);
lcd.print(out_str);
}
/*---- NTP code ----*/
unsigned int localTimeOffset = 9 * 60 * 60 +1; // offset time of local(JST)
unsigned long getNtpTime()
{
sendNTPpacket(timeServer); // send an NTP packet to a time server
delay(2000); //この1秒の遅れを、localTimeOffsetで補正しています。
if ( Udp.parsePacket() ){
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into buffer
// the timestamp starts at byte 40, convert four byte into a long integer
unsigned long hi = word(packetBuffer[40], packetBuffer[41]);
unsigned long low = word(packetBuffer[42], packetBuffer[43]);
// this is NTP time (seconds since Jan 1 1900
unsigned long secsSince1900 = hi << 16 | low;
// Unix time starts on Jan 1 1970
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears; // subtract 70 years
return (epoch + localTimeOffset);
}
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress address)
{
memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
// Initialize values needed to form NTP request
packetBuffer[0] = 0b11100011; //LI, Version, Mode
packetBuffer[1] = 0; // Stratum or type of clock
packetBuffer[2] = 6; // Max interval between message in seconds (Polling Interval)
packetBuffer[3] = 0xEC; // Peer Clock Precision
// byte 4 - 11 are for Root Delay and Dsipersion and were set to 0 by memset
packetBuffer[12] = 49; // four-byte reference ID identifying
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// send the packet requesting a timestamp:
Udp.beginPacket(address, 123); // NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}