NEO-6M GPSモジュールとArduino:配線、NMEA、TinyGPSPlus
NEO-6M GPSモジュールをArduinoで使う方法:TX/RXの配線、最初の測位までの時間、$GPGGAと$GPRMCのセンテンス、TinyGPSPlusのコード、ESP32とOLED、PicoのMicroPython。
NEO-6M GPSモジュールは、緯度、経度、速度、高度、そして衛星からのきわめて正確なUTC時刻を、Arduino、ESP32、Raspberry Pi Picoのプロジェクトに加えられる、安くてとても人気のあるGPS受信機です。Arduinoで使うのに必要な線は4本(VCC、GND、TX、RX)だけです。モジュールは1秒に1回、NMEAセンテンスと呼ばれる文字の行を9600ボーで送り、TinyGPSPlusライブラリがそれをすぐに使える数値に変えてくれます。
このガイドでは、モジュールのしくみ、Arduino Uno、ESP32、Picoへの配線、最初の測位に数分かかることがある理由、$GPGGAと$GPRMCのセンテンスの各項目の意味、緯度と経度をGoogleマップのリンクに変える方法、位置をOLEDディスプレイに表示する方法を、ステップごとに紹介します。最後に、最も多い2つの問題「データが来ない」と「測位できない」の原因と対処法を表にまとめています。

NEO-6M GPSモジュールとは?そのしくみ
GPS(全地球測位システム)は、高度約20,200 kmを回る30個以上の衛星でできています。各衛星は、搭載した原子時計の時刻と自分の軌道のデータをいつも送り続けています。NEO-6Mは、信号が衛星を出た瞬間と届いた瞬間の差を測り、そこから各衛星までの距離を求めます。少なくとも4つの衛星が見えれば、緯度、経度、高度、そして自分の時計のずれを解けます。衛星が3つだけなら、高度なしの2次元(2D)の位置を計算できます。衛星の信号は地上ではきわめて弱く(約-130 dBm)、コンクリート、金属の屋根、さらには茂った木々でもほとんどが遮られます。そのため、GPSの鉄則は簡単です。アンテナから空が見えていることです。
| 項目 | 値 |
|---|---|
| 受信チップ | u-blox NEO-6M(u-blox 6測位エンジン) |
| チャンネルと信号 | 50チャンネル、GPS L1 C/A(1575.42 MHz)、SBAS:EGNOS、WAAS、MSAS |
| 水平精度 | 2.5 m CEP(SBASで2.0 m) |
| 速度と方位の精度 | 0.1 m/sと0.5度 |
| 最初の測位までの時間(TTFF) | コールド27秒、ウォーム27秒、ホット1秒 |
| 感度 | 追尾 -161 dBm、コールドスタート -147 dBm |
| 更新レート | 初期設定で1 Hz、最大5 Hz |
| インターフェース | UART、初期設定で9600ボー 8N1、NMEA 0183 |
| 電源 | ボード:3.3〜5 V(レギュレーター付き)、チップ:2.7〜3.6 V |
| 電流 | 探索中は約47 mA、追尾中は約39 mA |
| 動作温度 | -40〜+85 °C |
買うボードは、16×12 mmのNEO-6Mのチップだけではありません。一般的なGY-NEO6MV2のボードには、25×25 mmのセラミックパッチアンテナ、3.3〜5 Vで給電できるようにするレギュレーター、設定を保存する小さなEEPROM、時刻と衛星のデータを保つ充電式のバックアップ電池、測位できると点滅するLEDが付いています。上の写真のボードのように、外部アンテナ用のSMAか小さなu.FLのコネクタや、パソコンに直接つなぐためのmicro USBポートを持つボードもあります。
NEO-6Mの配線:Arduino Uno、ESP32、Raspberry Pi Pico
NEO-6MはUARTで通信します。パソコンのシリアルポートのように動き、2本のデータ線を使います。ルールは簡単で、データ線は交差させます。モジュールのTX(送信)ピンをボードのRX(受信)ピンに、モジュールのRXピンをボードのTXピンにつなぎます。Arduino Unoの唯一のハードウェアシリアルポート(0番と1番のピン)はUSBでの書き込みとシリアルモニタに使われるので、GPSはSoftwareSerialでD4とD3のピンにつなぎます。シリアル通信の詳しいことはUARTシリアル通信の記事にあります。
| NEO-6Mのピン | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC | 5V | VIN(5V)または3V3 | VBUS(5V)または3V3(OUT) |
| GND | GND | GND | GND |
| TX | D4(SoftwareSerialのRX) | GPIO 16(RX2) | GP1(UART0 RX) |
| RX | D3、1 kΩ / 2 kΩの分圧回路を通して | GPIO 17(TX2) | GP0(UART0 TX) |
| PPS(ある場合) | つながなくてよい | つながなくてよい | つながなくてよい |
最初の測位:コールド、ウォーム、ホットスタート
GPSモジュールが、初めて電源を入れた瞬間に位置を出さないのはまったく普通のことです。位置を計算するには、見えている衛星の正確な軌道データ(エフェメリス)が必要で、各衛星はそれを約30秒ごとに送ります。すべての衛星のおおまかな軌道の一覧であるアルマナックを全部受け取るには12.5分かかります。電源を切る前にどれだけのデータを保っていたかによって、モジュールは3つのうちのどれかの方法で起動します。データシートの時間は、すべての衛星から強い信号が届く試験条件で測ったものです。ボードに付いている小さなセラミックアンテナなら、数分待つのが普通です。
| 起動の種類 | モジュールに保存されているデータ | データシート | 実際(屋外) |
|---|---|---|---|
| コールドスタート | 時刻、位置、衛星のデータなし(初めての使用か、バックアップ電池切れ) | 27秒 | 1〜5分。信号が弱いと10分を超えることも |
| ウォームスタート | アルマナック、おおよその時刻と位置。エフェメリスは古い(約4時間以上オフ) | 27秒 | 30秒〜1分 |
| ホットスタート | 最新のエフェメリスと時刻(電源を切ったばかりで、バックアップ電池が充電済み) | 1秒 | 数秒 |
- 屋外に出る:屋内、特にコンクリートの建物の中では、モジュールはまったく位置を見つけられないことがよくあります。テストにはベランダや庭、少なくとも開けた窓の前を使いましょう。
- アンテナを正しく向ける:セラミックアンテナの平らで明るい色の面を空に向けます。裏返したり手で覆ったりすると、信号が大きく弱まります。
- アンテナを電子回路から離す:ESP32のWi-Fiアンテナ、スイッチングレギュレーター、長いUSBケーブルはノイズを出します。アンテナを基板から数センチ離して置きましょう。
- 辛抱強く、動かさない:最初の測位の間は電源を切らないでください。再起動するたびに、探索は最初からやり直しになります。
- LEDを見る:ボードのLEDは測位できるまで消えたままで、測位できると1秒に1回点滅し始めます。
- バックアップ電池を充電する:電池が切れていると、電源を入れるたびにコールドスタートになります。モジュールに数時間給電したままにすると充電され、その後の起動は数秒になります。
NMEAセンテンス:$GPGGAと$GPRMCの読み方
NEO-6Mは、見つけたものをすべて、NMEA 0183規格のカンマ区切りの文字の行で送ります。初期設定では、GGA、GLL、GSA、GSV、RMC、VTGの6種類のセンテンスが毎秒届きます。TXTのセンテンスはふつう電源投入時にだけ現れ、ファームウェアのバージョンなどの情報メッセージを運びます。各センテンスは$で始まり、GPの接頭辞はデータがGPSから来ていることを、次の3文字はセンテンスの種類を表します。行の終わりの*のあとの2文字はチェックサムで、$と*の間のすべての文字をXORして求め、壊れた行はこれで取り除かれます。位置で最も大切な2つのセンテンスは次のとおりです:$GPGGA,092750.00,4100.82500,N,02858.74000,E,1,08,1.01,39.2,M,36.9,M,,*67と$GPRMC,092750.00,A,4100.82500,N,02858.74000,E,0.512,,281026,,,A*7E
| $GPGGAの項目 | 例 | 意味 |
|---|---|---|
| 時刻(UTC) | 092750.00 | 09:27:50 UTC |
| 緯度 | 4100.82500,N | 北緯41度00.825分 |
| 経度 | 02858.74000,E | 東経28度58.74分 |
| 測位の品質 | 1 | 0 = 測位なし、1 = GPS、2 = ディファレンシャル(SBAS)補正 |
| 衛星 | 08 | 位置の計算に使った衛星 |
| HDOP | 1.01 | 水平精度低下率。2未満ならとても良い |
| 高度 | 39.2,M | 平均海面から39.2 m |
| ジオイド高 | 36.9,M | 海面とWGS84楕円体の差 |
| $GPRMCの項目 | 例 | 意味 |
|---|---|---|
| 時刻(UTC) | 092750.00 | 時、分、秒 |
| 状態 | A | A = 有効な位置、V = 測位なし |
| 緯度 / 経度 | 4100.82500,N / 02858.74000,E | GGAと同じ形式 |
| 速度 | 0.512 | ノット単位(0.512 × 1.852 ≈ 0.95 km/h) |
| 進路 | (空) | 進む方向を度で。止まっているときは空のことがある |
| 日付 | 281026 | ddmmyy形式:2026年10月28日 |
| モード | A | A = 単独、D = ディファレンシャル、N = データなし |
NMEAの緯度と経度は10進数の度ではなく度と分(ddmm.mmmm)ですが、Googleマップやほとんどのソフトウェアは10進数の度を求めます。変換するには、緯度の最初の2桁(経度は最初の3桁)を度とし、残りの分を60で割って足します。4100.82500 → 41 + 0.825 / 60 = 41.013750、02858.74000 → 28 + 58.74 / 60 = 28.979000です。南緯(S)と西経(W)はマイナスの数で書きます。小数点以下6桁目は約11 cm、5桁目は1.1 mに相当します。NEO-6Mの精度は2.5 mなので、6桁あれば十分すぎるほどです。
ステップごとに:Arduino UnoでGPSの生のデータを見る
- 表のとおりにNEO-6Mを配線します。VCC → 5V、GND → GND、GPSのTX → D4、GPSのRX → 分圧回路を通してD3です。
- 下のコードを書き込みます。追加のライブラリは不要で、SoftwareSerialはArduino IDEに付属しています。
- シリアルモニタを開き、速度を115200ボーにします。
- モジュールを屋外か窓際に持っていき、$GPで始まる行が流れていくのを見ます。
- $GPRMCの行の2つ目の項目がVからAに変わり、LEDが点滅し始めたら、モジュールは位置を見つけています。
1#include <SoftwareSerial.h>
2
3SoftwareSerial gpsSerial(4, 3); // RX, TX → GPS TX D4'e, GPS RX D3'e / GPS TX to D4, GPS RX to D3
4
5void setup() {
6 Serial.begin(115200); // Seri Monitör hızı / Serial Monitor speed
7 gpsSerial.begin(9600); // NEO-6M varsayılan hızı / NEO-6M default baud rate
8 Serial.println(F("NEO-6M ham NMEA / raw NMEA"));
9}
10
11void loop() {
12 while (gpsSerial.available()) {
13 Serial.write(gpsSerial.read()); // gelen her baytı aynen aktar / pass every byte through
14 }
15}シリアルモニタには、毎秒6〜9行のまとまりが表示されます(見えている衛星が多いほど、GSVのセンテンスは多くの行に分かれます)。モジュールがまだ衛星を見つけていなければ、$GPRMC,,V,,,,,,,,,,N*53や$GPGGA,,,,,,0,00,99.99,,,,,,*48のように、行は空のカンマだらけです。これはエラーではなく、モジュールは動いているけれど、まだ位置を計算できないことを示しています。$GPGSVの行では、見えている衛星の数と、それぞれの信号強度(dB-Hz)を追えます。30 dB-Hzを超えていれば信号は良好です。行がまったく来なければ配線を、文字化けしていればボーレートを確認しましょう。
TinyGPSPlusで緯度、経度、速度、時刻を読む
NMEAの行を手で解析するより、Mikal HartのTinyGPSPlusライブラリを使うほうがずっと実用的です。ライブラリマネージャで「TinyGPSPlus」を検索してインストールしましょう。手順はArduinoライブラリの追加方法の記事で説明しています。注意点として、ライブラリの例は4800ボーを使いますが、NEO-6Mは9600ボーです。速度を変えずに例を動かすと、位置はいつもINVALIDになります。文字化けした文字からは、ライブラリがセンテンスを1つも読み解けないからです。下のコードは、位置、速度、高度、衛星の数、日付、時刻を1秒に1回表示し、位置をブラウザで開けるGoogleマップのリンクに変えます。
1#include <TinyGPSPlus.h>
2#include <SoftwareSerial.h>
3
4const int RX_PIN = 4; // GPS TX → D4
5const int TX_PIN = 3; // GPS RX ← D3 (bölücü üzerinden / through a divider)
6const uint32_t GPS_BAUD = 9600; // NEO-6M varsayılanı / NEO-6M default
7
8TinyGPSPlus gps;
9SoftwareSerial gpsSerial(RX_PIN, TX_PIN);
10unsigned long lastPrint = 0;
11
12void setup() {
13 Serial.begin(115200);
14 gpsSerial.begin(GPS_BAUD);
15 Serial.println(F("NEO-6M + TinyGPSPlus"));
16}
17
18void loop() {
19 while (gpsSerial.available()) {
20 gps.encode(gpsSerial.read()); // her baytı ayrıştırıcıya ver / feed every byte to the parser
21 }
22 if (millis() - lastPrint >= 1000) { // delay() yok, saniyede bir yaz / no delay(), print once per second
23 lastPrint = millis();
24 printInfo();
25 }
26}
27
28void printInfo() {
29 if (gps.charsProcessed() < 10) {
30 Serial.println(F("GPS verisi yok: kablolari ve baud hizini kontrol et / No GPS data: check wiring and baud"));
31 return;
32 }
33 Serial.print(F("Uydu / Sats: "));
34 Serial.println(gps.satellites.value());
35
36 if (gps.location.isValid()) {
37 Serial.print(F("Enlem / Lat: "));
38 Serial.println(gps.location.lat(), 6);
39 Serial.print(F("Boylam / Lng: "));
40 Serial.println(gps.location.lng(), 6);
41 Serial.print(F("Hiz / Speed: "));
42 Serial.print(gps.speed.kmph(), 1);
43 Serial.println(F(" km/h"));
44 Serial.print(F("Yukseklik / Alt: "));
45 Serial.print(gps.altitude.meters(), 1);
46 Serial.println(F(" m"));
47 // Google Haritalar bağlantısı / Google Maps link
48 Serial.print(F("https://www.google.com/maps/search/?api=1&query="));
49 Serial.print(gps.location.lat(), 6);
50 Serial.print(',');
51 Serial.println(gps.location.lng(), 6);
52 } else {
53 Serial.println(F("Konum yok, uydu araniyor / No fix yet, searching"));
54 }
55
56 if (gps.date.isValid() && gps.time.isValid()) {
57 char buf[32];
58 snprintf(buf, sizeof(buf), "%02d.%02d.%04d %02d:%02d:%02d UTC",
59 gps.date.day(), gps.date.month(), (int)gps.date.year(),
60 gps.time.hour(), gps.time.minute(), gps.time.second());
61 Serial.println(buf); // saat UTC'dir / time is UTC
62 }
63 Serial.println();
64}| 関数 | 返すもの |
|---|---|
| gps.encode(c) | 届いたバイトごとに呼ぶ。有効なセンテンスがそろうとtrueを返す |
| gps.location.lat() / lng()(緯度・経度) | 10進数の度での緯度と経度 |
| gps.location.isValid() / age()(有効か・経過時間) | 有効な位置があるか / 最後の更新からのミリ秒 |
| gps.speed.kmph()(速度) | km/hでの対地速度(mps()とknots()もある) |
| gps.course.deg()(進路) | 進む方向を度で(0 = 北、90 = 東) |
| gps.altitude.meters()(高度) | 海面からの高度(メートル) |
| gps.satellites.value()(衛星の数) | 位置に使った衛星の数 |
| gps.hdop.hdop()(HDOP) | 水平精度低下率(小さいほど良い) |
| gps.date.day() / gps.time.hour()(日付・時刻) | UTCの日付と時刻の項目 |
| TinyGPSPlus::distanceBetween(...) | 2つの座標の間の距離(メートル) |
| gps.failedChecksum() | 壊れたセンテンスの数。増え続けるならデータが失われている |
このコードで最も大切なルールは、loop()の中で決してdelay()を使わないことです。GPSは1秒に約500バイトを送りますが、SoftwareSerialの受信バッファは64バイトしかありません。長いdelay()の間にバッファがあふれ、センテンスが途中で切れ、TinyGPSPlusはそれをチェックサムのエラーで捨ててしまいます。そのため、millis()を使って1秒に1回表示しています。詳しくはmillis()とdelay()の記事をご覧ください。GPSの時刻はいつもUTCなので、タイムゾーンの差を足しましょう(たとえば冬の中央ヨーロッパ時間なら+1時間、トルコなら+3、日本なら+9)。Arduino Unoではdoubleは実際には32ビットのfloatなので、緯度と経度の最後の桁は約0.5メートル単位に丸められますが、モジュールの2.5 mの精度に比べれば問題になりません。
ESP32とSerial2:位置をOLEDディスプレイに表示する
ESP32ではSoftwareSerialは不要です。チップには3つのハードウェアUARTがあり、Serial2は好きなピンに割り当てられます。下の例では、GPSをGPIO 16(RX2)とGPIO 17(TX2)につないでいます。ESP32は3.3 Vで動くので、分圧回路も不要です。位置、速度、現地時刻は、GPIO 21(SDA)とGPIO 22(SCL)のI2Cにつないだ0.96インチのSSD1306 OLEDディスプレイに表示します。コードにはTinyGPSPlusのほかに、Adafruit SSD1306とAdafruit GFXのライブラリも必要です。
1#include <TinyGPSPlus.h>
2#include <Wire.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5
6#define GPS_RX 16 // ESP32 RX2 ← GPS TX
7#define GPS_TX 17 // ESP32 TX2 → GPS RX
8const int UTC_OFFSET = 3; // Türkiye UTC+3, kendi saat dilimini yaz / set your own time zone
9
10TinyGPSPlus gps;
11Adafruit_SSD1306 display(128, 64, &Wire, -1); // OLED: SDA 21, SCL 22
12unsigned long lastDraw = 0;
13
14String mapsLink() {
15 // Google Haritalar bağlantısı üret / build a Google Maps link
16 return "https://www.google.com/maps/search/?api=1&query=" +
17 String(gps.location.lat(), 6) + "," + String(gps.location.lng(), 6);
18}
19
20void drawScreen() {
21 display.clearDisplay();
22 display.setTextSize(1);
23 display.setCursor(0, 0);
24 display.print(F("Uydu/Sat: "));
25 display.print(gps.satellites.value());
26
27 if (gps.time.isValid()) {
28 int h = (gps.time.hour() + UTC_OFFSET + 24) % 24; // yerel saat / local time
29 char t[12];
30 snprintf(t, sizeof(t), "%02d:%02d:%02d", h, gps.time.minute(), gps.time.second());
31 display.setCursor(80, 0);
32 display.print(t);
33 }
34
35 if (gps.location.isValid()) {
36 display.setCursor(0, 16);
37 display.print(F("Lat: "));
38 display.println(gps.location.lat(), 6);
39 display.print(F("Lng: "));
40 display.println(gps.location.lng(), 6);
41 display.setTextSize(2); // hız büyük yazılsın / speed in large digits
42 display.setCursor(0, 44);
43 display.print(gps.speed.kmph(), 1);
44 display.print(F(" km/h"));
45 } else {
46 display.setCursor(0, 24);
47 display.println(F("Uydu araniyor..."));
48 display.println(F("Searching for fix"));
49 }
50 display.display();
51}
52
53void setup() {
54 Serial.begin(115200);
55 Serial2.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX); // donanım UART2 / hardware UART2
56 if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
57 Serial.println(F("OLED bulunamadi / OLED not found"));
58 while (true) delay(10);
59 }
60 display.setTextColor(SSD1306_WHITE);
61}
62
63void loop() {
64 while (Serial2.available()) {
65 gps.encode(Serial2.read()); // NMEA baytlarını çöz / decode NMEA bytes
66 }
67 if (millis() - lastDraw >= 1000) { // saniyede bir güncelle / refresh once per second
68 lastDraw = millis();
69 drawScreen();
70 if (gps.location.isValid()) {
71 Serial.println(mapsLink()); // Telegram ya da SMS ile gönderilebilir / can be sent via Telegram or SMS
72 }
73 }
74}UTC_OFFSETの変数でGPSの時刻を現地時刻に変えます。トルコなら3、中央ヨーロッパの冬時間なら1です(日本なら9)。mapsLink()関数は、位置をquery=緯度,経度のパラメーター付きのGoogleマップのアドレスに変えます。このリンクはシリアルモニタからコピーしてブラウザで開いたり、Telegramボットでスマートフォンに送ったり、SIM800L GSMモジュールでSMSにしたりできます。PSRAM付きのESP32-WROVERのボードでは、GPIO 16と17がメモリに使われるので、GPSはたとえばGPIO 26と27につなぎます。UARTが2つしかないESP32-C3では、Serial2の代わりにSerial1を使いましょう。
Raspberry Pi PicoとMicroPythonでGPSを読む
Raspberry Pi Picoでは、GPSをUART0につなぎます。モジュールのTXをGP1に、RXをGP0にします。MicroPythonでは、ライブラリを何もインストールせずにNMEAの行を自分で解析でき、センテンスの組み立てを学ぶよい練習にもなります。コードは各行のチェックサムを確認し、GGAから衛星の数を、RMCから位置、速度、時刻を取り出し、度と分を10進数の度に変換します。timeout=200の設定で、readline()は行の終わりを最大200 ms待ちます。起動時に半端な行がまぎれ込んでも、チェックサムの確認で捨てられます。センテンスの種類をf[0][3:]で取り出すので、複数の衛星システムを使うモジュールが送る$GNRMCや$GNGGAの行も認識します。Thonnyでのコードの動かし方がまだわからなければ、先にMicroPythonの基本の記事を見てみましょう。
1from machine import UART, Pin
2
3# GP0 (TX) → GPS RX, GP1 (RX) ← GPS TX
4gps = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1), timeout=200, rxbuf=1024)
5
6def checksum_ok(line):
7 # $ ile * arasındaki karakterlerin XOR'u / XOR of the characters between $ and *
8 try:
9 body, cs = line[1:].split('*')
10 x = 0
11 for ch in body:
12 x ^= ord(ch)
13 return x == int(cs[:2], 16)
14 except ValueError:
15 return False
16
17def to_deg(value, hemi):
18 # ddmm.mmmm → ondalık derece / decimal degrees
19 dot = value.index('.')
20 deg = int(value[:dot - 2]) + float(value[dot - 2:]) / 60
21 return -deg if hemi in ('S', 'W') else deg
22
23sats = '0'
24while True:
25 raw = gps.readline() # bir NMEA satırı / one NMEA line
26 if not raw:
27 continue
28 try:
29 line = raw.decode().strip()
30 except UnicodeError:
31 continue # bozuk bayt / corrupted byte
32 if not line.startswith('$') or not checksum_ok(line):
33 continue
34 f = line.split('*')[0].split(',')
35 kind = f[0][3:] # GPRMC ya da GNRMC → RMC / GPRMC or GNRMC → RMC
36 if kind == 'GGA':
37 sats = f[7] # kullanılan uydu sayısı / satellites in use
38 elif kind == 'RMC':
39 if f[2] != 'A':
40 print('Uydu araniyor / searching, uydu/sats:', sats)
41 continue
42 lat = to_deg(f[3], f[4])
43 lon = to_deg(f[5], f[6])
44 kmh = float(f[7] or 0) * 1.852 # knot → km/sa / knots → km/h
45 t = f[1]
46 print('UTC {}:{}:{} uydu/sats: {}'.format(t[0:2], t[2:4], t[4:6], sats))
47 print('Konum / position: {:.6f}, {:.6f} {:.1f} km/h'.format(lat, lon, kmh))
48 print('https://www.google.com/maps/search/?api=1&query={:.6f},{:.6f}'.format(lat, lon))NEO-6Mを使ったプロジェクトのアイデア
- 位置をSMSで送るトラッカー:NEO-6MとSIM800Lで、「LOCATION」というSMSにGoogleマップのリンクで答える、車や自転車のトラッカーを作ります。
- GPSデータロガー:ルートを毎秒CSVでSDカードモジュールに保存し、あとでパソコンで地図に描きます。
- 自転車のスピードメーター:現在の速度、最高速度、合計の距離をOLEDディスプレイに表示します。距離は、続く2点の間の距離をTinyGPSPlus::distanceBetween()で足していきます。
- 衛星に同期する時計:GPSから時刻を取り、インターネットなしでRTC時計モジュールを1秒単位で合わせます。
- ジオフェンスのアラーム:選んだ地点から100メートル以上離れると、ブザーを鳴らしたり通知を送ったりするシステムです。
- 長距離の追跡:LoRaモジュールで数キロ先の受信機に位置を送り、ESP32のディープスリープで電池のエネルギーを節約します。
データが来ない、測位できない:よくある問題と対処法
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| シリアルモニタがまったく空 | TXとRXが逆、共通GNDがない、またはモジュールに電源が来ていない | GPSのTXをボードのRXピン(UnoではD4)につなぎ、GNDをつなぎ、VCCを測る |
| 「No GPS detected: check wiring.」(GPSが見つからない) | 5秒の間に10文字もライブラリに届かなかった | 配線とコードのピン番号を確認し、TXとRXを入れ替えてみる |
| 文字化けした文字 | ボーレートの不一致 | GPSには9600を、シリアルモニタにはコードの速度を使う。だめなら4800と38400を試す |
| 行は来るが項目が空(,V,) | モジュールは動いているが、衛星を見つけていない | 屋外に出て、アンテナを空に向け、5〜15分待つ |
| 衛星の数が0〜3のまま | 信号が弱い、アンテナが逆さ、u.FLのコネクタがゆるい、または近くにノイズ源がある | アンテナを確認し、コネクタをしっかり差し込み、アンテナをESP32やレギュレーターから離す |
| 生のデータには位置があるのに、TinyGPSPlusはINVALID | loop()のdelay()でバッファがあふれる。failedChecksum()が増え続ける | delay()を消してmillis()を使い、encode()を絶えず呼ぶ |
| 時刻が数時間ずれている | GPSの時刻はUTC | タイムゾーンの差を足す |
| 止まっているのに位置が数メートル動き、速度が0にならない | GPSの普通のノイズと、建物に反射した信号 | 2 km/h未満の速度は0とみなし、HDOPの高い値は飛ばし、平均を取る |
| 電源を入れるたびにまた数分待つ | バックアップ電池が切れているか、ない | モジュールに数時間給電したままにして電池を充電する |
NEO-6Mは、「今どこにいて、どのくらいの速さで、何時か」という答えを、とても安くプロジェクトに加えてくれます。秘訣は、アンテナに空を見せることと、最初の測位に必要なだけ辛抱することです。シリアル通信をもっとよく理解するには、UARTの記事を見てみましょう。BlueGraysアプリには、2台のArduinoのシリアル通信、OLEDディスプレイ、microSDへのデータ記録、ESP32のディープスリープ、Telegram通知付きのアラームなど、GPSの工作にも役立つプロジェクトが、回路図とステップごとのコード付きで用意されています。
よくある質問
NEO-6M GPSモジュールは屋内で動きますか?
ふつうは動きません。衛星の信号は地上でとても弱く、コンクリートや金属の屋根がほとんどを遮ります。開けた窓の前ならいくつかの衛星をとらえられることもありますが、確実に測位するにはアンテナから空が直接見えている必要があります。屋内で位置が必要なら、屋根か窓に置いた外部のアクティブアンテナを使いましょう。
NEO-6Mの最初の測位にはどのくらいかかりますか?
データシートによると、コールドスタートは27秒、ホットスタートは1秒です。実際には、屋外で小さなセラミックアンテナを使うと、最初の測位にはふつう1〜5分かかり、初めての使用やバックアップ電池切れのときは10〜15分かかることもあります。電源を切ったばかりなら、数秒でまた測位します。
NEO-6M GPSモジュールの精度はどのくらいですか?
メーカーは、空が開けた場所での水平精度を2.5 m CEP、SBASの補正で2.0 mとしています。これは、値の半分が本当の地点から2.5 m以内にあるという意味です。街中では、建物に反射した信号で誤差が5〜10メートルに増えることがあります。高度は水平の位置より精度が低くなります。
NEO-6Mは5Vで動きますか?Arduinoに直接つなげますか?
一般的なGY-NEO6MV2のボードにはレギュレーターがあるので、VCCは5 Vで給電できます。ただし、チップのRXピンが耐えられるのは最大3.6 Vなので、Arduino Unoの5 VのTXの信号は1 kΩと2 kΩの分圧回路で下げましょう。モジュールのTX出力は3.3 Vで、Unoは問題なく読めます。
GPSモジュールにインターネットやSIMカードは必要ですか?
いいえ。NEO-6Mは衛星の信号から直接、無料で、インターネットなしで位置を計算します。インターネットやSIMカードが必要なのは、ESP32のWi-FiやSIM800LなどのGSMモジュールを使って、位置をどこかに送りたいときだけです。
NEO-6MとNEO-M8Nの違いは何ですか?
NEO-6MはGPSの衛星だけを使います。新しいNEO-M8Nは、GPSと同時にGLONASS、Galileo、BeiDouの衛星も使えます。見える衛星が多いので、街中でもより速く安定して測位できます。M8Nのセンテンスはふつう$GNで始まりますが、TinyGPSPlusはどちらも同じコードで読めます。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
2台のArduinoで一方向通信
2台のArduinoを1本のワイヤーで通信させます。一方のボタンを押すともう一方のLEDが点灯し、もう一度押すと消灯します。送信側のボードがボタンの押下を短いメッセージで伝え、受信側のボードはそのメッセージを受け取るとLEDの点灯と消灯を切り替えます。機器同士が情報を伝える仕組みを、最もシンプルな形で確かめられます。

