diff --git a/firmware/application/hw/rffc507x.cpp b/firmware/application/hw/rffc507x.cpp index cf5a73c09..10a1c43f7 100644 --- a/firmware/application/hw/rffc507x.cpp +++ b/firmware/application/hw/rffc507x.cpp @@ -98,13 +98,29 @@ size_t divider_log2(const rf::Frequency lo_frequency) { return; } */ - /* Compute LO divider. */ + /* Compute LO divider. + * + * Mirror the reference firmware (hackrf rffc5071.c rffc5071_config_synth()): + * pick the LARGEST divider that keeps the VCO at or below its maximum, i.e. + * the highest VCO frequency in range. The previous rule stopped at the first + * divider that lifted the VCO to or above the minimum, which parks the VCO + * exactly on its 2.7 GHz floor for some LOs (e.g. LO = 675 MHz, used for + * 3.000 GHz TX on PRALINE: 675 * 4 = 2700.0 MHz) where lock is marginal. + * The reference choice for that case is 675 * 8 = 5400 MHz. */ auto lo_divider_log2 = lo::divider_log2_min; auto vco_frequency = lo_frequency; +#ifdef PRALINE + while (((vco_frequency << 1) <= vco::range.maximum) && + (lo_divider_log2 < lo::divider_log2_max)) { + vco_frequency <<= 1; + lo_divider_log2 += 1; + } +#else while (vco::range.below_range(vco_frequency)) { vco_frequency <<= 1; lo_divider_log2 += 1; } +#endif return lo_divider_log2; } @@ -306,6 +322,15 @@ void RFFC507x::set_frequency(const rf::Frequency lo_frequency) { _dirty[Register::P2_FREQ2] = 1; _dirty[Register::P2_FREQ3] = 1; flush(); + + /* Reference rffc5071_set_frequency(): when the part is already enabled, + * request a relock so the new LO is tuned immediately. (radio.cpp normally + * disables the part around set_frequency(), in which case enable() starts a + * fresh calibration and this is a no-op.) */ + if (_map.r.sdi_ctrl.enbl) { + _map.r.pll_ctrl.relok = 1; /* RELOK lives in PLL_CTRL (reg 0x09, bit 3) */ + flush_one(Register::PLL_CTRL); + } } void RFFC507x::set_gpo1(const bool new_value) { diff --git a/firmware/application/rf_path.cpp b/firmware/application/rf_path.cpp index 01b2ad5f1..78dfbedd2 100644 --- a/firmware/application/rf_path.cpp +++ b/firmware/application/rf_path.cpp @@ -76,11 +76,19 @@ void Path::update() { tx_enable.setState(is_tx); - // On the PRALINE board, the mixer is used ONLY on the Low band. - // Since setState() internally handles the active-low (MIX_ENABLE_N) hardware inversion, - // we simply pass 'true' to enable the mixer on Low band, and 'false' for Mid/High bands. + // On the PRALINE board the RFFC5072 mixer is used on BOTH the Low band + // (<2320 MHz, low-side image reject, LPF) and the High band (>2580 MHz, + // high-side image reject, LPF off). It is bypassed only in the Mid window + // (2320-2580 MHz) where the MAX2831 tunes the RF directly. This mirrors the + // reference firmware (hackrf rf_path.c rf_path_set_filter(): LOW_PASS and + // HIGH_PASS both call mixer_enable(); only BYPASS disables it). + // + // setState() handles the active-low MIX_ENABLE_N inversion, so 'true' means + // "mixer enabled". Bypassing the mixer on the High band leaves the MAX2831 + // IF (~2.3-2.7 GHz) at the antenna port instead of the requested RF, which + // made TX (and RX) above 2580 MHz effectively not work. - mix_bypass.setState(band == Band::Low); + mix_bypass.setState(band != Band::Mid); lpf.setState(band == Band::Low); rf_amp_enable.setState(rf_amp_en); diff --git a/firmware/application/tuning.cpp b/firmware/application/tuning.cpp index 27186b2c5..573bc9103 100644 --- a/firmware/application/tuning.cpp +++ b/firmware/application/tuning.cpp @@ -29,7 +29,7 @@ namespace config { // Forward declarations Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit); Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit); -Config high_band(const rf::Frequency target_frequency); +Config high_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit); #ifdef PRALINE /* @@ -392,7 +392,7 @@ Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, c } /* 2580-2740 MHz: above the bypass window, downconvert. */ - return high_band(target_frequency); + return high_band(target_frequency, afe_rate, transmit); #else (void)afe_rate; (void)transmit; @@ -403,37 +403,53 @@ Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, c #endif } -// High band >2740 Mhz (HackRF One) or >2580 MHz (PRALINE): +// High band >2740 Mhz (HackRF One). On PRALINE the high band IF is taken from +// the reference tune tables instead; see high_band() below. +#ifndef PRALINE constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) { -#ifdef PRALINE - // Praline formula tuned for MAX2831 (2.3-2.6 GHz range) - // Keep second_lo in MAX2831's range while allowing RFFC5072 to work - // - // For high-band, we use LOW-side injection: LO = RF - IF - // So IF should be chosen to keep LO (and thus VCO) in a good range - // - // Based on hackrf_usb tune_config_tx patterns: - if (target_frequency < 3600'000'000) - return 2400'000'000 + ((target_frequency - 2740'000'000) / 4); - else if (target_frequency < 5100'000'000) - return 2500'000'000 + ((target_frequency - 3600'000'000) / 6); - else - return 2550'000'000 + ((target_frequency - 5100'000'000) / 10); -#else if (target_frequency < 3600'000'000) return (2170'000'000 + (((target_frequency - 2740'000'000) * 57) / 86)); else if (target_frequency < 5100'000'000) return (2350'000'000 + ((target_frequency - 3600'000'000) / 5)); else return (2500'000'000 + ((target_frequency - 5100'000'000) / 9)); -#endif } +#endif -Config high_band(const rf::Frequency target_frequency) { +Config high_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) { +#ifdef PRALINE + /* radio.c radio_update_frequency(), RF_PATH_FILTER_HIGH_PASS (>2580 MHz): + * the IF comes from the reference tune tables, which keep the MAX2831 + * within 2325-2575 MHz (TX) rather than letting a formula push it past + * ~2.6 GHz, and the first LO is low-side injected: LO = RF - IF. There is + * no spectrum inversion on the high band (hackrf_usb.c radio_changed() + * only inverts for LOW_PASS). The RX table also carries the quarter-rate + * shift, handled the same way as in low_band()/mid_band(). */ + const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit); + + /* Past the end of the table: no usable configuration. */ + if ((entry->rf_range_end_mhz == 0) && (entry->if_mhz == 0)) + return {}; + + const uint8_t shift = (afe_rate == 0) ? 0 : entry->shift; + const rf::Frequency analog_rf = analog_from_digital_rf(target_frequency, shift, afe_rate); + + /* if_mhz == 0 means bypass; the tables never do that above 2580 MHz, but + * fall back to tuning the MAX2831 direct rather than dividing by zero. */ + if (entry->if_mhz == 0) + return {0, analog_rf, rf::path::Band::Mid, false, shift}; + + const rf::Frequency second_lo_frequency = static_cast(entry->if_mhz) * 1'000'000; + const rf::Frequency first_lo_frequency = analog_rf - second_lo_frequency; + return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, false, shift}; +#else + (void)afe_rate; + (void)transmit; const rf::Frequency second_lo_frequency = high_band_second_lo_frequency(target_frequency); const rf::Frequency first_lo_frequency = target_frequency - second_lo_frequency; const bool mixer_invert = false; return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert}; +#endif } Config create(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) { @@ -443,7 +459,7 @@ Config create(const rf::Frequency target_frequency, const uint32_t afe_rate, con } else if (rf::path::band_mid.contains(target_frequency)) { return mid_band(target_frequency, afe_rate, transmit); } else if (rf::path::band_high.contains(target_frequency)) { - return high_band(target_frequency); + return high_band(target_frequency, afe_rate, transmit); } else { return {}; }