Program Resource

開発者向け各種コード、アルゴリズム、リソース情報ライブラリ もしくはねふぁの覚え書き

Arduinoの機能を拡張するシールドは様々なものがでており、LANや無線LAN、モーター制御やLCD制御等複雑な電子機器の制御がArduinoの基板上に乗せるだけで簡単に出来るようになっている。ソフト的にもライブラリを追加してクラスや関数を少し呼び出すだけで使えてしまう便利な世の中になっている。

GPSシールドもその一つで、Arduinoで簡単にGPS情報を取得し扱う事が出来る。今回使用しているのはElecrow製のGPSシールドで、NEO-6Mと言うモジュールが使われている。

DSC03242

http://www.elecrow.com/wiki/index.php?title=GPS_shield

SDカードスロットもついているが、今回SDカードの使い方については割愛する。SDカードはライブラリを使用して読み書き出来るので、GPSのログ等の記録に使えそうだ。

http://www.elecrow.com/wiki/index.php?title=Wireless_SDshield

GPSの使い方は簡単で、ライブラリすら必要無い。GPS情報がRX/TXピンからシリアル通信でどんどん流れてくるだけだ。
シールド上のジャンパピンでどのピンをRX/TXにするか設定し、後はSoftwareSerialでピンを指定し、9600bpsで開けばどんどんデータが流れてくる。ピンを0/1に指定すれば、シリアルモニタを開いた途端GPSの情報が表示される。

とりあえずGPSの情報を見て楽しみたいのであれば、無償で提供されているu-centerのソフトをインストールし、Arduinoのポートを指定して開けば各種情報が表示される。

http://www.elecrow.com/wiki/images/4/4a/U-centersetup-7.0.2.1.zip

BluetoothモジュールをArduinoに追加すれば、GPS情報を離れたPC等から見ることも出来る。

さて、GPSロガー等色々用途は考えられるが、使い方の例としてLCDシールドと組み合わせてGPS時計を作ってみよう。
しかし、単純なGPS時計だけでは他でも紹介されていて芸が無いので、さらに組み合わせてGPS時計で時計合わせをした後、JJY信号を発信し電波時計の調整が出来る様にしてみる。

DSC03325

GPSシールドの性能はなかなか良く、電波時計の電波が入らない屋内でもGPS情報は取得出来たりしたので(位置までは計測出来ないが)、GPS信号は入るが電波時計が入らない場所に設定してある電波時計を調整したり、海外で日本で購入したお気に入りの電波時計を調整するのにも使えるかもしれない。

SainsmartのLCDシールドとの組み合わせで、GPSシールドはRX/TXを0/1ピンに設定(このため通信にソフトシリアルでなく通常シリアル使用)、JJY信号の発信にはピン3を使用している。配線はArduinoの上にGPSシールド、その上にLCDシールドをスタックするだけ。Digital Pin3とGNDの間にJJYのアンテナケーブル。

LCDシールドのピンアサインは下記参照。

http://www.dfrobot.com/wiki/index.php/LCD_KeyPad_Shield_For_Arduino_SKU:_DFR0009

GPSシールドが無い場合でも、シリアルモニタから直接$GPRMCコマンドを入力する事で時計合わせをする事も可能。

Arudinoの電源を入れるとGPS情報待ちとなり、GPS情報から日時情報が取得出来たらLCDに時計が表示される。
この状態でもJJY信号は発信するが、余分な処理が走り信号が正しく出るかわからないのでLCDシールドのSELECTボタンを押すことでJJY信号発信専念モードと切り替える様にしている。

DSC03323

GPS信号を受信できるところで時計合わせを行い、電波時計のそばでJJY発信モードに切り替え。

DSC03322

電波はあまり飛ばないので、時計の電波状況を見ながらアンテナの位置を調整する。しばらくすると電波時計の時計がArduino表示の時計と同期する。

DSC03324

DSC03321

コードは以下の通り。下記サイトからタイムライブラリも入れておく事。

http://www.pjrc.com/teensy/td_libs_Time.html

csvテキストからパラメータ抜き出す関数は他の用途でも便利に使えるのではないかと思う。

#include <LCD4Bit_mod.h>
#include <Time.h>
#include <SPI.h>

//read gps data from serial, set to time object, show to LCD unit, send JJY signal
//press select button on lcd shield to switch GPS receive / JJY Transmit mode
//Though JJY trasmit is done while in GPS receive mode, switching to JJY Transmit mode
//may give better signal result

//http://www.hiramine.com/physicalcomputing/general/gps_nmeaformat.html
//http://www.elecrow.com/wiki/index.php?title=GPS_shield
//http://d.hatena.ne.jp/NeoCat/20110328/1301256560

