Arduinoで電波時計補正 2012/09/01
電波時計を買ったものの、部屋の中で受信できず、たまに窓際やベランダで陰干し、ないし天日干しが必要なことってないですか? これってハイテクなのか、ローテクなのか、デジタルなのかアナログなのか、しょっぱい気分になっちゃいますよね。
それに加えて、日本に2つしかない送信所のうち、東日本担当の福島の送信所が3.11のとき、40日程度止まってしまうということもあり、電波時計を補正する電波を自前で作る方法が紹介されていました。
-標準電波の代わりにPCで電波時計の時刻を合わせる「電波時計用JJYシミュレータ」
そのうち作ってみたいなとおもってたのですが、Arduino の IDE がバージョンアップして、2011年12月には1.0, 2012年5月には1.0.1となり、以前のスケッチ(プログラム)がそのままで動かない模様だったので、しばらく手を出さずにいたのです。
ふと、思い立って手を付けてみると、意外と簡単に出来上がってしまいました。
まず、ArduinoのTimeライブラリを入手。これが、不完全ながらもIDE 1.0に対応している。この中からTimeSerialとTimeRTCSetを読む。それと、標準添付になってるEthernetのサンプルスケッチUdpNtpClientを読む。あとは、肝心の電波発信方法が書かれたArduinoで電波時計を合わせようを加えればできあがり。
RTCは、SparkFunのリアルタイムクロック・モジュールを使用していますが、なければ、Timeライブラリが自分で多少精度が落ちますが時間計算してくれます。
ちなみに、時刻表示のために、ストロベリーリナックスの7セグメントLED表示キットを使用してPin5と6をソフトウエアーシリアルで使用しています。
配線は、Arduino UNO に Ethernetシールドを乗せ、その上にバニラシールドを重ねて以下の通り。左上から5メートル程度の線を巻いたアンテナへ。その下のコネクタは、7セグメントLED表示キットに接続します。
スケッチは、元のコードを合体させただけなので、整理してません。概要としては、最初にインターネットに接続してNTPから時間を取得してセット。その後インターネットは使用していないので、アンテナ横のLEDがちかちかして電波を発信しだしたら、ネットケーブルは外してOK(つけたままでもいいが)。RTCを使わない場合は、#define USE_RTC DS1307 をコメントアウトしてください。
#highlight(){ /* Udp NTP Client Get the time from a Network Time Protocol (NTP) time server Demonstrates use of UDP sendPacket and ReceivePacket For more on NTP time servers and the messages needed to communicate with them, see http://en.wikipedia.org/wiki/Network_Time_Protocol created 4 Sep 2010 by Michael Margolis modified 9 Apr 2012 by Tom Igoe This code is in the public domain. */ #include <SPI.h> #include <Ethernet.h> #include <EthernetUdp.h> #include <SoftwareSerial.h> #include <Time.h> #include <Wire.h> #include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t #define TIME_REQUEST 7 // ASCII bell character requests a time sync message #define USE_RTC DS1307 // no RTC_DS1307, comment out this // bit set / clear #ifndef cbi #define cbi(PORT, BIT) (_SFR_BYTE(PORT) &= ~_BV(BIT)) #endif #ifndef sbi #define sbi(PORT, BIT) (_SFR_BYTE(PORT) |= _BV(BIT)) #endif // Enter a MAC address for your controller below. // Newer Ethernet shields have a MAC address printed on a sticker on the shield byte mac[] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX }; // XXは自分のEthernetシールドに合わせて unsigned int localPort = 8888; // local port to listen for UDP packets //IPAddress timeServer(192, 43, 244, 18); // time.nist.gov NTP server IPAddress timeServer(133, 243, 238, 164); // ntp.nict.jp NTP server const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets // A UDP instance to let us send and receive packets over UDP EthernetUDP Udp; SoftwareSerial mySerial(5,6); // RX, TX int rwPin = 3; int localoffset = 2; // ntp の返答をセットするときのずれ byte timecode[60]; unsigned long lastNTPTime = 0; void setup() { // Open serial communications and wait for port to open: Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } mySerial.begin(9600); #ifdef USE_RTC setSyncProvider(RTC.get); // the function to get the time from the RTC if(timeStatus()!= timeSet) Serial.println("Unable to sync with the RTC"); else Serial.println("RTC has set the system time"); #else // for internal clock setSyncProvider( requestSync); //set function to call when sync required Serial.println("RTC is not used."); #endif // start Ethernet and UDP if (Ethernet.begin(mac) != 0) { Udp.begin(localPort); ntpRequest(); Udp.stop(); } else { Serial.println("Failed to configure Ethernet using DHCP"); } setupTimeCode(); } void loop() { digitalClockDisplay(); int wait_start = second(); while (wait_start == second()); // wait until time is corrected unsigned long startTime = millis(); // generate 40khz from 3 pin using PWM pinMode(rwPin, OUTPUT); digitalWrite(rwPin, LOW); TCCR2A = _BV(WGM20); TCCR2B = _BV(WGM22) | _BV(CS20); OCR2A = F_CPU / 2 / 40000/*hz*/; OCR2B = OCR2A / 2; /* 50% duty */ sbi(TCCR2A,COM2B1); // calc signal duration (ms) int ms = calcTimeCodeDuration(); // wait ms and stop PWM while (millis() - startTime < ms); cbi(TCCR2A,COM2B1); } time_t requestSync() { Serial.write(TIME_REQUEST); return 0; // the time will be sent later in response to serial mesg } void ntpRequest() { sendNTPpacket(timeServer); // send an NTP packet to a time server // wait to see if a reply is available delay(1000); if ( Udp.parsePacket() ) { // We've received a packet, read the data from it Udp.read(packetBuffer,NTP_PACKET_SIZE); // read the packet into the buffer //the timestamp starts at byte 40 of the received packet and is four bytes, // or two words, long. First, esxtract the two words: unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // combine the four bytes (two words) into a long integer // this is NTP time (seconds since Jan 1 1900): unsigned long secsSince1900 = highWord << 16 | lowWord; //Serial.print("Seconds since Jan 1 1900 = " ); //Serial.println(secsSince1900); // now convert NTP time into everyday time: //Serial.print("Unix time = "); // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: const unsigned long seventyYears = 2208988800UL; // subtract seventy years: unsigned long epoch = secsSince1900 - seventyYears; // print Unix time: //Serial.println(epoch); // print the hour, minute and second: //Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT) epoch = epoch + (9 * 60 * 60) + localoffset; RTC.set(epoch); setTime(epoch); Serial.print("JST is "); Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day) Serial.print(':'); if ( ((epoch % 3600) / 60) < 10 ) { // In the first 10 minutes of each hour, we'll want a leading '0' Serial.print('0'); } Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute) Serial.print(':'); if ( (epoch % 60) < 10 ) { // In the first 10 seconds of each minute, we'll want a leading '0' Serial.print('0'); } Serial.println(epoch %60); // print the second digitalClockDisplay(); } // wait ten seconds before asking for the time again } void digitalClockDisplay(){ // digital clock display of the time /* Serial.print(hour()); printDigits(minute()); printDigits(second()); Serial.print(" "); Serial.print(day()); Serial.print(" "); Serial.print(month()); Serial.print(" "); Serial.print(year()); Serial.println(); */ mySerial.write(0x08); mySerial.print(String(hour() / 10) + String(hour() % 10) + " "); mySerial.print(String(minute() / 10) + String(minute() % 10) + " "); mySerial.println(String(second() / 10) + String(second() % 10)); } void printDigits(int digits){ // utility function for digital clock display: prints preceding colon and leading 0 Serial.print(":"); if(digits < 10) Serial.print('0'); Serial.print(digits); } // 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: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer,NTP_PACKET_SIZE); Udp.endPacket(); } //=============== http://d.hatena.ne.jp/NeoCat/20110328/1301256560 unsigned int calcTimeCodeDuration() { int s = second(); if (s == 0) setupTimeCode(); return timecode[s] * 100; } void setupTimeCode() { int i; memset(timecode, 8, sizeof(timecode)); setupTimeCode100(minute(), 0); timecode[0] = 2; setupTimeCode100(hour(), 10); int d = dayOfYear(); setupTimeCode100(d/10, 20); setupTimeCode100(d%10*10, 30); int parity1 = 0, parity2 = 0; for (i = 12; i < 20; i++) parity1 ^= timecode[i] == 5; for (i = 1; i < 10; i++) parity2 ^= timecode[i] == 5; timecode[36] = parity1 ? 5 : 8; timecode[37] = parity2 ? 5 : 8; setupTimeCode100(year()%100, 40); for (i = 44; i > 40; i--) timecode[i] = timecode[i-1]; timecode[40] = 8; int w = weekday() - 1; timecode[50] = (w & 4) ? 5 : 8; timecode[51] = (w & 2) ? 5 : 8; timecode[52] = (w & 1) ? 5 : 8; timecode[59] = 2; /* dump */ for (i = 0; i < 60; i++) { Serial.print(timecode[i], DEC); Serial.print(i % 10 == 9 ? "\r\n" : " "); } } void setupTimeCode100(int m, int i) { timecode[i+0] = ((m/10) & 8) ? 5 : 8; timecode[i+1] = ((m/10) & 4) ? 5 : 8; timecode[i+2] = ((m/10) & 2) ? 5 : 8; timecode[i+3] = ((m/10) & 1) ? 5 : 8; timecode[i+4] = 8; timecode[i+5] = ((m%10) & 8) ? 5 : 8; timecode[i+6] = ((m%10) & 4) ? 5 : 8; timecode[i+7] = ((m%10) & 2) ? 5 : 8; timecode[i+8] = ((m%10) & 1) ? 5 : 8; timecode[i+9] = 2; } int dayOfYear() { tmElements_t tm = {0, 0, 0, 0, 1, 1, CalendarYrToTm(year())}; time_t t = makeTime(tm); return (now() - t) / SECS_PER_DAY + 1; } }