Skip to content

Instantly share code, notes, and snippets.

@tve
Last active November 7, 2024 21:55
Show Gist options
  • Save tve/a316f6b4a51215b5062d0b603eac0f67 to your computer and use it in GitHub Desktop.
Save tve/a316f6b4a51215b5062d0b603eac0f67 to your computer and use it in GitHub Desktop.
Minimal UBX parsing
// Configure which GNSS constellation to use
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgL c1, c2, c3;
Cksum ck;
} gpsConfigGNSS = {
MakeHeader(UBX_CFG_VALSET, sizeof(gpsConfigGNSS)-8), 0, L_RAM|L_BBR, 0,
CFG_SIGNAL_GAL_ENA_OFF, CFG_SIGNAL_BDS_ENA_OFF, CFG_SIGNAL_GLO_ENA_OFF,
{0,0}
};
// configuration for CloudLocate w/out power management
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU2 c4, c4b; cfgU1 c5, c6;
cfgU1 c10, c11, c12;
cfgU1 c15, c16, c17, c18;
cfgL c55;
cfgU1 cN;
Cksum ck;
} gpsConfigCL = {
MakeHeader(UBX_CFG_VALSET, sizeof(gpsConfigCL)-8), 0, L_RAM|L_BBR, 0,
CFG_RATE_MEAS_1s, CFG_RATE_SOLN_1, CFG_MON_RXR_1s, CFG_INFMSG_UBX_UART_WRN,
CFG_NAV_SAT_1, CFG_NAV_ORB_1, CFG_NAV_UTC_10,
CFG_NAV_MON_PVT_0, CFG_RXM_MEAS20_DIS, CFG_RXM_MEAS50_ENA, CFG_RXM_MEASX_ENA,
CFG_ANA_ENA,
CFG_PM_OPERATEMODE_FULL,
{0,0}
};
// configuration for NAV_PVT without power management
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU2 c4, c4b; cfgU1 c5, c6;
cfgU1 c10, c11, c12;
cfgU1 c15, c16, c17, c18;
cfgU2 c50; cfgL c55;
cfgU1 cN;
Cksum ck;
} gpsConfigPVT = {
MakeHeader(UBX_CFG_VALSET, sizeof(gpsConfigPVT)-8), 0, L_RAM|L_BBR, 0,
CFG_RATE_MEAS_1s, CFG_RATE_SOLN_1, CFG_MON_RXR_1s, CFG_INFMSG_UBX_UART_WRN,
CFG_NAV_SAT_1, CFG_NAV_ORB_1, CFG_NAV_UTC_10,
CFG_NAV_MON_PVT_1s, CFG_RXM_MEAS20_DIS, CFG_RXM_MEAS50_DIS, CFG_RXM_MEASX_DIS,
CFG_NAVSPG_POS_ACC_200m, CFG_ANA_ENA,
CFG_PM_OPERATEMODE_FULL,
{0,0}
};
// configuration for NAV_VT with power management
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU2 c4, c4b; cfgU1 c5, c6;
cfgU1 c10, c11, c12;
cfgU1 c15, c16, c17, c18;
cfgU2 c50; cfgL c55;
cfgU1 c20; cfgU2 c21; cfgU4 c25, c26;
cfgL c30; cfgU1 c31, c32;
Cksum ck;
} gpsConfigPVTPM = {
MakeHeader(UBX_CFG_VALSET, sizeof(gpsConfigPVTPM)-8), 0, L_RAM|L_BBR, 0,
CFG_RATE_MEAS_1s, CFG_RATE_SOLN_1, CFG_MON_RXR_1s, CFG_INFMSG_UBX_UART_WRN,
CFG_NAV_SAT_5, CFG_NAV_ORB_1, CFG_NAV_UTC_10,
CFG_NAV_MON_PVT_1s, CFG_RXM_MEAS20_DIS, CFG_RXM_MEAS50_DIS, CFG_RXM_MEASX_DIS,
CFG_NAVSPG_POS_ACC_200m, CFG_ANA_ENA,
CFG_PM_MAXACQTIME_0, CFG_PM_ONTIME_0s, CFG_PM_ACQPERIOD_0, CFG_PM_POSUPDATEPERIOD_4500s,
CFG_PM_DONOTENTEROFF_DIS, CFG_PM_UPDATEEPH_ON, CFG_PM_OPERATEMODE_OO,
{0,0}
};
constexpr struct PACKED {
Header hdr;
uint8_t version; uint8_t layers; uint16_t resv;
cfgU4 baudrate;
cfgL outnmea, outubx;
Cksum ck;
} portConfig = {
MakeHeader(UBX_CFG_VALSET, sizeof(portConfig)-8), 0, L_RAM|L_BBR, 0,
CFG_UART1_BAUDRATE_38400,
CFG_UART1_OUT_UBX_ON, CFG_UART1_OUT_NMEA_OFF,
{0,0}
};
constexpr struct PACKED {
Header hdr;
uint16_t bbrMask;
uint8_t resetMode, resv;
Cksum ck;
} warmReset = {
MakeHeader(UBX_CFG_RST, sizeof(warmReset)-8),
// 0x001F, // reset all nav info, but not time info
0x8001, // reset ephemeris and autonomous orbit info only
0x02, 0, // controlled software reset of GNSS only
{0,0}
};
constexpr struct PACKED {
Header hdr; uint32_t version;
uint32_t duration, flags, wakeup;
Cksum ck;
} gpsSleep = {
MakeHeader(UBX_RXM_PMREQ, sizeof(gpsSleep)-8), 0,
0, // wait forever
0x6, // backup mode and force (see integration manual)
0x8, // wake-up on uart edge
{0,0}
};
const struct {
uint8_t *buf; uint32_t len; char name[8];
} configs[] = {
{ (uint8_t*)&portConfig, sizeof(portConfig), "uart" },
{ (uint8_t*)&gpsConfigGNSS, sizeof(gpsConfigGNSS), "gnss" },
{ (uint8_t*)&CONFIG, sizeof(CONFIG), "config" },
{ nullptr, 0, "done" },
};
// Program the GPS to output an 8Mhz clock
#include <jee.h>
#include <jee/hal.h>
#include <alloca.h>
using namespace jeeh;
#include "defs.h"
#include "console.h"
#include "pin-power.h"
#include "timer.h"
#include "ublox.h"
using namespace ublox;
Uart *theUart = nullptr;
Uart *uart2 = nullptr;
auto fcnt = new Timer2<TIM2.ADDR>(ena::TIM2);
Pin fcntClk, fcntCap;
// auto lseOut = new Timer2<TIM16.ADDR>(ena::TIM16);
// Pin lsePin;
bool gpsReady = false;
// ===== Power levels
// assuming synchronous drivers: i2c/spi/uart are inactive when lowestPower() is called
uint8_t jeeh::lowestPower (uint8_t power, uint16_t ms) {
(void)power;
(void)ms;
return sys::SLEEP; // required due to bug
}
void jeeh::resumePower () {
// logf("back!");
}
uint32_t efficientClock () { return slowClock(true); } // 4Mhz
#define SysCoreClkMhz (uint8_t)(SystemCoreClock/1000000)
// return a message to call o.f(msg) in ms time
// usage: sys::send(delay<T>(...))
template< typename T >
Message& delay(Message &msg, uint16_t ms, T* o, void (T::* f)(Message&)) {
if (msg.inUse()) { logf("already delaying"); fail(); }
msg.mDst = '@'; // ticker device ID
msg.mLen = ms;
msg.mObj = (uint8_t*) o;
msg.mFun = (void (*) (void*,Message&)) ((uint32_t*) &f)[0];
assert(((uint32_t*) &f)[1] == 0); // TODO vtable support
return msg;
}
uint32_t deltaTime(uint32_t t0) { return (rtc::getMillis()-t0) & ((1<<26)-1); }
uint32_t deltaTime(uint32_t t1, uint32_t t0) { return (t1-t0) & ((1<<26)-1); }
void printBuf(uint8_t *buf, uint16_t len) {
for (uint16_t i=0; i<len; i+=1) {
uint8_t ch = buf[i];
// if (ch >= ' ' && ch < 127)
// printf("%1c", ch);
// else
printf("\\%02x", ch);
}
printf("\n");
}
// ===== Test activity just relaying the characters from the GPS to the console
//
// Kind'a assumes the GPS starts from a reset in NMEA 9600 baud mode
struct RelayGPSActivity {
Message txMsg[3] = {};
Message rxMsg{'G', 'R', 0, nullptr};
Uart uart;
// uint8_t buf[300];
RelayGPSActivity(Uart &u) : uart{u} {}
void start() {
logf("Listening to GPS");
uart.baudRate(38400);
startRx();
}
// void sendUBX(Message &m, uint8_t *ubxMsg, uint16_t len) {
// assert(!m.inUse());
// uint8_t *txBuf = (uint8_t*)malloc(len);
// memcpy(txBuf, ubxMsg, len);
// ublox::CalcCksum(txBuf+2, len-4);
// m.mDst = 'G';
// m.mTag = 'W';
// m.mPtr = txBuf;
// m.mLen = len;
// printf("UBX TX %d: ", m.mLen);
// printBuf(m.mPtr, m.mLen);
// sys::send(m);
// }
void freePtr(Message &m) {
free(m.mPtr);
m.mPtr = nullptr;
}
void recv(Message &m) {
led = 1;
logf("Got %d", m.mLen);
uint8_t *buf = (uint8_t*)malloc(m.mLen);
assert(buf != nullptr);
memcpy(buf, m.mPtr, m.mLen);
uint8_t i = 3;
while (i >= 3) {
for (i=0; i<3; i++) if (!txMsg[i].inUse()) break;
if (i >= 3) sys::recv();
}
txMsg[i].mDst = 'U';
txMsg[i].mTag = 'W';
txMsg[i].mPtr = buf;
txMsg[i].mLen = m.mLen;
sys::send(txMsg[i].setCallback(this, &RelayGPSActivity::freePtr));
startRx();
led = 0;
}
void startRx() { sys::send(rxMsg.setCallback(this, &RelayGPSActivity::recv)); }
};
void printNavPVT(ublox::UbxNavPVT *rec) {
// printf("UBX_NAV_PVT ");
if (rec->valid & 1) printf("%04d-%02d-%02d", rec->year, rec->month, rec->day);
else printf("nodate");
if (rec->valid & 2) printf(" %02d:%02d:%02d", rec->hour, rec->min, rec->sec);
else printf(" notime");
if (rec->fixType < 2 || rec->fixType > 3) {
printf(" nofix(%d)", rec->fixType);
} else {
int32_t lat = rec->lat >= 0 ? rec->lat : -rec->lat;
uint8_t latH = rec->lat >= 0 ? 'N' : 'S';
int32_t lon = rec->lon >= 0 ? rec->lon : -rec->lon;
uint8_t lonH = rec->lon >= 0 ? 'E' : 'W';
static constexpr uint32_t shift = 10'000'000;
printf(" %ld.%ld%c %ld.%ld%c %ldm",
lon/shift, (lon%shift)/100, lonH, lat/shift, (lat%shift)/100, latH, rec->hMSL/1000);
printf(" Δ%ldm/%ldm", rec->hAcc/1000, rec->vAcc/1000);
}
uint8_t f1 = rec->flags;
static constexpr char psmStates[] = "N/A\0STA\0ACQ\0TRK\0POT\0INA";
const char *psm = psmStates + 4*((f1>>2)&7);
const char *dim = rec->fixType == 2 ? "-2D" : rec->fixType == 3 ? "-3D" : "";
printf(" %c%s%s %s sv:%d\n", f1&1?'V':'I', dim, f1&2?"-diff":"", psm, rec->numSV);
}
// ===== GPS Activity
// Configure which GNSS constellation to use
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgL c1, c2, c3;
Cksum ck;
} gpsConfigGNSS = {
MakeHeader(UBX_CFG_VALSET, sizeof(gpsConfigGNSS)-8), 0, L_RAM|L_BBR, 0,
CFG_SIGNAL_GAL_ENA_OFF, CFG_SIGNAL_BDS_ENA_OFF, CFG_SIGNAL_GLO_ENA_OFF,
{0,0}
};
// configuration for NAV_PVT without power management
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU2 c4, c4b; cfgU1 c5, c6;
cfgU1 c10, c11, c12;
cfgU1 c15, c16, c17, c18;
cfgU2 c50; cfgL c55;
cfgU1 cN;
Cksum ck;
} gpsConfigPVT = {
MakeHeader(UBX_CFG_VALSET, sizeof(gpsConfigPVT)-8), 0, L_RAM|L_BBR, 0,
CFG_RATE_MEAS_1s, CFG_RATE_SOLN_1, CFG_MON_RXR_1s, CFG_INFMSG_UBX_UART_WRN,
CFG_NAV_SAT_0, CFG_NAV_ORB_0, CFG_NAV_UTC_10,
CFG_NAV_MON_PVT_1s, CFG_RXM_MEAS20_DIS, CFG_RXM_MEAS50_DIS, CFG_RXM_MEASX_DIS,
CFG_NAVSPG_POS_ACC_200m, CFG_ANA_ENA,
CFG_PM_OPERATEMODE_FULL,
{0,0}
};
// configuration for the serial port to get UBX and no NMEA stanzas
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU4 baudrate;
cfgL outnmea, outubx;
Cksum ck;
} portConfig = {
MakeHeader(UBX_CFG_VALSET, sizeof(portConfig)-8), 0, L_RAM|L_BBR, 0,
CFG_UART1_BAUDRATE_38400,
CFG_UART1_OUT_UBX_ON, CFG_UART1_OUT_NMEA_OFF,
{0,0}
};
// configuration to enable 8kHz timepulse output when locked
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU1 c1, c2, c3, c4;
cfgU4 c10; cfgR8 c20, c21;
Cksum ck;
} timepulse8MhzLock = {
MakeHeader(UBX_CFG_VALSET, sizeof(timepulse8MhzLock)-8), 0, L_RAM|L_BBR, 0,
CFG_TP_ISFREQ, CFG_TP_RATIO, CFG_TP_TIMEGRID_GPS, CFG_TP_USELOCKED_ENA,
CFG_TP_8MHZ_LOCKED, CFG_TP_DUTY_1, CFG_TP_DUTY_50_LOCKED,
{0,0}
};
// configuration to enable 8kHz timepulse output (unconditionally))
constexpr struct PACKED {
Header hdr; uint8_t version; uint8_t layers; uint16_t resv;
cfgU1 c1, c2, c3, c4;
cfgU4 c10; cfgR8 c20;
Cksum ck;
} timepulse8Mhz = {
MakeHeader(UBX_CFG_VALSET, sizeof(timepulse8Mhz)-8), 0, L_RAM|L_BBR, 0,
CFG_TP_ISFREQ, CFG_TP_RATIO, CFG_TP_TIMEGRID_GPS, CFG_TP_USELOCKED_DIS,
CFG_TP_8MHZ, CFG_TP_DUTY_50,
{0,0}
};
// array of configurations to send to device
const struct {
uint8_t *buf; uint32_t len; char name[8];
} configs[] = {
{ (uint8_t*)&portConfig, sizeof(portConfig), "uart" },
{ (uint8_t*)&gpsConfigGNSS, sizeof(gpsConfigGNSS), "gnss" },
{ (uint8_t*)&gpsConfigPVT, sizeof(gpsConfigPVT), "pvt" },
{ (uint8_t*)&timepulse8Mhz, sizeof(timepulse8Mhz), "tp" },
{ nullptr, 0, "done" },
};
// =====
#define MAXSV 24
// GPS data collection structs
struct GPSFix {
// from NAV_PVT
int32_t lat, lon, alt_msl;
uint16_t hAcc, vAcc; // in meters
bool valid;
uint8_t numSV;
// from NAV_SAT
struct {
uint8_t svId;
uint8_t cno;
uint8_t qual;
bool ephAvail;
} sats[MAXSV];
};
// GPSActivity states
enum { doConfig, doInit, doRun };
struct GPSActivity {
Message txMsg{'G', 'W'};
Message txMsg2{'G', 'W'};
Message rxMsg{'G', 'R', 0, nullptr};
Message wakeMsg{};
# define TXFREE txMsg.setCallback(this, &GPSActivity::freeUBX)
uint32_t itow; // ms in week
uint8_t state = doConfig;
bool gotDateTime = false;
uint8_t configIX = 0;
uint32_t lastCnt = 0;
GPSActivity() {}
void start() {
logf("Configuring GPS");
state = doConfig;
startRx(rxMsg);
itow = 0;
sendConfig();
// logf("Done sending GPS config");
}
void switchBaudRate(bool fast) {
uart2->baudRate(fast ? 38400 : 9600);
cycles::msBusy(2);
}
void sendUBX(Message &m, uint8_t *ubxMsg, uint16_t len) {
assert(!m.inUse());
uint8_t *txBuf = (uint8_t*)malloc(len);
if (txBuf == nullptr) logf("OOPS1");
memcpy(txBuf, ubxMsg, len);
ublox::CalcCksum(txBuf+2, len-4);
m.mDst = 'G';
m.mTag = 'W';
m.mPtr = txBuf;
m.mLen = len;
#if 1
printf("UBX TX %d (0x%02x%02x)\n", m.mLen, m.mPtr[2], m.mPtr[3]);
#elif 1
printf("UBX TX %d: ", m.mLen);
printBuf(m.mPtr, m.mLen);
#endif
sys::send(m);
}
void freeUBX(Message &m) {
assert(m.mPtr != nullptr);
free(m.mPtr);
}
void nopCB(Message &) {}
// poll for GNSS systems in use, this will trigger sending the config
// void queryMonGNSS(Message &m) {
// static constexpr ublox::Poll pollMonGNSS = ublox::PollUbxMonGNSS();
// sendUBX(m, (uint8_t*)&pollMonGNSS, sizeof(pollMonGNSS));
// }
// send current config stanza
void sendConfig() {
if (configs[configIX].len == 0) return;
printf("Sending '%s' config\n", configs[configIX].name);
sendUBX(TXFREE, configs[configIX].buf, configs[configIX].len);
}
void updateTime(uint16_t off) { // offset of year value in frame
uint8_t *p = ublox::frame.payload;
auto dd = rtc::getDate();
uint16_t yr = p[off] | ((uint16_t)p[off+1]<<8);
uint8_t mo=p[off+2], dy=p[off+3];
uint8_t hh=p[off+4], mm=p[off+5], ss=p[off+6];
if (dd.yr != yr%100 || dd.mo != mo || dd.dy != dy || dd.hh != hh || dd.mm != mm || dd.ss != ss) {
printf("Adjusting time %02d-%02d-%02d %02d:%02d:%02d -> %02d-%02d-%02d %02d:%02d:%02d\n",
dd.yr, dd.mo, dd.dy, dd.hh, dd.mm, dd.ss, yr, mo, dy, hh, mm, ss);
sys::wait(2); // before messing with rtc...
rtc::set(DateTime(yr, mo, dy, hh, mm, ss));
}
gotDateTime = true;
}
void recv(Message &m) {
#if 0
uint32_t at = rtc::getMillis();
printf("\n{%ldms:%d s=%d", at%10000, m.mLen, ublox::state);
for (int i=0; i<m.mLen; i++) printf("%c%02x", m.mPtr[i]==0xB5?'*':' ', m.mPtr[i]);
printf("\n");
// if (at - lastRecv > 300) ublox::state = 0;
// if (at - lastRecv > 300) sendUBX(txMsg2.setCallback(this, &GPSActivity::freeUBX), nulls, 20);
// lastRecv = at;
#endif
// detect 9600 baud operation and cause switch
if (state == doConfig && configIX == 0 && m.mLen >= 10) {
bool ok = false;
for (int i=0; i<m.mLen; i++) {
uint8_t ch = m.mPtr[i];
if (ch != 0x80 && ch != 0xf8) ok = true;
}
if (!ok) {
printf("Looks like 9600 baud\n");
switchBaudRate(false);
sendConfig();
cycles::msBusy(40); // 30 chars sent -> 31.25ms
switchBaudRate(true);
}
}
for (int i=0; i<m.mLen; i++) {
if (!ublox::parse(m.mPtr[i])) continue;
// printf(">%04x\n", ublox::frame.classId);
switch (ublox::frame.classId) {
// primary navigation message with time & location & more
case ublox::UBX_NAV_PVT: {
// logf("UBX_NAV_PVT");
if (ublox::frame.payLen < sizeof(ublox::UbxNavPVT)) { logf("UBX_NAV_PVT too short"); break; }
ublox::UbxNavPVT *rec = (ublox::UbxNavPVT*)ublox::frame.payload;
bool datetimeValid = (rec->valid & 0x7) == 7;
if (datetimeValid && !gotDateTime) updateTime(4); // FIXME: slew...
// figure out whether it's time to sleep
printNavPVT(rec);
if (state == doInit && datetimeValid) {
updateTime(4);
state = doRun;
printf("Got datetime, init done\n");
}
uint32_t cnt = fcnt->read();
uint32_t capVal = fcnt->capVal - fcnt->capVal1;
uint32_t capCnt = fcnt->capCnt;
printf("Cnt: %lu delta: %ld | cap: #%ld at %lu\n",
cnt, cnt-lastCnt, capCnt, capVal);
uint32_t freq = (uint64_t)capCnt * 8 * 8000000000 / capVal;
int32_t err = freq-32768000;
uint32_t absErr = err >= 0 ? err : -err;
uint32_t ppb = (uint64_t)absErr * 1000000000 / 32768000;
printf("F=%lu.%03luHz err=%ld.%03luHz=%luppb\n",
freq/1000, freq%1000, err/1000, absErr%1000, ppb);
lastCnt = cnt;
break; }
// UTC time info
case ublox::UBX_NAV_TIMEUTC: {
if (ublox::frame.payLen < 20) { logf("UBX_NAV_TIMEUTC too short"); break; }
uint8_t *p = ublox::frame.payload;
bool datetimeValid = (p[19] & 0x7) == 7; // may be off by leap seconds
if (!gotDateTime) {
uint16_t yy = p[12] | ((uint16_t)p[13]<<8);
if (p[19] & 1) printf("%04d-%02d-%02d", yy, p[14], p[15]);
else printf("nodate");
if (p[19] & 2) printf(" %02d:%02d:%02d", p[16], p[17], p[18]);
else printf(" notime");
if (datetimeValid) printf(" valid\n"); else printf(" not valid (%x)\n", p[19]&0x7);
}
if (!datetimeValid) break;
if (state == doInit) {
updateTime(12);
state = doRun;
printf("Got datetime, init done\n");
} else if (!gotDateTime) updateTime(12); // FIXME: should continue sync'ing
break; }
case ublox::NMEA: {
ublox::frame.payload[ublox::frame.payLen] = 0;
printf("NMEA %d: %s\n", ublox::frame.payLen, ublox::frame.payload);
#if 1
if (!txMsg2.inUse())
sendUBX(txMsg2.setCallback(this, &GPSActivity::freeUBX), (uint8_t*)&portConfig, sizeof(portConfig));
#else
if (!txMsg.inUse())
sendUBX(TXFREE, (uint8_t*)&portConfig, sizeof(portConfig));
#endif
break; }
default:
switch (ublox::frame.classId & 0xf) {
// informational messages (debug/info/warn/err text)
case 0x04: { // UBX-INF
ublox::frame.payload[ublox::frame.payLen] = 0; // hack
printf("%s %s\n", ublox::UbxInfo[ublox::frame.classId>>8], ublox::frame.payload);
break; }
// ack-nack to our config messages, used to drive state machine
case 0x05: { // UBX-ACK
uint8_t *p = ublox::frame.payload;
bool ack = !!(ublox::frame.classId>>4);
const char *which = ack ? "ACK" : "NAK ****";
printf("UBX_ACK_%s %02x:%02x\n", which, p[0], p[1]);
uint16_t classId = p[0] | ((uint16_t)p[1]<<8);
// FIXME: handle NACK, perhaps by resetting GPS?
// if this is a response to a CFG_VALSET command then check for more to send
if (classId == UBX_CFG_VALSET && state == doConfig) {
if (ack) {
configIX++;
if (configs[configIX].len > 0) {
sendConfig();
} else {
#if WARM_RESET
sendUBX(TXFREE, (uint8_t*)&warmReset, sizeof(warmReset));
printf("Config done, sending reset\n");
#else
printf("Config done\n");
#endif
state = doInit;
}
} else {
sendConfig(); // rexmit
}
}
break; }
default:
printf("Got Ublox %02x:%02x len:%d\n",
ublox::frame.classId & 0xff, ublox::frame.classId >> 8, ublox::frame.payLen);
}
}
}
startRx(m);
}
void startRx(Message &m) {
assert(!m.inUse());
sys::send(m.setCallback(this, &GPSActivity::recv));
}
};
// ===== main
int main () {
led.mode("P"); led = 0;
fastClock(true);
cycles::init();
sys::wait(0); // init Ticker
rtc::init(true); // got 32 kHz xtal
if (rtc::getSecs() < 367*86400) rtc::set(DateTime());
// handle debug signals
#ifdef LED2
led2.mode("P"); led2 = 1;
#endif
cycles::msBusy(1000);
// the UART config comes from platformio.ini and is defined in defs.h
theUart = new Uart{'U'};
theUart->init(UART_PINS, 115'200, { UART_NAME.ADDR, ena::UART_NAME,
UART_FREQ, Irq::UART_NAME, UART_CONF });
stdout_device = 'U';
printf("\n\n***** %s: JeeGPSFreqCtr @%ld MHz", SVDNAME, SystemCoreClock / 1'000'000);
auto dd = rtc::getDate();
printf(" -- 20%02d-%02d-%02d %02d:%02d:%02d\n", dd.yr, dd.mo, dd.dy, dd.hh, dd.mm, dd.ss);
cycles::msBusy(500);
#ifdef LED2
led2 = 0;
#else
led = 1;
#endif
// fcnt = new Timer2<TIM2.ADDR>((uint16_t)ena::TIM2, 1);
fcntClk.init(TIM2_CLK);
fcntCap.init("A0:1");
fcnt->init(3); // mode 3 -> capture
fcnt->start();
uart2 = new Uart{'G'};
uart2->init(UARTGPS_PINS, 38400, { UARTGPS_NAME.ADDR, ena::UARTGPS_NAME,
UARTGPS_FREQ, Irq::UARTGPS_NAME, UARTGPS_CONF });
#if 1
GPSActivity gpsAct{};
gpsAct.start();
#else
RelayGPSActivity gpsAct{*uart2};
gpsAct.start();
#endif
while (true) sys::recv();
}
#include <stdint.h>
namespace ublox {
constexpr int MAX_PAYLOAD = 700;
// Message class & type as uint16_t, class in low byte due to little endianness
enum {
UBX_ACK_ACK = 0x0105,
UBX_ACK_NAK = 0x0205,
UBX_CFG_CFG = 0x0906,
UBX_CFG_RST = 0x0406,
UBX_CFG_VALSET = 0x8a06,
UBX_NAV_PVT = 0x0701,
UBX_NAV_STATUS = 0x0301,
UBX_NAV_ORB = 0x3401,
UBX_NAV_SAT = 0x3501,
UBX_NAV_TIMEUTC = 0x2101,
UBX_MON_GNSS = 0x280a,
UBX_MON_RXR = 0x210a,
UBX_RXM_MEAS20 = 0x8402,
UBX_RXM_MEAS50 = 0x8602,
UBX_RXM_MEASX = 0x1402,
UBX_RXM_PMREQ = 0x4102,
NMEA = 0xffff,
};
// ===== RX
uint8_t paybuf[MAX_PAYLOAD];
uint16_t paylen;
uint8_t rxCkA;
uint8_t cksumA, cksumB;
struct Frame {
uint16_t classId;
uint16_t payLen;
uint8_t *payload;
} frame;
struct UbxNavPVT {
uint32_t itow;
uint16_t year;
uint8_t month, day, hour, min, sec;
uint8_t valid;
uint32_t tAcc; // time accuracy
int32_t nano;
uint8_t fixType, flags, flags2, numSV;
int32_t lon, lat, height, hMSL;
uint32_t hAcc, vAcc; // horiz/vert accuracy
int32_t velN, velE, velD, gSpeed; // velocity north, east, down
int32_t headMot;
uint32_t sAcc, headAcc; // accuracies
uint16_t pDOP;
uint16_t flags3;
};
struct UbxNavOrbSV {
uint8_t gnssId;
uint8_t svId;
uint8_t svFlag;
uint8_t eph;
uint8_t alm;
uint8_t otherOrb;
};
struct UbxNavOrb {
uint32_t itow;
uint8_t version;
uint8_t numSV;
uint16_t resv;
UbxNavOrbSV sv[1];
};
struct UbxNavSatSV {
uint8_t gnssId;
uint8_t svId;
uint8_t cno;
int8_t elev;
int16_t azim;
int16_t prRes;
// uint32_t flags;
uint8_t quality:3;
bool used:1;
uint8_t health:2; // unkn,healthy,unhealthy
bool diffCorr:1;
bool smoothed:1;
uint8_t orbitSrc:3; // none,eph,alm,ANoffline,ANauto,other..
bool ephAvail:1;
bool almAvail:1;
bool anoAvail:1; // AssistNow-offline
bool aopAvail:1; // AssistNow-autonomous
bool sbasUsed:1, rtcmUsed:1, slasUsed:1, spartnUsed:1;
bool prUsed:1, crUsed:1, doUsed:1, clasUsed:1;
};
struct UbxNavSat {
uint32_t itow;
uint8_t version;
uint8_t numSV;
uint16_t resv;
UbxNavSatSV sv[1];
};
struct UbxMeasxHdr {
uint32_t version; // really U1 and 3xU1 resv; v==1 for now
uint32_t gpsTow, gloTow, bdsTow, resv1, qzssTow;
uint16_t gpsTowAcc, gloTowAcc, bdsTowAcc, resv2, qzssTowAcc;
uint8_t numSV, flags, resv3[8];
};
static_assert(sizeof (UbxMeasxHdr) == 44);
struct UbxMeasxSat {
uint8_t gnssId, svId, cNo, mPathIndic;
int32_t dopplerMS, dopplerHz;
uint16_t wholeChips, fracChips;
uint32_t codePhase;
uint8_t intCodePhase, pseuRangeRMSErr, resv[2];
};
static_assert(sizeof (UbxMeasxSat) == 24);
// parsing states (the name indicates what we expect)
enum { sync1, sync2, mclass, mid, len1, len2, pay, skip, ckA, ckB, nmea_lf, nmea_dollar, nmea_cr };
uint8_t state = sync1;
void initCksum() { cksumA = 0; cksumB = 0; }
void addCksum(uint8_t ch) { cksumA += ch; cksumB += cksumA; }
// returns true if a full frame has been received
bool parse(uint8_t ch) {
switch (state) {
case sync1:
if (ch == 0xb5) { state = sync2; }
else if (ch == '\r') { state = nmea_lf; }
return false;
case sync2:
state = ch == 0x62 ? mclass : sync1;
return false;
case mclass:
frame.classId = ch;
initCksum();
addCksum(ch);
state = mid;
return false;
case mid:
frame.classId |= ch << 8;
addCksum(ch);
state = len1;
return false;
case len1:
frame.payLen = ch;
addCksum(ch);
state = len2;
return false;
case len2:
frame.payLen += (uint16_t)ch << 8;
addCksum(ch);
paylen = 0;
state = frame.payLen == 0 ? ckA
: frame.payLen < MAX_PAYLOAD ? pay
: skip;
if (state == skip) {
logf("Frame %02x:%02x len=%d (skip)", frame.classId&0xf, frame.classId>>8, frame.payLen);
if (frame.payLen > MAX_PAYLOAD) state = sync1; // assume corruption/overrun
} // else printf("[%02x:%02x %d]", frame.classId&0xf, frame.classId>>8, frame.payLen);
return false;
case pay:
assert(paylen < MAX_PAYLOAD);
paybuf[paylen++] = ch;
addCksum(ch);
if (paylen == frame.payLen) state = ckA;
return false;
case skip:
paylen++;
if (paylen == frame.payLen+2) state = sync1; // skip checksum too
return false;
case ckA:
rxCkA = ch;
state = ckB;
return false;
case ckB:
state = sync1;
if (rxCkA == cksumA && ch == cksumB) {
frame.payload = paybuf;
return true;
}
logf("Bad checksum %02x:%02x (calc %02x%02x got %02x%02x) len=%d",
frame.classId&0xff, frame.classId>>8, cksumA, cksumB, rxCkA, ch, paylen);
// for (int i=0; i<paylen && i<100; i++) printf(" %02x", paybuf[i]);
// printf("\n");
return false;
case nmea_lf:
state = ch == '\n' ? nmea_dollar : sync1;
return false;
case nmea_dollar:
if (ch != '$') {
state = sync1;
} else {
state = nmea_cr;
paylen = 1;
paybuf[0] = ch;
}
return false;
case nmea_cr:
if (ch == '\r') {
state = nmea_lf;
frame.classId = NMEA;
frame.payLen = paylen;
frame.payload = paybuf;
return true;
} else if (paylen < MAX_PAYLOAD) {
paybuf[paylen++] = ch;
}
return false;
default:
fail();
}
}
// ===== Configuration messages
#define PACKED __attribute__((packed))
struct PACKED cfgL { uint32_t key; bool val; };
struct PACKED cfgU1 { uint32_t key; uint8_t val; };
struct PACKED cfgU2 { uint32_t key; uint16_t val; };
struct PACKED cfgU4 { uint32_t key; uint32_t val; };
struct PACKED cfgI1 { uint32_t key; int8_t val; };
struct PACKED cfgI2 { uint32_t key; int16_t val; };
struct PACKED cfgI4 { uint32_t key; int32_t val; };
constexpr cfgL CFG_UART1_OUT_UBX_ON{0x10740001, 1}; // enable UBX output
constexpr cfgL CFG_UART1_OUT_NMEA_OFF{0x10740002, 0}; // disable NMEA output
constexpr cfgU4 CFG_UART1_BAUDRATE_38400{0x40520001, 38400};
constexpr cfgL CFG_SIGNAL_GAL_ENA_OFF{0x10310021, 0}; // disable Galileo
constexpr cfgL CFG_SIGNAL_GAL_ENA_ON{0x10310021, 1}; // disable Galileo
constexpr cfgL CFG_SIGNAL_BDS_ENA_OFF{0x10310022, 0}; // disable BeiDou
constexpr cfgL CFG_SIGNAL_GLO_ENA_OFF{0x10310025, 0}; // disable Glonass
constexpr cfgL CFG_SIGNAL_GLO_ENA_ON{0x10310025, 1}; // disable Glonass
constexpr cfgU1 CFG_PM_OPERATEMODE_FULL{0x20d00001, 0}; // full power mode
constexpr cfgU1 CFG_PM_OPERATEMODE_OO{0x20d00001, 1}; // on/off mode
constexpr cfgU1 CFG_PM_OPERATEMODE_CT{0x20d00001, 2}; // cyclic tracking mode
constexpr cfgU4 CFG_PM_ACQPERIOD_0{0x40d00003, 0}; // acquisition retry, 0=never retry
constexpr cfgU4 CFG_PM_ACQPERIOD_40s{0x40d00003, 40}; // acquisition retry in s
constexpr cfgU4 CFG_PM_ACQPERIOD_120s{0x40d00003, 120}; // acquisition retry in s
constexpr cfgU4 CFG_PM_ACQPERIOD_1805s{0x40d00003, 1805}; // acquisition retry in s
constexpr cfgU4 CFG_PM_ACQPERIOD_4500s{0x40d00003, 4500}; // acquisition retry in s
constexpr cfgU2 CFG_PM_ONTIME_0s{0x30d00005, 0}; // tracking time in s
constexpr cfgU2 CFG_PM_ONTIME_2s{0x30d00005, 2}; // tracking time in s
constexpr cfgU2 CFG_PM_ONTIME_10s{0x30d00005, 10}; // tracking time in s
constexpr cfgU1 CFG_PM_MAXACQTIME_0{0x20d00007, 0}; // max time in acquisition, 0=auto
constexpr cfgU1 CFG_PM_MAXACQTIME_30s{0x20d00007, 30}; // max time in acquisition in s
constexpr cfgU1 CFG_PM_MAXACQTIME_60s{0x20d00007, 60}; // max time in acquisition in s
constexpr cfgU1 CFG_PM_MAXACQTIME_120s{0x20d00007, 120}; // max time in acquisition in s
constexpr cfgU1 CFG_PM_UPDATEEPH_ON{0x10d0000a, 1}; // update ephemeris
constexpr cfgU1 CFG_PM_UPDATEEPH_OFF{0x10d0000a, 0}; // update ephemeris
constexpr cfgU4 CFG_PM_POSUPDATEPERIOD_20s{0x40d00002, 20}; // update period in OO mode
constexpr cfgU4 CFG_PM_POSUPDATEPERIOD_60s{0x40d00002, 60}; // update period in OO mode
constexpr cfgU4 CFG_PM_POSUPDATEPERIOD_300s{0x40d00002, 300}; // update period in OO mode
constexpr cfgU4 CFG_PM_POSUPDATEPERIOD_1805s{0x40d00002, 1805}; // update period in OO mode
constexpr cfgU4 CFG_PM_POSUPDATEPERIOD_3000s{0x40d00002, 3000}; // update period in OO mode
constexpr cfgU4 CFG_PM_POSUPDATEPERIOD_4500s{0x40d00002, 4500}; // update period in OO mode
constexpr cfgL CFG_PM_DONOTENTEROFF_ENA{0x10d00008, 1}; // do not turn off until fix
constexpr cfgL CFG_PM_DONOTENTEROFF_DIS{0x10d00008, 0}; // turn off even if no fix
constexpr cfgU2 CFG_RATE_MEAS_1s{0x30210001, 1000}; // time between measurements in ms
constexpr cfgU2 CFG_RATE_MEAS_4s{0x30210001, 4000}; // time between measurements in ms
constexpr cfgU2 CFG_RATE_MEAS_12s{0x30210001, 12000}; // time between measurements in ms
constexpr cfgU2 CFG_RATE_SOLN_1{0x30210002, 1}; // measurements per solution
constexpr cfgU2 CFG_RATE_SOLN_10{0x30210002, 10}; // measurements per solution
constexpr cfgU2 CFG_RATE_SOLN_127{0x30210002, 127}; // measurements per solution
constexpr cfgU1 CFG_NAV_MON_PVT_1s{0x20910007, 1}; // per second output rate of UBX-NAV-PVT (position, velocity, time)
constexpr cfgU1 CFG_NAV_MON_PVT_0{0x20910007, 0}; // per second output rate of UBX-NAV-PVT (position, velocity, time)
constexpr cfgU1 CFG_MON_RXR_1s{0x20910188, 1}; // receiver state output
constexpr cfgU1 CFG_NAV_ORB_1{0x20910011, 1}; // satellites known
constexpr cfgU1 CFG_NAV_ORB_10{0x20910011, 10}; // satellites known
constexpr cfgU1 CFG_NAV_SAT_1{0x20910016, 1}; // satellites theoretically visible
constexpr cfgU1 CFG_NAV_SAT_2{0x20910016, 2}; // satellites theoretically visible
constexpr cfgU1 CFG_NAV_SAT_4{0x20910016, 4}; // satellites theoretically visible
constexpr cfgU1 CFG_NAV_SAT_5{0x20910016, 5}; // satellites theoretically visible
constexpr cfgU1 CFG_NAV_UTC_10{0x2091005C, 10}; // UTC time
constexpr cfgU1 CFG_RXM_MEAS20_ENA{0x20910644, 1}; // cloud locate MEAS20 every second
constexpr cfgU1 CFG_RXM_MEAS20_DIS{0x20910644, 0}; // cloud locate disabled
constexpr cfgU1 CFG_RXM_MEAS50_ENA{0x20910649, 1}; // cloud locate MEAS50 every second
constexpr cfgU1 CFG_RXM_MEAS50_DIS{0x20910649, 0}; // cloud locate disabled
constexpr cfgU1 CFG_RXM_MEASX_ENA{0x20910205, 1}; // cloud locate MEASX enabled
constexpr cfgU1 CFG_RXM_MEASX_DIS{0x20910205, 0}; // cloud locate MEASX disabled
constexpr cfgU1 CFG_INFMSG_UBX_UART_DBG{0x20920002, 0x17}; // enable all but test messages
constexpr cfgU1 CFG_INFMSG_UBX_UART_WRN{0x20920002, 0x03}; // only warn & err messages
constexpr cfgU2 CFG_NAVSPG_POS_ACC_200m{0x301100b3, 200}; // valid fix position accuracy threshold
constexpr cfgL CFG_ANA_ENA{0x10230001, 1}; // AssistNow Autonomous
constexpr cfgL CFG_ANA_DIS{0x10230001, 0}; // AssistNow Autonomous
enum { L_RAM=1, L_BBR=2, L_FLASH=4 }; // config storage layers
constexpr char UbxInfo[5][5] = { "ERR", "WARN", "NOTE", "TEST", "DEBG"};
// ===== TX
struct Header {
uint8_t s1, s2; // 0xb5 0x62
uint16_t classId;
uint16_t payLen; // little endian
};
struct Cksum { uint8_t ckA, ckB; };
struct Poll { Header h; Cksum c; }; // message with no payload
constexpr Header MakeHeader(uint16_t clsId, uint16_t len=0) {
return Header{0xb5, 0x62, clsId, len};
}
constexpr Cksum MakeCksum(Header hdr) {
uint8_t ckA = hdr.classId & 0xff, ckB = ckA;
ckA += hdr.classId >> 8; ckB += ckA;
ckA += hdr.payLen & 0xff; ckB += ckA;
ckA += hdr.payLen >> 8; ckB += ckA;
return Cksum{ckA, ckB};
}
void CalcCksum(uint8_t *buf, uint16_t len) {
uint8_t ckA = 0, ckB = 0;
for (uint16_t i=0; i<len; i++) {
ckA += buf[i];
ckB += ckA;
}
buf[len+0] = ckA;
buf[len+1] = ckB;
}
constexpr Poll PollUbxMonGNSS() {
auto hdr = MakeHeader(UBX_MON_GNSS);
return Poll{hdr, MakeCksum(hdr)};
}
constexpr Poll PollUbxNavOrb() {
auto hdr = MakeHeader(UBX_NAV_ORB);
return Poll{hdr, MakeCksum(hdr)};
}
constexpr Poll PollUbxNavUTC() {
auto hdr = MakeHeader(UBX_NAV_TIMEUTC);
return Poll{hdr, MakeCksum(hdr)};
}
// constexpr auto CfgNavMonPvt(uint8_t rate) {
// (void) rate;
// const uint16_t paylen = 4 + sizeof(CFG_NAV_MON_PVT_1s) + sizeof(CFG_RATE_MEAS_4s);
// struct PACKED {
// Header hdr;
// uint8_t version; uint8_t layers; uint16_t resv;
// cfgU1 c1; cfgU2 c2;
// Cksum ck;
// } msg{MakeHeader(UBX_CFG_VALSET, paylen), 0, L_RAM, 0, CFG_NAV_MON_PVT, CFG_RATE_MEAS, {0,0}};
// return msg;
// }
// constexpr auto CfgUartOutProt() {
// const uint16_t paylen = 4 + sizeof(CFG_UART1_OUT_UBX_ON) + sizeof(CFG_UART1_OUT_NMEA_OFF);
// struct PACKED {
// Header hdr;
// uint8_t version; uint8_t layers; uint16_t resv;
// cfgL c1, c2;
// Cksum ck;
// } msg{MakeHeader(UBX_CFG_VALSET, paylen), 0, L_RAM, 0, CFG_UART1_OUT_UBX_ON, CFG_UART1_OUT_NMEA_OFF, {0,0}};
// return msg;
// }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment