getting time from GPS reworked

This commit is contained in:
cyberman54 2022-02-12 16:26:41 +01:00
parent 817f7793c4
commit a71a7e08a4
6 changed files with 128 additions and 156 deletions

View File

@ -5,17 +5,10 @@
#include <RtcDateTime.h> #include <RtcDateTime.h>
#include "timekeeper.h" #include "timekeeper.h"
#ifdef GPS_I2C // Needed for reading from I2C Bus
#include <Wire.h>
#endif
#ifndef GPS_BAUDRATE #ifndef GPS_BAUDRATE
#define GPS_BAUDRATE 115200 #define GPS_BAUDRATE 115200UL
#endif #endif
#define NMEA_FRAME_SIZE 82 // NEMA has a maxium of 82 bytes per record
#define NMEA_COMPENSATION_FACTOR 480 // empiric for Ublox Neo 6M
extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe
extern TaskHandle_t GpsTask; extern TaskHandle_t GpsTask;

View File

@ -21,13 +21,13 @@ extern Ticker timesyncer;
extern timesource_t timeSource; extern timesource_t timeSource;
extern TaskHandle_t ClockTask; extern TaskHandle_t ClockTask;
extern DRAM_ATTR bool TimePulseTick; // 1sec pps flag set by GPS or RTC extern DRAM_ATTR bool TimePulseTick; // 1sec pps flag set by GPS or RTC
extern DRAM_ATTR unsigned long lastPPS;
extern hw_timer_t *ppsIRQ; extern hw_timer_t *ppsIRQ;
extern portMUX_TYPE mux;
void IRAM_ATTR CLOCKIRQ(void); void IRAM_ATTR CLOCKIRQ(void);
void IRAM_ATTR GPSIRQ(void);
void clock_init(void); void clock_init(void);
void clock_loop(void *pvParameters); void clock_loop(void *pvParameters);
void timepulse_start(void);
void setTimeSyncIRQ(void); void setTimeSyncIRQ(void);
uint8_t timepulse_init(void); uint8_t timepulse_init(void);
bool timeIsValid(time_t const t); bool timeIsValid(time_t const t);
@ -38,5 +38,4 @@ time_t compileTime(void);
time_t mkgmtime(const struct tm *ptm); time_t mkgmtime(const struct tm *ptm);
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config, TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
int8_t rxPin, int8_t txPins); int8_t rxPin, int8_t txPins);
#endif // _timekeeper_H #endif // _timekeeper_H

View File

@ -317,9 +317,6 @@ void dp_drawPage(bool nextpage) {
#if (TIME_SYNC_INTERVAL) #if (TIME_SYNC_INTERVAL)
timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource]; timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource];
portENTER_CRITICAL(&mux);
TimePulseTick = false; // flip global variable pulse ticker
portEXIT_CRITICAL(&mux);
time(&now); time(&now);
localtime_r(&now, &timeinfo); localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);

View File

