From b2bf0f2459c8ed2963cd49b4ec94b84ed0138360 Mon Sep 17 00:00:00 2001 From: stafur Date: Fri, 27 Feb 2026 23:43:19 -0500 Subject: [PATCH] Legacy Initialization, Analog Audio App, and Aliasing fixes. (#3052) * Added PRO button to analog_audio.cpp for debugging metallic ringing sounds in Audio app. Ensured consitency to 4MHz in analog_audio.cpp * Restored blutooth after loss. * Added several sampling rates to analog_audio for testing. * Ran format-code.sh * Fixed 0x03 mode for DC/Q-INV/Q-SHFT being set at every tuning and sample rate change. This was the root cause for loss of bluetooth before. Sample rate, and frequency can now be changed without need for manually resetting DC/Q-INV/Q-SHIFT settings. Updated method for setting frequncies in praline so that we have more testing options. * Ran format-code.sh and cleaned up stale comments. * Addressed comments, and removed commented line, opting for higher register values, 5E and 5D. Added WFM Debug View to support testing demodulation ringing. * Addressed comments in PR conversation to clean comments and ensure consistency at initializtion accross updated methods and displays. * Ran format-code.sh * Set initial legacy state. Improved readability of clocking initialization settings. Updated set_sampling_frequency, and udpate_bandwidth to set decimation values in the fpga_registers to avoid aliasing in low band frequencies. * Updated clock_manager to use correct clock and clock parameters for audio pll. Ran format-code.sh. --- .../application/apps/analog_audio_app.cpp | 44 ++-- .../application/apps/analog_audio_app.hpp | 2 +- firmware/application/apps/ui_debug.cpp | 6 +- firmware/application/apps/ui_debug.hpp | 12 +- firmware/application/clock_manager.cpp | 190 ++++++++++-------- firmware/application/clock_manager.hpp | 6 + firmware/application/radio.cpp | 4 +- firmware/application/receiver_model.cpp | 50 ++--- firmware/application/receiver_model.hpp | 4 - firmware/baseband/proc_wfm_audio.hpp | 4 - 10 files changed, 179 insertions(+), 143 deletions(-) diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 01608d8aa..cadbc092e 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -208,16 +208,41 @@ PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) { &label_qi, &options_qi, &label_qs, &options_qs, &label_dec, &options_dec}); + // 1. READ the current state from hardware + fpga_reg_1 = radio::debug::fpga::register_read(1); + uint32_t fpga_reg_2 = radio::debug::fpga::register_read(2); + + // 2. INITIALIZE UI WIDGETS based on read bits + options_dc.set_by_value((fpga_reg_1 & 0x01) ? 1 : 0); // Bit 0 + options_qi.set_by_value((fpga_reg_1 & 0x02) ? 1 : 0); // Bit 1 + options_qs.set_by_value((fpga_reg_1 & 0x04) ? 1 : 0); // Bit 2 + options_dec.set_by_value(fpga_reg_2); options_sr.set_value(receiver_model.sampling_rate() / 1000); - options_dc.set_by_value(1); - options_qi.set_by_value(0); - options_qs.set_by_value(0); - options_dec.set_by_value(0); options_sr.on_change = [this](int32_t v) { receiver_model.set_sampling_rate(static_cast(v) * 1000); }; - options_dc.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x01; else fpga_reg_1 &= ~0x01; update_fpga_ctrl(); }; - options_qi.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x02; else fpga_reg_1 &= ~0x02; update_fpga_ctrl(); }; - options_qs.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x04; else fpga_reg_1 &= ~0x04; update_fpga_ctrl(); }; + options_dc.on_change = [this](size_t, OptionsField::value_t v) { + if (v) + fpga_reg_1 |= 0x01; + else + fpga_reg_1 &= ~0x01; + update_fpga_ctrl(); + }; + + options_qi.on_change = [this](size_t, OptionsField::value_t v) { + if (v) + fpga_reg_1 |= 0x02; + else + fpga_reg_1 &= ~0x02; + update_fpga_ctrl(); + }; + + options_qs.on_change = [this](size_t, OptionsField::value_t v) { + if (v) + fpga_reg_1 |= 0x04; + else + fpga_reg_1 &= ~0x04; + update_fpga_ctrl(); + }; options_dec.on_change = [this](size_t, OptionsField::value_t v) { radio::debug::fpga::register_write(2, v); }; } @@ -549,13 +574,8 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) { const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis); receiver_model.set_modulation(modulation); -#ifdef PRALINE - receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 4000000); - receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 2000000); -#else receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000); receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000); -#endif receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app. diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp index 5b04ee843..b2ed584fe 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -67,7 +67,7 @@ class PralineOptionsView : public View { Text label_dec{{UI_POS_X(26), UI_POS_Y(0), UI_POS_WIDTH(1), UI_POS_HEIGHT(1)}, "D"}; OptionsField options_dec{{UI_POS_X(28), UI_POS_Y(0)}, 2, {{" 1", 0}, {" 2", 1}, {" 4", 2}, {" 8", 3}, {"16", 4}}}; - uint8_t fpga_reg_1{0x01}; // Tracks DC, QI, QS bits + uint8_t fpga_reg_1{0x00}; // Tracks DC, QI, QS bits void update_fpga_ctrl(); }; #endif diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp index c0697bf23..2c45798d7 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -745,7 +745,7 @@ void WFMAudioDebugView::refresh() { // === De-emphasis Status === // We can't directly read the M4 de-emphasis config, but we can indicate what SHOULD be set // 75µs for USA, 50µs for Europe - text_deemph.set("Check M4 config"); + text_deemph.set("Chk M4 cfg."); text_deemph.set_style(Theme::getInstance()->fg_orange); // === Status Summary === @@ -756,7 +756,7 @@ void WFMAudioDebugView::refresh() { if (sample_rate_ok && lpf_ok && dc_ok) { text_status.set("Hardware config looks OK."); text_status.set_style(Theme::getInstance()->fg_green); - text_status2.set("If ringy: Check de-emphasis in M4!"); + text_status2.set("If ringy: Chk d-emph in M4!"); text_status2.set_style(Theme::getInstance()->fg_orange); } else { std::string issues = "Issues: "; @@ -765,7 +765,7 @@ void WFMAudioDebugView::refresh() { if (!dc_ok) issues += "DC_Block "; text_status.set(issues); text_status.set_style(Theme::getInstance()->fg_red); - text_status2.set("Fix above before checking audio."); + text_status2.set("Fix before checking audio."); text_status2.set_style(Theme::getInstance()->fg_red); } } diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp index 2af5fa839..9c1ed5a00 100644 --- a/firmware/application/apps/ui_debug.hpp +++ b/firmware/application/apps/ui_debug.hpp @@ -523,17 +523,17 @@ class WFMAudioDebugView : public View { Text text_lbl_fpga_r1{{0, 140, 140, 16}, "Reg1 (Ctrl):"}; Text text_fpga_r1{{150, 140, 90, 16}, "---"}; - Text text_lbl_dc_q{{0, 156, 140, 16}, "DC/Q/QS:"}; - Text text_dc_q{{150, 156, 90, 16}, "---"}; + Text text_lbl_dc_q{{0, 156, 70, 16}, "DC/Q/QS:"}; + Text text_dc_q{{72, 156, 168, 16}, "---"}; // Audio section Text text_section3{{0, 176, 240, 16}, "--- Audio Path ---"}; - Text text_lbl_expected{{0, 192, 140, 16}, "Expected Audio:"}; - Text text_expected{{150, 192, 90, 16}, "---"}; + Text text_lbl_expected{{0, 192, 96, 16}, "Expctd Aud:"}; + Text text_expected{{98, 192, 142, 16}, "---"}; - Text text_lbl_deemph{{0, 208, 140, 16}, "De-emph Config:"}; - Text text_deemph{{150, 208, 90, 16}, "---"}; + Text text_lbl_deemph{{0, 208, 96, 16}, "D-emph Cfg:"}; + Text text_deemph{{98, 208, 132, 16}, "---"}; // Status Text text_status{{0, 228, 240, 16}, "---"}; diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index 686ffde86..3701877a4 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -109,6 +109,15 @@ constexpr si5351::MultisynthFractional si5351_ms_0_20m { }; constexpr auto si5351_ms_0_20m_reg = si5351_ms_0_20m.reg(0); */ + +constexpr si5351::MultisynthFractional si5351_ms_0_4m{ + .f_src = si5351_vco_f, // 800,000,000 Hz + .a = 100, // Integer divider 100 + .b = 0, + .c = 1, + .r_div = 1 // Final R-divider: 2^1 = 2 +}; + constexpr si5351::MultisynthFractional si5351_ms_0_8m{ .f_src = si5351_vco_f, .a = 50, @@ -398,12 +407,35 @@ void ClockManager::init_clock_generator() { #endif #ifdef PRALINE - /* PRALINE uses Si5351A with: - * CLK0 = AFE_CLK (codec/FPGA sample clock) - * CLK1 = SCT_CLK (FPGA timing clock at 2x sample rate) - * CLK4 = second IF (MAX2831) - * CLK5 = first IF (RFFC5072) - * Uses PLLA on XTAL only (no CLKIN support). + + /* * Praline HackRF Pro Clock Assignments (800 MHz VCO Configuration) + * VCO Frequency: 800,000,000 Hz (Master Reference) + * * CLK0: AFE_CLK (MAX5864 Codec & FPGA ADC Interface) + * - Initialized to: 4,000,000 Hz (si5351_ms_4m) + * - Divider: 100 (Integer), R_DIV: 2 (r_div=1) + * - Note: Defines hardware sample rate. Essential for WFM purity. + * * CLK1: SCT_CLK (iCE40 FPGA System/Timing Clock) + * - Initialized to: 10,000,000 Hz (si5351_ms_10m) + * - Divider: 80 (Integer), R_DIV: 1 (r_div=0) + * - Note: Timing for SGPIO data bus; scales to 2x SR in wideband modes. + * * CLK2: MCU_CLKIN (LPC43xx MCU External Clock Input) + * - Initialized to: 10,000,000 Hz (si5351_ms_10m) + * - Divider: 80 (Integer), R_DIV: 1 (r_div=0) + * - Note: Synchronizes MCU processing to the RF clock tree. + * * CLK3: SG_CLK (Switching Regulator/Internal Logic Sync) + * - Initialized to: 10,000,000 Hz (si5351_ms_10m) + * - Divider: 80 (Integer), R_DIV: 1 (r_div=0) + * - Note: Used for internal FPGA logic/gateware synchronization. + * * CLK4: P_CLK (Peripheral/Expansion Clock) + * - Configured via: si5351c_ms_4_reg + * - Note: Routed to expansion headers for external hardware sync. + * * CLK5: AUX_CLK (Auxiliary reference for secondary logic) + * - Configured via: si5351c_ms_5_reg + * - Note: Provides additional timing flexibility for the iCE40 FPGA. + * * CLK6/7: Unused / Power-Down + * - State: Disabled (si5351a_ms6_7_off_reg) + * - Note: Kept OFF to reduce EMI/RFI near the RF front-end. + * * CLKOUT: Optional external clock output on the header. */ /* Step 1: Write PLL A configuration (800 MHz VCO from 25 MHz XTAL) */ @@ -417,25 +449,13 @@ void ClockManager::init_clock_generator() { } /* Step 2: Write multisynth configurations using single-byte writes */ - clock_generator.write_ms_single_byte(0, si5351_ms_0_8m); // MS0 = divider 50, r_div=1 for 8 MHz - clock_generator.write_ms_single_byte(1, si5351_ms_16m); // MS1 = divider 50, r_div=0 for 16 MHz - - /* CLK4 and CLK5 - use single-byte writes too */ - { - const auto& ms4_regs = si5351c_ms_4_reg; - const uint8_t base_reg = ms4_regs[0]; - for (size_t i = 1; i < ms4_regs.size(); i++) { - clock_generator.write_register(base_reg + i - 1, ms4_regs[i]); - } - } - { - const auto& ms5_regs = si5351c_ms_5_reg; - const uint8_t base_reg = ms5_regs[0]; - for (size_t i = 1; i < ms5_regs.size(); i++) { - clock_generator.write_register(base_reg + i - 1, ms5_regs[i]); - } - } - clock_generator.write(si5351a_ms6_7_off_reg); // MS6/7 off - short write is OK + // These cover all active channels on the Praline board + clock_generator.write_ms_single_byte(0, si5351_ms_0_4m); // CLK0: Codec (4 MHz) + clock_generator.write_ms_single_byte(1, si5351_ms_10m); // CLK1: FPGA Timing (10 MHz) + clock_generator.write_ms_single_byte(2, si5351_ms_10m); // CLK2: MCU Input (10 MHz) + clock_generator.write_ms_single_byte(3, si5351_ms_10m); // CLK3: Logic Sync (10 MHz) + clock_generator.write_ms_single_byte(4, si5351_ms_40m); // CLK4: First IF (40 MHz) + clock_generator.write_ms_single_byte(5, si5351_ms_40m); // CLK5: Second IF (40 MHz) /* Step 3: NOW set clock control registers (AFTER multisynths per HackRF reference) */ const auto ref_pll = ClockControl::MultiSynthSource::PLLA; @@ -489,21 +509,6 @@ void ClockManager::init_clock_generator() { // CRITICAL: Add delay to ensure Si5351 writes complete before I2C bus stops chThdSleepMilliseconds(100); - - // ===== ADD THIS SAFETY BLOCK ===== - // Pre-configure CLK4/CLK5 with correct settings including inversion. - // This ensures the registers have correct values even if enable_if_clocks() - // is not called or fails. The clocks will still be powered off until - // enable_if_clocks() enables the outputs. - - // CLK5: OFF (for now), Integer, PLLA, INVERTED, MS_Self, 6mA = 0xDE - // (Same as 0x5E but with bit 7 set for power off) - clock_generator.write_register(21, 0xDE); - - // CLK4: OFF (for now), Integer, PLLA, INVERTED, MS_Self, 4mA = 0xDD - clock_generator.write_register(20, 0xDD); - // ===== END SAFETY BLOCK ===== - #else // Wait for PLL(s) to lock - with timeout to prevent hang uint8_t device_status_mask = hackrf_r9 @@ -642,27 +647,6 @@ void ClockManager::disable_codec_clocks() { void ClockManager::enable_if_clocks() { #ifdef PRALINE - /* PRALINE: CLK4=MAX2831, CLK5=RFFC5072 - * - * Force-write complete configuration to guarantee correct setup. - * Added force-write to verify correct registers are applying necessary register clock settings. - */ - - // Configure and enable CLK4 (MAX2831) - // Register 20: ON, Integer, PLLA, INVERTED, MS_Self, 4mA = 0x5D - clock_generator.write_register(20, 0x5D); - - // Configure and enable CLK5 (RFFC5072) - CRITICAL! - // Register 21: ON, Integer, PLLA, INVERTED, MS_Self, 6mA = 0x5E - clock_generator.write_register(21, 0x5E); - - // Enable outputs (register 3, bits 4 and 5 = 0) - uint8_t reg3 = clock_generator.read_register(3); - reg3 &= ~0x30; - clock_generator.write_register(3, reg3); - - chThdSleepMilliseconds(10); - /* PRALINE uses CLK5 (first IF) and CLK4 (second IF) */ clock_generator.enable_clock(clock_generator_output_og_first_if); clock_generator.enable_output_mask(1U << clock_generator_output_og_first_if); @@ -710,15 +694,45 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) { /* PRALINE: Match HackRF USB sample_rate_frac_set() * Reference: hackrf_usb radio.c lines 29-91, hackrf_core.c lines 501-685 */ - _base_band_frequency = frequency; + _base_band_frequency = frequency; // Store frequency for StatusViews - // Set FPGA decimation to 0 (no decimation) for direct passthrough - fpga_debug_register_write(2, 0x00); + /* + * PRALINE sample rate strategy from GSG hackrf_usb radio.c: + * + * 1. Run ADC at the highest rate possible (up to 40 MHz) + * 2. Use FPGA decimation to achieve desired output rate + * 3. This makes the analog LPF effective at rejecting aliases + * 4. Re-apply frequency after to reconfigure LPF bandwidth + */ + + // 20 MHz, since GSG reference of 40MHz caused shifts at certain values. + constexpr uint32_t MAX_AFE_RATE = 20000000; + constexpr uint8_t MAX_N = 5; // Max decimation = 2^5 = 32 + + // Calculate optimal decimation factor for RX + // Start with n=1 (minimum decimation of 2) per reference + uint8_t n = 1; + uint32_t afe_rate_x2 = 2 * frequency; + + while ((afe_rate_x2 <= MAX_AFE_RATE) && (n < MAX_N)) { + afe_rate_x2 <<= 1; + n++; + } + + // Store decimation factor for potential use elsewhere + _resampling_n = n; + + // The actual AFE rate = frequency * 2^n + uint32_t afe_rate = frequency << n; + + // Set FPGA RX decimation register + fpga_debug_register_write(2, n); radio::invalidate_spi_config(); - // The following was originally from @kitty. Adopting for testing radio. - clock_generator.set_ms_frequency(0, frequency * 2, si5351_vco_f, 1); // CLK0: r_div=1 (÷2) - clock_generator.set_ms_frequency(1, frequency * 2, si5351_vco_f, 0); // CLK1: r_div=0 (÷1) + // Configure Si5351 clocks + clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_f, 1); // CLK0: AFE_CLK + clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_f, 0); // CLK1: SCT_CLK + #else /* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at * twice the frequency, and derived from the MS0 synth. So it's only @@ -797,27 +811,37 @@ void ClockManager::start_audio_pll() { * 12MHz * 1024 / 25 = 491.52MHz * MSEL=1024, NSEL=25, PSEL=20 */ - cgu::pll0audio::ctrl({ - .pd = 1, - .bypass = 0, - .directi = 0, - .directo = 0, - .clken = 0, - .frm = 0, - .autoblock = 1, - .pllfract_req = 0, - .sel_ext = 1, - .mod_pd = 1, - .clk_sel = cgu::CLK_SEL::XTAL, - }); cgu::pll0audio::mdiv({ - .mdec = 22625UL, // MDEC for MSEL=1024 + .mdec = 22625UL, // Encoded value for MSEL=1024 }); cgu::pll0audio::np_div({ - .pdec = 31, // PSEL=20 - .ndec = 69, // NDEC for NSEL=25 + .pdec = 31, // Encoded value for PSEL=20 + .ndec = 69, // Encoded value for NSEL=25 }); + + cgu::pll0audio::frac({ + .pllfract_ctrl = 0, + }); + + cgu::pll0audio::power_up(); + + // Praline Fix: Wait for lock with a safety timeout + { + uint32_t timeout = 100000; + while (!cgu::pll0audio::is_locked() && timeout > 0) { + timeout--; + } + } + + cgu::pll0audio::clock_enable(); + + /* Route the 12.288MHz PLL to the Base Audio Clock */ + // PD = 0 enables the clock; AUTOBLOCK = 1 prevents glitches during clock switching + LPC_CGU->BASE_AUDIO_CLK.PD = 0; + LPC_CGU->BASE_AUDIO_CLK.AUTOBLOCK = 1; + LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::PLL0AUDIO); + #else cgu::pll0audio::ctrl({ .pd = 1, diff --git a/firmware/application/clock_manager.hpp b/firmware/application/clock_manager.hpp index 999726bec..6d5f7ba05 100644 --- a/firmware/application/clock_manager.hpp +++ b/firmware/application/clock_manager.hpp @@ -77,6 +77,8 @@ class ClockManager { void set_reference_ppb(const int32_t ppb); #ifdef PRALINE + uint8_t get_resampling_n() const { return _resampling_n; } + // Si5351 diagnostic methods uint8_t si5351_read_status() { return clock_generator.device_status(); } uint8_t si5351_read_register(uint8_t reg) { return clock_generator.read_register(reg); } @@ -112,6 +114,10 @@ class ClockManager { ReferenceSource detect_reference_source(); Reference choose_reference(); bool loss_of_signal(); + +#ifdef PRALINE + uint8_t _resampling_n{0}; // Current decimation factor (log2) +#endif }; #endif /*__CLOCK_MANAGER_H__*/ diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index ef9798ebe..53bd77d9d 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -172,7 +172,7 @@ void init() { /* Initialize FPGA registers - DC_BLOCK must be enabled for RX */ // debug::fpga::init(); // These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation. - fpga_debug_register_write(1, 0x01); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0 + fpga_debug_register_write(1, 0x00); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0 fpga_debug_register_write(2, 0x00); // RX_DECIM=No Decim fpga_debug_register_write(3, 0x00); // TX_CTRL=0 fpga_debug_register_write(4, 0x00); // TX_INTRP=0 @@ -512,7 +512,7 @@ void register_write(const size_t register_number, uint32_t value) { void init() { // Initialize FPGA registers after bitstream load // DC_BLOCK (bit 0) must be enabled for RX to work - fpga_debug_register_write(1, 0x01); // CTRL: DC_BLOCK=1 + fpga_debug_register_write(1, 0x00); // CTRL: DC_BLOCK=1 fpga_debug_register_write(2, 0x00); // RX_DECIM: no decimation fpga_debug_register_write(3, 0x00); // TX_CTRL: NCO disabled fpga_debug_register_write(4, 0x00); // TX_INTRP: no interpolation diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index cd4159afb..a99377ca6 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -34,12 +34,6 @@ #include "dsp_iir_config.hpp" #include "utility.hpp" -#ifdef PRALINE -extern "C" { -#include "fpga_bridge.h" -} -#endif - using namespace hackrf::one; using namespace portapack; @@ -312,17 +306,6 @@ void ReceiverModel::update_tuning_frequency() { // TODO: use positive offset if freq < offset. if (enabled_) { radio::set_tuning_frequency(target_frequency() + hidden_offset + tuning_offset()); - -#ifdef PRALINE - /* Praline: Must re-apply baseband filter after frequency change - * Reference: hackrf_usb radio.c radio_set_frequency() - * - * Different frequency ranges may use different quarter-shift modes, - * which affects the required LPF bandwidth. For now we just - * recalculate the filter to be safe. - */ - update_baseband_bandwidth(); -#endif } } @@ -334,20 +317,25 @@ void ReceiverModel::set_hidden_offset(rf::Frequency offset) { void ReceiverModel::update_baseband_bandwidth() { if (enabled_) { #ifdef PRALINE - /* Praline: LPF bandwidth calculation - * Reference: hackrf_usb radio.c radio_set_filter() + /* + * PRALINE LPF bandwidth calculation from GSG hackrf_usb radio.c: * - * LPF = (sample_rate * 3) / 8 - * Plus additional offset if quarter-shift is enabled (not implemented yet) + * Base: (sample_rate * 3) / 8 + * If quarter-shift enabled: add (AFE_rate / 8) * 2 */ - uint32_t lpf_bandwidth = (sampling_rate() * 3) / 8; + uint32_t sample_rate = sampling_rate(); + uint32_t lpf_bandwidth = (sample_rate * 3) / 8; - // For now, quarter-shift is disabled, so no offset added - // When quarter-shift is implemented: - // if (quarter_shift_enabled) { - // uint32_t offset = (sampling_rate() << decimation_n) / 8; - // lpf_bandwidth += offset * 2; - // } + // Check if quarter-shift is enabled (FPGA register 1, bits 2-3) + uint32_t fpga_ctrl = radio::debug::fpga::register_read(1); + uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03; + + if (quarter_shift != 0) { + // Get resampling factor from clock manager + uint8_t resampling_n = portapack::clock_manager.get_resampling_n(); + uint32_t offset = (sample_rate << resampling_n) / 8; // AFE_rate / 8 + lpf_bandwidth += offset * 2; + } radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth); #else @@ -366,6 +354,12 @@ void ReceiverModel::update_sampling_rate() { radio::set_baseband_rate(sampling_rate()); } update_tuning_frequency(); + +#ifdef PRALINE + // GSG reference: re-apply frequency after sample rate change + // This reconfigures LPF bandwidth based on new decimation + update_baseband_bandwidth(); +#endif } void ReceiverModel::update_lna() { diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index 29fc070d9..ea9317bf5 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -48,11 +48,7 @@ class ReceiverModel { struct settings_t { uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum; -#ifdef PRALINE - uint32_t sampling_rate = 4'000'000; -#else uint32_t sampling_rate = 3'072'000; -#endif rf::Frequency frequency_step = 25'000; rf::Frequency frequency_app_override = 0; uint8_t lna_gain_db = 32; diff --git a/firmware/baseband/proc_wfm_audio.hpp b/firmware/baseband/proc_wfm_audio.hpp index 00abd5bc1..a9ebd87d5 100644 --- a/firmware/baseband/proc_wfm_audio.hpp +++ b/firmware/baseband/proc_wfm_audio.hpp @@ -81,11 +81,7 @@ class WidebandFMAudio : public BasebandProcessor { void on_message(const Message* const message) override; private: -#ifdef PRALINE - static constexpr size_t baseband_fs = 2000000; -#else static constexpr size_t baseband_fs = 3072000; -#endif static constexpr auto spectrum_rate_hz = 50.0f; std::array dst{};