GPSモジュール(GY-GPS6MV2)を試してみる <電子工作>

コンピュータ、組み込み

安価で簡単に使えるGPSモジュールを入手しました。
UARTで簡単に位置情報を読み出すことができます。
小型のRP2040搭載基板 RP2040-Zeroを使って位置情報をSDカードに保存するロガーを作ってみます。

今回紹介するもの

GPSモジュール GY-GPS6MV2

特徴

モジュールの位置をUARTで読み取ることができます。

電源3 – 5V
出力UART
外観

アンテナを小型の同軸コネクタで接続します。
とても小さいですがはめ込みが固いので、位置を確認して慎重にはめ込みます。

モジュール基板は留め穴やはんだで固定することができますが、アンテナは重いのに固定するための穴などがありません。
当方は両面テープなどで固定することにします。

使用感

電源を与えてUARTで読み取りを行うと位置情報が出てきます。

数分程度の暖気が必要です。
受信を始めるとNMEAフォーマット電文が出てくるので、解析すると位置情報を知ることができます。
読み取った位置情報をgooglemapで検索すると、誤差は数mほどでした。

木造一軒家の部屋で作業をしていますが、受信感度は悪くないと思います。
位置情報の精度や受信状態が改善されない場合は空の見える場所へ移動するなどしてください。

受信電文について解説できるだけの知識がありませんので、「NMEAフォーマット」などで検索されると詳しく紹介されているサイトが見つかります。

今回作成したロガーでは、国内を前提に電文から座標を読み出すスケッチを作っています。
遠出をする機会があれば移動ルートをロガーに記録してみようと思います。

準備

使うもの

画像名称、型番用途
GPSモジュール
(GY-GPS6MV2)
位置情報を読み取ってもらいます
FT232RLGPSモジュールから
位置情報を読み取ります
RP2040-Zeroロガーを作ります
TFカードリーダ位置情報を保存します
マイクロSDカード位置情報を保存します
3色LEDモジュール動作状態のインジケータ
タクトボタンロギングの開始と終了に使用します
ブレッドボード配線します
ジャンパワイヤ数本配線します

動作

位置情報の読み出し

説明

マイコンボードは使いません。

GPSモジュールは電源を与えるだけで位置情報を吐き出します。
UARTモジュールを使い、GPSモジュールからの位置情報を読み取ります。

配線

FT232RLのVCC出力は3V3に設定します。
(5Vでもいいですが、説明のために3V3に統一します)
画像のようにジャンパブロックを3V3側でショートさせます。

GPSモジュール(GY-GPS6MV2)配線FT232RL
3V3VCC
GNDGND
TXRX
RXTX
結果

結果の確認にTeratermを使用します。
FT232RLに電源を投入したあと、TeratermでFT232RLのCOM番号で接続します。
※Baudrateは 9,600bpsです。

しばらくするとGPSモジュールからMNEA電文を受信します。
GPSが位置情報の特定をできないうちは電文にカンマ “,”が続きますが、位置情報の特定ができるようになるとモジュールの赤LEDの点滅(約1秒周期)が始まり、位置情報を含んだ電文を読み取ることができます。

位置情報の特定ができるまで数分程度かかることがあります。
位置情報の受信に時間がかかるようであれば、
・アンテナの接続を確認
・空の見える位置へ移動する
をすると改善できるかもしれません。

GPSロガー

説明

GPSから読み出した位置情報をSDカードに保存します。

電源投入後、タクトボタンを押すことでロギングを開始します。
もう一度タクトボタンを押すことでロギングを終了します。

インジケータにフルカラーLED(RGB)を使います。
青・・・初期化中、青が継続する場合は異常が発生(SDカードリーダが無いなど)
緑・・・通常状態。
赤・・・ロギング中(10秒ごとに現在の位置をSDカードリーダに保存します)

ログファイルは”/gps_<nn>.txt” に保存します。
<nn>は数字で、同じファイル名があればユニークなファイル名になるまでインクリメントします。

保存書式
(北緯)度 分 (東経)(度 分)

配線
スケッチ
/**********************************************************************
【ライセンスについて】
Copyright(c) 2022 by tamanegi
Released under the MIT license
'http://tamanegi.digick.jp/about-licence/

【スケッチの説明】
GPSから読み出した位置情報をSDカードに保存します。

電源投入後、タクトボタンを押すことでロギングを開始します。
もう一度タクトボタンを押すことでロギングを終了します。

インジケータにフルカラーLED(RGB)を使います。
青・・・初期化中、青が継続する場合は異常が発生(SDカードリーダが無いなど)
緑・・・通常状態。
赤・・・ロギング中(10秒ごとに現在の位置をSDカードリーダに保存します)


【ライブラリ】
RaspberryPi Pico/RP2040 -> Generic RP2040


【準備】
RP2040-Zero <-> GPS Module
3V3         <-> VCC
GND         <-> GND
GPIO0(TX)   <-> RX
GPIO1(RX)   <-> TX

RP2040-Zero     <-> SD CardReader Module
3V3             <-> VCC
GND             <-> GND
GPIO2(SPI CLK)  <-> CLK
GPIO3(SPI MOSI) <-> MOSI
GPIO4(SPI MISO) <-> MISO
GPIO5(SPI CS)   <-> CS

RP2040-Zero <-> Button
3V3             <-> Terminal
GPIO6           <-> Terminal

RP2040-Zero  <-> LED Module(RGB)
GND          <-> GND
GPIO26       <-> B
GPIO15       <-> G
GPIO14       <-> R

【バージョン情報】
2023/1/8 : 新規
**********************************************************************/

#include <SPI.h>
#include <SD.h>

//GPSモジュールからUARTを読み取るためのピン設定
#define PIN_GPS_TX  (0)
#define PIN_GPS_RX  (1)

//SDカードリーダに書き込みをするためのピン設定
#define PIN_SD_MOSI (3)
#define PIN_SD_SCK  (2)
#define PIN_SD_CS   (5)
#define PIN_SD_MISO (4)

//状態インジケータのためのピン設定
#define PIN_LED_B   (26)
#define PIN_LED_G   (15)
#define PIN_LED_R   (14)

//ボタンを読み取るためのピン設定
#define PIN_BUTTON  (6)
#define CHATTERING_TIME   (10)    //チャタリングを判定するための時間(ミリ秒)

#define BUFFER_LENGTH     (128)   //GPSから読み取りデータでデリミタまでのバッファサイズ(Byte)

#define LOGSPAN           (10 * 1000)    //ロギングを行う時間間隔(ミリ秒)

#define FILENAME "gps_"        //保存ファイル名
#define EXTNAME  ".txt"           //拡張子

//内部の制御状態。インジケータLEDの表示に使う。
//BAlertOnがtrue の時は青。BAlertOn がfalseの時は、BLoggingOn が falseの時は 緑、trueの時は赤
boolean BLoggingOn;               //GPS情報のロギング状態(false = ロギングしていない, true = ロギング中)
boolean BButtonOn = false;        //前周回の時のボタンの操作状態(false = 押されていない, true = 押されている)


boolean  BAlertOn;

void ShowIndicator(void);
boolean CheckButton(void);
boolean CreateLogFilename(char *Filename);
void AddLog(char *Filename, char *North, char *East);
boolean PickupGPRMC(char *GPSData, char *North, char *East);

void setup()
{
  Serial.begin(115200);           //デバッグ用途

  //インジケータの設定。初期は青を点灯させる。
  pinMode(PIN_LED_R, OUTPUT);
  pinMode(PIN_LED_G, OUTPUT);
  pinMode(PIN_LED_B, OUTPUT);

  //初期化中は青を点灯させる。 setup()の最後でアラートを解除する
  BAlertOn = true;
  ShowIndicator();
  delay(1000);

  //ボタンの設定。プルダウン設定なので、ボタンを押すとHIGHになる。
  pinMode(PIN_BUTTON, INPUT_PULLDOWN);
  BLoggingOn = false;             //起動後はロギングはOff
  
  
  Serial1.setTX(0);               //GPS用
  Serial1.setRX(1);
  Serial1.begin(9600);

  SPI.setTX(PIN_SD_MOSI);         //RP2040用SPI ピン設定 (SDカードリーダ用)
  SPI.setSCK(PIN_SD_SCK);
  SPI.setRX(PIN_SD_MISO);
  SPI.setCS(PIN_SD_CS);

  //SDカードリーダの操作開始
  if (SD.begin(PIN_SD_CS, SPI) == false)
  {
    Serial.println("initialization failed.");   //SDカードモジュールの初期化に失敗したらポーリング
    while (1);
  }


  //初期化が終了したらアラート情報を解除する。
  BAlertOn = false;
}

