diff --git a/README.md b/README.md index 5b39fef15..9f3923f52 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ In the L1 band: - 🛰 GPS L1 C/A (centered at 1575.420 MHz) ✅ - 🛰 Galileo E1b/c (centered at 1575.420 MHz) ✅ - 🛰 BeiDou B1I (centered at 1561.098 MHz) ✅ -- 🛰 QZSS L1 C/A, where available (centered at 1575.420 MHz) ✅ +- 🛰 QZSS L1 C/A and C/B, where available (centered at 1575.420 MHz) ✅ In the E6 band: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2d7d32dc1..20674f6cb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -57,6 +57,11 @@ All notable changes to GNSS-SDR will be documented in this file. - Improved validation of GPS/QZSS CNAV Clock, Ephemeris, Integrity (CEI) dataset. - Implemented QZSS LNAV almanac/auxiliary pages decoding. +- Added support for the QZSS L1 C/B signal (PRNs 203-206), broadcast by + satellites configured to transmit it in place of L1 C/A. Observables and + ephemerides from L1 C/B PRNs are attributed to the PRN of the satellite's + nominal PNT signals in PVT and output products, following the RINEX 4.00 + convention. Contributed by @vladisslav2011. - Hardened BeiDou DNAV and Glonass GNAV decoding. - Improved performance of Galileo's Viterbi decoder. - Fixed edge cases in the retrieving of GPS L1 C/A navigation data. diff --git a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc index 5a6691e5c..93ed39c4f 100644 --- a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc +++ b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc @@ -55,6 +55,7 @@ #include "nmea_printer.h" #include "osnma_data.h" #include "pvt_conf.h" +#include "qzss.h" #include "rinex_printer.h" #include "rtcm_printer.h" #include "rtklib_rtkcmn.h" @@ -1203,6 +1204,9 @@ void rtklib_pvt_gs::msg_handler_telemetry(const pmt::pmt_t& msg) { // ### GPS EPHEMERIS ### const auto gps_eph = wht::any_cast>(pmt::any_ref(msg)); + // Ephemeris decoded from QZSS L1 C/B (PRN 203-206) belongs to the satellite + // identified by the PRN of its nominal PNT signals (RINEX 4.00, Table 6) + gps_eph->PRN = qzss_l1cb_prn_to_nominal_prn(gps_eph->PRN); DLOG(INFO) << "Ephemeris record has arrived from SAT ID " << gps_eph->PRN << " (Block " << gps_eph->satelliteBlock[gps_eph->PRN] << ")" @@ -2012,21 +2016,29 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item // ############ 1. READ PSEUDORANGES #### for (uint32_t i = 0; i < d_nchannels; i++) { - if (in[i][epoch].Flag_valid_pseudorange) + Gnss_Synchro gnss_synchro = in[i][epoch]; + if (gnss_synchro.System == 'J') { - const auto tmp_eph_iter_gps = d_internal_pvt_solver->gps_ephemeris_map.find(in[i][epoch].PRN); - const auto tmp_eph_iter_gal = d_internal_pvt_solver->galileo_ephemeris_map.find(in[i][epoch].PRN); - const auto tmp_eph_iter_cnav = d_internal_pvt_solver->gps_cnav_ephemeris_map.find(in[i][epoch].PRN); - const auto tmp_eph_iter_glo_gnav = d_internal_pvt_solver->glonass_gnav_ephemeris_map.find(in[i][epoch].PRN); - const auto tmp_eph_iter_bds_dnav = d_internal_pvt_solver->beidou_dnav_ephemeris_map.find(in[i][epoch].PRN); + // QZSS L1 C/B is broadcast with a dedicated PRN (203-206), but the + // transmitting satellite is identified by the PRN of its nominal PNT + // signals (RINEX 4.00, Table 6: QZSS PRN to RINEX Satellite Identifier) + gnss_synchro.PRN = qzss_l1cb_prn_to_nominal_prn(gnss_synchro.PRN); + } + if (gnss_synchro.Flag_valid_pseudorange) + { + const auto tmp_eph_iter_gps = d_internal_pvt_solver->gps_ephemeris_map.find(gnss_synchro.PRN); + const auto tmp_eph_iter_gal = d_internal_pvt_solver->galileo_ephemeris_map.find(gnss_synchro.PRN); + const auto tmp_eph_iter_cnav = d_internal_pvt_solver->gps_cnav_ephemeris_map.find(gnss_synchro.PRN); + const auto tmp_eph_iter_glo_gnav = d_internal_pvt_solver->glonass_gnav_ephemeris_map.find(gnss_synchro.PRN); + const auto tmp_eph_iter_bds_dnav = d_internal_pvt_solver->beidou_dnav_ephemeris_map.find(gnss_synchro.PRN); bool store_valid_observable = false; if (!d_osnma_strict && tmp_eph_iter_gps != d_internal_pvt_solver->gps_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_gps->second.PRN; - if ((prn_aux == in[i][epoch].PRN) && - ((std::string(in[i][epoch].Signal, 2) == std::string("1C")) || (std::string(in[i][epoch].Signal, 2) == std::string("J1"))) && + if ((prn_aux == gnss_synchro.PRN) && + ((std::string(gnss_synchro.Signal, 2) == std::string("1C")) || (std::string(gnss_synchro.Signal, 2) == std::string("J1"))) && (d_use_unhealthy_sats || (tmp_eph_iter_gps->second.SV_health == 0))) { store_valid_observable = true; @@ -2035,10 +2047,10 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item if (tmp_eph_iter_gal != d_internal_pvt_solver->galileo_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_gal->second.PRN; - if ((prn_aux == in[i][epoch].PRN) && - (((std::string(in[i][epoch].Signal, 2) == std::string("1B")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E1B_DVS == false) && (tmp_eph_iter_gal->second.E1B_HS == 0)))) || - ((std::string(in[i][epoch].Signal, 2) == std::string("5X")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E5a_DVS == false) && (tmp_eph_iter_gal->second.E5a_HS == 0)))) || - ((std::string(in[i][epoch].Signal, 2) == std::string("7X")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E5b_DVS == false) && (tmp_eph_iter_gal->second.E5b_HS == 0)))))) + if ((prn_aux == gnss_synchro.PRN) && + (((std::string(gnss_synchro.Signal, 2) == std::string("1B")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E1B_DVS == false) && (tmp_eph_iter_gal->second.E1B_HS == 0)))) || + ((std::string(gnss_synchro.Signal, 2) == std::string("5X")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E5a_DVS == false) && (tmp_eph_iter_gal->second.E5a_HS == 0)))) || + ((std::string(gnss_synchro.Signal, 2) == std::string("7X")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E5b_DVS == false) && (tmp_eph_iter_gal->second.E5b_HS == 0)))))) { if (d_osnma_strict) { @@ -2083,7 +2095,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item if (!d_osnma_strict && tmp_eph_iter_cnav != d_internal_pvt_solver->gps_cnav_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_cnav->second.PRN; - if ((prn_aux == in[i][epoch].PRN) && (((std::string(in[i][epoch].Signal, 2) == std::string("2S")) || (std::string(in[i][epoch].Signal, 2) == std::string("L5")) || (std::string(in[i][epoch].Signal, 2) == std::string("J5"))))) + if ((prn_aux == gnss_synchro.PRN) && (((std::string(gnss_synchro.Signal, 2) == std::string("2S")) || (std::string(gnss_synchro.Signal, 2) == std::string("L5")) || (std::string(gnss_synchro.Signal, 2) == std::string("J5"))))) { store_valid_observable = true; } @@ -2091,7 +2103,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item if (!d_osnma_strict && tmp_eph_iter_glo_gnav != d_internal_pvt_solver->glonass_gnav_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_glo_gnav->second.PRN; - if ((prn_aux == in[i][epoch].PRN) && ((std::string(in[i][epoch].Signal, 2) == std::string("1G")) || (std::string(in[i][epoch].Signal, 2) == std::string("2G")))) + if ((prn_aux == gnss_synchro.PRN) && ((std::string(gnss_synchro.Signal, 2) == std::string("1G")) || (std::string(gnss_synchro.Signal, 2) == std::string("2G")))) { store_valid_observable = true; } @@ -2099,12 +2111,12 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item if (!d_osnma_strict && tmp_eph_iter_bds_dnav != d_internal_pvt_solver->beidou_dnav_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_bds_dnav->second.PRN; - if ((prn_aux == in[i][epoch].PRN) && (((std::string(in[i][epoch].Signal, 2) == std::string("B1")) || (std::string(in[i][epoch].Signal, 2) == std::string("B3"))) && (d_use_unhealthy_sats || (tmp_eph_iter_bds_dnav->second.SV_health == 0)))) + if ((prn_aux == gnss_synchro.PRN) && (((std::string(gnss_synchro.Signal, 2) == std::string("B1")) || (std::string(gnss_synchro.Signal, 2) == std::string("B3"))) && (d_use_unhealthy_sats || (tmp_eph_iter_bds_dnav->second.SV_health == 0)))) { store_valid_observable = true; } } - if (std::string(in[i][epoch].Signal, 2) == std::string("E6")) + if (std::string(gnss_synchro.Signal, 2) == std::string("E6")) { if (d_osnma_strict) { @@ -2119,7 +2131,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item if (store_valid_observable) { // store valid observables in a map. - d_gnss_observables_map.insert(std::pair(i, in[i][epoch])); + d_gnss_observables_map.insert(std::pair(i, gnss_synchro)); } if (d_rtcm_enabled) @@ -2130,28 +2142,28 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item { if (tmp_eph_iter_gps != d_internal_pvt_solver->gps_ephemeris_map.cend()) { - d_rtcm_printer->lock_time(d_internal_pvt_solver->gps_ephemeris_map.find(in[i][epoch].PRN)->second, in[i][epoch].RX_time, in[i][epoch]); // keep track of locking time + d_rtcm_printer->lock_time(d_internal_pvt_solver->gps_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time } } if (d_internal_pvt_solver->galileo_ephemeris_map.empty() == false) { if (tmp_eph_iter_gal != d_internal_pvt_solver->galileo_ephemeris_map.cend()) { - d_rtcm_printer->lock_time(d_internal_pvt_solver->galileo_ephemeris_map.find(in[i][epoch].PRN)->second, in[i][epoch].RX_time, in[i][epoch]); // keep track of locking time + d_rtcm_printer->lock_time(d_internal_pvt_solver->galileo_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time } } if (d_internal_pvt_solver->gps_cnav_ephemeris_map.empty() == false) { if (tmp_eph_iter_cnav != d_internal_pvt_solver->gps_cnav_ephemeris_map.cend()) { - d_rtcm_printer->lock_time(d_internal_pvt_solver->gps_cnav_ephemeris_map.find(in[i][epoch].PRN)->second, in[i][epoch].RX_time, in[i][epoch]); // keep track of locking time + d_rtcm_printer->lock_time(d_internal_pvt_solver->gps_cnav_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time } } if (d_internal_pvt_solver->glonass_gnav_ephemeris_map.empty() == false) { if (tmp_eph_iter_glo_gnav != d_internal_pvt_solver->glonass_gnav_ephemeris_map.cend()) { - d_rtcm_printer->lock_time(d_internal_pvt_solver->glonass_gnav_ephemeris_map.find(in[i][epoch].PRN)->second, in[i][epoch].RX_time, in[i][epoch]); // keep track of locking time + d_rtcm_printer->lock_time(d_internal_pvt_solver->glonass_gnav_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time } } } diff --git a/src/algorithms/PVT/libs/rtklib_solver.cc b/src/algorithms/PVT/libs/rtklib_solver.cc index e0fa090df..db9f055ad 100644 --- a/src/algorithms/PVT/libs/rtklib_solver.cc +++ b/src/algorithms/PVT/libs/rtklib_solver.cc @@ -1366,7 +1366,7 @@ bool Rtklib_Solver::get_PVT(const std::map &gnss_observables_ case 'G': case 'J': { - const bool is_qzss = (gnss_observables_iter->second.PRN >= 193 && gnss_observables_iter->second.PRN <= 203); + const bool is_qzss = (gnss_observables_iter->second.PRN >= 193 && gnss_observables_iter->second.PRN <= 206); // GPS/QZSS L1 // find the ephemeris for the current SV observation. The SV PRN ID is the map key const std::string gnss_str = is_qzss ? "QZSS" : "GPS"; diff --git a/src/algorithms/libs/qzss_signal_replica.cc b/src/algorithms/libs/qzss_signal_replica.cc index 90256fcf1..65eb20796 100644 --- a/src/algorithms/libs/qzss_signal_replica.cc +++ b/src/algorithms/libs/qzss_signal_replica.cc @@ -31,6 +31,7 @@ struct QzssL1Entry { uint16_t g2_delay_chips; // Provided by ICD, but not applied if using init_g2_octal uint16_t init_g2_octal; // 10-bit value expressed in octal digits in ICD + uint8_t BOC; // L1 C/B BOC(1,1) flag }; @@ -40,36 +41,36 @@ static QzssL1Entry qzss_l1_table(uint32_t prn) switch (prn) { case 193: - return {339, 01050}; + return {339, 01050, 0}; case 194: - return {208, 01607}; + return {208, 01607, 0}; case 195: - return {711, 01747}; + return {711, 01747, 0}; case 196: - return {189, 01305}; + return {189, 01305, 0}; case 197: - return {263, 00540}; + return {263, 00540, 0}; case 198: - return {537, 01363}; // non-standard + return {537, 01363, 0}; // non-standard case 199: - return {663, 00727}; + return {663, 00727, 0}; case 200: - return {942, 00147}; + return {942, 00147, 0}; case 201: - return {173, 01206}; + return {173, 01206, 0}; case 202: - return {900, 01045}; // non-standard + return {900, 01045, 0}; // non-standard case 203: - return {30, 00476}; // L1C/B + return {30, 00476, 1}; // L1C/B case 204: - return {500, 00604}; // L1C/B + return {500, 00604, 1}; // L1C/B case 205: - return {935, 01757}; // L1C/B + return {935, 01757, 1}; // L1C/B case 206: - return {556, 01330}; // L1C/B + return {556, 01330, 1}; // L1C/B default: LOG(WARNING) << "Unsupported QZSS L1 PRN"; - return {0, 0}; + return {0, 0, 0}; } } @@ -119,15 +120,21 @@ static QzssL5Entry qzss_l5_table(uint32_t prn) // ----------------------------------- -// QZSS L1 C/A code generation +// QZSS L1 C/A(B) code generation // ----------------------------------- -// Generates real QZSS L1 C/A code (1023 chips, +/-1) +bool qzss_l1_code_is_boc(uint32_t prn) +{ + return qzss_l1_table(prn).BOC != 0; +} + + +// Generates real QZSS L1 C/A(B) replica (1023 chips, 2 samples per chip, +/-1) void qzss_l1_code_gen_float(own::span dest, uint32_t prn) { - if (dest.size() < QZSS_L1_CODE_LENGTH) + if (dest.size() < QZSS_L1_SAMPLES_PER_CHIP * QZSS_L1_CODE_LENGTH) { - LOG(WARNING) << "QZSS L1 code must be >= 1023 chips"; + LOG(WARNING) << "QZSS L1 code buffer must hold >= " << QZSS_L1_SAMPLES_PER_CHIP * QZSS_L1_CODE_LENGTH << " samples"; return; } @@ -143,15 +150,19 @@ void qzss_l1_code_gen_float(own::span dest, uint32_t prn) g2[s] = static_cast((init >> s) & 0x1); } - // Generate 1023 chips - for (int i = 0; i < QZSS_L1_CODE_LENGTH; ++i) + // Generate 1023 chips, 2 samples per chip: both halves equal for L1 C/A, + // sinBOC(1,1) subchips for L1 C/B + for (int i = 0, j = 0; i < QZSS_L1_CODE_LENGTH; ++i) { const uint8_t g1_out = g1[9]; const uint8_t g2_out = g2[9]; const auto prn_bit = static_cast(g1_out ^ g2_out); // Map 0 -> -1, 1 -> +1 - dest[i] = prn_bit ? 1.0F : -1.0F; + // BOC sequence starts with 1, so invert the first subchip + // when generating L1 C/B code + dest[j++] = (prn_bit ^ entry.BOC) ? 1.0F : -1.0F; + dest[j++] = prn_bit ? 1.0F : -1.0F; // Step G1 const auto g1_fb = static_cast(g1[2] ^ g1[9]); @@ -184,7 +195,7 @@ void qzss_l1_code_gen_complex_sampled( return; } - std::array code{}; + std::array code{}; qzss_l1_code_gen_float(own::span(code.data(), code.size()), prn); const double phase_step = QZSS_L1_CHIP_RATE / static_cast(sampling_freq); @@ -192,9 +203,9 @@ void qzss_l1_code_gen_complex_sampled( for (auto& d : dest) { - int chip = static_cast(code_phase); - chip %= QZSS_L1_CODE_LENGTH; - d = {code[static_cast(chip)], 0.0F}; + auto sample = static_cast(code_phase * QZSS_L1_SAMPLES_PER_CHIP); + sample %= QZSS_L1_SAMPLES_PER_CHIP * QZSS_L1_CODE_LENGTH; + d = {code[static_cast(sample)], 0.0F}; code_phase += phase_step; if (code_phase >= QZSS_L1_CODE_LENGTH) @@ -305,7 +316,7 @@ void qzss_l5i_code_gen_complex_sampled( for (auto& d : dest) { - int chip = static_cast(code_phase); + auto chip = static_cast(code_phase); chip %= QZSS_L5_CODE_LENGTH; d = {code[static_cast(chip)], 0.0F}; @@ -338,7 +349,7 @@ void qzss_l5q_code_gen_complex_sampled( for (auto& d : dest) { - int chip = static_cast(code_phase); + auto chip = static_cast(code_phase); chip %= QZSS_L5_CODE_LENGTH; d = {0.0F, code[static_cast(chip)]}; diff --git a/src/algorithms/libs/qzss_signal_replica.h b/src/algorithms/libs/qzss_signal_replica.h index c0f511a3f..77b8bdb93 100644 --- a/src/algorithms/libs/qzss_signal_replica.h +++ b/src/algorithms/libs/qzss_signal_replica.h @@ -32,10 +32,13 @@ namespace own = gsl_lite; /** \addtogroup Algorithm_libs algorithms_libs * \{ */ -//! Generates complex QZSS L1 C/A code for the desired SV ID, and sampled to specific sampling frequency +//! Returns true if the given QZSS PRN broadcasts the BOC(1,1)-modulated L1 C/B signal (IS-QZSS-PNT-006) +bool qzss_l1_code_is_boc(uint32_t prn); + +//! Generates complex QZSS L1 C/A(B) code for the desired SV ID, and sampled to specific sampling frequency void qzss_l1_code_gen_complex_sampled(own::span> dest, uint32_t prn, int32_t sampling_freq); -//! Generates real QZSS L1 C/A code for the desired SV ID +//! Generates real QZSS L1 C/A(B) code for the desired SV ID (QZSS_L1_SAMPLES_PER_CHIP samples per chip) void qzss_l1_code_gen_float(own::span dest, uint32_t prn); //! Generates complex QZSS L5I code for the desired SV ID, and sampled to specific sampling frequency diff --git a/src/algorithms/libs/rtklib/rtklib.h b/src/algorithms/libs/rtklib/rtklib.h index 03b6effdc..1961994d9 100644 --- a/src/algorithms/libs/rtklib/rtklib.h +++ b/src/algorithms/libs/rtklib/rtklib.h @@ -186,7 +186,7 @@ const int MAXPRNGAL = 36; //!< max satellite PRN numbe const int NSATGAL = (MAXPRNGAL - MINPRNGAL + 1); //!< number of Galileo satellites const int NSYSGAL = 1; -const int MAXPRNQZS = 202; //!< max satellite PRN number of QZSS +const int MAXPRNQZS = 206; //!< max satellite PRN number of QZSS const int MINPRNQZS = 193; //!< min satellite PRN number of QZSS #define ENAQZS #ifdef ENAQZS diff --git a/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc b/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc index 661729128..c6b6c659e 100644 --- a/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc +++ b/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc @@ -523,11 +523,14 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_) d_code_period = QZSS_L1_CA_CODE_PERIOD_S; d_code_chip_rate = QZSS_L1_CHIP_RATE; d_correlation_length_ms = 1; - d_code_samples_per_chip = 1; + // 2 samples per chip: L1 C/A chips duplicated, or L1 C/B sinBOC(1,1) subchips + d_code_samples_per_chip = QZSS_L1_SAMPLES_PER_CHIP; d_code_length_chips = static_cast(QZSS_L1_CODE_LENGTH); // QZSS L1 C/A does not have pilot component nor secondary code d_secondary = false; d_trk_parameters.track_pilot = false; + // BPSK (L1 C/A) correlation function; updated per PRN in start_tracking() + // when the satellite broadcasts the BOC(1,1)-modulated L1 C/B signal d_trk_parameters.slope = 1.0; d_trk_parameters.spc = d_trk_parameters.early_late_space_chips; d_trk_parameters.y_intercept = 1.0; @@ -1013,6 +1016,25 @@ void dll_pll_veml_tracking::start_tracking() else if (d_systemName == "QZSS" && d_signal_type == "J1") { qzss_l1_code_gen_float(d_tracking_code, d_acquisition_gnss_synchro->PRN); + if (qzss_l1_code_is_boc(d_acquisition_gnss_synchro->PRN)) + { + // L1 C/B is BOC(1,1)-modulated: normalize the DLL discriminator + // with the sinBOC(1,1) correlation function, as done for Galileo E1 + d_trk_parameters.slope = static_cast(-CalculateSlopeAbs(&SinBocCorrelationFunction<1, 1>, d_trk_parameters.spc)); + d_trk_parameters.y_intercept = static_cast(GetYInterceptAbs(&SinBocCorrelationFunction<1, 1>, d_trk_parameters.spc)); + if (d_trk_parameters.spc > 0.33F) + { + LOG(WARNING) << "early_late_space_chips = " << d_trk_parameters.spc + << " places the correlators outside the BOC(1,1) main peak while tracking QZSS L1 C/B PRN " + << d_acquisition_gnss_synchro->PRN << "; consider a value below 0.33"; + } + } + else + { + // L1 C/A is BPSK: triangular correlation function + d_trk_parameters.slope = 1.0; + d_trk_parameters.y_intercept = 1.0; + } } else if (d_systemName == "QZSS" && d_signal_type == "J5") { diff --git a/src/core/receiver/gnss_flowgraph.cc b/src/core/receiver/gnss_flowgraph.cc index a147cb04d..a72dbda4f 100644 --- a/src/core/receiver/gnss_flowgraph.cc +++ b/src/core/receiver/gnss_flowgraph.cc @@ -1958,7 +1958,13 @@ void GNSSFlowgraph::priorize_satellites(const std::vectorsecond; gs = Gnss_Signal(visible_satellite.second, signal_str); old_size = available_signals.size(); available_signals.remove(gs); @@ -2096,7 +2102,10 @@ void GNSSFlowgraph::set_signals_list() 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}}, - {"QZSS", {193, 194, 195, 196, 197, 199, 200, 201}}, + // PRNs 198 and 202 are excluded: they are reserved but not assigned to any + // operational satellite (marked as non-standard codes in IS-QZSS-PNT). + // They can still be searched by setting the QZSS.prns configuration option. + {"QZSS", {193, 194, 195, 196, 197, 199, 200, 201, 203, 204, 205, 206}}, }; #if CXX_LESS_THAN_17 for (auto& entry : available_prn_map) @@ -2170,6 +2179,11 @@ void GNSSFlowgraph::set_signals_list() for (const auto& prn : available_prn_map.at(gnss_system_str)) { + if (signal_str == "J5" && prn > QZSS_L5_MAX_PRN) + { + // QZSS L1 C/B PRNs (203-206) do not transmit an L5 signal + continue; + } available_signals.emplace_back(Gnss_Satellite(gnss_system_str, prn), signal_str); } } diff --git a/src/core/system_parameters/gnss_satellite.cc b/src/core/system_parameters/gnss_satellite.cc index 0da89b139..2de21b62c 100644 --- a/src/core/system_parameters/gnss_satellite.cc +++ b/src/core/system_parameters/gnss_satellite.cc @@ -309,7 +309,7 @@ void Gnss_Satellite::set_PRN(uint32_t PRN_) } else if (system == "QZSS") { - if (PRN_ < 193 || PRN_ > 201) + if (PRN_ < 193 || PRN_ > 206) { DLOG(INFO) << "This PRN is not defined"; PRN = 0; diff --git a/src/core/system_parameters/gps_navigation_message.cc b/src/core/system_parameters/gps_navigation_message.cc index e03a593e2..274b4ad83 100644 --- a/src/core/system_parameters/gps_navigation_message.cc +++ b/src/core/system_parameters/gps_navigation_message.cc @@ -36,7 +36,7 @@ Gps_Navigation_Message::Gps_Navigation_Message(LnavSystem system) const std::string sys_str = (d_system == LnavSystem::GPS) ? "GPS" : "QZSS"; uint32_t prn_min = (d_system == LnavSystem::GPS) ? 1 : 193; - uint32_t prn_max = (d_system == LnavSystem::GPS) ? 32 : 202; + uint32_t prn_max = (d_system == LnavSystem::GPS) ? 32 : 206; for (uint32_t i = prn_min; i <= prn_max; ++i) { @@ -623,7 +623,7 @@ Gps_Ephemeris Gps_Navigation_Message::get_ephemeris() const if (d_system == LnavSystem::QZSS) { auto gnss_sat = Gnss_Satellite(); - for (uint32_t i = 193; i < 203; i++) + for (uint32_t i = 193; i < 207; i++) { ephemeris.satelliteBlock[i] = gnss_sat.what_block("QZSS", i); } diff --git a/src/core/system_parameters/qzss.h b/src/core/system_parameters/qzss.h index 0a0ce9de5..c2d06ece8 100644 --- a/src/core/system_parameters/qzss.h +++ b/src/core/system_parameters/qzss.h @@ -34,6 +34,10 @@ static constexpr double QZSS_L1_CA_CODE_PERIOD_S = 0.001; static constexpr double QZSS_L5I_CODE_PERIOD_S = 0.001; static constexpr int QZSS_L1_CODE_LENGTH = 1023; +// The L1 replica is generated with 2 samples per chip, so a single buffer format +// holds either the L1 C/A BPSK chips (each repeated twice) or the L1 C/B +// sinBOC(1,1) subchips, selected per PRN (IS-QZSS-PNT-006) +static constexpr int QZSS_L1_SAMPLES_PER_CHIP = 2; static constexpr int QZSS_L5_CODE_LENGTH = 10230; static constexpr int QZSS_L1_PERIOD_MS = 1; static constexpr int QZSS_L5I_PERIOD_MS = 1; @@ -57,9 +61,22 @@ static constexpr int32_t QZSS_ALMANAC_EPOCH_HEALTH_SV_ID = 51; static constexpr int32_t QZSS_IONO_UTC_WIDE_AREA_SV_ID = 56; static constexpr int32_t QZSS_IONO_UTC_JAPAN_AREA_SV_ID = 61; static constexpr uint32_t QZSS_PRN_OFFSET = 192U; +static constexpr uint32_t QZSS_L5_MAX_PRN = 202U; //!< L5 PRN codes are only defined for PRNs 193-202 (IS-QZSS-PNT, Table 3.2.5-1); L1 C/B PRNs 203-206 have no L5 counterpart static constexpr double QZSS_QZO_ECCENTRICITY_REF = 0.06; static constexpr double QZSS_QZO_INCLINATION_REF = 0.25; + +//! \brief Maps QZSS L1 C/B PRNs to the PRN carrying the satellite's nominal PNT signals. +//! L1 C/B is broadcast with a dedicated PRN (203-206), but the transmitting satellite is +//! identified by the PRN of its nominal signals (RINEX 4.00, Table 6: QZSS PRN to RINEX +//! Satellite Identifier): 203->196 (J04), 204->197 (J05), 205->200 (J08), 206->201 (J09). +//! Returns the input PRN unchanged if it is not an L1 C/B alias; in particular, +//! PRN 202 (J10) broadcasts L1 C/B under its own PRN. +constexpr uint32_t qzss_l1cb_prn_to_nominal_prn(uint32_t prn) +{ + return prn == 203 ? 196 : (prn == 204 ? 197 : (prn == 205 ? 200 : (prn == 206 ? 201 : prn))); +} + /** \} */ /** \} */ diff --git a/tests/unit-tests/system-parameters/qzss_code_generation_test.cc b/tests/unit-tests/system-parameters/qzss_code_generation_test.cc index 1f501b3e5..703f3ffd3 100644 --- a/tests/unit-tests/system-parameters/qzss_code_generation_test.cc +++ b/tests/unit-tests/system-parameters/qzss_code_generation_test.cc @@ -15,6 +15,7 @@ * ----------------------------------------------------------------------------- */ +#include "qzss.h" #include "qzss_signal_replica.h" #include #include @@ -23,8 +24,8 @@ TEST(QzssL1Code, Periodicity) { constexpr uint32_t prn = 193; - std::vector code1(1023); - std::vector code2(1023); + std::vector code1(QZSS_L1_SAMPLES_PER_CHIP * QZSS_L1_CODE_LENGTH); + std::vector code2(QZSS_L1_SAMPLES_PER_CHIP * QZSS_L1_CODE_LENGTH); qzss_l1_code_gen_float(code1, prn); qzss_l1_code_gen_float(code2, prn); @@ -36,42 +37,54 @@ TEST(QzssL1Code, Periodicity) TEST(QzssL1Code, GoldenFirst32Chips) { constexpr uint32_t prn = 193; - std::vector code(1023); + std::vector code(QZSS_L1_SAMPLES_PER_CHIP * QZSS_L1_CODE_LENGTH); qzss_l1_code_gen_float(code, prn); - const float golden[32] = { - -1, 1, 1, 1, -1, 1, -1, 1, - 1, 1, -1, -1, -1, -1, 1, 1, - -1, -1, -1, -1, -1, -1, 1, -1, - 1, -1, -1, -1, -1, -1, -1, 1}; + const float golden[64] = { + -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, + 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, + 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1}; - for (int i = 0; i < 32; ++i) + for (int i = 0; i < 64; ++i) { EXPECT_FLOAT_EQ(code[i], golden[i]); } constexpr uint32_t prn2 = 195; qzss_l1_code_gen_float(code, prn2); - const float golden2[32] = { - -1, -1, -1, -1, -1, 1, 1, -1, - -1, -1, -1, 1, -1, 1, -1, 1, - 1, 1, 1, 1, -1, -1, 1, 1, - -1, -1, 1, -1, 1, 1, -1, -1}; - for (int i = 0; i < 32; ++i) + const float golden2[64] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, + -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, + -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1}; + for (int i = 0; i < 64; ++i) { EXPECT_FLOAT_EQ(code[i], golden2[i]); } constexpr uint32_t prn3 = 199; qzss_l1_code_gen_float(code, prn3); - const float golden3[32] = { - 1, -1, -1, -1, 1, -1, 1, -1, - -1, -1, -1, -1, 1, -1, -1, -1, - -1, 1, -1, -1, 1, -1, -1, -1, - -1, 1, 1, 1, -1, 1, -1, -1}; - for (int i = 0; i < 32; ++i) + const float golden3[64] = { + 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, + -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, + -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1}; + for (int i = 0; i < 64; ++i) { EXPECT_FLOAT_EQ(code[i], golden3[i]); } + + constexpr uint32_t prn4 = 205; + qzss_l1_code_gen_float(code, prn4); + const float golden4[64] = { + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, + 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, + -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1}; + for (int i = 0; i < 64; ++i) + { + EXPECT_FLOAT_EQ(code[i], golden4[i]); + } }