Phase 1: Prototype & Test on Arduino
- Hardware Setup
– GPS module wired to Arduino’s SoftwareSerial pins (e.g. D3=RX, D4=TX).
– USB-serial connection from Arduino to PC. - Load & Verify Sketch
– Upload the existing sketch that parses RMC/GGA/GSV and emits JSON at 115 200 baud.
– Open the Serial Monitor to confirm well-formed JSON lines every 10 s. - Test Web Client
– Serve the HTML/JS page locally or open it in a Chromium browser (with Web Serial API).
– Click “Connect to Arduino,” verify map, sky-view and info panel update correctly. - Stability Checks
– Log for several minutes, ensure no parser crashes or buffer overruns.
– Confirm JSON timing, sequence of fields, and client responsiveness under different GPS conditions.
Phase 2: Port Core Parsing to ATtiny85
- Toolchain & Bootloader
– Install ATtiny85 support in your Arduino IDE (e.g. “ATtinyCore”).
– Burn a suitable bootloader (e.g. Micronucleus or Optiboot-Tiny). - Reduce Sketch to Essentials
– RemoveSerialand JSON-output code.
– ReplaceSoftwareSerial(requires ~2 kB flash) with one-pin USI-based UART RX only, or TinySoftwareSerial.
– Keep only the minimal parsing routines for RMC, GGA, GSV. - Memory & Flash Optimization
– Move all constant strings ("$GPRMC,", format strings) into PROGMEM.
– Use integer arithmetic where possible (e.g. fixed-point for lat/lon).
– Limit maximum satellites (e.g. 12 instead of 32) to shrink RAM usage. - Verify on ATtiny
– Temporarily re-enable a simpleSerial-to-SoftwareSerialbridge on Arduino as USB-serial proxy.
– Connect GPS → ATtiny UART RX, ATtiny’s debug TX → Arduino RX.
– Confirm that your parsing still produces correct intermediate values (e.g. blink LEDs or toggle a pin for sanity).
Phase 3: Add I²C Slave Interface
- Define Register Map
– Decide fixed addresses for timestamp, lat×10⁷, lon×10⁷, alt×100, HDOP×100, total_sats, sat blocks. - Implement USI-TWI Slave
– Use AVR’s USI hardware in slave mode at address 0x42 (or your choice).
– OnUSI_OVF_vect, handle master’s read requests by returning the requested register bytes. - Data-Ready Notification (Optional)
– Reserve an ATtiny GPIO (e.g. PB2) as an open-drain “DATA_RDY” output.
– In parsing code, when a fresh fix is fully assembled, pulse DATA_RDY low for a few µs. - Test I²C Bridge
– On a standard Arduino UNO (I²C master), write a small sketch that:
• Polls the DATA_RDY pin viaattachInterrupt()or polls registers every 10 s.
• Reads back timestamp, lat/lon, HDOP and prints them to Serial.
– Validate the values against a known-good source (e.g. your original JSON client).
Phase 4: Finalize & Optimize
- Power Management
– Implementsleep_mode()between sentence parsing loops to save power.
– Optionally control GPS module’sPWR_ENpin to fully switch it off between fixes. - Flash & Fuse Settings
– Set ATtiny clock to 8 MHz internal (or 16 MHz if supported) for reliable USI timing.
– Configure Brown-Out Detector (BOD) for safe EEPROM/I²C operation. - Documentation & Packaging
– Create a concise datasheet: pinout, I²C address, register map, example host code.
– Offer the bridge as a small PCB or DIP-module for easy integration.
With these four phases—prototype on Arduino, port parsers to ATtiny, add I²C, and finalize—you’ll end up with a compact, low-power ATtiny85 GPS-to-I²C bridge that any I²C master can use without dealing with UART or NMEA parsing.

