Resolved root cause for loss of bluetooth functionality and added more testing options (#3049)

* 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
This commit is contained in:
stafur
2026-02-26 11:16:07 -05:00
committed by GitHub
parent cd7c412aa4
commit 56adca3bf6
7 changed files with 280 additions and 17 deletions
@@ -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<uint32_t>(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(); };
+10 -2
View File
@@ -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
+182
View File
@@ -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<SystemDiagnosticsView>(); }},
{"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RadioDiagnosticsView>(); }},
{"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<WFMAudioDebugView>(); }},
{"ProRadio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<PralineRadioDebugView>(); }},
{"Signal Path", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SignalPathStatusView>(); }},
{"GPIO Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<GPIODebugView>(); }},
+63
View File
@@ -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 {
+1 -1
View File
@@ -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.
+17 -11
View File
@@ -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;
+4
View File
@@ -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;