2台のArduinoで双方向通信
2台のArduinoをケーブルでつないで通信します。片方のボタンを押すともう片方のライトが点灯し、もう一度押すと消灯します。両方のボードで同じプログラムを動かすので、どちらからでもメッセージを送受信できます。ボード同士でメッセージをやり取りする仕組みを、シンプルな構成で学べます。

ESP32温度データロガー(DHT22 + DS1307 RTC + microSD + OLED)
ESP32で日時付きの温度・湿度記録システムを作ります!DHT22で1分ごとに温度と湿度を読み取り、DS1307 RTCから取得した日時とともにmicroSDカードへCSV形式で記録します。OLEDには最新の温度を大きな文字で表示し、湿度のパーセント値、日時、総記録数も表示します。SDカードの/temp_log.csvファイルはExcel、Google Sheets、Pythonのpandasで開いて分析できます。温室の監視、家や部屋の温度管理、実験室のデータ収集、食品の保管環境や養蜂箱の監視に適しています。

ESP32 HTTPClient+天気API(SSD1306 OLED付き)
このプロジェクトでは、ESP32でHTTPClient.hを使い、インターネット経由で天気API(wttr.in)をREST APIとして呼び出します。前のI2C LCD版とは異なり、SSD1306 OLED(128×64)ディスプレイを使うため、大きな文字やベクターアイコンで、1つの画面に豊かな表現ができます。天気を表す7種類のベクターアイコン(晴れ、晴れ時々曇り、曇り、雨、雪、嵐、霧)を実行時に描画するので、ビットマップファイルは不要です。気温は中央に3倍の大きさの文字で表示します。

