Introduction
This guide will help you to build a Pico GPS tracker using the NEO-6M GPS module and OLED display. You’ll learn how to wire the components together and write a Micropython script to read data from the GPS module and display it on the OLED display.
Thank You, PCBWay:
This project is successfully completed because of the support and help from PCBWay. Guys if you have a PCB project, please see their website and get exciting bargains and coupons.
PCBway is the best prototype PCB company offering great PCB boards at lower prices If you sign-up using this link you will get beginner and sign-up coupon rewards. https://www.pcbway.com
Global Positioning System, or GPS, technology allows accurate Tracking of residents, assets, and units. GPS allows exact location determination, route optimization, asset security, routine tracking, and real-time monitoring. this project allows you to create a portable GPS tracking device that displays accurate location coordinates on the OLED screen.
Required Material
- Raspberry Pi Pico
- NEO-6M GPS Module
- OLED Display
- Jumper Wires
- Breadboard (optional)
Raspberry Pi Pico
The Raspberry Pi Pico is a small microcontroller board based on the RP2040 microcontroller chip. It provides GPIO pins for connecting and controlling multiple electronic components. The Pico runs multiple languages, including MicroPython and C/C++, making programming and interacting with many features easy.
Raspberry Pi Pico, is a highly capable microcontroller featuring dual-core ARM Cortex-M0+ processors clocked at 133MHz. It has 256KB of RAM and 2MB of onboard QSPI Flash memory for storing code and data. With 30 GPIO pins. which offers multi-interfacing options, including SPI, I2C, UART, ADC, and PWM channels.
NEO-6M GPS Module
NEO-6M GPS module is a high-performance, highly reliable GPS receiver. The module receives signals from GPS satellites and It uses the NMEA protocol to transmit GPS information, such as latitude, longitude, altitude, and satellite status and time.
Through UART communication, the module provides accurate GPS information for applications such as navigation, tracking, and geolocation.
NEO-6M GPS module Specifications
- 5Hz position update rate
- Operating temperature range: -40°C to 85°C
- UART TTL socket for easy communication
- EEPROM for saving configuration settings
- Rechargeable backup battery
- Fast start times: 38 seconds cold start, 1-second hot start
- Power Supply: 3.3V~5V
- Configurable baud rates (4800 to 115200, default 9600)
- SuperSense® Indoor GPS with -162 dBm tracking sensitivity
- Support for SBAS (WAAS, EGNOS, MSAS, GAGAN)
- Separated 18x18mm GPS antenna
Pinout of NEO-6M GPS Module
The common pinout of the NEO-6M GPS module includes VCC (power supply), GND (ground), TX (UART transmit), and RX (UART receive).
0.96″ OLED Display
The 0.96″ OLED (Organic Light-Emitting Diode) Display measures 0.96 inches diagonally. It is a small-sized display commonly used in electronic devices and DIY projects. check out How To Use SSD1306 Oled Display With Raspberry Pi Pico
0.96″ OLED display typically offers a resolution of 128×64 pixels or similar, allowing for the display of text, graphics, and simple images. It is used to show information such as sensor readings, notifications, or menu options in a compact and visual.
The OLED display is used to show the GPS data received from the NEO-6M module. It can show information such as latitude, and longitude details about the location.
Interfacing NEO-6M GPS Module with Raspberry Pi Pico W & OLED Display
- GPS VCC to Pico 3.3V pin for power.
- GPS GND to Pico GND pin for ground reference.
- GPS TX to Pico GP5 pin for receiving data.
- GPS RX to Pico GP4 pin for transmitting data.
Connect the OLED Display SDA SCL Pin to Pico GP14 & GP15 Pin. You can power the OLED Display using the 3.3V & GND pin of Pico. Hence the hardware setup & connections for Raspberry Pi Pico GPS Tracker is ready.
Code & Librires
The code and libraries for the project are divided into three parts.
micropyGPS.py
The first part consists of the library for the GPS module, which handles communication with the NEO-6M GPS module and provides functions for retrieving GPS data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 |
""" # MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X # Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com) # The MIT License (MIT) - see LICENSE file """ # TODO: # Time Since First Fix # Distance/Time to Target # More Helper Functions # Dynamically limit sentences types to parse from math import floor, modf # Import utime or time for fix time handling try: # Assume running on MicroPython import utime except ImportError: # Otherwise default to time module for non-embedded implementations # Should still support millisecond resolution. import time class MicropyGPS(object): """GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics. Parses sentences one character at a time using update(). """ # Max Number of Characters a valid sentence can be (based on GGA sentence) SENTENCE_LIMIT = 90 __HEMISPHERES = ('N', 'S', 'E', 'W') __NO_FIX = 1 __FIX_2D = 2 __FIX_3D = 3 __DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW') __MONTHS = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') def __init__(self, local_offset=0, location_formatting='ddm'): """ Setup GPS Object Status Flags, Internal Data Registers, etc local_offset (int): Timzone Difference to UTC location_formatting (str): Style For Presenting Longitude/Latitude: Decimal Degree Minute (ddm) - 40° 26.767′ N Degrees Minutes Seconds (dms) - 40° 26′ 46″ N Decimal Degrees (dd) - 40.446° N """ ##################### # Object Status Flags self.sentence_active = False self.active_segment = 0 self.process_crc = False self.gps_segments = [] self.crc_xor = 0 self.char_count = 0 self.fix_time = 0 ##################### # Sentence Statistics self.crc_fails = 0 self.clean_sentences = 0 self.parsed_sentences = 0 ##################### # Logging Related self.log_handle = None self.log_en = False ##################### # Data From Sentences # Time self.timestamp = [0, 0, 0.0] self.date = [0, 0, 0] self.local_offset = local_offset # Position/Motion self._latitude = [0, 0.0, 'N'] self._longitude = [0, 0.0, 'W'] self.coord_format = location_formatting self.speed = [0.0, 0.0, 0.0] self.course = 0.0 self.altitude = 0.0 self.geoid_height = 0.0 # GPS Info self.satellites_in_view = 0 self.satellites_in_use = 0 self.satellites_used = [] self.last_sv_sentence = 0 self.total_sv_sentences = 0 self.satellite_data = dict() self.hdop = 0.0 self.pdop = 0.0 self.vdop = 0.0 self.valid = False self.fix_stat = 0 self.fix_type = 1 ######################################## # Coordinates Translation Functions ######################################## @property def latitude(self): """Format Latitude Data Correctly""" if self.coord_format == 'dd': decimal_degrees = self._latitude[0] + (self._latitude[1] / 60) return [decimal_degrees, self._latitude[2]] elif self.coord_format == 'dms': minute_parts = modf(self._latitude[1]) seconds = round(minute_parts[0] * 60) return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]] else: return self._latitude @property def longitude(self): """Format Longitude Data Correctly""" if self.coord_format == 'dd': decimal_degrees = self._longitude[0] + (self._longitude[1] / 60) return [decimal_degrees, self._longitude[2]] elif self.coord_format == 'dms': minute_parts = modf(self._longitude[1]) seconds = round(minute_parts[0] * 60) return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]] else: return self._longitude ######################################## # Logging Related Functions ######################################## def start_logging(self, target_file, mode="append"): """ Create GPS data log object """ # Set Write Mode Overwrite or Append mode_code = 'w' if mode == 'new' else 'a' try: self.log_handle = open(target_file, mode_code) except AttributeError: print("Invalid FileName") return False self.log_en = True return True def stop_logging(self): """ Closes the log file handler and disables further logging """ try: self.log_handle.close() except AttributeError: print("Invalid Handle") return False self.log_en = False return True def write_log(self, log_string): """Attempts to write the last valid NMEA sentence character to the active file handler """ try: self.log_handle.write(log_string) except TypeError: return False return True ######################################## # Sentence Parsers ######################################## def gprmc(self): """Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence. Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status """ # UTC Timestamp try: utc_string = self.gps_segments[1] if utc_string: # Possible timestamp found hours = (int(utc_string[0:2]) + self.local_offset) % 24 minutes = int(utc_string[2:4]) seconds = float(utc_string[4:]) self.timestamp = [hours, minutes, seconds] else: # No Time stamp yet self.timestamp = [0, 0, 0.0] except ValueError: # Bad Timestamp value present return False # Date stamp try: date_string = self.gps_segments[9] # Date string printer function assumes to be year >=2000, # date_string() must be supplied with the correct century argument to display correctly if date_string: # Possible date stamp found day = int(date_string[0:2]) month = int(date_string[2:4]) year = int(date_string[4:6]) self.date = (day, month, year) else: # No Date stamp yet self.date = (0, 0, 0) except ValueError: # Bad Date stamp value present return False # Check Receiver Data Valid Flag if self.gps_segments[2] == 'A': # Data from Receiver is Valid/Has Fix # Longitude / Latitude try: # Latitude l_string = self.gps_segments[3] lat_degs = int(l_string[0:2]) lat_mins = float(l_string[2:]) lat_hemi = self.gps_segments[4] # Longitude l_string = self.gps_segments[5] lon_degs = int(l_string[0:3]) lon_mins = float(l_string[3:]) lon_hemi = self.gps_segments[6] except ValueError: return False if lat_hemi not in self.__HEMISPHERES: return False if lon_hemi not in self.__HEMISPHERES: return False # Speed try: spd_knt = float(self.gps_segments[7]) except ValueError: return False # Course try: if self.gps_segments[8]: course = float(self.gps_segments[8]) else: course = 0.0 except ValueError: return False # TODO - Add Magnetic Variation # Update Object Data self._latitude = [lat_degs, lat_mins, lat_hemi] self._longitude = [lon_degs, lon_mins, lon_hemi] # Include mph and hm/h self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852] self.course = course self.valid = True # Update Last Fix Time self.new_fix_time() else: # Clear Position Data if Sentence is 'Invalid' self._latitude = [0, 0.0, 'N'] self._longitude = [0, 0.0, 'W'] self.speed = [0.0, 0.0, 0.0] self.course = 0.0 self.valid = False return True def gpgll(self): """Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude, longitude, and fix status""" # UTC Timestamp try: utc_string = self.gps_segments[5] if utc_string: # Possible timestamp found hours = (int(utc_string[0:2]) + self.local_offset) % 24 minutes = int(utc_string[2:4]) seconds = float(utc_string[4:]) self.timestamp = [hours, minutes, seconds] else: # No Time stamp yet self.timestamp = [0, 0, 0.0] except ValueError: # Bad Timestamp value present return False # Check Receiver Data Valid Flag if self.gps_segments[6] == 'A': # Data from Receiver is Valid/Has Fix # Longitude / Latitude try: # Latitude l_string = self.gps_segments[1] lat_degs = int(l_string[0:2]) lat_mins = float(l_string[2:]) lat_hemi = self.gps_segments[2] # Longitude l_string = self.gps_segments[3] lon_degs = int(l_string[0:3]) lon_mins = float(l_string[3:]) lon_hemi = self.gps_segments[4] except ValueError: return False if lat_hemi not in self.__HEMISPHERES: return False if lon_hemi not in self.__HEMISPHERES: return False # Update Object Data self._latitude = [lat_degs, lat_mins, lat_hemi] self._longitude = [lon_degs, lon_mins, lon_hemi] self.valid = True # Update Last Fix Time self.new_fix_time() else: # Clear Position Data if Sentence is 'Invalid' self._latitude = [0, 0.0, 'N'] self._longitude = [0, 0.0, 'W'] self.valid = False return True def gpvtg(self): """Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course""" try: course = float(self.gps_segments[1]) if self.gps_segments[1] else 0.0 spd_knt = float(self.gps_segments[5]) if self.gps_segments[5] else 0.0 except ValueError: return False # Include mph and km/h self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852) self.course = course return True def gpgga(self): """Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude, fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status""" try: # UTC Timestamp utc_string = self.gps_segments[1] # Skip timestamp if receiver doesn't have on yet if utc_string: hours = (int(utc_string[0:2]) + self.local_offset) % 24 minutes = int(utc_string[2:4]) seconds = float(utc_string[4:]) else: hours = 0 minutes = 0 seconds = 0.0 # Number of Satellites in Use satellites_in_use = int(self.gps_segments[7]) # Get Fix Status fix_stat = int(self.gps_segments[6]) except (ValueError, IndexError): return False try: # Horizontal Dilution of Precision hdop = float(self.gps_segments[8]) except (ValueError, IndexError): hdop = 0.0 # Process Location and Speed Data if Fix is GOOD if fix_stat: # Longitude / Latitude try: # Latitude l_string = self.gps_segments[2] lat_degs = int(l_string[0:2]) lat_mins = float(l_string[2:]) lat_hemi = self.gps_segments[3] # Longitude l_string = self.gps_segments[4] lon_degs = int(l_string[0:3]) lon_mins = float(l_string[3:]) lon_hemi = self.gps_segments[5] except ValueError: return False if lat_hemi not in self.__HEMISPHERES: return False if lon_hemi not in self.__HEMISPHERES: return False # Altitude / Height Above Geoid try: altitude = float(self.gps_segments[9]) geoid_height = float(self.gps_segments[11]) except ValueError: altitude = 0 geoid_height = 0 # Update Object Data self._latitude = [lat_degs, lat_mins, lat_hemi] self._longitude = [lon_degs, lon_mins, lon_hemi] self.altitude = altitude self.geoid_height = geoid_height # Update Object Data self.timestamp = [hours, minutes, seconds] self.satellites_in_use = satellites_in_use self.hdop = hdop self.fix_stat = fix_stat # If Fix is GOOD, update fix timestamp if fix_stat: self.new_fix_time() return True def gpgsa(self): """Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical Dilution of Precision, and fix status""" # Fix Type (None,2D or 3D) try: fix_type = int(self.gps_segments[2]) except ValueError: return False # Read All (up to 12) Available PRN Satellite Numbers sats_used = [] for sats in range(12): sat_number_str = self.gps_segments[3 + sats] if sat_number_str: try: sat_number = int(sat_number_str) sats_used.append(sat_number) except ValueError: return False else: break # PDOP,HDOP,VDOP try: pdop = float(self.gps_segments[15]) hdop = float(self.gps_segments[16]) vdop = float(self.gps_segments[17]) except ValueError: return False # Update Object Data self.fix_type = fix_type # If Fix is GOOD, update fix timestamp if fix_type > self.__NO_FIX: self.new_fix_time() self.satellites_used = sats_used self.hdop = hdop self.vdop = vdop self.pdop = pdop return True def gpgsv(self): """Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence parsed, and data on each satellite present in the sentence""" try: num_sv_sentences = int(self.gps_segments[1]) current_sv_sentence = int(self.gps_segments[2]) sats_in_view = int(self.gps_segments[3]) except ValueError: return False # Create a blank dict to store all the satellite data from this sentence in: # satellite PRN is key, tuple containing telemetry is value satellite_dict = dict() # Calculate Number of Satelites to pull data for and thus how many segment positions to read if num_sv_sentences == current_sv_sentence: # Last sentence may have 1-4 satellites; 5 - 20 positions sat_segment_limit = (sats_in_view - ((num_sv_sentences - 1) * 4)) * 5 else: sat_segment_limit = 20 # Non-last sentences have 4 satellites and thus read up to position 20 # Try to recover data for up to 4 satellites in sentence for sats in range(4, sat_segment_limit, 4): # If a PRN is present, grab satellite data if self.gps_segments[sats]: try: sat_id = int(self.gps_segments[sats]) except (ValueError,IndexError): return False try: # elevation can be null (no value) when not tracking elevation = int(self.gps_segments[sats+1]) except (ValueError,IndexError): elevation = None try: # azimuth can be null (no value) when not tracking azimuth = int(self.gps_segments[sats+2]) except (ValueError,IndexError): azimuth = None try: # SNR can be null (no value) when not tracking snr = int(self.gps_segments[sats+3]) except (ValueError,IndexError): snr = None # If no PRN is found, then the sentence has no more satellites to read else: break # Add Satellite Data to Sentence Dict satellite_dict[sat_id] = (elevation, azimuth, snr) # Update Object Data self.total_sv_sentences = num_sv_sentences self.last_sv_sentence = current_sv_sentence self.satellites_in_view = sats_in_view # For a new set of sentences, we either clear out the existing sat data or # update it as additional SV sentences are parsed if current_sv_sentence == 1: self.satellite_data = satellite_dict else: self.satellite_data.update(satellite_dict) return True ########################################## # Data Stream Handler Functions ########################################## def new_sentence(self): """Adjust Object Flags in Preparation for a New Sentence""" self.gps_segments = [''] self.active_segment = 0 self.crc_xor = 0 self.sentence_active = True self.process_crc = True self.char_count = 0 def update(self, new_char): """Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*') Function builds a list of received string that are validate by CRC prior to parsing by the appropriate sentence function. Returns sentence type on successful parse, None otherwise""" valid_sentence = False # Validate new_char is a printable char ascii_char = ord(new_char) if 10 <= ascii_char <= 126: self.char_count += 1 # Write Character to log file if enabled if self.log_en: self.write_log(new_char) # Check if a new string is starting ($) if new_char == '$': self.new_sentence() return None elif self.sentence_active: # Check if sentence is ending (*) if new_char == '*': self.process_crc = False self.active_segment += 1 self.gps_segments.append('') return None # Check if a section is ended (,), Create a new substring to feed # characters to elif new_char == ',': self.active_segment += 1 self.gps_segments.append('') # Store All Other printable character and check CRC when ready else: self.gps_segments[self.active_segment] += new_char # When CRC input is disabled, sentence is nearly complete if not self.process_crc: if len(self.gps_segments[self.active_segment]) == 2: try: final_crc = int(self.gps_segments[self.active_segment], 16) if self.crc_xor == final_crc: valid_sentence = True else: self.crc_fails += 1 except ValueError: pass # CRC Value was deformed and could not have been correct # Update CRC if self.process_crc: self.crc_xor ^= ascii_char # If a Valid Sentence Was received and it's a supported sentence, then parse it!! if valid_sentence: self.clean_sentences += 1 # Increment clean sentences received self.sentence_active = False # Clear Active Processing Flag if self.gps_segments[0] in self.supported_sentences: # parse the Sentence Based on the message type, return True if parse is clean if self.supported_sentences[self.gps_segments[0]](self): # Let host know that the GPS object was updated by returning parsed sentence type self.parsed_sentences += 1 return self.gps_segments[0] # Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete if self.char_count > self.SENTENCE_LIMIT: self.sentence_active = False # Tell Host no new sentence was parsed return None def new_fix_time(self): """Updates a high resolution counter with current time when fix is updated. Currently only triggered from GGA, GSA and RMC sentences""" try: self.fix_time = utime.ticks_ms() except NameError: self.fix_time = time.time() ######################################### # User Helper Functions # These functions make working with the GPS object data easier ######################################### def satellite_data_updated(self): """ Checks if the all the GSV sentences in a group have been read, making satellite data complete :return: boolean """ if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence: return True else: return False def unset_satellite_data_updated(self): """ Mark GSV sentences as read indicating the data has been used and future updates are fresh """ self.last_sv_sentence = 0 def satellites_visible(self): """ Returns a list of of the satellite PRNs currently visible to the receiver :return: list """ return list(self.satellite_data.keys()) def time_since_fix(self): """Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if no fix has been found""" # Test if a Fix has been found if self.fix_time == 0: return -1 # Try calculating fix time using utime; if not running MicroPython # time.time() returns a floating point value in secs try: current = utime.ticks_diff(utime.ticks_ms(), self.fix_time) except NameError: current = (time.time() - self.fix_time) * 1000 # ms return current def compass_direction(self): """ Determine a cardinal or inter-cardinal direction based on current course. :return: string """ # Calculate the offset for a rotated compass if self.course >= 348.75: offset_course = 360 - self.course else: offset_course = self.course + 11.25 # Each compass point is separated by 22.5 degrees, divide to find lookup value dir_index = floor(offset_course / 22.5) final_dir = self.__DIRECTIONS[dir_index] return final_dir def latitude_string(self): """ Create a readable string of the current latitude data :return: string """ if self.coord_format == 'dd': formatted_latitude = self.latitude lat_string = str(formatted_latitude[0]) + '° ' + str(self._latitude[2]) elif self.coord_format == 'dms': formatted_latitude = self.latitude lat_string = str(formatted_latitude[0]) + '° ' + str(formatted_latitude[1]) + "' " + str(formatted_latitude[2]) + '" ' + str(formatted_latitude[3]) else: lat_string = str(self._latitude[0]) + '° ' + str(self._latitude[1]) + "' " + str(self._latitude[2]) return lat_string def longitude_string(self): """ Create a readable string of the current longitude data :return: string """ if self.coord_format == 'dd': formatted_longitude = self.longitude lon_string = str(formatted_longitude[0]) + '° ' + str(self._longitude[2]) elif self.coord_format == 'dms': formatted_longitude = self.longitude lon_string = str(formatted_longitude[0]) + '° ' + str(formatted_longitude[1]) + "' " + str(formatted_longitude[2]) + '" ' + str(formatted_longitude[3]) else: lon_string = str(self._longitude[0]) + '° ' + str(self._longitude[1]) + "' " + str(self._longitude[2]) return lon_string def speed_string(self, unit='kph'): """ Creates a readable string of the current speed data in one of three units :param unit: string of 'kph','mph, or 'knot' :return: """ if unit == 'mph': speed_string = str(self.speed[1]) + ' mph' elif unit == 'knot': if self.speed[0] == 1: unit_str = ' knot' else: unit_str = ' knots' speed_string = str(self.speed[0]) + unit_str else: speed_string = str(self.speed[2]) + ' km/h' return speed_string def date_string(self, formatting='s_mdy', century='20'): """ Creates a readable string of the current date. Can select between long format: Januray 1st, 2014 or two short formats: 11/01/2014 (MM/DD/YYYY) 01/11/2014 (DD/MM/YYYY) :param formatting: string 's_mdy', 's_dmy', or 'long' :param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX) :return: date_string string with long or short format date """ # Long Format Januray 1st, 2014 if formatting == 'long': # Retrieve Month string from private set month = self.__MONTHS[self.date[1] - 1] # Determine Date Suffix if self.date[0] in (1, 21, 31): suffix = 'st' elif self.date[0] in (2, 22): suffix = 'nd' elif self.date[0] == (3, 23): suffix = 'rd' else: suffix = 'th' day = str(self.date[0]) + suffix # Create Day String year = century + str(self.date[2]) # Create Year String date_string = month + ' ' + day + ', ' + year # Put it all together else: # Add leading zeros to day string if necessary if self.date[0] < 10: day = '0' + str(self.date[0]) else: day = str(self.date[0]) # Add leading zeros to month string if necessary if self.date[1] < 10: month = '0' + str(self.date[1]) else: month = str(self.date[1]) # Add leading zeros to year string if necessary if self.date[2] < 10: year = '0' + str(self.date[2]) else: year = str(self.date[2]) # Build final string based on desired formatting if formatting == 's_dmy': date_string = day + '/' + month + '/' + year else: # Default date format date_string = month + '/' + day + '/' + year return date_string # All the currently supported NMEA sentences supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc, 'GPGGA': gpgga, 'GLGGA': gpgga, 'GPVTG': gpvtg, 'GLVTG': gpvtg, 'GPGSA': gpgsa, 'GLGSA': gpgsa, 'GPGSV': gpgsv, 'GLGSV': gpgsv, 'GPGLL': gpgll, 'GLGLL': gpgll, 'GNGGA': gpgga, 'GNRMC': gprmc, 'GNVTG': gpvtg, 'GNGLL': gpgll, 'GNGSA': gpgsa, } if __name__ == "__main__": pass |
ssd1306.py
The second part is the library for the OLED display, which handles the communication and display functionalities of the 0.96″ OLED screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces from micropython import const import framebuf # register definitions SET_CONTRAST = const(0x81) SET_ENTIRE_ON = const(0xA4) SET_NORM_INV = const(0xA6) SET_DISP = const(0xAE) SET_MEM_ADDR = const(0x20) SET_COL_ADDR = const(0x21) SET_PAGE_ADDR = const(0x22) SET_DISP_START_LINE = const(0x40) SET_SEG_REMAP = const(0xA0) SET_MUX_RATIO = const(0xA8) SET_COM_OUT_DIR = const(0xC0) SET_DISP_OFFSET = const(0xD3) SET_COM_PIN_CFG = const(0xDA) SET_DISP_CLK_DIV = const(0xD5) SET_PRECHARGE = const(0xD9) SET_VCOM_DESEL = const(0xDB) SET_CHARGE_PUMP = const(0x8D) # Subclassing FrameBuffer provides support for graphics primitives # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): def __init__(self, width, height, external_vcc): self.width = width self.height = height self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) self.init_display() def init_display(self): for cmd in ( SET_DISP | 0x00, # off # address setting SET_MEM_ADDR, 0x00, # horizontal # resolution and layout SET_DISP_START_LINE | 0x00, SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 SET_MUX_RATIO, self.height - 1, SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 SET_DISP_OFFSET, 0x00, SET_COM_PIN_CFG, 0x02 if self.width > 2 * self.height else 0x12, # timing and driving scheme SET_DISP_CLK_DIV, 0x80, SET_PRECHARGE, 0x22 if self.external_vcc else 0xF1, SET_VCOM_DESEL, 0x30, # 0.83*Vcc # display SET_CONTRAST, 0xFF, # maximum SET_ENTIRE_ON, # output follows RAM contents SET_NORM_INV, # not inverted # charge pump SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, SET_DISP | 0x01, ): # on self.write_cmd(cmd) self.fill(0) self.show() def poweroff(self): self.write_cmd(SET_DISP | 0x00) def poweron(self): self.write_cmd(SET_DISP | 0x01) def contrast(self, contrast): self.write_cmd(SET_CONTRAST) self.write_cmd(contrast) def invert(self, invert): self.write_cmd(SET_NORM_INV | (invert & 1)) def show(self): x0 = 0 x1 = self.width - 1 if self.width == 64: # displays with width of 64 pixels are shifted by 32 x0 += 32 x1 += 32 self.write_cmd(SET_COL_ADDR) self.write_cmd(x0) self.write_cmd(x1) self.write_cmd(SET_PAGE_ADDR) self.write_cmd(0) self.write_cmd(self.pages - 1) self.write_data(self.buffer) class SSD1306_I2C(SSD1306): def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): self.i2c = i2c self.addr = addr self.temp = bytearray(2) self.write_list = [b"\x40", None] # Co=0, D/C#=1 super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.temp[0] = 0x80 # Co=1, D/C#=0 self.temp[1] = cmd self.i2c.writeto(self.addr, self.temp) def write_data(self, buf): self.write_list[1] = buf self.i2c.writevto(self.addr, self.write_list) class SSD1306_SPI(SSD1306): def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): self.rate = 10 * 1024 * 1024 dc.init(dc.OUT, value=0) res.init(res.OUT, value=0) cs.init(cs.OUT, value=1) self.spi = spi self.dc = dc self.res = res self.cs = cs import time self.res(1) time.sleep_ms(1) self.res(0) time.sleep_ms(10) self.res(1) super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.spi.init(baudrate=self.rate, polarity=0, phase=0) self.cs(1) self.dc(0) self.cs(0) self.spi.write(bytearray([cmd])) self.cs(1) def write_data(self, buf): self.spi.init(baudrate=self.rate, polarity=0, phase=0) self.cs(1) self.dc(1) self.cs(0) self.spi.write(buf) self.cs(1) |
main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
from machine import Pin, UART, I2C import utime from ssd1306 import SSD1306_I2C from micropyGPS import MicropyGPS # Initialize I2C and OLED display i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=400000) oled = SSD1306_I2C(128, 64, i2c) # Initialize GPS module gps_module = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5)) time_zone = -3 gps = MicropyGPS(time_zone) def convert_coordinates(sections): if sections[0] == 0: # sections[0] contains the degrees return None # sections[1] contains the minutes data = sections[0] + (sections[1] / 60.0) # sections[2] contains 'E', 'W', 'N', 'S' if sections[2] == 'S': data = -data if sections[2] == 'W': data = -data data = '{0:.6f}'.format(data) # 6 decimal places return str(data) while True: length = gps_module.any() if length > 0: data = gps_module.read(length) for byte in data: message = gps.update(chr(byte)) latitude = convert_coordinates(gps.latitude) longitude = convert_coordinates(gps.longitude) if latitude is None or longitude is None: oled.fill(0) oled.text("Data unavailable", 35, 25) oled.text("No coordinates", 22, 40) oled.show() continue oled.fill(0) oled.text('Satellites: ' + str(gps.satellites_in_use), 10, 0) oled.text('Lat: ' + latitude, 0, 18) print('Lat: ' + latitude) oled.text('Lon: ' + longitude, 0, 36) print('Lon: ' + longitude) oled.show() |
Displaying GPS Data on OLED
Check location using latitude and longitude
Here’s an example of how you can use the latitude and longitude values obtained from the code and search for the location on www.latlong.net
- Open a web browser and go to www.latlong.net.
- Enter the latitude value in the “Latitude” input field on the website.
- Enter the longitude value in the “Longitude” input field on the website.
- Click on the “Convert” button on the website.
- The website will display the corresponding location on the map.
3 Comments
sir it is showing data unavailable no coordinate: please help me out
Please go to an open place or rooftop because the signal of NEO6m GPS is very poor.
The GPS receiver is acquiring data, I know this because I can print lat_degs and lon_mins from micropyGPS.py module line 300 and 307 but the OLED shows ‘Data unavail’, ‘No coordinate:
When I add print commands to main.py
latitude = convert_coordinates(gps.latitude)
print(latitude)
longitude = convert_coordinates(gps.longitude)
print(longitude)
this returns
51.719337
None
51.719341
None
51.719341
None
51.719341
None
51.719341
None
51.719341
None
51.719341 is the correct latitude
It seems longitude is not reaching main.py
Thanks for any help