//e.g. $GPRMC,025256.00,V,,,,,,,120513,,,N*7F
//     $GPGGA,025254.00,,,,,0,00,99.99,,,,,,*62
//     $GPRMC,225446.00,A,4916.452653,N,12311.123747,W,000.5,054.7,191114,06.2,W,A*68
//     $GPGGA,085120.307,3541.1493,N,13945.3994,E,1,08,1.0,6.9,M,35.9,M,,0000*5E

#ifndef cbi
#define cbi(PORT, BIT) (_SFR_BYTE(PORT) &= ~_BV(BIT))
#endif
#ifndef sbi
#define sbi(PORT, BIT) (_SFR_BYTE(PORT) |= _BV(BIT))
#endif

char   junk = ' ';
String datain; //data from serial
char   subch[96]; //sub string used for cvs
char   chbuff[128];
char   *cmd;
long   gpstime; //gps time info
long   gpsdate; //gps date info
bool   gpsstatus; //gps data valid / invalid flag
bool   initialized; //time initialized
int    lastsec; //last second updated
int    lastgpssec; //last gps data received
int    numgps;
static long timezoneadjust = 3600 * 9; //+9 hour from UTC

//mode and keypad input
int    mode;
int  adc_key_val[5] = {
  30, 150, 360, 535, 760
};
int NUM_KEYS = 5;
int adc_key_in;
int key;
int oldkey;

//for JJY transmission
byte   timecode[60];
bool   jjyemitting;
int    jjy_msdur;
unsigned long jjy_startTime;

LCD4Bit_mod lcd = LCD4Bit_mod(2);

void setup() {
  mode = 0;
  lcd.init();
  lcd.clear();
  jjyemitting = false;
  initialized = false;
  lastsec = -1;
  lastgpssec = -1;
  numgps = 0;
  Serial.begin(9600);
  //Serial.println("Waiting for GPS...");
  lcd.cursorTo(1, 0);
  lcd.printIn("---[--]");
}

void loop() {
  if (mode == 0) { //GPS receive mode
    if (Serial.available()) {
      while (Serial.available() > 0) {
        junk = Serial.read();
        datain = datain + junk;
      }
      if (junk == '\n' || junk == '\r') { //end of line
        datain.trim();
        if (datain.length() > 0) {
          Serial.println(datain);
          cmd = getcsvparam(&datain[0], 0);
          if (strcmp(cmd, "$GPRMC") == 0) { //get time from RMC data
            if (strlen(getcsvparam(&datain[0], 1)) > 6) {
              gpstime = (long)atof(getcsvparam(&datain[0], 1)); //time
              gpsstatus = strcmp(getcsvparam(&datain[0], 2), "A") == 0 ? true : false; //validity
              gpsdate = atol(getcsvparam(&datain[0], 9)); //date
              setTime(gpstime / 10000, (gpstime / 100) % 100, gpstime % 100, gpsdate / 10000, (gpsdate / 100) % 100, (gpsdate % 100) + 2000);
              adjustTime(timezoneadjust);
              initialized = true;
            }
            datain = "";
          }
          else if (strcmp(cmd, "$GPGGA") == 0) { //get satellite number
            numgps = atoi(getcsvparam(&datain[0], 6));
            lastgpssec = second();
            datain = "";
          }
          else {
            datain = "";
          }
        }
      }
    }

    if (initialized) {
      if (lastsec != second()) {
        emitJJY();
        char gpsinfobuff[5];
        if ((second() + 60 - lastgpssec) % 60 > 5) //if over 5 sec since last gps data receive, clear flag
          lastgpssec = -1;
        if (lastgpssec == -1) {
          strcpy(gpsinfobuff, "GPS[--]");
        } else {
          sprintf(gpsinfobuff, "GPS[%02d]", numgps);
        }
        if (gpsstatus) {
          //sprintf(chbuff, "GPS[%s]:%02d:%02d:%02d %04d/%02d/%02d", gpsinfobuff, hour(), minute(), second(), year(), month(), day());
          //Serial.println(chbuff);
          lcd.cursorTo(1, 0);
          lcd.printIn(gpsinfobuff);
          sprintf(chbuff, "%02d:%02d:%02d %02d-%02d", hour(), minute(), second(), month(), day());
          lcd.cursorTo(2, 0);
          lcd.printIn(chbuff);
        } else {
          //sprintf(chbuff, "---[--]:%02d:%02d:%02d %04d/%02d/%02d", hour(), minute(), second(), year(), month(), day());
          //Serial.println(chbuff);
          lcd.cursorTo(1, 0);
          lcd.printIn("---[--]");
          sprintf(chbuff, "%02d:%02d:%02d %02d-%02d", hour(), minute(), second(), month(), day());
          lcd.cursorTo(2, 0);
          lcd.printIn(chbuff);
        }
        lastsec = second();
      }
    }
  } else { //JJY transmit mode
    if (initialized) {
      if (lastsec != second()) {
        emitJJY();
        sprintf(chbuff, "%02d:%02d:%02d %02d-%02d", hour(), minute(), second(), month(), day());
        lcd.cursorTo(2, 0);
        lcd.printIn(chbuff);
        while (jjyemitting)
          procJJY();
        lastsec = second();
      }
    } else {
      lcd.cursorTo(1, 0);
      lcd.printIn("Time not synced");
    }
  }

  checkkey();

  procJJY(); //to stop emission
  //do not put delay in loop or serial data will get lost (only 62 bytes will be read)
}

//return n-th char array from csv text
char *getcsvparam(char *cvstxt, int index) {
  int strcnt = 0;
  int csvcnt = 0;
  memset(subch, 0, 96);
  for (int i = 0; i < strlen(cvstxt); i++) {
    if (cvstxt[i] == ',') {
      csvcnt++;
      if (csvcnt > index)
        break;
    } else if (csvcnt == index) {
      subch[strcnt++] = cvstxt[i];
    }
  }
  return &subch[0];
}

void checkkey() {
  adc_key_in = analogRead(0);    // read the value from the sensor
  key = get_key(adc_key_in);	 // convert into key press
  if (key != oldkey)		 // if keypress is detected
  {
    delay(50);		         // wait for debounce time
    adc_key_in = analogRead(0);  // read the value from the sensor
    key = get_key(adc_key_in);	 // convert into key press
    if (key != oldkey)
    {
      oldkey = key;
      if (key == 4) { //select key
        lcd.clear();
        if (mode == 0) {
          mode = 1; //JJY transmit mode
          lcd.cursorTo(1,0);
          lcd.printIn("JJY Transmit");
        } else {
          mode = 0; //GPS receive mode
          //flush any incoming data before switching
          if (Serial.available()) {
            while (Serial.available() > 0) {
              junk = Serial.read();
            }
          }
          lcd.cursorTo(1,0);
          lcd.printIn("---[--]");
        }
      }
    }
  }
}

int get_key(unsigned int input)
{
  int k;

  for (k = 0; k < NUM_KEYS; k++)
  {
    if (input < adc_key_val[k])
    {
      return k;
    }
  }

  if (k >= NUM_KEYS)
    k = -1;     // No valid key pressed

  return k;
}

//JJY signal
void emitJJY() {
  if (jjyemitting)
    return;
  jjy_startTime = millis();
  // generate 40khz from 3 pin using PWM
  pinMode(3, OUTPUT);
  digitalWrite(3, 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)
  jjy_msdur = calcTimeCodeDuration();
  jjyemitting = true;
  lcd.cursorTo(1, 15);
  lcd.printIn("*");
}

void procJJY() {
  if (jjyemitting) {
    if (millis() - jjy_startTime >= jjy_msdur) {
      cbi(TCCR2A, COM2B1);
      jjyemitting = false;
      lcd.cursorTo(1, 15);
      lcd.printIn(" ");
    }
  }
}

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;
}

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;
}

[GPG] Arduino用GPSシールド アンテナ付き NEO-6Mチップ
[GPG] Arduino用GPSシールド アンテナ付き NEO-6Mチップ


ArduinoとGPSシールドでGPS時計とおまけの電波時計調整機能付き」 に3件のコメント

  1. axion より:

    初めまして、同じGPSシールドで試しています、このようなエラーを吐いてコンパイルできませんでした
    対策を教えて頂けたら助かります、

    Arduino: 1.6.4 (Windows 7), ボード:”Arduino Uno”

    C:\Users\axion.tk\Documents\Arduino\libraries\LCD4Bit_mod\LCD4Bit_mod.cpp:29:57: fatal error: WConstants.h: No such file or directory
    #include “WConstants.h” //all things wiring / arduino
    ^
    compilation terminated.
    コンパイル時にエラーが発生しました。

    This report would have more information with
    “コンパイル中の詳細な出力を表示する”
    ファイル > 設定 で有効にする

    • NeFa より:

      返信遅くなりましたが

      #include “WConstants.h”

      #include <Arduino.h>

      にしてみて下さい。

      • axion より:

        色々試しましたが#include でコンパイルが通り作動しました、有難うございます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*