エントリー

GPS時計 - 完結編

  • 2009/12/11 14:31
  • カテゴリー:make:

 GPS時計ですが,その後も毎日のように検討を続けていました。

 GPSを使って時刻情報を得ていながら,1秒近くずれるというのはもはや言語道断といえて,何とかなるならしてみたいし,駄目な場合でも駄目な理由を理解してからあきらめたいと思ったからです。

 さて,前回,UARTのボーレートを38400ボーにするということと,使っていないセンテンスは送出させないようにしたこと,それと1秒に1回だったデータの送信周期を1秒に2回にしたことで対応を図ったが,あまり良い結果が得られなかったことを書きました。最大で1秒弱の遅れは解消されず,遅れる時間も長かったり短かったりとバラツキがみられます。

 そして,根本的にGPS本来の精度の時計を作るなら,GPSモジュールから1PPSの信号を得なければどうにもならない,ということもはっきりしました。ここまでが前回までのお話です。

 一般論としてですが,どうもGPSモジュールから出てくる時刻情報は,実際の時刻よりも少しだけ進んでいるのが普通のようです。少しだけ進んだ時刻をマイコンが取り込み,その後やってくる1PPSの信号で表示を更新することで,GPS本来の精度の時計が実現出来るというわけです。

 今回使っているGT-720Fというモジュールはこの1PPSの信号が出ていませんので,そもそもGPS時計を作るのに適していないモジュールを選んでいることになります。

 それに,これまでいじった結果として感じるのですが,少し進んだ時刻が出ているかどうかもあやしいです。

 ということで,1PPSがないと正確な時計を作る事が出来ないなら,GT-720Fから1PPSの信号を引っ張り出せないか,と考えました。

 幸いないことに,GT-720Fで使われているベースバンドチップは,Venus621LPというもので,1PPS信号の出力を持つLSIです。きっと,モジュールのどこかに1PPS信号が出ているに違いないと考えました。

 そこで背面のシールドケースをあけ,1PPS信号を探したのですが,結論からいうと見つかりませんでした。Venus621はBGAパッケージのLSIですから,端子から直接信号を取り出せません。基板上のどこかに出してくれていることを期待したのですが,スルーホールや抵抗などの部品をすべてあたっても,1PPSの出ている箇所を見つけることができませんでした。

 ならば,と衛星をつかまえたことを示すLEDはどうだろう,と考えました。GT-720Fは,衛星をつかまえていない時はLEDが点灯,つかまえると一定間隔で点滅します。

 このLEDの点滅が1PPSに同期しているなら,1PPS信号として使えるでしょう。いい所に気が付いたと思ったのですが,残念ながら全く同期しておらず,無関係でした。この段階で,GT-720Fから1PPS信号を得る事はあきらめ,外したシールドケースを元に戻しました。寂しいですね。


 ここに至って,1秒近く遅れる,という人間の感覚でもばれてしまうズレを,せめて意識しないで済む程度にする,という消極的な方法しかなくなってしまいました。

 そのためには,前回も書きましたが,何らかの理由でずれた時刻が送られて来ても,更新周期を早めて表示を頻繁に行うのが有利です。周期が短いほど,ずれた時刻の表示が正しい時刻になるまでの時間が短くなります。

 そこで,GT-720Fの設定ツールを使って,更新周期を設定可能な最大である1秒間に10回に設定しました。

 また,ボーレートを19200ボーに下げました。これは,38400ボーに上げたところで数msしか高速化されませんし,96バイトとバッファサイズを大きくしてまでこのボーレートで受け取るのは無駄で,64バイトのバッファで受信可能な19200ボーが最適という判断です。

 前回書きましたが,バッファを64バイトにすれば,リングバッファの実現が条件分岐ではなく,ANDによるマスクという処理だけで可能となります。これは処理速度にもコードサイズにも有利です。

 さらに,LCDへのデータ転送タイミングには十分すぎる時間を確保してあったのですが,これを大幅に切り詰めました。LCDへの転送クロック周期はスペックでは500nsから1000ns程度なのに,2msと2000倍も長くとってありました。動作は確実でしょうが,処理時間の大半はLCDへの転送にかかっていたことになります。

 試したところ,確かに見た目のズレはかなり軽減されています。しかし,そのズレ方は一定ではなく,1/10秒くらい遅れているときもあれば,ほとんどずれていない時もあります。

 この違いはなんだろう・・・はっきりしないと,時計として全く信用できません。

 この疑問は,GPSモジュールの吐き出すセンテンスを直接PCで見た時に氷解しました。

 実は,GPGGAにしてもGPRMCにしても,時刻情報として