Part 1: Arduino Sketch (NMEA → JSON)
First the Arduino sketch, then the HTML/JS client—so you can see exactly how data flows from GPS hardware all the way to map+sky‐view in the browser.
1.1. Libraries & Pins
• SoftwareSerial on pins 3 (RX) & 4 (TX) at 9 600 baud to talk to the GPS module.
• TimeLib to keep track of a Unix timestamp once we parse a valid date/time.
#include <SoftwareSerial.h>
#include <TimeLib.h>
#define RX_PIN 3
#define TX_PIN 4
#define BAUD 9600
SoftwareSerial gps(RX_PIN, TX_PIN);
1.2. Data Buffers & Flags
– lineBuf[] & linePos accumulate incoming bytes until a full NMEA line (\n) arrives.
– Booleans haveDate, haveGGA, haveGSV track which pieces we’ve collected this cycle.
– Parsed fields:
• Time+date → set in TimeLib (now())
• lat_dd, lon_dd, alt_m, hdop from GGA
• An array sats[] of up to 32 satellites from GSV (PRN, elevation, azimuth, SNR)
1.3. setup()
Serial.begin(115200); // output JSON at 115 200 baud
gps.begin(BAUD); // GPS speaks at 9 600 baud
1.4. loop()
- Read & dispatch lines
– Whilegps.available(), read one char at a time.
– On'\n', terminatelineBufand calldispatch(lineBuf). - When we have date + GGA + GSV
– If all flags are true and 10 000 ms elapsed since last JSON, callemitJSON().
– ResethaveGGA,haveGSV, and satellite counter for next cycle.
1.5. dispatch(const char *s)
Looks at s[3..5] (characters 4–6) to pick the parser:
"RMC"→parseRMC()"GGA"→parseGGA()"GSV"→parseGSV()
1.6. Parsing RMC (Time & Date)
// Skip "$GPRMC," then read hhmmss.ss into tbuf[]
// Convert tbuf to int hh, mm, ss
// Check status ‘A’ = valid fix
// Skip 6 commas to reach date field ddmmyy in dbuf[]
// Convert to day, month, year
setTime(hh, mm, ss, day, month, year);
haveDate = true;
1.7. Parsing GGA (Position, HDOP, Altitude)
// Skip "$GPGGA," & time field
rawLat = atof(); // e.g. 3723.2475 (DDMM.MMMM)
ns = 'N' or 'S'
rawLon = atof(); // e.g. 12258.3416 (DDDMM.MMMM)
ew = 'E' or 'W'
fix = digit
if fix < 1 return; // no valid fix
hdop = atof();
alt_m = atof();
// Convert raw DDMM.MMMM → decimal degrees:
dlat = int(rawLat/100);
lat_dd = dlat + (rawLat - dlat*100)/60.0; apply sign for S
(same for lon_dd)
haveGGA = true;
1.8. Parsing GSV (Satellite Info)
// Skip "$GPGSV," + total sentences + sentence index
totalSats = atoi(); // number of sats in view
Then up to 4 satellites per sentence:
S.prn = atoi();
S.el = atoi();
S.az = atoi();
S.snr = atoi();
satCount++;
haveGSV = true;
1.9. Emitting JSON
Every 10 s, once we’ve got date, GGA, GSV:
uint32_t unixTs = now(); // epoch seconds
float acc_m = hdop * 5.0; // rough accuracy
Serial.print("{");
Serial.print("\"ts\":"); Serial.print(unixTs); Serial.print(",");
Serial.print("\"lat\":"); Serial.print(lat_dd,6); Serial.print(",");
Serial.print("\"lon\":"); Serial.print(lon_dd,6); Serial.print(",");
Serial.print("\"alt_m\":"); Serial.print(alt_m,1); Serial.print(",");
Serial.print("\"hdop\":"); Serial.print(hdop,2); Serial.print(",");
Serial.print("\"acc_m\":"); Serial.print(acc_m,1); Serial.print(",");
Serial.print("\"total_sats\":");Serial.print(totalSats); Serial.print(",");
Serial.print("\"sats\":[");
for each satellite i:
Serial.print("{\"prn\":"); Serial.print(sats[i].prn);
… print el,az,snr …
Serial.print("}");
if not last, print comma
Serial.println("]}");
Part 2: HTML + JavaScript Client
2.1. Page Structure & Styles
<button id="connect">Connect to Arduino</button>
<canvas id="mapCanvas"></canvas> <!-- slippy map -->
<div id="marker"></div> <!-- red center marker -->
<div id="info">No data</div> <!-- text status panel -->
<div id="sky"><canvas></canvas></div> <!-- sky‐view plot -->
CSS absolutely positions everything full‐screen, with map in background, marker centered, info bottom‐left, sky bottom‐right.
2.2. Map Rendering (OpenStreetMap Tiles)
- Coordinate ↔ Tile
- lon2x/lat2y convert geo → fractional tile coordinates at zoom level.
- Tile Cache & Fetch
ensureTile(z,x,y)fetches and caches PNG fromtile.openstreetmap.org/z/x/y.png.
- Drawing
- In
drawMap(), figure out which tiles cover the canvas, draw each cached tile at correct offset. - If
accuracy_m> 0, draw a translucent blue circle around the center to represent horizontal accuracy.
- In
2.3. Sky‐View Plot
- A polar “sky” circle of radius R.
- For each satellite
{ el, az, prn }, compute
r = (90−el)/90 * R
θ = az in radians
x = cx + r·sin(θ), y = cy − r·cos(θ) - Draw a small filled circle and label with PRN.
2.4. Web Serial Connection
connectBtn.onclick = async () => {
// Request port, open at 115200
// Create TextDecoderStream to split incoming by newline
// reader.read() yields lines; parse JSON
const obj = JSON.parse(line);
update(obj);
};
2.5. Data Update Routine
function update(data) {
if (data.acc_m) accuracy_m = data.acc_m;
if (data.lat && data.lon) {
centerLat = data.lat;
centerLon = data.lon;
scheduleDraw();
}
if (Array.isArray(data.sats)) {
satsData = data.sats;
drawSky();
}
if (data.ts) {
// format HH:MM:SS from epoch seconds
info.textContent = `Time: … UTC Lat: … Lon: … Alt: … HDOP: … Sats: …`;
}
}
How It All Flows
- GPS module → raw NMEA sentences at 9 600 baud →
- Arduino sketch parses RMC/GGA/GSV → every 10 s emits one JSON line at 115 200 baud →
- Browser (Web Serial API) reads JSON → calls
update()→ - Map canvas recenters and draws tiles + accuracy circle →
- Sky canvas redraws satellite positions →
- Info panel shows time, lat/lon, altitude, HDOP, satellite count.
With this breakdown, you can see exactly what data structures and timing each component uses—and you’re now ready to port the parsers to ATTiny85 and swap the UART→USB link for an I²C interface in Phase 2. Let me know where you’d like to dig in next!