@ -6,38 +6,12 @@
// Local logging tag // Local logging tag
static const char TAG[] = __FILE__; static const char TAG[] = __FILE__;
// we use NMEA ZDA sentence field 1 for time synchronization
// ZDA gives time for preceding pps pulse
// downsight is that it does not have a constant offset
// thus precision is only +/- 1 second
TinyGPSPlus gps; TinyGPSPlus gps;
TinyGPSCustom gpstime(gps, "GPZDA", 1); // field 1 = UTC time (hhmmss.ss)
TinyGPSCustom gpsday(gps, "GPZDA", 2); // field 2 = day (01..31)
TinyGPSCustom gpsmonth(gps, "GPZDA", 3); // field 3 = month (01..12)
TinyGPSCustom gpsyear(gps, "GPZDA", 4); // field 4 = year (4-digit)
static const String ZDA_Request = "$EIGPQ,ZDA*39\r\n";
TaskHandle_t GpsTask; TaskHandle_t GpsTask;
HardwareSerial GPS_Serial(1); // use UART #1 HardwareSerial GPS_Serial(1); // use UART #1
static uint16_t nmea_txDelay_ms =
(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL) / portTICK_PERIOD_MS);
// helper functions to send UBX commands to ublox gps chip // helper functions to send UBX commands to ublox gps chip
/*
// Print the UBX packet for debugging
void printPacket(byte *packet, byte len) {
char temp[3];
for (byte i = 0; i < len; i++) {
sprintf(temp, "%.2X", packet[i]);
ESP_LOGD(TAG, "%s", temp);
}
}
*/
// Send the packet specified to the receiver.
void sendPacket(byte *packet, byte len) { void sendPacket(byte *packet, byte len) {
uint8_t CK_A = 0; uint8_t CK_A = 0;
@ -55,55 +29,85 @@ void sendPacket(byte *packet, byte len) {
GPS_Serial.write(CK_B); GPS_Serial.write(CK_B);
} }
// Send a packet to the receiver to restore default configuration.
void restoreDefaults() { void restoreDefaults() {
// CFG-CFG packet. // UBX CFG-CFG packet
byte packet[] = { byte packet[] = {
0xB5, // sync char 1 0xB5, // sync char 1
0x62, // sync char 2 0x62, // sync char 2
0x06, // class 0x06, // class
0x09, // id 0x09, // id
0x0D, // length 0x0D, // length
0x00, // length 0x00, // .
0b00011111, // clearmask 0b00011111, // clearmask
0b00000110, // clearmask 0b00000110, // .
0x00, // clearmask 0x00, // .
0x00, // clearmask 0x00, // .
0x00, // savemask
0x00, // savemask
0x00, // savemask
0x00, // savemask 0x00, // savemask
0x00, // .
0x00, // .
0x00, // .
0b00011111, // loadmask 0b00011111, // loadmask
0b00000110, // loadmask 0b00000110, // .
0x00, // loadmask 0x00, // .
0x00, // loadmask 0x00, // .
0b00010001 // devicemask 0b00010001 // devicemask
}; };
sendPacket(packet, sizeof(packet)); sendPacket(packet, sizeof(packet));
} }
// Send a set of packets to the receiver to disable NMEA messages. void setTimePulse() {
// UBX TIM-TP packet
byte packet[] = {
0xB5, // sync char 1
0x62, // sync char 2
0x06, // class
0x07, // id
0x14, // length
0x40, // time interval for time pulse [us]
0x42, // -> 1 sec = 1000000us
0x0F, // .
0x00, // .
0xE8, // length of time pulse [us]
0x03, // -> 1000us
0x00, // .
0x00, // .
0x01, // status -> positive edge
0x00, // timeRef -> UTC
0b00000001, // syncMode asynchronized
0x00, // reserved
0x00, // antenna cable delay [ns]
0x00, // .
0x00, // receiver rf group delay [ns]
0x00, // .
0x00, // user time function delay [ns]
0x00, // .
0x00, // .
0x00 // .
};
sendPacket(packet, sizeof(packet));
}
void disableNmea() { void disableNmea() {
// for tinygps++ we need only $GPGGA and $GPRMC // for tinygps++ we need only $GPGGA and $GPRMC
// for getting time we use $GPZDA // thus, we disable all other NMEA messages
// we disable all other NMEA messages
// Array of two bytes for CFG-MSG packets payload. // Array of two bytes for CFG-MSG packets payload.
byte messages[][2] = {{0xF0, 0x01}, {0xF0, 0x02}, {0xF0, 0x03}, {0xF0, 0x05}, byte messages[][2] = {{0xF0, 0x01}, {0xF0, 0x02}, {0xF0, 0x03}, {0xF0, 0x05},
{0xF0, 0x06}, {0xF0, 0x07}, {0xF0, 0x09}, {0xF0, 0x0A}, {0xF0, 0x06}, {0xF0, 0x07}, {0xF0, 0x08}, {0xF0, 0x09},
{0xF0, 0x0E}, {0xF1, 0x00}, {0xF1, 0x03}, {0xF1, 0x04}, {0xF0, 0x0A}, {0xF0, 0x0E}, {0xF1, 0x00}, {0xF1, 0x03},
{0xF1, 0x05}, {0xF1, 0x06}}; {0xF1, 0x04}, {0xF1, 0x05}, {0xF1, 0x06}};
// CFG-MSG packet buffer. // UBX CFG-MSG packet
byte packet[] = { byte packet[] = {
0xB5, // sync char 1 0xB5, // sync char 1
0x62, // sync char 2 0x62, // sync char 2
0x06, // class 0x06, // class
0x01, // id 0x01, // id
0x03, // length 0x03, // length
0x00, // length 0x00, // .
0x00, // payload (first byte from messages array element) 0x00, // payload (first byte from messages array element)
0x00, // payload (second byte from messages array element) 0x00, // payload (second byte from messages array element)
0x00 // payload (zero to disable message) 0x00 // payload (zero to disable message)
@ -124,25 +128,24 @@ void disableNmea() {
} }
} }
// Send a packet to the receiver to change baudrate to 115200.
void changeBaudrate(uint32_t baudRate) { void changeBaudrate(uint32_t baudRate) {
// CFG-PRT packet. // UBX CFG-PRT packet
byte packet[] = { byte packet[] = {
0xB5, // sync char 1 0xB5, // sync char 1
0x62, // sync char 2 0x62, // sync char 2
0x06, // class 0x06, // class
0x00, // id 0x00, // id
0x14, // length 0x14, // length
0x00, // length 0x00, // .
0x01, // portID (UART 1) 0x01, // portID (UART 1)
0x00, // reserved 0x00, // reserved
0x00, // txReady 0x00, // txReady
0x00, // . 0x00, // .
0b11010000, // UART mode: 8bit 0b11010000, // UART mode: 8N1
0b00001000, // UART mode: No Parity, 1 Stopbit 0b00001000, // .
0x00, // . 0x00, // .
0x00, // . 0x00, // .
(byte)baudRate, // baudrate (4 bytes) (byte)baudRate, // baudrate
(byte)(baudRate >> 8), // . (byte)(baudRate >> 8), // .
(byte)(baudRate >> 16), // . (byte)(baudRate >> 16), // .
(byte)(baudRate >> 24), // . (byte)(baudRate >> 24), // .
@ -151,30 +154,9 @@ void changeBaudrate(uint32_t baudRate) {
0b00000010, // output protocols: NMEA 0b00000010, // output protocols: NMEA
0x00000000, // . 0x00000000, // .
0x00, // reserved 0x00, // reserved
0x00, // reserved 0x00, // .
0x00, // reserved 0x00, // .
0x00 // reserved 0x00 // .
};
sendPacket(packet, sizeof(packet));
}
// Send a packet to the receiver to change frequency to 100 ms.
void changeFrequency() {
// CFG-RATE packet.
byte packet[] = {
0xB5, // sync char 1
0x62, // sync char 2
0x06, // class
0x08, // id
0x06, // length
0x00, // length
0x64, // Measurement rate 100ms
0x00, // Measurement rate
0x01, // Measurement cycles
0x00, // Measurement cycles
0x00, // Alignment to reference time: UTC time
0x00 // payload
}; };
sendPacket(packet, sizeof(packet)); sendPacket(packet, sizeof(packet));
@ -184,8 +166,11 @@ void changeFrequency() {
int gps_init(void) { int gps_init(void) {
ESP_LOGI(TAG, "Opening serial GPS"); ESP_LOGI(TAG, "Opening serial GPS");
GPS_Serial.begin(GPS_SERIAL); GPS_Serial.begin(GPS_SERIAL);
restoreDefaults(); restoreDefaults();
delay(100);
changeBaudrate(GPS_BAUDRATE); changeBaudrate(GPS_BAUDRATE);
delay(100); delay(100);
@ -193,8 +178,7 @@ int gps_init(void) {
GPS_Serial.updateBaudRate(GPS_BAUDRATE); GPS_Serial.updateBaudRate(GPS_BAUDRATE);
disableNmea(); disableNmea();
changeFrequency(); setTimePulse();
// enableNavTimeUTC();
return 1; return 1;
@ -224,40 +208,38 @@ bool gps_hasfix() {
// function to poll UTC time from GPS NMEA data; note: this is costly // function to poll UTC time from GPS NMEA data; note: this is costly
time_t get_gpstime(uint16_t *msec) { time_t get_gpstime(uint16_t *msec) {
// poll NMEA ZDA sentence *msec = 0;
GPS_Serial.print(ZDA_Request);
// wait for gps NMEA answer
// vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL));
// did we get a current date & time? // did we get a current date & time?
if (gpstime.isValid()) { if (gps.time.isValid() && gps.date.isValid() && gps.time.age() < 1000) {
uint32_t delay_ms = // convert tinygps time format to struct tm format
gpstime.age() + nmea_txDelay_ms + NMEA_COMPENSATION_FACTOR;
uint32_t zdatime = atof(gpstime.value());
// convert UTC time from gps NMEA ZDA sentence to tm format
struct tm gps_tm = {0}; struct tm gps_tm = {0};
gps_tm.tm_sec = zdatime % 100; // second (UTC) gps_tm.tm_sec = gps.time.second();
gps_tm.tm_min = (zdatime / 100) % 100; // minute (UTC) gps_tm.tm_min = gps.time.minute();
gps_tm.tm_hour = zdatime / 10000; // hour (UTC) gps_tm.tm_hour = gps.time.hour();
gps_tm.tm_mday = atoi(gpsday.value()); // day, 01 to 31 gps_tm.tm_mday = gps.date.day();
gps_tm.tm_mon = atoi(gpsmonth.value()) - 1; // month, 01 to 12 gps_tm.tm_mon = gps.date.month() - 1; // 1-12 -> 0-11
gps_tm.tm_year = atoi(gpsyear.value()) - 1900; // year, YYYY gps_tm.tm_year = gps.date.year() - 1900; // 2000+ -> years since 1900
// convert UTC tm to time_t epoch // convert UTC tm to time_t epoch
gps_tm.tm_isdst = 0; // UTC has no DST gps_tm.tm_isdst = 0; // UTC has no DST
time_t t = mkgmtime(&gps_tm); time_t t = mkgmtime(&gps_tm);
// add protocol delay with millisecond precision #ifdef GPS_INT
t += (time_t)(delay_ms / 1000); // if we have a recent GPS PPS pulse, sync on top of next second
*msec = delay_ms % 1000; // fractional seconds if (millis() - lastPPS < 1000)
*msec = (uint16_t)(millis() - lastPPS);
else {
ESP_LOGD(TAG, "no PPS from GPS");
return 0;
}
#endif
return t; return t;
} }
ESP_LOGD(TAG, "no valid GPS time"); ESP_LOGD(TAG, "no valid GPS time");
return 0; return 0;
} // get_gpstime() } // get_gpstime()
@ -271,20 +253,24 @@ void gps_loop(void *pvParameters) {
while (cfg.payloadmask & GPS_DATA) { while (cfg.payloadmask & GPS_DATA) {
// feed GPS decoder with serial NMEA data from GPS device // feed GPS decoder with serial NMEA data from GPS device
while (GPS_Serial.available()) while (GPS_Serial.available()) {
if (gps.encode(GPS_Serial.read())) if (gps.encode(GPS_Serial.read())) {
break; // leave encode loop after each NMEA complete sentence
// show NMEA data, very noisy, useful only for debugging GPS // show NMEA data, very noisy, for debugging GPS
// ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: // ESP_LOGV(
// %u", gps.passedChecksum(), gps.failedChecksum(), gps // TAG,
// .sentencesWithFix()); // "GPS NMEA data: chars %u / passed %u / failed: %u / with fix:
// %u", gps.charsProcessed(), gps.passedChecksum(),
// gps.failedChecksum(), gps.sentencesWithFix());
delay(5); // yield after each sentence to crack NMEA burst
}
} // read from serial buffer loop
delay(5); delay(5);
} // inner while loop }
delay(1000); delay(1000);
} // outer while loop } // infinite while loop
} // gps_loop() } // gps_loop()

View File

@ -62,7 +62,7 @@ triggers pps 1 sec impulse
ISRs fired by CPU or GPIO: ISRs fired by CPU or GPIO:
DisplayIRQ <- esp32 timer 0 DisplayIRQ <- esp32 timer 0
CLOCKIRQ <- esp32 timer 1 or GPIO (RTC_INT or GPS_INT) CLOCKIRQ <- esp32 timer 1 or GPIO (RTC_INT)
MatrixDisplayIRQ<- esp32 timer 3 MatrixDisplayIRQ<- esp32 timer 3
ButtonIRQ <- GPIO <- Button ButtonIRQ <- GPIO <- Button
PMUIRQ <- GPIO <- PMU chip PMUIRQ <- GPIO <- PMU chip
@ -492,8 +492,7 @@ void setup() {
#endif #endif
ESP_LOGI(TAG, "Starting Timekeeper..."); ESP_LOGI(TAG, "Starting Timekeeper...");
_ASSERT(timepulse_init()); // setup pps timepulse _ASSERT(timepulse_init()); // starts pps and cyclic time sync
timepulse_start(); // starts pps and cyclic time sync
strcat_P(features, " TIME"); strcat_P(features, " TIME");
#endif // timesync #endif // timesync

View File

@ -19,9 +19,8 @@ static const char TAG[] = __FILE__;
// G = GPS / R = RTC / L = LORA / * = no sync / ? = never synced // G = GPS / R = RTC / L = LORA / * = no sync / ? = never synced
const char timeSetSymbols[] = {'G', 'R', 'L', '*', '?'}; const char timeSetSymbols[] = {'G', 'R', 'L', '*', '?'};
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
DRAM_ATTR bool TimePulseTick = false; DRAM_ATTR bool TimePulseTick = false;
DRAM_ATTR unsigned long lastPPS = millis();
timesource_t timeSource = _unsynced; timesource_t timeSource = _unsynced;
TaskHandle_t ClockTask = NULL; TaskHandle_t ClockTask = NULL;
hw_timer_t *ppsIRQ = NULL; hw_timer_t *ppsIRQ = NULL;
@ -144,26 +143,23 @@ uint8_t timepulse_init() {
// sntp_init(); // sntp_init();
sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED); sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED);
// use time pulse from GPS as time base with fixed 1Hz frequency // if we have, use PPS time pulse from GPS for syncing time on top of second
#ifdef GPS_INT #ifdef GPS_INT
// setup external interupt pin for rising edge of GPS PPS
// setup external interupt pin for rising edge GPS INT
pinMode(GPS_INT, INPUT_PULLDOWN); pinMode(GPS_INT, INPUT_PULLDOWN);
// setup external rtc 1Hz clock as pulse per second clock attachInterrupt(digitalPinToInterrupt(GPS_INT), GPSIRQ, RISING);
ESP_LOGI(TAG, "Timepulse: external (GPS)"); #endif
return 1; // success
// use pulse from on board RTC chip as time base with fixed frequency // if we have, use pulse from on board RTC chip as time base for calendar time
#elif defined RTC_INT #if defined RTC_INT
// setup external interupt pin for falling edge RTC INT // setup external rtc 1Hz clock pulse
pinMode(RTC_INT, INPUT_PULLUP);
// setup external rtc 1Hz clock as pulse per second clock
if (I2C_MUTEX_LOCK()) { if (I2C_MUTEX_LOCK()) {
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
I2C_MUTEX_UNLOCK(); I2C_MUTEX_UNLOCK();
pinMode(RTC_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
ESP_LOGI(TAG, "Timepulse: external (RTC)"); ESP_LOGI(TAG, "Timepulse: external (RTC)");
return 1; // success return 1; // success
} else { } else {
@ -173,33 +169,39 @@ uint8_t timepulse_init() {
return 1; // success return 1; // success
#else #else
// use ESP32 hardware timer as time base with adjustable frequency // use ESP32 hardware timer as time base for calendar time
ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
timerAlarmEnable(ppsIRQ);
ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)"); ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)");
return 1; // success return 1; // success
#endif #endif
} // timepulse_init
void timepulse_start(void) { // start cyclic time sync
#ifdef GPS_INT // start external clock gps pps line timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ);
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
#elif defined RTC_INT // start external clock rtc
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
#else // start internal clock esp32 hardware timer
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
timerAlarmEnable(ppsIRQ);
#endif
// get time if we don't have one // get time if we don't have one
if (timeSource != _set) if (timeSource != _set)
setTimeSyncIRQ(); // init systime by RTC or GPS or LORA setTimeSyncIRQ(); // init systime by RTC or GPS or LORA
// start cyclic time sync
timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ); } // timepulse_init
// interrupt service routine triggered by GPS PPS
void IRAM_ATTR GPSIRQ(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// take timestamp
lastPPS = millis(); // last time of pps
// yield only if we should
if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
} }
// interrupt service routine triggered by either pps or esp32 hardware timer // interrupt service routine triggered by esp32 hardware timer
void IRAM_ATTR CLOCKIRQ(void) { void IRAM_ATTR CLOCKIRQ(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
@ -212,11 +214,7 @@ void IRAM_ATTR CLOCKIRQ(void) {
// flip time pulse ticker, if needed // flip time pulse ticker, if needed
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
#if (defined GPS_INT || defined RTC_INT)
portENTER_CRITICAL(&mux);
TimePulseTick = !TimePulseTick; // flip global variable pulse ticker TimePulseTick = !TimePulseTick; // flip global variable pulse ticker
portEXIT_CRITICAL(&mux);
#endif
#endif #endif
// yield only if we should // yield only if we should