void loop()
{
  unsigned long LoggingTimer = 0;            //ロギングを行う時間間隔管理タイマー
  char LogFilename[128];
  char GPS_Buffer[BUFFER_LENGTH];
  char GPS_Data;
  int16_t GPSDelimiter = 0x0000;
  int16_t BufferIndex = 0;

  char GPSNorth[128];                         //北緯データ
  char GPSEast[128];                          //東経データ

  while(1)
  {
    //<<インジケータの更新>>
    ShowIndicator();

    //<<GPSからの読み取り>>
    //一度にデリミタまで受信できなかった場合は次回の周回時に続きを読み取る。
    //デリミタ(CR)を検出したら解析を開始してバッファのインデックス(BufferIndex)を初期化する。
    while(Serial1.available() != 0)
    {
      GPS_Data = Serial1.read();

      GPS_Buffer[BufferIndex] = GPS_Data;
      GPSDelimiter <<= 8;
      GPSDelimiter |= GPS_Data;
  
      if(GPSDelimiter == 0x0d0a)                              //デリミタ(CRLF = 0x0d, 0x0a)の確認
      {
        GPS_Buffer[(BufferIndex - 1)] = 0x00;
//Serial.printf("Loop() : %s\r\n", GPS_Buffer);
        PickupGPRMC(GPS_Buffer, GPSNorth, GPSEast);           //読み取ったデータの解析
        
        BufferIndex = 0;
        break;
      }
      BufferIndex ++;
    }

    //<<ボタン操作の確認>>
    //ボタンが押されたかを確認する(false = 押されていない, true = 押された)
    if(CheckButton() == true)
    {
      //ロギング可能かを判断する。
      //ロギング状態 false = ロギングしていない, true = ロギングしている
      if(BLoggingOn == false)
      {
        //ロギングを開始する。
        BLoggingOn = true;
        CreateLogFilename(LogFilename);               //ログを保存するファイル名を作成

        LoggingTimer = millis();                      //現在の時間を保存(初回は直ちに行うためスパン時間の加算はしない)
      }
      else
      {
        //ロギングを中止する。
        BLoggingOn = false;
      }
    }

    //<<ロギング処理>>
    if(BLoggingOn == true)
    {
      if(LoggingTimer < millis())
      {

        LoggingTimer = millis() + LOGSPAN;          //次回のロギング時間を設定

        AddLog(LogFilename, GPSNorth, GPSEast);
      }
    }
  }
}

//GPSから読み出したデータがGPRMCフォーマットだった場合、北緯,東経データを抜き出す。
//GPSData : GPSからの読み出しデータ
//North : 北緯データ
//East : 東経データ
//戻り値 : false = GPRMCではない、解析ができない, true = GPRMCデータでデータの解析ができた。
boolean PickupGPRMC(char *GPSData, char *North, char *East)
{
 
  Serial.printf("PickupGPRMC() : GPSData = %s\r\n", GPSData);
  const char Header[] = "$GPRMC";
  char *p;

  char Degree[16];
  char Minute[16];
  boolean Result = false;

  //カンマによる文字列の分割
  //ヘッダ文字列の比較を行う
  p = strtok(GPSData, ",");
  if(strcmp(p, Header) != 0) return false;

  //2番目データの有効/警告を読み取る
  //有効('A')の場合は3番目の北緯、5番目の東経データを抜き取る
  int Index = 0;
  while(p != NULL)
  {
    Index ++;
    p = strtok(NULL, ",");

    if(Index == 2)
    {
      //有効'A'以外の場合、位置情報の更新をしないで終了
      if(*p != 'A')
      {
        break;
      }
    }
    else if(Index == 3)
    {
//Serial.printf("PickupGPGGA() : Index = 2, %s\r\n", p);
      strncpy(Degree, p, 2);        //度の抜き取り
      *(Degree + 2) = 0x00;
      strcpy(Minute, (p + 2));      //分以下の抜き取り
      sprintf(North, "%s %s", Degree, Minute);
    }
    else if(Index == 5)
    {
//Serial.printf("PickupGPGGA() : Index = 3, %s\r\n", p);
      strncpy(Degree, p, 3);        //度の抜き取り
      *(Degree + 3) = 0x00;
      strcpy(Minute, (p + 3));      //分以下の抜き取り
      sprintf(East, "%s %s", Degree, Minute);
      Result = true;
    }
  }

Serial.printf("PickupGPRMC() : North = %s, East = %s\r\n", North, East);
  return Result;
}


//ログデータを追記する。
//ファイルが無ければ新規で作成、ファイルがあれば追記する。
//Filename : 追記するファイル名
void AddLog(char *Filename, char *North, char *East)
{
Serial.printf("AddLog() : Filename = %s, North = %s, East = %s\n", Filename, North, East);

  File myFile = SD.open(Filename, FILE_WRITE);       //追記モードでオープン

  myFile.printf("%ld, %s,%s\n", (millis() / 1000), North, East);
  
  myFile.close();

}

