
GPS I²C Bridge on ATtiny85 with Optional Power Control (Part 3)
In Phase 3 we extend the I²C-slave GPS bridge from Part 2 by:
- Porting the sketch to TinyWireS (USI-TWI) on an ATtiny85
- Adding an optional GPS power-switch controlled via I²C register 9
Hardware
- Microcontroller: ATtiny85
- GPS module: TTL-level NMEA TX/RX
- I²C pull-ups: 4.7 kΩ on SDA (A4) and SCL (A5)
- Optional low-side power switch:
- Logic-level N-channel MOSFET (e.g. 2N7000) or NPN transistor (e.g. 2N2222)
- Gate/base resistor: 100 Ω (MOSFET) or 4.7 kΩ (BJT)
- Gate/base pull-down: 100 kΩ
Pin Assignments
| ATtiny85 Pin | Function |
|---|---|
| D3 | GPS TX → SoftwareSerial RX |
| D4 | GPS RX ← SoftwareSerial TX |
| A4 (SDA) | I²C data (slave @ 0x42) |
| A5 (SCL) | I²C clock |
| D6 | GPS_PWR_PIN → gate/base of MOSFET/BJT (optional) |
I²C “Registers” and Data Packet
On each I²C request, the ATtiny85 sends one of the following registers (little-endian):
| Index | Field | Type | Bytes | Units / Description |
|---|---|---|---|---|
| 0 | utcHundredths | uint32_t | 4 | hhmmss.ss → total hundredths |
| 1 | dateDDMMYY | uint32_t | 4 | DDMMYY as integer |
| 2 | lat_fp | int32_t | 4 | Latitude × 1 000 000 (micro-degrees) |
| 3 | lon_fp | int32_t | 4 | Longitude × 1 000 000 |
| 4 | speed_x100 | uint16_t | 2 | Speed (knots)×100 |
| 5 | course_x100 | uint16_t | 2 | Course (deg)×100 |
| 6 | alt_cm | int32_t | 4 | Altitude (meters)×100 |
| 7 | hdop_x100 | uint16_t | 2 | HDOP×100 |
| 8 | satsUsed | uint8_t | 1 | Number of satellites |
| 9 | GPS_PWR state | uint8_t | 1 | 0=OFF, 1=ON (read/write) |
Slave Sketch
#include <Wire.h> // USIWire (or TinyWireS‐shim)
#include <SoftwareSerial.h>
#include <TinyGPSPlus.h>
#define I2C_ADDR 0x42
#define GPS_RX_PIN 3 // ← GPS TX
#define GPS_TX_PIN 4 // → GPS RX
#define GPS_PWR_PIN 6 // power‐enable for GPS
SoftwareSerial gpsSerial(GPS_RX_PIN, GPS_TX_PIN);
TinyGPSPlus gps;
// last register index
volatile uint8_t regIndex = 0;
// parsed GPS values
uint32_t utcHundredths, dateDDMMYY;
int32_t lat_fp, lon_fp, alt_cm;
uint16_t speed_x100, course_x100, hdop_x100;
uint8_t satsUsed;
// called when master does Wire.write(regIndex[, value])
void receiveEvent(int howMany) {
if (Wire.available()) {
uint8_t r = Wire.read();
regIndex = r;
// if master is writing to register 9, read the next byte value
if (r == 9 && Wire.available()) {
uint8_t v = Wire.read();
// v==1 → ON, v==0 → OFF
digitalWrite(GPS_PWR_PIN, v ? HIGH : LOW);
}
}
}
// called when master does Wire.requestFrom()
void requestEvent() {
// update fields only on a full valid fix
if (gps.time.isValid() &&
gps.date.isValid() &&
gps.location.isValid()&&
gps.hdop.isValid() &&
gps.speed.isValid() &&
gps.course.isValid() &&
gps.altitude.isValid()&&
gps.satellites.isValid())
{
utcHundredths =
(uint32_t(gps.time.hour()) * 3600UL +
uint32_t(gps.time.minute()) * 60UL +
uint32_t(gps.time.second())) * 100UL +
uint32_t(gps.time.centisecond());
dateDDMMYY =
uint32_t(gps.date.day()) * 10000UL +
uint32_t(gps.date.month()) * 100UL +
uint32_t(gps.date.year() % 100);
lat_fp = int32_t(gps.location.lat() * 1e6);
lon_fp = int32_t(gps.location.lng() * 1e6);
speed_x100 = uint16_t(gps.speed.knots() * 100.0);
course_x100 = uint16_t(gps.course.deg() * 100.0);
alt_cm = int32_t(gps.altitude.meters() * 100.0);
hdop_x100 = uint16_t(gps.hdop.hdop() * 100.0);
satsUsed = gps.satellites.value();
}
// respond to master based on regIndex
uint8_t *p;
switch (regIndex) {
case 0:
p = (uint8_t*)&utcHundredths; Wire.write(p, 4); break;
case 1:
p = (uint8_t*)&dateDDMMYY; Wire.write(p, 4); break;
case 2:
p = (uint8_t*)&lat_fp; Wire.write(p, 4); break;
case 3:
p = (uint8_t*)&lon_fp; Wire.write(p, 4); break;
case 4:
p = (uint8_t*)&speed_x100; Wire.write(p, 2); break;
case 5:
p = (uint8_t*)&course_x100; Wire.write(p, 2); break;
case 6:
p = (uint8_t*)&alt_cm; Wire.write(p, 4); break;
case 7:
p = (uint8_t*)&hdop_x100; Wire.write(p, 2); break;
case 8:
Wire.write(&satsUsed, 1); break;
case 9:
{ // report current GPS_PWR_PIN state
uint8_t st = digitalRead(GPS_PWR_PIN);
Wire.write(&st, 1);
}
break;
default:
// nothing
break;
}
}
void setup() {
// GPS power‐control pin
pinMode(GPS_PWR_PIN, OUTPUT);
// by default keep GPS ON
digitalWrite(GPS_PWR_PIN, HIGH);
gpsSerial.begin(9600);
Wire.begin(I2C_ADDR);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
}
void loop() {
while (gpsSerial.available()) {
gps.encode(gpsSerial.read());
}
// no other tasks
}
Master Usage
- Write the desired register index:
Wire.beginTransmission(0x42); Wire.write(regIndex); Wire.endTransmission(); - Request N bytes (matching that register’s length) and read them:
Wire.requestFrom(0x42, N); … - Reconstruct the little-endian value and print or use in your code.
- To toggle GPS power, write register 9 with
0(OFF) or1(ON).

