diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 1fd64c114..01608d8aa 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -208,13 +208,13 @@ PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) { &label_qi, &options_qi, &label_qs, &options_qs, &label_dec, &options_dec}); - options_sr.set_by_value(receiver_model.sampling_rate()); + options_sr.set_value(receiver_model.sampling_rate() / 1000); options_dc.set_by_value(1); - options_qi.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](size_t, OptionsField::value_t v) { receiver_model.set_sampling_rate(v); }; + 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(); }; diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp index 95dbb6c4b..5b04ee843 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -45,7 +45,15 @@ class PralineOptionsView : public View { private: // Layout: SR (Sample Rate), DC (DC Block), QI (Q-Invert), QS (Quarter Shift), D (Decim) Text label_sr{{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "SR"}; - OptionsField options_sr{{UI_POS_X(3), UI_POS_Y(0)}, 4, {{"0.25M", 250000}, {"0.5M", 500000}, {"0.75M", 750000}, {"1.0M", 1000000}, {"1.25M", 1250000}, {"1.5M", 1500000}, {"1.75M", 1750000}, {"2.0M", 2000000}, {"2.25M", 2250000}, {"2.5M", 2500000}, {"2.75M", 2750000}, {"3.0M", 3000000}, {"3.05M", 3050000}, {"3.072M", 3072000}, {"3.10M", 3100000}, {"3.15M", 3150000}, {"3.20M", 3200000}, {"3.25M", 3250000}, {"3.30M", 3300000}, {"3.35M", 3350000}, {"3.40M", 3400000}, {"3.45M", 3450000}, {"3.5M", 3500000}, {"3.51M", 3510000}, {"3.52M", 3520000}, {"3.53M", 3530000}, {"3.54M", 3540000}, {"3.55M", 3550000}, {"3.56M", 3560000}, {"3.57M", 3570000}, {"3.58M", 3580000}, {"3.59M", 3590000}, {"3.60M", 3600000}, {"3.61M", 3610000}, {"3.62M", 3620000}, {"3.63M", 3630000}, {"3.64M", 3640000}, {"3.65M", 3650000}, {"3.66M", 3660000}, {"3.67M", 3670000}, {"3.68M", 3680000}, {"3.69M", 3690000}, {"3.70M", 3700000}, {"3.71M", 3710000}, {"3.72M", 3720000}, {"3.73M", 3730000}, {"3.74M", 3740000}, {"3.75M", 3750000}, {"3.76M", 3760000}, {"3.77M", 3770000}, {"3.78M", 3780000}, {"3.79M", 3790000}, {"3.80M", 3800000}, {"3.81M", 3810000}, {"3.82M", 3820000}, {"3.83M", 3830000}, {"3.84M", 3840000}, {"3.85M", 3850000}, {"3.86M", 3860000}, {"3.87M", 3870000}, {"3.88M", 3880000}, {"3.89M", 3890000}, {"3.90M", 3900000}, {"3.91M", 3910000}, {"3.92M", 3920000}, {"3.93M", 3930000}, {"3.94M", 3940000}, {"3.95M", 3950000}, {"3.96M", 3960000}, {"3.97M", 3970000}, {"3.98M", 3980000}, {"3.99M", 3990000}, {"4.0M", 4000000}, {"5.0M", 5000000}}}; + + // 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 + }; 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}}}; @@ -59,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{0x03}; // Tracks DC, QI, QS bits + uint8_t fpga_reg_1{0x01}; // 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 d3e077486..c0697bf23 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -590,6 +590,187 @@ void PralineRadioDebugView::refresh() { } #endif +#ifdef PRALINE +/* WFMAudioDebugView *************************************************/ + +WFMAudioDebugView::WFMAudioDebugView(NavigationView& nav) + : nav_(nav) { + add_children({ + &text_title, + &text_lbl_clk0, + &text_clk0, + &text_lbl_fpga_dec, + &text_fpga_dec, + &text_lbl_post_fpga, + &text_post_fpga, + &text_section1, + &text_lbl_reg8, + &text_reg8, + &text_lbl_lpf_bw, + &text_lpf_bw, + &text_section2, + &text_lbl_fpga_r1, + &text_fpga_r1, + &text_lbl_dc_q, + &text_dc_q, + &text_section3, + &text_lbl_expected, + &text_expected, + &text_lbl_deemph, + &text_deemph, + &text_status, + &text_status2, + &button_refresh, + &button_toggle_q, + &button_done, + }); + + text_title.set_style(Theme::getInstance()->fg_yellow); + text_section1.set_style(Theme::getInstance()->fg_yellow); + text_section2.set_style(Theme::getInstance()->fg_yellow); + text_section3.set_style(Theme::getInstance()->fg_yellow); + + button_refresh.on_select = [this](Button&) { + refresh(); + }; + + button_toggle_q.on_select = [this](Button&) { + uint32_t current = radio::debug::fpga::register_read(1); + uint8_t new_val = current ^ 0x02; // Toggle Q_INVERT bit + radio::debug::fpga::register_write(1, new_val); + radio::invalidate_spi_config(); + refresh(); + }; + + button_done.on_select = [&nav](Button&) { + nav.pop(); + }; + + refresh(); +} + +void WFMAudioDebugView::focus() { + button_refresh.focus(); +} + +void WFMAudioDebugView::refresh() { + // === Si5351 CLK0 Sample Rate === + // Read MS0 parameters to calculate frequency + uint8_t reg44 = portapack::clock_manager.si5351_read_register(44); + uint8_t reg45 = portapack::clock_manager.si5351_read_register(45); + uint8_t reg46 = portapack::clock_manager.si5351_read_register(46); + + uint8_t r_div_encoded = (reg44 >> 4) & 0x07; + uint32_t r_div = 1 << r_div_encoded; + uint32_t p1 = ((uint32_t)(reg44 & 0x03) << 16) | ((uint32_t)reg45 << 8) | reg46; + uint32_t ms_div = (p1 + 512) / 128; + + // PLL A is 800 MHz + uint32_t clk0_khz = 800000 / ms_div / r_div; + uint32_t clk0_mhz_int = clk0_khz / 1000; + uint32_t clk0_khz_frac = clk0_khz % 1000; + + text_clk0.set(to_string_dec_uint(clk0_mhz_int) + "." + + to_string_dec_uint(clk0_khz_frac / 100) + + to_string_dec_uint((clk0_khz_frac / 10) % 10) + + to_string_dec_uint(clk0_khz_frac % 10) + " MHz"); + + if (clk0_khz >= 3000 && clk0_khz <= 3200) { + text_clk0.set_style(Theme::getInstance()->fg_green); + } else { + text_clk0.set_style(Theme::getInstance()->fg_red); + } + + // === FPGA Decimation === + uint8_t fpga_decim = radio::debug::fpga::register_read(2); + uint32_t fpga_div = 1 << fpga_decim; + text_fpga_dec.set("/" + to_string_dec_uint(fpga_div) + " (n=" + to_string_dec_uint(fpga_decim) + ")"); + + // === Post-FPGA Rate === + uint32_t post_fpga_khz = clk0_khz / fpga_div; + text_post_fpga.set(to_string_dec_uint(post_fpga_khz) + " kHz"); + + // For WFM, post-FPGA should be >= 384 kHz for proper audio decimation + if (post_fpga_khz >= 384) { + text_post_fpga.set_style(Theme::getInstance()->fg_green); + } else { + text_post_fpga.set_style(Theme::getInstance()->fg_orange); + } + + // === MAX2831 LPF === + uint32_t reg8 = radio::debug::second_if::register_read(8); + text_reg8.set("0x" + to_string_hex(reg8, 4)); + + uint8_t lpf_coarse = reg8 & 0x03; + const char* lpf_names[] = {"7.5 MHz", "8.5 MHz", "15 MHz", "18 MHz"}; + text_lpf_bw.set(lpf_names[lpf_coarse]); + + // 7.5 MHz is minimum, OK for mono WFM but tight for stereo + if (lpf_coarse >= 1) { + text_lpf_bw.set_style(Theme::getInstance()->fg_green); + } else { + text_lpf_bw.set_style(Theme::getInstance()->fg_orange); + } + + // === FPGA Control Register === + uint32_t fpga_ctrl = radio::debug::fpga::register_read(1); + text_fpga_r1.set("0x" + to_string_hex(fpga_ctrl, 2)); + + bool dc_block = fpga_ctrl & 0x01; + bool q_invert = fpga_ctrl & 0x02; + uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03; + + text_dc_q.set(std::string(dc_block ? "DC:ON" : "DC:OFF") + + " Q:" + std::string(q_invert ? "INV" : "NOR") + + " QS:" + to_string_dec_uint(quarter_shift)); + + if (dc_block) { + text_dc_q.set_style(Theme::getInstance()->fg_green); + } else { + text_dc_q.set_style(Theme::getInstance()->fg_orange); + } + + // === Expected Audio Rate === + // WFM typically: 3072 kHz / 64 = 48 kHz audio + // Or: 3072 kHz → /8 (channel) → 384 kHz → /8 (audio) → 48 kHz + uint32_t expected_audio = post_fpga_khz / 64; // Simplified assumption + text_expected.set(to_string_dec_uint(expected_audio) + " kHz (est)"); + + if (expected_audio >= 44 && expected_audio <= 50) { + text_expected.set_style(Theme::getInstance()->fg_green); + } else { + text_expected.set_style(Theme::getInstance()->fg_red); + } + + // === 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_style(Theme::getInstance()->fg_orange); + + // === Status Summary === + bool sample_rate_ok = (clk0_khz >= 3000 && clk0_khz <= 3200); + bool lpf_ok = (lpf_coarse >= 0); // 7.5 MHz minimum is technically OK + bool dc_ok = dc_block; + + 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_style(Theme::getInstance()->fg_orange); + } else { + std::string issues = "Issues: "; + if (!sample_rate_ok) issues += "SampleRate "; + if (!lpf_ok) issues += "LPF "; + 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_style(Theme::getInstance()->fg_red); + } +} +#endif + /* BasebandStatusView ******************************************************/ BasebandStatusView::BasebandStatusView(NavigationView& nav) @@ -2823,6 +3004,7 @@ void DebugMenuView::on_populate() { #ifdef PRALINE {"System Diag", 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(); }}, {"ProRadio 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(); }}, {"GPIO Debug", 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 5b4dc6698..2af5fa839 100644 --- a/firmware/application/apps/ui_debug.hpp +++ b/firmware/application/apps/ui_debug.hpp @@ -482,6 +482,69 @@ class PralineRadioDebugView : public View { }; #endif +/* WFMAudioDebugView ***************************************************/ + +#ifdef PRALINE +class WFMAudioDebugView : public View { + public: + WFMAudioDebugView(NavigationView& nav); + void focus() override; + std::string title() const override { return "WFM Audio Debug"; } + + private: + void refresh(); + + NavigationView& nav_; + + Text text_title{{0, 0, 240, 16}, "=== WFM Audio Debug ==="}; + + // Sample rates section + Text text_lbl_clk0{{0, 20, 140, 16}, "Si5351 CLK0 Rate:"}; + Text text_clk0{{150, 20, 90, 16}, "---"}; + + Text text_lbl_fpga_dec{{0, 36, 140, 16}, "FPGA Decimation:"}; + Text text_fpga_dec{{150, 36, 90, 16}, "---"}; + + Text text_lbl_post_fpga{{0, 52, 140, 16}, "Post-FPGA Rate:"}; + Text text_post_fpga{{150, 52, 90, 16}, "---"}; + + // MAX2831 section + Text text_section1{{0, 72, 240, 16}, "--- MAX2831 LPF ---"}; + + Text text_lbl_reg8{{0, 88, 140, 16}, "Reg8 (LPF RX):"}; + Text text_reg8{{150, 88, 90, 16}, "---"}; + + Text text_lbl_lpf_bw{{0, 104, 140, 16}, "LPF Bandwidth:"}; + Text text_lpf_bw{{150, 104, 90, 16}, "---"}; + + // FPGA section + Text text_section2{{0, 124, 240, 16}, "--- FPGA Control ---"}; + + 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}, "---"}; + + // 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_deemph{{0, 208, 140, 16}, "De-emph Config:"}; + Text text_deemph{{150, 208, 90, 16}, "---"}; + + // Status + Text text_status{{0, 228, 240, 16}, "---"}; + Text text_status2{{0, 244, 240, 16}, "---"}; + + Button button_refresh{{10, 268, 70, 24}, "Refresh"}; + Button button_toggle_q{{90, 268, 70, 24}, "Toggle Q"}; + Button button_done{{170, 268, 60, 24}, "Done"}; +}; +#endif + /* BasebandStatusView ***************************************************/ class BasebandStatusView : public View { diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index 2a6bb156c..686ffde86 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -713,7 +713,7 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) { _base_band_frequency = frequency; // Set FPGA decimation to 0 (no decimation) for direct passthrough - fpga_debug_register_write(2, 0); + fpga_debug_register_write(2, 0x00); radio::invalidate_spi_config(); // The following was originally from @kitty. Adopting for testing radio. diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index c84f6b07d..ef9798ebe 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -154,11 +154,26 @@ void init() { baseband_codec.init(); #ifdef PRALINE + + /* Praline-Specific Bus and Gateware Configuration */ + + // SYNC SGPIO TO FPGA CLOCK: + // Configure all 16 SGPIO slices to use the external clock (SGPIO8) + // provided by the FPGA. This allows the MCU to stay at 40MHz + // while the data bus scales to the RF sample rate. + // Bit 2:1 of SGPIO_MUX_CFG = 01 (External clock from SGPIO8) + // SYNC SGPIO TO FPGA CLOCK WITH FALLING EDGE LATCH + for (int i = 0; i < 16; i++) { + // (1 << 1) = External clock from SGPIO8 + // (1 << 3) = Sample on the FALLING edge of the clock + LPC_SGPIO->SGPIO_MUX_CFG[i] = (1 << 1) | (1 << 3); + } + /* 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, 0x03); // DC_BLOCK=1, QUARTER_SHIFT=1, Q_INVERT=0 - fpga_debug_register_write(2, 0x03); // RX_DECIM=8 (2^3 decimation for testing 20 MHz -> 2.5 MHz with audio for now) + fpga_debug_register_write(1, 0x01); // 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 fpga_debug_register_write(5, 0x00); // TX_PSTEP=0 @@ -210,10 +225,6 @@ void set_direction(const rf::Direction new_direction) { #ifdef PRALINE - // This FPGA registers fix DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation. - fpga_debug_register_write(1, 0x03); // DC_BLOCK, Q-Inv, no-QUARTER_SHIFT. - fpga_debug_register_write(2, 0x03); // RX_DECIM=8 (2^3 decimation for testing 20 MHz -> 2.5 MHz with audio for now) - // Q inversion controlled by GPIO0[13] (SGPIO12), not FPGA register bool q_invert = mixer_invert ^ baseband_invert; if (q_invert) { @@ -284,11 +295,6 @@ bool set_tuning_frequency(const rf::Frequency frequency) { mixer_invert = tuning_config.mixer_invert; #ifdef PRALINE - // TEST: Force baseband invert for Praline (like r9) - // baseband_invert = (direction == rf::Direction::Receive); - - // CORRECT: FPGA register 1 only controls DC_BLOCK - fpga_debug_register_write(1, 0x01); // DC_BLOCK only, no QUARTER_SHIFT! // Q inversion controlled by GPIO0[13] (SGPIO12), not FPGA register bool q_invert = mixer_invert ^ baseband_invert; diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index ea9317bf5..29fc070d9 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -48,7 +48,11 @@ 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;