mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-12 01:29:29 +00:00
Imrpoved sound quality in analog_audio_app, and added Nyquist protection option to BBW filter for praline. (#3054)
This commit is contained in:
@@ -574,7 +574,11 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
|||||||
const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis);
|
const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis);
|
||||||
receiver_model.set_modulation(modulation);
|
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);
|
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_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.
|
receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
|
||||||
|
|||||||
@@ -750,7 +750,7 @@ void WFMAudioDebugView::refresh() {
|
|||||||
|
|
||||||
// === Status Summary ===
|
// === Status Summary ===
|
||||||
bool sample_rate_ok = (clk0_khz >= 3000 && clk0_khz <= 3200);
|
bool sample_rate_ok = (clk0_khz >= 3000 && clk0_khz <= 3200);
|
||||||
bool lpf_ok = (lpf_coarse >= 0); // 7.5 MHz minimum is technically OK
|
bool lpf_ok = (lpf_coarse <= 0x0F); // check against the 4-bit max
|
||||||
bool dc_ok = dc_block;
|
bool dc_ok = dc_block;
|
||||||
|
|
||||||
if (sample_rate_ok && lpf_ok && dc_ok) {
|
if (sample_rate_ok && lpf_ok && dc_ok) {
|
||||||
@@ -2182,6 +2182,78 @@ void SystemDiagnosticsView::refresh() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PRALINE
|
||||||
|
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}} {
|
||||||
|
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,
|
||||||
|
&button_refresh, &button_done});
|
||||||
|
|
||||||
|
button_refresh.on_select = [this](Button&) { this->refresh(); };
|
||||||
|
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PralineClockDebugView::focus() {
|
||||||
|
button_refresh.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PralineClockDebugView::refresh() {
|
||||||
|
// 1. System Status
|
||||||
|
uint8_t status = portapack::clock_manager.si5351_read_status();
|
||||||
|
bool pll_a = !(status & 0x20);
|
||||||
|
bool pll_b = !(status & 0x40);
|
||||||
|
text_pll_status.set(std::string(pll_a ? "A:OK " : "A:ERR ") + (pll_b ? "B:OK" : "B:ERR"));
|
||||||
|
text_pll_status.set_style((pll_a && pll_b) ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
|
||||||
|
|
||||||
|
// 2. AFE & Decimation Info
|
||||||
|
uint32_t base_rate = portapack::clock_manager.get_sampling_frequency();
|
||||||
|
uint8_t n = portapack::clock_manager.get_resampling_n();
|
||||||
|
text_afe_rate.set(to_string_dec_uint(base_rate << n) + " Hz");
|
||||||
|
text_n_val.set(to_string_dec_uint(n));
|
||||||
|
|
||||||
|
// 3. Clock Table Decoding
|
||||||
|
uint8_t output_en = portapack::clock_manager.si5351_read_register(3);
|
||||||
|
const char* ma_lookup[] = {"2m", "4m", "6m", "8m"};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 6; i++) {
|
||||||
|
uint8_t ctrl = portapack::clock_manager.si5351_read_register(16 + i);
|
||||||
|
|
||||||
|
// mA (Bits 1:0)
|
||||||
|
rows[i].ma->set(ma_lookup[ctrl & 0x03]);
|
||||||
|
|
||||||
|
// Mode (Bit 6: 1=Integer, 0=Fractional)
|
||||||
|
rows[i].mode->set((ctrl & 0x40) ? "INT" : "FRAC");
|
||||||
|
rows[i].mode->set_style((ctrl & 0x40) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_yellow);
|
||||||
|
|
||||||
|
// Phase (Bit 4: 1=Inverted, 0=Normal)
|
||||||
|
// Use 0x10 (Bit 4)
|
||||||
|
rows[i].phase->set((ctrl & 0x10) ? "INVRT" : "NORM ");
|
||||||
|
rows[i].phase->set_style((ctrl & 0x10) ? Theme::getInstance()->fg_orange : Theme::getInstance()->fg_light);
|
||||||
|
|
||||||
|
// Status (Powered On and Output Enabled)
|
||||||
|
bool is_on = !(ctrl & 0x80) && !(output_en & (1 << i));
|
||||||
|
rows[i].stat->set(is_on ? "ON" : "OFF");
|
||||||
|
rows[i].stat->set_style(is_on ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/* GPIODebugView *************************************************/
|
/* GPIODebugView *************************************************/
|
||||||
GPIODebugView::GPIODebugView(NavigationView& nav) {
|
GPIODebugView::GPIODebugView(NavigationView& nav) {
|
||||||
@@ -3003,6 +3075,7 @@ void DebugMenuView::on_populate() {
|
|||||||
add_items({
|
add_items({
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
{"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SystemDiagnosticsView>(); }},
|
{"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SystemDiagnosticsView>(); }},
|
||||||
|
{"PRO Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<ui::PralineClockDebugView>(); }},
|
||||||
{"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RadioDiagnosticsView>(); }},
|
{"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>(); }},
|
{"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>(); }},
|
{"ProRadio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<PralineRadioDebugView>(); }},
|
||||||
|
|||||||
@@ -1125,6 +1125,79 @@ class SystemDiagnosticsView : public View {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PRALINE
|
||||||
|
class PralineClockDebugView : public View {
|
||||||
|
public:
|
||||||
|
PralineClockDebugView(NavigationView& nav);
|
||||||
|
void focus() override;
|
||||||
|
std::string title() const override { return "Pro Clock Status"; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
Text text_title{{0, 0, 240, 16}, "=== Pro Clock Dashboard ==="};
|
||||||
|
|
||||||
|
// System Status
|
||||||
|
Text text_lbl_pll{{0, 20, 80, 16}, "PLL Lock:"};
|
||||||
|
Text text_pll_status{{88, 20, 152, 16}, "---"};
|
||||||
|
|
||||||
|
Text text_lbl_afe{{0, 36, 80, 16}, "AFE Rate:"};
|
||||||
|
Text text_afe_rate{{88, 36, 152, 16}, "---"};
|
||||||
|
|
||||||
|
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"};
|
||||||
|
|
||||||
|
// Helper structure to group row widgets for CLK0-CLK5
|
||||||
|
struct ClockRow {
|
||||||
|
Text* id;
|
||||||
|
Text* ma;
|
||||||
|
Text* mode;
|
||||||
|
Text* phase;
|
||||||
|
Text* stat;
|
||||||
|
};
|
||||||
|
std::vector<ClockRow> rows;
|
||||||
|
|
||||||
|
// 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}, "-"};
|
||||||
|
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 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 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 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 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}, "-"};
|
||||||
|
|
||||||
|
Button button_refresh{{8, 260, 100, 24}, "Refresh"};
|
||||||
|
Button button_done{{132, 260, 100, 24}, "Done"};
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class DebugPeripheralsMenuView : public BtnGridView {
|
class DebugPeripheralsMenuView : public BtnGridView {
|
||||||
|
|||||||
@@ -247,21 +247,20 @@ constexpr ClockControls si5351c_clock_control_common{{
|
|||||||
|
|
||||||
constexpr ClockControls si5351a_clock_control_common{{
|
constexpr ClockControls si5351a_clock_control_common{{
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
// CLK0: MAX5864 (ADC)
|
// CLK0: MAX5864 (ADC) - 4mA, Inverted (Standard for Praline sync)
|
||||||
{ClockControl::ClockCurrentDrive::_4mA, 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_On},
|
||||||
// CLK1: SCT_CLK - iCE40 FPGA timing clock
|
// CLK1: SCT_CLK (iCE40 FPGA) - 6mA, Inverted (Fixes 30-60Hz Drumming)
|
||||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||||
// CLK2: LPC43xx MCU
|
// CLK2: LPC43xx MCU - 4mA, Normal (Must be Integer for MCU stability)
|
||||||
{ClockControl::ClockCurrentDrive::_4mA, 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::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||||
// CLK3: CLKOUT (optional) SMA Port P1
|
// 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_Off},
|
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||||
// CLK4: PRALINE MAX2831 reference (40 MHz) - INVERTED per hackrf_usb, 4mA, Integer mode
|
// CLK4: MAX2831 reference (40 MHz) - Inverted (Required for mixer lock)
|
||||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||||
// CLK5: PRALINE RFFC5072 reference (40 MHz) - INVERTED, 6mA, Integer mode
|
// CLK5: RFFC5072 reference (40 MHz) - Inverted (Required for mixer lock)
|
||||||
// This matches HackRF One OG configuration for RFFC5072
|
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
// CLK6: SMA Port P2 - 8mA, Normal
|
||||||
// CLK6: SMA Port P2
|
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||||
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
|
||||||
#else
|
#else
|
||||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
{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},
|
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_Off},
|
||||||
@@ -274,7 +273,6 @@ constexpr ClockControls si5351a_clock_control_common{{
|
|||||||
// CLK6: Not used
|
// CLK6: Not used
|
||||||
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// CLK7: Not used
|
// CLK7: Not used
|
||||||
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||||
|
|
||||||
@@ -469,7 +467,9 @@ void ClockManager::init_clock_generator() {
|
|||||||
si5351a_clock_control_common[6].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[7].ms_src(ref_pll),
|
||||||
}};
|
}};
|
||||||
clock_generator.set_clock_control(si5351_clock_control);
|
// clock_generator.set_clock_control(si5351_clock_control);
|
||||||
|
// Use single-byte writes instead of multi-byte
|
||||||
|
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||||
#else
|
#else
|
||||||
if (hackrf_r9) {
|
if (hackrf_r9) {
|
||||||
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
||||||
@@ -686,53 +686,41 @@ void ClockManager::disable_if_clocks() {
|
|||||||
|
|
||||||
void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/* PRALINE: CLK0=AFE_CLK runs at sample rate (VCO/divider/2)
|
|
||||||
* CLK1=SCT_CLK runs at 2x sample rate (VCO/divider/1)
|
|
||||||
* Reference: hackrf_core.c sample_rate_frac_set() lines 580-582
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* 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; // Store frequency for StatusViews
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PRALINE sample rate strategy from GSG hackrf_usb radio.c:
|
* PRALINE sample rate strategy:
|
||||||
*
|
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
|
||||||
* 1. Run ADC at the highest rate possible (up to 40 MHz)
|
|
||||||
* 2. Use FPGA decimation to achieve desired output rate
|
* 2. Use FPGA decimation to achieve desired output rate
|
||||||
* 3. This makes the analog LPF effective at rejecting aliases
|
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
|
||||||
* 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 = 40000000; // Use 40 MHz per GSG reference
|
||||||
constexpr uint32_t MAX_AFE_RATE = 20000000;
|
constexpr uint8_t MAX_N = 5;
|
||||||
constexpr uint8_t MAX_N = 5; // Max decimation = 2^5 = 32
|
|
||||||
|
|
||||||
// Calculate optimal decimation factor for RX
|
_base_band_frequency = frequency;
|
||||||
// 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)) {
|
uint8_t n = 0;
|
||||||
afe_rate_x2 <<= 1;
|
uint32_t afe_rate = frequency;
|
||||||
|
|
||||||
|
// Find the largest n where AFE rate stays within limit
|
||||||
|
// Start at n=0 and work up
|
||||||
|
while (n < MAX_N) {
|
||||||
|
uint32_t next_rate = afe_rate << 1;
|
||||||
|
if (next_rate > MAX_AFE_RATE) break;
|
||||||
|
afe_rate = next_rate;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store decimation factor for potential use elsewhere
|
|
||||||
_resampling_n = n;
|
_resampling_n = n;
|
||||||
|
|
||||||
// The actual AFE rate = frequency * 2^n
|
|
||||||
uint32_t afe_rate = frequency << n;
|
|
||||||
|
|
||||||
// Set FPGA RX decimation register
|
// Set FPGA RX decimation register
|
||||||
fpga_debug_register_write(2, n);
|
fpga_debug_register_write(2, n);
|
||||||
radio::invalidate_spi_config();
|
radio::invalidate_spi_config();
|
||||||
|
|
||||||
// Configure Si5351 clocks
|
// Configure Si5351 clocks
|
||||||
clock_generator.set_ms_frequency(0, afe_rate * 4, si5351_vco_f, 2); // CLK0: AFE_CLK
|
// CLK0: AFE_CLK (with r_div=1 for ÷2)
|
||||||
clock_generator.set_ms_frequency(1, afe_rate * 4, si5351_vco_f, 1); // CLK1: SCT_CLK
|
// 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);
|
||||||
#else
|
#else
|
||||||
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
|
/* 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
|
* twice the frequency, and derived from the MS0 synth. So it's only
|
||||||
|
|||||||
@@ -373,6 +373,16 @@ class Si5351 {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PRALINE
|
||||||
|
void set_clock_control_single_byte(const ClockControls& clock_control) {
|
||||||
|
_clock_control = clock_control;
|
||||||
|
// Use single-byte writes for PRALINE (multi-byte I2C fails)
|
||||||
|
for (size_t i = 0; i < 8; i++) {
|
||||||
|
write_register(Register::CLKControl_Base + i, _clock_control[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool plla_loss_of_signal() {
|
bool plla_loss_of_signal() {
|
||||||
return (device_status() >> 5) & 1;
|
return (device_status() >> 5) & 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -589,25 +589,6 @@ init_status_t init() {
|
|||||||
|
|
||||||
clock_manager.init_clock_generator();
|
clock_manager.init_clock_generator();
|
||||||
|
|
||||||
#ifdef PRALINE
|
|
||||||
// Force CLK4/CLK5 configuration BEFORE I2C bus stops
|
|
||||||
// This ensures the inversion bits are written while I2C is still active
|
|
||||||
|
|
||||||
// CLK4 (MAX2831): ON, Integer, PLLA, INVERTED, MS_Self, 4mA = 0x5D
|
|
||||||
clock_manager.si5351_write_register(20, 0x5D);
|
|
||||||
|
|
||||||
// CLK5 (RFFC5072): ON, Integer, PLLA, INVERTED, MS_Self, 6mA = 0x5E
|
|
||||||
clock_manager.si5351_write_register(21, 0x5E);
|
|
||||||
|
|
||||||
// Enable CLK4 and CLK5 outputs NOW (before I2C stops)
|
|
||||||
uint8_t reg3 = clock_manager.si5351_read_register(3);
|
|
||||||
reg3 &= ~0x30; // Clear bits 4 and 5 to enable
|
|
||||||
clock_manager.si5351_write_register(3, reg3);
|
|
||||||
|
|
||||||
// Wait for clocks to stabilize
|
|
||||||
chThdSleepMilliseconds(10);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
i2c0.stop();
|
i2c0.stop();
|
||||||
|
|
||||||
chThdSleepMilliseconds(10);
|
chThdSleepMilliseconds(10);
|
||||||
|
|||||||
@@ -326,12 +326,23 @@ void ReceiverModel::update_baseband_bandwidth() {
|
|||||||
if (enabled_) {
|
if (enabled_) {
|
||||||
#ifdef PRALINE
|
#ifdef PRALINE
|
||||||
/*
|
/*
|
||||||
* PRALINE LPF bandwidth calculation from GSG hackrf_usb radio.c:
|
* PRALINE LPF bandwidth calculation from GSG hackrf_usb radio.c
|
||||||
*
|
*
|
||||||
* Base: (sample_rate * 3) / 8
|
* The LPF should be set to capture the desired signal bandwidth
|
||||||
* If quarter-shift enabled: add (AFE_rate / 8) * 2
|
* while the FPGA decimation filter handles anti-aliasing.
|
||||||
|
*
|
||||||
|
* For most modes: LPF = (output_sample_rate * 3) / 8
|
||||||
|
* For quarter-shift: add offset for shifted spectrum
|
||||||
|
*
|
||||||
|
* Note: MAX2831 minimum LPF is 11.6 MHz, so for narrow sample rates
|
||||||
|
* the hardware limit applies and FPGA filter does the real work.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
uint32_t sample_rate = sampling_rate();
|
uint32_t sample_rate = sampling_rate();
|
||||||
|
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
|
||||||
|
uint32_t afe_rate = sample_rate << resampling_n;
|
||||||
|
|
||||||
|
// Base LPF: enough to capture desired bandwidth
|
||||||
uint32_t lpf_bandwidth = (sample_rate * 3) / 8;
|
uint32_t lpf_bandwidth = (sample_rate * 3) / 8;
|
||||||
|
|
||||||
// Check if quarter-shift is enabled (FPGA register 1, bits 2-3)
|
// Check if quarter-shift is enabled (FPGA register 1, bits 2-3)
|
||||||
@@ -339,12 +350,23 @@ void ReceiverModel::update_baseband_bandwidth() {
|
|||||||
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
|
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
|
||||||
|
|
||||||
if (quarter_shift != 0) {
|
if (quarter_shift != 0) {
|
||||||
// Get resampling factor from clock manager
|
// Quarter-shift moves spectrum by AFE_rate/4, need wider LPF
|
||||||
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
|
uint32_t offset = afe_rate / 8;
|
||||||
uint32_t offset = (sample_rate << resampling_n) / 8; // AFE_rate / 8
|
|
||||||
lpf_bandwidth += offset * 2;
|
lpf_bandwidth += offset * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For best anti-alias performance, also consider AFE Nyquist
|
||||||
|
// If our calculated LPF is below MAX2831 minimum, it doesn't matter
|
||||||
|
// But if we can set LPF to just below AFE Nyquist, that's optimal
|
||||||
|
uint32_t afe_nyquist = afe_rate / 2;
|
||||||
|
|
||||||
|
// Use the larger of: signal bandwidth requirement OR Nyquist protection
|
||||||
|
// (but MAX2831 driver will clamp to its available settings anyway)
|
||||||
|
if (lpf_bandwidth < afe_nyquist) {
|
||||||
|
// Set LPF close to Nyquist for maximum alias rejection
|
||||||
|
lpf_bandwidth = (afe_nyquist * 9) / 10; // 90% of Nyquist
|
||||||
|
}
|
||||||
|
|
||||||
radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth);
|
radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth);
|
||||||
#else
|
#else
|
||||||
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
|
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
|
||||||
|
|||||||
Reference in New Issue
Block a user