133227.999

 というデータが,13時32分28秒の段階で送り出されていることがあるようなのです。

 27.999秒というのは,もうほとんど28秒なのですが,1/10秒以下を無視してしまうと,27秒と表示されてしまいます。

 もし,1秒間に1度の更新周期なら,次のデータは28.999秒となり,実際の時刻である29秒との表示のズレは,またしても1秒出てきてしまいます。こうして,GPSモジュールが何かのきっかけでxx.999ではなく,xx.000を出してくれるようになるまで,この1秒の遅れは続きます。

 もし1秒間に2回の更新を行う場合,送られて来るデータは

133227.999 133228.500 132228.999 133229.500

 という順番で出てくる事になり,ズレは0.5秒まで軽減されます。とはいえ,0.5秒のズレはばれてしまいますね。

 さらに,もしなんらかの理由で,

133228.000 133229.000

 という形で送られて来るようになると,.xxxを無視して表示すれば,ほとんどずれることなく表示が出来る事になります。

 さらに調べていくと,.xxxの部分は一定ではなくばらつくこと,バラツキの大きさによって遅れる時間が変わることも分かりました。

 これが,表示時刻が不規則に遅れる理由でした。

 少し実験をします。PCで.999というデータが出ていることを確認した上で,実機の表示を1/10秒まで表示させてみました。すると秒の桁が,

11.1 -> 11.2 -> 11.3 ... -> 11.8 -> 11.9 -> 11.9 -> 12.1 -> 12.2 ...

 という具合に変化します。1/10秒にゼロが表示されないことと,1秒の桁は11.9では変わらず12.1で変わるので,実際の時刻よりも1/10秒遅れているのがよくわかります。

 また,PCで.000で揃ったデータが出ている状態で同様に確認すると,ほとんど同時に表示も変化するようになります。これではっきりしました。

 そこで,暫定的に.999になったら秒のデータに1を加えるという簡易的な方法を試してみたところ,表示は概ねずれることなく更新されるようになったのですが,

18.8 -> 18.9 -> 19.0 -> 19.1 ... 19.8 -> 19.9 -> 1:.0 -> 20.1 ...

 という変則的な表示となり,見た目が非常に悪くなりました。キャラクタコードに1を加えただけの手抜きはダメで,やっぱり真面目にやるしかありません。

 そこで,さらに大幅な変更をします。日付はともかくとして,時刻だけはちゃんと処理する事にしましょう。日付は24時間に一度変化しますが,GPSからのデータの取得はは1秒おきですので,1秒だけ32日とか出てしまうことが考えられますが,まあいいでしょう。メモリも足りませんし。

 このため,これまで直接リングバッファの値を表示用の関数に渡していたものを,一度グローバル変数に取り込んで,文字列-から数値への変換,補正や表示を行う事にしました。

 あと,潜在的なバグとして日付の処理に問題があり,UTCで9日をJSTに変換するところでくだらないミスをしていたため,10日とならず,0:日と表示されていました。配列の宣言のミスで,暴走一歩手前だったことも白状します。お恥ずかしい。

 そうこうして出来た最終的なソースが,以下です。



つづきはこちら


----

//
// GPS Clock Original part3 Dec.10,2009
//    clock:20MHz(Exnternal)

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

volatile char Rx_Buf[64];    // ring buffer
volatile unsigned char Rx_write_pointer;    // write pointer
unsigned char Rx_read_pointer;    // read pointer

unsigned char year = 0; // present year
unsigned char mon = 0; // present mon
unsigned char day = 0; // present day
unsigned char hour; // present hour
unsigned char min; // present min
unsigned char sec; // present sec

void LCD_Init(void);
void LCD_E_clk(void);
void LCD_busy_check(void);
void LCD_Control(unsigned char);
void LCD_WriteData(unsigned char);
void LCD_Position(unsigned char, unsigned char);

void Usart_Init(unsigned int);
int Rx_check(void);
void Rx_wait(void);
int Get_RxData(void);

void decode_gga(void);
void decode_rmc(void);

int main(void)
{
    char sentens[3];

    DDRB = 0x0f;
    DDRD = 0x78;
    PORTD |= _BV(PD6);

    LCD_Init();

    LCD_WriteData('V');
    LCD_WriteData('1');
    LCD_WriteData('.');
    LCD_WriteData('3');
    LCD_WriteData('3');

    _delay_ms(2000);

    LCD_Control(0x01);
    LCD_Position(13, 0);
    LCD_WriteData('S');
    LCD_WriteData('A');
    LCD_WriteData('T');

    LCD_Position(0, 0);
    LCD_WriteData('2');
    LCD_WriteData('0');

    Usart_Init(19200);

    sei();    //

    while(1){

        while (Get_RxData() != '$');
    
        Get_RxData();    // G
        Get_RxData();    // P

        sentens[0] = Get_RxData();
        sentens[1] = Get_RxData();
        sentens[2] = Get_RxData();

        Get_RxData();    // ,

        if (sentens[0] == 'G'){
            if (sentens[1] == 'G'){
                if (sentens[2] == 'A'){
                    decode_gga();
                }
            }
        }
        
        if (sentens[0] == 'R'){
            if (sentens[1] == 'M'){
                if (sentens[2] == 'C'){
                    decode_rmc();
                }
            }
        }
    }
}


