From 584587d814f9d022dc3b966248177f1721c62aad Mon Sep 17 00:00:00 2001 From: stafur Date: Wed, 4 Mar 2026 02:11:19 -0500 Subject: [PATCH] Resolve "crackling" sounds in analog_audio_app (#3068) * Updated PRALINE audio processing methods to account for potential slip in 25MHz reference clock. Moved all clocks to PLLB except for CLK0 to isolate the ADC from potential EMI and clock spills from the other clocks to CLK0. Added PLL reference to Clock ui_debug display and added debug view for PLLA and PLLB. Updated PRO menu in analog_audio_app to allow for 100Hz resolution in sample rate for finer control of samplerate debugging of crackling noise artifacts. Updated ppb method and set it for account for a potential 49 Hz shift in the PRALINE crystal. Started instatiating methods for synchronized DMA transfers for the spectrogram since the FFT calculations and the LCD data updates appear to be related to the crackling noise. * Added firmware/flash.h to .gitignore. * Changed reference clock for start_audio_pll to 40MHz instead of ~12MHz xtal. Moved clocks around to reflect best audio quality in analog_audio_app at 3.072 MHz sample frequency. * Ran format-code.sh. * Removed ppb in set_sampling_frequency since it created aliasing in analog_audio_app. * Expanded baseband_bandwidth back to 1750000 after testing since audio quality was retained. * Updated comments to address copilot recommendations where acceptable. * Removed firmware/flashsize.h from .gitignore as coordinated with @gullradriel. --- .../application/apps/analog_audio_app.cpp | 13 +- .../application/apps/analog_audio_app.hpp | 23 +- firmware/application/apps/ui_debug.cpp | 180 +++++++++++-- firmware/application/apps/ui_debug.hpp | 135 ++++++++-- firmware/application/clock_manager.cpp | 248 +++++++++++------- firmware/application/hw/si5351.hpp | 10 + firmware/baseband/proc_wfm_audio.cpp | 2 +- 7 files changed, 461 insertions(+), 150 deletions(-) diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 8e7af6e93..0f6a5af52 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -205,8 +205,7 @@ SPECOptionsView::SPECOptionsView( PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) { set_parent_rect(parent_rect); add_children({&label_sr, &options_sr, &label_dc, &options_dc, - &label_qi, &options_qi, &label_qs, &options_qs, - &label_dec, &options_dec}); + &label_qi, &options_qi, &label_qs, &options_qs}); // 1. READ the current state from hardware fpga_reg_1 = radio::debug::fpga::register_read(1); @@ -216,10 +215,9 @@ PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) { 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_sr.set_value(receiver_model.sampling_rate()); - options_sr.on_change = [this](int32_t v) { receiver_model.set_sampling_rate(static_cast(v) * 1000); }; + options_sr.on_change = [this](int32_t v) { receiver_model.set_sampling_rate(static_cast(v)); }; options_dc.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x01; @@ -243,7 +241,6 @@ PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) { 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); }; } void PralineOptionsView::update_fpga_ctrl() { @@ -573,11 +570,7 @@ 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 : 3023000); -#else receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000); -#endif receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000); 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 b2ed584fe..2e1b1ccfb 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -49,23 +49,20 @@ class PralineOptionsView : public View { // Parameters: Position, Length (in chars), Range, Step, and default value NumberField options_sr{ {UI_POS_X(3), UI_POS_Y(0)}, - 5, // Number of digits to show - {0, 40000}, // Range: 0 to 40,000 kHz - 1, // Step: 1 kHz - ' ' // Fix: Single quotes for char + 8, // Number of digits to show + {0, 40000000}, // Range: 0 to 40,000 kHz + 100, // Step: 100 Hz + ' ' // Fix: Single quotes for char }; - Text label_dc{{UI_POS_X(8), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "DC"}; - OptionsField options_dc{{UI_POS_X(11), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}}; + Text label_dc{{UI_POS_X(14), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "DC"}; + OptionsField options_dc{{UI_POS_X(16), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}}; - Text label_qi{{UI_POS_X(14), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QI"}; - OptionsField options_qi{{UI_POS_X(17), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}}; + Text label_qi{{UI_POS_X(20), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QI"}; + OptionsField options_qi{{UI_POS_X(22), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}}; - Text label_qs{{UI_POS_X(20), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QS"}; - OptionsField options_qs{{UI_POS_X(23), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}}; - - 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}}}; + Text label_qs{{UI_POS_X(26), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QS"}; + OptionsField options_qs{{UI_POS_X(28), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}}; uint8_t fpga_reg_1{0x00}; // Tracks DC, QI, QS bits void update_fpga_ctrl(); diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp index 6c9fc2e55..45b132b46 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -1712,8 +1712,9 @@ void Si5351DebugView::refresh_status() { // Expected for 8 MHz: P1=5888 (0x1700), a=50 uint32_t ms_div = (p1 + 512) / 128; // Integer divider value - // Calculate frequency: f_out = 800 MHz / ms_div / r_div - uint32_t freq_khz = 800000 / ms_div / r_div; // Result in kHz + // Calculate frequency: f_out = 800 or 800 MHz / ms_div / r_div + uint32_t vco_khz = (clk0_ctrl & 0x20) ? 800000 : 800000; // PLLB vs PLLA + uint32_t freq_khz = vco_khz / ms_div / r_div; // Result in kHz // Show P1 value and R45 for debugging text_clk0_freq_value.set("F:" + to_string_dec_uint(freq_khz / 1000) + "MHz (P1:" + to_string_hex(p1, 4) + ")"); @@ -2182,6 +2183,145 @@ void SystemDiagnosticsView::refresh() { } #endif +#ifdef PRALINE +/* Si5351PLLADebugView *******************************************************/ + +Si5351PLLADebugView::Si5351PLLADebugView(NavigationView& nav) + : nav_(nav) { + add_children({&text_title, &text_lbl_raw, &text_r26_27, &text_r28_30, &text_r31_33, + &text_lbl_decoded, &text_lbl_p1, &text_p1, &text_lbl_p2, &text_p2, + &text_lbl_p3, &text_p3, &text_lbl_calc, &text_lbl_mult, &text_mult, + &text_lbl_vco, &text_vco, &text_status, &button_refresh, &button_done}); + + text_title.set_style(Theme::getInstance()->fg_yellow); + text_lbl_raw.set_style(Theme::getInstance()->fg_yellow); + text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow); + text_lbl_calc.set_style(Theme::getInstance()->fg_yellow); + + button_refresh.on_select = [this](Button&) { refresh(); }; + button_done.on_select = [&nav](Button&) { nav.pop(); }; + + refresh(); +} + +void Si5351PLLADebugView::focus() { + button_refresh.focus(); +} + +void Si5351PLLADebugView::refresh() { + // Read PLL A parameters + uint8_t r26 = portapack::clock_manager.si5351_read_register(26); + uint8_t r27 = portapack::clock_manager.si5351_read_register(27); + uint8_t r28 = portapack::clock_manager.si5351_read_register(28); + uint8_t r29 = portapack::clock_manager.si5351_read_register(29); + uint8_t r30 = portapack::clock_manager.si5351_read_register(30); + uint8_t r31 = portapack::clock_manager.si5351_read_register(31); + uint8_t r32 = portapack::clock_manager.si5351_read_register(32); + uint8_t r33 = portapack::clock_manager.si5351_read_register(33); + + // Display Raw registers + text_r26_27.set("R26-27 (P3 LO): " + to_string_hex(r26, 2) + " " + to_string_hex(r27, 2)); + text_r28_30.set("R28-30 (P1): " + to_string_hex(r28, 2) + " " + to_string_hex(r29, 2) + " " + to_string_hex(r30, 2)); + text_r31_33.set("R31-33 (P3H|P2):" + to_string_hex(r31, 2) + " " + to_string_hex(r32, 2) + " " + to_string_hex(r33, 2)); + + // Decode parameters + uint32_t pll_p1 = ((uint32_t)(r28 & 0x03) << 16) | ((uint32_t)r29 << 8) | r30; + uint32_t pll_p2 = ((uint32_t)(r31 & 0x0F) << 16) | ((uint32_t)r32 << 8) | r33; + uint32_t pll_p3 = ((uint32_t)(r31 >> 4) << 16) | ((uint32_t)r26 << 8) | r27; + + text_p1.set(to_string_dec_uint(pll_p1) + " (0x" + to_string_hex(pll_p1, 5) + ")"); + text_p2.set(to_string_dec_uint(pll_p2) + " (0x" + to_string_hex(pll_p2, 5) + ")"); + text_p3.set(to_string_dec_uint(pll_p3) + " (0x" + to_string_hex(pll_p3, 5) + ")"); + + // Calculate Multiplier: M = (128 * P3 + P1 * 128 + 512 + P2) / (128 * P3) + // Simplified as: Multiplier = ((P1 + 512) / 128) + (P2 / P3) + uint32_t a = (pll_p1 + 512) / 128; + + if (pll_p3 > 0) { + text_mult.set(to_string_dec_uint(a) + " + " + to_string_dec_uint(pll_p2) + "/" + to_string_dec_uint(pll_p3)); + } else { + text_mult.set("ERR: P3=0"); + } + + // Calculate VCO Frequency (f_vco = f_xtal * Multiplier) + // The HackRF crystal (f_xtal) is 25 MHz. + if (pll_p3 > 0) { + uint64_t vco_num = (uint64_t)pll_p2 + (uint64_t)pll_p3 * (pll_p1 + 512); + uint64_t vco_den = 128ULL * pll_p3; + uint32_t vco_khz = (uint32_t)((25000ULL * vco_num) / vco_den); + + text_vco.set(to_string_dec_uint(vco_khz / 1000) + "." + to_string_dec_uint(vco_khz % 1000, 3) + " MHz"); + + // Si5351 VCO range is 600-900 MHz + if (vco_khz >= 600000 && vco_khz <= 900000) { + text_vco.set_style(Theme::getInstance()->fg_green); + text_status.set("VCO within valid range."); + } else { + text_vco.set_style(Theme::getInstance()->fg_red); + text_status.set("VCO OUT OF RANGE (600-900)!"); + } + } +} +#endif + +#ifdef PRALINE +Si5351PLLBDebugView::Si5351PLLBDebugView(NavigationView& nav) + : nav_(nav) { + add_children({&text_title, + &text_lbl_raw, &text_r34_35, &text_r36_38, &text_r39_41, + &text_lbl_decoded, &text_lbl_p1, &text_p1, &text_lbl_p2, &text_p2, &text_lbl_p3, &text_p3, + &text_lbl_calc, &text_lbl_vco, &text_vco, + &text_status, &button_refresh, &button_done}); + + text_title.set_style(Theme::getInstance()->fg_yellow); + text_lbl_raw.set_style(Theme::getInstance()->fg_yellow); + text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow); + text_lbl_calc.set_style(Theme::getInstance()->fg_yellow); + + button_refresh.on_select = [this](Button&) { refresh(); }; + button_done.on_select = [&nav](Button&) { nav.pop(); }; + refresh(); +} + +void Si5351PLLBDebugView::focus() { + button_refresh.focus(); +} + +void Si5351PLLBDebugView::refresh() { + uint8_t r34 = portapack::clock_manager.si5351_read_register(34); // P3[15:8] + uint8_t r35 = portapack::clock_manager.si5351_read_register(35); // P3[7:0] + uint8_t r36 = portapack::clock_manager.si5351_read_register(36); // P1[17:16] + uint8_t r37 = portapack::clock_manager.si5351_read_register(37); // P1[15:8] + uint8_t r38 = portapack::clock_manager.si5351_read_register(38); // P1[7:0] + uint8_t r39 = portapack::clock_manager.si5351_read_register(39); // P3[19:16] | P2[19:16] + uint8_t r40 = portapack::clock_manager.si5351_read_register(40); // P2[15:8] + uint8_t r41 = portapack::clock_manager.si5351_read_register(41); // P2[7:0] + + text_r34_35.set("R34-35: " + to_string_hex(r34, 2) + " " + to_string_hex(r35, 2)); + text_r36_38.set("R36-38: " + to_string_hex(r36, 2) + " " + to_string_hex(r37, 2) + " " + to_string_hex(r38, 2)); + text_r39_41.set("R39-41: " + to_string_hex(r39, 2) + " " + to_string_hex(r40, 2) + " " + to_string_hex(r41, 2)); + + uint32_t p1 = ((uint32_t)(r36 & 0x03) << 16) | ((uint32_t)r37 << 8) | r38; + uint32_t p2 = ((uint32_t)(r39 & 0x0F) << 16) | ((uint32_t)r40 << 8) | r41; + uint32_t p3 = ((uint32_t)(r39 >> 4) << 16) | ((uint32_t)r34 << 8) | r35; + + text_p1.set(to_string_dec_uint(p1) + " (0x" + to_string_hex(p1, 5) + ")"); + text_p2.set(to_string_dec_uint(p2) + " (0x" + to_string_hex(p2, 5) + ")"); + text_p3.set(to_string_dec_uint(p3) + " (0x" + to_string_hex(p3, 5) + ")"); + + if (p3 > 0) { + uint64_t vco_num = (uint64_t)p2 + (uint64_t)p3 * (p1 + 512); + uint64_t vco_den = 128ULL * p3; + uint32_t vco_khz = (uint32_t)((25000ULL * vco_num) / vco_den); + text_vco.set(to_string_dec_uint(vco_khz / 1000) + "." + to_string_dec_uint(vco_khz % 1000, 3) + " MHz"); + + bool ok = (vco_khz >= 600000 && vco_khz <= 900000); + text_vco.set_style(ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red); + text_status.set(ok ? "PLL B VCO OK" : "VCO OUT OF RANGE!"); + } +} +#endif + #ifdef PRALINE /* Si5351MultiSynthDebugView *************************************************/ @@ -2448,21 +2588,21 @@ void Si5351MultiSynthDebugView::refresh() { PralineClockDebugView::PralineClockDebugView(NavigationView& nav) : View(), rows{ - {&t0_id, &t0_ma, &t0_mode, &t0_ph, &t0_st}, - {&t1_id, &t1_ma, &t1_mode, &t1_ph, &t1_st}, - {&t2_id, &t2_ma, &t2_mode, &t2_ph, &t2_st}, - {&t3_id, &t3_ma, &t3_mode, &t3_ph, &t3_st}, - {&t4_id, &t4_ma, &t4_mode, &t4_ph, &t4_st}, - {&t5_id, &t5_ma, &t5_mode, &t5_ph, &t5_st}} { + {&t0_id, &t0_ma, &t0_mode, &t0_src, &t0_ph, &t0_st}, + {&t1_id, &t1_ma, &t1_mode, &t1_src, &t1_ph, &t1_st}, + {&t2_id, &t2_ma, &t2_mode, &t2_src, &t2_ph, &t2_st}, + {&t3_id, &t3_ma, &t3_mode, &t3_src, &t3_ph, &t3_st}, + {&t4_id, &t4_ma, &t4_mode, &t4_src, &t4_ph, &t4_st}, + {&t5_id, &t5_ma, &t5_mode, &t5_src, &t5_ph, &t5_st}} { add_children({&text_title, &text_lbl_pll, &text_pll_status, &text_lbl_afe, &text_afe_rate, &text_lbl_n, &text_n_val, &text_header, - &t0_id, &t0_ma, &t0_mode, &t0_ph, &t0_st, - &t1_id, &t1_ma, &t1_mode, &t1_ph, &t1_st, - &t2_id, &t2_ma, &t2_mode, &t2_ph, &t2_st, - &t3_id, &t3_ma, &t3_mode, &t3_ph, &t3_st, - &t4_id, &t4_ma, &t4_mode, &t4_ph, &t4_st, - &t5_id, &t5_ma, &t5_mode, &t5_ph, &t5_st, + &t0_id, &t0_ma, &t0_mode, &t0_src, &t0_ph, &t0_st, + &t1_id, &t1_ma, &t1_mode, &t1_src, &t1_ph, &t1_st, + &t2_id, &t2_ma, &t2_mode, &t2_src, &t2_ph, &t2_st, + &t3_id, &t3_ma, &t3_mode, &t3_src, &t3_ph, &t3_st, + &t4_id, &t4_ma, &t4_mode, &t4_src, &t4_ph, &t4_st, + &t5_id, &t5_ma, &t5_mode, &t5_src, &t5_ph, &t5_st, &button_refresh, &button_done}); button_refresh.on_select = [this](Button&) { this->refresh(); }; @@ -2503,6 +2643,10 @@ void PralineClockDebugView::refresh() { rows[i].mode->set((ctrl & 0x40) ? "INT" : "FRAC"); rows[i].mode->set_style((ctrl & 0x40) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_yellow); + // PLL Source (Bit 5: 0=PLLA, 1=PLLB) + rows[i].src->set((ctrl & 0x20) ? "PLLB" : "PLLA"); + rows[i].src->set_style((ctrl & 0x20) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_green); + // Phase (Bit 4: 1=Inverted, 0=Normal) // Use 0x10 (Bit 4) rows[i].phase->set((ctrl & 0x10) ? "INVRT" : "NORM "); @@ -3336,13 +3480,15 @@ void DebugMenuView::on_populate() { } add_items({ #ifdef PRALINE - {"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"MSynth Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"Radio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"Signal Path", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"PLL A Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"PLL B Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"GPIO Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"RFFC Status", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"RFFC Tuning", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp index 8debfa625..0160bb15f 100644 --- a/firmware/application/apps/ui_debug.hpp +++ b/firmware/application/apps/ui_debug.hpp @@ -1125,6 +1125,85 @@ class SystemDiagnosticsView : public View { }; #endif +#ifdef PRALINE +/* Si5351PLLADebugView - Debug fractional parameters for PLL A */ +class Si5351PLLADebugView : public View { + public: + Si5351PLLADebugView(NavigationView& nav); + void focus() override; + std::string title() const override { return "Si5351 PLL A Debug"; }; + + private: + NavigationView& nav_; + void refresh(); + + Text text_title{{0, 0, 240, 16}, "===PLL A Fractional Debug==="}; + + // Raw register values + Text text_lbl_raw{{0, 24, 240, 16}, "---Raw Registers (26-33)---"}; + Text text_r26_27{{8, 40, 224, 16}, "---"}; + Text text_r28_30{{8, 56, 224, 16}, "---"}; + Text text_r31_33{{8, 72, 224, 16}, "---"}; + + // Decoded values + Text text_lbl_decoded{{0, 96, 240, 16}, "---Decoded Parameters---"}; + Text text_lbl_p1{{0, 112, 48, 16}, "P1:"}; + Text text_p1{{50, 112, 190, 16}, "---"}; + Text text_lbl_p2{{0, 128, 48, 16}, "P2:"}; + Text text_p2{{50, 128, 190, 16}, "---"}; + Text text_lbl_p3{{0, 144, 48, 16}, "P3:"}; + Text text_p3{{50, 144, 190, 16}, "---"}; + + // Calculated VCO + Text text_lbl_calc{{0, 168, 240, 16}, "---Calculated VCO---"}; + Text text_lbl_mult{{0, 184, 80, 16}, "Multiplier:"}; + Text text_mult{{82, 184, 158, 16}, "---"}; + Text text_lbl_vco{{0, 200, 80, 16}, "VCO Freq:"}; + Text text_vco{{82, 200, 158, 16}, "---"}; + + Text text_status{{0, 224, 240, 32}, ""}; + + Button button_refresh{{8, 280, 100, 24}, "Refresh"}; + Button button_done{{132, 280, 100, 24}, "Done"}; +}; +#endif + +#ifdef PRALINE +class Si5351PLLBDebugView : public View { + public: + Si5351PLLBDebugView(NavigationView& nav); + void focus() override; + std::string title() const override { return "Si5351 PLL B Debug"; }; + + private: + void refresh(); + NavigationView& nav_; + + Text text_title{{0, 0, 240, 16}, "=== PLL B Fractional Debug ==="}; + + Text text_lbl_raw{{0, 24, 240, 16}, "--- Raw Registers (34-41) ---"}; + Text text_r34_35{{8, 40, 224, 16}, "---"}; + Text text_r36_38{{8, 56, 224, 16}, "---"}; + Text text_r39_41{{8, 72, 224, 16}, "---"}; + + Text text_lbl_decoded{{0, 96, 240, 16}, "--- Decoded Parameters ---"}; + Text text_lbl_p1{{0, 112, 48, 16}, "P1:"}; + Text text_p1{{50, 112, 190, 16}, "---"}; + Text text_lbl_p2{{0, 128, 48, 16}, "P2:"}; + Text text_p2{{50, 128, 190, 16}, "---"}; + Text text_lbl_p3{{0, 144, 48, 16}, "P3:"}; + Text text_p3{{50, 144, 190, 16}, "---"}; + + Text text_lbl_calc{{0, 168, 240, 16}, "--- Calculated VCO ---"}; + Text text_lbl_vco{{0, 200, 80, 16}, "VCO Freq:"}; + Text text_vco{{82, 200, 158, 16}, "---"}; + + Text text_status{{0, 224, 240, 32}, ""}; + Button button_refresh{{8, 280, 100, 24}, "Refresh"}; + Button button_done{{132, 280, 100, 24}, "Done"}; +}; +#endif + #ifdef PRALINE /* Si5351MultiSynthDebugView - Debug fractional register values for MS0 */ class Si5351MultiSynthDebugView : public View { @@ -1216,14 +1295,15 @@ class PralineClockDebugView : public View { Text text_lbl_n{{0, 52, 80, 16}, "Decim (n):"}; Text text_n_val{{88, 52, 152, 16}, "-"}; - // Table Header - Text text_header{{0, 72, 240, 16}, "ID mA Mode Phase Stat"}; + // Table Header (Width 240px, 30 characters) + Text text_header{{0, 72, 240, 16}, "ID mA Mode Src Phase Stat"}; // Helper structure to group row widgets for CLK0-CLK5 struct ClockRow { Text* id; Text* ma; Text* mode; + Text* src; // PLL A, or B Text* phase; Text* stat; }; @@ -1231,36 +1311,57 @@ class PralineClockDebugView : public View { // We define the actual widgets for 6 clocks // Note: Layout uses 16px vertical spacing per row - Text t0_id{{0, 88, 24, 16}, "C0:"}; - Text t0_ma{{32, 88, 24, 16}, "-"}; - Text t0_mode{{64, 88, 48, 16}, "-"}; - Text t0_ph{{128, 88, 56, 16}, "-"}; - Text t0_st{{192, 88, 48, 16}, "-"}; + + // Full list of clock row entries (C0 through C5) + // Layout: Y starts at 88, increments by 16 per row. + + // C0: MAX5864 (AFE/Sampling) + Text t0_id{{0, 88, 24, 16}, "C0:"}; // Column 0 + Text t0_ma{{32, 88, 24, 16}, "-"}; // Column 4 (mA) + Text t0_mode{{64, 88, 48, 16}, "-"}; // Column 8 (Mode) + Text t0_src{{112, 88, 32, 16}, "-"}; // Column 14 (Src) + Text t0_ph{{160, 88, 48, 16}, "-"}; // Column 20 (Phase) + Text t0_st{{208, 88, 32, 16}, "-"}; // Column 26 (Stat) + + // C1: iCE40 FPGA Timing Text t1_id{{0, 104, 24, 16}, "C1:"}; Text t1_ma{{32, 104, 24, 16}, "-"}; Text t1_mode{{64, 104, 48, 16}, "-"}; - Text t1_ph{{128, 104, 56, 16}, "-"}; - Text t1_st{{192, 104, 48, 16}, "-"}; + Text t1_src{{112, 104, 32, 16}, "-"}; + Text t1_ph{{160, 104, 48, 16}, "-"}; + Text t1_st{{208, 104, 32, 16}, "-"}; + + // C2: LPC43xx MCU Input Text t2_id{{0, 120, 24, 16}, "C2:"}; Text t2_ma{{32, 120, 24, 16}, "-"}; Text t2_mode{{64, 120, 48, 16}, "-"}; - Text t2_ph{{128, 120, 56, 16}, "-"}; - Text t2_st{{192, 120, 48, 16}, "-"}; + Text t2_src{{112, 120, 32, 16}, "-"}; + Text t2_ph{{160, 120, 48, 16}, "-"}; + Text t2_st{{208, 120, 32, 16}, "-"}; + + // C3: SG_CLK (Switching Regulator/Logic Sync) Text t3_id{{0, 136, 24, 16}, "C3:"}; Text t3_ma{{32, 136, 24, 16}, "-"}; Text t3_mode{{64, 136, 48, 16}, "-"}; - Text t3_ph{{128, 136, 56, 16}, "-"}; - Text t3_st{{192, 136, 48, 16}, "-"}; + Text t3_src{{112, 136, 32, 16}, "-"}; + Text t3_ph{{160, 136, 48, 16}, "-"}; + Text t3_st{{208, 136, 32, 16}, "-"}; + + // C4: P_CLK (Peripheral/Expansion Clock) Text t4_id{{0, 152, 24, 16}, "C4:"}; Text t4_ma{{32, 152, 24, 16}, "-"}; Text t4_mode{{64, 152, 48, 16}, "-"}; - Text t4_ph{{128, 152, 56, 16}, "-"}; - Text t4_st{{192, 152, 48, 16}, "-"}; + Text t4_src{{112, 152, 32, 16}, "-"}; + Text t4_ph{{160, 152, 48, 16}, "-"}; + Text t4_st{{208, 152, 32, 16}, "-"}; + + // C5: AUX_CLK (Auxiliary Reference) Text t5_id{{0, 168, 24, 16}, "C5:"}; Text t5_ma{{32, 168, 24, 16}, "-"}; Text t5_mode{{64, 168, 48, 16}, "-"}; - Text t5_ph{{128, 168, 56, 16}, "-"}; - Text t5_st{{192, 168, 48, 16}, "-"}; + Text t5_src{{112, 168, 32, 16}, "-"}; + Text t5_ph{{160, 168, 48, 16}, "-"}; + Text t5_st{{208, 168, 32, 16}, "-"}; Button button_refresh{{8, 260, 100, 24}, "Refresh"}; Button button_done{{132, 260, 100, 24}, "Done"}; diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index fd19c725c..c3aa25257 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -41,6 +41,10 @@ extern "C" { constexpr uint32_t si5351_vco_f = 800000000; +#ifdef PRALINE +constexpr uint32_t si5351_vco_afe_f = 800000000; // Optimal for 3.072 MHz sample frequencies commonly used by apps +#endif + constexpr si5351::Inputs si5351_inputs{ .f_xtal = si5351_xtal_f, .f_clkin = si5351_clkin_f, @@ -62,8 +66,24 @@ constexpr si5351::PLL si5351_pll_xtal_25m{ .b = 0, .c = 1, }; + +// PLL A registers (Base 26) constexpr auto si5351_pll_a_xtal_reg = si5351_pll_xtal_25m.reg(0); + #ifdef PRALINE +// PLL A: 800 MHz VCO (32x Multiplier for jitter-free 3.072 MHz sampling) +constexpr si5351::PLL si5351_pll_xtal_800m{ + .f_in = si5351_inputs.f_xtal, + .a = 32, + .b = 0, + .c = 1, +}; +constexpr auto si5351_pll_a_800_reg = si5351_pll_xtal_800m.reg(0); // Base 26 + +// PLL B: registers (Base 34) 800 MHz VCO (32x Multiplier for stable Digital/SGPIO bus) +constexpr auto si5351_pll_b_800_reg = si5351_pll_xtal_25m.reg(1); // Base 34 +constexpr auto si5351_pll_b_xtal_reg = si5351_pll_xtal_25m.reg(1); + static_assert(si5351_pll_xtal_25m.f_vco() == si5351_vco_f, "PLL XTAL frequency wrong"); static_assert(si5351_pll_xtal_25m.p1() == 3584, "PLL XTAL P1 wrong"); static_assert(si5351_pll_xtal_25m.p2() == 0, "PLL XTAL P2 wrong"); @@ -99,16 +119,7 @@ constexpr si5351::MultisynthFractional si5351_ms_18m432 { .r_div = 1, }; */ -/* -constexpr si5351::MultisynthFractional si5351_ms_0_20m { - .f_src = si5351_vco_f, - .a = 20, - .b = 0, - .c = 1, - .r_div = 1, -}; -constexpr auto si5351_ms_0_20m_reg = si5351_ms_0_20m.reg(0); -*/ +// 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 @@ -125,6 +136,42 @@ constexpr si5351::MultisynthFractional si5351_ms_0_8m{ .c = 1, .r_div = 1, }; + +#ifdef PRALINE +// (initial 4 MHz from 800 MHz VCO: 800 / 200 = 4) +constexpr si5351::MultisynthFractional si5351_ms_afe_4m{ + .f_src = si5351_vco_afe_f, // 800 MHz + .a = 200, + .b = 0, + .c = 1, + .r_div = 0}; + +// (10 MHz from 800 MHz VCO: 800 / 80 = 10) +constexpr si5351::MultisynthFractional si5351_ms_afe_10m{ + .f_src = si5351_vco_afe_f, // 800 MHz + .a = 80, + .b = 0, + .c = 1, + .r_div = 0}; +// (40 MHz from 800 MHz VCO: 800 / 20 = 40) +constexpr si5351::MultisynthFractional si5351_ms_afe_40m{ + .f_src = si5351_vco_afe_f, // 800 MHz + .a = 20, + .b = 0, + .c = 1, + .r_div = 0}; + +// (20 MHz from 800 MHz VCO: 800 / 40 = 20) +constexpr si5351::MultisynthFractional si5351_ms_20m{ + .f_src = si5351_vco_f, + .a = 40, + .b = 0, + .c = 1, + .r_div = 0, +}; +// constexpr auto si5351_ms_20m_reg = si5351_ms_20m.reg(0); +#endif + constexpr auto si5351c_ms_0_8m_reg = si5351_ms_0_8m.reg(clock_generator_output_og_codec); #ifdef PRALINE @@ -247,20 +294,20 @@ constexpr ClockControls si5351c_clock_control_common{{ constexpr ClockControls si5351a_clock_control_common{{ #ifdef PRALINE - // CLK0: MAX5864 (ADC) - 4mA, Normal (Standard for Praline sync) - {ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_On}, - // CLK1: SCT_CLK (iCE40 FPGA) - 6mA, Inverted (Fixes 30-60Hz Drumming) - {ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_On}, - // CLK2: LPC43xx MCU - 4mA, Normal (Must be Integer for MCU stability) + // CLK0: MAX5864 (ADC) - 4mA, Normal PLLA Integer (Standard for Praline sync) {ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, - // CLK3: CLKOUT SMA Port P1 - 8mA, Normal - {ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, - // CLK4: MAX2831 reference (40 MHz) - Inverted (Required for mixer lock) + // CLK1: SCT_CLK (iCE40 FPGA) - 6mA, PLLA Normal Integer + {ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, + // CLK2: LPC43xx MCU - 4mA, Normal PLLB (Must be Integer for MCU stability) + {ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, + // CLK3: CLKOUT SMA Port P1 - 8mA, Normal PLLB Integer + {ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, + // CLK4: MAX2831 reference (40 MHz) - Inverted PLLA Integer (Required for mixer lock) {ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, - // CLK5: RFFC5072 reference (40 MHz) - Inverted (Required for mixer lock) + // CLK5: RFFC5072 reference (40 MHz) - Inverted PLLA Integer (Required for mixer lock) {ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, - // CLK6: SMA Port P2 - 8mA, Normal - {ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On}, + // CLK6: Not used (disabled) 2mA, Normal PLLB, Power_Off + {ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off}, #else {ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off}, {ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_Off}, @@ -409,26 +456,16 @@ void ClockManager::init_clock_generator() { /* * 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) @@ -436,36 +473,44 @@ void ClockManager::init_clock_generator() { * * CLKOUT: Optional external clock output on the header. */ - /* Step 1: Write PLL A configuration (800 MHz VCO from 25 MHz XTAL) */ + /* Step 1: Write PLL A (800 MHz for RF) and PLL B (800 MHz for Digital) */ /* Use single-byte writes to debug I2C issues */ { - const auto& pll_regs = si5351_pll_a_xtal_reg; - const uint8_t base_reg = pll_regs[0]; - for (size_t i = 1; i < pll_regs.size(); i++) { - clock_generator.write_register(base_reg + i - 1, pll_regs[i]); + // Write PLLA (Registers 26-33) + /* Write PLL A configuration (Base 26) */ + const auto& pll_a = si5351_pll_a_800_reg; + for (size_t i = 1; i < pll_a.size(); i++) { + clock_generator.write_register(pll_a[0] + i - 1, pll_a[i]); + } + + // Write PLLB (Registers 34-41) + const auto& pll_b = si5351_pll_b_800_reg; + for (size_t i = 1; i < pll_b.size(); i++) { + clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]); } } /* Step 2: Write multisynth configurations using single-byte writes */ // 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) + clock_generator.write_ms_single_byte(0, si5351_ms_afe_40m); // CLK0: PLL A Codec (40 MHz) + clock_generator.write_ms_single_byte(1, si5351_ms_afe_40m); // CLK1: PLL A FPGA Timing (40 MHz) + clock_generator.write_ms_single_byte(2, si5351_ms_40m); // CLK2: PLL B MCU Input (40 MHz) + clock_generator.write_ms_single_byte(3, si5351_ms_10m); // CLK3: PLL B Logic Sync (10 MHz) + clock_generator.write_ms_single_byte(4, si5351_ms_afe_40m); // CLK4: PLL A Second IF (40 MHz) + clock_generator.write_ms_single_byte(5, si5351_ms_afe_40m); // CLK5: PLL A First IF (40 MHz) /* Step 3: NOW set clock control registers (AFTER multisynths per HackRF reference) */ - const auto ref_pll = ClockControl::MultiSynthSource::PLLA; + const auto ref_pll_a = ClockControl::MultiSynthSource::PLLA; + const auto ref_pll_b = ClockControl::MultiSynthSource::PLLB; const ClockControls si5351_clock_control = ClockControls{{ - si5351a_clock_control_common[0].ms_src(ref_pll), - si5351a_clock_control_common[1].ms_src(ref_pll), - si5351a_clock_control_common[2].ms_src(ref_pll), - si5351a_clock_control_common[3].ms_src(ref_pll), - si5351a_clock_control_common[4].ms_src(ref_pll), - si5351a_clock_control_common[5].ms_src(ref_pll), - si5351a_clock_control_common[6].ms_src(ref_pll), - si5351a_clock_control_common[7].ms_src(ref_pll), + si5351a_clock_control_common[0].ms_src(ref_pll_a), + si5351a_clock_control_common[1].ms_src(ref_pll_a), + si5351a_clock_control_common[2].ms_src(ref_pll_b), + si5351a_clock_control_common[3].ms_src(ref_pll_b), + si5351a_clock_control_common[4].ms_src(ref_pll_a), + si5351a_clock_control_common[5].ms_src(ref_pll_a), + si5351a_clock_control_common[6].ms_src(ref_pll_b), + si5351a_clock_control_common[7].ms_src(ref_pll_b), }}; // clock_generator.set_clock_control(si5351_clock_control); // Use single-byte writes instead of multi-byte @@ -497,8 +542,8 @@ void ClockManager::init_clock_generator() { // Wait for PLL(s) to lock. #ifdef PRALINE - // PRALINE: Wait for PLLA to lock (0x20 = LOL_A bit) - uint8_t device_status_mask = 0x20; + // PRALINE: Wait for 0x60 (0x20 | 0x40), PLLA and PLLB to lock (0x20 = LOL_A bit, 0x40 = LOL_B bit) + uint8_t device_status_mask = 0x60; uint32_t pll_timeout = 100000; while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) { pll_timeout--; @@ -507,8 +552,6 @@ void ClockManager::init_clock_generator() { static volatile uint32_t pll_lock_timeout = pll_timeout; (void)pll_lock_timeout; - // CRITICAL: Add delay to ensure Si5351 writes complete before I2C bus stops - chThdSleepMilliseconds(100); #else // Wait for PLL(s) to lock - with timeout to prevent hang uint8_t device_status_mask = hackrf_r9 @@ -719,8 +762,9 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) { // Configure Si5351 clocks // CLK0: AFE_CLK (with r_div=1 for ÷2) // CLK1: SCT_CLK (with r_div=0 for ÷1, runs at 2× AFE for FPGA timing) - clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_f, 1); - clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_f, 0); + // Configure Si5351 clocks using the correct AFE VCO + clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1); + clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0); #else /* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at @@ -745,7 +789,18 @@ void ClockManager::set_reference_ppb(const int32_t ppb) { if (hackrf_r9 && reference.source != ReferenceSource::Xtal) { return; } + +#ifdef PRALINE + // On Praline, only apply if we aren't locked to a superior external 10MHz source + // (Assuming you have a way to detect the 10MHz presence on Praline) + if (reference.source == ReferenceSource::External) { + return; + } + constexpr uint32_t pll_multiplier = si5351_pll_xtal_800m.a; +#else constexpr uint32_t pll_multiplier = si5351_pll_xtal_25m.a; +#endif + constexpr uint32_t denominator = 1000000 / pll_multiplier; const uint32_t new_a = (ppb >= 0) ? pll_multiplier : (pll_multiplier - 1); const uint32_t new_b = (ppb >= 0) ? (ppb / 1000) : (denominator + (ppb / 1000)); @@ -757,8 +812,14 @@ void ClockManager::set_reference_ppb(const int32_t ppb) { .b = new_b, .c = new_c, }; + +#ifdef PRALINE + clock_generator.write_pll_single_byte(0, pll); + clock_generator.reset_plls(); +#else const auto pll_a_reg = pll.reg(0); clock_generator.write(pll_a_reg); +#endif } void ClockManager::start_frequency_monitor_measurement(const cgu::CLK_SEL clk_sel) { @@ -794,28 +855,28 @@ uint32_t ClockManager::get_frequency_monitor_measurement_in_hertz() { void ClockManager::start_audio_pll() { #ifdef PRALINE - /* PRALINE: Use 12MHz XTAL for audio PLL - * For 12MHz XTAL input, 48kHz audio rate, 256Fs MCLK: - * Fout=12.288MHz, Fcco=491.52MHz - * 12MHz * 1024 / 25 = 491.52MHz - * MSEL=1024, NSEL=25, PSEL=20 - */ - - cgu::pll0audio::mdiv({ - .mdec = 22625UL, // Encoded value for MSEL=1024 - }); - cgu::pll0audio::np_div({ - .pdec = 31, // Encoded value for PSEL=20 - .ndec = 69, // Encoded value for NSEL=25 + // Comprehensive init matches non-PRALINE for stability and source selection + cgu::pll0audio::ctrl({ + .pd = 1, // Start powered down + .bypass = 0, // Use the PLL + .directi = 0, // Enable N-divider + .directo = 0, // Enable P-divider + .clken = 0, // Disable output initially + .frm = 0, // Normal mode + .autoblock = 1, // Glitchless switching + .pllfract_req = 0, // Integer mode + .sel_ext = 1, // MUST BE 1 to use clk_sel GP_CLKIN + .mod_pd = 1, // Power down modulator (Reduces noise/hiss) + .clk_sel = cgu::CLK_SEL::GP_CLKIN, }); - cgu::pll0audio::frac({ - .pllfract_ctrl = 0, - }); + cgu::pll0audio::mdiv({.mdec = 30542UL}); + cgu::pll0audio::np_div({.pdec = 31, .ndec = 45}); + cgu::pll0audio::frac({.pllfract_ctrl = 0}); cgu::pll0audio::power_up(); - // Praline Fix: Wait for lock with a safety timeout + // Safety timeout prevents boot hang if Si5351 clock is missing { uint32_t timeout = 100000; while (!cgu::pll0audio::is_locked() && timeout > 0) { @@ -824,13 +885,10 @@ void ClockManager::start_audio_pll() { } cgu::pll0audio::clock_enable(); + set_base_audio_clock_divider(1); - /* 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); - + LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IDIVD); #else cgu::pll0audio::ctrl({ .pd = 1, @@ -860,30 +918,21 @@ void ClockManager::start_audio_pll() { .pdec = 31, .ndec = 45, }); -#endif cgu::pll0audio::frac({ .pllfract_ctrl = 0, }); cgu::pll0audio::power_up(); -#ifndef PRALINE - while (!cgu::pll0audio::is_locked()); -#else - // PRALINE FIX: Add timeout to prevent infinite hang if GP_CLKIN not present - { - uint32_t timeout = 100000; - while (!cgu::pll0audio::is_locked() && timeout > 0) { - timeout--; - } - } -#endif - cgu::pll0audio::clock_enable(); + while (!cgu::pll0audio::is_locked()); + + cgu::pll0audio::clock_enable(); set_base_audio_clock_divider(1); LPC_CGU->BASE_AUDIO_CLK.AUTOBLOCK = 1; LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IDIVD); +#endif } void ClockManager::set_base_audio_clock_divider(const size_t divisor) { @@ -892,9 +941,24 @@ void ClockManager::set_base_audio_clock_divider(const size_t divisor) { } void ClockManager::stop_audio_pll() { +#ifdef PRALINE + /* PRALINE: Gracefully switch audio peripherals away from the PLL branch */ + LPC_CGU->BASE_AUDIO_CLK.PD = 1; // Power down the branch first + LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IRC); // Reset to safe IRC source +#endif + cgu::pll0audio::clock_disable(); cgu::pll0audio::power_down(); + +#ifdef PRALINE + /* PRALINE: Add a safety timeout to the unlock check to prevent potential hangs */ + uint32_t timeout = 100000; + while (cgu::pll0audio::is_locked() && timeout > 0) { + timeout--; + } +#else while (cgu::pll0audio::is_locked()); +#endif } void ClockManager::enable_clock_output(bool enable) { diff --git a/firmware/application/hw/si5351.hpp b/firmware/application/hw/si5351.hpp index b03a61fb0..c459ac1ac 100644 --- a/firmware/application/hw/si5351.hpp +++ b/firmware/application/hw/si5351.hpp @@ -423,6 +423,7 @@ class Si5351 { #ifdef PRALINE /* Write multisynth config using single-byte writes for debugging */ + void write_ms_single_byte(const size_t ms_number, const MultisynthFractional& config) { const auto regs = config.reg(ms_number); // regs[0] is the base register address, regs[1-8] are the data bytes @@ -431,6 +432,15 @@ class Si5351 { write_register(base_reg + i - 1, regs[i]); } } + + void write_pll_single_byte(const uint8_t pll_n, const PLL& pll_config) { + const auto regs = pll_config.reg(pll_n); + // regs[0] is base register address, regs[1-8] are data + const uint8_t base_reg = regs[0]; + for (size_t i = 1; i < regs.size(); i++) { + write_register(base_reg + i - 1, regs[i]); + } + } #endif void set_ms_frequency( diff --git a/firmware/baseband/proc_wfm_audio.cpp b/firmware/baseband/proc_wfm_audio.cpp index de265aaf5..4f43186d2 100644 --- a/firmware/baseband/proc_wfm_audio.cpp +++ b/firmware/baseband/proc_wfm_audio.cpp @@ -113,13 +113,13 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) { fft_c_preswapped(audio_spectrum, fft_step, fft_step + 1); fft_step++; } else { + constexpr float mag_scale = 5.0f; const size_t spectrum_end = spectrum.db.size(); for (size_t i = 0; i < spectrum_end; i++) { // const auto corrected_sample = spectrum_window_hamming_3(audio_spectrum, i); const auto corrected_sample = audio_spectrum[i]; const auto mag2 = magnitude_squared(corrected_sample * (1.0f / 32768.0f)); const float db = mag2_to_dbv_norm(mag2); - constexpr float mag_scale = 5.0f; const unsigned int v = (db * mag_scale) + 255.0f; spectrum.db[i] = std::max(0U, std::min(255U, v)); }