//ユニークなファイル名を作成する。
//ベースのファイル名は FILENAME + _nnn + EXTNAME (例 : "gpslog_000.txt")
//*Filename : 作成したファイル名
//戻り値 : false = ファイル名の作成に失敗, true = ファイル名の作成に成功
boolean CreateLogFilename(char *Filename)
{
  //SDカードのファイルは 100個以下を前提にします。
  const int FileMax = 100;
  char FileList[FileMax][128];
  File myFile = SD.open("/");                  //ルートフォルダ(/)のファイルをすべて読み取る。

  File tFileList;
  int FileIndex = 0;
  
  //ファイルリストを作る
  while(1)
  {
    tFileList = myFile.openNextFile();      //ルートフォルダの見つかったリストがまだあるか?
    if(tFileList == 0)break;

    //ファイル数が多いので検索エラーにする。
    if(FileIndex >= FileMax)
    {
      BAlertOn = true;
      return false;
    }
    sprintf(FileList[FileIndex], "%s", tFileList.name());
    FileIndex ++;
    
  }
  tFileList.close();
  myFile.close();

  char DummyFilename[128];
  boolean SameOn;
  boolean Result = false;

  //作ったファイル名と同名ファイルがリストの中に存在するか確認する。
  for(int16_t SerialNo = 1; SerialNo < FileMax; SerialNo ++)
  {
    //仮のファイル名を作成
    sprintf(DummyFilename, "%s%03d%s", FILENAME, SerialNo, EXTNAME);

    //仮のファイル名がリストの中にあるか確認
    //SameOn がfalseのままならユニークファイル名、trueになったら存在する。
    SameOn = false;
    for(int i = 1; i < FileIndex; i ++)
    {
      if (0 == strcmp(DummyFilename, FileList[i]))
      {
        SameOn = true;
      }
    }

    //ユニークなファイル名が見つかったら引数に与えて終了
    //一致するファイル名があった場合番号を変えて再検索
    if(SameOn == false)
    {
      strcpy(Filename, DummyFilename);
      Result = true;
      break;
    }
  }

  return Result;
}


//ボタンが押されたかを判定する。
//ボタンが押され続けていると、この関数が呼ばれるたびに押された判定になるため、
//一度ボタンを解放したことが確認されるまで次の押された判定はされない。
//戻り値
// false = ボタンは押されていない, true = ボタンは押された
boolean CheckButton(void)
{
  //簡素化したチャタリング対策
  //10msの間隔で読み取った結果を論理積してボタンの状態とする。
  boolean ButtonStatus = digitalRead(PIN_BUTTON);
  delay(CHATTERING_TIME);
  ButtonStatus &= digitalRead(PIN_BUTTON);

  //前回ボタンを押していない判定をしてした場合
  if(BButtonOn == false)
  {
    //今回の結果を戻り値にする。
    //今回の情報が次回使用される。
    BButtonOn = ButtonStatus;
    return ButtonStatus;
  }
  else
  {
    //押していても押していなくても、false(押していない)結果を返す。
    //今回の情報が次回使用される。
    BButtonOn = ButtonStatus;
    return false;
  }
}


//現在の動作状態をLEDで表示します。
//RGB LEDを使用します。
//R = ロギング中。ロギング開始/中止ボタンを押すと緑に変化します。
//G = アイドリング中。ロギング開始/中止ボタンを押すと赤に変化します。
//B = 初期化失敗など何かトラブルが発生している状態
void ShowIndicator(void)
{
  //アラートが無い場合
  if(BAlertOn == false)
  {
    //ロギングしていない
    if(BLoggingOn == false)
    {
      digitalWrite(PIN_LED_R, false);
      digitalWrite(PIN_LED_B, false);
      digitalWrite(PIN_LED_G, true);
    }
    //ロギング中
    else
    {
      digitalWrite(PIN_LED_R, true);
      digitalWrite(PIN_LED_B, false);
      digitalWrite(PIN_LED_G, false);
    }
  }
  //アラート発生
  else
  {
    digitalWrite(PIN_LED_R, false);
    digitalWrite(PIN_LED_B, true);
    digitalWrite(PIN_LED_G, false);
  }
}
結果

ボタンを押してロギングが成功するとSDカード上にログファイルが作成されます。
以下は5回ロギングを行っています。

ロギングが正常にできていれば下のように10秒ごとの位置情報を保存しています。
この位置情報をgoogle map の検索にドラッグ&ドロップすると位置情報の位置を表示します。

コメント

タイトルとURLをコピーしました