ArduinoUnoでRealTimeClockをNTPと同期させる Step.3 Aruduino IDE Ver.0021用にライブラリを修正する

ArduinoUnoでRealTimeClockをNTPと同期させる Step.2 RTC8564用ライブラリの制作で、RealTimeClockを利用して、Aruduinoに計時させようと考えているわけですが、そもそも基準となる正確な時間をどのように与えるのかという問題があります。
やはり、NTPサーバーより時刻を得るのがいいのではないかと考えて、Aruduinoの開発環境用に作成されたライブラリ「Time Library」を利用してみることにします。
ダウンロードして得られた「Time.zip」を解凍すると「DS1307RTC」「Time」「TimAlarms」のフォルダができるので、librariesフォルダにコピーします。

そして、サンプルスケッチに「TimeNTP.pde」というNTPサーバーから時刻を得るスケッチがありましたので、Aruduino IDE Ver.0021環境からAruduino Unoに当該スケッチをアップロードしてみます。
TimeNTP.pdeをIDEで開いて確認すると、冒頭部分に

#include <Time.h> 
#include <Ethernet.h>
#include <UdpBytewise.h>  // UDP library from: bjoern@cs.stanford.edu 12/30/2008 
#if  UDP_TX_PACKET_MAX_SIZE <64 ||  UDP_RX_PACKET_MAX_SIZE <64
#error : UDP packet size to small - modify UdpBytewise.h to set buffers to 64 bytes
#endif

と記述されていて、他にも「UdpBytewise.h」が必要なことがわかります。NTPサーバーはUDPを利用しますから、UDP関連のライブラリがいくつか必要なようです。このライブラリ一式はここからダウンロードできます。解凍したらできあがったファイルを「Ethernet Library」フォルダにコピーします。
ちなみに、UdpBytewise.hに記述されていてるUDP_TX_PACKET_MAX_SIZE とUDP_RX_PACKET_MAX_SIZE の値を64に変更しておけと書いてありますから、直しておきます。

では、コンパイルしてみます。はい・・・見事エラーの嵐ですね。
どうやらSPI.hをインクルードしろといっているようです。確かIDEがVer.0019になってからEthernet Liburaryを使うときにはインクルードが必要になりました。
TimeNTP.pdeに以下を書き加えます。

#include <SPI.h>

さて、もう一度コンパイルしてみます。ますますひどいエラーの洗礼を受けました。これだけ一気に表示されると意味がわかりませんね・・・。
気を取り直して落ち着いてエラーを見ながら考えてみます。まずtypes.hについて解決します。これは「UdpBytewise.cpp」を以下のように修正して解決します。

//extern "C" {
//#include "types.h"
#include "w5100.h"
#include "socket.h"
//}

次に、 ‘Sn_MR_UDP’と ‘getSn_RX_RSR’が宣言されていないというエラーを解決します。いや、確かに宣言されていません。インクルードされているファイルに宣言されているのでしょう。いやいやいやいや、宣言されていないからエラーになるんだから、「かつて宣言されていた」変数名と考えた方がいいわけですね。つまり、依存しているライブラリのコードが修正されて、命名方法が変わったと言うことでしょう。となると、AruduinoIDEの純正ライブラリ群が怪しいです。関連するライブラリをざっと見ます。 ‘Sn_MR_UDP’と ‘getSn_RX_RSR’という命名からソケット関係のライブラリじゃないかと見当をつけておきます。やはり、UDPライブラリの中で、’Sn_MR_UDP’と ‘getSn_RX_RSR’に対応する新しい命名の変数を見つけました。これが変わっていたので、IDEのVer.0019あたりからコンパイルエラーが出るようです。

すべてを修正したコードを次に記述しておきます。
UdpBytewise.cpp

/*
* UdpBytewise.cpp: Library to send/receive UDP packets with the Arduino ethernet shield.
* Drop UdpBytewise.h/.cpp into the Ethernet library directory at hardware/libraries/Ethernet/
* TODO: should protect buffer access with critical sections
*
* MIT License:
* Copyright (c) 2008 Bjoern Hartmann
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* bjoern@cs.stanford.edu 12/30/2008
*/
 
//extern "C" {
//#include "types.h"
#include "w5100.h"
#include "socket.h"
//}
 
#include "Ethernet.h"
#include "UdpBytewise.h"
 