ESP32のディープスリープで節電(OLED + RTC RAMカウンター)
ESP32のディープスリープモードは、CPUとほとんどの周辺機器を停止し、消費電流を約10 µAまで抑えます。電池で動くIoT機器に欠かせない機能です。このプロジェクトでは、タイマー(15秒)、外部ピン(EXT0、ボタンはGPIO 33)、タッチ(T0/GPIO 4)の3種類の復帰方法を使います。RTC_DATA_ATTRを付けた変数はディープスリープ中も保持され、起動回数、タイマーによる復帰回数、ボタンによる復帰回数を継続して記録できます。復帰するたびにSSD1306 OLEDディスプレイに復帰理由と回数を表示します。

ESP32とNTPでインターネットから正確な時刻を取得
ESP32をWi-Fiに接続し、NTP(Network Time Protocol)サーバーからミリ秒精度の現在時刻を取得します。configTzTime()関数でタイムゾーンを自動適用し、夏時間を採用する国ではPOSIX TZ文字列で夏時間と標準時間の切り替えも自動管理します。時刻はシリアルモニタと、必要に応じて接続するI2C LCDに表示します。configTzTime()がSNTPサービスを開始し、ネットワークにアクセスできる間は定期的に再同期します。

ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)
PIRセンサーが動きを検知するとブザーとLEDを作動させ、同時にTelegramで持ち主に即座に通知するスマート防犯アラームです。アラームが繰り返される頻度を抑えるため、30秒のクールダウン(待ち時間)を設けています。この仕組みはPIRの検知が正しいかを確認したり、誤検知をなくしたりするものではありません。家の防犯、ガレージの監視、倉庫や作業部屋などに使えます。ESP32-CAMなら写真付きの通知を追加でき、通常のESP32 DevKit V1ではテキストで通知します。

ESP32とDS1307 RTCのデジタル時計(I2C + LCD)
このプロジェクトでは、DS1307リアルタイムクロック(RTC)モジュールを使って16x2 I2C LCDに時刻と日付を表示します。DS1307は電池でバックアップされるRTCで、ESP32の電源を切っても時刻を保持します。両方のモジュールが同じI2Cバス(SDA/SCL)を共有するため、2本の信号線だけで複数の機器を制御する方法を学べます。I2C通信プロトコルやRTCモジュールの動作原理、LCDの表示データを随時更新する方法を実践的に学ぶプロジェクトです。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

