Slave I2C Interface for Serial GPS using Arduino Pro Mini 328P
This post contains the code for creating a slave I2C device using an Arduino for a GPS receiver.
Hardware serial ports are precious on ATMega devices and I don't have enough available in my project to use on a GPS receiver, despite using an ATMega2560.
Software serial ports are often ok, but can suffer compatibility issues with other libraries in larger projects, and this has been prohibitive for me.
You can buy I2C GPS devices, and I do prefer to use them, but the one I was using failed, and I needed a quick solution to get my project back on the water without having to wait the 2 or 3 weeks for a replacement I2C GPS to arrive.
So, after an evening's research I was able to create a working I2C slave device using an Arduino Pro Mini 328P and integrate it with a regular serial GPS receiver.
The Arduino Pro Mini 328P running is at 16MHz and 3V3. Yes, I know its outside of specification with that combination of voltage and clock speed.
Overview
This simple sketch uses the very excellent TinyGPS++ to parse the GPS serial data and load the GPS object. Then we copy individual field values into our GPS structure which is mapped to linear Data buffer using UNION statement.
The Data buffer is then written out to the I2C interface in response to an I2C request.
Wiring
The Tx Pin from the GPS is connected to the RX pin of the Arduino Pro Mini 328P using a 1k resistor. This provides isolation to allow the Arduino to be programmed over the serial interface while the GPS is connected. If this were a direct connection, serial programming of the Arduino would not be possible without disconnecting the GPS.
Slave I2C GPS sketch
/*
Name: I2C_GPS.ino
Created: 2 May 2019
Author: John Semmens
Slave I2C Interface for Serial GPS using Arduino Pro Mini 328P
----------------------------------------------------------------
Interface for converting a serial GPS to allow it to be used on an I2C bus.
This uses the Arduino Pro Mini 328P running at 16MHz and 3V3. (Yes, i know its outside of specification!!!).
This simple sketch uses the very excellent TinyGPS++ to parse the GPS serial data and load the GPS object.
Then we copy individual field values into our GPS structure which is mapped to linear Data buffer using UNION statement.
The Data buffer is then written out to the I2C interface in response to an I2C request.
Wiring:
The Tx Pin from the GPS is connected to the RX pin of the Arduino Pro Mini 328P using a 1k resistor.
This provides isolation to allow the Arduino to be programmed over the serial interface while the GPS is connected.
If this were a direct connection, serial programming of the Arduino would not be possible without disconnecting the GPS.
*/
#include <SPI.h>
#include <Wire.h>
#include "TinyGPS++.h"
// The TinyGPS++ object
TinyGPSPlus gps;
static const uint32_t GPSBaud = 9600;
#define SLAVE_ADDRESS 0x29 //slave I2C address: 0x01 to 0x7F
#define REG_MAP_SIZE 18
// GPS variables
typedef union {
struct {
long Lat, Long; // 2 x 4 bytes
uint8_t Year, Month, Day; // 3 x 1 bytes
uint8_t Hour, Minute, Second; // 3 x 1 bytes
int COG, SOG; // 2 x 2 bytes SOG is in m/s
};
uint8_t Data[REG_MAP_SIZE]; // = 18 bytes
} buffer_t;
buffer_t gps_data,buf;
boolean newDataAvailable = false;
void setup()
{
Wire.begin(SLAVE_ADDRESS);
Wire.onRequest(requestEvent);
Serial.begin(GPSBaud);
}
void loop()
{
while (Serial.available() > 0)
{
if (gps.encode(Serial.read()))
{
LoadRegisters();
newDataAvailable = true;
}
}
}
void requestEvent()
{
if (newDataAvailable)
{
for (int c = 0; c < (REG_MAP_SIZE); c++)
{
buf.Data[c] = gps_data.Data[c];
}
}
newDataAvailable = false;
Wire.write(buf.Data, REG_MAP_SIZE);
}
void LoadRegisters()
{
gps_data.Lat = gps.location.lat() * 10000000UL;
gps_data.Long = gps.location.lng() * 10000000UL;
gps_data.COG = gps.course.deg() * 100;
gps_data.SOG = gps.speed.mps() * 100; // m/s
gps_data.Year = gps.date.year()-2000;
gps_data.Month = gps.date.month();
gps_data.Day = gps.date.day();
gps_data.Hour = gps.time.hour();
gps_data.Minute = gps.time.minute();
gps_data.Second = gps.time.second();
// Copy gps buffer to buf buffer
for (int i = 0; i<REG_MAP_SIZE; i++)
buf.Data[i] = gps_data.Data[i];
}
Master I2C Code Excerpt
void GPS_Read() {
// V1.1 27/4/2019 Temp GPS reader for Temp I2C Arduino interface to a serial GPS
Wire.requestFrom(0x29, 18); // Ask for 18 bytes
long Lat=0, Long=0; // 8 bytes
uint8_t Year =0, Month=0, Day=0; // 3 bytes
uint8_t Hour=0, Minute=0, Second=0; // 3 bytes
int COG=0, SOG=0; // 8 bytes
for (int i = 0; i<4; i++)
Lat = Lat | (long)Wire.read() << (i * 8);
for (int i = 0; i<4; i++)
Long = Long | (long)Wire.read() << (i * 8);
Year = Wire.read();
Month = Wire.read();
Day = Wire.read();
Hour = Wire.read();
Minute = Wire.read();
Second = Wire.read();
for (int i = 0; i<2; i++)
COG = COG | Wire.read() << (i * 8);
for (int i = 0; i<2; i++)
SOG = SOG | Wire.read() << (i * 8);
NavData.Currentloc.lat = Lat;
NavData.Currentloc.lng = Long;
// get the date and time from GPS into CurrentTime object.
CurrentUTCTime.year = Year;
CurrentUTCTime.month = Month;
CurrentUTCTime.dayOfMonth = Day;
CurrentUTCTime.hour = Hour;
CurrentUTCTime.minute = Minute;
CurrentUTCTime.second = Second;
// get course and speed directly from GPS
NavData.COG = ((float)COG/100);
NavData.SOG_mps = ((float)SOG / 100);
};