diff --git a/firmware/application/app_settings.hpp b/firmware/application/app_settings.hpp index 4db5caf35..75cd5c44a 100644 --- a/firmware/application/app_settings.hpp +++ b/firmware/application/app_settings.hpp @@ -33,6 +33,7 @@ #include #include "file.hpp" +#include "hackrf_hal.hpp" #include "max283x.hpp" #include "string_format.hpp" @@ -134,7 +135,7 @@ struct AppSettings { uint8_t vga = 32; uint8_t rx_amp = 0; uint8_t tx_amp = 0; - uint8_t tx_gain = 35; + uint8_t tx_gain = hackrf::one::default_tx_gain_db; uint32_t channel_bandwidth = 1; uint32_t rx_frequency; uint32_t tx_frequency; diff --git a/firmware/application/apps/ui_aprs_tx.cpp b/firmware/application/apps/ui_aprs_tx.cpp index 92713a0b7..939ceab12 100644 --- a/firmware/application/apps/ui_aprs_tx.cpp +++ b/firmware/application/apps/ui_aprs_tx.cpp @@ -238,6 +238,7 @@ APRSTXView::APRSTXView(NavigationView& nav) { process_coordinates(lat, lon); }); }; + // process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users } diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index 863b76e33..74396c9bc 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -325,6 +325,12 @@ static constexpr ClockControl::MultiSynthSource get_si5351a_reference_clock_gene ? ClockControl::MultiSynthSource::PLLA : ClockControl::MultiSynthSource::PLLB; } + +static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) { + return (reference_source == ClockManager::ReferenceSource::Xtal) + ? ClockControl::MultiSynthSource::PLLA + : ClockControl::MultiSynthSource::PLLB; +} #else static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) { return (reference_source == ClockManager::ReferenceSource::Xtal) @@ -424,32 +430,22 @@ using namespace hackrf::one; void ClockManager::init_clock_generator() { #ifdef PRALINE - // thoese PIN can review platform_scu file - // have many conflict and modify for clock init - // P2_10 -> GPIO0[14] P1_CTRL0 + /* Route the PRALINE P22 10MHz reference to the Si5351 CLKIN pin. */ LPC_SCU->SFSP[2][10] = 0xF0; - // P6_8 -> GPIO5[16] P1_CTRL1 LPC_SCU->SFSP[6][8] = 0xF4; - // P6_9 -> GPIO3[5] P1_CTRL2 LPC_SCU->SFSP[6][9] = 0xF0; - // P1_20 -> GPIO0[15] CLKIN_CTRL LPC_SCU->SFSP[1][20] = 0xF0; - constexpr GPIO gpio_p1_ctrl0 = gpio[GPIO0_14]; - constexpr GPIO gpio_p1_ctrl1 = gpio[GPIO5_16]; - constexpr GPIO gpio_p1_ctrl2 = gpio[GPIO3_5]; - constexpr GPIO gpio_clkin_ctrl = gpio[GPIO0_15]; - gpio_p1_ctrl0.output(); gpio_p1_ctrl1.output(); gpio_p1_ctrl2.output(); gpio_clkin_ctrl.output(); - // P1 control = 100 and choose P22 clock external clock on portapack gpio_p1_ctrl0.write(0); gpio_p1_ctrl1.write(0); gpio_p1_ctrl2.write(1); gpio_clkin_ctrl.write(1); + chThdSleepMilliseconds(20); #else @@ -465,35 +461,45 @@ void ClockManager::init_clock_generator() { clock_generator.enable_fanout(); #ifdef PRALINE - /* PRALINE has Si5351A (NOT Si5351C like HackRF One OG). - * Must use Si5351A configuration: PLLA only, no CLKIN support. - * - * IMPORTANT: Follow HackRF reference sequence: - * 1. Set PLL input sources - * 2. Configure PLL and multisynths - * 3. Set clock control registers (AFTER multisynths!) - * 4. Reset PLLs - * 5. Enable outputs - */ - // change there detect method same as hackrf one - // PLLA -> XTAL,PLLB -> CLKIN - clock_generator.set_pll_input_sources(si5351c_pll_input_sources); - // although the name a and the branch define CLK2 MCU CLOCK - // only use CLK2 - auto si5351_clock_control_common = si5351a_clock_control_common; constexpr size_t clock_generator_output_pro_mcu_clkin = 2; - // clk_src define CLKIN + auto si5351_clock_control_common = si5351a_clock_control_common; + + /* + * Match the HackRF One init flow: + * 1. PLLA stays on the local 25MHz crystal. + * 2. PLLB is configured from the Si5351 CLKIN pin. + * 3. CLK2 temporarily drives GP_CLKIN directly from CLKIN so we can + * detect whether P22 is carrying a valid 10MHz reference. + * 4. Once the source is chosen, all runtime clocks are switched to the + * selected PLL and the working multisynths are programmed. + */ + clock_generator.set_pll_input_sources(si5351c_pll_input_sources); + clock_generator.set_clock_control( clock_generator_output_pro_mcu_clkin, si5351_clock_control_common[clock_generator_output_pro_mcu_clkin] .clk_src(ClockControl::ClockSource::CLKIN) .clk_pdn(ClockControl::ClockPowerDown::Power_On)); clock_generator.enable_output(clock_generator_output_pro_mcu_clkin); - chThdSleepMilliseconds(20); - // should be extern icon and 10MHz - reference = choose_reference(); - /* Clock control will be set AFTER multisynth configuration - see below */ + chThdSleepMilliseconds(20); + reference = choose_reference(); + clock_generator.disable_output(clock_generator_output_pro_mcu_clkin); + + const auto ref_pll = + get_si5351c_reference_clock_generator_pll(reference.source); + + const ClockControls si5351_clock_control = ClockControls{{ + si5351_clock_control_common[0].ms_src(ref_pll), + si5351_clock_control_common[1].ms_src(ref_pll), + si5351_clock_control_common[2].ms_src(ref_pll), + si5351_clock_control_common[3].ms_src(ref_pll), + si5351_clock_control_common[4].ms_src(ref_pll), + si5351_clock_control_common[5].ms_src(ref_pll), + si5351_clock_control_common[6].ms_src(ref_pll), + si5351_clock_control_common[7].ms_src(ref_pll), + }}; + clock_generator.set_clock_control_single_byte(si5351_clock_control); #else clock_generator.set_pll_input_sources(hackrf_r9 ? si5351a_pll_input_sources @@ -538,86 +544,50 @@ void ClockManager::init_clock_generator() { #endif #ifdef PRALINE + clock_generator.write_pll_single_byte(0, si5351_pll_a_afe_800m); - /* * 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) - * - Note: Defines hardware sample rate. Essential for WFM purity. - * * CLK1: SCT_CLK (iCE40 FPGA System/Timing Clock) - * - Note: Timing for SGPIO data bus; scales to 2x SR in wideband modes. - * * CLK2: MCU_CLKIN (LPC43xx MCU External Clock Input) - * - Note: Synchronizes MCU processing to the RF clock tree. - * * CLK3: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 1 - * - Note: Used for internal FPGA logic/gateware synchronization. - * * CLK4: P_CLK (MAX2831 Peripheral/Expansion Clock) - * - Note: Routed to expansion headers for external hardware sync. - * * CLK5: AUX_CLK (RFFC5371 Auxiliary reference for secondary logic) - * - Note: Provides additional timing flexibility for the iCE40 FPGA. - * * CLK6: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 2 - * - Note: Used for internal FPGA logic/gateware synchronization. - * * CLK7: Unused / Power-Down - * - State: Disabled (si5351a_ms6_7_off_reg) - * - Note: Kept OFF to reduce EMI/RFI near the RF front-end. - * * CLKOUT: Optional external clock output on the header. - */ - - /* Write PLL A (800 MHz based on 25 MHz xtal for RF) and - * PLL B (800 MHZ based on 25 MHz xtalfor Digital) - * Use single-byte writes to debug I2C issues - */ { - // 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) - /* Write PLL B configuration (Base 34) */ - const auto& pll_b = si5351_pll_b_800_reg; + const auto& pll_b = si5351c_pll_b_clkin_reg; for (size_t i = 1; i < pll_b.size(); i++) { clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]); } } + // CLK0: DAFE_CLK initial 4 MHz + clock_generator.write_ms_single_byte( + 0, + si5351_ms_afe_4m); - /* 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_afe_4m); // CLK0: PLL A AFE Codec (4 MHz) - clock_generator.write_ms_single_byte(1, si5351_ms_10m); // CLK1: PLL B SGPIO/FPGA Timing (10 MHz) - clock_generator.write_ms_single_byte(2, si5351_ms_afe_40m); // CLK2: PLL A Audio and MCU Input (40 MHz) - clock_generator.write_ms_single_byte(3, si5351_ms_0_4m); // CLK3: PLL B SMA Port 1 Logic Sync (4 MHz or 10 MHz) - clock_generator.write_ms_single_byte(4, si5351_ms_afe_40m); // CLK4: PLL A MAX2831 Second IF (40 MHz) - clock_generator.write_ms_single_byte(5, si5351_ms_afe_40m); // CLK5: PLL A RFFC5071First IF (40 MHz) - clock_generator.write_ms_single_byte(6, si5351_ms_0_4m); // CLK6: PLL B SMA Port 2 Logic Sync (4 MHz or 10 MHz) - clock_generator.write_ms_single_byte(7, si5351_ms_0_4m); // CLK7: PLL B Unused (4 MHz) + // CLK1: DSCT_CLK initial 10 MHz + clock_generator.write_ms_single_byte( + 1, + si5351_ms_10m); - /* NOW set clock control registers (AFTER multisynths per HackRF reference) */ - 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_a), - si5351a_clock_control_common[1].ms_src(ref_pll_b), - si5351a_clock_control_common[2].ms_src(ref_pll_a), - 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 - clock_generator.set_clock_control_single_byte(si5351_clock_control); + // CLK2: MCU_CLK 40 MHz + clock_generator.write_ms_single_byte( + 2, + si5351_ms_afe_40m); - // Don't write CLKS 3, 6, and 7 multisynth - // Ensure CLK3 clock control has Power_Off - // Verify output is disabled - clock_generator.disable_output(3); - clock_generator.disable_clock(3); - clock_generator.disable_output(6); - clock_generator.disable_clock(6); - clock_generator.disable_output(7); - clock_generator.disable_clock(7); + // CLK3: P2 clock mux, disabled below + clock_generator.write_ms_single_byte( + 3, + si5351_ms_10m); + + // CLK4: DXCVR_CLK 40 MHz + clock_generator.write_ms_single_byte( + 4, + si5351_ms_afe_40m); + + // CLK5: DMIX_CLK 40 MHz + clock_generator.write_ms_single_byte( + 5, + si5351_ms_afe_40m); + + { + const auto& ms6_7 = si5351a_ms6_7_off_reg; + for (size_t i = 1; i < ms6_7.size(); i++) { + clock_generator.write_register(ms6_7[0] + i - 1, ms6_7[i]); + } + } #else if (hackrf_r9) { const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal) @@ -645,15 +615,14 @@ void ClockManager::init_clock_generator() { // Wait for PLL(s) to lock. #ifdef PRALINE - // 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; + uint8_t device_status_mask = + (ref_pll == ClockControl::MultiSynthSource::PLLB) ? 0x40 : 0x20; uint32_t pll_timeout = 100000; while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) { pll_timeout--; } - // Store PLL lock status for debugging - static volatile uint32_t pll_lock_timeout = pll_timeout; - (void)pll_lock_timeout; + + clock_generator.enable_output(clock_generator_output_pro_mcu_clkin); #else // Wait for PLL(s) to lock - with timeout to prevent hang @@ -753,13 +722,13 @@ void ClockManager::shutdown() { void ClockManager::enable_codec_clocks() { #ifdef PRALINE - /* PRALINE: CLK0 (AFE_CLK) for codec/FPGA, CLK1 (SCT_CLK) for FPGA timing. - * Reference hackrf_core.c shows PRALINE needs both CLK0 and CLK1. */ + /* PRALINE: only gate the clocks owned by the RF datapath here. + * CLK2 is MCU_CLKIN and must stay independent of runtime RX/TX clock + * management or the SGPIO/GPIO subsystem can glitch mid-stream. */ clock_generator.enable_clock(clock_generator_output_og_codec); /* CLK0 MAX5864*/ clock_generator.enable_clock(clock_generator_output_og_cpld); /* CLK1 iCE40 FPGA*/ - clock_generator.enable_clock(clock_generator_output_og_sgpio); /* CLK2 LPC43xx*/ clock_generator.enable_output_mask( - (1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio)); + (1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld)); #else if (hackrf_r9) { clock_generator.enable_clock(clock_generator_output_r9_sgpio); @@ -787,12 +756,11 @@ void ClockManager::disable_codec_clocks() { * CLKx_DISABLE_STATE. */ #ifdef PRALINE - /* PRALINE: CLK0 (AFE_CLK), CLK1 (SCT_CLK), and CLK2 MCU used for codec/FPGA */ + /* PRALINE: leave CLK2/MCU_CLKIN alone; only disable datapath-owned clocks. */ clock_generator.disable_output_mask( - (1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio)); + (1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld)); clock_generator.disable_clock(clock_generator_output_og_codec); clock_generator.disable_clock(clock_generator_output_og_cpld); - clock_generator.disable_clock(clock_generator_output_og_sgpio); #else if (hackrf_r9) { clock_generator.disable_output_mask(1U << clock_generator_output_r9_sgpio); @@ -851,7 +819,7 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) { /* * PRALINE sample rate strategy: * 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum - * 2. Use FPGA decimation to achieve desired output rate + * 2. Use FPGA decimation/interpolation to achieve desired output rate * 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO) */ @@ -872,50 +840,96 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) { n++; } - _resampling_n = n; - - // === Stop FPGA processing and flush filters === - fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators) - - // Verify we're in RX mode before writing RX registers - if (fpga_get_mode() != FPGA_MODE_RX) { - // Either set mode or return error - fpga_set_mode(FPGA_MODE_RX); + /* The TX gateware only implements interpolation factors x1..x16 + * (tx_intrp 0..4). The rate search above can select n == 5 (x32) for very + * low TX sample rates. If we kept afe_rate/CLK0/CLK1 at the x32 rate while + * the FPGA only interpolated by x16, the TX datapath would be clocked at + * twice the gateware's output rate, doubling the waveform speed. Cap the + * AFE rate and _resampling_n to the x16 limit in TX so the clocks and the + * interpolation setting stay aligned. */ + if (radio::debug::get_cached_direction() == rf::Direction::Transmit) { + constexpr uint8_t MAX_TX_N = 4; // x16, matches max tx_intrp + if (n > MAX_TX_N) { + n = MAX_TX_N; + afe_rate = frequency << n; + } } - // Set FPGA RX decimation register - fpga_debug_register_write(FPGA_REG_DECIM, n); - - /* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN. - * We shift up by (3 * n) to compensate for CIC bit-growth. - * Relationship: ds = (Stages * n) - Offset - * For a 3-stage filter, every increment of n grows the signal by 3 bits. - * We subtract a baseline offset to keep the signal within 8-bit bounds. - * Add a baseline shift to ensure the signal isn't too quiet - */ - uint8_t ds = (3 * n); - ds += 2; - fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds); + _resampling_n = n; radio::invalidate_spi_config(); - // 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) // Configure Si5351 clocks using the correct AFE VCO + // CLK0 always drives the AFE/MAX5864 clock. 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); - // === Reset PLL A for phase alignment === - clock_generator.write_register(si5351::Register::PLLReset, 0x20); + /* Do not reset PLLA here. On PRALINE, CLK2/MCU_CLKIN is sourced from PLLA, + * so a runtime PLLA reset can glitch the LPC43xx peripheral clock tree and + * break SGPIO-driven RX apps such as ADSB/APRS. Updating the multisynths is + * sufficient for sample-rate changes. */ - // Brief delay for PLL lock and clock stability === - // ~1ms at 96MHz = ~96000 cycles, use 10ms for safety - volatile uint32_t delay = 240000; // ~2.5ms - while (delay--); + if (radio::debug::get_cached_direction() == rf::Direction::Transmit) { + /* + * TX path: + * Baseband generators such as AFSK still synthesize samples at their + * legacy logical sample rates (for APRS this is 1.536MHz). On PRALINE, + * the FPGA must interpolate those samples up to the active AFE rate. + * + * The TX datapath is clocked from CLK1/fpgaclk. Therefore CLK1 must + * equal the desired complex sample rate at the DAC side. Driving CLK1 + * at 2x the intended TX sample rate causes the entire APRS waveform to + * run twice as fast, which matches the "energy present, undecodable" + * symptom seen on HackRF One receivers. + * + * Gateware mapping from standard.py: + * tx_intrp = 0 -> x1 + * tx_intrp = 1 -> x2 + * tx_intrp = 2 -> x4 + * tx_intrp = 3 -> x8 + * tx_intrp = 4 -> x16 + */ + if (fpga_get_mode() != FPGA_MODE_TX) { + fpga_set_mode(FPGA_MODE_TX); + } - // Re-enable FPGA processing with clean state === - fpga_debug_register_write(1, 0x01); + // TX gateware consumes complex samples at the rate presented on CLK1. + clock_generator.set_ms_frequency(1, afe_rate, si5351_vco_afe_f, 0); + + uint8_t tx_interp = 0; + uint32_t tx_rate = frequency; + while ((tx_rate < afe_rate) && (tx_interp < 4)) { + tx_rate <<= 1; + tx_interp++; + } + fpga_tx_set_interpolation(tx_interp); + fpga_tx_set_nco_enable(false); + fpga_tx_set_phase_step(0); + } + // for RX mode + else { + // RX path keeps CLK1 at 2x AFE for receive timing / SGPIO alignment. + clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0); + + // === Stop FPGA processing and flush filters === + fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators) + + if (fpga_get_mode() != FPGA_MODE_RX) { + fpga_set_mode(FPGA_MODE_RX); + } + + // Set FPGA RX decimation register + fpga_debug_register_write(FPGA_REG_DECIM, n); + + /* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN. + * We shift up by (3 * n) to compensate for CIC bit-growth. + */ + uint8_t ds = (3 * n); + ds += 2; + fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds); + + // Re-enable FPGA processing with clean state === + fpga_debug_register_write(1, 0x01); + } #else /* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 72d00616a..e6ca2e2f7 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -360,19 +360,12 @@ static void set_cpu_clock_speed() { /* Step into the 90-110MHz M4 clock range */ #ifdef PRALINE - /* PRALINE: Enable and use 12MHz XTAL directly (no GP_CLKIN from Si5351) */ - - /* Step 1: Enable the crystal oscillator */ - LPC_CGU->XTAL_OSC_CTRL.ENABLE = 0; // 0 = enable (active low) - LPC_CGU->XTAL_OSC_CTRL.HF = 0; // 0 = low frequency mode (1-20MHz) - - /* Step 2: Wait for oscillator to stabilize (~250us at IRC speed) */ - volatile uint32_t delay = 3000; // ~250us at 12MHz IRC - while (delay--); - - /* Step 3: Configure PLL1 from XTAL - * Fclkin = 12M, /N=1 = 12M, Fcco = 12M * 17 = 204M - * Fclk = Fcco / (2*(P=1)) = 102M + /* PRALINE: source PLL1 from GP_CLKIN, which is driven by the Si5351 + * CLK2/MCU_CLK output at 40MHz (see ClockManager Si5351 setup). The + * on-chip 12MHz XTAL bootstrap path is intentionally not used here. + * + * Fclkin = 40M (GP_CLKIN), /N=2 = 20M, Fcco = 20M * 10 = 200M + * Fclk = Fcco / (2*(P=1)) = 100M */ cgu::pll1::ctrl({ .pd = 1, @@ -381,10 +374,11 @@ static void set_cpu_clock_speed() { .direct = 0, .psel = 0, .autoblock = 1, - .nsel = 0, // N = 1 - .msel = 16, // M = 17, so 12MHz * 17 = 204MHz - .clk_sel = cgu::CLK_SEL::XTAL, + .nsel = 1UL, // N = 2 + .msel = 9UL, // M = 10 + .clk_sel = cgu::CLK_SEL::GP_CLKIN, }); + #else /* OG: * Fclkin = 40M, /N=2 = 20M, Fcco = 20M * 10 = 200M @@ -613,6 +607,7 @@ init_status_t init() { cgu::pll1::disable(); set_cpu_clock_speed(); + /* sample max: 1023 sample_t AKA uint16_t * touch_sensitivity: range: 1 to 128 * threshold range: 1023/1 to 1023/128 = 1023 to 8 diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index 76fea4879..0354e445f 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -390,11 +390,13 @@ void set_baseband_rate(const uint32_t rate) { } void set_antenna_bias(const bool on) { - /* Pull MOSFET gate low to turn on antenna bias. */ #ifdef PRALINE - // Praline: P2_12 = GPIO1[12], ANT_BIAS_EN_N (active LOW) - LPC_GPIO->CLR[1] = on ? (1 << 12) : 0; - LPC_GPIO->SET[1] = on ? 0 : (1 << 12); + /* Keep bias control inside the PRALINE RF path state machine so later + * band/direction/amp updates don't silently force ANT_BIAS_EN_N off. + * Reassert output mode here because this pin is safety-critical and + * direct GPIO writes on an input-configured pad have no effect. */ + gpio_ant_bias_disable.output(); + rf_path.set_antenna_bias(on); #else if (hackrf_r9) { gpio_r9_not_ant_pwr.write(on ? 0 : 1); @@ -627,4 +629,4 @@ uint32_t register_read(const size_t register_number) { } /* namespace debug */ -} /* namespace radio */ \ No newline at end of file +} /* namespace radio */ diff --git a/firmware/application/rf_path.cpp b/firmware/application/rf_path.cpp index 45e655b09..103fc8b92 100644 --- a/firmware/application/rf_path.cpp +++ b/firmware/application/rf_path.cpp @@ -263,6 +263,11 @@ void Path::set_rf_amp(const bool new_rf_amp) { update(); } +void Path::set_antenna_bias(const bool new_antenna_bias) { + antenna_bias = new_antenna_bias; + update(); +} + void Path::update() { /* 0 ^ 0 => 0 & 0 = 0 ^ 0 = 0 (no change) * 0 ^ 1 => 1 & 0 = 0 ^ 0 = 0 (ignore change to 1) @@ -301,8 +306,8 @@ void Path::update() { /* RF amp when amplification requested */ config.rf_amp_en = rf_amp; - /* Antenna bias off by default */ - config.ant_bias_en_n = true; + /* Preserve requested antenna bias state across band/direction/amp updates. */ + config.ant_bias_en_n = !antenna_bias; config.apply(); diff --git a/firmware/application/rf_path.hpp b/firmware/application/rf_path.hpp index d098b2e37..150bfe59f 100644 --- a/firmware/application/rf_path.hpp +++ b/firmware/application/rf_path.hpp @@ -65,6 +65,7 @@ class Path { void set_direction(const Direction direction); void set_band(const Band band); void set_rf_amp(const bool rf_amp); + void set_antenna_bias(const bool antenna_bias); Band get_band() const { return _band; } //_band is used solely for debugging purposes. @@ -72,6 +73,7 @@ class Path { Direction direction{Direction::Receive}; Band band{Band::Mid}; bool rf_amp{false}; + bool antenna_bias{false}; void update(); diff --git a/firmware/application/transmitter_model.hpp b/firmware/application/transmitter_model.hpp index 058bc2ff7..ed2683b33 100644 --- a/firmware/application/transmitter_model.hpp +++ b/firmware/application/transmitter_model.hpp @@ -28,6 +28,7 @@ #include #include "app_settings.hpp" +#include "hackrf_hal.hpp" #include "max2837.hpp" #include "message.hpp" #include "receiver_model.hpp" @@ -40,8 +41,9 @@ class TransmitterModel { uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum; uint32_t sampling_rate = 3'072'000; uint32_t channel_bandwidth = 1; - /* 35 should give approx 1m transmission range. */ - uint8_t tx_gain_db = 35; + /* Platform default chosen to avoid PRALINE MAX2831 overdrive while + * preserving legacy HackRF One behavior. */ + uint8_t tx_gain_db = hackrf::one::default_tx_gain_db; bool rf_amp = false; }; diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c index 3b08b2eda..e9491e443 100644 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c @@ -140,7 +140,7 @@ // ============================================================================ static fpga_mode_t current_mode = FPGA_MODE_OFF; // Cached register values for debug reads (since reads may require mode switch) -static uint8_t fpga_reg_cache[6] = {0, 0x01, 0x00, 0x00, 0x00, 0x00}; +static uint8_t fpga_reg_cache[7] = {0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; // Context structure for SPIFI-based reading struct spifi_fpga_read_ctx { @@ -315,7 +315,7 @@ static void fpga_spi_write(uint8_t reg, uint8_t value) { // Public function to read FPGA register (callable from C++ application code) // Switches SPI mode, reads register, switches back uint8_t fpga_debug_register_read(uint8_t reg) { - if (reg == 0 || reg > 5) return 0xFF; + if (reg == 0 || reg > 6) return 0xFF; uint8_t value; ssp1_set_mode_ice40(); @@ -328,7 +328,7 @@ uint8_t fpga_debug_register_read(uint8_t reg) { // Public function to write FPGA register (callable from C++ application code) void fpga_debug_register_write(uint8_t reg, uint8_t value) { - if (reg == 0 || reg > 5) return; + if (reg == 0 || reg > 6) return; ssp1_set_mode_ice40(); fpga_spi_write(reg, value); @@ -342,7 +342,7 @@ void fpga_debug_register_write(uint8_t reg, uint8_t value) { // ============================================================================ uint8_t fpga_register_read(uint8_t reg) { - if (reg == 0 || reg > 5) return 0xFF; + if (reg == 0 || reg > 6) return 0xFF; ssp1_set_mode_ice40(); uint8_t val = fpga_spi_read(reg); @@ -353,7 +353,7 @@ uint8_t fpga_register_read(uint8_t reg) { } void fpga_register_write(uint8_t reg, uint8_t value) { - if (reg == 0 || reg > 5) return; + if (reg == 0 || reg > 6) return; ssp1_set_mode_ice40(); fpga_spi_write(reg, value); @@ -422,22 +422,22 @@ void fpga_rx_set_dc_adapt_rate(uint8_t rate) { /* TX Functions with mode assertion */ void fpga_tx_set_nco_enable(bool enable) { if (current_mode != FPGA_MODE_TX) return; - uint8_t val = fpga_register_read(FPGA_REG_SHARED_3); + uint8_t val = fpga_register_read(FPGA_REG3_TX_NCO_CTRL); if (enable) val |= FPGA_TX_NCO_EN; else val &= ~FPGA_TX_NCO_EN; - fpga_register_write(FPGA_REG_SHARED_3, val); + fpga_register_write(FPGA_REG3_TX_NCO_CTRL, val); } void fpga_tx_set_interpolation(uint8_t ratio) { if (current_mode != FPGA_MODE_TX) return; - fpga_register_write(FPGA_REG_SHARED_4, ratio & FPGA_TX_INTERP_MASK); + fpga_register_write(FPGA_REG_TX_INTERP, ratio & FPGA_TX_INTERP_MASK); } void fpga_tx_set_phase_step(uint8_t step) { if (current_mode != FPGA_MODE_TX) return; - fpga_register_write(FPGA_REG_SHARED_5, step); + fpga_register_write(FPGA_REG_TX_PHASE_STEP, step); } // ============================================================================ diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h index ee561e029..16d0bb661 100644 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h @@ -16,17 +16,21 @@ extern "C" { #ifdef PRALINE -/* FPGA Register Map Address 0x03 (Dual Purpose) */ +/* RX path (legacy PRALINE software map kept for compatibility) */ #define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */ -#define FPGA_REG_TX_CONTROL 0x03 /* NCO_EN and TX flags (TX Mode) */ - -/* FPGA Register Map Address 0x04 (Shared) */ #define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */ -#define FPGA_REG_TX_INTERP 0x04 /* Interpolation ratio (TX Mode) */ - -/* FPGA Register Map Address 0x05 (Shared) */ #define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */ -#define FPGA_REG_TX_PHASE_STEP 0x05 /* NCO frequency step (TX Mode) */ + +/* + * TX path must match the currently built PRALINE standard gateware in + * hackrf/firmware/fpga/top/standard.py: + * 0x04 tx_ctrl + * 0x05 tx_intrp + * 0x06 tx_pstep + */ +#define FPGA_REG_TX_CONTROL 0x04 +#define FPGA_REG_TX_INTERP 0x05 +#define FPGA_REG_TX_PHASE_STEP 0x06 /* * FPGA Operating Mode @@ -39,13 +43,21 @@ typedef enum { /* * FPGA Register Addresses - * Note: Registers 3-5 are dual-purpose (meaning depends on RX/TX mode) + * NOTE: + * The currently loaded PRALINE standard gateware uses: + * 0x01 CTRL + * 0x02 RX_DECIM + * 0x03 RX_DIGITAL_GAIN (RX) / TX_NCO_CTRL (TX) + * 0x04 TX_CTRL + * 0x05 TX_INTRP + * 0x06 TX_PSTEP */ #define FPGA_REG_CTRL 0x01 /* Control register */ -#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */ -#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */ -#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */ -#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose register */ +#define FPGA_REG_DECIM 0x02 /* RX decimation */ +#define FPGA_REG_SHARED_3 0x03 /* Legacy shared register */ +#define FPGA_REG_SHARED_4 0x04 /* Legacy shared register */ +#define FPGA_REG_SHARED_5 0x05 /* Legacy shared register */ +#define FPGA_REG_SHARED_6 0x06 /* TX phase step */ /* * Register 1 (CTRL) Bit Definitions @@ -77,7 +89,7 @@ typedef enum { #define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */ /* TX Mode: Interpolation ratio */ -#define FPGA_REG4_TX_INTERP 0x04 +#define FPGA_REG4_TX_INTERP 0x05 #define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */ /* @@ -88,7 +100,7 @@ typedef enum { #define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */ /* TX Mode: NCO phase step (frequency) */ -#define FPGA_REG5_TX_PHASE_STEP 0x05 +#define FPGA_REG5_TX_PHASE_STEP 0x06 #define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */ /* Export default values so other methods can use them */ diff --git a/firmware/common/hackrf_hal.hpp b/firmware/common/hackrf_hal.hpp index efd905c14..ff3b34e66 100644 --- a/firmware/common/hackrf_hal.hpp +++ b/firmware/common/hackrf_hal.hpp @@ -54,6 +54,12 @@ constexpr ClockFrequency max283x_reference_f = 40000000U; constexpr ClockFrequency mcu_clkin_og_f = 40000000U; constexpr ClockFrequency mcu_clkin_r9_f = 10000000U; +#ifdef PRALINE +constexpr int8_t default_tx_gain_db = 16; +#else +constexpr int8_t default_tx_gain_db = 35; +#endif + constexpr uint8_t si5351_i2c_address = 0x60; /* Clock Generator */