Skip to content

Commit f8de4c0

Browse files
authored
Merge pull request #36 from ladyada/master
Add I2C support and various example fixes
2 parents f49ac2e + 44cef8e commit f8de4c0

File tree

7 files changed

+174
-96
lines changed

7 files changed

+174
-96
lines changed

adafruit_gps.py

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@
4343
4444
"""
4545
import time
46+
from micropython import const
4647

4748
__version__ = "0.0.0-auto.0"
4849
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GPS.git"
4950

51+
52+
_GPSI2C_DEFAULT_ADDRESS = const(0x10)
53+
5054
# Internal helper parsing functions.
5155
# These handle input that might be none or null and return none instead of
5256
# throwing errors.
53-
54-
5557
def _parse_degrees(nmea_data):
5658
# Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
5759
# Where ddd is the degrees, mm.mmmm is the minutes.
@@ -135,11 +137,11 @@ def update(self):
135137
data_type, args = sentence
136138
data_type = bytes(data_type.upper(), "ascii")
137139
# return sentence
138-
if data_type == b'GPGLL': # GLL, Geographic Position – Latitude/Longitude
140+
if data_type in (b'GPGLL', b'GNGGL'): # GLL, Geographic Position – Latitude/Longitude
139141
self._parse_gpgll(args)
140-
elif data_type == b'GPRMC': # RMC, minimum location info
142+
elif data_type in (b'GPRMC', b'GNRMC'): # RMC, minimum location info
141143
self._parse_gprmc(args)
142-
elif data_type == b'GPGGA': # GGA, 3d location fix
144+
elif data_type in (b'GPGGA', b'GNGGA'): # GGA, 3d location fix
143145
self._parse_gpgga(args)
144146
return True
145147

@@ -149,15 +151,15 @@ def send_command(self, command, add_checksum=True):
149151
Note you should NOT add the leading $ and trailing * to the command
150152
as they will automatically be added!
151153
"""
152-
self._uart.write(b'$')
153-
self._uart.write(command)
154+
self.write(b'$')
155+
self.write(command)
154156
if add_checksum:
155157
checksum = 0
156158
for char in command:
157159
checksum ^= char
158-
self._uart.write(b'*')
159-
self._uart.write(bytes('{:02x}'.format(checksum).upper(), "ascii"))
160-
self._uart.write(b'\r\n')
160+
self.write(b'*')
161+
self.write(bytes('{:02x}'.format(checksum).upper(), "ascii"))
162+
self.write(b'\r\n')
161163

162164
@property
163165
def has_fix(self):
@@ -181,16 +183,36 @@ def nmea_sentence(self):
181183
"""Return raw_sentence which is the raw NMEA sentence read from the GPS"""
182184
return self._raw_sentence
183185

186+
def read(self, num_bytes):
187+
"""Read up to num_bytes of data from the GPS directly, without parsing.
188+
Returns a bytearray with up to num_bytes or None if nothing was read"""
189+
return self._uart.read(num_bytes)
190+
191+
def write(self, bytestr):
192+
"""Write a bytestring data to the GPS directly, without parsing
193+
or checksums"""
194+
return self._uart.write(bytestr)
195+
196+
@property
197+
def in_waiting(self):
198+
"""Returns number of bytes available in UART read buffer"""
199+
return self._uart.in_waiting
200+
201+
def readline(self):
202+
"""Returns a newline terminated bytearray, must have timeout set for
203+
the underlying UART or this will block forever!"""
204+
return self._uart.readline()
205+
184206
def _read_sentence(self):
185207
# Parse any NMEA sentence that is available.
186208
# pylint: disable=len-as-condition
187209
# This needs to be refactored when it can be tested.
188210

189211
# Only continue if we have at least 32 bytes in the input buffer
190-
if self._uart.in_waiting < 32:
212+
if self.in_waiting < 32:
191213
return None
192214

