てきとうなさいと べぇたばん

ArduinoでGPSモジュールの内容をもっと表示させる

座標モード。白い導線に隠れているが、ブレッドボードの左端にスイッチが有り、モードを切り替えられる 時刻モード。UTCとJSTを表示させている。

GPSには色々な情報がある

GPSは座標だけではない。座標を取得するためには正確な時刻が必要だし。

スイッチを使って切り替えてみる

そうしたらどうしようか。電源を投入したらはじめは座標を表示させて、ボタンを押したら時間を表示させてみよう。

コード

参考にしたコード

#include <SoftwareSerial.h>
#include <LiquidCrystal.h>

#define READBUFFERSIZE (256)
#define DELIMITER (",")

const int POSITIONMODE = 1;
const int TIMEMODE = 2;

const int BUTTON = 8;

SoftwareSerial g_gps( 6, 7 );
LiquidCrystal g_lcd( 11, 12, 2, 3, 4, 5 );

char g_szReadBuffer[READBUFFERSIZE] = "";
int g_iIndexCounter = 0;

void setup()
{
  g_gps.begin( 9600 );

  g_lcd.begin( 16, 2 );
  g_lcd.clear();

  g_lcd.setCursor( 0, 0 );
  g_lcd.print( "unko" );

  Serial.begin( 9600 );
  pinMode(BUTTON, INPUT);
}

int analyzePosition ( char szLineString[], float &rfLat, float &rfLong )
{
  rfLat = 0.0;
  rfLong = 0.0;

  if ( 0 != strncmp( "$GPRMC", szLineString, 6 ) )
  {
    return 0;
  }

  strtok( szLineString, DELIMITER );
  strtok( NULL, DELIMITER );
  strtok( NULL, DELIMITER );
  char* pszLat = strtok( NULL, DELIMITER );
  strtok( NULL, DELIMITER );
  char* pszLong = strtok( NULL, DELIMITER );

  if (NULL == pszLong )
  {
    return 0;
  }

  float temp, deg, min;

  temp = atof(pszLat);
  deg = (int)(temp/100);
  min = temp - deg * 100;
  rfLat = deg + min / 60;

  temp = atof(pszLong);
  deg = (int)(temp/100);
  min = temp - deg * 100;
  rfLong = deg + min / 60;

  return 1;
}

int analyzeTIME( char szLineString[], int& hour, int& minutes, int& seconds )
{
  hour     = 0;
  minutes  = 0;
  seconds  = 0;

  if ( 0 != strncmp( "$GPRMC", szLineString, 6 ) )
  {
    return 0;
  }

  strtok( szLineString, DELIMITER );
  char *hms = strtok( NULL, DELIMITER );

  char temp[3];
  temp[2] = '\\0';

  strncpy( temp, hms + 0, 2 );
  hour = atoi( temp );

  strncpy( temp, hms + 2, 2 );
  minutes = atoi( temp );

  strncpy( temp, hms + 4, 2 );
  seconds = atoi( temp );

  return 1;
}

int readLineString( SoftwareSerial& serial,
char szReadBuffer[], const int ciReadBufferSize, int& riIndexChar,
char szLineString[], const int ciLineStringSize )
{
  char c;
  while ( ( c = serial.read() ) != -1 )
  {
    switch ( c )
    {
    case '\\r':
      szReadBuffer[riIndexChar] = '\\0';
      strncpy( szLineString, szReadBuffer, ciLineStringSize - 1 );
      szLineString[ciLineStringSize - 1] = '\\0';
      riIndexChar = 0;
      return 1;
    case '\\n':
    case -1:
      break;
    default:
      if ( (ciReadBufferSize - 1) > riIndexChar )
      {
        szReadBuffer[riIndexChar] = c;
        riIndexChar++;
      }
    }
  }
  return 0;
}

void selectPrintGPSMode(char *szLineString, int mode)
{
  int year = 0;
  int month = 0;
  int day = 0;
  int hour = 0;
  int minutes = 0;
  int seconds = 0;
  float fLat = 0.0;
  float fLong = 0.0;

  switch( mode ) {
    case POSITIONMODE:
      if ( analyzePosition( szLineString, fLat, fLong ) )
      {
        printPosition(fLat, fLong);
      }
      break;
    case TIMEMODE:
      if ( analyzeTIME( szLineString, hour, minutes, seconds ) )
      {
        printTime( hour, minutes, seconds );
      }
      break;
    default:
      break;
  }
}

void printPosition(float fLat, float fLong)
{
  char szLat[11];
  char szLong[11];
  char szBuffer[17];

  dtostrf(fLat, 10, 6, szLat);
  dtostrf(fLong, 10, 6, szLong);

  snprintf( szBuffer, 17, "Lat. :%s", szLat );
  g_lcd.setCursor( 0, 0 );
  g_lcd.print( szBuffer );
  snprintf( szBuffer, 17, "Long.:%s", szLong );
  g_lcd.setCursor( 0, 1 );
  g_lcd.print( szBuffer );
}

void printTime(int hour, int minutes, int seconds)
{
  char szBuffer[17];
  snprintf( szBuffer, 17, "UTC.  : %02d:%02d:%02d", hour, minutes, seconds );
  g_lcd.setCursor( 0, 0 );
  g_lcd.print( szBuffer );
  snprintf( szBuffer, 17, "JST.  : %02d:%02d:%02d", ( hour + 9 ) % 24, minutes, seconds );
  g_lcd.setCursor( 0, 1 );
  g_lcd.print( szBuffer );
}

int isPushed()
{
  static int old_val = 0, state = 0;
  static unsigned long wait = millis();

  int val;
  val = digitalRead(BUTTON);

  if ( ( val == HIGH ) && ( old_val == LOW ) && ( millis() - wait ) > 10 )
  {
    state = 1 - state;
    wait = millis();
  }

  old_val = val;
  return state;
}

int changeGPSMode(int state)
{
  switch( state )
  {
    case 0:
      return POSITIONMODE;
    case 1:
      return TIMEMODE;
  }

  return 0;
}

void loop()
{
  char szLineString[READBUFFERSIZE];
  int mode = 0, gpsMode = 0;

  mode = isPushed();

  if ( !readLineString( g_gps,
  g_szReadBuffer, READBUFFERSIZE, g_iIndexCounter,
  szLineString, READBUFFERSIZE ) )
  {
    return;
  }

  gpsMode = changeGPSMode( mode );
  selectPrintGPSMode( szLineString, gpsMode );
}

これで、ある程度正確なGPS時計としても使えるようになった。

ちなみになんだけど、loop関数で行っていたLCDへの表示を関数へ持って行ったら、表示がバグった。

これは一体なんだろうと思ったら、charで定義されている配列のサイズが16byteしかなかったからだったようだ。16文字表示させたい場合には、charの最後にはnull文字が入るから、char str[17]となる必要があったのだ。

また、sprintfを使用するよりは、snprintfを使って文字数を制限しないと、やはりnull文字が入らず、バグのきっかけになりやすい。ArduinoのスケッチはほとんどCやC++のようだから、文字列の扱いには結構気を配らないと行けないなぁと感じる。