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