193-
sentence = self._uart.readline()
215+
sentence = self.readline()
194216
if sentence is None or sentence == b'' or len(sentence) < 1:
195217
return None
196218
try:
@@ -423,3 +445,64 @@ def _parse_gpgsv(self, args):
423445
except TypeError:
424446
pass
425447
self.satellites_prev = self.satellites
448+
449+
class GPS_GtopI2C(GPS):
450+
"""GTop-compatible I2C GPS parsing module. Can parse simple NMEA data
451+
sentences from an I2C-capable GPS module to read latitude, longitude, and more.
452+
"""
453+
def __init__(self, i2c_bus, *, address=_GPSI2C_DEFAULT_ADDRESS, debug=False,
454+
timeout=5):
455+
import adafruit_bus_device.i2c_device as i2c_device
456+
super().__init__(None, debug) # init the parent with no UART
457+
self._i2c = i2c_device.I2CDevice(i2c_bus, address)
458+
self._lastbyte = None
459+
self._charbuff = bytearray(1)
460+
self._internalbuffer = []
461+
self._timeout = timeout
462+
463+
def read(self, num_bytes=1):
464+
"""Read up to num_bytes of data from the GPS directly, without parsing.
465+
Returns a bytearray with up to num_bytes or None if nothing was read"""
466+
result = []
467+
for _ in range(num_bytes):
468+
with self._i2c as i2c:
469+
# we read one byte at a time, verify it isnt part of a string of
470+
# 'stuffed' newlines and then append to our result array for byteification
471+
i2c.readinto(self._charbuff)
472+
char = self._charbuff[0]
473+
if (char == ord('\n')) and (self._lastbyte != ord('\r')):
474+
continue # skip duplicate \n's!
475+
result.append(char)
476+
self._lastbyte = char # keep track of the last character approved
477+
return bytearray(result)
478+
479+
def write(self, bytestr):
480+
"""Write a bytestring data to the GPS directly, without parsing
481+
or checksums"""
482+
with self._i2c as i2c:
483+
i2c.write(bytestr)
484+
485+
@property
486+
def in_waiting(self):
487+
"""Returns number of bytes available in UART read buffer, always 32
488+
since I2C does not have the ability to know how much data is available"""
489+
return 32
490+
491+
def readline(self):
492+
"""Returns a newline terminated bytearray, must have timeout set for
493+
the underlying UART or this will block forever!"""
494+
timeout = time.monotonic() + self._timeout
495+
while timeout > time.monotonic():
496+
# check if our internal buffer has a '\n' termination already
497+
if self._internalbuffer and (self._internalbuffer[-1] == ord('\n')):
498+
break
499+
char = self.read(1)
500+
if not char:
501+
continue
502+
self._internalbuffer.append(char[0])
503+
#print(bytearray(self._internalbuffer))
504+
if self._internalbuffer and self._internalbuffer[-1] == ord('\n'):
505+
ret = bytearray(self._internalbuffer)
506+
self._internalbuffer = [] # reset the buffer to empty
507+
return ret
508+
return None # no completed data yet

examples/gps_computer_datalogging.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

examples/gps_datalogging.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,69 @@
11
# Simple GPS datalogging demonstration.
2-
# This actually doesn't even use the GPS library and instead just reads raw
3-
# NMEA sentences from the GPS unit and dumps them to a file on an SD card
4-
# (recommended) or internal storage (be careful as only a few kilobytes to
5-
# megabytes are available). Before writing to internal storage you MUST
6-
# carefully follow the steps in this guide to enable writes to the internal
7-
# filesystem:
2+
# This example uses the GPS library and to read raw NMEA sentences
3+
# over I2C or UART from the GPS unit and dumps them to a file on an SD card
4+
# (recommended), microcontroller internal storage (be careful as only a few
5+
# kilobytes are available), or to a filesystem.
6+
# If you are using a microcontroller, before writing to internal storage you
7+
# MUST carefully follow the steps in this guide to enable writes to the
8+
# internal filesystem:
89
# https://learn.adafruit.com/adafruit-ultimate-gps-featherwing/circuitpython-library
910
import board
1011
import busio
11-
12+
import adafruit_gps
1213

1314
# Path to the file to log GPS data. By default this will be appended to
1415
# which means new lines are added at the end and all old data is kept.
1516
# Change this path to point at internal storage (like '/gps.txt') or SD
1617
# card mounted storage ('/sd/gps.txt') as desired.
17-
LOG_FILE = '/gps.txt' # Example for writing to internal path /gps.txt
18-
#LOG_FILE = '/sd/gps.txt' # Example for writing to SD card path /sd/gps.txt
18+
LOG_FILE = 'gps.txt' # Example for writing to internal path gps.txt
1919

2020
# File more for opening the log file. Mode 'ab' means append or add new lines
2121
# to the end of the file rather than erasing it and starting over. If you'd
2222
# like to erase the file and start clean each time use the value 'wb' instead.
2323
LOG_MODE = 'ab'
2424

25-
# Define RX and TX pins for the board's serial port connected to the GPS.
26-
# These are the defaults you should use for the GPS FeatherWing.
27-
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
28-
RX = board.RX
29-
TX = board.TX
30-
31-
# If writing to SD card customize and uncomment these lines to import the
32-
# necessary library and initialize the SD card:
33-
#SD_CS_PIN = board.SD_CS # CS for SD card (SD_CS is for Feather Adalogger)
34-
#import adafruit_sdcard
35-
#spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
36-
#sd_cs = digitalio.DigitalInOut(SD_CS_PIN)
37-
#sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
38-
#vfs = storage.VfsFat(sdcard)
39-
#storage.mount(vfs, '/sd') # Mount SD card under '/sd' path in filesystem.
25+
# If writing to SD card on a microcontroller customize and uncomment these
26+
# lines to import the necessary library and initialize the SD card:
27+
# NOT for use with a single board computer like Raspberry Pi!
28+
"""
29+
import adafruit_sdcard
30+
import digitalio
31+
import storage
32+
33+
SD_CS_PIN = board.D10 # CS for SD card using Adalogger Featherwing
34+
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
35+
sd_cs = digitalio.DigitalInOut(SD_CS_PIN)
36+
sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
37+
vfs = storage.VfsFat(sdcard)
38+
storage.mount(vfs, '/sd') # Mount SD card under '/sd' path in filesystem.
39+
LOG_FILE = '/sd/gps.txt' # Example for writing to SD card path /sd/gps.txt
40+
"""
4041

4142
# Create a serial connection for the GPS connection using default speed and
4243
# a slightly higher timeout (GPS modules typically update once a second).
43-
uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
44+
# These are the defaults you should use for the GPS FeatherWing.
45+
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
46+
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)
47+
48+
# If using a USB/Serial converter, use pyserial and update the serial
49+
# port name to match the serial connection for the GPS!
50+
#import serial
51+
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)
52+
53+
# If using I2C, we'll create an I2C interface to talk to using default pins
54+
#i2c = busio.I2C(board.SCL, board.SDA)
55+
56+
# Create a GPS module instance.
57+
gps = adafruit_gps.GPS(uart) # Use UART/pyserial
58+
#gps = adafruit_gps.GPS_GtopI2C(i2c) # Use I2C interface
4459

4560
# Main loop just reads data from the GPS module and writes it back out to
4661
# the output file while also printing to serial output.
4762
with open(LOG_FILE, LOG_MODE) as outfile:
4863
while True:
49-
sentence = uart.readline()
64+
sentence = gps.readline()
65+
if not sentence:
66+
continue
5067
print(str(sentence, 'ascii').strip())
5168
outfile.write(sentence)
5269
outfile.flush()

examples/gps_echotest.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
# Simple GPS module demonstration.
22
# Will print NMEA sentences received from the GPS, great for testing connection
3-
# Uses the GPS only to send some commands, then reads directly from UART
3+
# Uses the GPS to send some commands, then reads directly from the GPS
44
import time
55
import board
66
import busio
77

88
import adafruit_gps
99

10-
11-
# Define RX and TX pins for the board's serial port connected to the GPS.
12-
# These are the defaults you should use for the GPS FeatherWing.
13-
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
14-
RX = board.RX
15-
TX = board.TX
16-
1710
# Create a serial connection for the GPS connection using default speed and
1811
# a slightly higher timeout (GPS modules typically update once a second).
19-
uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
12+
# These are the defaults you should use for the GPS FeatherWing.
13+
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
14+
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)
2015

2116
# for a computer, use the pyserial library for uart access
2217
#import serial
23-
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=3000)
18+
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)
19+
20+
# If using I2C, we'll create an I2C interface to talk to using default pins
21+
#i2c = busio.I2C(board.SCL, board.SDA)
2422

2523
# Create a GPS module instance.
26-
gps = adafruit_gps.GPS(uart)
24+
gps = adafruit_gps.GPS(uart) # Use UART/pyserial
25+
#gps = adafruit_gps.GPS_GtopI2C(i2c) # Use I2C interface
2726

2827
# Initialize the GPS module by changing what data it sends and at what rate.
2928
# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and
@@ -52,7 +51,7 @@
5251
# Main loop runs forever printing data as it comes in
5352
timestamp = time.monotonic()
5453
while True:
55-
data = uart.read(32) # read up to 32 bytes
54+
data = gps.read(32) # read up to 32 bytes
5655
# print(data) # this is a bytearray type
5756

5857
if data is not None:

examples/gps_simpletest.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,22 @@
77

88
import adafruit_gps
99

10-
11-
# Define RX and TX pins for the board's serial port connected to the GPS.
12-
# These are the defaults you should use for the GPS FeatherWing.
13-
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
14-
RX = board.RX
15-
TX = board.TX
16-
1710
# Create a serial connection for the GPS connection using default speed and
1811
# a slightly higher timeout (GPS modules typically update once a second).
19-
uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
12+
# These are the defaults you should use for the GPS FeatherWing.
13+
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
14+
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)
2015

2116
# for a computer, use the pyserial library for uart access
2217
#import serial
23-
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=3000)
18+
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)
19+
20+
# If using I2C, we'll create an I2C interface to talk to using default pins
21+
#i2c = busio.I2C(board.SCL, board.SDA)
2422

2523
# Create a GPS module instance.
26-
gps = adafruit_gps.GPS(uart, debug=False)
24+
gps = adafruit_gps.GPS(uart, debug=False) # Use UART/pyserial
25+
#gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface
2726

2827
# Initialize the GPS module by changing what data it sends and at what rate.
2928
# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and

0 commit comments

Comments
 (0)