diff --git a/Tools/scripts/build_options.py b/Tools/scripts/build_options.py index d98b38533afce..2173f35835142 100644 --- a/Tools/scripts/build_options.py +++ b/Tools/scripts/build_options.py @@ -458,6 +458,7 @@ def config_option(self): Feature('GPS Drivers', 'GSOF', 'AP_GPS_GSOF_ENABLED', 'Enable GSOF GPS', 0, None), Feature('GPS Drivers', 'NMEA_GPS', 'AP_GPS_NMEA_ENABLED', 'Enable NMEA GPS', 0, None), Feature('GPS Drivers', 'NMEA_UNICORE', 'AP_GPS_NMEA_UNICORE_ENABLED', 'Enable NMEA Unicore GPS', 0, "NMEA_GPS"), + Feature('GPS Drivers', 'NMEA_QUECTEL', 'AP_GPS_NMEA_QUECTEL_ENABLED', 'Enable NMEA Quectel GPS', 0, "NMEA_GPS"), Feature('GPS Drivers', 'MAV', 'AP_GPS_MAV_ENABLED', 'Enable MAVLink GPS', 0, None), Feature('GPS Drivers', 'NOVA', 'AP_GPS_NOVA_ENABLED', 'Enable NOVA GPS', 0, None), Feature('GPS Drivers', 'SBF', 'AP_GPS_SBF_ENABLED', 'Enable SBF GPS', 0, None), diff --git a/Tools/scripts/decode_devid.py b/Tools/scripts/decode_devid.py index 3a432403e4be5..f9abe1a910df7 100755 --- a/Tools/scripts/decode_devid.py +++ b/Tools/scripts/decode_devid.py @@ -110,6 +110,7 @@ def num(s): 0x3B : "DEVTYPE_INS_ICM45686", 0x3C : "DEVTYPE_INS_SCHA63T", 0x3D : "DEVTYPE_INS_IIM42653", + 0x3E : "DEVTYPE_INS_SCH16T", } baro_types = { diff --git a/Tools/scripts/extract_features.py b/Tools/scripts/extract_features.py index b60adeca03ea8..6d81892f9a0ea 100755 --- a/Tools/scripts/extract_features.py +++ b/Tools/scripts/extract_features.py @@ -82,6 +82,12 @@ def __init__(self, filename, nm="arm-none-eabi-nm", strings="strings"): ('AP_RANGEFINDER_LIGHTWARE_GRF_ENABLED', 'AP_RangeFinder_LightWareGRF::get_reading'), ('AP_GPS_NMEA_UNICORE_ENABLED', r'AP_GPS_NMEA::parse_agrica_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmver_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmepe_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmvel_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmpvt_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmtar_field',), + ('AP_GPS_{type}_ENABLED', r'AP_GPS_(?P.*)::read\b',), ('AP_OPTICALFLOW_ENABLED', 'AP_OpticalFlow::AP_OpticalFlow',), diff --git a/config_and_rebuild.sh b/config_and_rebuild.sh new file mode 100644 index 0000000000000..699e4e5f2cb1b --- /dev/null +++ b/config_and_rebuild.sh @@ -0,0 +1,5 @@ +# git clone --recurse-submodules git@github.com:vidma/ardupilot.git +#./waf configure --board=MambaF405-2022 && ./waf clean && ./waf plane + +# ./waf configure --board=Pixhawk6X && ./waf clean && ./waf plane +./waf configure --board=MicoAir743-Lite && ./waf clean && ./waf plane diff --git a/libraries/AP_GPS/AP_GPS.cpp b/libraries/AP_GPS/AP_GPS.cpp index c569798cb775c..37ecea118aeb2 100644 --- a/libraries/AP_GPS/AP_GPS.cpp +++ b/libraries/AP_GPS/AP_GPS.cpp @@ -808,6 +808,9 @@ AP_GPS_Backend *AP_GPS::_detect_instance(const uint8_t instance) #if AP_GPS_NMEA_UNICORE_ENABLED type == GPS_TYPE_UNICORE_NMEA || type == GPS_TYPE_UNICORE_MOVINGBASE_NMEA || +#endif +#if AP_GPS_NMEA_QUECTEL_ENABLED + type == GPS_TYPE_QUECTEL_NMEA || #endif type == GPS_TYPE_ALLYSTAR) && AP_GPS_NMEA::_detect(dstate->nmea_detect_state, data)) { @@ -1950,6 +1953,48 @@ void AP_GPS::Write_GPS(uint8_t i) }; AP::logger().WriteBlock(&pkt2, sizeof(pkt2)); } + +void AP_GPS::Write_PPS(uint64_t interrupt_time_us, uint64_t earlier_interrupt_time_us) +{ + + const uint64_t USEC_PER_SEC = 1000000ULL; + const float clock_drift_percent = (((float)interrupt_time_us - (float)earlier_interrupt_time_us) - (float)USEC_PER_SEC) * 100.0f / ((float)USEC_PER_SEC); + + // FIXME: should it be dependent on GPS instance? but makes little sense + uint8_t instance = 0; // FIXME? + + const GPS_State &istate = state[instance]; + + // it makes no sense to report PPS if there's no GPS FIX + if ((istate.last_gps_time_ms == 0 && istate.last_corrected_gps_time_us == 0) || istate.time_week == 0) { + return; + } + uint64_t _last_gps_systime_us = istate.last_gps_time_ms * 1000ULL; ///< the system time we got the last GPS timestamp, microseconds + + // FIXME: check if fix still exists!? + uint64_t last_gps_fix_time_us = istate_time_to_epoch_ms(istate.time_week, istate.time_week_ms) * 1000ULL; // last GPS time + + // GPS UTC time when the GPIO interrupt was triggered + // Last UTC time received from the GPS + elapsed time to the PPS interrupt + + // FIXME: I possibly don't understand what this correction is used for? (based on PX4 PPSCpature driver) + // oh it's probably needed in case the GPS time was not received but PPS still fired? + uint64_t gps_utc_time_us = last_gps_fix_time_us + (interrupt_time_us - _last_gps_systime_us); + + // (For ubx F9P and NMEA Quectel LG580P) The rising edge of the PPS pulse is aligned to the top of second GPS time base. + // So, remove the fraction of second and shift to the next second. The interrupt is triggered + // before the matching timestamp is received via a UART message, which means the last received GPS time is always + // behind. + const uint64_t corrected_utc_time_us = gps_utc_time_us - (gps_utc_time_us % USEC_PER_SEC) + USEC_PER_SEC; + + struct log_PPS pkt { + LOG_PACKET_HEADER_INIT(LOG_PPS_MSG), + time_us : interrupt_time_us, + utc_time_ms : corrected_utc_time_us / 1000ULL, + clock_drift_percent : clock_drift_percent + }; + AP::logger().WriteBlock(&pkt, sizeof(pkt)); +} #endif bool AP_GPS::is_rtk_base(uint8_t instance) const diff --git a/libraries/AP_GPS/AP_GPS.h b/libraries/AP_GPS/AP_GPS.h index 2bae27c6374bc..168a4c05e17cb 100644 --- a/libraries/AP_GPS/AP_GPS.h +++ b/libraries/AP_GPS/AP_GPS.h @@ -112,6 +112,7 @@ class AP_GPS GPS_TYPE_UNICORE_NMEA = 24, GPS_TYPE_UNICORE_MOVINGBASE_NMEA = 25, GPS_TYPE_SBF_DUAL_ANTENNA = 26, + GPS_TYPE_QUECTEL_NMEA = 27, #if AP_SIM_GPS_ENABLED GPS_TYPE_SITL = 100, #endif @@ -791,6 +792,8 @@ class AP_GPS // logging support void Write_GPS(uint8_t instance); + void Write_PPS(uint64_t interrupt_time_us, uint64_t earlier_interrupt_time_us); + #if AP_GPS_RTCM_DECODE_ENABLED /* per mavlink channel RTCM decoder, enabled with RTCM decode diff --git a/libraries/AP_GPS/AP_GPS_NMEA.cpp b/libraries/AP_GPS/AP_GPS_NMEA.cpp index b61b6e1d1a3be..b5e024f25bd1f 100644 --- a/libraries/AP_GPS/AP_GPS_NMEA.cpp +++ b/libraries/AP_GPS/AP_GPS_NMEA.cpp @@ -281,6 +281,28 @@ bool AP_GPS_NMEA::_have_new_message() } #endif // AP_GPS_NMEA_UNICORE_ENABLED +#if AP_GPS_NMEA_QUECTEL_ENABLED + // Check for PQTM message timeouts + // Note: PQTMPVT only provides auxiliary data (undulation, VDOP, leap seconds) + // so we don't treat it as essential for GPS health + if (now - _last_PQTM_vel_ms > 500) { + if (_last_PQTM_vel_ms != 0) { + // we have lost PQTM velocity messages + state.have_vertical_velocity = false; + state.have_speed_accuracy = false; + _last_PQTM_vel_ms = 0; + } + } + if (now - _last_PQTM_acc_ms > 500) { + if (_last_PQTM_acc_ms != 0) { + // we have lost PQTM accuracy messages + state.have_horizontal_accuracy = false; + state.have_vertical_accuracy = false; + _last_PQTM_acc_ms = 0; + } + } +#endif // AP_GPS_NMEA_QUECTEL_ENABLED + _last_fix_ms = now; _last_GGA_ms = 1; @@ -508,6 +530,27 @@ bool AP_GPS_NMEA::_term_complete() break; } #endif // AP_GPS_NMEA_UNICORE_ENABLED + +#if AP_GPS_NMEA_QUECTEL_ENABLED + case _GPS_SENTENCE_PQTMVER: { + if (_have_pqtmver) { + GCS_SEND_TEXT(MAV_SEVERITY_INFO, + "NMEA Quectel %s %s", + _pqtmver.version, + _pqtmver.build_date); + } + break; + } + case _GPS_SENTENCE_PQTMEPE: + // Accuracy information already processed in parse function + break; + case _GPS_SENTENCE_PQTMVEL: + // Velocity information already processed in parse function + break; + case _GPS_SENTENCE_PQTMPVT: + // PVT information already processed in parse function + break; +#endif // AP_GPS_NMEA_QUECTEL_ENABLED } // see if we got a good message return _have_new_message(); @@ -543,6 +586,30 @@ bool AP_GPS_NMEA::_term_complete() return false; } #endif + +#if AP_GPS_NMEA_QUECTEL_ENABLED + // Check for Quectel PQTM messages + if (strcmp(_term, "PQTMVERNO") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMVER; + return false; + } + if (strcmp(_term, "PQTMEPE") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMEPE; + return false; + } + if (strcmp(_term, "PQTMVEL") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMVEL; + return false; + } + if (strcmp(_term, "PQTMPVT") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMPVT; + return false; + } + if (strcmp(_term, "PQTMTAR") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMTAR; + return false; + } +#endif // AP_GPS_NMEA_QUECTEL_ENABLED /* The first two letters of the NMEA term are the talker ID. The most common is 'GP' but there are a bunch of others @@ -670,6 +737,27 @@ bool AP_GPS_NMEA::_term_complete() break; #endif #endif + +#if AP_GPS_NMEA_QUECTEL_ENABLED + // Quectel PQTM message parsing + case _GPS_SENTENCE_PQTMVER + 1 ... _GPS_SENTENCE_PQTMVER + 3: // PQTMVER message (3 data fields) + parse_pqtmver_field(_term_number, _term); + break; + case _GPS_SENTENCE_PQTMEPE + 1 ... _GPS_SENTENCE_PQTMEPE + 6: // PQTMEPE message (6 fields) + parse_pqtmepe_field(_term_number, _term); + break; + case _GPS_SENTENCE_PQTMVEL + 1 ... _GPS_SENTENCE_PQTMVEL + 11: // PQTMVEL message (11 fields) + parse_pqtmvel_field(_term_number, _term); + break; + case _GPS_SENTENCE_PQTMPVT + 1 ... _GPS_SENTENCE_PQTMPVT + 19: // PQTMPVT message (19 fields) + parse_pqtmpvt_field(_term_number, _term); + break; + #if GPS_MOVING_BASELINE + case _GPS_SENTENCE_PQTMTAR + 1 ... _GPS_SENTENCE_PQTMTAR + 12: // PQTMTAR message (12 fields) + parse_pqtmtar_field(_term_number, _term); + break; + #endif // GPS_MOVING_BASELINE +#endif // AP_GPS_NMEA_QUECTEL_ENABLED } } @@ -794,6 +882,313 @@ void AP_GPS_NMEA::parse_versiona_field(uint16_t term_number, const char *term) } #endif // AP_GPS_NMEA_UNICORE_ENABLED +#if AP_GPS_NMEA_QUECTEL_ENABLED +/* + Parse PQTM message fields for Quectel GNSS modules + */ + +/* + parse PQTMVER field - Firmware Version Output + Example: $PQTMVER,1,MODULE,LG290P03AANR01A03S,2024/04/30,10:53:07*32 + */ +void AP_GPS_NMEA::parse_pqtmver_field(uint16_t term_number, const char *term) +{ + auto &pv = _pqtmver; + + switch (term_number) { + case 1: // Version string (VerStr) + strncpy(pv.version, term, sizeof(pv.version)); + break; + case 2: // Build date (BuildDate) + strncpy(pv.build_date, term, sizeof(pv.build_date)); + break; + case 3: // Build time (BuildTime) + strncpy(pv.build_time, term, sizeof(pv.build_time)); + _have_pqtmver = true; + break; + } +} + +/* + parse PQTMEPE field - Estimated Position Error + Example: $PQTMEPE,2,1.000,1.000,1.000,1.414,1.732*52 + */ +void AP_GPS_NMEA::parse_pqtmepe_field(uint16_t term_number, const char *term) +{ + auto &pe = _pqtmepe; + switch (term_number) { + case 2: // EPE North + pe.epe_north = atof(term); + break; + case 3: // EPE East + pe.epe_east = atof(term); + break; + case 4: // EPE Down + pe.epe_down = atof(term); + break; + case 5: // EPE 2D + pe.epe_2d = atof(term); + break; + case 6: // EPE 3D + pe.epe_3d = atof(term); + // Update state accuracy information + state.horizontal_accuracy = pe.epe_2d; + state.have_horizontal_accuracy = true; + state.vertical_accuracy = pe.epe_down; + state.have_vertical_accuracy = true; + _last_PQTM_acc_ms = AP_HAL::millis(); + break; + } +} + +/* + parse PQTMVEL field - Velocity Information + Example: $PQTMVEL,1,154512.100,1.251,2.452,1.245,2.752,3.021,180.512,0.124,0.254,0.250*67 + */ +void AP_GPS_NMEA::parse_pqtmvel_field(uint16_t term_number, const char *term) +{ + auto &pv = _pqtmvel; + switch (term_number) { + case 2: // UTC time + pv.time_ms = _parse_decimal_100(term) * 10; + break; + case 3: // North velocity + pv.vel_NED.x = atof(term); + break; + case 4: // East velocity + pv.vel_NED.y = atof(term); + break; + case 5: // Down velocity + pv.vel_NED.z = atof(term); + break; + case 6: // Ground speed + pv.ground_speed = atof(term); + break; + case 7: // 3D speed + pv.speed_3d = atof(term); + break; + case 8: // Heading + pv.heading = atof(term); + break; + case 9: // Ground speed accuracy + pv.ground_speed_acc = atof(term); + break; + case 10: // 3D speed accuracy + pv.speed_acc = atof(term); + break; + case 11: // Heading accuracy + pv.heading_acc = atof(term); + // Update state with velocity information + state.velocity = pv.vel_NED; + state.have_vertical_velocity = true; + state.ground_speed = pv.ground_speed; + state.ground_course = pv.heading; + state.speed_accuracy = pv.speed_acc; + state.have_speed_accuracy = true; + _last_PQTM_vel_ms = AP_HAL::millis(); + break; + } +} + +/* + parse PQTMPVT field - Only auxiliary data not available in standard NMEA + Example: $PQTMPVT,1,31075000,20221225,083737.000,,3,09,18,31.12738291,117.26372910,34.212,5.267,3.212,2.928,0.238,4.346,34.12,2.16,4.38*51 + */ +void AP_GPS_NMEA::parse_pqtmpvt_field(uint16_t term_number, const char *term) +{ + auto &pp = _pqtmpvt; + switch (term_number) { + case 2: // Time of week + pp.tow = atol(term); + break; + case 8: // Leap seconds + if (strlen(term) > 0) { + pp.leap_seconds = atol(term); + } + break; + case 12: // Geoidal separation + if (strlen(term) > 0) { + pp.sep = atof(term); + state.undulation = -pp.sep; + state.have_undulation = true; + } + break; + case 19: // PDOP + if (strlen(term) > 0) { + pp.pdop = atof(term); + state.vdop = pp.pdop * 100; + } + break; + } +} + +#if GPS_MOVING_BASELINE + +#if HAL_LOGGING_ENABLED + +int16_t float_to_int16_100(float value) +{ + const float scale = 100.0f; + return (uint16_t)(value * scale); +} +void AP_GPS_NMEA::log_GPS_Heading_pqtmtar() +{ + auto &ph = _pqmtarheading; + + struct log_GPS_Heading pkt { + LOG_PACKET_HEADER_INIT(LOG_GPS_Heading_MSG), + // FIXME: dont i need autopilot time here from bootup? + time_us : AP_HAL::micros64(), //ph.time_ms * 1000, // convert from milliseconds to microseconds + instance : 0U, // FIXME !? should be logged from elsewhere? + quality : (uint8_t)ph.quality, + baseline_length_m : float_to_int16_100(ph.baseline_length), + pitch_deg : float_to_int16_100(ph.pitch), + roll_deg : float_to_int16_100(ph.roll), + heading_deg : float_to_int16_100(ph.heading), + pitch_acc_deg : float_to_int16_100(ph.pitch_acc), + roll_acc_deg : float_to_int16_100(ph.roll_acc), + heading_acc_deg : float_to_int16_100(ph.heading_acc), + //used_sv : (uint16_t)ph.used_sv, + }; + AP::logger().WriteBlock(&pkt, sizeof(pkt)); +} +#endif // HAL_LOGGING_ENABLED + +/* + parse PQTMTAR field - Outputs the time and attitude. The attitude computation in this message is computed from the two-antenna system. + Only the LG580P supports this message. + + Examples: + - with RTK fix: + $PQTMTAR,1,215951.600,5,,0.206,-11.841884,,234.657328,89.989998,,177.466919,20*49 + - without RTK fix: + $PQTMTAR,1,215857.400,1,,0.000,,,,,,,21*64 + + + Format: + $PQTMTAR,,