// GPGGA sentens decode & print
void decode_gga(void)
{
    unsigned char i;
    unsigned char sss[2];

    hour = ((Get_RxData() - '0') * 10);
    hour += (Get_RxData() - '0');
    hour += 9;
                    
    if (hour >= 24){
        hour -= 24;
    }                    

    min = ((Get_RxData() - '0') * 10);
    min += (Get_RxData() - '0');


    sec = ((Get_RxData() - '0') * 10);
    sec += (Get_RxData() - '0');

    Get_RxData();        // .

    sss[0] = Get_RxData();    // 1/10sec
    sss[1] = Get_RxData();    // 1/100sec

    if (sss[0] == '9' && sss[1] >= '5'){
        sss[0] = '0';
        sss[1] = '0';
        PORTD &= ~(_BV(PD6));

        if (++sec == 60){
            sec = 0;
    
            if (++min == 60){
                min = 0;
    
                if (++hour == 24){
                    hour = 0;
                    day++;
                }
            }
        }
    }else{
        PORTD |= _BV(PD6);
    }

    for(i = 0;i < 6;i++){
        while(Get_RxData() != ',');
    }

    LCD_Position(14, 1);

    LCD_WriteData(Get_RxData());
    LCD_WriteData(Get_RxData());    // number of catched satellites

    LCD_Position(0, 1);
    
    LCD_WriteData(hour / 10 + '0');
    LCD_WriteData(hour % 10 + '0');    // hour

    LCD_WriteData(':');

    LCD_WriteData(min / 10 + '0');
    LCD_WriteData(min % 10 + '0');    // min

    LCD_WriteData(':');

    LCD_WriteData(sec / 10 + '0');
    LCD_WriteData(sec % 10 + '0');    // sec

    LCD_WriteData('.');

    LCD_WriteData(sss[0]);        // 1/10sec
    LCD_WriteData(sss[1]);        // 1/100sec


    LCD_Position(2, 0);

    LCD_WriteData(year / 10 + '0');
    LCD_WriteData(year % 10 + '0');    // year

    LCD_WriteData('/');

    LCD_WriteData(mon / 10 + '0');
    LCD_WriteData(mon % 10 + '0');    // month

    LCD_WriteData('/');

    LCD_WriteData(day / 10 + '0');
    LCD_WriteData(day % 10 + '0');    // day

}


//    GPRMC sentens decode &
void decode_rmc(void)
{
    unsigned char i;

    for (i = 0 ; i < 8 ; i++){
        while(Get_RxData() != ',');
    }

    day = ((Get_RxData() - '0') * 10);
    day += (Get_RxData() - '0');
    day += (hour < 9);

    mon = ((Get_RxData() - '0') * 10);
    mon += (Get_RxData() - '0');

    year = ((Get_RxData() - '0') * 10);
    year += (Get_RxData() - '0');

}

//
// USART library
//

// USART init
void Usart_Init(unsigned int baud)
{
    unsigned int ubrr = (((F_CPU >> 4) + (baud >> 1)) / baud - 1);

    UBRRH = (unsigned char)(ubrr >> 8); // Baudrate High8bit
    UBRRL = (unsigned char)ubrr; // Baudrate Low8bit
    UCSRB = _BV(RXCIE) | _BV(RXEN);    // Receive Interupt Enable
}

// Check for receiving
int Rx_check(void)
{
    return (Rx_write_pointer != Rx_read_pointer) ? 1 : 0;
}

// Wait for receiving
void Rx_wait(void)
{
    while(!Rx_check());
}

// Get received data
int Get_RxData(void)
{
    Rx_wait();

    return Rx_Buf[Rx_read_pointer++ & 0x3f];
}

// Interupt
ISR(USART_RX_vect)
{
    Rx_Buf[Rx_write_pointer++ & 0x3f] = UDR;
}


//
// LCD Library Nov.30,2009
//

// Pin assign
#define LCD_D7 _BV(PB3)
#define LCD_D6 _BV(PB2)
#define LCD_D5 _BV(PB1)
#define LCD_D4 _BV(PB0)
#define LCD_RS _BV(PD5)
#define LCD_RW _BV(PD4)
#define LCD_E _BV(PD3)

