The BerryGPS-IMU uses a CAM-M8C U-Blox GPS module, this GPS module includes a DDC interface which is fully I2C compatible.
This guide will show how to read NMEA sentences from the GPS module via I2C, using a Raspberry Pi. This leaves the serial interface on the Raspberry Pi free for other uses. You can also use a QWIIC connector to connect the BerryGPS-IMU to the Raspberry Pi
We will create a virtual node where we will send the NMEA sentences, we will then configure GPSD to read this virtual node.
Caveat: There is a well know I2C clock stretching bug on the Raspberry Pi which will be encountered when trying to communicate with the uBlox module via native I2C. This results with random characters appearing in the retrieved data.
We will cover two methods of how to get around this;
Method 1 – Include a lot of checks to ignore NMEA sentences with corrupt data.
The overall amount of NMEA sentences which will be corrupt is very small(20 out of 1,000), which still makes this method very usable.
Method 2 – Using bit banging to overcome the clock stretching bug. This will result in zero errors, but requires I2C to be disabled on the Raspberry Pi.
I2C Jumpers
By default, the GPS module on the BerryGPS-IMU is not connected to the I2C bus. This can be fixed by placing a solder blob on the jumpers JP11 and JP10 on the back of the PCB.
Method 1 – Using native I2C
Enable I2C on your Raspberry Pi and set the speed to 400Khz.
Near the bottom, add the following line
dtparam=i2c_arm=on,i2c_arm_baudrate=400000
Now reboot.
You can confirm if you see the GPS module by using the below command.
Here is the output when a BerryGPS-IMU is connected. 42 is the GPS module
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: — — — — — — — — — — — — —
10: — — — — — — — — — — — — 1c — — —
20: — — — — — — — — — — — — — — — —
30: — — — — — — — — — — — — — — — —
40: — — 42 — — — — — — — — — — — — —
50: — — — — — — — — — — — — — — — —
60: — — — — — — — — — — 6a — — — — —
70: — — — — — — — 77
Create the python script which will read the data via I2C from the GPS module
Copy in the below code;
#! /usr/bin/python import time import smbus import signal import sys BUS = None address = 0x42 gpsReadInterval = 0.03 def connectBus(): global BUS BUS = smbus.SMBus(1) def parseResponse(gpsLine): if(gpsLine.count(36) == 1): # Check #1, make sure '$' doesnt appear twice if len(gpsLine) < 84: # Check #2, 83 is maximun NMEA sentenace length. CharError = 0; for c in gpsLine: # Check #3, Make sure that only readiable ASCII charaters and Carriage Return are seen. if (c < 32 or c > 122) and c != 13: CharError+=1 if (CharError == 0):# Only proceed if there are no errors. gpsChars = ''.join(chr(c) for c in gpsLine) if (gpsChars.find('txbuf') == -1): # Check #4, skip txbuff allocation error gpsStr, chkSum = gpsChars.split('*',2) # Check #5 only split twice to avoid unpack error gpsComponents = gpsStr.split(',') chkVal = 0 for ch in gpsStr[1:]: # Remove the $ and do a manual checksum on the rest of the NMEA sentence chkVal ^= ord(ch) if (chkVal == int(chkSum, 16)): # Compare the calculated checksum with the one in the NMEA sentence print gpsChars def handle_ctrl_c(signal, frame): sys.exit(130) #This will capture exit when using Ctrl-C signal.signal(signal.SIGINT, handle_ctrl_c) def readGPS(): c = None response = [] try: while True: # Newline, or bad char. c = BUS.read_byte(address) if c == 255: return False elif c == 10: break else: response.append(c) parseResponse(response) except IOError: connectBus() except Exception,e: print e connectBus() while True: readGPS() time.sleep(gpsReadInterval)
You can test the script with python i2c-gps.py. If you have a GPS fix, you will get output similar to below.
$GNRMC,071423.00,A,3254.18201,S,15243.27916,E,0.252,,110721,,,A*72
$GNVTG,,T,,M,0.252,N,0.466,K,A*3C
$GNGGA,071423.00,3254.18201,S,15243.27916,E,1,10,1.01,29.0,M,22.6,M,,*63
$GNGSA,A,3,30,14,07,17,13,19,15,,,,,,1.66,1.01,1.31*17
$GNGSA,A,3,73,74,72,,,,,,,,,,1.66,1.01,1.31*1C
$GPGSV,3,1,12,01,21,124,,06,13,009,20,07,11,048,21,13,47,286,34*70
$GPGSV,3,2,12,14,54,143,27,15,25,253,31,17,84,132,28,19,70,328,31*7D
$GPGSV,3,3,12,21,09,139,,24,08,225,18,28,,,29,30,49,052,26*45
$GLGSV,3,1,10,65,14,243,16,71,00,330,,72,15,287,21,73,39,099,22*65
$GLGSV,3,2,10,74,53,176,19,75,16,227,,80,00,070,,83,24,149,*64
Create a virtual node, this is where we will send the NMEA sentences from the GPS module to.
Now run the python script and redirect the output to the virtual node we just created.
We will also use stdbuf so the output from the script is sent to the virtual node one line at a time. Without this, the output is buffered and only sent to the virtual node when the buffer is full.
Configure GPSD to use the virtual buffer
No you can configure GPSD to point to the virtual buffer.
Look for
DEVICES=””
and change it to
DEVICES=”/tmp/gps”
Restart GPSD so the new settings take effect.
You can now start using your GPS module with your Raspberry Pi
Method 2 – Bit Bang I2C
Confirm that you do not have I2C enabled. There should be no i2c devices under /dev/
ls: cannot access ‘/dev/i2c*’: No such file or directory
If you do have I2C enabled, the above command will return a file under the /dev/ directory.
You can disable I2C in /boot/config.txt
Look for the below line and comment it out by adding a “#” in front.
dtparam=i2c_arm=on
Now reboot.
Create the python script which will read the data by bit banging I2C from the GPS module
Copy in the below code;
import time import signal import sys import pigpio address = 0x42 gpsReadInterval = 0.03 SDA=2 SCL=3 pi = pigpio.pi() pi.set_pull_up_down(SDA, pigpio.PUD_UP) pi.set_pull_up_down(SCL, pigpio.PUD_UP) pi.bb_i2c_open(SDA,SCL,100000) def handle_ctrl_c(signal, frame): pi.bb_i2c_close(SDA) pi.stop() sys.exit(130) #This will capture exit when using Ctrl-C signal.signal(signal.SIGINT, handle_ctrl_c) def readGPS(): c = None response = [] while True: # Newline, or bad char. a=pi.bb_i2c_zip(SDA, [4, address, 2, 6, 1]) # Bit bang I2C read. 2 = Start, 6 = read, 1= How many bytes to read c = ord(a[1]) if c == 255: return False elif c == 10: break else: response.append(c) gpsChars = ''.join(chr(c) for c in response) #Convert list to string print gpsChars while True: readGPS() time.sleep(gpsReadInterval)
Create a virtual node, this is where we will send the NMEA sentences from the GPS module to
Now run the python script and redirect the output to the virtual node we just created.
We will also use stdbuf so the output from the script is sent to the virtual node one line at a time. Without this, the output is buffered and only sent to the virtual node when the buffer is full.
Configure GPSD to use the virtual buffer
No you can configure GPSD to point to the virtual buffer
Look for
DEVICES=””
and change it to
DEVICES=”/tmp/gps”
Restart GPSD so the new settings take effect.
You can now start using your GPS module with your Raspberry Pi