diff --git a/include/gpsread.h b/include/gpsread.h index 1025065d..95cd1afa 100644 --- a/include/gpsread.h +++ b/include/gpsread.h @@ -5,17 +5,10 @@ #include #include "timekeeper.h" -#ifdef GPS_I2C // Needed for reading from I2C Bus -#include -#endif - #ifndef GPS_BAUDRATE -#define GPS_BAUDRATE 115200 +#define GPS_BAUDRATE 115200UL #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 TaskHandle_t GpsTask; diff --git a/include/timekeeper.h b/include/timekeeper.h index 88084916..bb97a043 100644 --- a/include/timekeeper.h +++ b/include/timekeeper.h @@ -21,13 +21,13 @@ extern Ticker timesyncer; extern timesource_t timeSource; extern TaskHandle_t ClockTask; 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 portMUX_TYPE mux; void IRAM_ATTR CLOCKIRQ(void); +void IRAM_ATTR GPSIRQ(void); void clock_init(void); void clock_loop(void *pvParameters); -void timepulse_start(void); void setTimeSyncIRQ(void); uint8_t timepulse_init(void); bool timeIsValid(time_t const t); @@ -38,5 +38,4 @@ time_t compileTime(void); time_t mkgmtime(const struct tm *ptm); TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPins); - #endif // _timekeeper_H \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp index e41dea81..7fab780e 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -317,9 +317,6 @@ void dp_drawPage(bool nextpage) { #if (TIME_SYNC_INTERVAL) timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource]; - portENTER_CRITICAL(&mux); - TimePulseTick = false; // flip global variable pulse ticker - portEXIT_CRITICAL(&mux); time(&now); localtime_r(&now, &timeinfo); strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 6aef95b0..dac8e4d6 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -6,38 +6,12 @@ // Local logging tag 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; -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; - 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 -/* -// 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) { uint8_t CK_A = 0; @@ -55,55 +29,85 @@ void sendPacket(byte *packet, byte len) { GPS_Serial.write(CK_B); } -// Send a packet to the receiver to restore default configuration. void restoreDefaults() { - // CFG-CFG packet. + // UBX CFG-CFG packet byte packet[] = { 0xB5, // sync char 1 0x62, // sync char 2 0x06, // class 0x09, // id 0x0D, // length - 0x00, // length + 0x00, // . 0b00011111, // clearmask - 0b00000110, // clearmask - 0x00, // clearmask - 0x00, // clearmask - 0x00, // savemask - 0x00, // savemask - 0x00, // savemask + 0b00000110, // . + 0x00, // . + 0x00, // . 0x00, // savemask + 0x00, // . + 0x00, // . + 0x00, // . 0b00011111, // loadmask - 0b00000110, // loadmask - 0x00, // loadmask - 0x00, // loadmask + 0b00000110, // . + 0x00, // . + 0x00, // . 0b00010001 // devicemask }; 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() { // for tinygps++ we need only $GPGGA and $GPRMC - // for getting time we use $GPZDA - // we disable all other NMEA messages + // thus, we disable all other NMEA messages // Array of two bytes for CFG-MSG packets payload. byte messages[][2] = {{0xF0, 0x01}, {0xF0, 0x02}, {0xF0, 0x03}, {0xF0, 0x05}, - {0xF0, 0x06}, {0xF0, 0x07}, {0xF0, 0x09}, {0xF0, 0x0A}, - {0xF0, 0x0E}, {0xF1, 0x00}, {0xF1, 0x03}, {0xF1, 0x04}, - {0xF1, 0x05}, {0xF1, 0x06}}; + {0xF0, 0x06}, {0xF0, 0x07}, {0xF0, 0x08}, {0xF0, 0x09}, + {0xF0, 0x0A}, {0xF0, 0x0E}, {0xF1, 0x00}, {0xF1, 0x03}, + {0xF1, 0x04}, {0xF1, 0x05}, {0xF1, 0x06}}; - // CFG-MSG packet buffer. + // UBX CFG-MSG packet byte packet[] = { 0xB5, // sync char 1 0x62, // sync char 2 0x06, // class 0x01, // id 0x03, // length - 0x00, // length + 0x00, // . 0x00, // payload (first byte from messages array element) 0x00, // payload (second byte from messages array element) 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) { - // CFG-PRT packet. + // UBX CFG-PRT packet byte packet[] = { 0xB5, // sync char 1 0x62, // sync char 2 0x06, // class 0x00, // id 0x14, // length - 0x00, // length + 0x00, // . 0x01, // portID (UART 1) 0x00, // reserved 0x00, // txReady 0x00, // . - 0b11010000, // UART mode: 8bit - 0b00001000, // UART mode: No Parity, 1 Stopbit + 0b11010000, // UART mode: 8N1 + 0b00001000, // . 0x00, // . 0x00, // . - (byte)baudRate, // baudrate (4 bytes) + (byte)baudRate, // baudrate (byte)(baudRate >> 8), // . (byte)(baudRate >> 16), // . (byte)(baudRate >> 24), // . @@ -151,30 +154,9 @@ void changeBaudrate(uint32_t baudRate) { 0b00000010, // output protocols: NMEA 0x00000000, // . 0x00, // reserved - 0x00, // reserved - 0x00, // reserved - 0x00 // reserved - }; - - 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 + 0x00, // . + 0x00, // . + 0x00 // . }; sendPacket(packet, sizeof(packet)); @@ -184,8 +166,11 @@ void changeFrequency() { int gps_init(void) { ESP_LOGI(TAG, "Opening serial GPS"); + GPS_Serial.begin(GPS_SERIAL); + restoreDefaults(); + delay(100); changeBaudrate(GPS_BAUDRATE); delay(100); @@ -193,8 +178,7 @@ int gps_init(void) { GPS_Serial.updateBaudRate(GPS_BAUDRATE); disableNmea(); - changeFrequency(); - // enableNavTimeUTC(); + setTimePulse(); return 1; @@ -224,40 +208,38 @@ bool gps_hasfix() { // function to poll UTC time from GPS NMEA data; note: this is costly time_t get_gpstime(uint16_t *msec) { - // poll NMEA ZDA sentence - GPS_Serial.print(ZDA_Request); - // wait for gps NMEA answer - // vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL)); + *msec = 0; // 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 = - 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 + // convert tinygps time format to struct tm format struct tm gps_tm = {0}; - gps_tm.tm_sec = zdatime % 100; // second (UTC) - gps_tm.tm_min = (zdatime / 100) % 100; // minute (UTC) - gps_tm.tm_hour = zdatime / 10000; // hour (UTC) - gps_tm.tm_mday = atoi(gpsday.value()); // day, 01 to 31 - gps_tm.tm_mon = atoi(gpsmonth.value()) - 1; // month, 01 to 12 - gps_tm.tm_year = atoi(gpsyear.value()) - 1900; // year, YYYY + gps_tm.tm_sec = gps.time.second(); + gps_tm.tm_min = gps.time.minute(); + gps_tm.tm_hour = gps.time.hour(); + gps_tm.tm_mday = gps.date.day(); + gps_tm.tm_mon = gps.date.month() - 1; // 1-12 -> 0-11 + gps_tm.tm_year = gps.date.year() - 1900; // 2000+ -> years since 1900 // convert UTC tm to time_t epoch gps_tm.tm_isdst = 0; // UTC has no DST time_t t = mkgmtime(&gps_tm); - // add protocol delay with millisecond precision - t += (time_t)(delay_ms / 1000); - *msec = delay_ms % 1000; // fractional seconds +#ifdef GPS_INT + // if we have a recent GPS PPS pulse, sync on top of next second + if (millis() - lastPPS < 1000) + *msec = (uint16_t)(millis() - lastPPS); + else { + ESP_LOGD(TAG, "no PPS from GPS"); + return 0; + } +#endif return t; } ESP_LOGD(TAG, "no valid GPS time"); - return 0; } // get_gpstime() @@ -271,20 +253,24 @@ void gps_loop(void *pvParameters) { while (cfg.payloadmask & GPS_DATA) { // feed GPS decoder with serial NMEA data from GPS device - while (GPS_Serial.available()) - if (gps.encode(GPS_Serial.read())) - break; // leave encode loop after each NMEA complete sentence + while (GPS_Serial.available()) { + if (gps.encode(GPS_Serial.read())) { - // show NMEA data, very noisy, useful only for debugging GPS - // ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: - // %u", gps.passedChecksum(), gps.failedChecksum(), gps - // .sentencesWithFix()); + // show NMEA data, very noisy, for debugging GPS + // ESP_LOGV( + // TAG, + // "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); - } // inner while loop + } delay(1000); - } // outer while loop + } // infinite while loop } // gps_loop() diff --git a/src/main.cpp b/src/main.cpp index b1d12fbb..b4b780af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,7 +62,7 @@ triggers pps 1 sec impulse ISRs fired by CPU or GPIO: 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 ButtonIRQ <- GPIO <- Button PMUIRQ <- GPIO <- PMU chip @@ -492,8 +492,7 @@ void setup() { #endif ESP_LOGI(TAG, "Starting Timekeeper..."); - _ASSERT(timepulse_init()); // setup pps timepulse - timepulse_start(); // starts pps and cyclic time sync + _ASSERT(timepulse_init()); // starts pps and cyclic time sync strcat_P(features, " TIME"); #endif // timesync diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 84445e77..6a4d2904 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -19,9 +19,8 @@ static const char TAG[] = __FILE__; // G = GPS / R = RTC / L = LORA / * = no sync / ? = never synced const char timeSetSymbols[] = {'G', 'R', 'L', '*', '?'}; -portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; - DRAM_ATTR bool TimePulseTick = false; +DRAM_ATTR unsigned long lastPPS = millis(); timesource_t timeSource = _unsynced; TaskHandle_t ClockTask = NULL; hw_timer_t *ppsIRQ = NULL; @@ -144,26 +143,23 @@ uint8_t timepulse_init() { // sntp_init(); 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 - - // setup external interupt pin for rising edge GPS INT + // setup external interupt pin for rising edge of GPS PPS pinMode(GPS_INT, INPUT_PULLDOWN); - // setup external rtc 1Hz clock as pulse per second clock - ESP_LOGI(TAG, "Timepulse: external (GPS)"); - return 1; // success + attachInterrupt(digitalPinToInterrupt(GPS_INT), GPSIRQ, RISING); +#endif -// use pulse from on board RTC chip as time base with fixed frequency -#elif defined RTC_INT +// if we have, use pulse from on board RTC chip as time base for calendar time +#if defined RTC_INT - // setup external interupt pin for falling edge RTC INT - pinMode(RTC_INT, INPUT_PULLUP); - - // setup external rtc 1Hz clock as pulse per second clock + // setup external rtc 1Hz clock pulse if (I2C_MUTEX_LOCK()) { Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); I2C_MUTEX_UNLOCK(); + pinMode(RTC_INT, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING); ESP_LOGI(TAG, "Timepulse: external (RTC)"); return 1; // success } else { @@ -173,33 +169,39 @@ uint8_t timepulse_init() { return 1; // success #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 timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms + timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true); + timerAlarmEnable(ppsIRQ); ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)"); return 1; // success #endif -} // timepulse_init -void timepulse_start(void) { -#ifdef GPS_INT // start external clock gps pps line - 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 + // start cyclic time sync + timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ); // get time if we don't have one if (timeSource != _set) 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) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -212,11 +214,7 @@ void IRAM_ATTR CLOCKIRQ(void) { // flip time pulse ticker, if needed #ifdef HAS_DISPLAY -#if (defined GPS_INT || defined RTC_INT) - portENTER_CRITICAL(&mux); TimePulseTick = !TimePulseTick; // flip global variable pulse ticker - portEXIT_CRITICAL(&mux); -#endif #endif // yield only if we should