Pad artikel ini, Anda akan mempelajari cara menghubungkan (interface) modul GPS NEO-6M dengan board ESP32 yang diprogram menggunakan MicroPython. Pembahasan mencakup cara membaca data GPS mentah (raw data) serta mengekstrak informasi penting seperti tanggal, waktu, dan posisi geografis saat ini, yang meliputi lintang (latitude), bujur (longitude), dan ketinggian (altitude).
Pengenalan Modul GPS NEO-6M
Modul GPS NEO-6M adalah penerima sinyal GPS yang kompatibel dengan berbagai board mikrokontroler. Modul ini mampu memperoleh data posisi geografis, kecepatan, ketinggian, serta informasi waktu secara akurat melalui sinyal satelit GPS.
Modul ini dilengkapi dengan baterai cadangan (backup battery), EEPROM eksternal, serta LED indikator status sinyal. LED tersebut akan mulai berkedip ketika modul berhasil memperoleh position fix dari satelit GPS.
Umumnya, modul GPS NEO-6M sudah disertai dengan antena GPS tipe keramik untuk meningkatkan penerimaan sinyal.
Namun, antena tersebut dapat diganti dengan antena lain yang kompatibel sesuai dengan kebutuhan proyek Anda. Sebagai contoh, penggunaan antena GPS eksternal yang bersifat tahan air (waterproof) dan dilengkapi kabel panjang memberikan fleksibilitas lebih dalam penempatan antena, terutama untuk aplikasi luar ruangan atau lingkungan dengan keterbatasan ruang pemasangan.
Modul GPS NEO-6M berkomunikasi dengan mikrokontroler menggunakan protokol komunikasi serial (UART).
Modul ini mengirimkan data dalam format kalimat standar NMEA (NMEA sentences). NMEA merupakan singkatan dari National Marine Electronics Association, yaitu standar format data yang digunakan secara luas oleh produsen perangkat GPS untuk menyampaikan informasi navigasi.
Fitur Modul GPS NEO-6M
Ringkasan Spesifikasi:
- Modul ini menggunakan antena eksternal dan dilengkapi EEPROM internal.
- Antarmuka komunikasi: RS232 level TTL (UART).
- Tegangan catu daya: 3 V hingga 5 V.
- Baud rate bawaan (default): 9600 bps.
- Menggunakan format data standar NMEA (NMEA sentences).
Pengkabelan Modul GPS NEO-6M ke ESP32
Modul GPS NEO-6M akan dihubungkan ke board ESP32 menggunakan pin UART2 bawaan. Anda dapat menggunakan ilustrasi dan tabel berikut sebagai referensi dalam proses pengkabelan.
Membaca Data GPS Mentah – Pengujian Modul GPS NEO-6M dengan ESP32 (MicroPython)
Untuk memperoleh data GPS mentah (raw GPS data), cukup dengan menginisialisasi komunikasi serial antara ESP32 dan modul GPS, kemudian membaca data yang tersedia dari modul tersebut.
Kode berikut digunakan untuk menginisialisasi komunikasi serial dengan modul GPS serta membaca data yang dikirimkan oleh modul tersebut.
import machine
from time import sleep
# Define the UART pins and create a UART object
gps_serial = machine.UART(2, baudrate=9600, tx=17, rx=16)
while True:
if gps_serial.any():
line = gps_serial.readline() # Read a complete line from the UART
if line:
line = line.decode('utf-8')
print(line.strip())
sleep(0.5)
Pengujian Program
Setelah koneksi dengan board berhasil dilakukan melalui Thonny IDE, jalankan kode program yang telah dibuat sebelumnya.
Pastikan antena GPS terhubung dengan benar dan modul atau antena ditempatkan di area terbuka atau dekat jendela agar dapat menerima sinyal dari satelit GPS secara optimal.
LED biru pada modul akan mulai berkedip ketika modul berhasil memperoleh position fix. Pada penggunaan pertama, proses ini dapat memerlukan waktu beberapa menit hingga sinyal satelit terkunci dengan baik.
Shell akan menampilkan kalimat NMEA (NMEA sentences) yang berisi data GPS dari modul.
Setiap baris data yang ditampilkan pada serial monitor merupakan satu kalimat NMEA (NMEA sentence).
NMEA merupakan singkatan dari National Marine Electronics Association, yaitu standar format data yang digunakan secara luas oleh produsen perangkat GPS untuk menyampaikan informasi navigasi.
Struktur Kalimat NMEA
Kalimat NMEA diawali dengan karakter `$`, dan setiap field data di dalamnya dipisahkan oleh tanda koma (,).
$GPRMC,110827.00,A,4107.32485,N,00831.79799,W,0.888,30.44,180724,,,A*4B
$GPVTG,30.44,T,,M,0.888,N,1.644,K,A*01
$GPGGA,110827.00,41XX.32485,N,00831.79799,W,1,07,0.99,123.1,M,50.1,M,,*48
$GPGSA,A,3,03,32,22,08,04,14,17,,,,,,2.25,0.99,2.02*0A
$GPGSV,3,1,11,3,11,22,26,296,29,27,01,142,,32,17,042,23*48
$GPGLL,4107.32485,N,00831.79799,W,110827.00,A,A*7F
Terdapat berbagai jenis kalimat NMEA, di mana tipe pesan ditentukan oleh karakter yang muncul sebelum tanda koma pertama.
Karakter GP setelah simbol `$` menunjukkan bahwa data tersebut berasal dari sistem GPS. Kalimat $GPGGA merupakan salah satu pesan NMEA utama yang menyediakan informasi posisi tiga dimensi (3D) beserta data akurasi.
Pada contoh kalimat berikut:
$GPGGA,110827.00,41XX.32485,N,008XX.XXXXX,W,1,07,0.99,123.1,M,50.1,M,,*48
- 110827 – Menunjukkan waktu pengambilan data position fix, yaitu pukul 11:08:27 UTC.
- 41XX.32845,N – Menyatakan koordinat lintang (latitude) 41 derajat XX.32845 menit Lintang Utara.
- 00831.79799,W – Menyatakan koordinat bujur (longitude) 8 derajat XX.XXXXX menit Bujur Barat.
- 1 – Kualitas fix posisi
(0 = tidak valid; 1 = GPS fix; 2 = DGPS fix; 3 = PPS fix; 4 = Real-Time Kinematic; 5 = Float RTK; 6 = estimasi (dead reckoning); 7 = mode input manual; 8 = mode simulasi).
- 07 – Jumlah satelit yang sedang dilacak.
- 0.99 – Horizontal Dilution of Precision (HDOP); nilai di bawah 1 menunjukkan tingkat akurasi yang sangat baik.
- 123.1, M – Ketinggian (altitude) dalam meter di atas permukaan laut.
- 50.1, M – Tinggi geoid (permukaan laut rata-rata) terhadap elipsoid referensi WGS84.
- Field kosong – Waktu (dalam detik) sejak pembaruan DGPS terakhir.
- Field kosong – ID stasiun DGPS.
- *48 – Data checksum untuk verifikasi integritas kalimat NMEA, selalu diawali dengan karakter `*`.
Kalimat NMEA lainnya menyediakan informasi tambahan, antara lain:
- $GPGSA – Informasi Dilution of Precision (DOP) dan satelit yang aktif.
- $GPGSV – Informasi detail mengenai satelit GPS yang terlihat.
- $GPGLL – Data koordinat lintang dan bujur geografis.
- $GPRMC – Data utama GPS yang mencakup posisi, kecepatan, dan waktu (position, velocity, time).
- $GPVTG – Informasi kecepatan pergerakan (velocity made good).
Untuk membantu memahami isi kalimat NMEA, Anda dapat menggunakan online NMEA analyzer dengan menempelkan (paste) data NMEA yang diperoleh ke dalam alat tersebut.
Namun, cara paling praktis untuk memperoleh dan mengolah data GPS sesuai kebutuhan adalah dengan melakukan parsing kalimat NMEA secara langsung di dalam kode program. Untuk tujuan ini, kita akan menggunakan modul micropyGPS.
Mengunggah Modul micropyGPS
Agar proses parsing kalimat NMEA dari modul GPS menjadi lebih mudah, kita akan memanfaatkan library micropyGPS. Library ini tidak termasuk dalam pustaka bawaan MicroPython, sehingga Anda perlu mengunggah file berikut ke board ESP32 dan menyimpannya dengan nama micropyGPS.py.
# TODO:
# Time Since First Fix
# Distance/Time to Target
# More Helper Functions
# Dynamically limit sentences types to parse
from math import floor, modf
# Import utime or time for fix time handling
try:
# Assume running on MicroPython
import utime
except ImportError:
# Otherwise default to time module for non-embedded implementations
# Should still support millisecond resolution.
import time
class MicropyGPS(object):
"""GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics.
Parses sentences one character at a time using update(). """
# Max Number of Characters a valid sentence can be (based on GGA sentence)
SENTENCE_LIMIT = 90
__HEMISPHERES = ('N', 'S', 'E', 'W')
__NO_FIX = 1
__FIX_2D = 2
__FIX_3D = 3
__DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W',
'WNW', 'NW', 'NNW')
__MONTHS = ('January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September', 'October',
'November', 'December')
def __init__(self, local_offset=0, location_formatting='ddm'):
"""
Setup GPS Object Status Flags, Internal Data Registers, etc
local_offset (int): Timzone Difference to UTC
location_formatting (str): Style For Presenting Longitude/Latitude:
Decimal Degree Minute (ddm) - 40° 26.767′ N
Degrees Minutes Seconds (dms) - 40° 26′ 46″ N
Decimal Degrees (dd) - 40.446° N
"""
#####################
# Object Status Flags
self.sentence_active = False
self.active_segment = 0
self.process_crc = False
self.gps_segments = []
self.crc_xor = 0
self.char_count = 0
self.fix_time = 0
#####################
# Sentence Statistics
self.crc_fails = 0
self.clean_sentences = 0
self.parsed_sentences = 0
#####################
# Logging Related
self.log_handle = None
self.log_en = False
#####################
# Data From Sentences
# Time
self.timestamp = [0, 0, 0.0]
self.date = [0, 0, 0]
self.local_offset = local_offset
# Position/Motion
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.coord_format = location_formatting
self.speed = [0.0, 0.0, 0.0]
self.course = 0.0
self.altitude = 0.0
self.geoid_height = 0.0
# GPS Info
self.satellites_in_view = 0
self.satellites_in_use = 0
self.satellites_used = []
self.last_sv_sentence = 0
self.total_sv_sentences = 0
self.satellite_data = dict()
self.hdop = 0.0
self.pdop = 0.0
self.vdop = 0.0
self.valid = False
self.fix_stat = 0
self.fix_type = 1
########################################
# Coordinates Translation Functions
########################################
@property
def latitude(self):
"""Format Latitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._latitude[0] + (self._latitude[1] / 60)
return [decimal_degrees, self._latitude[2]]
elif self.coord_format == 'dms':
minute_parts = modf(self._latitude[1])
seconds = round(minute_parts[0] * 60)
return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]]
else:
return self._latitude
@property
def longitude(self):
"""Format Longitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._longitude[0] + (self._longitude[1] / 60)
return [decimal_degrees, self._longitude[2]]
elif self.coord_format == 'dms':
minute_parts = modf(self._longitude[1])
seconds = round(minute_parts[0] * 60)
return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]]
else:
return self._longitude
########################################
# Logging Related Functions
########################################
def start_logging(self, target_file, mode="append"):
"""
Create GPS data log object
"""
# Set Write Mode Overwrite or Append
mode_code = 'w' if mode == 'new' else 'a'
try:
self.log_handle = open(target_file, mode_code)
except AttributeError:
print("Invalid FileName")
return False
self.log_en = True
return True
def stop_logging(self):
"""
Closes the log file handler and disables further logging
"""
try:
self.log_handle.close()
except AttributeError:
print("Invalid Handle")
return False
self.log_en = False
return True
def write_log(self, log_string):
"""Attempts to write the last valid NMEA sentence character to the active file handler
"""
try:
self.log_handle.write(log_string)
except TypeError:
return False
return True
########################################
# Sentence Parsers
########################################
def gprmc(self):
"""Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence.
Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status
"""
# UTC Timestamp
try:
utc_string = self.gps_segments[1]
if utc_string: # Possible timestamp found
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
self.timestamp = [hours, minutes, seconds]
else: # No Time stamp yet
self.timestamp = [0, 0, 0.0]
except ValueError: # Bad Timestamp value present
return False
# Date stamp
try:
date_string = self.gps_segments[9]
# Date string printer function assumes to be year >=2000,
# date_string() must be supplied with the correct century argument to display correctly
if date_string: # Possible date stamp found
day = int(date_string[0:2])
month = int(date_string[2:4])
year = int(date_string[4:6])
self.date = (day, month, year)
else: # No Date stamp yet
self.date = (0, 0, 0)
except ValueError: # Bad Date stamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments[2] == 'A': # Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[3]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[4]
# Longitude
l_string = self.gps_segments[5]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[6]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Speed
try:
spd_knt = float(self.gps_segments[7])
except ValueError:
return False
# Course
try:
if self.gps_segments[8]:
course = float(self.gps_segments[8])
else:
course = 0.0
except ValueError:
return False
# TODO - Add Magnetic Variation
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
# Include mph and hm/h
self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852]
self.course = course
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else: # Clear Position Data if Sentence is 'Invalid'
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.speed = [0.0, 0.0, 0.0]
self.course = 0.0
self.valid = False
return True
def gpgll(self):
"""Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude,
longitude, and fix status"""
# UTC Timestamp
try:
utc_string = self.gps_segments[5]
if utc_string: # Possible timestamp found
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
self.timestamp = [hours, minutes, seconds]
else: # No Time stamp yet
self.timestamp = [0, 0, 0.0]
except ValueError: # Bad Timestamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments[6] == 'A': # Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[1]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[2]
# Longitude
l_string = self.gps_segments[3]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[4]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else: # Clear Position Data if Sentence is 'Invalid'
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.valid = False
return True
def gpvtg(self):
"""Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course"""
try:
course = float(self.gps_segments[1]) if self.gps_segments[1] else 0.0
spd_knt = float(self.gps_segments[5]) if self.gps_segments[5] else 0.0
except ValueError:
return False
# Include mph and km/h
self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852)
self.course = course
return True
def gpgga(self):
"""Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude,
fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status"""
try:
# UTC Timestamp
utc_string = self.gps_segments[1]
# Skip timestamp if receiver doesn't have on yet
if utc_string:
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
else:
hours = 0
minutes = 0
seconds = 0.0
# Number of Satellites in Use
satellites_in_use = int(self.gps_segments[7])
# Get Fix Status
fix_stat = int(self.gps_segments[6])
except (ValueError, IndexError):
return False
try:
# Horizontal Dilution of Precision
hdop = float(self.gps_segments[8])
except (ValueError, IndexError):
hdop = 0.0
# Process Location and Speed Data if Fix is GOOD
if fix_stat:
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[2]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[3]
# Longitude
l_string = self.gps_segments[4]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[5]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Altitude / Height Above Geoid
try:
altitude = float(self.gps_segments[9])
geoid_height = float(self.gps_segments[11])
except ValueError:
altitude = 0
geoid_height = 0
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
self.altitude = altitude
self.geoid_height = geoid_height
# Update Object Data
self.timestamp = [hours, minutes, seconds]
self.satellites_in_use = satellites_in_use
self.hdop = hdop
self.fix_stat = fix_stat
# If Fix is GOOD, update fix timestamp
if fix_stat:
self.new_fix_time()
return True
def gpgsa(self):
"""Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in
fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical
Dilution of Precision, and fix status"""
# Fix Type (None,2D or 3D)
try:
fix_type = int(self.gps_segments[2])
except ValueError:
return False
# Read All (up to 12) Available PRN Satellite Numbers
sats_used = []
for sats in range(12):
sat_number_str = self.gps_segments[3 + sats]
if sat_number_str:
try:
sat_number = int(sat_number_str)
sats_used.append(sat_number)
except ValueError:
return False
else:
break
# PDOP,HDOP,VDOP
try:
pdop = float(self.gps_segments[15])
hdop = float(self.gps_segments[16])
vdop = float(self.gps_segments[17])
except ValueError:
return False
# Update Object Data
self.fix_type = fix_type
# If Fix is GOOD, update fix timestamp
if fix_type > self.__NO_FIX:
self.new_fix_time()
self.satellites_used = sats_used
self.hdop = hdop
self.vdop = vdop
self.pdop = pdop
return True
def gpgsv(self):
"""Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence
parsed, and data on each satellite present in the sentence"""
try:
num_sv_sentences = int(self.gps_segments[1])
current_sv_sentence = int(self.gps_segments[2])
sats_in_view = int(self.gps_segments[3])
except ValueError:
return False
# Create a blank dict to store all the satellite data from this sentence in:
# satellite PRN is key, tuple containing telemetry is value
satellite_dict = dict()
# Calculate Number of Satelites to pull data for and thus how many segment positions to read
if num_sv_sentences == current_sv_sentence:
# Last sentence may have 1-4 satellites; 5 - 20 positions
sat_segment_limit = (sats_in_view - ((num_sv_sentences - 1) * 4)) * 5
else:
sat_segment_limit = 20 # Non-last sentences have 4 satellites and thus read up to position 20
# Try to recover data for up to 4 satellites in sentence
for sats in range(4, sat_segment_limit, 4):
# If a PRN is present, grab satellite data
if self.gps_segments[sats]:
try:
sat_id = int(self.gps_segments[sats])
except (ValueError,IndexError):
return False
try: # elevation can be null (no value) when not tracking
elevation = int(self.gps_segments[sats+1])
except (ValueError,IndexError):
elevation = None
try: # azimuth can be null (no value) when not tracking
azimuth = int(self.gps_segments[sats+2])
except (ValueError,IndexError):
azimuth = None
try: # SNR can be null (no value) when not tracking
snr = int(self.gps_segments[sats+3])
except (ValueError,IndexError):
snr = None
# If no PRN is found, then the sentence has no more satellites to read
else:
break
# Add Satellite Data to Sentence Dict
satellite_dict[sat_id] = (elevation, azimuth, snr)
# Update Object Data
self.total_sv_sentences = num_sv_sentences
self.last_sv_sentence = current_sv_sentence
self.satellites_in_view = sats_in_view
# For a new set of sentences, we either clear out the existing sat data or
# update it as additional SV sentences are parsed
if current_sv_sentence == 1:
self.satellite_data = satellite_dict
else:
self.satellite_data.update(satellite_dict)
return True
##########################################
# Data Stream Handler Functions
##########################################
def new_sentence(self):
"""Adjust Object Flags in Preparation for a New Sentence"""
self.gps_segments = ['']
self.active_segment = 0
self.crc_xor = 0
self.sentence_active = True
self.process_crc = True
self.char_count = 0
def update(self, new_char):
"""Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*')
Function builds a list of received string that are validate by CRC prior to parsing by the appropriate
sentence function. Returns sentence type on successful parse, None otherwise"""
valid_sentence = False
# Validate new_char is a printable char
ascii_char = ord(new_char)
if 10 <= ascii_char <= 126:
self.char_count += 1
# Write Character to log file if enabled
if self.log_en:
self.write_log(new_char)
# Check if a new string is starting ($)
if new_char == '$':
self.new_sentence()
return None
elif self.sentence_active:
# Check if sentence is ending (*)
if new_char == '*':
self.process_crc = False
self.active_segment += 1
self.gps_segments.append('')
return None
# Check if a section is ended (,), Create a new substring to feed
# characters to
elif new_char == ',':
self.active_segment += 1
self.gps_segments.append('')
# Store All Other printable character and check CRC when ready
else:
self.gps_segments[self.active_segment] += new_char
# When CRC input is disabled, sentence is nearly complete
if not self.process_crc:
if len(self.gps_segments[self.active_segment]) == 2:
try:
final_crc = int(self.gps_segments[self.active_segment], 16)
if self.crc_xor == final_crc:
valid_sentence = True
else:
self.crc_fails += 1
except ValueError:
pass # CRC Value was deformed and could not have been correct
# Update CRC
if self.process_crc:
self.crc_xor ^= ascii_char
# If a Valid Sentence Was received and it's a supported sentence, then parse it!!
if valid_sentence:
self.clean_sentences += 1 # Increment clean sentences received
self.sentence_active = False # Clear Active Processing Flag
if self.gps_segments[0] in self.supported_sentences:
# parse the Sentence Based on the message type, return True if parse is clean
if self.supported_sentences[self.gps_segments[0]](self):
# Let host know that the GPS object was updated by returning parsed sentence type
self.parsed_sentences += 1
return self.gps_segments[0]
# Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete
if self.char_count > self.SENTENCE_LIMIT:
self.sentence_active = False
# Tell Host no new sentence was parsed
return None
def new_fix_time(self):
"""Updates a high resolution counter with current time when fix is updated. Currently only triggered from
GGA, GSA and RMC sentences"""
try:
self.fix_time = utime.ticks_ms()
except NameError:
self.fix_time = time.time()
#########################################
# User Helper Functions
# These functions make working with the GPS object data easier
#########################################
def satellite_data_updated(self):
"""
Checks if the all the GSV sentences in a group have been read, making satellite data complete
:return: boolean
"""
if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence:
return True
else:
return False
def unset_satellite_data_updated(self):
"""
Mark GSV sentences as read indicating the data has been used and future updates are fresh
"""
self.last_sv_sentence = 0
def satellites_visible(self):
"""
Returns a list of of the satellite PRNs currently visible to the receiver
:return: list
"""
return list(self.satellite_data.keys())
def time_since_fix(self):
"""Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if
no fix has been found"""
# Test if a Fix has been found
if self.fix_time == 0:
return -1
# Try calculating fix time using utime; if not running MicroPython
# time.time() returns a floating point value in secs
try:
current = utime.ticks_diff(utime.ticks_ms(), self.fix_time)
except NameError:
current = (time.time() - self.fix_time) * 1000 # ms
return current
def compass_direction(self):
"""
Determine a cardinal or inter-cardinal direction based on current course.
:return: string
"""
# Calculate the offset for a rotated compass
if self.course >= 348.75:
offset_course = 360 - self.course
else:
offset_course = self.course + 11.25
# Each compass point is separated by 22.5 degrees, divide to find lookup value
dir_index = floor(offset_course / 22.5)
final_dir = self.__DIRECTIONS[dir_index]
return final_dir
def latitude_string(self):
"""
Create a readable string of the current latitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude[0]) + '° ' + str(self._latitude[2])
elif self.coord_format == 'dms':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude[0]) + '° ' + str(formatted_latitude[1]) + "' " + str(formatted_latitude[2]) + '" ' + str(formatted_latitude[3])
else:
lat_string = str(self._latitude[0]) + '° ' + str(self._latitude[1]) + "' " + str(self._latitude[2])
return lat_string
def longitude_string(self):
"""
Create a readable string of the current longitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude[0]) + '° ' + str(self._longitude[2])
elif self.coord_format == 'dms':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude[0]) + '° ' + str(formatted_longitude[1]) + "' " + str(formatted_longitude[2]) + '" ' + str(formatted_longitude[3])
else:
lon_string = str(self._longitude[0]) + '° ' + str(self._longitude[1]) + "' " + str(self._longitude[2])
return lon_string
def speed_string(self, unit='kph'):
"""
Creates a readable string of the current speed data in one of three units
:param unit: string of 'kph','mph, or 'knot'
:return:
"""
if unit == 'mph':
speed_string = str(self.speed[1]) + ' mph'
elif unit == 'knot':
if self.speed[0] == 1:
unit_str = ' knot'
else:
unit_str = ' knots'
speed_string = str(self.speed[0]) + unit_str
else:
speed_string = str(self.speed[2]) + ' km/h'
return speed_string
def date_string(self, formatting='s_mdy', century='20'):
"""
Creates a readable string of the current date.
Can select between long format: Januray 1st, 2014
or two short formats:
11/01/2014 (MM/DD/YYYY)
01/11/2014 (DD/MM/YYYY)
:param formatting: string 's_mdy', 's_dmy', or 'long'
:param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX)
:return: date_string string with long or short format date
"""
# Long Format Januray 1st, 2014
if formatting == 'long':
# Retrieve Month string from private set
month = self.__MONTHS[self.date[1] - 1]
# Determine Date Suffix
if self.date[0] in (1, 21, 31):
suffix = 'st'
elif self.date[0] in (2, 22):
suffix = 'nd'
elif self.date[0] == (3, 23):
suffix = 'rd'
else:
suffix = 'th'
day = str(self.date[0]) + suffix # Create Day String
year = century + str(self.date[2]) # Create Year String
date_string = month + ' ' + day + ', ' + year # Put it all together
else:
# Add leading zeros to day string if necessary
if self.date[0] < 10:
day = '0' + str(self.date[0])
else:
day = str(self.date[0])
# Add leading zeros to month string if necessary
if self.date[1] < 10:
month = '0' + str(self.date[1])
else:
month = str(self.date[1])
# Add leading zeros to year string if necessary
if self.date[2] < 10:
year = '0' + str(self.date[2])
else:
year = str(self.date[2])
# Build final string based on desired formatting
if formatting == 's_dmy':
date_string = day + '/' + month + '/' + year
else: # Default date format
date_string = month + '/' + day + '/' + year
return date_string
# All the currently supported NMEA sentences
supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc,
'GPGGA': gpgga, 'GLGGA': gpgga,
'GPVTG': gpvtg, 'GLVTG': gpvtg,
'GPGSA': gpgsa, 'GLGSA': gpgsa,
'GPGSV': gpgsv, 'GLGSV': gpgsv,
'GPGLL': gpgll, 'GLGLL': gpgll,
'GNGGA': gpgga, 'GNRMC': gprmc,
'GNVTG': gpvtg, 'GNGLL': gpgll,
'GNGSA': gpgsa,
}
if __name__ == "__main__":
pass
Terlepas dari IDE yang digunakan, berikut adalah langkah-langkah umum untuk mengunggah library micropyGPS ke board Anda:
1. Pastikan board telah menjalankan firmware MicroPython. Silakan periksa kembali bagian Prerequisites untuk memastikan firmware sudah terpasang dengan benar.
2. Buat file baru pada IDE yang digunakan dengan nama micropyGPS.py, lalu salin dan tempelkan kode library ke dalam file tersebut, kemudian simpan.
3. Hubungkan board ke komputer dan lakukan komunikasi serial melalui IDE.
4. Unggah file micropyGPS.py ke board ESP32.
5. Jika proses unggah berhasil, library micropyGPS sudah siap digunakan. Selanjutnya, Anda dapat memanfaatkan fungsi-fungsi yang tersedia dengan mengimpor library tersebut ke dalam kode program menggunakan perintah `import micropyGPS`.
ESP32 dengan NEO-6M: Membaca Data GPS Menggunakan MicroPython
Library micropyGPS mempermudah proses pengambilan dan pengolahan data GPS dengan menyajikannya dalam format yang lebih terstruktur dan mudah dipahami.
Contoh kode berikut menunjukkan cara menggunakan library ini untuk memperoleh berbagai informasi GPS, seperti koordinat lintang dan bujur, ketinggian (altitude), tanggal dan waktu, jumlah satelit yang terdeteksi, serta nilai HDOP sebagai indikator tingkat akurasi sinyal.
Setelah library micropyGPS berhasil diimpor ke board ESP32, Anda dapat menjalankan kode program berikut.
import machine
from time import sleep
from micropyGPS import MicropyGPS
# Instantiate the micropyGPS object
my_gps = MicropyGPS()
# Define the UART pins and create a UART object
gps_serial = machine.UART(2, baudrate=9600, tx=17, rx=16)
while True:
try:
while gps_serial.any():
data = gps_serial.read()
for byte in data:
stat = my_gps.update(chr(byte))
if stat is not None:
# Print parsed GPS data
print('UTC Timestamp:', my_gps.timestamp)
print('Date:', my_gps.date_string('long'))
print('Latitude:', my_gps.latitude_string())
print('Longitude:', my_gps.longitude_string())
print('Altitude:', my_gps.altitude)
print('Satellites in use:', my_gps.satellites_in_use)
print('Horizontal Dilution of Precision:', my_gps.hdop)
print()
except Exception as e:
print(f"An error occurred: {e}")
Cara Kerja Program
Silakan lanjutkan membaca bagian ini untuk memahami alur kerja program, atau langsung menuju bagian demonstrasi jika ingin melihat hasil eksekusinya.
Langkah pertama adalah mengimpor modul-modul yang diperlukan, termasuk kelas MicropyGPS dari modul micropyGPS yang telah diunggah sebelumnya ke board ESP32.
import machine
from time import sleep
from micropyGPS import MicropyGPS
Buat sebuah instance dari kelas MicropyGPS dengan nama my_gps.
# Instantiate the micropyGPS object
my_gps = MicropyGPS()
Selanjutnya, lakukan inisialisasi antarmuka UART untuk komunikasi serial dengan modul GPS. Pada konfigurasi ini, GPIO 17 digunakan sebagai pin TX dan GPIO 16 sebagai pin RX. Selain itu, baud rate komunikasi ditetapkan sebesar 9600 bps, sesuai dengan baud rate bawaan modul GPS NEO-6M.
# Define the UART pins and create a UART object
gps_serial = machine.UART(2, baudrate=9600, tx=17, rx=16)
Selanjutnya, dibuat sebuah loop tak hingga (infinite loop) untuk membaca data GPS secara kontinu.
Program akan memeriksa apakah terdapat data baru yang tersedia pada antarmuka UART. Jika data tersedia, data tersebut dibaca dan diteruskan ke instance my_gps menggunakan metode `update()` untuk diproses lebih lanjut.
while gps_serial.any():
data = gps_serial.read()
for byte in data:
stat = my_gps.update(chr(byte))
Metode `update()` akan mengembalikan kalimat GPS yang valid, atau nilai None jika data yang diterima belum valid. Oleh karena itu, dilakukan pengecekan terlebih dahulu untuk memastikan data GPS valid sebelum melanjutkan proses berikutnya.
if stat is not None:
Selanjutnya, data GPS dapat diakses melalui berbagai metode yang disediakan oleh library micropyGPS pada objek my_gps, yang telah berisi data hasil pembacaan dari modul GPS.
Baris kode berikut menunjukkan cara memperoleh informasi waktu, tanggal, koordinat lintang dan bujur, ketinggian (altitude), jumlah satelit yang digunakan, serta nilai HDOP.
# Print parsed GPS data
print('UTC Timestamp:', my_gps.timestamp)
print('Date:', my_gps.date_string('long'))
print('Latitude:', my_gps.latitude_string())
print('Longitude:', my_gps.longitude_string())
print('Altitude:', my_gps.altitude)
print('Satellites in use:', my_gps.satellites_in_use)
print('Horizontal Dilution of Precision:', my_gps.hdop)
print()
Library micropyGPS menyediakan berbagai metode tambahan untuk memperoleh data GPS yang lebih lengkap serta dalam format yang berbeda. Untuk mengetahui seluruh fitur yang tersedia, disarankan untuk mempelajari dokumentasi resmi library tersebut.
Demonstrasi
Setelah modul micropyGPS berhasil diunggah ke board ESP32, Anda dapat menjalankan kode program sebelumnya untuk menampilkan dan memantau data GPS.
Pastikan board ESP32 atau antena GPS ditempatkan di dekat jendela atau, idealnya, di area terbuka agar dapat menerima sinyal dari satelit GPS secara optimal. Proses perolehan position fix dapat memerlukan waktu beberapa menit, terutama pada penggunaan awal, hingga modul mampu mengirimkan data yang valid. LED biru pada modul GPS NEO-6M akan mulai berkedip ketika modul telah siap digunakan.
Pada MicroPython shell, akan ditampilkan informasi posisi geografis saat ini, tanggal dan waktu dalam format UTC, jumlah satelit yang digunakan, serta nilai HDOP. Semakin banyak satelit yang terdeteksi dan semakin kecil nilai HDOP, maka tingkat akurasi data GPS yang diperoleh akan semakin baik.
Siap Untuk Membuat Proyek Impianmu Menjadi Kenyataan?
Klik di sini untuk chat langsung via WhatsApp dan dapatkan dukungan langsung dari tim ahli kami!













0 on: "Cara Menggunakan Modul GPS NEO-6M dengan ESP32 Menggunakan MicroPython"