/* Start UDP socket, listening at local port PORT */
void UdpBytewiseClass::begin(uint16_t port) {
     _port = port;
     _sock = 0; //TODO: should not be hardcoded
     _txIndex =0;
     _rxIndex =0;
     _rxSize = 0;
     _txOverflowStrategy = UDP_TX_OVERFLOW_SPLIT;
     //socket(_sock,Sn_MR_UDP,port,0);
     socket(_sock,SnMR::UDP,_port,0);
}
 
 
/* Is data available in rx buffer? Returns 0 if no, number of available bytes if yes. */
int UdpBytewiseClass::available() {
     if(_rxSize==0 || _rxSize-_rxIndex==0) {
           //if local buffer is empty or depleted
           //check wiz5100 buffer for new packet
           // _rxSize = getSn_RX_RSR(_sock); //note: return value is inflated by 8 byte header
              _rxSize = W5100.getRXReceivedSize(_sock); //note: return value is inflated by 8 byte header
           if(_rxSize){
                 //if we have a new packet there
                 //reset buffer index
                 _rxIndex=0;
                 //copy packet into our local buffer
                 _rxSize = recvfrom(_sock,_rxBuffer,_rxSize-8,_rxIp,&_rxPort);
           } else {
                 //else do nothing and rxsize is still 0
                 ;
           }
           return _rxSize; //return the new number of bytes in our buffer
     } else{
           //if buffer is not empty, return remaining # of bytes
           return (_rxSize-_rxIndex);
     }
}
 
 
/* Start a new packet with given target ip and port
* returns 1 on success, 0 if we already started a packet */
int UdpBytewiseClass::beginPacket(uint8_t *ip, unsigned int port) {
     if(_txIndex==0) {
           //ok to start new packet - copy ip and port
           _txIp[0]=ip[0];
           _txIp[1]=ip[1];
           _txIp[2]=ip[2];
           _txIp[3]=ip[3];
           _txPort = port;
           return 1;
     }
     else {
           //we already started a packet and have data in it
           return 0;
     }
}
 
 
/* Add a byte to the currently assembled packet if there is space
* if there isn't space, either truncate (ignore) or split the packet.
*/
void UdpBytewiseClass::write(uint8_t b) {
     if(_txIndex>= UDP_TX_PACKET_MAX_SIZE) {
           //buffer is full - we can either truncate the packet or split in two
           switch (_txOverflowStrategy) {
                 case UDP_TX_OVERFLOW_SPLIT:
                       endPacket();
                       beginPacket(_txIp,_txPort);
                       //fall through to normal add of byte to buffer below
                       break;
                 case UDP_TX_OVERFLOW_TRUNCATE:
                 default:
                       //don't add - just ignore bytes past buffer size
                       return;
           }
     }
     _txBuffer[_txIndex++] = b;
}
 
/* send an assembled packet out
* returns # of bytes sent on success, 0 if there's nothing to send */
int UdpBytewiseClass::endPacket() {
     // send the packet
     uint16_t result = sendto(_sock,(const uint8_t *)_txBuffer,_txIndex,_txIp,_txPort);
     // reset buffer index
     _txIndex=0;
     // return sent bytes
     return (int)result;
}
 
/* read the next byte of the last rececived packet */
int UdpBytewiseClass::read() {
     if(_rxIndex <_rxSize) {
           // if there is something to be read, return the next byte
           return _rxBuffer[_rxIndex++];
     } else {
           //we already sent the last byte - nothing to do
           return -1;
     }
}
 
void UdpBytewiseClass::getSenderIp(uint8_t*ip) {
     ip[0]=_rxIp[0];
     ip[1]=_rxIp[1];
     ip[2]=_rxIp[2];
     ip[3]=_rxIp[3];
}
 
unsigned int  UdpBytewiseClass::getSenderPort() {
     return _rxPort;
}
 
/* what should we do when we try to add to a full outgoing packet?
* UDP_TX_OVERFLOW_TRUNCATE - throw overflow bytes away
* UDP_TX_OVERFLOW_SPLIT - split into multiple packets
*/
void UdpBytewiseClass::setOverflowStrategy(uint8_t strategy) {
     _txOverflowStrategy = strategy;
}
 
/* Create one global object */
UdpBytewiseClass UdpBytewise;

コンパイルすると通りました。

さらに、今回は必要ありませんでしたが、UdpRawというライブラリもIDE Ver.0021でコンパイルすると同様のエラーが出ますので、上記と同じ変数を同じように修正します。
同様にいかに修正したファイルを掲載しておきます。

UdpRaw.cpp

/*
 *  UdpRaw.cpp: Library to send/receive UDP packets with the Arduino ethernet shield.
 *  This version only offers minimal wrapping of socket.c/socket.h
 *  Drop UdpRaw.h/.cpp into the Ethernet library directory at hardware/libraries/Ethernet/ 
 *
 * MIT License:
 * Copyright (c) 2008 Bjoern Hartmann
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * bjoern@cs.stanford.edu 12/30/2008
 */
 
//extern "C" {
//#include "types.h"
#include "w5100.h"
#include "socket.h"
//}
 
#include "Ethernet.h"
#include "UdpRaw.h"
 
/* Start UDP socket, listening at local port PORT */
void UdpRawClass::begin(uint16_t port) {
    _port = port;
    _sock = 0; //TODO: should not be hardcoded
    //socket(_sock,Sn_MR_UDP,_port,0);
    socket(_sock,SnMR::UDP,_port,0);//Sn_MR_UDP
}
 
