From 1d07bab68a234afe3f6dce178567d1a44f410f80 Mon Sep 17 00:00:00 2001 From: ladyada Date: Tue, 15 Sep 2026 11:56:03 -0400 Subject: [PATCH] Support antenna status on newer CDTop GPS modules Co-authored-by: Limor Fried --- .../GPS_HardwareSerial_LOCUS_Start.ino | 5 +- .../GPS_HardwareSerial_LOCUS_Status.ino | 5 +- .../GPS_HardwareSerial_Parsing.ino | 5 +- .../GPS_HardwareSerial_Timing.ino | 5 +- examples/GPS_I2C_Parsing/GPS_I2C_Parsing.ino | 5 +- .../GPS_SoftwareSerial_Parsing.ino | 5 +- examples/shield_sdlog/shield_sdlog.ino | 3 +- .../tests/antenna_status/antenna_status.ino | 74 +++++++++++++++++++ src/Adafruit_GPS.h | 2 +- src/Adafruit_PMTK.h | 4 + src/NMEA_parse.cpp | 36 ++++++--- 11 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 extras/tests/antenna_status/antenna_status.ino diff --git a/examples/GPS_HardwareSerial_LOCUS_Start/GPS_HardwareSerial_LOCUS_Start.ino b/examples/GPS_HardwareSerial_LOCUS_Start/GPS_HardwareSerial_LOCUS_Start.ino index ca7ace0..d90f90e 100644 --- a/examples/GPS_HardwareSerial_LOCUS_Start/GPS_HardwareSerial_LOCUS_Start.ino +++ b/examples/GPS_HardwareSerial_LOCUS_Start/GPS_HardwareSerial_LOCUS_Start.ino @@ -46,8 +46,9 @@ void setup() GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); // Default is 1 Hz update rate GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); - // Request updates on antenna status, comment out to keep quiet - GPS.sendCommand(PGCMD_ANTENNA); + // Request antenna status from old and new modules; comment out both to keep quiet + GPS.sendCommand(F(PGCMD_ANTENNA)); + GPS.sendCommand(F(CDCMD_ANTENNA)); // Ask for firmware version GPS.sendCommand(PMTK_Q_RELEASE); diff --git a/examples/GPS_HardwareSerial_LOCUS_Status/GPS_HardwareSerial_LOCUS_Status.ino b/examples/GPS_HardwareSerial_LOCUS_Status/GPS_HardwareSerial_LOCUS_Status.ino index 75f20af..f3b9319 100644 --- a/examples/GPS_HardwareSerial_LOCUS_Status/GPS_HardwareSerial_LOCUS_Status.ino +++ b/examples/GPS_HardwareSerial_LOCUS_Status/GPS_HardwareSerial_LOCUS_Status.ino @@ -46,8 +46,9 @@ void setup() GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); // Default is 1 Hz update rate GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); - // Request updates on antenna status, comment out to keep quiet - GPS.sendCommand(PGCMD_ANTENNA); + // Request antenna status from old and new modules; comment out both to keep quiet + GPS.sendCommand(F(PGCMD_ANTENNA)); + GPS.sendCommand(F(CDCMD_ANTENNA)); // Ask for firmware version GPS.sendCommand(PMTK_Q_RELEASE); diff --git a/examples/GPS_HardwareSerial_Parsing/GPS_HardwareSerial_Parsing.ino b/examples/GPS_HardwareSerial_Parsing/GPS_HardwareSerial_Parsing.ino index da3844d..9edfc75 100644 --- a/examples/GPS_HardwareSerial_Parsing/GPS_HardwareSerial_Parsing.ino +++ b/examples/GPS_HardwareSerial_Parsing/GPS_HardwareSerial_Parsing.ino @@ -51,8 +51,9 @@ void setup() // For the parsing code to work nicely and have time to sort thru the data, and // print it out we don't suggest using anything higher than 1 Hz - // Request updates on antenna status, comment out to keep quiet - GPS.sendCommand(PGCMD_ANTENNA); + // Request antenna status from old and new modules; comment out both to keep quiet + GPS.sendCommand(F(PGCMD_ANTENNA)); + GPS.sendCommand(F(CDCMD_ANTENNA)); delay(1000); diff --git a/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino b/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino index 98386f6..dc8d46d 100644 --- a/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino +++ b/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino @@ -72,8 +72,9 @@ void setup() { // For the parsing code to work nicely and have time to sort thru the data, // and print it out we don't suggest using anything higher than 1 Hz - // Request updates on antenna status, comment out to keep quiet - GPS.sendCommand(PGCMD_ANTENNA); + // Request antenna status from old and new modules; comment out both to keep quiet + GPS.sendCommand(F(PGCMD_ANTENNA)); + GPS.sendCommand(F(CDCMD_ANTENNA)); delay(1000); diff --git a/examples/GPS_I2C_Parsing/GPS_I2C_Parsing.ino b/examples/GPS_I2C_Parsing/GPS_I2C_Parsing.ino index 59d5d1d..4c0ec68 100644 --- a/examples/GPS_I2C_Parsing/GPS_I2C_Parsing.ino +++ b/examples/GPS_I2C_Parsing/GPS_I2C_Parsing.ino @@ -39,8 +39,9 @@ void setup() // For the parsing code to work nicely and have time to sort thru the data, and // print it out we don't suggest using anything higher than 1 Hz - // Request updates on antenna status, comment out to keep quiet - GPS.sendCommand(PGCMD_ANTENNA); + // Request antenna status from old and new modules; comment out both to keep quiet + GPS.sendCommand(F(PGCMD_ANTENNA)); + GPS.sendCommand(F(CDCMD_ANTENNA)); delay(1000); diff --git a/examples/GPS_SoftwareSerial_Parsing/GPS_SoftwareSerial_Parsing.ino b/examples/GPS_SoftwareSerial_Parsing/GPS_SoftwareSerial_Parsing.ino index 396da93..bc126d5 100644 --- a/examples/GPS_SoftwareSerial_Parsing/GPS_SoftwareSerial_Parsing.ino +++ b/examples/GPS_SoftwareSerial_Parsing/GPS_SoftwareSerial_Parsing.ino @@ -51,8 +51,9 @@ void setup() // For the parsing code to work nicely and have time to sort thru the data, and // print it out we don't suggest using anything higher than 1 Hz - // Request updates on antenna status, comment out to keep quiet - GPS.sendCommand(PGCMD_ANTENNA); + // Request antenna status from old and new modules; comment out both to keep quiet + GPS.sendCommand(F(PGCMD_ANTENNA)); + GPS.sendCommand(F(CDCMD_ANTENNA)); delay(1000); // Ask for firmware version diff --git a/examples/shield_sdlog/shield_sdlog.ino b/examples/shield_sdlog/shield_sdlog.ino index 8bd09ba..9ed8467 100644 --- a/examples/shield_sdlog/shield_sdlog.ino +++ b/examples/shield_sdlog/shield_sdlog.ino @@ -127,8 +127,9 @@ void setup() { // Set the update rate GPS.sendCommand(F(PMTK_SET_NMEA_UPDATE_1HZ)); // 100 millihertz (once every 10 seconds), 1Hz or 5Hz update rate - // Turn off updates on antenna status, if the firmware permits it + // Disable antenna status on old and new modules, if the firmware permits it. GPS.sendCommand(F(PGCMD_NOANTENNA)); + GPS.sendCommand(F(CDCMD_NOANTENNA)); // the nice thing about this code is you can have a timer0 interrupt go off // every 1 millisecond, and read data from the GPS for you. that makes the diff --git a/extras/tests/antenna_status/antenna_status.ino b/extras/tests/antenna_status/antenna_status.ino new file mode 100644 index 0000000..6a74dc4 --- /dev/null +++ b/extras/tests/antenna_status/antenna_status.ino @@ -0,0 +1,74 @@ +#include + +Adafruit_GPS GPS; +bool testsPassed = false; + +void setup() { + Serial.begin(115200); + // Wait briefly for the Serial Monitor on native USB boards. + while (!Serial && millis() < 5000) { + delay(10); + } + delay(250); + Serial.println(F("Adafruit GPS antenna status regression")); + + char valid[][20] = { + "$PGTOP,11,1*6D", + "$PGTOP,11,2*6E", + "$PGTOP,11,3*6F", + "$PCD,11,1*66", + "$PCD,11,2*65", + "$PCD,11,3*64", + }; + // PGTOP: problem, internal, external. PCD: internal, external, problem. + const uint8_t expected[] = {1, 2, 3, 2, 3, 1}; + for (uint8_t i = 0; i < 6; i++) { + GPS.antenna = 0; + if (!GPS.parse(valid[i]) || GPS.antenna != expected[i]) { + Serial.print(F("FAIL: antenna status for ")); + Serial.println(valid[i]); + return; + } + if (i < 3) { + if (strcmp(GPS.lastSource, "PG") || strcmp(GPS.lastSentence, "TOP")) { + Serial.println(F("FAIL: legacy sentence identity")); + return; + } + } else if (strcmp(GPS.lastSource, "P") || strcmp(GPS.lastSentence, "CD")) { + Serial.println(F("FAIL: new sentence identity")); + return; + } + Serial.print(F("PASS: ")); + Serial.println(valid[i]); + } + + char invalid[][22] = { + "$PCD,11*7B", + "$PCD,11,*57", + "$PCD,11,4*63", + "$PCD,9,2*5C", + "$PCDXYZ,11,1*3D", + "$PGTOP,11*70", + "$PCD,11,10*56", + "$PGTOP,11,10*5D", + }; + for (uint8_t i = 0; i < 8; i++) { + GPS.antenna = 2; + if (GPS.parse(invalid[i]) || GPS.antenna != 2) { + Serial.print(F("FAIL: invalid sentence changed antenna status: ")); + Serial.println(invalid[i]); + return; + } + } + Serial.println(F("PASS: incomplete and unrelated sentences preserve antenna status")); + testsPassed = true; +} + +void loop() { + if (testsPassed) { + Serial.println(F("PASS: antenna status regression")); + } else { + Serial.println(F("FAIL: antenna status regression")); + } + delay(2000); +} diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index 964007f..e60a2f7 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -202,7 +202,7 @@ public: uint8_t fixquality; ///< Fix quality (0, 1, 2 = Invalid, GPS, DGPS) uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix) uint8_t satellites; ///< Number of satellites in use - uint8_t antenna; ///< Antenna that is used (from PGTOP) + uint8_t antenna; ///< 1=problem, 2=internal, 3=external (PGTOP or PCD) uint16_t LOCUS_serial; ///< Log serial number uint16_t LOCUS_records; ///< Log number of data record diff --git a/src/Adafruit_PMTK.h b/src/Adafruit_PMTK.h index d0c4a84..0fc4fc9 100644 --- a/src/Adafruit_PMTK.h +++ b/src/Adafruit_PMTK.h @@ -109,6 +109,10 @@ #define PGCMD_ANTENNA \ "$PGCMD,33,1*6C" ///< request for updates on antenna status #define PGCMD_NOANTENNA "$PGCMD,33,0*6D" ///< don't show antenna status messages +#define CDCMD_ANTENNA \ + "$CDCMD,33,1*7C" ///< request antenna status on newer CDTop modules +#define CDCMD_NOANTENNA \ + "$CDCMD,33,0*7D" ///< disable antenna status on newer CDTop modules #define MAXWAITSENTENCE \ 10 ///< maximum number of sentences to examine when looking for a response diff --git a/src/NMEA_parse.cpp b/src/NMEA_parse.cpp index a984dba..59eb24b 100644 --- a/src/NMEA_parse.cpp +++ b/src/NMEA_parse.cpp @@ -26,14 +26,14 @@ const char PROGMEM Adafruit_GPS::sources[][4] = {"II", "WI", "GP", "PG", "GL", "GA", "GN", "P", "ZZZ"}; #ifdef NMEA_EXTENSIONS const char PROGMEM Adafruit_GPS::sentences_parsed[][4] = { - "GGA", "GLL", "GSA", "RMC", "DBT", "HDM", "HDT", "MDA", "MTW", "MWV", + "GGA", "GLL", "GSA", "RMC", "CD", "DBT", "HDM", "HDT", "MDA", "MTW", "MWV", "RMB", "TOP", "TXT", "VHW", "VLW", "VPW", "VWR", "WCV", "XTE", "ZZZ"}; const char PROGMEM Adafruit_GPS::sentences_known[][4] = { "APB", "DPT", "GSV", "HDG", "MWD", "ROT", "RPM", "RSA", "VDR", "VTG", "ZDA", "ZZZ"}; #else // make the lists short to save flash on small boards -const char PROGMEM Adafruit_GPS::sentences_parsed[][4] = {"GGA", "GLL", "GSA", - "RMC", "TOP", "ZZZ"}; +const char PROGMEM Adafruit_GPS::sentences_parsed[][4] = { + "GGA", "GLL", "GSA", "RMC", "TOP", "CD", "ZZZ"}; const char PROGMEM Adafruit_GPS::sentences_known[][4] = {"DBT", "HDM", "HDT", "ZZZ"}; #endif @@ -201,16 +201,27 @@ bool Adafruit_GPS::parse(char *nmea) { if (!isEmpty(p)) VDOP = atof(p); // last before checksum - } else if (!strcmp(thisSentence, "TOP")) { //*****************************TOP + } else if (!strcmp(thisSentence, "TOP") || + !strcmp_P(thisSentence, PSTR("CD"))) { // Antenna status if (fields < 2) return false; - // See: - // https://learn.adafruit.com/adafruit-ultimate-gps-featherwing/antenna-options - // There is an output sentence that will tell you the status of the - // antenna. $PGTOP,11,x where x is the status number. If x is 3 that means - // it is using the external antenna. If x is 2 it's using the internal + bool cdtop = !strcmp_P(thisSentence, PSTR("CD")); + // Only PCD subtype 11 describes the antenna; other CD messages differ. + if (cdtop && strncmp_P(nmea, PSTR("$PCD,11,"), 8)) + return false; p = strchr(p, ',') + 1; - parseAntenna(p); + if (!parseAntenna(p)) + return false; + if (cdtop) { + // PCD uses 1=internal, 2=external, 3=shorted. Preserve the public + // PGTOP convention: 1=problem, 2=internal, 3=external. + if (antenna == 1) + antenna = 2; + else if (antenna == 2) + antenna = 3; + else + antenna = 1; + } } #ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality @@ -895,11 +906,12 @@ bool Adafruit_GPS::parseFix(char *p) { /*! @brief Parse a part of an NMEA string for antenna that is used @param p Pointer to the location of the token in the NMEA string - @return 3=external 2=internal 1=there was an antenna short or problem + @return True if the antenna status was recognized, false otherwise */ /**************************************************************************/ bool Adafruit_GPS::parseAntenna(char *p) { - if (!isEmpty(p)) { + if (!isEmpty(p) && p[0] != '\0' && + (p[1] == ',' || p[1] == '*' || p[1] == '\0')) { if (p[0] == '3') { antenna = 3; } else if (p[0] == '2') {