Master sketch
#include <Wire.h>
#define GPS_ADDR 0x42
enum {
R_UTC,
R_DATE,
R_LAT,
R_LON,
R_SPEED,
R_COURSE,
R_ALT,
R_HDOP,
R_SATS,
R_COUNT
};
void setup() {
Serial.begin(115200);
while (!Serial);
Wire.begin();
}
void loop() {
// Read all nine registers
uint32_t utc = readU32(R_UTC);
uint32_t date = readU32(R_DATE);
int32_t lat_fp = readS32(R_LAT);
int32_t lon_fp = readS32(R_LON);
uint16_t speed = readU16(R_SPEED);
uint16_t course = readU16(R_COURSE);
int32_t alt_cm = readS32(R_ALT);
uint16_t hdop = readU16(R_HDOP);
uint8_t sats = readU8(R_SATS);
// Convert & print
Serial.print("Time: ");
printTime(utc);
Serial.print("Date: ");
printDate(date);
Serial.print("Lat: "); Serial.println(lat_fp/1e6,6);
Serial.print("Lon: "); Serial.println(lon_fp/1e6,6);
Serial.print("Speed: "); Serial.println(speed/100.0,2);
Serial.print("Course: "); Serial.println(course/100.0,2);
Serial.print("Alt: "); Serial.println(alt_cm/100.0,2);
Serial.print("HDOP: "); Serial.println(hdop/100.0,2);
Serial.print("Sats: "); Serial.println(sats);
Serial.println("-------------------------");
delay(1000);
}
uint32_t readU32(uint8_t reg) {
Wire.beginTransmission(GPS_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(GPS_ADDR, (uint8_t)4);
uint32_t v=0;
for(int i=0;i<4;i++) v |= (uint32_t)Wire.read() << (8*i);
return v;
}
int32_t readS32(uint8_t reg) { return (int32_t)readU32(reg); }
uint16_t readU16(uint8_t reg) {
Wire.beginTransmission(GPS_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(GPS_ADDR, (uint8_t)2);
uint16_t v= Wire.read();
v |= (uint16_t)Wire.read()<<8;
return v;
}
uint8_t readU8(uint8_t reg) {
Wire.beginTransmission(GPS_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(GPS_ADDR, (uint8_t)1);
return Wire.read();
}
void printTime(uint32_t ch) {
uint8_t cs= ch%100;
uint32_t s= ch/100;
uint8_t ss= s%60, mm= (s/60)%60, hh= s/3600;
if(hh<10) Serial.print('0'); Serial.print(hh); Serial.print(':');
if(mm<10) Serial.print('0'); Serial.print(mm); Serial.print(':');
if(ss<10) Serial.print('0'); Serial.print(ss); Serial.print('.');
if(cs<10) Serial.print('0'); Serial.println(cs);
}
void printDate(uint32_t d) {
uint8_t DD= d/10000, MM= (d/100)%100, YY= d%100;
if(DD<10) Serial.print('0'); Serial.print(DD); Serial.print('/');
if(MM<10) Serial.print('0'); Serial.print(MM); Serial.print('/');
if(YY<10) Serial.print('0'); Serial.println(YY);
}
Conclusion
This ATtiny85 + TinyWireS sketch provides a fully self-contained GPS-to-I²C bridge. It delivers compact, fixed-point GPS fixes on demand and optionally allows the master to power-cycle the GPS module via a low-side MOSFET or BJT—no USB/UART needed on the host side.