/* Send packet contained in buf of length len to peer at specified ip, and port */
/* Use this function to transmit binary data that might contain 0x00 bytes*/
/* This function returns sent data size for success else -1. */
uint16_t UdpRawClass::sendPacket(uint8_t * buf, uint16_t len,  uint8_t * ip, uint16_t port){
    return sendto(_sock,(const uint8_t *)buf,len,ip,port);
}
 
/* Send  zero-terminated string str as packet to peer at specified ip, and port */
/* This function returns sent data size for success else -1. */
uint16_t UdpRawClass::sendPacket(const char str[], uint8_t * ip, uint16_t port){    
    // compute strlen
    const char *s;
    for(s = str; *s; ++s);
    uint16_t len = (s-str);
    // send packet
    return sendto(_sock,(const uint8_t *)str,len,ip,port);
}
/* Is data available in rx buffer? Returns 0 if no, number of available bytes if yes. 
 * returned value includes 8 byte UDP header!*/
int UdpRawClass::available() {
    //return getSn_RX_RSR(_sock);//W5100.getRXReceivedSize(_sock);
    return W5100.getRXReceivedSize(_sock);//getSn_RX_RSR(_sock);
}
 
 
/* Read a received packet into buffer buf (which is of maximum length len); */
/* store calling ip and port as well. Call available() to make sure data is ready first. */
/* NOTE: I don't believe len is ever checked in implementation of recvfrom(),*/
/*       so it's easy to overflow buffer. so we check and truncate. */
/* returns number of bytes read, or negative number of bytes we would have needed if we truncated */
int UdpRawClass::readPacket(uint8_t * buf, uint16_t bufLen, uint8_t *ip, uint16_t *port) {
    int packetLen = available()-8; //skip UDP header;
    if(packetLen <0 ) return 0; // no real data here    
    if(packetLen > (int)bufLen) {
        //packet is too large - truncate
        //HACK - hand-parse the UDP packet using TCP recv method
        uint8_t tmpBuf[8];
        int i;
        //read 8 header bytes and get IP and port from it
        recv(_sock,tmpBuf,8);
        ip[0] = tmpBuf[0];
        ip[1] = tmpBuf[1];
        ip[2] = tmpBuf[2];
        ip[3] = tmpBuf[3];
        *port = tmpBuf[4];
        *port = (*port <<8) + tmpBuf[5];
        
        //now copy first (bufLen) bytes into buf        
        for(i=0;i<(int)bufLen;i++) {
            recv(_sock,tmpBuf,1);
            buf[i]=tmpBuf[0];
        }
        
        //and just read the rest byte by byte and throw it away
        while(available()) {
            recv(_sock,tmpBuf,1);
        }
        
        return (-1*packetLen);
        
        //ALTERNATIVE: requires stdlib - takes a bunch of space
        /*//create new buffer and read everything into it
        uint8_t * tmpBuf = (uint8_t *)malloc(packetLen);
        recvfrom(_sock,tmpBuf,packetLen,ip,port);
        if(!tmpBuf) return 0; //couldn't allocate
        // copy first bufLen bytes
        for(unsigned int i=0; i<bufLen; i++) {
            buf[i]=tmpBuf[i];
        }
        //free temp buffer
        free(tmpBuf);
        */
        
        
    } 
    return recvfrom(_sock,buf,bufLen,ip,port);
}
 
/* Read a received packet, throw away peer's ip and port.  See note above. */
int UdpRawClass::readPacket(uint8_t * buf, uint16_t len) {
    uint8_t ip[4];
    uint16_t port[1];
    return recvfrom(_sock,buf,len,ip,port);
}
 
 
 
 
/* Create one global object */
UdpRawClass UdpRaw;

これでイーサネットを使うライブラリのかなりものもがAruduino IDE Ver.0021でコンパイルが通るようになるはずです。
需要があれば修正ライブラリという形で、ダウンロードできるようにします。

それにしても、Aruduinoは便利ですが、IDEのバージョンアップに伴って依存しているライブラリによってコンパイルができなかったりするという現象は、かつてのWindows環境でのDLL地獄を連想させます。このあたりがもう少し洗練されるといいんですが・・・。

2 Responses - “ArduinoUnoでRealTimeClockをNTPと同期させる Step.3 Aruduino IDE Ver.0021用にライブラリを修正する”

  1. akeboshi : 2011/07/15 - 17:24:53 -

    NTPサーバにアクセスして時間同期をしようと思ってるものです.
    これらの記事とても参考になりました!
    ありがとうございます!
    しかし現状,この記事の方法でやってるんですがコンパイルエラーが出てしまう状況です><
    そこで修正ライブラリをダウンロードできるようにしていただきたいと思っているのですが可能でしょうか?

  2. chameleon : 2011/07/21 - 23:00:11 -

    かなりご返事が遅くなってしまって申し訳ありません。
    たぶんもう見ていないと思いますが・・・一応、Ver.0021対応版のライブラリを置いておきます。

    http://www.chameleonic.org/download/0021_Ethernet.zip

Leave a Reply

XHTML: You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>