void LCD_Init(void)
{
    _delay_ms(30);

    PORTB = 0x03;
    LCD_E_clk();
    _delay_ms(10);

    PORTB = 0x03;
    LCD_E_clk();
    _delay_us(200);

    PORTB = 0x03;
    LCD_E_clk();

    PORTB = 0x02;
    LCD_E_clk();

    LCD_Control(0x2c);    // Function Set(4bit,Duty=1)
    LCD_Control(0x08);    // Display OFF.Cursor OFF.Blink OFF
    LCD_Control(0x06);    // Entry Mode(Right Shift)
    LCD_Control(0x0c);    // Display ON,Cursor OFF,Blink OFF
    LCD_Control(0x01);    // Display Clear
}

void LCD_E_clk(void)
{
    PORTD |= LCD_E;
    _delay_us(1);
    PORTD &= ~LCD_E;
    _delay_us(1);
}

void LCD_busy_check(void)
{
    unsigned char i;

    DDRB = 0;
    PORTD |= LCD_RW;
    while(1){
        PORTD |= LCD_E;
        _delay_us(1);
        i = PINB << 4;
        PORTD &= ~LCD_E;
        _delay_us(1);
        PORTD |= LCD_E;
        _delay_us(1);
        i |= PINB & 0x0f;
        PORTD &= ~LCD_E;
        _delay_us(1);
        if((i & 0x80) == 0)    break;
    }
    PORTD &= ~LCD_RW;
    DDRB = 0x0f;
}

void LCD_Control(unsigned char data)
{
    LCD_busy_check();
    PORTB = data >> 4;
    LCD_E_clk();
    PORTB = data & 0x0f;
    LCD_E_clk();
}

void LCD_WriteData(unsigned char data)
{
    LCD_busy_check();
    PORTD |= LCD_RS;
    PORTB = data >> 4;
    LCD_E_clk();
    PORTB = data & 0x0f;
    LCD_E_clk();
    PORTD &= ~LCD_RS;
}

void LCD_Position(unsigned char x, unsigned char y)
{
    LCD_Control(0x80 + (y * 0x40) + x);
}

----


 当初,.xxxの判定と桁上げの処理が結構大きくなり,メモリをオーバーしていました。

 バージョン表示を削ったり,レジスタの初期化で不必要なものを省いたりといろいろやってみましたが全然追いつきません。

 そこでやむを得ず,statusの表示をやめることにしました。しばらく使っていてはっきりしたのですが,GPRMCのstatusというのは,受信した衛星の数が0になるとVになるだけのものです。だから表示しても意味のないことが明確になったので,これをやめることにします。

 その.xxxの判定ですが,.999だけを判定するのでは足りないですから,1/100秒を四捨五入することにし,0.50以上なら1秒の桁を1つ進める事にしました。加えて四捨五入が行われた時にLEDを点灯させることにしました。

 表示は,1/10秒と1/100秒を表示することにしました。更新周期が1/10秒ですから1/100秒など意味もないのですが,四捨五入が行われるかどうかが表示で分かるので気分的にすっきりします。

 これで数時間様子を見たのですが,1/100秒の桁にどんな数字が来ても,校正したばかりの電波時計の1秒カウントに,ほぼ一致したタイミングで1秒の桁が変化するようになりました。しかも遅れ分の変化量もばれない程度に抑えられていて,これなら置き時計としての機能は十分に果たしてくれるでしょう。

 ということで,GT-720Fを使ったGPS時計は,ひとまずこれで終了。これ以上の検討は,1PPS信号の出ているGPSモジュールを手に入れてから先の話です。どちらかというと,感度がよいと評判のGT-720Fをもってしても,私の部屋ではさっぱり受信出来ません。置き場所を考えることの方が目下の所最優先です。

 今回のお話には,目的の1つとしてAVRを使ってみるというのがあったわけですが,これはとても楽しくできました。私はソフトは初心者並みのヘタレですが,それでも楽しくできたのはAVRのおかげだったのではないかと思います。

 ただ,2kByteというメモリサイズは狭いなあと感じました。Cで書いているからも知れませんが,安いtiny2313に過度な期待はしない方がいいとも思いました。

 AVRを使った応用は,いろいろ頭の中にあります。いつになるかはわかりませんが,ちょっとしたことに気軽に使える事が一番の個性だよなと,AVRを見直した工作でした。

ページ移動

ユーティリティ

2020年05月

- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31 - - - - - -

検索

エントリー検索フォーム
キーワード

ユーザー

新着画像

新着エントリー

過去ログ

Feed