Raspberry Pi Pico: GPS-логгер NEO-M8N и отображение маршрута в Google Earth (MicroPython)
Руководство по началу работы с GPS-модулем NEO-M8N и Raspberry Pi Pico. Научитесь подключать модуль к плате Pico и получать данные о местоположении и времени. Мы также создадим проект GPS-логгера с microSD-картой для записи GPS-координат с течением времени. Затем вы узнаете, как использовать эти GPS-данные в Google Earth для отображения маршрута.
В этом руководстве вы научитесь следующему:
Подключать GPS-модуль NEO-M8N к Raspberry Pi Pico по последовательному интерфейсу;
Получать необработанные (raw) GPS-данные;
Парсить необработанные данные для получения выбранной и читаемой GPS-информации;
Получать текущее местоположение;
Записывать местоположение в файл на microSD-карте;
Преобразовывать данные в файл .kml, который может читать Google Earth;
Загружать файл .kml в Google Earth для отображения маршрута.
Предварительные требования — Прошивка MicroPython
Для работы с этим руководством вам необходима прошивка MicroPython, установленная на вашей плате Raspberry Pi Pico. Также вам понадобится IDE для написания и загрузки кода на плату.
Рекомендуемая IDE для MicroPython на Raspberry Pi Pico — Thonny IDE. Следуйте следующему руководству, чтобы узнать, как установить Thonny IDE, прошить прошивку MicroPython и загрузить код на плату.
Знакомство с GPS-модулем NEO-M8N
GPS-модуль NEO-M8N — один из самых популярных GPS-приёмников, используемых с микроконтроллерами в проектах навигации и отслеживания. Он может получать данные о широте, долготе, высоте и времени.
Модуль поддерживает несколько спутниковых систем, включая GPS, Galileo, ГЛОНАСС и BeiDou. Он обеспечивает лучшее отслеживание спутников по сравнению с NEO-6M, что делает его более надёжным в сложных условиях.
Согласно даташиту, модуль имеет точность определения горизонтального положения от 2,5 до 4 метров и быстрое время запуска (1 секунда для горячего старта, 26–57 секунд для холодного старта — ожидайте более длительное время, если вы находитесь рядом со зданиями).
Модуль включает резервную батарею, встроенную EEPROM и светодиодный индикатор, который мигает при получении фиксации положения.
Этот модуль обычно поставляется с керамической GPS-антенной. Но вы можете заменить её на любую другую совместимую антенну, которая лучше подходит для вашего проекта. Например, мне нравится использовать ту, что справа на фото ниже, потому что она водонепроницаема, и антенна поставляется с длинным кабелем, что обеспечивает большую гибкость.
GPS-модуль NEO-M8N взаимодействует с микроконтроллером по протоколу последовательной связи и работает со стандартными NMEA-предложениями. NMEA расшифровывается как National Marine Electronics Association (Национальная ассоциация морской электроники), и в мире GPS это стандартный формат данных, поддерживаемый производителями GPS.
Подключение GPS-модуля NEO-M8N к Raspberry Pi Pico
Для получения данных от GPS-модуля NEO-M8N нам нужно установить последовательную связь. Raspberry Pi Pico имеет следующие варианты выводов UART:
Интерфейс UART |
TX GPIO |
RX GPIO |
|---|---|---|
UART0 |
GPIO0, GPIO12, GPIO16 |
GPIO1, GPIO13, GPIO17 |
UART1 |
GPIO4, GPIO8 |
GPIO5, GPIO9 |
Мы будем использовать UART 1 и GPIO 4 (TX) и GPIO 5 (RX).
GPS-модуль NEO-M8N |
Raspberry Pi Pico |
|---|---|
VCC |
3V3 |
RX |
TX (GPIO 4) (Pin 6) |
TX |
RX (GPIO 5) (Pin 7) |
GND |
GND |
Получение необработанных GPS-данных — Тестирование GPS-модуля NEO-M8N с Raspberry Pi Pico (MicroPython)
Для получения необработанных GPS-данных достаточно установить последовательную связь с GPS-модулем и прочитать доступные данные.
Следующий код устанавливает последовательную связь с GPS-модулем и считывает доступные данные.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-neo-m8n-gps-micropython/
import machine
from time import sleep
# Define the UART pins and create a UART object
gps_serial = machine.UART(1, baudrate=9600, tx=4, rx=5)
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)
Тестирование кода
После установления соединения с платой через Thonny IDE запустите предыдущий код.
Убедитесь, что антенна подключена и что модуль или антенна размещены снаружи или рядом с окном, чтобы получать данные со спутников.
Синий светодиод модуля начнёт мигать, когда он найдёт фиксацию положения — это может занять несколько минут при первом запуске.
В консоли отобразятся NMEA-предложения с GPS-данными.
Важно: Если вы запускаете этот скетч впервые, может потребоваться несколько минут, пока модуль получит фиксацию положения. Вы начнёте получать актуальные данные, когда синий светодиод начнёт мигать. Если вы находитесь внутри здания, очень маловероятно, что вы сможете получить GPS-данные. Выйдите на улицу или разместите антенну снаружи, чтобы максимизировать шансы на приём спутникового сигнала.
Каждая строка, которую вы получаете в мониторе порта, является NMEA-предложением.
NMEA расшифровывается как National Marine Electronics Association (Национальная ассоциация морской электроники), и в мире GPS это стандартный формат данных, поддерживаемый производителями GPS.
NMEA-предложения
NMEA-предложения начинаются с символа $, и каждое поле данных разделяется запятой.
$GNRMC,115209.00,A,4114.5500,N,00861.4900,W,0.129,,160125,,,D*XX
$GNVTG,,T,,M,0.129,N,0.239,K,D*XX
$GNGGA,115209.00,4114.5500,N,00861.4900,W,2,10,0.93,130.6,M,50.1,M,,0000*XX
$GNGSA,A,3,24,25,28,32,29,,,,,,,,1.65,0.93,1.37*XX
$GNGSA,A,3,78,66,67,77,,86,26,083,20*XX
$GLGSV,3,3,09,87,13,131,*XX
$GNGLL,4114.5500,N,00861.4900,W,115209.00,A,D*XX
Существуют различные типы NMEA-предложений. Тип сообщения обозначается символами перед первой запятой.
GN после $ указывает, что это позиция GPS. $GNGGA — это базовое GNSS NMEA-сообщение, которое предоставляет данные о 3D-местоположении и точности.
В следующем предложении:
$GNGGA,110827.00,4114.32485,N,00831.79799,W,1,10,0.93,130.6,M,50.1,M,,*5F
Вот как выглядят поля для M8N:
$GNGGA: Глобальные данные о местоположении GNSS.
110827.00: Время в UTC (11:08:27).
4114.32485,N: Широта.
00831.79799,W: Долгота.
1: Качество фиксации (1 = GPS-фикс, 2 = DGPS и т.д.).
10: Количество отслеживаемых спутников (больше для M8N по сравнению с NEO-6M).
0.93: Горизонтальное снижение точности (чем меньше, тем лучше).
130.6,M: Высота над средним уровнем моря (в метрах).
50.1,M: Высота геоида над эллипсоидом WGS84.
*5F: Пересчитанная контрольная сумма для NEO-M8N.
Другие NMEA-предложения предоставляют дополнительную информацию:
$GNRMC — Основные данные GNSS PVT (Position, Velocity, Time — Положение, Скорость, Время)
$GNVTG — Информация о скорости и курсе
$GNGGA — Информация о фиксации GNSS
$GNGSA — DOP GNSS и активные спутники
$GLGSV — Подробная информация о спутниках (ГЛОНАСС)
$GNGLL — Географическая широта и долгота.
Для получения дополнительной информации о NMEA-предложениях рекомендуем этот сайт с очень подробной информацией.
Вы можете использовать онлайн-анализатор NMEA и вставить туда свои предложения для интерпретации GPS-данных.
Однако самый простой способ получить и интерпретировать нужные вам GPS-данные — это парсить NMEA-предложения непосредственно в коде. Для этого мы будем использовать модуль micropyGPS.
Загрузка модуля micropyGPS
Для парсинга NMEA-предложений от GPS-модуля и простого получения GPS-данных мы будем использовать модуль micropyGPS. Эта библиотека не входит в стандартную библиотеку MicroPython по умолчанию. Поэтому вам нужно загрузить следующий файл на вашу плату Raspberry Pi Pico (сохраните его с именем micropyGPS.py).
"""
# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X
# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com)
# The MIT License (MIT) - see LICENSE file
"""
# 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
Вот общие инструкции по загрузке библиотеки micropyGPS на вашу плату:
Сначала убедитесь, что на вашей плате установлена прошивка MicroPython — ознакомьтесь с разделом предварительных требований.
Создайте новый файл в вашей IDE с именем micropyGPS.py и вставьте туда предыдущий код. Сохраните этот файл.
Установите последовательную связь с вашей платой через IDE.
Загрузите файл micropyGPS.py на вашу плату. В Thonny IDE перейдите в File > Save as… и выберите MicroPython Device / Raspberry Pi Pico.
На этом этапе библиотека должна быть успешно загружена на вашу плату. Теперь вы можете использовать функциональность библиотеки в своём коде, импортировав библиотеку
import micropyGPS.
Raspberry Pi Pico с NEO-M8N: получение GPS-данных с MicroPython
Библиотека micropyGPS упрощает получение GPS-данных в удобном для понимания формате.
Следующий код показывает, как использовать библиотеку для получения GPS-данных, таких как широта, долгота, высота, дата и время, количество видимых спутников и HDOP (мера точности сигнала).
После импорта библиотеки micropyGPS на вашу плату вы можете запустить следующий код.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-neo-m8n-gps-micropytho
from machine import UART, Pin
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 = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
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()
number = my_gps.latitude
print(number)
except Exception as e:
print(f"An error occurred: {e}")
Как работает код?
Продолжите чтение, чтобы узнать, как работает код, или перейдите к разделу демонстрации.
Сначала импортируйте необходимые модули, включая класс MicropyGPS из модуля micropyGPS, который вы импортировали ранее.
from machine import UART, Pin
from time import sleep
from micropyGPS import MicropyGPS
Создайте экземпляр класса MicropyGPS с именем my_gps.
# Instantiate the micropyGPS object
my_gps = MicropyGPS()
Затем инициализируйте экземпляр UART для последовательной связи с модулем. Мы используем UART 1 и GPIO 4 для TX и GPIO 5 для RX. Также определяем скорость передачи данных для GPS-модуля (NEO-M8N использует 9600).
gps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
Затем мы создаём бесконечный цикл для непрерывного чтения GPS-данных.
Мы проверяем, есть ли новые данные для чтения. Если есть, мы считываем данные и передаём их экземпляру my_gps с помощью метода update().
while gps_serial.any():
data = gps_serial.read()
for byte in data:
stat = my_gps.update(chr(byte))
Метод update() возвращает корректные GPS-предложения или None в противном случае. Поэтому мы проверяем наличие валидных данных перед продолжением.
if stat is not None:
Затем мы можем получить доступ к GPS-данным, используя методы micropyGPS на объекте my_gps, который должен содержать данные, собранные с GPS-модуля.
Следующие строки показывают, как получить время, дату, широту, долготу, высоту, количество используемых спутников и 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()
Библиотека micropyGPS поддерживает другие методы для получения дополнительных GPS-данных в различных форматах. Рекомендуем ознакомиться с документацией и посмотреть все доступные варианты.
Демонстрация
После загрузки модуля micropyGPS на вашу плату вы можете запустить предыдущий код для получения GPS-данных.
Убедитесь, что ваша плата или антенна находится рядом с окном или, предпочтительно, снаружи, чтобы получать данные со спутников. Возможно, придётся подождать несколько минут, пока модуль получит фиксацию положения и сможет отправить валидные данные. Синий светодиод GPS-модуля NEO-M8N начнёт мигать, когда он будет готов.
В консоли MicroPython вы должны получить информацию о вашем текущем местоположении, дате и времени в UTC, количестве спутников и HDOP. Чем больше количество спутников и чем ниже HDOP, тем лучше.
GPS-логгер и отображение маршрута в Google Earth
Теперь, когда вы лучше знакомы с использованием GPS-модуля NEO-M8N с Raspberry Pi Pico, давайте создадим GPS-логгер, который записывает ваше местоположение с течением времени в файл на microSD-карте. Затем вы можете изменить и использовать этот файл в Google Earth для визуализации того, как менялось местоположение с течением времени (маршрут).
Рекомендуемое чтение: Raspberry Pi Pico: руководство по MicroSD-карте с примером записи данных (MicroPython).
Обзор проекта
Вот краткий обзор работы этого проекта:
Raspberry Pi Pico подключён к GPS-модулю NEO-M8N и получает данные о местоположении;
Каждые три секунды мы сохраняем данные о местоположении (широта, долгота и высота) в файл на microSD-карте;
Для визуализации данных в Google Earth мы покажем, как вручную преобразовать файл .txt с координатами местоположения в файл .kml, который может читать Google Earth;
Мы покажем, как загрузить файл .kml в Google Earth для визуализации маршрута.
Необходимые компоненты
Вот список необходимых компонентов для этого проекта:
Подключение схемы
Подключите GPS-модуль к выводам UART Pico (как мы делали ранее), а модуль microSD-карты — к выводам SPI по вашему выбору. Мы будем использовать выводы, показанные на следующей схеме и таблицах — используйте их как справочник.
GPS-модуль NEO-M8N |
Raspberry Pi Pico |
|---|---|
VCC |
3V3(OUT) (Pin 36) |
RX |
TX (GPIO 4) (Pin 6) |
TX |
RX (GPIO 5) (Pin 7) |
GND |
GND |
Модуль microSD-карты |
Raspberry Pi Pico |
|---|---|
3V3* |
3V3(OUT) (Pin 36) |
CS |
GPIO 17 (Pin 22) |
MOSI |
GPIO 19 (Pin 25) |
CLK |
GPIO 18 (Pin 24) |
MISO |
GPIO 16 (Pin 21) |
GND |
GND |
* некоторые модули microSD-карт требуют 5V вместо 3V3. В этом случае подключите VCC microSD-карты к выводу VBUS.
Импорт модуля sdcard.py
На данный момент поддержка библиотек для работы с microSD-картой на Raspberry Pi Pico невелика. Мы нашли модуль sdcard.py, который хорошо справляется с обработкой файлов на microSD-карте. Следуйте следующим шагам для установки библиотеки.
Создайте новый файл в Thonny IDE и скопируйте код библиотеки.
Перейдите в File > Save as и выберите Raspberry Pi Pico.
Назовите файл
sdcard.pyи нажмите OK, чтобы сохранить файл на Raspberry Pi Pico.
Вот и всё. Библиотека загружена на вашу плату. Теперь вы можете использовать функциональность библиотеки в своём коде, импортировав библиотеку.
Тестирование MicroSD-карты
Загрузите следующий код на Raspberry Pi Pico, чтобы проверить, может ли он взаимодействовать с microSD-картой.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-neo-m8n-gps-micropytho
from machine import SPI, Pin
import sdcard, os
# Constants
SPI_BUS = 0
SCK_PIN = 18
MOSI_PIN = 19
MISO_PIN = 16
CS_PIN = 17
SD_MOUNT_PATH = '/sd'
try:
# Init SPI communication
spi = SPI(SPI_BUS,sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN), miso=Pin(MISO_PIN))
cs = Pin(CS_PIN)
sd = sdcard.SDCard(spi, cs)
# Mount microSD card
os.mount(sd, SD_MOUNT_PATH)
# List files on the microSD card
print(os.listdir(SD_MOUNT_PATH))
except Exception as e:
print('An error occurred:', e)
Предыдущий код запускает SPI-связь на выводах, к которым подключена microSD-карта, пытается смонтировать microSD-карту и затем пытается вывести список файлов.
Если вы используете другие выводы SPI, вам следует изменить код соответствующим образом. Также обратите внимание, что в зависимости от используемых выводов вам может потребоваться изменить номер шины SPI. На распиновке Raspberry Pi Pico вы можете увидеть, какие выводы находятся на шине SPI 1 или 0.
SPI_BUS = 0
SCK_PIN = 18
MOSI_PIN = 19
MISO_PIN = 16
CS_PIN = 17
Запустите предыдущий код на Raspberry Pi Pico, нажав зелёную кнопку запуска в Thonny IDE.
Вы должны получить похожее сообщение в консоли (смотрите скриншот ниже). Если это так, значит всё работает как ожидалось. Если нет, проверьте подключение, правильно ли вставлена microSD-карта и используете ли вы правильный номер шины SPI для используемых выводов.
Если всё работает как ожидалось, теперь вы можете протестировать код для записи GPS-данных на microSD-карту.
Запись GPS-данных на microSD-карту — Код
Следующий код инициализирует microSD-карту и GPS-модуль и сохраняет GPS-данные, как только точки данных становятся доступны, каждые три секунды (вы можете изменить время задержки).
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-neo-m8n-gps-micropytho
from machine import UART, Pin, Timer, SPI
from time import sleep, ticks_ms, ticks_diff
from micropyGPS import MicropyGPS
import sdcard, os
# microSD card pin definition
SPI_BUS = 0
SCK_PIN = 18
MOSI_PIN = 19
MISO_PIN = 16
CS_PIN = 17
# microSD card filesystem mount path
SD_MOUNT_PATH = '/sd'
# file path to save data
FILE_PATH = '/sd/data.txt'
# Init SPI communication for the microSD card
spi = SPI(SPI_BUS, sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN), miso=Pin(MISO_PIN))
cs = Pin(CS_PIN)
# Instantiate the micropyGPS object
my_gps = MicropyGPS()
# Define the UART pins and create a UART object
gps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
def mount_sdcard(spi, cs_pin):
try:
sd = sdcard.SDCard(spi, cs_pin)
# Mount microSD card
os.mount(sd, SD_MOUNT_PATH)
# List files on the microSD card
print(os.listdir(SD_MOUNT_PATH))
except Exception as e:
print('An error occurred mounting the SD card:', e)
def write_to_sd(data, file_path):
try:
# Open the file in append mode ('a')
with open(file_path, 'a') as file:
file.write(data + '\n')
print("Data logged successfully.")
except Exception as e:
print(f"An error occurred while writing to SD card: {e}")
# Convert Longitude and Latitude to decimal degrees
def convert_to_decimal_degrees(coord):
degrees = coord[0]
minutes = coord[1]
hemisphere = coord[2]
decimal_degrees = degrees + (minutes / 60)
# Make the value negative if it's in the western or southern hemisphere
if hemisphere in ['S', 'W']:
decimal_degrees *= -1
return decimal_degrees
# Mount SD card
mount_sdcard(spi, cs)
# Initialize a timer variable
last_print_time = ticks_ms() # Get the current time in milliseconds
while True:
try:
# Check if data is available in the UART buffer
while gps_serial.any():
data = gps_serial.read()
for byte in data:
stat = my_gps.update(chr(byte))
if stat is not None:
# Check if 3 seconds have passed since the last print
current_time = ticks_ms()
if ticks_diff(current_time, last_print_time) >= 3000:
# Get longitude, latitude, and altitude
longitude = convert_to_decimal_degrees(my_gps.longitude)
latitude = convert_to_decimal_degrees(my_gps.latitude)
altitude = my_gps.altitude
GPS_data = f"{longitude},{latitude},{altitude}"
# Print the GPS data
print(GPS_data)
print()
# Save data on the microSD card
write_to_sd(GPS_data, FILE_PATH);
# Update the last print time
last_print_time = current_time
except Exception as e:
print(f"An error occurred: {e}")
Чтобы сохранить данные в формате, который Google Earth может прочитать, нам нужно сохранить долготу, широту и высоту именно в таком порядке, разделённые запятыми (широта и долгота должны быть в формате десятичных градусов). Это именно то, что мы делаем в этом примере. Позже нам нужно будет преобразовать эту информацию в файл .kml.
Как работает код?
Начнём с импорта необходимых модулей и классов.
from machine import UART, Pin, Timer, SPI
from time import sleep, ticks_ms, ticks_diff
from micropyGPS import MicropyGPS
import sdcard, os
Определите выводы, которые вы используете для подключения к microSD-карте.
# microSD card pin definition
SPI_BUS = 0
SCK_PIN = 18
MOSI_PIN = 19
MISO_PIN = 16
CS_PIN = 17
Создайте переменные для сохранения пути монтирования файловой системы microSD-карты и пути к файлу, в который будут сохраняться данные. В нашем случае — в файл с именем data.txt.
# microSD card filesystem mount path
SD_MOUNT_PATH = '/sd'
# file path to save data
FILE_PATH = '/sd/data.txt'
Инициализируйте SPI-связь для microSD-карты.
# Init SPI communication for the microSD card
spi = SPI(SPI_BUS, sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN), miso=Pin(MISO_PIN))
cs = Pin(CS_PIN)
Инициализируйте объект micropyGPS. Мы назовём его my_gps.
# Instantiate the micropyGPS object
my_gps = MicropyGPS()
Создайте новый объект UART на выводах, которые будут использоваться для взаимодействия с GPS-модулем.
# Define the UART pins and create a UART object
gps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
Следующая функция инициализирует и монтирует microSD-карту на шине SPI и выводе CS, определённых ранее.
def mount_sdcard(spi, cs_pin):
try:
sd = sdcard.SDCard(spi, cs_pin)
# Mount microSD card
os.mount(sd, SD_MOUNT_PATH)
# List files on the microSD card
print(os.listdir(SD_MOUNT_PATH))
except Exception as e:
print('An error occurred mounting the SD card:', e)
Функция write_to_sd() добавляет данные в файл на microSD-карте. Первый аргумент — строка, которую вы хотите сохранить в файл, а второй аргумент — путь к файлу. Эта функция добавит данные в файл, если он уже существует, или создаст его, если он ещё не существует.
def write_to_sd(data, file_path):
try:
# Open the file in append mode ('a')
with open(file_path, 'a') as file:
file.write(data + '\n')
print("Data logged successfully.")
except Exception as e:
print(f"An error occurred while writing to SD card: {e}")
Следующая функция преобразует данные широты и долготы из формата, полученного от библиотеки micropyGPS, в десятичные градусы (формат, используемый файлами .kml).
# Convert Longitude and Latitude to decimal degrees
def convert_to_decimal_degrees(coord):
degrees = coord[0]
minutes = coord[1]
hemisphere = coord[2]
decimal_degrees = degrees + (minutes / 60)
# Make the value negative if it's in the western or southern hemisphere
if hemisphere in ['S', 'W']:
decimal_degrees *= -1
return decimal_degrees
После определения всех переменных и функций вызовите функцию mount_sdcard() для инициализации и монтирования microSD-карты.
mount_sdcard(spi, cs)
Затем создайте переменную, которая будет сохранять, сколько миллисекунд прошло с начала выполнения программы (эта переменная будет использоваться для проверки того, сколько времени прошло с момента последней записи показаний).
last_print_time = ticks_ms() # Get the current time in milliseconds
Затем мы создаём бесконечный цикл для непрерывного чтения и записи GPS-данных.
while True:
try:
Мы проверяем, есть ли новые данные для чтения. Если есть, мы считываем данные и передаём их экземпляру my_gps с помощью метода update().
while gps_serial.any():
data = gps_serial.read()
for byte in data:
stat = my_gps.update(chr(byte))
Метод update() возвращает корректные GPS-предложения или None в противном случае. Поэтому мы проверяем наличие валидных данных перед продолжением.
if stat is not None:
Затем мы можем получить доступ к GPS-данным, используя методы micropyGPS на объекте my_gps, который должен содержать данные, собранные с GPS-модуля.
Мы проверяем, прошло ли три секунды с момента последней записи GPS-данных, и получаем текущие долготу, широту и высоту. Мы преобразуем долготу и широту в десятичные градусы.
if ticks_diff(current_time, last_print_time) >= 3000:
# Get longitude, latitude, and altitude
longitude = convert_to_decimal_degrees(my_gps.longitude)
latitude = convert_to_decimal_degrees(my_gps.latitude)
altitude = my_gps.altitude
Затем мы объединяем долготу, широту и высоту в строку, разделённую запятыми.
GPS_data = f"{longitude},{latitude},{altitude}"
Выводим эти данные в консоль.
print(GPS_data)
Наконец, мы записываем эти данные на microSD-карту, вызывая функцию write_to_sd(), созданную ранее, и передавая в качестве аргументов GPS_data и FILE_PATH.
write_to_sd(GPS_data, FILE_PATH);
В конце мы обновляем переменную last_print_time текущим временем.
last_print_time = current_time
Тестирование проекта
После импорта всех необходимых библиотек вы можете запустить код на Raspberry Pi Pico для тестирования. Убедитесь, что модуль или антенна расположены снаружи или рядом с окном, чтобы получать данные со спутников.
Вы должны получить что-то подобное в консоли:
Загрузка кода на Raspberry Pi Pico
Важное замечание: простой запуск файла через Thonny IDE не копирует его навсегда в файловую систему платы. Это значит, что если вы отключите её от компьютера и подадите питание на плату Pico, ничего не произойдёт, потому что на её файловой системе нет сохранённых файлов Python. Функция Run в Thonny IDE полезна для тестирования кода, но если вы хотите загрузить его навсегда на плату, вам нужно создать и сохранить файл в файловую систему платы. Если вы хотите отключить Pico от компьютера и пойти на прогулку с ним для записи GPS-данных для создания маршрута, вам нужно загрузить код на плату. Следуйте следующим шагам:
1) Остановите выполнение предыдущей программы, нажав кнопку Stop, если вы ещё этого не сделали.
2) С кодом, скопированным в Thonny IDE, перейдите в File > Save as…. Затем выберите Raspberry Pi Pico.
3) Сохраните файл с именем: main.py.
Примечание: Когда вы называете файл main.py, Raspberry Pi Pico будет запускать этот файл автоматически при загрузке. Если вы назовёте его по-другому, он всё равно будет сохранён в файловой системе платы, но не будет запускаться автоматически при загрузке.
4) Наконец, нажмите OK для продолжения.
Теперь вы можете отключить RPi Pico от компьютера и питать его от портативного источника питания, такого как батарея или портативное зарядное устройство.
Отправьтесь на прогулку с вашей схемой, чтобы она начала записывать ваши координаты с течением времени, и вы могли получить достаточное количество точек для построения маршрута.
После записи некоторых данных отключите microSD-карту от модуля и подключите её к компьютеру. На microSD-карте должен быть файл data.txt с координатами вашего маршрута.
Чтобы загрузить эти данные в Google Earth и построить маршрут, нам нужно преобразовать этот файл в формат .kml.
Файлы KML
KML — это формат файлов, используемый для отображения географических данных в браузерах Земли, таких как Google Earth. KML использует структуру на основе тегов с вложенными элементами и атрибутами и основан на стандарте XML. Мы не будем подробно разбирать структуру этого файла. Если вы хотите узнать больше, ознакомьтесь с этим руководством о файлах KML от Google.
Преобразование файла data.txt в формат .kml
1) Откройте файл data.txt с GPS-данными, собранными с GPS-модуля.
2) Отредактируйте ваш файл так, чтобы координаты находились между тегами <coordinates></coordinates> следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Style id="yellowPoly">
<LineStyle>
<color>7f00ffff</color>
<width>4</width>
</LineStyle>
<PolyStyle>
<color>7f00ff00</color>
</PolyStyle>
</Style>
<Placemark><styleUrl>#yellowPoly</styleUrl>
<LineString>
<extrude>1</extrude>
<tesselate>1</tesselate>
<altitudeMode>absolute</altitudeMode>
<coordinates>
YOUR COORDINATES GO HERE
</coordinates>
</LineString></Placemark>
</Document></kml>
3) Сохраните этот файл.
4) Измените его имя с data.txt на data.kml. Вы получите предупреждение об изменении формата файла. Примите его. Теперь файл должен быть пригоден для загрузки в Google Earth.
Отображение маршрута в Google Earth
Теперь следуйте следующим шагам для отображения и визуализации вашего маршрута в Google Earth.
1) Перейдите на сайт Google Earth.
2) Создайте новый проект и дайте ему имя.
3) Перейдите в File > Import File to Project > Upload from device.
4) Выберите файл .kml, созданный ранее.
5) Ваш маршрут будет отображён в Google Earth с жёлтым контуром.
Вот и всё! Вы научились записывать GPS-данные в файл на microSD-карте и использовать эти данные для отображения маршрута в Google Earth.
Заключение
В этом руководстве вы узнали, как использовать GPS-модуль NEO-M8N с Raspberry Pi Pico для получения данных о местоположении, времени и многом другом. Мы показали вам, как получить необработанные GPS-данные и как парсить данные.
Наконец, мы создали проект для сохранения данных о местоположении в файл на microSD-карте. Вы узнали, как преобразовать данные в формат, который Google Earth может прочитать, для построения и отображения маршрута.
Надеемся, что это руководство было для вас полезным. У нас есть руководства для других модулей, которые могут быть вам полезны: