From 85eaa9d800838e3bd2dfc1c9c256f8ff6d7350b7 Mon Sep 17 00:00:00 2001 From: stafur Date: Sun, 22 Feb 2026 19:40:49 -0500 Subject: [PATCH] Updated praline low and high band tuning (#3030) * Updated harckrf_gpio methods to more closely reflect hackrf_usb. Added some ui debug updates to RFFC5072 Status View. * Updated tuning tables for tuning.cpp. Remnoved 15MHz lower limit in max2831.cpp since lower bandwidths don't seem to be causing lower band issues. Added more opportunities for clocks to stabalize at startup in board.cpp. Added/amended UI to help with addresssing low band tuning issues. Cleaned up stale comments in radio.cpp. * Ran format-code.sh * Updated to remove commented lines as part of clean up addressing review comments. --- firmware/application/apps/ui_debug.cpp | 315 +++++++++++++----- firmware/application/apps/ui_debug.hpp | 13 +- firmware/application/hw/max2831.cpp | 3 - firmware/application/hw/rffc507x.cpp | 14 - firmware/application/radio.cpp | 33 +- firmware/application/rf_path.cpp | 14 - firmware/application/tuning.cpp | 117 ++++++- .../boards/PORTAPACK_APPLICATION/board.cpp | 46 ++- firmware/common/hackrf_gpio.hpp | 11 +- firmware/flashsize.h | 9 + 10 files changed, 413 insertions(+), 162 deletions(-) create mode 100644 firmware/flashsize.h diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp index c5cd42ece..1672e0188 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -2104,9 +2104,7 @@ void GPIODebugView::refresh() { RFFC5072StatusView::RFFC5072StatusView(NavigationView& nav) : nav_(nav) { add_children({ - &text_title, - &text_lbl_lock, - &text_lock, + &text_gpio4, &text_ctrl, &text_lbl_enabled, &text_enabled, @@ -2129,25 +2127,133 @@ RFFC5072StatusView::RFFC5072StatusView(NavigationView& nav) &text_lbl_calc, &text_calc, &text_status, - &text_lbl_regs_status, + &text_status2, + &text_status3, &text_regs_status, &button_refresh, - &button_force_enx, + &button_force, &button_done, }); - text_title.set_style(Theme::getInstance()->fg_yellow); - button_refresh.on_select = [this](Button&) { refresh_status(); }; - button_force_enx.on_select = [this](Button&) { + button_force.on_select = [this](Button&) { // Force ENX to OUTPUT and drive LOW - LPC_GPIO->DIR[2] |= (1 << 13); // Set as OUTPUT - LPC_GPIO->CLR[2] = (1 << 13); // Drive LOW (enabled) + // LPC_GPIO->DIR[2] |= (1 << 13); // Set as OUTPUT + // LPC_GPIO->CLR[2] = (1 << 13); // Drive LOW (enabled) - refresh_status(); + // refresh_status(); + + // Disable RFFC5072 + // uint32_t r0 = radio::debug::first_if::register_read(0); + // radio::debug::first_if::register_write(0, r0 & ~0x0010); // Clear ENBL + + // Wait 1ms + // chThdSleepMilliseconds(1); + + // Re-enable - this triggers new calibration + // radio::debug::first_if::register_write(0, r0 | 0x0010); // Set ENBL + + // Wait for calibration + // chThdSleepMilliseconds(10); + + // refresh_status(); + + // Force lodiv=4 (log2=2) instead of lodiv=2 (log2=1) + // This gives VCO = LO × 4 = 2595 × 4 = 10380 MHz - TOO HIGH! + + // Actually, we need lodiv=1 which gives VCO = 2595 MHz - TOO LOW (below 2700) + + // Let's try a different approach: manually write registers for VCO ~ 3500 MHz + // LO = 3500/2 = 1750 MHz (not useful for FM, but tests if VCO can lock) + + // VCO = 3500 MHz, lodiv=2, presc=2, f_ref=40 + // N = (VCO × presc) / f_ref = (3500 × 2) / 40 = 175 + + // Write P2_FREQ1: N=175, lodiv=1 (log2), presc=1 (log2) + // uint16_t p2_freq1 = (175 << 7) | (1 << 4) | (1 << 2); + // radio::debug::first_if::register_write(15, p2_freq1); + + // Clear fractional part + // radio::debug::first_if::register_write(16, 0); + // radio::debug::first_if::register_write(17, 0); + + // Trigger recalibration by toggling ENBL + // uint32_t r0 = radio::debug::first_if::register_read(0); + // radio::debug::first_if::register_write(0, r0 & ~0x0010); + // chThdSleepMilliseconds(1); + // radio::debug::first_if::register_write(0, r0 | 0x0010); + // chThdSleepMilliseconds(20); + + // refresh_status(); + + // Test SPI SDATA direction switching + // PRALINE: SDATA = P9_2 = GPIO4[14] + + // Check current direction + uint32_t dir_before = LPC_GPIO->DIR[4]; + bool sdata_output_before = (dir_before >> 14) & 1; + + // Try a register read + uint32_t dummy = radio::debug::first_if::register_read(0); + (void)dummy; + + // Check direction after read + uint32_t dir_after = LPC_GPIO->DIR[4]; + bool sdata_output_after = (dir_after >> 14) & 1; + + // Read the actual SDATA pin state + uint32_t pin_state = LPC_GPIO->PIN[4]; + bool sdata_pin = (pin_state >> 14) & 1; + + text_status.set("SDATA: dir_b=" + to_string_dec_uint(sdata_output_before) + + " dir_a=" + to_string_dec_uint(sdata_output_after) + + " pin=" + to_string_dec_uint(sdata_pin) + " "); + + // If both are 1 (OUTPUT), the read direction switch isn't happening + // if (sdata_output_before && sdata_output_after) { + // text_status2.set("ERROR: SDATA stuck as OUTPUT! "); + // text_status2.set_style(Theme::getInstance()->fg_red); + //} else { + // text_status2.set("SDATA direction OK "); + // text_status2.set_style(Theme::getInstance()->fg_green); + //} + + // Test: Write a known pattern to register 0, then read back + // Register 0 (DEV_CTRL) default = 0xBEFA + + // Step 1: Read current value + uint32_t before = radio::debug::first_if::register_read(0); + + // Step 2: Write a different value (change ENBL bit to toggle) + uint32_t test_val = before ^ 0x0010; // Toggle ENBL bit + radio::debug::first_if::register_write(0, test_val); + + // Step 3: Read back + uint32_t after = radio::debug::first_if::register_read(0); + + // Step 4: Restore original + radio::debug::first_if::register_write(0, before); + + // Display results + text_status.set("WR TEST: " + to_string_hex(before, 4) + + "->" + to_string_hex(test_val, 4) + + " rb:" + to_string_hex(after, 4)); + + // If after == before (not test_val), reads are broken + // If after == test_val, reads work + if (after == test_val) { + text_status2.set("READ-AFTER-WRITE: PASS! "); + text_status2.set_style(Theme::getInstance()->fg_green); + } else if (after == before) { + text_status2.set("READ-AFTER-WRITE: FAIL (no change) "); + text_status2.set_style(Theme::getInstance()->fg_red); + } else { + text_status2.set("READ-AFTER-WRITE: CORRUPT " + to_string_hex(after, 4) + " "); + text_status2.set_style(Theme::getInstance()->fg_red); + } }; button_done.on_select = [&nav](Button&) { @@ -2163,15 +2269,14 @@ void RFFC5072StatusView::focus() { } void RFFC5072StatusView::refresh_status() { - // === DEBUG: Capture GPIO state BEFORE any operations === - uint32_t gpio2_before = LPC_GPIO->PIN[2]; - bool enx_before = (gpio2_before >> 13) & 1; + // === DIAGNOSTIC: Capture initial GPIO state === + uint32_t gpio2_initial = LPC_GPIO->PIN[2]; + uint32_t dir2_initial = LPC_GPIO->DIR[2]; + bool enx_initial = (gpio2_initial >> 13) & 1; - // === READ RAW GPIO STATES FOR DEBUGGING === + // === READ RAW GPIO STATES FOR DISPLAY === uint32_t gpio2_dir = LPC_GPIO->DIR[2]; uint32_t gpio2_pin = LPC_GPIO->PIN[2]; - - // Check if the pins are even configured as outputs bool enx_is_output = (gpio2_dir >> 13) & 1; bool resetx_is_output = (gpio2_dir >> 14) & 1; @@ -2179,79 +2284,80 @@ void RFFC5072StatusView::refresh_status() { uint32_t gpio6_pin = LPC_GPIO->PIN[6]; bool rffc_locked = (gpio6_pin >> 25) & 1; - text_lock.set(rffc_locked ? "LOCKED" : "UNLOCKED"); - text_lock.set_style(rffc_locked ? Theme::getInstance()->fg_green - : Theme::getInstance()->fg_red); - - uint8_t fpga_reg1 = radio::debug::fpga::register_read(1); // CTRL register - uint8_t fpga_reg2 = radio::debug::fpga::register_read(2); // RX_DECIM - - text_regs_status.set( - "FPGA R1:" + to_string_hex(fpga_reg1, 2) + - " R2:" + to_string_hex(fpga_reg2, 2)); + // === FPGA REGISTERS (non-SPI) === + uint8_t fpga_reg1 = radio::debug::fpga::register_read(1); + uint8_t fpga_reg2 = radio::debug::fpga::register_read(2); + uint8_t fpga_reg3 = radio::debug::fpga::register_read(3); + text_regs_status.set("FPGA R1:" + to_string_hex(fpga_reg1, 2) + + " R2:" + to_string_hex(fpga_reg2, 2) + + " R3:" + to_string_hex(fpga_reg3, 2)); // === CONTROL PINS === - // ENX = GPIO2[13] (P5_4) - active LOW (0 = enabled) - // RESETX = GPIO2[14] (P5_5) - active LOW (0 = reset) bool enx = (gpio2_pin >> 13) & 1; bool resetx = (gpio2_pin >> 14) & 1; - text_ctrl.set("ENX: " + std::string(enx ? "DIS" : "EN") + " O:" + std::string(enx_is_output ? "Y" : "N") + - " | RST: " + std::string(resetx ? "RUN" : "RST") + + " | RSTX: " + std::string(resetx ? "H" : "L") + " O:" + std::string(resetx_is_output ? "Y" : "N")); - text_ctrl.set_style((enx == 0 && resetx == 1) ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red); - // === REGISTERS === - // Read CORRECT registers for Path 2 (active path!) - uint32_t r0 = radio::debug::first_if::register_read(0); // Control - uint32_t r15 = radio::debug::first_if::register_read(15); // P2_FREQ1 - uint32_t r16 = radio::debug::first_if::register_read(16); // P2_FREQ2 + // === DIAGNOSTIC: Check BEFORE first RFFC5072 SPI read === + uint32_t gpio2_before_spi = LPC_GPIO->PIN[2]; + bool enx_before_spi = (gpio2_before_spi >> 13) & 1; - // Display + // === RFFC5072 REGISTERS (SPI reads - this is where corruption happens) === + uint32_t r0 = radio::debug::first_if::register_read(0); + + // === DIAGNOSTIC: Check AFTER first read === + uint32_t gpio2_after_r0 = LPC_GPIO->PIN[2]; + bool enx_after_r0 = (gpio2_after_r0 >> 13) & 1; + + uint32_t r15 = radio::debug::first_if::register_read(15); + + // === DIAGNOSTIC: Check AFTER second read === + uint32_t gpio2_after_r15 = LPC_GPIO->PIN[2]; + bool enx_after_r15 = (gpio2_after_r15 >> 13) & 1; + + uint32_t r16 = radio::debug::first_if::register_read(16); + + // === DIAGNOSTIC: Check AFTER third read === + uint32_t gpio2_final = LPC_GPIO->PIN[2]; + uint32_t dir2_final = LPC_GPIO->DIR[2]; + bool enx_final = (gpio2_final >> 13) & 1; + + // === Display register values === text_r0.set(to_string_hex(r0, 4)); text_r1.set(to_string_hex(r15, 4) + " (R15)"); text_r2.set(to_string_hex(r16, 4) + " (R16)"); - // Check enabled (R0 bit 4) bool enabled = (r0 & 0x0010) != 0; text_enabled.set(enabled ? "ENABLED" : "DISABLED"); text_enabled.set_style(enabled ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red); - // Decode from P2_FREQ1 (R15) - uint16_t n_int = (r15 >> 7) & 0x1FF; // 9 bits - uint8_t lodiv_sel = (r15 >> 4) & 0x07; // 3 bits - uint8_t presc_sel = (r15 >> 2) & 0x03; // 2 bits - + // === Decode frequency info (keeping existing code) === + uint16_t n_int = (r15 >> 7) & 0x1FF; + uint8_t lodiv_sel = (r15 >> 4) & 0x07; + uint8_t presc_sel = (r15 >> 2) & 0x03; text_n.set(to_string_dec_uint(n_int)); - // LO divider: 0=÷2, 1=÷4, 2=÷8, 3=÷16, 4=÷32, 5=÷64 uint16_t lodiv_val = 1u << lodiv_sel; uint16_t presc_val = 1u << presc_sel; - text_lodiv.set("/" + to_string_dec_uint(lodiv_val) + " (P:/" + to_string_dec_uint(presc_val) + ")"); - // Calculate frequencies const uint32_t f_ref_mhz = 40; uint32_t f_vco_mhz = (f_ref_mhz * n_int) / presc_val; uint32_t f_lo_mhz = f_vco_mhz / lodiv_val; - // Check ranges - // RFFC5072 datasheet: Output 85-4200 MHz, VCO 2700-5400 MHz bool vco_ok = (f_vco_mhz >= 2700) && (f_vco_mhz <= 5400); bool lo_ok = (f_lo_mhz >= 85) && (f_lo_mhz <= 4200); - - // PRALINE mid-band (2320-2740 MHz) uses direct path, not RFFC5072 bool in_bypass_range = (f_lo_mhz >= 2320) && (f_lo_mhz <= 2740); - // Display with range annotation if (in_bypass_range) { text_calc.set(to_string_dec_uint(f_lo_mhz) + " MHz (MID)"); - text_calc.set_style(Theme::getInstance()->fg_orange); // Orange = bypass band + text_calc.set_style(Theme::getInstance()->fg_orange); } else { text_calc.set(to_string_dec_uint(f_lo_mhz) + " MHz"); text_calc.set_style(lo_ok ? Theme::getInstance()->fg_green @@ -2262,38 +2368,93 @@ void RFFC5072StatusView::refresh_status() { text_freq.set_style(vco_ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red); - // Check mixer mode bool path2_active = (r0 & 0x0020) != 0; text_path.set(path2_active ? "PATH2" : "PATH1"); text_mixer.set(path2_active ? "ACTIVE" : "INACTIVE"); text_mixer.set_style(path2_active ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_orange); - // === SUMMARY STATUS === - if (!rffc_locked) { - text_status.set("PLL UNLOCKED!"); - text_status.set_style(Theme::getInstance()->fg_red); - } else if (enx == 1) { - text_status.set("DISABLED (ENX=1)!"); - text_status.set_style(Theme::getInstance()->fg_red); - } else if (resetx == 0) { - text_status.set("IN RESET (RESETX=0)!"); - text_status.set_style(Theme::getInstance()->fg_red); - } else if (!enabled) { - text_status.set("R0 bit 4 = 0 (disabled)"); - text_status.set_style(Theme::getInstance()->fg_red); - } else if (!path2_active) { - text_status.set("Path 2 not selected!"); - text_status.set_style(Theme::getInstance()->fg_red); - } else if (!vco_ok) { - text_status.set("VCO out of range!"); - text_status.set_style(Theme::getInstance()->fg_red); - } else if (!lo_ok) { - text_status.set("LO out of range!"); + // === DIAGNOSTIC STATUS (replaces normal status) === + // Read register 31 with readsel=0 (device ID) + radio::debug::first_if::register_write(0, (r0 & 0xFFF0) | 0x0000); // readsel=0 + uint32_t device_id = radio::debug::first_if::register_read(31); + + // Read calibration status (readback register 1) + // First, set DEV_CTRL.readsel = 1, then read READBACK register + uint32_t dev_ctrl_orig = radio::debug::first_if::register_read(0); // Save original + + // Write DEV_CTRL with readsel=1 (bits 3:0) + radio::debug::first_if::register_write(0, (dev_ctrl_orig & 0xFFF0) | 0x0001); + + // Now read the READBACK register (register address for readback) + uint32_t cal_status = radio::debug::first_if::register_read(31); // READBACK is at reg 31 + + // Decode calibration status: + // Bit 15: lock (should be 1) + // Bits 14:8: ct_cal (coarse tune calibration value, 0-127) + // Bits 7:1: cp_cal (charge pump calibration value) + // Bit 0: ctfail (1 = calibration FAILED) + + bool lock_bit = (cal_status >> 15) & 1; + uint8_t ct_cal = (cal_status >> 8) & 0x7F; + uint8_t cp_cal = (cal_status >> 1) & 0x7F; + bool ct_fail = cal_status & 1; + + // Add to refresh_status(): + uint32_t r6 = radio::debug::first_if::register_read(6); + uint32_t r5 = radio::debug::first_if::register_read(5); + uint32_t r3 = radio::debug::first_if::register_read(3); // VCO_CTRL + + // Check SDATA (GPIO4[14]) direction + uint32_t gpio4_dir = LPC_GPIO->DIR[4]; + bool sdata_is_output = (gpio4_dir >> 14) & 1; + text_gpio4.set("GPIO4 DIR: " + to_string_hex(gpio4_dir, 8) + + " SDATA=" + std::string(sdata_is_output ? "OUT" : "IN")); + + // Display these values + text_status2.set("CAL ct=" + to_string_dec_uint(ct_cal) + + " cp=" + to_string_dec_uint(cp_cal) + + (ct_fail ? " FAIL!" : " OK") + + " lck_b=" + to_string_dec_uint(lock_bit)); + text_status3.set("R3:" + to_string_hex(r3, 4) + + " R5:" + to_string_hex(r5, 4) + + " R6:" + to_string_hex(r6, 4)); + + if (enx_initial != enx_final || dir2_initial != dir2_final) { + // ENX or DIR changed - report which operation caused it + std::string diag = "CHG: "; + if (enx_initial != enx_before_spi) diag += "pre "; + if (enx_before_spi != enx_after_r0) diag += "R0 "; + if (enx_after_r0 != enx_after_r15) diag += "R15 "; + if (enx_after_r15 != enx_final) diag += "R16 "; + diag += std::to_string(enx_initial) + "->" + std::to_string(enx_final); + + if (dir2_initial != dir2_final) { + diag += " DIR!"; + } + + text_status.set(diag); text_status.set_style(Theme::getInstance()->fg_red); + + // Blink LED + /*for (int i = 0; i < 3; i++) { + hackrf::one::led_rx.on(); + chThdSleepMilliseconds(100); + hackrf::one::led_rx.off(); + chThdSleepMilliseconds(100); + }*/ } else { - text_status.set("All checks passed!"); - text_status.set_style(Theme::getInstance()->fg_green); + // No change - normal status + if (!rffc_locked) { + text_status.set("ID 0x" + to_string_hex(device_id, 4) + " PLL UNLOCKED!"); + text_status.set_style(Theme::getInstance()->fg_red); + } else if (enx == 1) { + text_status.set("ID 0x" + to_string_hex(device_id, 4) + " DSBLD,ENX=1!"); + text_status.set_style(Theme::getInstance()->fg_red); + } else { + text_status.set("ID 0x" + to_string_hex(device_id, 4) + " Passed!"); + text_status.set_style(Theme::getInstance()->fg_green); + } } } diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp index 7914e2ea8..90b5cab87 100644 --- a/firmware/application/apps/ui_debug.hpp +++ b/firmware/application/apps/ui_debug.hpp @@ -804,10 +804,9 @@ class RFFC5072StatusView : public View { NavigationView& nav_; void refresh_status(); - Text text_title{{0, 0, 240, 16}, "=== RFFC5072 (1st IF) ==="}; + Text text_status{{0, 0, 240, 16}, "---"}; - Text text_lbl_lock{{0, 16, 114, 16}, "Lock Detect:"}; - Text text_lock{{116, 16, 124, 16}, "---"}; + Text text_gpio4{{0, 16, 240, 16}, "---"}; Text text_ctrl{{0, 32, 240, 16}, "---"}; @@ -841,13 +840,13 @@ class RFFC5072StatusView : public View { Text text_lbl_calc{{0, 192, 114, 16}, "Calc freq:"}; Text text_calc{{116, 192, 124, 16}, "---"}; - Text text_status{{0, 208, 240, 16}, ""}; + Text text_regs_status{{0, 208, 240, 16}, "---"}; - Text text_lbl_regs_status{{0, 224, 48, 16}, "Regs:"}; - Text text_regs_status{{50, 224, 190, 16}, "---"}; + Text text_status2{{0, 224, 240, 16}, ""}; + Text text_status3{{0, 240, 240, 16}, ""}; Button button_refresh{{2, 280, 72, 24}, "Refresh"}; - Button button_force_enx{{98, 280, 60, 24}, "T_ENX"}; + Button button_force{{98, 280, 60, 24}, "SPI"}; Button button_done{{182, 280, 56, 24}, "Done"}; }; diff --git a/firmware/application/hw/max2831.cpp b/firmware/application/hw/max2831.cpp index 8ce7e0218..4d5073d0b 100644 --- a/firmware/application/hw/max2831.cpp +++ b/firmware/application/hw/max2831.cpp @@ -343,9 +343,6 @@ void MAX2831::set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) { _desired_lpf_bw = bandwidth_minimum; #ifdef PRALINE uint32_t actual_bw = bandwidth_minimum; - if (actual_bw < 22000000) { - actual_bw = 22000000; // Never go below 15 MHz, set by choosing 22.6 MHz bandwidth - } _desired_lpf_bw = actual_bw; if (_mode == Mode::Receive || _mode == Mode::Rx_Calibration) { diff --git a/firmware/application/hw/rffc507x.cpp b/firmware/application/hw/rffc507x.cpp index ef71ac239..e8c2041b4 100644 --- a/firmware/application/hw/rffc507x.cpp +++ b/firmware/application/hw/rffc507x.cpp @@ -197,19 +197,9 @@ struct SynthConfig { */ void RFFC507x::init() { -#ifdef PRALINE - // CRITICAL: Enable RFFC5072 BEFORE any SPI communication! - // Without this, SPI writes are ignored when ENX=1 (disabled) - gpio_rffc5072_enx.output(); - gpio_rffc5072_enx.clear(); // ENX=0 (enabled) - - // Small delay for chip to power up - chThdSleepMilliseconds(1); -#else gpio_rffc5072_resetx.set(); gpio_rffc5072_resetx.output(); reset(); -#endif _bus.init(); @@ -221,12 +211,10 @@ void RFFC507x::reset() { /* TODO: Is RESETB pin ignored if sdi_ctrl.sipin=1? Programming guide * description of sdi_ctrl.sipin suggests the pin is not ignored. */ -#ifndef PRALINE gpio_rffc5072_resetx.clear(); halPolledDelay(ticks_during_reset); gpio_rffc5072_resetx.set(); halPolledDelay(ticks_after_reset); -#endif } void RFFC507x::flush() { @@ -299,8 +287,6 @@ void RFFC507x::set_frequency(const rf::Frequency lo_frequency) { const SynthConfig synth_config = SynthConfig::calculate(lo_frequency); #ifdef PRALINE - // Ensure RFFC5072 is enabled before SPI writes - gpio_rffc5072_enx.clear(); // ENX=0 (enabled) // Calculate VCO frequency from LO frequency and divider const size_t lo_divider = 1U << synth_config.lo_divider_log2; // 2^lodiv_log2 diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index 09266320e..5585b5278 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -208,15 +208,18 @@ void set_direction(const rf::Direction new_direction) { } #ifdef PRALINE - // TEST: Force baseband invert for Praline (like r9) - // baseband_invert = (direction == rf::Direction::Receive); - // Praline: Control Q inversion via FPGA register - uint8_t ctrl_reg = 0x01; // DC_BLOCK enabled - if (mixer_invert ^ baseband_invert) { - ctrl_reg |= 0x02; // Set Q_INVERT bit + // 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; + if (q_invert) { + LPC_GPIO->SET[0] = (1 << 13); // SGPIO12 = 1 (Q inverted) + } else { + LPC_GPIO->CLR[0] = (1 << 13); // SGPIO12 = 0 (Q normal) } - fpga_debug_register_write(1, ctrl_reg); + ssp1_arbiter.invalidate(); #else baseband_cpld.set_invert(mixer_invert ^ baseband_invert); @@ -282,13 +285,19 @@ bool set_tuning_frequency(const rf::Frequency frequency) { // TEST: Force baseband invert for Praline (like r9) // baseband_invert = (direction == rf::Direction::Receive); - // PRALINE: Update FPGA Q inversion when tuning changes - uint8_t ctrl_reg = 0x01; // DC_BLOCK enabled - if (mixer_invert ^ baseband_invert) { - ctrl_reg |= 0x02; // Set Q_INVERT bit + // 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; + if (q_invert) { + LPC_GPIO->SET[0] = (1 << 13); // SGPIO12 = 1 (Q inverted) + } else { + LPC_GPIO->CLR[0] = (1 << 13); // SGPIO12 = 0 (Q normal) } - fpga_debug_register_write(1, ctrl_reg); + ssp1_arbiter.invalidate(); + // Log this value somewhere you can see it uint32_t written_r15 = first_if.read(15); #else diff --git a/firmware/application/rf_path.cpp b/firmware/application/rf_path.cpp index 51b092db5..0df0745ab 100644 --- a/firmware/application/rf_path.cpp +++ b/firmware/application/rf_path.cpp @@ -41,8 +41,6 @@ namespace { */ struct PralineConfig { bool tx_en; - // bool mix_en_n; // Inverted: 0 = mixer enabled - bool rffc_enx; // RFFC5072 ENX (GPIO2[13]) bool mix_bypass; // RF path mixer bypass (GPIO3[2]) bool lpf_en; bool rf_amp_en; @@ -50,8 +48,6 @@ struct PralineConfig { static void gpio_init() { gpio_tx_enable.output(); - // gpio_mix_enable_n.output(); - gpio_rffc5072_enx.output(); gpio_mix_bypass.output(); gpio_lpf_enable.output(); gpio_rf_amp_enable.output(); @@ -60,8 +56,6 @@ struct PralineConfig { void apply() const { gpio_tx_enable.write(tx_en); - // gpio_mix_enable_n.write(mix_en_n); - gpio_rffc5072_enx.write(rffc_enx); // Control RFFC5072 ENX gpio_mix_bypass.write(mix_bypass); // Control RF path mixer gpio_lpf_enable.write(lpf_en); gpio_rf_amp_enable.write(rf_amp_en); @@ -245,8 +239,6 @@ void Path::init() { /* Set safe initial state: RX mode, mixer enabled, LPF on, amp off, no bias */ PralineConfig config = { .tx_en = false, - //.mix_en_n = false, // Mixer enabled (inverted) - .rffc_enx = false, // RFFC5072 ENX (GPIO2[13]) .mix_bypass = false, // RF path mixer bypass (GPIO3[2]) .lpf_en = true, // LPF on for low band .rf_amp_en = false, // Amp off @@ -284,8 +276,6 @@ void Path::update() { #ifdef PRALINE /* PRALINE RF path control: * - tx_en: 1 for TX, 0 for RX - * // - mix_en_n: 0 to enable mixer (inverted), 1 to bypass - * - rffc_enx: 0 to enable RFFC5072 ENX (GPIO2[13]) * - mix_bypass: 0 to enable RF path mixer bypass (GPIO3[2]) * - lpf_en: 1 for low band (< 2.4 GHz), 0 for high band * - rf_amp_en: 1 to enable RF amplifier @@ -304,10 +294,6 @@ void Path::update() { config.tx_en = (direction == Direction::Transmit); - // RFFC5072 ENX: Active LOW, so invert the band check - // ENX=0 (enabled) for Low/High, ENX=1 (disabled) for Mid - config.rffc_enx = (band == Band::Mid); // 0=enabled, 1=disabled - // RF path mixer bypass: 0=enabled, 1=bypassed config.mix_bypass = (band == Band::Mid); diff --git a/firmware/application/tuning.cpp b/firmware/application/tuning.cpp index 462032851..e1b2d74ba 100644 --- a/firmware/application/tuning.cpp +++ b/firmware/application/tuning.cpp @@ -31,18 +31,86 @@ Config low_band(const rf::Frequency target_frequency); Config mid_band(const rf::Frequency target_frequency); Config high_band(const rf::Frequency target_frequency); -// Low band <2170 Mhz: +#ifdef PRALINE +/* + * PRALINE Tuning Configuration + * ============================ + * + * Reference: hackrf_usb/common/tune_config.h praline_tune_config_rx[] + * + * The hackrf_usb firmware uses a table-driven approach where each entry + * specifies: + * - rf_range_end_mhz: Upper frequency limit for this config + * - if_mhz: IF frequency (what MAX2831 tunes to) + * - high_lo: true = high-side injection, false = low-side + * - shift: FPGA quarter-shift mode (not implemented in Mayhem yet) + * + * Key insight: The IF frequency varies to keep the RFFC5072 VCO in a + * safe operating range (ideally 3500-5000 MHz, avoiding extremes). + * + * RFFC5072 VCO calculation: + * High-side injection: LO = IF + RF, VCO = LO × lodiv + * Low-side injection: LO = IF - RF, VCO = LO × lodiv + * Where lodiv = 2 for frequencies where VCO > 2700 MHz + * + * From hackrf_usb tune_config_rx (simplified): + * 0-2100 MHz: IF=2375, high_lo=true → VCO = (2375+RF)×2 + * 2105-2115: IF=2375, high_lo=false → VCO = (2375-RF)×2 + * 2115-2130: IF=2425, high_lo=false → VCO = (2425-RF)×2 + * ... (more entries for fine-grained control) + * 2320-2580: IF=0 (bypass mode, no mixer) + * 2580+: High-pass mode + */ + +// Simplified tune_config lookup for Mayhem +// Returns the IF frequency in Hz for a given target frequency +constexpr rf::Frequency praline_get_if_frequency(const rf::Frequency target_frequency) { + const uint32_t freq_mhz = target_frequency / 1'000'000; + + // Based on hackrf_usb tune_config_rx table + if (freq_mhz < 2100) { + // Most low-band frequencies: use 2375 MHz IF + // This keeps VCO around 4750-4950 MHz for FM band + return 2375'000'000; + } else if (freq_mhz < 2320) { + // Transition zone: use varying IF to avoid VCO edges + // These frequencies are tricky - near MAX2831 minimum + // Use 2425 MHz to give some margin + return 2425'000'000; + } else { + // Bypass mode or high-band - IF not used for mixer + return 0; + } +} + +// Returns true for high-side injection, false for low-side +constexpr bool praline_use_high_side_injection(const rf::Frequency target_frequency) { + const uint32_t freq_mhz = target_frequency / 1'000'000; + + // Based on hackrf_usb tune_config_rx table + if (freq_mhz < 2100) { + // Standard low-band: high-side injection + // LO = IF + RF, mixer inverts spectrum + return true; + } else if (freq_mhz < 2105) { + // Narrow transition: still high-side + return true; + } else if (freq_mhz < 2320) { + // Near MAX2831 minimum: use low-side injection + // LO = IF - RF, no spectrum inversion + return false; + } else { + // Bypass/high-band - doesn't matter, mixer bypassed + return false; + } +} +#endif // PRALINE + +// Low band <2170 Mhz (HackRF One) or <2320 MHz (PRALINE): constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_frequency) { #ifdef PRALINE - // Praline-specific formula for MAX2831 (2.3-2.6 GHz range) - // Use a fixed second_lo that: - // 1. Falls in MAX2831's sweet spot (2.3-2.6 GHz) - // 2. Gives RFFC5072 a VCO frequency in its range (2700-5400 MHz) - - // For most low-band frequencies, use 2500 MHz as second_lo - // This gives RFFC5072 plenty of headroom - (void)target_frequency; // Unused in fixed formula - return 2500'000'000; + // Use the tune_config lookup for PRALINE + return praline_get_if_frequency(target_frequency); #else return 2650'000'000 - (target_frequency / 7); #endif @@ -50,18 +118,36 @@ constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_ Config low_band(const rf::Frequency target_frequency) { const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency); + +#ifdef PRALINE + rf::Frequency first_lo_frequency; + bool mixer_invert; + + if (praline_use_high_side_injection(target_frequency)) { + // High-side injection: LO = IF + RF + first_lo_frequency = second_lo_frequency + target_frequency; + mixer_invert = true; + } else { + // Low-side injection: LO = IF - RF + first_lo_frequency = second_lo_frequency - target_frequency; + mixer_invert = false; + } + + return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert}; +#else const rf::Frequency first_lo_frequency = target_frequency + second_lo_frequency; const bool mixer_invert = true; return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert}; +#endif } -// Mid band 2170-2740 Mhz: +// Mid band 2170-2740 Mhz (HackRF One) or 2320-2580 MHz (PRALINE): Config mid_band(const rf::Frequency target_frequency) { #ifdef PRALINE // For Praline with MAX2831 (2.3-2.6 GHz range) // Frequencies 2170-2300 MHz need upconversion since they're below MAX2831 minimum if (target_frequency < 2300'000'000) { - // Treat as low band + // Treat as low band - need mixer return low_band(target_frequency); } // Frequencies 2300-2600 MHz can go direct (no RFFC5072) @@ -84,11 +170,16 @@ Config mid_band(const rf::Frequency target_frequency) { #endif } -// High band >2740 Mhz: +// High band >2740 Mhz (HackRF One) or >2580 MHz (PRALINE): constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) { #ifdef PRALINE // Praline formula tuned for MAX2831 (2.3-2.6 GHz range) // Keep second_lo in MAX2831's range while allowing RFFC5072 to work + // + // For high-band, we use LOW-side injection: LO = RF - IF + // So IF should be chosen to keep LO (and thus VCO) in a good range + // + // Based on hackrf_usb tune_config_tx patterns: if (target_frequency < 3600'000'000) return 2400'000'000 + ((target_frequency - 2740'000'000) / 4); else if (target_frequency < 5100'000'000) diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/board.cpp b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/board.cpp index 02ec6573f..43e1c9cd0 100755 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/board.cpp +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/board.cpp @@ -906,19 +906,19 @@ extern "C" void boardInit(void) { LPC_SCU->SFSP[6][7] = 0xF4; /* SCU_GPIO_FAST | FUNCTION4 */ LPC_GPIO->DIR[5] |= (1 << 15); LPC_GPIO->CLR[5] = (1 << 15); /* Clear = enable 3.3V aux */ - { volatile uint32_t delay = 100000; while(delay--); } + { volatile uint32_t delay = 200000; while(delay--); } /* Enable 1.2V for FPGA - P8_7 = GPIO4[7], active high */ LPC_SCU->SFSP[8][7] = 0x10; LPC_GPIO->DIR[4] |= (1 << 7); LPC_GPIO->SET[4] = (1 << 7); - { volatile uint32_t delay = 100000; while(delay--); } + { volatile uint32_t delay = 200000; while(delay--); } /* Enable VAA for RF - P8_1 = GPIO4[1], active low */ LPC_SCU->SFSP[8][1] = 0x10; LPC_GPIO->DIR[4] |= (1 << 1); LPC_GPIO->CLR[4] = (1 << 1); - { volatile uint32_t delay = 100000; while(delay--); } + { volatile uint32_t delay = 200000; while(delay--); } /* Configure RFFC5072 pins for PRALINE */ /* Set GPIO directions for RFFC5072 SPI pins */ @@ -937,6 +937,7 @@ extern "C" void boardInit(void) { /* SCU configured in PAL array above with mode=4 */ LPC_GPIO->CLR[5] = (1 << 6); /* Default low (mixer enabled) */ LPC_GPIO->DIR[5] |= (1 << 6); /* Output */ + { volatile uint32_t delay = 200000; while(delay--); } /* Configure Port D pins for PRALINE (use SFSPD registers) */ /* PD_14 = GPIO6[28] MAX2831 chip select */ @@ -953,6 +954,7 @@ extern "C" void boardInit(void) { LPC_SCU->SFSPD[16] = 0xF4; /* SCU_GPIO_FAST | FUNCTION4 */ LPC_GPIO->SET[6] |= (1 << 30); /* CS high (inactive) */ LPC_GPIO->DIR[6] |= (1 << 30); /* Output */ + { volatile uint32_t delay = 200000; while(delay--); } /* Configure Port E pins for MAX2831 control (use SFSPE registers) */ /* PE_1 = GPIO7[1] MAX2831 ENABLE */ @@ -963,6 +965,7 @@ extern "C" void boardInit(void) { LPC_SCU->SFSPE[2] = 0xF4; /* SCU_GPIO_FAST | FUNCTION4 */ LPC_GPIO->CLR[7] = (1 << 2); /* Start in shutdown mode */ LPC_GPIO->DIR[7] |= (1 << 2); /* Output */ + { volatile uint32_t delay = 200000; while(delay--); } /* Configure Port 6 pins for RF path control */ /* P6_3 = GPIO3[2] Mixer enable (inverted: 0 = mixer ON) */ @@ -973,6 +976,7 @@ extern "C" void boardInit(void) { LPC_SCU->SFSP[6][5] = 0xF0; /* SCU_GPIO_FAST | FUNCTION0 */ LPC_GPIO->CLR[3] = (1 << 4); /* TX off by default (RX mode) */ LPC_GPIO->DIR[3] |= (1 << 4); /* Output */ + { volatile uint32_t delay = 200000; while(delay--); } /* Configure Port A pins for RF path control */ /* PA_1 = GPIO4[8] LPF enable */ @@ -983,22 +987,30 @@ extern "C" void boardInit(void) { LPC_SCU->SFSP[0xA][2] = 0xF0; /* SCU_GPIO_FAST | FUNCTION0 */ LPC_GPIO->CLR[4] = (1 << 9); /* RF amp off by default */ LPC_GPIO->DIR[4] |= (1 << 9); /* Output */ + { volatile uint32_t delay = 200000; while(delay--); } /* Configure RFFC5072 control pins for PRALINE */ - /* P5_4 = GPIO2[13] RFFC5072 ENX (active low: 0=enabled) */ - LPC_SCU->SFSP[5][4] = 0xF0; /* FUNCTION0 (GPIO), no pulls */ + /* P5_4 = GPIO2[13] RFFC5072 ENX - SPI chip select (managed by SPI driver) */ + LPC_SCU->SFSP[5][4] = 0x10; /* FUNCTION0 (GPIO), pull-up, slow mode (matches HackRF USB) */ LPC_GPIO->DIR[2] |= (1 << 13); /* ENX: OUTPUT */ - LPC_GPIO->CLR[2] = (1 << 13); /* ENX = 0 (ENABLED) */ - - /* P5_5 = GPIO2[14] RFFC5072 RESETX - FPGA controlled, MCU should not touch */ - LPC_SCU->SFSP[5][5] = 0xF0; /* FUNCTION0 (GPIO), no pulls */ - LPC_GPIO->DIR[2] &= ~(1 << 14); /* RESETX: INPUT (let FPGA control) */ - + LPC_GPIO->SET[2] = (1 << 13); /* ENX = 1 (deselected initially) */ + { volatile uint32_t delay = 200000; while(delay--); } + + /* P5_5 = GPIO2[14] RFFC5072 RESETX (active high: 1=running) */ + LPC_SCU->SFSP[5][5] = 0x10; /* FUNCTION0 (GPIO), pull-up, slow mode (matches HackRF USB) */ + LPC_GPIO->DIR[2] |= (1 << 14); /* RESETX: OUTPUT */ + LPC_GPIO->SET[2] = (1 << 14); /* RESETX = 1 (RUNNING) */ + { volatile uint32_t delay = 200000; while(delay--); } + + /* Ensure RESETX is stable */ + for (volatile int i = 0; i < 10; i++) { + LPC_GPIO->W2[14] = 1; + } + /* PD_11 = GPIO6[25] RFFC5072 Lock Detect (input) */ - LPC_SCU->SFSP[0xD][11] = 0xF4; /* FUNCTION4 (GPIO), no pulls */ - - /* Small delay for signals to stabilize */ - for (volatile int i = 0; i < 10000; i++) {} + LPC_SCU->SFSPD[11] = 0x10; /* FUNCTION0 (GPIO), pull-up (matches HackRF USB) */ + LPC_GPIO->DIR[6] &= ~(1 << 25); /* LD: INPUT */ + { volatile uint32_t delay = 200000; while(delay--); } /* Configure PRALINE-specific SGPIO pins for FPGA sample interface. * These override the HackRF One pin config from pins_setup. @@ -1018,6 +1030,7 @@ extern "C" void boardInit(void) { LPC_SCU->SFSP[8][2] = 0xF4; /* SCU_GPIO_FAST | func 4 */ /* SGPIO11 = P1_17 function 6 (HOST_DIRECTION - output to FPGA, tells FPGA TX vs RX) */ LPC_SCU->SFSP[1][17] = 0xF6; /* SCU_GPIO_FAST | func 6 */ + { volatile uint32_t delay = 200000; while(delay--); } /* SGPIO data pins (SGPIO0-7) - all 8 bits required for sample data */ LPC_SCU->SFSP[0][0] = 0xF3; /* SGPIO0: P0_0 function 3, HOST_DATA0 */ @@ -1028,6 +1041,7 @@ extern "C" void boardInit(void) { LPC_SCU->SFSP[6][6] = 0xF2; /* SGPIO5: P6_6 function 2, HOST_DATA5 */ LPC_SCU->SFSP[2][2] = 0xF0; /* SGPIO6: P2_2 function 0, HOST_DATA6 */ LPC_SCU->SFSP[1][0] = 0xF6; /* SGPIO7: P1_0 function 6, HOST_DATA7 */ + { volatile uint32_t delay = 200000; while(delay--); } // Trigger FPGA bitstream loading via fpga bridge // Attempt to load the FPGA bitstream @@ -1041,11 +1055,13 @@ extern "C" void boardInit(void) { // Turn off all LEDs to start // PRALINE LEDs are active-low: SET (HIGH) = OFF, CLR (LOW) = ON LPC_GPIO->SET[2] = (1 << 1) | (1 << 2) | (1 << 8); + { volatile uint32_t delay = 200000; while(delay--); } // Call fpga_bridge_init and continue boot regardless of result // (Watchdog was resetting device when we halted with while(1)) int load_result = fpga_bridge_init(); (void)load_result; // Ignore result for now, just let boot continue + { volatile uint32_t delay = 200000; while(delay--); } // Keep LEDs off after FPGA load LPC_GPIO->SET[2] = (1 << 1) | (1 << 2) | (1 << 8); diff --git a/firmware/common/hackrf_gpio.hpp b/firmware/common/hackrf_gpio.hpp index dedc6f4fe..459bea7c5 100644 --- a/firmware/common/hackrf_gpio.hpp +++ b/firmware/common/hackrf_gpio.hpp @@ -73,16 +73,13 @@ constexpr GPIO gpio_amp_bypass = gpio[GPIO0_14]; constexpr GPIO gpio_not_rx_amp_pwr = gpio[GPIO1_12]; constexpr GPIO gpio_not_tx_amp_pwr = gpio[GPIO3_5]; -#ifndef PRALINE -constexpr GPIO gpio_rffc5072_resetx = gpio[GPIO2_14]; -#endif - #ifdef PRALINE -constexpr GPIO gpio_rffc5072_enx = gpio[GPIO2_13]; // P5_4: RFFC5072 ENX (active LOW) -constexpr GPIO gpio_rffc5072_select = gpio[GPIO2_13]; // P5_4: ENX doubles as SPI strobe -// constexpr GPIO gpio_rffc5072_select = gpio[GPIO2_7]; // P5_7: PRALINE CS +// PRALINE: GPIO2[13] is SPI CS only, FPGA controls ENX/RESETX +constexpr GPIO gpio_rffc5072_select = gpio[GPIO2_13]; // P5_4: SPI CS (ENX) +constexpr GPIO gpio_rffc5072_resetx = gpio[GPIO2_14]; // P5_5: LPC43xx controls directly #else constexpr GPIO gpio_rffc5072_select = gpio[GPIO2_13]; +constexpr GPIO gpio_rffc5072_resetx = gpio[GPIO2_14]; #endif #ifdef PRALINE diff --git a/firmware/flashsize.h b/firmware/flashsize.h new file mode 100644 index 000000000..dadfa980d --- /dev/null +++ b/firmware/flashsize.h @@ -0,0 +1,9 @@ +#pragma once +// DO NOT EDIT: IT IS AUTO GENERATED BY CMAKE!!!! + +// clang-format off +//Allowed fw size in MB +#define FLASH_SIZE_MB 2 +//Current compiled fw size in MB +#define FLASH_SIZE_LIMIT_MB 1 +// clang-format on