I burned through 3 months of field testing before I figured this out: the GPS module's time to first fix (TTFF) was eating 70% of our tracker's battery budget, and the datasheet numbers were completely misleading.
Here's what I learned after 20+ years of shipping IoT tracking hardware to 100+ countries — and the firmware patterns that actually solve it.
The Problem in One Number
A typical GNSS module draws 25–40 mA during satellite acquisition. Here's the per-fix energy cost:
Cold start: 30 mA × 60s = 0.50 mAh per fix
Hot start: 30 mA × 3s = 0.025 mAh per fix
─────────────────
20× difference
Compound that across 4 reports/day, 365 days/year, on a 6,000 mAh battery:
| Scenario | GNSS mAh/day | GNSS-only life | Real-world life |
|---|---|---|---|
| All cold starts | 2.0 | ~8 years | 14 months |
| All hot starts | 0.1 | ~164 years | 38 months |
That's a 2.7× real-world battery life improvement from firmware alone — same hardware, same battery.
The Three GNSS Start Modes
Every GNSS receiver classifies startup based on cached data:
Cold Start (worst case)
No stored ephemeris, no almanac, no position, no time. The receiver performs a full sky search and downloads the navigation message from scratch.
TTFF: 30s to 12+ minutes
When: first power-on, device off > 4 hours, moved > 100km while off
The navigation message itself takes 18–30s to download per satellite, and the full almanac cycle is 12.5 minutes.
Warm Start
Has almanac + approximate position/time, but ephemeris is outdated. Knows which satellites to look for but needs fresh orbital data.
TTFF: 25–45 seconds
When: device off for 2–4 hours, ephemeris expired
Hot Start (the goal)
Valid ephemeris (< 2–4 hours old), accurate time via RTC, recent position. Receiver tracks known satellites immediately.
TTFF: 1–5 seconds
When: short sleep cycles, ephemeris still valid, RTC running
Five Firmware Strategies That Work
1. Assisted GNSS (AGNSS)
Pre-load ephemeris via cellular before starting GNSS acquisition:
// Pseudocode: AGNSS-first approach
void start_position_fix() {
modem_wake(); // ~2s
agnss_download_ephemeris(); // ~3s via LTE-M
gnss_inject_ephemeris(cache); // inject to GNSS module
gnss_start_acquisition(); // now it's a hot start
// Net cost: 5s modem + 3s GNSS = 8s total
// vs. 60-120s cold start GNSS-only
}
Modern implementations (u-blox AssistNow, Qualcomm gpsOneXTRA) provide predicted ephemeris valid for 1–14 days, so even after long sleep periods you get near-hot-start performance.
2. Ephemeris Caching in Flash
Store last valid ephemeris, almanac, position, and UTC offset in non-volatile memory:
typedef struct {
uint8_t ephemeris[MAX_SATS][EPHEMERIS_SIZE];
uint32_t almanac_week;
double last_lat, last_lon;
uint32_t utc_offset_ms;
uint32_t timestamp; // for freshness check
} gnss_cache_t;
void on_valid_fix(gnss_fix_t *fix) {
gnss_cache_t cache;
gnss_read_ephemeris(cache.ephemeris);
cache.last_lat = fix->latitude;
cache.last_lon = fix->longitude;
cache.timestamp = rtc_get_time();
flash_write(GNSS_CACHE_ADDR, &cache, sizeof(cache));
}
void on_wake() {
gnss_cache_t cache;
flash_read(GNSS_CACHE_ADDR, &cache, sizeof(cache));
uint32_t age = rtc_get_time() - cache.timestamp;
if (age < EPHEMERIS_MAX_AGE) {
gnss_inject_ephemeris(cache.ephemeris);
gnss_set_position(cache.last_lat, cache.last_lon);
// -> hot start
} else {
// -> fall back to AGNSS or warm start
}
}
Critical detail: the RTC must remain powered during sleep. If the RTC resets, cached ephemeris is useless because the receiver can't compute current satellite positions without accurate time.
3. Multi-Constellation (GPS + GLONASS + BeiDou + Galileo)
More visible satellites = faster acquisition:
| Configuration | Visible SVs | Cold TTFF | Improvement |
|---|---|---|---|
| GPS only | ~8 | 45s | baseline |
| GPS + GLONASS | ~14 | 32s | -29% |
| GPS + GLO + BDS + GAL | ~24 | 22s | -51% |
The tradeoff is slightly higher current draw (scanning more frequencies), but the faster fix time more than compensates.
4. Adaptive Timeout with Fallback
#define GNSS_TIMEOUT_1ST 60 // seconds
#define GNSS_TIMEOUT_RETRY 120
#define SKIP_CYCLES_ON_FAIL 2
static uint8_t consecutive_fails = 0;
void position_report_cycle() {
if (consecutive_fails >= SKIP_CYCLES_ON_FAIL) {
report_cell_id_position(); // fallback: 50-200m accuracy
consecutive_fails = 0;
return;
}
uint16_t timeout = (consecutive_fails > 0)
? GNSS_TIMEOUT_RETRY
: GNSS_TIMEOUT_1ST;
gnss_fix_t fix = gnss_acquire(timeout);
if (fix.valid) {
consecutive_fails = 0;
report_gnss_position(&fix);
cache_ephemeris(&fix);
} else {
consecutive_fails++;
report_cell_id_position(); // don't waste the cycle
}
}
This prevents a tracker stuck in a warehouse from burning battery on 5-minute GNSS searches it'll never succeed at.
5. Standby Mode vs Full Power-Off
Keep GNSS in low-power standby (~10–50 µA) instead of full power-off:
- Report every 1–4 hours: standby mode pays for itself — guaranteed hot starts
- Report every 12–24 hours: AGNSS is better — ephemeris expires, standby current adds up
- Report once per day: AGNSS + full power-off — minimize continuous drain
What to Ask Your Tracker Vendor
If you're evaluating trackers for fleet or asset management:
- "Does the firmware support AGNSS?" — If no, every post-sleep fix is cold.
- "What's the GNSS timeout?" — If they say "unlimited" or can't answer, run.
- "Is the RTC battery-backed?" — Without it, hot starts are impossible.
- "What's the fallback when GNSS fails?" — Cell-ID/Wi-Fi fingerprinting should kick in.
A tracker that advertises "5-year battery life" based on hot-start TTFF numbers will deliver 14 months if the firmware can't maintain hot starts in the field.
Wrapping Up
TTFF management is the single highest-leverage optimization in asset tracker firmware. The difference between "GNSS just runs" and "GNSS is actively managed" is measured in years of battery life.
Have you run into unexpected battery drain from GNSS cold starts in your deployments? What strategies worked for you?
This article was written with AI assistance for research and drafting.
Top comments (0)