mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
Hopefully faster gps fix after cold boot
This commit is contained in:
@@ -3,15 +3,20 @@
|
||||
#include <Arduino.h>
|
||||
#include "variant.h"
|
||||
#include "GPSStreamCounter.h"
|
||||
#include "GPSAiding.h"
|
||||
|
||||
// GPS Duty Cycle Manager
|
||||
// Controls the hardware GPS enable pin (PIN_GPS_EN) to save power.
|
||||
// When enabled, cycles between acquiring a fix and sleeping with power cut.
|
||||
//
|
||||
// After each power-on, sends UBX-MGA-INI aiding messages (last known
|
||||
// position + RTC time) to the MIA-M10Q to reduce TTFF from cold-start
|
||||
// (~3 min) down to ~30-60 seconds.
|
||||
//
|
||||
// States:
|
||||
// OFF – User has disabled GPS. Hardware power is cut.
|
||||
// ACQUIRING – GPS module powered on, waiting for a fix or timeout.
|
||||
// SLEEPING – GPS module powered off, timer counting down to next cycle.
|
||||
// OFF — User has disabled GPS. Hardware power is cut.
|
||||
// ACQUIRING — GPS module powered on, waiting for a fix or timeout.
|
||||
// SLEEPING — GPS module powered off, timer counting down to next cycle.
|
||||
|
||||
#if HAS_GPS
|
||||
|
||||
@@ -31,6 +36,13 @@
|
||||
#define GPS_MIN_ON_TIME_MS 5000 // 5 seconds after fix
|
||||
#endif
|
||||
|
||||
// Delay after hardware power-on before sending UBX aiding (ms).
|
||||
// The MIA-M10Q needs time to boot its firmware before it can accept
|
||||
// UBX commands on UART. 200ms is conservative; 100ms may work.
|
||||
#ifndef GPS_BOOT_DELAY_MS
|
||||
#define GPS_BOOT_DELAY_MS 250
|
||||
#endif
|
||||
|
||||
enum class GPSDutyState : uint8_t {
|
||||
OFF = 0, // User-disabled, hardware power off
|
||||
ACQUIRING, // Hardware on, waiting for fix
|
||||
@@ -41,11 +53,34 @@ class GPSDutyCycle {
|
||||
public:
|
||||
GPSDutyCycle() : _state(GPSDutyState::OFF), _state_entered(0),
|
||||
_last_fix_time(0), _got_fix(false), _time_synced(false),
|
||||
_stream(nullptr) {}
|
||||
_stream(nullptr), _serial(nullptr),
|
||||
_aid_lat(0.0), _aid_lon(0.0), _aid_has_pos(false),
|
||||
_rtc_time_fn(nullptr) {}
|
||||
|
||||
// Attach the stream counter so we can reset it on power cycles
|
||||
void setStreamCounter(GPSStreamCounter* stream) { _stream = stream; }
|
||||
|
||||
// Attach the raw GPS serial port for sending UBX aiding commands.
|
||||
// This should be the same underlying Stream that GPSStreamCounter wraps
|
||||
// (e.g. &Serial2). If not set, aiding is silently skipped.
|
||||
void setSerialPort(Stream* serial) { _serial = serial; }
|
||||
|
||||
// Provide the last known position for aiding on next power-on.
|
||||
// Call this at startup with saved prefs, and again after each fix
|
||||
// with updated coordinates. lat/lon in degrees.
|
||||
void setLastKnownPosition(double lat, double lon) {
|
||||
if (lat != 0.0 || lon != 0.0) {
|
||||
_aid_lat = lat;
|
||||
_aid_lon = lon;
|
||||
_aid_has_pos = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Provide a function that returns the current UTC epoch (Unix time).
|
||||
// Used for time aiding. Typically: []() { return rtc.getCurrentTime(); }
|
||||
// If not set or if the returned value is < year 2024, time aiding is skipped.
|
||||
void setRTCTimeSource(uint32_t (*fn)()) { _rtc_time_fn = fn; }
|
||||
|
||||
// Call once in setup() after board.begin() and GPS serial init.
|
||||
void begin(bool initial_enable) {
|
||||
if (initial_enable) {
|
||||
@@ -100,6 +135,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notify that a GPS fix was obtained. Optionally update the stored
|
||||
// aiding position so the next power cycle uses the freshest data.
|
||||
void notifyFix() {
|
||||
if (_state == GPSDutyState::ACQUIRING && !_got_fix) {
|
||||
_got_fix = true;
|
||||
@@ -108,6 +145,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Extended version: also capture the fix position for future aiding
|
||||
void notifyFix(double lat, double lon) {
|
||||
notifyFix();
|
||||
setLastKnownPosition(lat, lon);
|
||||
}
|
||||
|
||||
void notifyTimeSync() {
|
||||
_time_synced = true;
|
||||
}
|
||||
@@ -161,6 +204,9 @@ private:
|
||||
delay(10);
|
||||
#endif
|
||||
if (_stream) _stream->resetCounters();
|
||||
|
||||
// Send aiding data after the module has booted
|
||||
_sendAiding();
|
||||
}
|
||||
|
||||
void _powerOff() {
|
||||
@@ -169,6 +215,60 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send UBX-MGA-INI position and time aiding to the GPS module.
|
||||
// Called immediately after _powerOn(). The module needs a short
|
||||
// boot delay before it can process UBX commands.
|
||||
void _sendAiding() {
|
||||
if (_serial == nullptr) return;
|
||||
|
||||
// Wait for the MIA-M10Q firmware to boot after power-on
|
||||
delay(GPS_BOOT_DELAY_MS);
|
||||
|
||||
// Gather aiding data
|
||||
uint32_t utcTime = 0;
|
||||
if (_rtc_time_fn) {
|
||||
utcTime = _rtc_time_fn();
|
||||
}
|
||||
|
||||
bool hasTime = (utcTime >= 1704067200UL); // >= 2024-01-01
|
||||
|
||||
if (!_aid_has_pos && !hasTime) {
|
||||
MESH_DEBUG_PRINTLN("GPS aid: no aiding data available (cold start)");
|
||||
return;
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("GPS aid: sending aiding (pos=%s, time=%s)",
|
||||
_aid_has_pos ? "yes" : "no",
|
||||
hasTime ? "yes" : "no");
|
||||
|
||||
// Use generous accuracy for stale position data.
|
||||
// 300m (30000cm) is reasonable for a device that hasn't moved much.
|
||||
// If position is from prefs (potentially very old), use 500m.
|
||||
uint32_t posAccCm = 50000; // 500m default for saved prefs
|
||||
|
||||
// If we got a fix this boot session, the position is fresher
|
||||
if (_last_fix_time > 0) {
|
||||
unsigned long ageMs = millis() - _last_fix_time;
|
||||
if (ageMs < 3600000UL) { // < 1 hour old
|
||||
posAccCm = 10000; // 100m
|
||||
} else if (ageMs < 86400000UL) { // < 24 hours old
|
||||
posAccCm = 30000; // 300m
|
||||
}
|
||||
}
|
||||
|
||||
// Time accuracy: RTC without GPS sync drifts ~2ppm = ~1 min/month.
|
||||
// After a recent GPS time sync, accuracy is within a few seconds.
|
||||
// Conservative default: 10 seconds.
|
||||
uint16_t timeAccSec = 10;
|
||||
if (_time_synced) {
|
||||
// RTC was synced from GPS this boot — much more accurate
|
||||
timeAccSec = 2;
|
||||
}
|
||||
|
||||
GPSAiding::sendAllAiding(*_serial, _aid_lat, _aid_lon,
|
||||
utcTime, posAccCm, timeAccSec);
|
||||
}
|
||||
|
||||
void _setState(GPSDutyState s) {
|
||||
_state = s;
|
||||
_state_entered = millis();
|
||||
@@ -180,6 +280,13 @@ private:
|
||||
bool _got_fix;
|
||||
bool _time_synced;
|
||||
GPSStreamCounter* _stream;
|
||||
|
||||
// Aiding support
|
||||
Stream* _serial; // Raw GPS UART for sending UBX commands
|
||||
double _aid_lat; // Last known latitude (degrees)
|
||||
double _aid_lon; // Last known longitude (degrees)
|
||||
bool _aid_has_pos; // true if we have a valid position
|
||||
uint32_t (*_rtc_time_fn)(); // Returns current UTC epoch, or nullptr
|
||||
};
|
||||
|
||||
#endif // HAS_GPS
|
||||
239
variants/lilygo_tdeck_pro/GPSaiding.h
Normal file
239
variants/lilygo_tdeck_pro/GPSaiding.h
Normal file
@@ -0,0 +1,239 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// =============================================================================
|
||||
// GPS Aiding Helper for u-blox MIA-M10Q
|
||||
// =============================================================================
|
||||
//
|
||||
// Sends UBX-MGA-INI aiding messages to the GNSS receiver after each power
|
||||
// cycle to reduce Time-To-First-Fix (TTFF). When the duty cycle manager
|
||||
// cuts hardware power, the MIA-M10Q loses all ephemeris, almanac, position,
|
||||
// and time data — every wake-up is a cold start (~2-3 min).
|
||||
//
|
||||
// By injecting the last known position (UBX-MGA-INI-POS_LLH) and the
|
||||
// current UTC time (UBX-MGA-INI-TIME_UTC) immediately after power-on,
|
||||
// the receiver can narrow its satellite search window and skip the slow
|
||||
// almanac download phase. This typically reduces TTFF from ~3 min to
|
||||
// 30-60 seconds even without backup battery support.
|
||||
//
|
||||
// Usage:
|
||||
// After powering the GPS module on and waiting for it to boot (~200ms),
|
||||
// call sendPositionAid() and sendTimeAid() via the GPS serial port.
|
||||
//
|
||||
// Reference:
|
||||
// u-blox MIA-M10Q Integration Manual (UBX-21028173)
|
||||
// Section 2.13 - AssistNow and aiding data
|
||||
// =============================================================================
|
||||
|
||||
namespace GPSAiding {
|
||||
|
||||
// ---- UBX frame helpers ----
|
||||
|
||||
// Compute UBX Fletcher checksum over a buffer (class+id+length+payload)
|
||||
static void ubxChecksum(const uint8_t* buf, size_t len, uint8_t& ckA, uint8_t& ckB) {
|
||||
ckA = 0;
|
||||
ckB = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ckA += buf[i];
|
||||
ckB += ckA;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a complete UBX frame: sync(2) + class/id/len/payload + checksum(2)
|
||||
// 'body' points to class byte, 'bodyLen' = 4 (header) + payload length
|
||||
static void ubxSend(Stream& port, const uint8_t* body, size_t bodyLen) {
|
||||
const uint8_t sync[] = { 0xB5, 0x62 };
|
||||
port.write(sync, 2);
|
||||
port.write(body, bodyLen);
|
||||
|
||||
uint8_t ckA, ckB;
|
||||
ubxChecksum(body, bodyLen, ckA, ckB);
|
||||
port.write(ckA);
|
||||
port.write(ckB);
|
||||
}
|
||||
|
||||
// ---- Aiding messages ----
|
||||
|
||||
// Send UBX-MGA-INI-POS_LLH (position aiding)
|
||||
//
|
||||
// lat, lon: degrees (double, e.g. -33.8688, 151.2093)
|
||||
// altCm: altitude in centimetres (0 if unknown)
|
||||
// accCm: position accuracy estimate in cm (e.g. 30000 = 300m)
|
||||
//
|
||||
// The MIA-M10Q uses this to seed its position estimate, reducing the
|
||||
// satellite search space from global to a regional window.
|
||||
static void sendPositionAid(Stream& port, double lat, double lon,
|
||||
int32_t altCm = 0, uint32_t accCm = 30000)
|
||||
{
|
||||
// Validate: skip if position is clearly invalid (0,0 or out of range)
|
||||
if (lat == 0.0 && lon == 0.0) return;
|
||||
if (lat < -90.0 || lat > 90.0 || lon < -180.0 || lon > 180.0) return;
|
||||
|
||||
// UBX-MGA-INI (class 0x13, id 0x40), payload = 20 bytes
|
||||
// Payload layout for type 0x01 (POS_LLH):
|
||||
// [0] type = 0x01
|
||||
// [1] version = 0x00
|
||||
// [2-3] reserved = 0x00
|
||||
// [4-7] lat = int32 (degrees * 1e7)
|
||||
// [8-11] lon = int32 (degrees * 1e7)
|
||||
// [12-15] alt = int32 (cm above ellipsoid)
|
||||
// [16-19] posAcc = uint32 (cm, position accuracy)
|
||||
|
||||
const uint16_t payloadLen = 20;
|
||||
uint8_t frame[4 + payloadLen]; // class + id + length(2) + payload
|
||||
|
||||
// Header
|
||||
frame[0] = 0x13; // class: MGA
|
||||
frame[1] = 0x40; // id: INI
|
||||
frame[2] = payloadLen & 0xFF; // length low
|
||||
frame[3] = (payloadLen >> 8) & 0xFF; // length high
|
||||
|
||||
// Payload
|
||||
uint8_t* p = &frame[4];
|
||||
memset(p, 0, payloadLen);
|
||||
|
||||
p[0] = 0x01; // type = POS_LLH
|
||||
p[1] = 0x00; // version
|
||||
|
||||
int32_t latE7 = (int32_t)(lat * 1e7);
|
||||
int32_t lonE7 = (int32_t)(lon * 1e7);
|
||||
|
||||
memcpy(&p[4], &latE7, 4);
|
||||
memcpy(&p[8], &lonE7, 4);
|
||||
memcpy(&p[12], &altCm, 4);
|
||||
memcpy(&p[16], &accCm, 4);
|
||||
|
||||
ubxSend(port, frame, sizeof(frame));
|
||||
|
||||
MESH_DEBUG_PRINTLN("GPS aid: sent POS_LLH lat=%.5f lon=%.5f acc=%ucm",
|
||||
lat, lon, (unsigned)accCm);
|
||||
}
|
||||
|
||||
// Send UBX-MGA-INI-TIME_UTC (time aiding)
|
||||
//
|
||||
// utcEpoch: Unix timestamp (seconds since 1970-01-01)
|
||||
// accSec: time accuracy estimate in seconds (e.g. 2 = within 2s)
|
||||
//
|
||||
// Providing approximate UTC time lets the receiver predict which
|
||||
// satellites should be visible, dramatically speeding up acquisition.
|
||||
static void sendTimeAid(Stream& port, uint32_t utcEpoch, uint16_t accSec = 2)
|
||||
{
|
||||
// Validate: skip if time looks invalid (before year 2024)
|
||||
if (utcEpoch < 1704067200UL) return; // 2024-01-01
|
||||
|
||||
// Break epoch into calendar components
|
||||
// Simple gmtime implementation for embedded use
|
||||
uint32_t t = utcEpoch;
|
||||
uint16_t year;
|
||||
uint8_t month, day, hour, minute, second;
|
||||
|
||||
second = t % 60; t /= 60;
|
||||
minute = t % 60; t /= 60;
|
||||
hour = t % 24; t /= 24;
|
||||
|
||||
// Days since 1970-01-01
|
||||
uint32_t days = t;
|
||||
|
||||
// Calculate year
|
||||
year = 1970;
|
||||
while (true) {
|
||||
uint16_t daysInYear = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ? 366 : 365;
|
||||
if (days < daysInYear) break;
|
||||
days -= daysInYear;
|
||||
year++;
|
||||
}
|
||||
|
||||
// Calculate month and day
|
||||
bool leap = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
|
||||
const uint16_t daysInMonth[] = { 31, (uint16_t)(leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
month = 1;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (days < daysInMonth[i]) break;
|
||||
days -= daysInMonth[i];
|
||||
month++;
|
||||
}
|
||||
day = days + 1;
|
||||
|
||||
// UBX-MGA-INI (class 0x13, id 0x40), payload = 24 bytes
|
||||
// Payload layout for type 0x10 (TIME_UTC):
|
||||
// [0] type = 0x10
|
||||
// [1] version = 0x00
|
||||
// [2] ref = 0x00 (UTC reference)
|
||||
// [3] leapSecs = 0x80 (unknown)
|
||||
// [4-5] year = uint16
|
||||
// [6] month = uint8 (1-12)
|
||||
// [7] day = uint8 (1-31)
|
||||
// [8] hour = uint8 (0-23)
|
||||
// [9] minute = uint8 (0-59)
|
||||
// [10] second = uint8 (0-60)
|
||||
// [11] reserved1 = 0x00
|
||||
// [12-15] ns = uint32 (nanoseconds, 0)
|
||||
// [16-17] tAccS = uint16 (accuracy, seconds part)
|
||||
// [18-19] reserved2 = 0x0000
|
||||
// [20-23] tAccNs = uint32 (accuracy, nanoseconds part, 0)
|
||||
|
||||
const uint16_t payloadLen = 24;
|
||||
uint8_t frame[4 + payloadLen];
|
||||
|
||||
// Header
|
||||
frame[0] = 0x13; // class: MGA
|
||||
frame[1] = 0x40; // id: INI
|
||||
frame[2] = payloadLen & 0xFF;
|
||||
frame[3] = (payloadLen >> 8) & 0xFF;
|
||||
|
||||
// Payload
|
||||
uint8_t* p = &frame[4];
|
||||
memset(p, 0, payloadLen);
|
||||
|
||||
p[0] = 0x10; // type = TIME_UTC
|
||||
p[1] = 0x00; // version
|
||||
p[2] = 0x00; // ref = UTC
|
||||
p[3] = 0x80; // leapSecs = unknown (signed, 0x80 = flag for unknown)
|
||||
|
||||
memcpy(&p[4], &year, 2);
|
||||
p[6] = month;
|
||||
p[7] = day;
|
||||
p[8] = hour;
|
||||
p[9] = minute;
|
||||
p[10] = second;
|
||||
// p[11] reserved = 0
|
||||
|
||||
uint32_t ns = 0;
|
||||
memcpy(&p[12], &ns, 4);
|
||||
memcpy(&p[16], &accSec, 2);
|
||||
// p[18-19] reserved = 0
|
||||
uint32_t tAccNs = 0;
|
||||
memcpy(&p[20], &tAccNs, 4);
|
||||
|
||||
ubxSend(port, frame, sizeof(frame));
|
||||
|
||||
MESH_DEBUG_PRINTLN("GPS aid: sent TIME_UTC %04u-%02u-%02u %02u:%02u:%02u acc=%us",
|
||||
year, month, day, hour, minute, second, accSec);
|
||||
}
|
||||
|
||||
// Convenience: send all available aiding data after a GPS power-on.
|
||||
// Call this ~200ms after enabling PIN_GPS_EN to give the module time to boot.
|
||||
//
|
||||
// lat, lon: last known position (degrees). Skipped if both are 0.
|
||||
// utcEpoch: current UTC time from RTC. Skipped if < year 2024.
|
||||
// posAccCm: position accuracy in cm (default 300m for stale fixes)
|
||||
// timeAccSec: time accuracy in seconds (default 2s for RTC-derived time)
|
||||
static void sendAllAiding(Stream& port, double lat, double lon,
|
||||
uint32_t utcEpoch,
|
||||
uint32_t posAccCm = 30000,
|
||||
uint16_t timeAccSec = 2)
|
||||
{
|
||||
// Position aiding
|
||||
sendPositionAid(port, lat, lon, 0, posAccCm);
|
||||
|
||||
// Small delay between messages to avoid overrunning the receiver's
|
||||
// input buffer at 38400 baud (each message is ~30 bytes, well within
|
||||
// one UART buffer, but better safe)
|
||||
delay(10);
|
||||
|
||||
// Time aiding
|
||||
sendTimeAid(port, utcEpoch, timeAccSec);
|
||||
}
|
||||
|
||||
} // namespace GPSAiding
|
||||
Reference in New Issue
Block a user