{"id":166784,"date":"2025-02-04T15:53:10","date_gmt":"2025-02-04T15:53:10","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=166784"},"modified":"2025-02-04T16:18:03","modified_gmt":"2025-02-04T16:18:03","slug":"raspberry-pi-pico-neo-m8n-gps-micropython","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-neo-m8n-gps-micropython\/","title":{"rendered":"Raspberry Pi Pico: NEO-M8N GPS Logger and Display on Google Earth (MicroPython)"},"content":{"rendered":"\n<p>Getting started guide for the NEO-M8N GPS Module with the Raspberry Pi Pico. Learn how to interface the module with the Pico board and get data about location and time. We&#8217;ll also create a GPS logger project with a microSD card to record your GPS position over time. Then, you&#8217;ll learn how to use that GPS data on Google Earth to display a path.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" fetchpriority=\"high\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Raspberry Pi Pico with NEO-M8N GPS Module: GPS Logger and Display on Google Earth (MicroPython)\" class=\"wp-image-166865\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?w=1920&amp;quality=100&amp;strip=all&amp;ssl=1 1920w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?resize=1024%2C576&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?resize=768%2C432&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?resize=1536%2C864&amp;quality=100&amp;strip=all&amp;ssl=1 1536w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntclgreen\"><strong>New to the Raspberry Pi Pico?<\/strong> Check out <a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-w-micropython-ebook\/\" title=\"\">Learn Raspberry Pi Pico\/Pico W with MicroPython eBook<\/a>.<\/p>\n\n\n\n<p>In this tutorial, you&#8217;ll learn the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wire the NEO-M8N GPS module to the Raspberry Pi Pico via serial;<\/li>\n\n\n\n<li>Get raw GPS data;<\/li>\n\n\n\n<li>Parse raw data to obtain selected and readable GPS information;<\/li>\n\n\n\n<li>Get your current location;<\/li>\n\n\n\n<li>Log the location to a file on the microSD card;<\/li>\n\n\n\n<li>Transform your data into a <em>.kml<\/em> file that Google Earth can read;<\/li>\n\n\n\n<li>Upload the <em>.kml<\/em> file to Google Earth to display a path.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Table of Contents<\/h2>\n\n\n\n<p>We&#8217;ll cover the following subjects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"#intro-neom8n\" title=\"\">Introducing the NEO-M8N GPS Module<\/a><\/li>\n\n\n\n<li><a href=\"#rpi-pico-neo-m8n-wiring\" title=\"\">Wiring the NEO-M8N GPS Module to the Raspberry Pi Pico<\/a><\/li>\n\n\n\n<li><a href=\"#test-neo-m8n-rpi-pico-raw-data\" title=\"\">Getting Raw GPS Data &#8211; Testing the NEO-M8N GPS Module with the Raspberry Pi Pico<\/a><\/li>\n\n\n\n<li><a href=\"#micropygps-module\" title=\"\">Uploading the micropyGPS Module<\/a><\/li>\n\n\n\n<li><a href=\"#rpi-pico-neo-m8n-gps-data-micropygps\" title=\"\">Raspberry Pi Pico with NEO-M8N: Getting GPS Data with MicroPython<\/a><\/li>\n\n\n\n<li><a href=\"#RPi-Pico-neo-m8n-gps-logger-google-earth\" title=\"\">GPS Logger and Display Path on Google Earth<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisites\">Prerequisites &#8211; MicroPython Firmware<\/h2>\n\n\n\n<p>To follow this tutorial you need MicroPython firmware installed in your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board. <\/p>\n\n\n\n<p>The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/getting-started-raspberry-pi-pico-w\/#install-thonny-ide\" title=\"\">Programming Raspberry Pi Pico using MicroPython<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"intro-neom8n\">Introducing the NEO-M8N GPS Module<\/h2>\n\n\n\n<p>The NEO-M8N GPS module is one of the most popular GPS receivers used with microcontrollers in navigation and tracking projects. It can get data about latitude, longitude, altitude, and time.<\/p>\n\n\n\n<p>It supports multiple satellite systems, including GPS, Galileo, GLONASS, and BeiDou. It offers better satellite tracking than the <a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-neo-6m-micropython\/\" title=\"\">NEO-6M<\/a>, making it more reliable in challenging conditions.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/M8N-GPS-Module.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"NEO-M8N-GPS-Module\" class=\"wp-image-166757\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/M8N-GPS-Module.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/M8N-GPS-Module.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>According to the datasheet, it has a horizontal position accuracy of 2.5 to 4 meters and quick startup times (1 second for hot start, 26\u201357 seconds for cold start\u2014expect longer times if you&#8217;re close to buildings).<\/p>\n\n\n\n<p>The module includes a backup battery, built-in EEPROM, and an LED indicator that blinks when a position fix is achieved.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-NEO-M8N-GPS-Module-Blink-LED-Position-Fix.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"RPi Pico NEO-M8N Position Fix - Blinking LED\" class=\"wp-image-166804\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-NEO-M8N-GPS-Module-Blink-LED-Position-Fix.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-NEO-M8N-GPS-Module-Blink-LED-Position-Fix.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>This module typically comes with a ceramic GPS antenna. But, you can change it to any other compatible antenna that might suit your project better. For example, I like to use the one at the right in the picture below because it is waterproof, and the antenna comes with a long cable which allows for more flexibility.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/07\/antennas-for-GPS-Modules.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"antennas for GPS modules\" class=\"wp-image-160852\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/07\/antennas-for-GPS-Modules.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/07\/antennas-for-GPS-Modules.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>The NEO-M8N GPS Module communicates with a microcontroller using Serial communication protocol, and works with standard NMEA sentences. NMEA stands for National Marine Electronics Association, and in the world of GPS, it is a standard data format supported by GPS manufacturers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where to buy?<\/h3>\n\n\n\n<p>You can check our Maker Advisor Tools page to compare the NEO-M8N GPS receiver module price in different stores:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/neo-m8n-gps-module\/\" target=\"_blank\" rel=\"noopener\" title=\"\">NEO-M8N GPS Receiver Module<\/a><\/li>\n<\/ul>\n\n\n<p>You can use the preceding links or go directly to <a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\">MakerAdvisor.com\/tools<\/a> to find all the parts for your projects at the best price!<\/p><p style=\"text-align:center;\"><a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2017\/10\/header-200.png?w=1200&#038;quality=100&#038;strip=all&#038;ssl=1\"><\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"rpi-pico-neo-m8n-wiring\">Wiring the NEO-M8N GPS Module to the Raspberry Pi Pico<\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-NEO-M8N-GPS-Module-Wiring-Get-Raw-Data.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Wiring the NEO-M8N GPS Module to the Raspberry Pi Pico\" class=\"wp-image-166806\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-NEO-M8N-GPS-Module-Wiring-Get-Raw-Data.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-NEO-M8N-GPS-Module-Wiring-Get-Raw-Data.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>To get data from the NEO-6M GPS module, we need to establish a serial communication. The Raspberry Pi Pico has the following options for UART pins:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>UART Interface<\/strong><\/td><td><strong>TX GPIOs<\/strong><\/td><td><strong>RX GPIOs<\/strong><\/td><\/tr><tr><td><strong>UART0<\/strong><\/td><td>GPIO0, GPIO12, GPIO16<\/td><td>GPIO1, GPIO13, GPIO17<\/td><\/tr><tr><td><strong>UART1<\/strong><\/td><td>GPIO4, GPIO8<\/td><td>GPIO5, GPIO9<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>We&#8217;ll use <strong>UART 1 and GPIO 4 (TX) and GPIO 5 (RX)<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"990\" height=\"628\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS_bb.png?resize=990%2C628&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Wiring NEO-6M GPS Module to the Raspberry Pi Pico\" class=\"wp-image-166787\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS_bb.png?w=990&amp;quality=100&amp;strip=all&amp;ssl=1 990w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS_bb.png?resize=300%2C190&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS_bb.png?resize=768%2C487&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 990px) 100vw, 990px\" \/><\/figure><\/div>\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>NEO-6M GPS Module<\/strong><\/td><td><strong>Raspberry Pi Pico<\/strong><\/td><\/tr><tr><td>VCC<\/td><td><span class=\"rnthl rntcred\">3V3<\/span><\/td><\/tr><tr><td>RX<\/td><td>TX (<span class=\"rnthl rntcyellow\">GPIO 4<\/span>) (Pin 6)<\/td><\/tr><tr><td>TX<\/td><td>RX (<span class=\"rnthl rntcgreen\">GPIO 5<\/span>) (Pin 7)<\/td><\/tr><tr><td>GND<\/td><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"rntbox rntclblue\"><strong>Related article:<\/strong> <a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-w-pinout-gpios\/\">Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"test-neo-m8n-rpi-pico-raw-data\">Getting Raw GPS Data &#8211; Testing the NEO-M8N GPS Module with the Raspberry Pi Pico (MicroPython)<\/h2>\n\n\n\n<p>To get raw GPS data you just need to start a serial communication with the GPS module and read the available data.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-NEO-M8N-Get-Raw-GPS-Data.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Getting Raw GPS Data - Testing the NEO-M8N GPS Module with the Raspberry Pi Pico \" class=\"wp-image-166807\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-NEO-M8N-Get-Raw-GPS-Data.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-NEO-M8N-Get-Raw-GPS-Data.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>The following code establishes a serial communication with the GPS module and reads the available data.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n# Complete project details at https:\/\/RandomNerdTutorials.com\/raspberry-pi-pico-neo-m8n-gps-micropython\/\r\n\r\nimport machine\r\nfrom time import sleep\r\n\r\n# Define the UART pins and create a UART object\r\ngps_serial = machine.UART(1, baudrate=9600, tx=4, rx=5)\r\n\r\nwhile True:\r\n    if gps_serial.any():\r\n        line = gps_serial.readline()  # Read a complete line from the UART\r\n        if line:\r\n            line = line.decode('utf-8')\r\n            print(line.strip())\r\n    sleep(0.5)<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/NEO_M8N_GPS_Raw_Data.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Testing the Code<\/h3>\n\n\n\n<p>After establishing a connection with the board using Thonny IDE, run the previous code.<\/p>\n\n\n\n<p>Make sure the antenna is connected and that the module or antenna is placed outside or next to a window so that it can get data from the satellites.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/07\/active-GPS-antenna-for-NEO-6M.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"active GPS antenna for NEO-6M\" class=\"wp-image-160850\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/07\/active-GPS-antenna-for-NEO-6M.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/07\/active-GPS-antenna-for-NEO-6M.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>The module&#8217;s blue LED will start blinking when it finds a position fix\u2014this might take a few minutes on the first run.<\/p>\n\n\n\n<p>The shell will display NMEA sentences with GPS data.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"753\" height=\"355\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-NEO-M8N-getting-raw-data.jpg?resize=753%2C355&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Raspberry Pi Pico with Neo-M8N GPS Module - Get Raw GPS Data\" class=\"wp-image-166788\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-NEO-M8N-getting-raw-data.jpg?w=753&amp;quality=100&amp;strip=all&amp;ssl=1 753w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-NEO-M8N-getting-raw-data.jpg?resize=300%2C141&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 753px) 100vw, 753px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntcred\"><strong>Important: <\/strong> If you&#8217;re running this sketch for the first time, it may take a few minutes until the module gets a position fix. You&#8217;ll start getting actual data when the blue LED starts blinking. If you&#8217;re inside a building, it is very unlikely that you can get GPS data. Go outside or place your antenna outside to maximize your chances of catching a satellite signal.<\/p>\n\n\n\n<p>Each line you get in the serial monitor is an NMEA sentence.<\/p>\n\n\n\n<p>NMEA stands for National Marine Electronics Association, and in the world of GPS, it is a standard data format supported by GPS manufacturers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"NMEA-sentences\">NMEA Sentences<\/h3>\n\n\n\n<p>NMEA sentences start with the $ character, and each data field is separated by a comma.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>$GNRMC<\/strong>,115209.00,A,4114.5500,N,00861.4900,W,0.129,,160125,,,D*XX<br><strong>$GNVTG<\/strong>,,T,,M,0.129,N,0.239,K,D*XX<br><strong>$GNGGA<\/strong>,115209.00,4114.5500,N,00861.4900,W,2,10,0.93,130.6,M,50.1,M,,0000*XX<br><strong>$GNGSA<\/strong>,A,3,24,25,28,32,29,,,,,,,,1.65,0.93,1.37*XX<br><strong>$GNGSA<\/strong>,A,3,78,66,67,77,,86,26,083,20*XX<br><strong>$GLGSV<\/strong>,3,3,09,87,13,131,*XX<br><strong>$GNGLL<\/strong>,4114.5500,N,00861.4900,W,115209.00,A,D*XX<\/pre>\n\n\n\n<p>There are different types of NMEA sentences. The type of message is indicated by the characters before the first comma.<\/p>\n\n\n\n<p>The <strong>GN <\/strong>after the <strong>$<\/strong> indicates it is a GPS position.&nbsp;The <strong>$GNGGA<\/strong> is the basic GNSS NMEA message, that provides 3D location and accuracy data.<\/p>\n\n\n\n<p>In the following sentence:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>$GNGGA<\/strong>,110827.00,4114.32485,N,00831.79799,W,1,10,0.93,130.6,M,50.1,M,,*5F<\/pre>\n\n\n\n<p>Here\u2019s how the fields look for the M8N:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>$GNGGA: Global GNSS location data.<\/li>\n\n\n\n<li>110827.00: Time in UTC (11:08:27).<\/li>\n\n\n\n<li>4114.32485,N: Latitude.<\/li>\n\n\n\n<li>00831.79799,W: Longitude.<\/li>\n\n\n\n<li>1: Fix quality (1 = GPS fix, 2 = DGPS, etc.).<\/li>\n\n\n\n<li>10: Number of satellites tracked (higher for M8N compared to NEO-6M).<\/li>\n\n\n\n<li>0.93: Horizontal dilution of precision (lower is better).<\/li>\n\n\n\n<li>130.6,M: Altitude above mean sea level (in meters).<\/li>\n\n\n\n<li>50.1,M: Height of geoid above the WGS84 ellipsoid.<\/li>\n\n\n\n<li>*5F: Recalculated checksum for the NEO-M8N.<\/li>\n<\/ul>\n\n\n\n<p>The other NMEA sentences provide additional information:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>$GNRMC<\/strong> \u2013 Essential GNSS PVT (Position, Velocity, Time) data<\/li>\n\n\n\n<li><strong>$GNVTG<\/strong> \u2013 Velocity and track information<\/li>\n\n\n\n<li><strong>$GNGGA<\/strong> \u2013 GNSS Fix Information<\/li>\n\n\n\n<li><strong>$GNGSA <\/strong>\u2013 GNSS DOP and active satellites<\/li>\n\n\n\n<li><strong>$GLGSV<\/strong> \u2013 Detailed satellite information (GLONASS)<\/li>\n\n\n\n<li><strong>$GNGLL<\/strong> \u2013 Geographic Latitude and Longitude.<\/li>\n<\/ul>\n\n\n\n<p>For more information about NMEA sentences, I found <a href=\"https:\/\/campar.in.tum.de\/twiki\/pub\/Chair\/NaviGpsDemon\/nmea.html\" target=\"_blank\" rel=\"noopener\" title=\"\">this website with very detailed information<\/a>.<\/p>\n\n\n\n<p>You can use this <a href=\"https:\/\/swairlearn.bluecover.pt\/nmea_analyser\" target=\"_blank\" rel=\"noopener\" title=\"\">Online NME Analyser <\/a>and paste your sentences there to interpret the GPS data.<\/p>\n\n\n\n<p>However, the easiest way to get and interpret the GPS data you want is to parse your NMEA sentences directly in the code. For that, we&#8217;ll use the <a href=\"https:\/\/github.com\/inmcm\/micropyGPS\/tree\/master\" target=\"_blank\" rel=\"noopener\" title=\"\">micropyGPS module<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"micropygps-module\">Uploading the micropyGPS Module<\/h2>\n\n\n\n<p>To parse the NMEA sentences from the GPS module and get GPS data easily, we&#8217;ll use the <a href=\"https:\/\/github.com\/inmcm\/micropyGPS\/tree\/master\" target=\"_blank\" rel=\"noopener\" title=\"\">micropyGPS module<\/a>. This library isn\u2019t part of the standard MicroPython library by default. So, you need to upload the following file to your Raspberry Pi Pico board (save it with the name <em>micropyGPS.py<\/em>).<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\">&quot;&quot;&quot;\n# MicropyGPS - a GPS NMEA sentence parser for Micropython\/Python 3.X\n# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com)\n# The MIT License (MIT) - see LICENSE file\n&quot;&quot;&quot;\n\n# TODO:\n# Time Since First Fix\n# Distance\/Time to Target\n# More Helper Functions\n# Dynamically limit sentences types to parse\n\nfrom math import floor, modf\n\n# Import utime or time for fix time handling\ntry:\n    # Assume running on MicroPython\n    import utime\nexcept ImportError:\n    # Otherwise default to time module for non-embedded implementations\n    # Should still support millisecond resolution.\n    import time\n\n\nclass MicropyGPS(object):\n    &quot;&quot;&quot;GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics.\n    Parses sentences one character at a time using update(). &quot;&quot;&quot;\n\n    # Max Number of Characters a valid sentence can be (based on GGA sentence)\n    SENTENCE_LIMIT = 90\n    __HEMISPHERES = ('N', 'S', 'E', 'W')\n    __NO_FIX = 1\n    __FIX_2D = 2\n    __FIX_3D = 3\n    __DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W',\n                    'WNW', 'NW', 'NNW')\n    __MONTHS = ('January', 'February', 'March', 'April', 'May',\n                'June', 'July', 'August', 'September', 'October',\n                'November', 'December')\n\n    def __init__(self, local_offset=0, location_formatting='ddm'):\n        &quot;&quot;&quot;\n        Setup GPS Object Status Flags, Internal Data Registers, etc\n            local_offset (int): Timzone Difference to UTC\n            location_formatting (str): Style For Presenting Longitude\/Latitude:\n                                       Decimal Degree Minute (ddm) - 40\u00b0 26.767\u2032 N\n                                       Degrees Minutes Seconds (dms) - 40\u00b0 26\u2032 46\u2033 N\n                                       Decimal Degrees (dd) - 40.446\u00b0 N\n        &quot;&quot;&quot;\n\n        #####################\n        # Object Status Flags\n        self.sentence_active = False\n        self.active_segment = 0\n        self.process_crc = False\n        self.gps_segments = []\n        self.crc_xor = 0\n        self.char_count = 0\n        self.fix_time = 0\n\n        #####################\n        # Sentence Statistics\n        self.crc_fails = 0\n        self.clean_sentences = 0\n        self.parsed_sentences = 0\n\n        #####################\n        # Logging Related\n        self.log_handle = None\n        self.log_en = False\n\n        #####################\n        # Data From Sentences\n        # Time\n        self.timestamp = [0, 0, 0.0]\n        self.date = [0, 0, 0]\n        self.local_offset = local_offset\n\n        # Position\/Motion\n        self._latitude = [0, 0.0, 'N']\n        self._longitude = [0, 0.0, 'W']\n        self.coord_format = location_formatting\n        self.speed = [0.0, 0.0, 0.0]\n        self.course = 0.0\n        self.altitude = 0.0\n        self.geoid_height = 0.0\n\n        # GPS Info\n        self.satellites_in_view = 0\n        self.satellites_in_use = 0\n        self.satellites_used = []\n        self.last_sv_sentence = 0\n        self.total_sv_sentences = 0\n        self.satellite_data = dict()\n        self.hdop = 0.0\n        self.pdop = 0.0\n        self.vdop = 0.0\n        self.valid = False\n        self.fix_stat = 0\n        self.fix_type = 1\n\n    ########################################\n    # Coordinates Translation Functions\n    ########################################\n    @property\n    def latitude(self):\n        &quot;&quot;&quot;Format Latitude Data Correctly&quot;&quot;&quot;\n        if self.coord_format == 'dd':\n            decimal_degrees = self._latitude[0] + (self._latitude[1] \/ 60)\n            return [decimal_degrees, self._latitude[2]]\n        elif self.coord_format == 'dms':\n            minute_parts = modf(self._latitude[1])\n            seconds = round(minute_parts[0] * 60)\n            return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]]\n        else:\n            return self._latitude\n\n    @property\n    def longitude(self):\n        &quot;&quot;&quot;Format Longitude Data Correctly&quot;&quot;&quot;\n        if self.coord_format == 'dd':\n            decimal_degrees = self._longitude[0] + (self._longitude[1] \/ 60)\n            return [decimal_degrees, self._longitude[2]]\n        elif self.coord_format == 'dms':\n            minute_parts = modf(self._longitude[1])\n            seconds = round(minute_parts[0] * 60)\n            return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]]\n        else:\n            return self._longitude\n\n    ########################################\n    # Logging Related Functions\n    ########################################\n    def start_logging(self, target_file, mode=&quot;append&quot;):\n        &quot;&quot;&quot;\n        Create GPS data log object\n        &quot;&quot;&quot;\n        # Set Write Mode Overwrite or Append\n        mode_code = 'w' if mode == 'new' else 'a'\n\n        try:\n            self.log_handle = open(target_file, mode_code)\n        except AttributeError:\n            print(&quot;Invalid FileName&quot;)\n            return False\n\n        self.log_en = True\n        return True\n\n    def stop_logging(self):\n        &quot;&quot;&quot;\n        Closes the log file handler and disables further logging\n        &quot;&quot;&quot;\n        try:\n            self.log_handle.close()\n        except AttributeError:\n            print(&quot;Invalid Handle&quot;)\n            return False\n\n        self.log_en = False\n        return True\n\n    def write_log(self, log_string):\n        &quot;&quot;&quot;Attempts to write the last valid NMEA sentence character to the active file handler\n        &quot;&quot;&quot;\n        try:\n            self.log_handle.write(log_string)\n        except TypeError:\n            return False\n        return True\n\n    ########################################\n    # Sentence Parsers\n    ########################################\n    def gprmc(self):\n        &quot;&quot;&quot;Parse Recommended Minimum Specific GPS\/Transit data (RMC)Sentence.\n        Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status\n        &quot;&quot;&quot;\n\n        # UTC Timestamp\n        try:\n            utc_string = self.gps_segments[1]\n\n            if utc_string:  # Possible timestamp found\n                hours = (int(utc_string[0:2]) + self.local_offset) % 24\n                minutes = int(utc_string[2:4])\n                seconds = float(utc_string[4:])\n                self.timestamp = [hours, minutes, seconds]\n            else:  # No Time stamp yet\n                self.timestamp = [0, 0, 0.0]\n\n        except ValueError:  # Bad Timestamp value present\n            return False\n\n        # Date stamp\n        try:\n            date_string = self.gps_segments[9]\n\n            # Date string printer function assumes to be year &gt;=2000,\n            # date_string() must be supplied with the correct century argument to display correctly\n            if date_string:  # Possible date stamp found\n                day = int(date_string[0:2])\n                month = int(date_string[2:4])\n                year = int(date_string[4:6])\n                self.date = (day, month, year)\n            else:  # No Date stamp yet\n                self.date = (0, 0, 0)\n\n        except ValueError:  # Bad Date stamp value present\n            return False\n\n        # Check Receiver Data Valid Flag\n        if self.gps_segments[2] == 'A':  # Data from Receiver is Valid\/Has Fix\n\n            # Longitude \/ Latitude\n            try:\n                # Latitude\n                l_string = self.gps_segments[3]\n                lat_degs = int(l_string[0:2])\n                lat_mins = float(l_string[2:])\n                lat_hemi = self.gps_segments[4]\n\n                # Longitude\n                l_string = self.gps_segments[5]\n                lon_degs = int(l_string[0:3])\n                lon_mins = float(l_string[3:])\n                lon_hemi = self.gps_segments[6]\n            except ValueError:\n                return False\n\n            if lat_hemi not in self.__HEMISPHERES:\n                return False\n\n            if lon_hemi not in self.__HEMISPHERES:\n                return False\n\n            # Speed\n            try:\n                spd_knt = float(self.gps_segments[7])\n            except ValueError:\n                return False\n\n            # Course\n            try:\n                if self.gps_segments[8]:\n                    course = float(self.gps_segments[8])\n                else:\n                    course = 0.0\n            except ValueError:\n                return False\n\n            # TODO - Add Magnetic Variation\n\n            # Update Object Data\n            self._latitude = [lat_degs, lat_mins, lat_hemi]\n            self._longitude = [lon_degs, lon_mins, lon_hemi]\n            # Include mph and hm\/h\n            self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852]\n            self.course = course\n            self.valid = True\n\n            # Update Last Fix Time\n            self.new_fix_time()\n\n        else:  # Clear Position Data if Sentence is 'Invalid'\n            self._latitude = [0, 0.0, 'N']\n            self._longitude = [0, 0.0, 'W']\n            self.speed = [0.0, 0.0, 0.0]\n            self.course = 0.0\n            self.valid = False\n\n        return True\n\n    def gpgll(self):\n        &quot;&quot;&quot;Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude,\n        longitude, and fix status&quot;&quot;&quot;\n\n        # UTC Timestamp\n        try:\n            utc_string = self.gps_segments[5]\n\n            if utc_string:  # Possible timestamp found\n                hours = (int(utc_string[0:2]) + self.local_offset) % 24\n                minutes = int(utc_string[2:4])\n                seconds = float(utc_string[4:])\n                self.timestamp = [hours, minutes, seconds]\n            else:  # No Time stamp yet\n                self.timestamp = [0, 0, 0.0]\n\n        except ValueError:  # Bad Timestamp value present\n            return False\n\n        # Check Receiver Data Valid Flag\n        if self.gps_segments[6] == 'A':  # Data from Receiver is Valid\/Has Fix\n\n            # Longitude \/ Latitude\n            try:\n                # Latitude\n                l_string = self.gps_segments[1]\n                lat_degs = int(l_string[0:2])\n                lat_mins = float(l_string[2:])\n                lat_hemi = self.gps_segments[2]\n\n                # Longitude\n                l_string = self.gps_segments[3]\n                lon_degs = int(l_string[0:3])\n                lon_mins = float(l_string[3:])\n                lon_hemi = self.gps_segments[4]\n            except ValueError:\n                return False\n\n            if lat_hemi not in self.__HEMISPHERES:\n                return False\n\n            if lon_hemi not in self.__HEMISPHERES:\n                return False\n\n            # Update Object Data\n            self._latitude = [lat_degs, lat_mins, lat_hemi]\n            self._longitude = [lon_degs, lon_mins, lon_hemi]\n            self.valid = True\n\n            # Update Last Fix Time\n            self.new_fix_time()\n\n        else:  # Clear Position Data if Sentence is 'Invalid'\n            self._latitude = [0, 0.0, 'N']\n            self._longitude = [0, 0.0, 'W']\n            self.valid = False\n\n        return True\n\n    def gpvtg(self):\n        &quot;&quot;&quot;Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course&quot;&quot;&quot;\n        try:\n            course = float(self.gps_segments[1]) if self.gps_segments[1] else 0.0\n            spd_knt = float(self.gps_segments[5]) if self.gps_segments[5] else 0.0\n        except ValueError:\n            return False\n\n        # Include mph and km\/h\n        self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852)\n        self.course = course\n        return True\n\n    def gpgga(self):\n        &quot;&quot;&quot;Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude,\n        fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status&quot;&quot;&quot;\n\n        try:\n            # UTC Timestamp\n            utc_string = self.gps_segments[1]\n\n            # Skip timestamp if receiver doesn't have on yet\n            if utc_string:\n                hours = (int(utc_string[0:2]) + self.local_offset) % 24\n                minutes = int(utc_string[2:4])\n                seconds = float(utc_string[4:])\n            else:\n                hours = 0\n                minutes = 0\n                seconds = 0.0\n\n            # Number of Satellites in Use\n            satellites_in_use = int(self.gps_segments[7])\n\n            # Get Fix Status\n            fix_stat = int(self.gps_segments[6])\n\n        except (ValueError, IndexError):\n            return False\n\n        try:\n            # Horizontal Dilution of Precision\n            hdop = float(self.gps_segments[8])\n        except (ValueError, IndexError):\n            hdop = 0.0\n\n        # Process Location and Speed Data if Fix is GOOD\n        if fix_stat:\n\n            # Longitude \/ Latitude\n            try:\n                # Latitude\n                l_string = self.gps_segments[2]\n                lat_degs = int(l_string[0:2])\n                lat_mins = float(l_string[2:])\n                lat_hemi = self.gps_segments[3]\n\n                # Longitude\n                l_string = self.gps_segments[4]\n                lon_degs = int(l_string[0:3])\n                lon_mins = float(l_string[3:])\n                lon_hemi = self.gps_segments[5]\n            except ValueError:\n                return False\n\n            if lat_hemi not in self.__HEMISPHERES:\n                return False\n\n            if lon_hemi not in self.__HEMISPHERES:\n                return False\n\n            # Altitude \/ Height Above Geoid\n            try:\n                altitude = float(self.gps_segments[9])\n                geoid_height = float(self.gps_segments[11])\n            except ValueError:\n                altitude = 0\n                geoid_height = 0\n\n            # Update Object Data\n            self._latitude = [lat_degs, lat_mins, lat_hemi]\n            self._longitude = [lon_degs, lon_mins, lon_hemi]\n            self.altitude = altitude\n            self.geoid_height = geoid_height\n\n        # Update Object Data\n        self.timestamp = [hours, minutes, seconds]\n        self.satellites_in_use = satellites_in_use\n        self.hdop = hdop\n        self.fix_stat = fix_stat\n\n        # If Fix is GOOD, update fix timestamp\n        if fix_stat:\n            self.new_fix_time()\n\n        return True\n\n    def gpgsa(self):\n        &quot;&quot;&quot;Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in\n        fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical\n        Dilution of Precision, and fix status&quot;&quot;&quot;\n\n        # Fix Type (None,2D or 3D)\n        try:\n            fix_type = int(self.gps_segments[2])\n        except ValueError:\n            return False\n\n        # Read All (up to 12) Available PRN Satellite Numbers\n        sats_used = []\n        for sats in range(12):\n            sat_number_str = self.gps_segments[3 + sats]\n            if sat_number_str:\n                try:\n                    sat_number = int(sat_number_str)\n                    sats_used.append(sat_number)\n                except ValueError:\n                    return False\n            else:\n                break\n\n        # PDOP,HDOP,VDOP\n        try:\n            pdop = float(self.gps_segments[15])\n            hdop = float(self.gps_segments[16])\n            vdop = float(self.gps_segments[17])\n        except ValueError:\n            return False\n\n        # Update Object Data\n        self.fix_type = fix_type\n\n        # If Fix is GOOD, update fix timestamp\n        if fix_type &gt; self.__NO_FIX:\n            self.new_fix_time()\n\n        self.satellites_used = sats_used\n        self.hdop = hdop\n        self.vdop = vdop\n        self.pdop = pdop\n\n        return True\n\n    def gpgsv(self):\n        &quot;&quot;&quot;Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence\n        parsed, and data on each satellite present in the sentence&quot;&quot;&quot;\n        try:\n            num_sv_sentences = int(self.gps_segments[1])\n            current_sv_sentence = int(self.gps_segments[2])\n            sats_in_view = int(self.gps_segments[3])\n        except ValueError:\n            return False\n\n        # Create a blank dict to store all the satellite data from this sentence in:\n        # satellite PRN is key, tuple containing telemetry is value\n        satellite_dict = dict()\n\n        # Calculate  Number of Satelites to pull data for and thus how many segment positions to read\n        if num_sv_sentences == current_sv_sentence:\n            # Last sentence may have 1-4 satellites; 5 - 20 positions\n            sat_segment_limit = (sats_in_view - ((num_sv_sentences - 1) * 4)) * 5\n        else:\n            sat_segment_limit = 20  # Non-last sentences have 4 satellites and thus read up to position 20\n\n        # Try to recover data for up to 4 satellites in sentence\n        for sats in range(4, sat_segment_limit, 4):\n\n            # If a PRN is present, grab satellite data\n            if self.gps_segments[sats]:\n                try:\n                    sat_id = int(self.gps_segments[sats])\n                except (ValueError,IndexError):\n                    return False\n\n                try:  # elevation can be null (no value) when not tracking\n                    elevation = int(self.gps_segments[sats+1])\n                except (ValueError,IndexError):\n                    elevation = None\n\n                try:  # azimuth can be null (no value) when not tracking\n                    azimuth = int(self.gps_segments[sats+2])\n                except (ValueError,IndexError):\n                    azimuth = None\n\n                try:  # SNR can be null (no value) when not tracking\n                    snr = int(self.gps_segments[sats+3])\n                except (ValueError,IndexError):\n                    snr = None\n            # If no PRN is found, then the sentence has no more satellites to read\n            else:\n                break\n\n            # Add Satellite Data to Sentence Dict\n            satellite_dict[sat_id] = (elevation, azimuth, snr)\n\n        # Update Object Data\n        self.total_sv_sentences = num_sv_sentences\n        self.last_sv_sentence = current_sv_sentence\n        self.satellites_in_view = sats_in_view\n\n        # For a new set of sentences, we either clear out the existing sat data or\n        # update it as additional SV sentences are parsed\n        if current_sv_sentence == 1:\n            self.satellite_data = satellite_dict\n        else:\n            self.satellite_data.update(satellite_dict)\n\n        return True\n\n    ##########################################\n    # Data Stream Handler Functions\n    ##########################################\n\n    def new_sentence(self):\n        &quot;&quot;&quot;Adjust Object Flags in Preparation for a New Sentence&quot;&quot;&quot;\n        self.gps_segments = ['']\n        self.active_segment = 0\n        self.crc_xor = 0\n        self.sentence_active = True\n        self.process_crc = True\n        self.char_count = 0\n\n    def update(self, new_char):\n        &quot;&quot;&quot;Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*')\n        Function builds a list of received string that are validate by CRC prior to parsing by the  appropriate\n        sentence function. Returns sentence type on successful parse, None otherwise&quot;&quot;&quot;\n\n        valid_sentence = False\n\n        # Validate new_char is a printable char\n        ascii_char = ord(new_char)\n\n        if 10 &lt;= ascii_char &lt;= 126:\n            self.char_count += 1\n\n            # Write Character to log file if enabled\n            if self.log_en:\n                self.write_log(new_char)\n\n            # Check if a new string is starting ($)\n            if new_char == '$':\n                self.new_sentence()\n                return None\n\n            elif self.sentence_active:\n\n                # Check if sentence is ending (*)\n                if new_char == '*':\n                    self.process_crc = False\n                    self.active_segment += 1\n                    self.gps_segments.append('')\n                    return None\n\n                # Check if a section is ended (,), Create a new substring to feed\n                # characters to\n                elif new_char == ',':\n                    self.active_segment += 1\n                    self.gps_segments.append('')\n\n                # Store All Other printable character and check CRC when ready\n                else:\n                    self.gps_segments[self.active_segment] += new_char\n\n                    # When CRC input is disabled, sentence is nearly complete\n                    if not self.process_crc:\n\n                        if len(self.gps_segments[self.active_segment]) == 2:\n                            try:\n                                final_crc = int(self.gps_segments[self.active_segment], 16)\n                                if self.crc_xor == final_crc:\n                                    valid_sentence = True\n                                else:\n                                    self.crc_fails += 1\n                            except ValueError:\n                                pass  # CRC Value was deformed and could not have been correct\n\n                # Update CRC\n                if self.process_crc:\n                    self.crc_xor ^= ascii_char\n\n                # If a Valid Sentence Was received and it's a supported sentence, then parse it!!\n                if valid_sentence:\n                    self.clean_sentences += 1  # Increment clean sentences received\n                    self.sentence_active = False  # Clear Active Processing Flag\n\n                    if self.gps_segments[0] in self.supported_sentences:\n\n                        # parse the Sentence Based on the message type, return True if parse is clean\n                        if self.supported_sentences[self.gps_segments[0]](self):\n\n                            # Let host know that the GPS object was updated by returning parsed sentence type\n                            self.parsed_sentences += 1\n                            return self.gps_segments[0]\n\n                # Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete\n                if self.char_count &gt; self.SENTENCE_LIMIT:\n                    self.sentence_active = False\n\n        # Tell Host no new sentence was parsed\n        return None\n\n    def new_fix_time(self):\n        &quot;&quot;&quot;Updates a high resolution counter with current time when fix is updated. Currently only triggered from\n        GGA, GSA and RMC sentences&quot;&quot;&quot;\n        try:\n            self.fix_time = utime.ticks_ms()\n        except NameError:\n            self.fix_time = time.time()\n\n    #########################################\n    # User Helper Functions\n    # These functions make working with the GPS object data easier\n    #########################################\n\n    def satellite_data_updated(self):\n        &quot;&quot;&quot;\n        Checks if the all the GSV sentences in a group have been read, making satellite data complete\n        :return: boolean\n        &quot;&quot;&quot;\n        if self.total_sv_sentences &gt; 0 and self.total_sv_sentences == self.last_sv_sentence:\n            return True\n        else:\n            return False\n\n    def unset_satellite_data_updated(self):\n        &quot;&quot;&quot;\n        Mark GSV sentences as read indicating the data has been used and future updates are fresh\n        &quot;&quot;&quot;\n        self.last_sv_sentence = 0\n\n    def satellites_visible(self):\n        &quot;&quot;&quot;\n        Returns a list of of the satellite PRNs currently visible to the receiver\n        :return: list\n        &quot;&quot;&quot;\n        return list(self.satellite_data.keys())\n\n    def time_since_fix(self):\n        &quot;&quot;&quot;Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if\n        no fix has been found&quot;&quot;&quot;\n\n        # Test if a Fix has been found\n        if self.fix_time == 0:\n            return -1\n\n        # Try calculating fix time using utime; if not running MicroPython\n        # time.time() returns a floating point value in secs\n        try:\n            current = utime.ticks_diff(utime.ticks_ms(), self.fix_time)\n        except NameError:\n            current = (time.time() - self.fix_time) * 1000  # ms\n\n        return current\n\n    def compass_direction(self):\n        &quot;&quot;&quot;\n        Determine a cardinal or inter-cardinal direction based on current course.\n        :return: string\n        &quot;&quot;&quot;\n        # Calculate the offset for a rotated compass\n        if self.course &gt;= 348.75:\n            offset_course = 360 - self.course\n        else:\n            offset_course = self.course + 11.25\n\n        # Each compass point is separated by 22.5 degrees, divide to find lookup value\n        dir_index = floor(offset_course \/ 22.5)\n\n        final_dir = self.__DIRECTIONS[dir_index]\n\n        return final_dir\n\n    def latitude_string(self):\n        &quot;&quot;&quot;\n        Create a readable string of the current latitude data\n        :return: string\n        &quot;&quot;&quot;\n        if self.coord_format == 'dd':\n            formatted_latitude = self.latitude\n            lat_string = str(formatted_latitude[0]) + '\u00b0 ' + str(self._latitude[2])\n        elif self.coord_format == 'dms':\n            formatted_latitude = self.latitude\n            lat_string = str(formatted_latitude[0]) + '\u00b0 ' + str(formatted_latitude[1]) + &quot;' &quot; + str(formatted_latitude[2]) + '&quot; ' + str(formatted_latitude[3])\n        else:\n            lat_string = str(self._latitude[0]) + '\u00b0 ' + str(self._latitude[1]) + &quot;' &quot; + str(self._latitude[2])\n        return lat_string\n\n    def longitude_string(self):\n        &quot;&quot;&quot;\n        Create a readable string of the current longitude data\n        :return: string\n        &quot;&quot;&quot;\n        if self.coord_format == 'dd':\n            formatted_longitude = self.longitude\n            lon_string = str(formatted_longitude[0]) + '\u00b0 ' + str(self._longitude[2])\n        elif self.coord_format == 'dms':\n            formatted_longitude = self.longitude\n            lon_string = str(formatted_longitude[0]) + '\u00b0 ' + str(formatted_longitude[1]) + &quot;' &quot; + str(formatted_longitude[2]) + '&quot; ' + str(formatted_longitude[3])\n        else:\n            lon_string = str(self._longitude[0]) + '\u00b0 ' + str(self._longitude[1]) + &quot;' &quot; + str(self._longitude[2])\n        return lon_string\n\n    def speed_string(self, unit='kph'):\n        &quot;&quot;&quot;\n        Creates a readable string of the current speed data in one of three units\n        :param unit: string of 'kph','mph, or 'knot'\n        :return:\n        &quot;&quot;&quot;\n        if unit == 'mph':\n            speed_string = str(self.speed[1]) + ' mph'\n\n        elif unit == 'knot':\n            if self.speed[0] == 1:\n                unit_str = ' knot'\n            else:\n                unit_str = ' knots'\n            speed_string = str(self.speed[0]) + unit_str\n\n        else:\n            speed_string = str(self.speed[2]) + ' km\/h'\n\n        return speed_string\n\n    def date_string(self, formatting='s_mdy', century='20'):\n        &quot;&quot;&quot;\n        Creates a readable string of the current date.\n        Can select between long format: Januray 1st, 2014\n        or two short formats:\n        11\/01\/2014 (MM\/DD\/YYYY)\n        01\/11\/2014 (DD\/MM\/YYYY)\n        :param formatting: string 's_mdy', 's_dmy', or 'long'\n        :param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX)\n        :return: date_string  string with long or short format date\n        &quot;&quot;&quot;\n\n        # Long Format Januray 1st, 2014\n        if formatting == 'long':\n            # Retrieve Month string from private set\n            month = self.__MONTHS[self.date[1] - 1]\n\n            # Determine Date Suffix\n            if self.date[0] in (1, 21, 31):\n                suffix = 'st'\n            elif self.date[0] in (2, 22):\n                suffix = 'nd'\n            elif self.date[0] == (3, 23):\n                suffix = 'rd'\n            else:\n                suffix = 'th'\n\n            day = str(self.date[0]) + suffix  # Create Day String\n\n            year = century + str(self.date[2])  # Create Year String\n\n            date_string = month + ' ' + day + ', ' + year  # Put it all together\n\n        else:\n            # Add leading zeros to day string if necessary\n            if self.date[0] &lt; 10:\n                day = '0' + str(self.date[0])\n            else:\n                day = str(self.date[0])\n\n            # Add leading zeros to month string if necessary\n            if self.date[1] &lt; 10:\n                month = '0' + str(self.date[1])\n            else:\n                month = str(self.date[1])\n\n            # Add leading zeros to year string if necessary\n            if self.date[2] &lt; 10:\n                year = '0' + str(self.date[2])\n            else:\n                year = str(self.date[2])\n\n            # Build final string based on desired formatting\n            if formatting == 's_dmy':\n                date_string = day + '\/' + month + '\/' + year\n\n            else:  # Default date format\n                date_string = month + '\/' + day + '\/' + year\n\n        return date_string\n\n    # All the currently supported NMEA sentences\n    supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc,\n                           'GPGGA': gpgga, 'GLGGA': gpgga,\n                           'GPVTG': gpvtg, 'GLVTG': gpvtg,\n                           'GPGSA': gpgsa, 'GLGSA': gpgsa,\n                           'GPGSV': gpgsv, 'GLGSV': gpgsv,\n                           'GPGLL': gpgll, 'GLGLL': gpgll,\n                           'GNGGA': gpgga, 'GNRMC': gprmc,\n                           'GNVTG': gpvtg, 'GNGLL': gpgll,\n                           'GNGSA': gpgsa,\n                          }\n\nif __name__ == &quot;__main__&quot;:\n    pass\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/micropyGPS.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>These are the general instructions to upload the <span class=\"rnthl rntliteral\">micropyGPS<\/span> library to your board:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>First, make sure your board is running MicroPython firmware\u2014check the&nbsp;<a href=\"#prerequisites\" title=\"Prerequisites section\">Prerequisites section<\/a>.<\/li>\n\n\n\n<li>Create a new file in your IDE with the name&nbsp;micropyGPS.py&nbsp;and paste the previous code there. Save that file.<\/li>\n\n\n\n<li>Establish a serial communication with your board using your IDE.<\/li>\n\n\n\n<li>Upload the\u00a0<strong>micropyGPS.py<\/strong>\u00a0file to your board. In Thonny IDE, go to <strong>File <\/strong>> <strong>Save as&#8230;<\/strong> and select <strong>MicroPython Device<\/strong>\/<strong>Raspberry Pi Pico<\/strong>.<\/li>\n\n\n\n<li>At this point, the library should have been successfully uploaded to your board. Now, you can use the library functionalities in your code by importing the library <span class=\"rnthl rntliteral\">import micropyGPS<\/span>.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"rpi-pico-neo-m8n-gps-data-micropygps\">Raspberry Pi Pico with NEO-M8N: Getting GPS Data with MicroPython<\/h2>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">micropyGPS<\/span> library makes it easier to get GPS data in a format that is easy to understand.<\/p>\n\n\n\n<p>The following code shows how to use the library to get GPS data like latitude, longitude, altitude, date and time, number of visible satellites and HDOP (a measurement of how precise the signal is).<\/p>\n\n\n\n<p><strong>After importing the micropyGPS library <\/strong>to your board, you can run the following code.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n# Complete project details at https:\/\/RandomNerdTutorials.com\/raspberry-pi-pico-neo-m8n-gps-micropytho\r\n\r\nfrom machine import UART, Pin\r\nfrom time import sleep\r\nfrom micropyGPS import MicropyGPS\r\n\r\n# Instantiate the micropyGPS object\r\nmy_gps = MicropyGPS()\r\n\r\n# Define the UART pins and create a UART object\r\ngps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))\r\n\r\nwhile True:\r\n    try:\r\n        while gps_serial.any():\r\n            data = gps_serial.read()\r\n            for byte in data:\r\n                stat = my_gps.update(chr(byte))\r\n                if stat is not None:\r\n                    # Print parsed GPS data\r\n                    print('UTC Timestamp:', my_gps.timestamp)\r\n                    print('Date:', my_gps.date_string('long'))\r\n                    print('Latitude:', my_gps.latitude_string())\r\n                    print('Longitude:', my_gps.longitude_string())\r\n                    print('Altitude:', my_gps.altitude)\r\n                    print('Satellites in use:', my_gps.satellites_in_use)\r\n                    print('Horizontal Dilution of Precision:', my_gps.hdop)\r\n                    print()\r\n                    number = my_gps.latitude\r\n                    print(number)\r\n    except Exception as e:\r\n        print(f&quot;An error occurred: {e}&quot;)<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/NEO_M8N_GPS_Parsed_Data.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>Continue reading to learn how the code works, or skip to the <a href=\"#demonstration\" title=\"\">demonstration section<\/a>.<\/p>\n\n\n\n<p>First, import the required modules, including the <span class=\"rnthl rntliteral\">MicropyGPS<\/span> class from the <span class=\"rnthl rntliteral\">micropyGPS<\/span> module you imported previously.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>from machine import UART, Pin\nfrom time import sleep\nfrom micropyGPS import MicropyGPS<\/code><\/pre>\n\n\n\n<p>Create an instance of the <span class=\"rnthl rntliteral\">MicropyGPS<\/span> class called <span class=\"rnthl rntliteral\">my_gps<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Instantiate the micropyGPS object\nmy_gps = MicropyGPS()<\/code><\/pre>\n\n\n\n<p>Then, initialize a <span class=\"rnthl rntliteral\">UART<\/span> instance for serial communication with the module. We&#8217;re using UART 1 and GPIO 4 for TX and GPIO 5 for RX. We also define the baud rate for the GPS module (the NEO-M8N uses 9600).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>gps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))<\/code><\/pre>\n\n\n\n<p>Then, we create an infinite loop to continuously read GPS data.<\/p>\n\n\n\n<p>We check if there is new data available to read. If there is, we read the data and pass it to the <span class=\"rnthl rntliteral\">my_gps<\/span> instance using the <span class=\"rnthl rntliteral\">update()<\/span> method. <\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>while gps_serial.any():\n    data = gps_serial.read()\n    for byte in data:\n        stat = my_gps.update(chr(byte))<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">update()<\/span> method returns valid GPS sentences or <span class=\"rnthl rntliteral\">None<\/span> if that&#8217;s not the case. So, we check if we have valid data before proceeding.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>if stat is not None:<\/code><\/pre>\n\n\n\n<p>Then, we can access the GPS data by using the <span class=\"rnthl rntliteral\">micropyGPS<\/span> methods on the <span class=\"rnthl rntliteral\">my_gps<\/span> object that should contain the data gathered from the GPS module.<\/p>\n\n\n\n<p>The following lines show how to get time, date, latitude, longitude, altitude, number of satellites used, and HDOP.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Print parsed GPS data\nprint('UTC Timestamp:', my_gps.timestamp)\nprint('Date:', my_gps.date_string('long'))\nprint('Latitude:', my_gps.latitude_string())\nprint('Longitude:', my_gps.longitude_string())\nprint('Altitude:', my_gps.altitude)\nprint('Satellites in use:', my_gps.satellites_in_use)\nprint('Horizontal Dilution of Precision:', my_gps.hdop)\nprint()<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">micropyGPS<\/span> library supports other methods to get more GPS data and in different formats. We recommend you take a look at the <a href=\"https:\/\/github.com\/inmcm\/micropyGPS\/tree\/master?tab=readme-ov-file#micropygps\" target=\"_blank\" rel=\"noopener\" title=\"\">documentation <\/a>and see all the available options.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"demonstration\">Demonstration<\/h3>\n\n\n\n<p>After uploading the <span class=\"rnthl rntliteral\">micropyGPS<\/span> module to your board, you can run this previous code to get GPS data.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"470\" height=\"114\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?resize=470%2C114&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Run code Thonny IDE\" class=\"wp-image-144594\" style=\"width:470px;height:auto\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?w=470&amp;quality=100&amp;strip=all&amp;ssl=1 470w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?resize=300%2C73&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 470px) 100vw, 470px\" \/><\/figure><\/div>\n\n\n<p>Make sure you place your board or antenna next to a window, or preferably outside so that it can get data from satellites. You may need to wait a few minutes until it gets a position fix and can send valid data. The NEO-M8N GPS module&#8217;s blue LED will start blinking when it&#8217;s ready.<\/p>\n\n\n\n<p>In the MicroPython shell, you should get information about your current location, date and time in UTC, number of satellites, and HDOP. The higher the number of satellites and the lower the HDO the better.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"731\" height=\"430\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-Get-Parsed-GPS-Data.jpg?resize=731%2C430&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Raspberry Pi Pico with NEO-M8N Get GPS data with MicroPython Demonstration\" class=\"wp-image-166792\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-Get-Parsed-GPS-Data.jpg?w=731&amp;quality=100&amp;strip=all&amp;ssl=1 731w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-Get-Parsed-GPS-Data.jpg?resize=300%2C176&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 731px) 100vw, 731px\" \/><\/figure><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"RPi-Pico-neo-m8n-gps-logger-google-earth\">GPS Logger and Display Path on Google Earth<\/h2>\n\n\n\n<p>Now that you&#8217;re more familiar with using the NEO-M8N GPS module with the Raspberry Pi Pico, let&#8217;s create a GPS logger that records your location over time to a file on a microSD card. Then, you can modify and use that file on Google Earth to visualize how the location changed over time (path).<\/p>\n\n\n\n<p class=\"rntbox rntclgreen\">Recommended reading: <a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-microsd-card-micropython\/\">Raspberry Pi Pico: MicroSD Card Guide with Datalogging Example (MicroPython)<\/a>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-GPS-logger.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"GPS Logger and Display Path on Google Earth - RPi Pico and NEO-M8N\" class=\"wp-image-166809\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-GPS-logger.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-GPS-logger.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Project Overview<\/h3>\n\n\n\n<p>Here&#8217;s a quick overview of how this project works:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The Raspberry Pi Pico is connected to the NEO-M8N GPS Module and gets data about the location;<\/li>\n\n\n\n<li>Every three seconds, we save the location data (latitude, longitude, and altitude) to a file on the microSD card;<\/li>\n\n\n\n<li>To visualize the data on Google Earth, we&#8217;ll show you how to manually convert a <em>.txt<\/em> file with the location coordinates to a <em>.kml<\/em> file that Google Earth can read;<\/li>\n\n\n\n<li>We&#8217;ll show you how to upload the <em>.kml<\/em> to Google Earth to visualize that path.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Parts Required<\/h3>\n\n\n\n<p>Here&#8217;s a list of the parts required for this project:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/raspberry-pi-pico-w\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Raspberry Pi Pico<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/neo-m8n-gps-module\/\" target=\"_blank\" rel=\"noopener\" title=\"\">NEO-M8N GPS Module<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/sd-card-module\/\" target=\"_blank\" rel=\"noopener\" title=\"\">MicroSD card Module<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/microsd-card-raspberry-pi-16gb-class-10\/\" target=\"_blank\" rel=\"noopener\" title=\"\">MicroSD card<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/mb-102-solderless-breadboard-830-points\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Breadboard<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/jumper-wires-kit-120-pieces\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Jumper wires<\/a><\/li>\n<\/ul>\n\n\n<p>You can use the preceding links or go directly to <a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\">MakerAdvisor.com\/tools<\/a> to find all the parts for your projects at the best price!<\/p><p style=\"text-align:center;\"><a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2017\/10\/header-200.png?w=1200&#038;quality=100&#038;strip=all&#038;ssl=1\"><\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Wiring the Circuit<\/h3>\n\n\n\n<p>Wire the GPS Module to the Pico UART pins (as we did previously) and the microSD card module to SPI pins of your choice. We&#8217;ll use the pins shown in the following schematic diagram and tables &#8211; use those as a reference.<\/p>\n\n\n\n<p>Recommended reading: <a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-w-pinout-gpios\/\" title=\"\">Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained<\/a>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"557\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS-microsdcard_bb.png?resize=1200%2C557&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Raspberry Pi Pico GPS logger with NEO-M8N and microSD card\" class=\"wp-image-166815\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS-microsdcard_bb.png?w=1352&amp;quality=100&amp;strip=all&amp;ssl=1 1352w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS-microsdcard_bb.png?resize=300%2C139&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS-microsdcard_bb.png?resize=1024%2C476&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Raspberry-Pi-Pico-NEO-M8N-GPS-microsdcard_bb.png?resize=768%2C357&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>NEO-6M GPS Module<\/strong><\/td><td><strong>Raspberry Pi Pico<\/strong><\/td><\/tr><tr><td>VCC<\/td><td><span class=\"rnthl rntcred\">3V3(OUT)<\/span> (Pin 36)<\/td><\/tr><tr><td>RX<\/td><td>TX (<span class=\"rnthl rntcyellow\">GPIO 4<\/span>) (Pin 6)<\/td><\/tr><tr><td>TX<\/td><td>RX (<span class=\"rnthl rntcgreen\">GPIO 5<\/span>) (Pin 7)<\/td><\/tr><tr><td>GND<\/td><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>microSD Card Module<\/strong><\/td><td><strong>Raspberry Pi Pico<\/strong><\/td><\/tr><tr><td>3V3*<\/td><td><span class=\"rnthl rntcred\">3V3<\/span>(OUT) (Pin 36)<\/td><\/tr><tr><td>CS<\/td><td><span class=\"rnthl rntclgray\">GPIO 17<\/span> (Pin 22)<\/td><\/tr><tr><td>MOSI<\/td><td><span class=\"rnthl rntcbrown\">GPIO 19<\/span> (Pin 25)<\/td><\/tr><tr><td> CLK<\/td><td><span class=\"rnthl rntcorange\">GPIO 18<\/span> (Pin 24)<\/td><\/tr><tr><td>MISO<\/td><td><span class=\"rnthl rntclblue\">GPIO 16<\/span> (Pin 21)<\/td><\/tr><tr><td>GND<\/td><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">* some microSD card modules require 5V instead of 3V3. If that&#8217;s the case, connect the VCC of the microSD card to the VBUS pin.<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Importing the sdcard.py Module<\/h3>\n\n\n\n<p>At the moment, there isn\u2019t much support in terms of libraries to use the microSD card with the Raspberry Pi Pico. We\u2019ve found the <em>sdcard.py<\/em> module that seems to work just fine to handle files on the microSD card. Follow the next steps to install the library.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/raw.githubusercontent.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/refs\/heads\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/sd_card\/sdcard.py\" target=\"_blank\" rel=\"noopener\" title=\"\"><strong>Click here to download the <em>sdcard.py<\/em> library code.<\/strong><\/a><\/li>\n\n\n\n<li>Create a new file in Thonny IDE and copy the library code.<\/li>\n\n\n\n<li>Go to&nbsp;<strong>File<\/strong>&nbsp;&gt;&nbsp;<strong>Save&nbsp;as<\/strong> and select&nbsp;<strong>Raspberry Pi Pico<\/strong>.<\/li>\n\n\n\n<li>Name the file&nbsp;<a href=\"https:\/\/raw.githubusercontent.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/refs\/heads\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/sd_card\/sdcard.py\" target=\"_blank\" rel=\"noopener\" title=\"\">sdcard.py<\/a>&nbsp;and click&nbsp;<strong>OK<\/strong>&nbsp;to save the file on the Raspberry Pi Pico.<\/li>\n<\/ol>\n\n\n\n<p>And that\u2019s it. The library was uploaded to your board. Now, you can use the library functionalities in your code by importing the library.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"testing-microsd-card\">Testing the MicroSD Card<\/h4>\n\n\n\n<p>Upload the following code to your Raspberry Pi Pico to check if it can communicate with the microSD card.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n# Complete project details at https:\/\/RandomNerdTutorials.com\/raspberry-pi-pico-neo-m8n-gps-micropytho\r\n\r\nfrom machine import SPI, Pin\r\nimport sdcard, os\r\n\r\n# Constants\r\nSPI_BUS = 0\r\nSCK_PIN = 18\r\nMOSI_PIN = 19\r\nMISO_PIN = 16\r\nCS_PIN = 17\r\nSD_MOUNT_PATH = '\/sd'\r\n\r\ntry:\r\n    # Init SPI communication\r\n    spi = SPI(SPI_BUS,sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN), miso=Pin(MISO_PIN))\r\n    cs = Pin(CS_PIN)\r\n    sd = sdcard.SDCard(spi, cs)\r\n    # Mount microSD card\r\n    os.mount(sd, SD_MOUNT_PATH)\r\n    # List files on the microSD card\r\n    print(os.listdir(SD_MOUNT_PATH))\r\n    \r\nexcept Exception as e:\r\n    print('An error occurred:', e)<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/Test_Micro_SD_Card.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>The previous code starts SPI communication on the pins that the microSD card is connected to, tries to mount the microSD card, and then, tries to list the files there.<\/p>\n\n\n\n<p>If you\u2019re using different SPI pins, you should modify the code accordingly. Also notice that depending on the pins used, you might need to change the SPI bus. On the Raspberry Pi Pico pinout, you can see which pins are on SPI bus 1 or 0.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>SPI_BUS = 0\nSCK_PIN = 18\nMOSI_PIN = 19\nMISO_PIN = 16\nCS_PIN = 17<\/code><\/pre>\n\n\n\n<p>Run the previous code on your Raspberry Pi Pico by clicking on the Thonny IDE run green button.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"470\" height=\"114\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?resize=470%2C114&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Run code in Thonny IDE\" class=\"wp-image-144594\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?w=470&amp;quality=100&amp;strip=all&amp;ssl=1 470w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?resize=300%2C73&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 470px) 100vw, 470px\" \/><\/figure><\/div>\n\n\n<p>You should get a similar message on the Shell (see the screenshot below). If that\u2019s the case, it means everything is working as expected. If not, check the wiring, if the microSD card is properly inserted, and if you\u2019re using the right SPI Bus number for the pins you\u2019re using.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"702\" height=\"242\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/10\/mount-micro-sd-card-successful-thonny-ide.png?resize=702%2C242&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"microsd card mount successfully Thonny IDE RPI Pico - Testing\" class=\"wp-image-163484\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/10\/mount-micro-sd-card-successful-thonny-ide.png?w=702&amp;quality=100&amp;strip=all&amp;ssl=1 702w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/10\/mount-micro-sd-card-successful-thonny-ide.png?resize=300%2C103&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 702px) 100vw, 702px\" \/><\/figure><\/div>\n\n\n<p>If everything is working as expected, you can now test the code to log GPS data to the microSD card.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Log GPS Data to the microSD Card &#8211; Code<\/h3>\n\n\n\n<p>The following code initializes the microSD card and the GPS module and saves GPS data as soon as the data points are available every three seconds (you can change the delay time).<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n# Complete project details at https:\/\/RandomNerdTutorials.com\/raspberry-pi-pico-neo-m8n-gps-micropytho\r\n\r\nfrom machine import UART, Pin, Timer, SPI\r\nfrom time import sleep, ticks_ms, ticks_diff\r\nfrom micropyGPS import MicropyGPS\r\nimport sdcard, os\r\n\r\n# microSD card pin definition\r\nSPI_BUS = 0\r\nSCK_PIN = 18\r\nMOSI_PIN = 19\r\nMISO_PIN = 16\r\nCS_PIN = 17\r\n\r\n# microSD card filesystem mount path\r\nSD_MOUNT_PATH = '\/sd'\r\n# file path to save data\r\nFILE_PATH = '\/sd\/data.txt'\r\n\r\n# Init SPI communication for the microSD card\r\nspi = SPI(SPI_BUS, sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN), miso=Pin(MISO_PIN))\r\ncs = Pin(CS_PIN)\r\n\r\n# Instantiate the micropyGPS object\r\nmy_gps = MicropyGPS()\r\n\r\n# Define the UART pins and create a UART object\r\ngps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))\r\n\r\ndef mount_sdcard(spi, cs_pin):\r\n    try:\r\n        sd = sdcard.SDCard(spi, cs_pin)\r\n        # Mount microSD card\r\n        os.mount(sd, SD_MOUNT_PATH)\r\n        # List files on the microSD card\r\n        print(os.listdir(SD_MOUNT_PATH))\r\n    \r\n    except Exception as e:\r\n        print('An error occurred mounting the SD card:', e)\r\n        \r\ndef write_to_sd(data, file_path):\r\n    try:\r\n        # Open the file in append mode ('a')\r\n        with open(file_path, 'a') as file:\r\n            file.write(data + '\\n')\r\n        print(&quot;Data logged successfully.&quot;)\r\n    except Exception as e:\r\n        print(f&quot;An error occurred while writing to SD card: {e}&quot;)\r\n\r\n# Convert Longitude and Latitude to decimal degrees\r\ndef convert_to_decimal_degrees(coord):\r\n    degrees = coord[0]\r\n    minutes = coord[1]\r\n    hemisphere = coord[2]\r\n    \r\n    decimal_degrees = degrees + (minutes \/ 60)\r\n    \r\n    # Make the value negative if it's in the western or southern hemisphere\r\n    if hemisphere in ['S', 'W']:\r\n        decimal_degrees *= -1\r\n    \r\n    return decimal_degrees\r\n\r\n# Mount SD card\r\nmount_sdcard(spi, cs)\r\n\r\n# Initialize a timer variable\r\nlast_print_time = ticks_ms()  # Get the current time in milliseconds\r\n\r\nwhile True:\r\n    try:\r\n        # Check if data is available in the UART buffer\r\n        while gps_serial.any():\r\n            data = gps_serial.read()\r\n            for byte in data:\r\n                stat = my_gps.update(chr(byte))\r\n                if stat is not None:\r\n                    # Check if 3 seconds have passed since the last print\r\n                    current_time = ticks_ms()\r\n                    if ticks_diff(current_time, last_print_time) &gt;= 3000:\r\n                        # Get longitude, latitude, and altitude\r\n                        longitude = convert_to_decimal_degrees(my_gps.longitude)\r\n                        latitude = convert_to_decimal_degrees(my_gps.latitude)\r\n                        altitude = my_gps.altitude\r\n                        GPS_data = f&quot;{longitude},{latitude},{altitude}&quot;\r\n                        \r\n                        # Print the GPS data\r\n                        print(GPS_data)\r\n                        print()\r\n                        \r\n                        # Save data on the microSD card\r\n                        write_to_sd(GPS_data, FILE_PATH);\r\n                        \r\n                        # Update the last print time\r\n                        last_print_time = current_time\r\n            \r\n    except Exception as e:\r\n        print(f&quot;An error occurred: {e}&quot;)<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/Raspberry-Pi-Pico\/MicroPython\/NEO_M8N_GPS_SD_Card_Logger.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>To save data in a format that Google Earth can read, we need to save longitude, latitude, and altitude in this order separated by commas (the latitude and longitude need to be in decimal degrees format). That&#8217;s what we do in this example. Later, we need to convert that information to a <em>.kml<\/em> file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>Start by importing the required modules and classes.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>from machine import UART, Pin, Timer, SPI\nfrom time import sleep, ticks_ms, ticks_diff\nfrom micropyGPS import MicropyGPS\nimport sdcard, os<\/code><\/pre>\n\n\n\n<p>Define the pins you&#8217;re using to interface with the microSD card.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># microSD card pin definition\nSPI_BUS = 0\nSCK_PIN = 18\nMOSI_PIN = 19\nMISO_PIN = 16\nCS_PIN = 17<\/code><\/pre>\n\n\n\n<p>Create variables to save the microSD card filesystem mount path and the file path where you&#8217;ll save the data. In our case, in a file called <em>data.txt<\/em>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># microSD card filesystem mount path\nSD_MOUNT_PATH = '\/sd'\n# file path to save data\nFILE_PATH = '\/sd\/data.txt'<\/code><\/pre>\n\n\n\n<p>Initialize the SPI communication for the microSD card.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Init SPI communication for the microSD card\nspi = SPI(SPI_BUS, sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN), miso=Pin(MISO_PIN))\ncs = Pin(CS_PIN)<\/code><\/pre>\n\n\n\n<p>Initialize the <span class=\"rnthl rntliteral\">micropyGPS<\/span> object. We&#8217;re calling it <span class=\"rnthl rntliteral\">my_gps<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Instantiate the micropyGPS object\nmy_gps = MicropyGPS()<\/code><\/pre>\n\n\n\n<p>Create a new <span class=\"rnthl rntliteral\">UART<\/span> object on the pins that will be used to interface with the microSD card.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Define the UART pins and create a UART object\ngps_serial = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))<\/code><\/pre>\n\n\n\n<p>The following function initializes and mounts the microSD card on the spi bus and cs pin defined earlier.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>def mount_sdcard(spi, cs_pin):\n    try:\n        sd = sdcard.SDCard(spi, cs_pin)\n        # Mount microSD card\n        os.mount(sd, SD_MOUNT_PATH)\n        # List files on the microSD card\n        print(os.listdir(SD_MOUNT_PATH))\n    \n    except Exception as e:\n        print('An error occurred mounting the SD card:', e)<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">write_to_sd()<\/span> function appends data to a file on the microSD card. The first argument is the string that you want to save to the file, and the second argument is the file path. This function will append data to a file if it already exists, or it will create it if it doesn&#8217;t exist yet.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>def write_to_sd(data, file_path):\n    try:\n        # Open the file in append mode ('a')\n        with open(file_path, 'a') as file:\n            file.write(data + '\\n')\n        print(\"Data logged successfully.\")\n    except Exception as e:\n        print(f\"An error occurred while writing to SD card: {e}\")<\/code><\/pre>\n\n\n\n<p>The following function converts the latitude and longitude data in the format obtained from the <span class=\"rnthl rntliteral\">micropyGPS<\/span> library to decimal degrees (the format used by <em>.kml<\/em> files).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Convert Longitude and Latitude to decimal degrees\ndef convert_to_decimal_degrees(coord):\n    degrees = coord&#091;0]\n    minutes = coord&#091;1]\n    hemisphere = coord&#091;2]\n    \n    decimal_degrees = degrees + (minutes \/ 60)\n    \n    # Make the value negative if it's in the western or southern hemisphere\n    if hemisphere in &#091;'S', 'W']:\n        decimal_degrees *= -1\n    \n    return decimal_degrees<\/code><\/pre>\n\n\n\n<p>After all variables and functions definitions, call the <span class=\"rnthl rntliteral\">mount_sdcard()<\/span> function to initialize and mount the microSD card.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>mount_sdcard(spi, cs)<\/code><\/pre>\n\n\n\n<p>Then, create a variable that will save how many milliseconds have passed since the program started running (this variable will be used to check how long has passed since the last time we recorded a reading).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>last_print_time = ticks_ms()  # Get the current time in milliseconds<\/code><\/pre>\n\n\n\n<p>Then, we create an infinite loop to continuously read and log GPS data.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>while True:\n    try:<\/code><\/pre>\n\n\n\n<p>We check if there is new data available to read. If there is, we read the data and pass it to the <span class=\"rnthl rntliteral\">my_gps<\/span> instance using the <span class=\"rnthl rntliteral\">update()<\/span> method. <\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>while gps_serial.any():\n    data = gps_serial.read()\n    for byte in data:\n        stat = my_gps.update(chr(byte))<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">update()<\/span> method returns valid GPS sentences or <span class=\"rnthl rntliteral\">None<\/span> if that&#8217;s not the case. So, we check if we have valid data before proceeding.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>if stat is not None:<\/code><\/pre>\n\n\n\n<p>Then, we can access the GPS data by using the <span class=\"rnthl rntliteral\">micropyGPS<\/span> methods on the <span class=\"rnthl rntliteral\">my_gps<\/span> object that should contain the data gathered from the GPS module.<\/p>\n\n\n\n<p>We check if three seconds have passed since the last time we logged the GPS data and we get the current longitude, latitude, and altitude. We convert the longitude and latitude to decimal degrees.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>if ticks_diff(current_time, last_print_time) &gt;= 3000:\n    # Get longitude, latitude, and altitude\n    longitude = convert_to_decimal_degrees(my_gps.longitude)\n    latitude = convert_to_decimal_degrees(my_gps.latitude)\n    altitude = my_gps.altitude<\/code><\/pre>\n\n\n\n<p>Then, we concatenate the longitude, latitude, and altitude in a string separated by commas.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>GPS_data = f\"{longitude},{latitude},{altitude}\"<\/code><\/pre>\n\n\n\n<p>We print that data on the shell.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>print(GPS_data)<\/code><\/pre>\n\n\n\n<p>Finally, we write that data to the microSD card by calling the <span class=\"rnthl rntliteral\">write_to_sd()<\/span> function we create earlier and passing as arguments the <span class=\"rnthl rntliteral\">GPS_data<\/span> and <span class=\"rnthl rntliteral\">FILE_PATH<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>write_to_sd(GPS_data, FILE_PATH);<\/code><\/pre>\n\n\n\n<p>In the end, we update the <span class=\"rnthl rntliteral\">last_print_time<\/span> variable to the current time.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>last_print_time = current_time<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Testing the Project<\/h3>\n\n\n\n<p>After importing all the required libraries, you can run the code on the Raspberry Pi Pico to test it. Make sure the module or antenna is located outside or close to a window so that it can get data from the satellites.<\/p>\n\n\n\n<p>You should get something similar in the Shell:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"689\" height=\"323\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-log-data-to-microsd-card.jpg?resize=689%2C323&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"RPi Pico Log GPS Data to a microSD card\" class=\"wp-image-166794\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-log-data-to-microsd-card.jpg?w=689&amp;quality=100&amp;strip=all&amp;ssl=1 689w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-log-data-to-microsd-card.jpg?resize=300%2C141&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 689px) 100vw, 689px\" \/><\/figure><\/div>\n\n\n<h4 class=\"wp-block-heading\">Uploading the Code to the Raspberry Pi Pico<\/h4>\n\n\n\n<p><strong>Important note<\/strong>: just running the file with Thonny IDE doesn\u2019t copy it permanently to the board\u2019s filesystem. This means that if you unplug it from your computer and apply power to the Pico board, nothing will happen because it doesn\u2019t have any Python files saved on its filesystem. The Thonny IDE&nbsp;<em>Run&nbsp;<\/em>function is useful to test the code, but if you want to upload it permanently to your board, you need to create and save a file to the board filesystem. If you want to disconnect the Pico from your computer and go for a walk with it to record the GPS data to create a path, you need to upload the code to the board. Follow the next steps:<\/p>\n\n\n\n<p><strong>1)<\/strong> Stop the execution of the previous program by clicking on the&nbsp;<strong>Stop&nbsp;<\/strong>button if you haven\u2019t already.<\/p>\n\n\n\n<p><strong>2) <\/strong>With the code copied to Thony IDE, go to <strong>File<\/strong> &gt; <strong>Save as..<\/strong>. Then, select&nbsp;<strong>Raspberry Pi Pico<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"214\" height=\"203\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/05\/Save-file-to-Raspberry-Pi-Pico.png?resize=214%2C203&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Save Files to Raspberry Pi Pico Thonny IDE\" class=\"wp-image-130899\"\/><\/figure><\/div>\n\n\n<p><strong>3) <\/strong>Save the file with the following name:&nbsp;<span class=\"rnthl rntliteral\"><strong>main.py<\/strong><\/span>.<\/p>\n\n\n\n<p><strong>Note<\/strong>: When you name a file&nbsp;<em>main.py<\/em>, the Raspberry Pi Pico will run that file automatically on boot. If you call it a different name, it will still be saved on the board filesystem, but it will not run automatically on boot.<\/p>\n\n\n\n<p><strong>4) <\/strong>Finally, click&nbsp;<strong>OK&nbsp;<\/strong>to proceed.<\/p>\n\n\n\n<p>Now, you can disconnect the RPi Pico from the computer and power it using a portable power source like a battery or portable charger.<\/p>\n\n\n\n<p>Go for a walk with your circuit so that it starts recording your coordinates over time and you can obtain a considerable amount of points to design a path.<\/p>\n\n\n\n<p>After recording some data, disconnect the microSD card from the module and connect it to your computer. The microSD card should have a <em>data.txt<\/em> file with the coordinates of your path.<\/p>\n\n\n\n<p>For you to upload that data to Google Earth and design a path, we need to convert that file to <em>.kml<\/em> format.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">KML Files<\/h3>\n\n\n\n<p>KML is a file format used to display geographic data in an Earth browser such as Google Earth. KML uses a tag-based structure with nested elements and attributes and is based on the XML standard. We won&#8217;t go into detail about the structure of this file. If you want to learn more you can <a href=\"https:\/\/developers.google.com\/kml\/documentation\/kml_tut\" target=\"_blank\" rel=\"noopener\" title=\"\">check this tutorial about KML files by Google<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Converting your data.txt file to .kml format<\/h4>\n\n\n\n<p><strong>1)<\/strong> Open the <em>data.txt<\/em> file with the GPS data gathered from the GPS module.<\/p>\n\n\n\n<p><strong>2)<\/strong> Edit your file so that your coordinates are between the <span class=\"rnthl rntliteral\">&lt;coordinates&gt;&lt;\/coordinates&gt;<\/span> tags as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;<br>&lt;kml xmlns=\"http:\/\/www.opengis.net\/kml\/2.2\"&gt;<br>&lt;Document&gt;<br>&lt;Style id=\"yellowPoly\"&gt;<br>&lt;LineStyle&gt;<br>&lt;color&gt;7f00ffff&lt;\/color&gt;<br>&lt;width&gt;4&lt;\/width&gt;<br>&lt;\/LineStyle&gt;<br>&lt;PolyStyle&gt;<br>&lt;color&gt;7f00ff00&lt;\/color&gt;<br>&lt;\/PolyStyle&gt;<br>&lt;\/Style&gt;<br>&lt;Placemark&gt;&lt;styleUrl&gt;#yellowPoly&lt;\/styleUrl&gt;<br>&lt;LineString&gt;<br>&lt;extrude&gt;1&lt;\/extrude&gt;<br>&lt;tesselate&gt;1&lt;\/tesselate&gt;<br>&lt;altitudeMode&gt;absolute&lt;\/altitudeMode&gt;<br>&lt;coordinates&gt;<br><strong>YOUR COORDINATES GO HERE<br><\/strong>&lt;\/coordinates&gt;<br>&lt;\/LineString&gt;&lt;\/Placemark&gt;<br>&lt;\/Document&gt;&lt;\/kml&gt;<\/pre>\n\n\n\n<p><strong>3)<\/strong> Save that file.<\/p>\n\n\n\n<p><strong>4)<\/strong> Edit its name from <strong><em>data.txt<\/em><\/strong> to <strong><em>data.kml.<\/em><\/strong> You&#8217;ll receive a warning about changing the file format. Accept it. Now, the file should be usable to upload to Google Earth.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Display the Path on Google Earth<\/h3>\n\n\n\n<p>Now, follow the next steps to display and visualize your path on Google Earth.<\/p>\n\n\n\n<p><strong>1) <\/strong>Go to the <a href=\"https:\/\/earth.google.com\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Google Earth Website<\/a>.<\/p>\n\n\n\n<p><strong>2)<\/strong> Create a new project and give it a name.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"242\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/1-google-earth-create-project.png?resize=750%2C242&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Google Earth - Create Project\" class=\"wp-image-166721\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/1-google-earth-create-project.png?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/1-google-earth-create-project.png?resize=300%2C97&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p><strong>3)<\/strong> Go to <strong>File<\/strong> &gt; <strong>Import File to Project<\/strong> &gt; <strong>Upload from device<\/strong>.<\/p>\n\n\n\n<p><strong>4) <\/strong>Select the <em>.kml<\/em> file created previously.<\/p>\n\n\n\n<p><strong>5)<\/strong> Your path will be displayed on Google Earth with a yellow outline.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"526\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-Display-Path-Google-Earth.jpg?resize=750%2C526&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Rpi Pico with NEO-M8N GPS Logger - Display path on Google Earth\" class=\"wp-image-166793\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-Display-Path-Google-Earth.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/RPi-Pico-Display-Path-Google-Earth.jpg?resize=300%2C210&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>And that&#8217;s it! You learned how to log GPS data to a file on a microSD card and how to use that data to display a path on Google Earth.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>In this guide, you learned how to use the NEO-M8N GPS Module with the Raspberry Pi Pico to get data about location, time, and more. We&#8217;ve shown you how to get raw GPS data and how to parse data.<\/p>\n\n\n\n<p>Finally, we created a project to save the location data to a file on a microSD card. You learned how to convert the data to a format that Google Earth can read to design and display a path.<\/p>\n\n\n\n<p>We hope you&#8217;ve found this guide useful. We have guides for other modules that you may find useful:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-ds1307-rtc-micropython\/\">Raspberry Pi Pico: DS1307 RTC Module \u2013 Keep Track of Time (MicroPython)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-microsd-card-micropython\/\">Raspberry Pi Pico: MicroSD Card Guide with Datalogging Example (MicroPython)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-ssd1306-oled-micropython\/\">Raspberry Pi Pico: SSD1306 OLED Display (MicroPython)<\/a><\/li>\n<\/ul>\n\n\n\n<p>To learn more about the Raspberry Pi Pico, take a look at our resources:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-w-micropython-ebook\/\" title=\"\">Learn Raspberry Pi Pico with Micropython (eBook)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/projects-raspberry-pi-pico\/\" title=\"\"><strong>All our Raspberry Pi Pico Projects and Guides<\/strong><\/a><\/li>\n<\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Getting started guide for the NEO-M8N GPS Module with the Raspberry Pi Pico. Learn how to interface the module with the Pico board and get data about location and time. &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"Raspberry Pi Pico: NEO-M8N GPS Logger and Display on Google Earth (MicroPython)\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/raspberry-pi-pico-neo-m8n-gps-micropython\/#more-166784\" aria-label=\"Read more about Raspberry Pi Pico: NEO-M8N GPS Logger and Display on Google Earth (MicroPython)\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":5,"featured_media":166865,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[324,326],"tags":[],"class_list":["post-166784","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-raspberry-pi-pico","category-raspberry-pi-pico-micropython"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/01\/Rpi-Pico-M8N-GPS-Module-Display-Google-Earth-f.jpg?fit=1920%2C1080&quality=100&strip=all&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/166784","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/comments?post=166784"}],"version-history":[{"count":19,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/166784\/revisions"}],"predecessor-version":[{"id":167265,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/166784\/revisions\/167265"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/166865"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=166784"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=166784"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=166784"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}