diff --git a/firmware/application/apps/ble_rx_app.cpp b/firmware/application/apps/ble_rx_app.cpp index b16cf913b..f553061f3 100644 --- a/firmware/application/apps/ble_rx_app.cpp +++ b/firmware/application/apps/ble_rx_app.cpp @@ -645,6 +645,20 @@ BLERxView::BLERxView(NavigationView& nav) options_sort.set_selected_index(sort_index, true); options_filter.set_selected_index(filter_index, true); + // ------------------------------------------------------------------------------ + // Handle Max Recent Entries + // ------------------------------------------------------------------------------ + + // Max Recent Entries UI widget + add_child(&label_max_entries); + add_child(&field_max_entries); + field_max_entries.set_value(max_recent_entries); + + // Define what happens when the user changes the number + field_max_entries.on_change = [this](int32_t v) { + max_recent_entries = (size_t)v; + }; + // Auto-configure modem for LCR RX (will be removed later) baseband::set_btlerx(channel_number); @@ -819,6 +833,10 @@ void BLERxView::on_data(BlePacketData* packet) { recent.push_front(*it); recent.erase(it); } else { + // Enforce limit + while (recent.size() >= max_recent_entries) { + recent.pop_back(); + } recent.emplace_front(key); truncate_entries(recent); } @@ -887,6 +905,9 @@ void BLERxView::on_filter_change(std::string value) { } void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) { + // Clear searchList + searchList.clear(); + file_path = new_file_path; found_count = 0; total_count = 0; @@ -919,8 +940,10 @@ void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) { break; } - searchList.push_back(currentLine); - total_count++; + if (searchList.size() < max_recent_entries) { + searchList.push_back(currentLine); + total_count++; + } bytePos += bytesRead; @@ -940,6 +963,21 @@ void BLERxView::on_timer() { channel_number = (channel_number < 39) ? channel_number + 1 : 37; } } + + // Debug: Check heap status every ~1 second + static int heap_check_counter = 0; + if (++heap_check_counter >= 60) { + heap_check_counter = 0; + size_t heap_free = chCoreStatus(); + if (heap_free < 4096) { // Less than 4KB free + // Emergency: clear old entries + while (recent.size() > (max_recent_entries - 1)) { + recent.pop_back(); + } + recent_entries_view.set_dirty(); + } + } + if (ble_rx_error != BLE_RX_NO_ERROR) { if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) { nav_.display_modal("Error", "List filename is empty !"); @@ -1059,7 +1097,6 @@ bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0)); entry.timestamp = to_string_timestamp(rtc_time::now()); entry.dataString = data_string; - entry.packetData.type = packet->type; entry.packetData.size = packet->size; entry.packetData.dataLen = packet->dataLen; diff --git a/firmware/application/apps/ble_rx_app.hpp b/firmware/application/apps/ble_rx_app.hpp index 5c4ac8c14..bc9163d22 100644 --- a/firmware/application/apps/ble_rx_app.hpp +++ b/firmware/application/apps/ble_rx_app.hpp @@ -42,6 +42,12 @@ #include "recent_entries.hpp" +// Add for heap debugging +// For ChibiOS core functions +#include "ch.h" +#include "chcore.h" +#include "chheap.h" + class BLELogger { public: Optional append(const std::filesystem::path& filename) { @@ -90,6 +96,7 @@ struct BleRecentEntry { uint64_t uniqueKey; int dbValue; BlePacketData packetData; + std::string timestamp; std::string dataString; std::string nameString; @@ -426,6 +433,20 @@ class BLERxView : public View { [this](const Message* const) { this->on_timer(); }}; + + // Widget to control the list limit + Labels label_max_entries{ + {{UI_POS_X(22), 10 * 8 - 2}, "List:", Theme::getInstance()->fg_light->foreground}}; + NumberField field_max_entries{ + {UI_POS_X(28), 10 * 8 - 2}, // Correct: Position (x, y) + 2, // Number of digits + {5, 64}, // Range (min, max) <- Coincides with max_entries + 1, // Step size + ' ' // Filler character + }; + + // Variable to store the current limit + size_t max_recent_entries = 32; }; /* BLERxView */ } /* namespace ui */ diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp index 6ede43f6c..b3dae22a4 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -3478,30 +3478,48 @@ DebugMenuView::DebugMenuView(NavigationView& nav) set_max_rows(2); // allow wider buttons } +#ifdef PRALINE +/* PralineDebugMenuView *************************************************/ + +PralineDebugMenuView::PralineDebugMenuView(NavigationView& nav) + : nav_(nav) { + set_max_rows(2); // Allows wider buttons for descriptive titles +} + +void PralineDebugMenuView::on_populate() { + if (portapack::persistent_memory::show_gui_return_icon()) { + add_items({{"..", ui::Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}}); + } + add_items({ + {"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"MSynth", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Radio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Signal Path", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"PLL A", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"PLL B", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"GPIO", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"RFFC Status", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"RFFC Tuning", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"MAX2831", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Si5351", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"SGPIO Clk", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Baseband", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"SGPIO Live", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"RX Test", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + }); +} +#endif + void DebugMenuView::on_populate() { if (portapack::persistent_memory::show_gui_return_icon()) { add_items({{"..", ui::Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}}); } add_items({ #ifdef PRALINE - {"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"MSynth Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Radio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Signal Path", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"PLL A Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"PLL B Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"GPIO Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"RFFC Status", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"RFFC Tuning", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"MAX2831 Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Si5351 Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"SGPIO8 Clock", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"Baseband Status", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"SGPIO Live", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, - {"RX Test", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, + {"Pro Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_setup, [this]() { nav_.push(); }}, #endif {"Buttons Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_controls, [this]() { nav_.push(); }}, {"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }}, diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp index 413434a63..85e93c710 100644 --- a/firmware/application/apps/ui_debug.hpp +++ b/firmware/application/apps/ui_debug.hpp @@ -439,6 +439,16 @@ class RadioDiagnosticsView : public View { }; #ifdef PRALINE +class PralineDebugMenuView : public BtnGridView { + public: + PralineDebugMenuView(NavigationView& nav); + std::string title() const override { return "Pro Debug"; }; + + private: + NavigationView& nav_; + void on_populate() override; +}; + /* Praline-Specific Radio Debug View * Monitors Mixer Lock, SPI Bit Depth, and toggles Si5351 CLK5 */ diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index b9412a2c0..51dca3007 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -821,8 +821,26 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) { // === 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); + } + // Set FPGA RX decimation register - fpga_debug_register_write(2, n); + 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); + radio::invalidate_spi_config(); // Configure Si5351 clocks @@ -892,7 +910,6 @@ void ClockManager::set_reference_ppb(const int32_t ppb) { #ifdef PRALINE clock_generator.write_pll_single_byte(0, pll); - clock_generator.reset_plls(); #else const auto pll_a_reg = pll.reg(0); clock_generator.write(pll_a_reg); diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index 4873503ec..2f6242946 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -180,12 +180,21 @@ void init() { /* Initialize FPGA registers - DC_BLOCK must be enabled for RX */ // debug::fpga::init(); + + fpga_set_mode(FPGA_MODE_RX); + // These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation. - fpga_debug_register_write(1, 0x00); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0 - fpga_debug_register_write(2, 0x00); // RX_DECIM=No Decim - fpga_debug_register_write(3, 0x00); // TX_CTRL=0 - fpga_debug_register_write(4, 0x00); // TX_INTRP=0 - fpga_debug_register_write(5, 0x00); // TX_PSTEP=0 + fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0 + fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim + + // RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift). + fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN); + + /* RX Mode: Initialize DC Block parameters to standard Praline values. + * 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability. + */ + fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH); + fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE); ssp1_arbiter.invalidate(); chThdSleepMilliseconds(10); // Let FPGA registers settle @@ -206,6 +215,24 @@ void set_direction(const rf::Direction new_direction) { #ifdef PRALINE cached_direction = new_direction; // Track state for debug and potentially other purposes. + + if (new_direction == rf::Direction::Transmit) { + fpga_set_mode(FPGA_MODE_TX); + // TX Mode: Clear RX gain and ensure NCO is off initially + fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00); + // Placeholder: Set TX-specific interpolation and phase + fpga_debug_register_write(FPGA_REG_TX_INTERP, 0x00); + fpga_debug_register_write(FPGA_REG_TX_PHASE_STEP, 0x00); + } else { + fpga_set_mode(FPGA_MODE_RX); + // RX Mode: Ensure NCO is disabled and reset digital gain + fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN); + /* RX Mode: Initialize DC Block parameters to standard Praline values. + * 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability. + */ + fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH); + fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE); + } #endif direction = new_direction; @@ -519,14 +546,22 @@ void register_write(const size_t register_number, uint32_t value) { } void init() { - // Initialize FPGA registers after bitstream load - // DC_BLOCK (bit 0) must be enabled for RX to work - fpga_debug_register_write(1, 0x00); // CTRL: DC_BLOCK=1 - fpga_debug_register_write(2, 0x00); // RX_DECIM: no decimation - fpga_debug_register_write(3, 0x00); // TX_CTRL: NCO disabled - fpga_debug_register_write(4, 0x00); // TX_INTRP: no interpolation - fpga_debug_register_write(5, 0x00); // TX_PSTEP: zero phase step - ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer + fpga_set_mode(FPGA_MODE_RX); + + // These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation. + fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0 + fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM=No Decim + + // RX Mode: Register 3 is RX Digital Gain. Start with 0dB (no shift). + fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, FPGA_RX_DEFAULT_DIGITAL_GAIN); + + /* RX Mode: Initialize DC Block parameters to standard Praline values. + * 0x04 Width and 0x08 Adapt Rate are typical for 40MHz stability. + */ + fpga_debug_register_write(FPGA_REG_RX_DC_BLOCK_WIDTH, FPGA_RX_DEFAULT_DC_WIDTH); + fpga_debug_register_write(FPGA_REG_RX_DC_ADAPT_RATE, FPGA_RX_DEFAULT_ADAPT_RATE); + + ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer } } /* namespace fpga */ diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c index afdc6cd9e..b64e0dacd 100644 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c @@ -3,6 +3,7 @@ // Check if PRALINE was passed from CMake #ifdef PRALINE + #include "fpga_bridge.h" // Necessary headers #include "lz4_blk.h" @@ -120,6 +121,27 @@ #define FPGA_CDONE_PIN 14 #define FPGA_SPI_CS_PORT 2 #define FPGA_SPI_CS_PIN 10 + + // ============================================================================ + // Canonical Default Values - SINGLE SOURCE OF TRUTH + // ============================================================================ + /* Define the canonical RX defaults in ONE place */ + #define FPGA_RX_DEFAULT_DC_WIDTH 0x04 /* Typical for 40MHz stability */ + #define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 /* Typical for 40MHz stability */ + #define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 /* No shift initially */ + + /* Define TX defaults */ + #define FPGA_TX_DEFAULT_NCO_CTRL 0x00 /* NCO disabled */ + #define FPGA_TX_DEFAULT_INTERP 0x00 /* No interpolation */ + #define FPGA_TX_DEFAULT_PHASE_STEP 0x00 /* Zero phase step */ + + + // ============================================================================ + // Static Variables - Declare BEFORE use + // ============================================================================ + 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}; // Context structure for SPIFI-based reading struct spifi_fpga_read_ctx { @@ -129,6 +151,10 @@ uint8_t buffer[4096 + 2]; // Compressed block + next size }; + // ============================================================================ + // Low-Level Helper Functions + // ============================================================================ + // Simple delay loop static void delay_cycles(volatile uint32_t count) { while (count--) { @@ -224,14 +250,19 @@ // FPGA Register Map: // Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7) // Reg 2 (RX_DECIM): Decimation ratio [2:0] - // Reg 3 (TX_CTRL): NCO_EN(b0) - // Reg 4 (TX_INTRP): Interpolation ratio [2:0] - // Reg 5 (TX_PSTEP): NCO phase step [7:0] + // Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control + // Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0] + // Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0] // // SPI Protocol: // Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3 // Write: Send [(reg | 0x80), value, 0x00] + + // ============================================================================ + // SPI Mode Switching + // ============================================================================ + // Configure SSP1 for iCE40 FPGA register access (Mode 3, 8-bit) static void ssp1_set_mode_ice40(void) { SSP1_CR1_LOCAL = 0; // Disable SSP1 @@ -252,6 +283,11 @@ SSP1_CR1_LOCAL = SSP_CR1_SSE; // Enable SSP1 } + + // ============================================================================ + // Low-Level SPI Register Access (internal, no mode switch) + // ============================================================================ + // Read an FPGA register via SPI static uint8_t fpga_spi_read(uint8_t reg) { uint8_t value; @@ -271,31 +307,10 @@ ssp1_transfer_byte(0x00); // Dummy byte fpga_cs_high(); } - - // Initialize FPGA registers after bitstream load - // This is equivalent to fpga_init() in the reference HackRF firmware - static void fpga_register_init(void) { - // Already in iCE40 mode after programming, so we can directly access registers - - // Register 1 (CTRL): Enable DC block (bit 0), disable everything else - // DC_BLOCK is CRITICAL for RX to work! - fpga_spi_write(1, 0x01); // DC_BLOCK = 1 - - // Register 2 (RX_DECIM): No decimation - fpga_spi_write(2, 0x00); - - // Register 3 (TX_CTRL): Disable NCO - fpga_spi_write(3, 0x00); - - // Register 4 (TX_INTRP): No interpolation - fpga_spi_write(4, 0x00); - - // Register 5 (TX_PSTEP): Zero phase step - fpga_spi_write(5, 0x00); - } - - // 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}; + + // ============================================================================ + // Public Debug Functions (switch SPI mode, access register, switch back) + // ============================================================================ // Public function to read FPGA register (callable from C++ application code) // Switches SPI mode, reads register, switches back @@ -322,6 +337,139 @@ fpga_reg_cache[reg] = value; } + // ============================================================================ + // Public Register Access (wraps debug functions) + // ============================================================================ + + uint8_t fpga_register_read(uint8_t reg) { + if (reg == 0 || reg > 5) return; + + ssp1_set_mode_ice40(); + uint8_t val = fpga_spi_read(reg); + ssp1_set_mode_max2831(); + + fpga_reg_cache[reg] = val; + return val; + } + + void fpga_register_write(uint8_t reg, uint8_t value) { + if (reg == 0 || reg > 5) return; + + ssp1_set_mode_ice40(); + fpga_spi_write(reg, value); + ssp1_set_mode_max2831(); + + fpga_reg_cache[reg] = value; + } + + // ============================================================================ + // Mode Management + // ============================================================================ + + fpga_mode_t fpga_get_mode(void) { + return current_mode; + } + + /* fpga_set_mode with consistent values */ + void fpga_set_mode(fpga_mode_t mode) { + current_mode = mode; + } + + // ============================================================================ + // RX Mode Functions (with mode assertion) + // ============================================================================ + + /* RX decimation */ + void fpga_rx_set_decimation(uint8_t ratio) { + if (current_mode != FPGA_MODE_RX) return; + fpga_register_write(FPGA_REG_DECIM, ratio & 0x07); + } + + /* RX DC block enable (bit 0 of register 1) */ + void fpga_rx_enable_dc_block(bool enable) { + if (current_mode != FPGA_MODE_RX) return; + uint8_t ctrl = fpga_register_read(FPGA_REG_CTRL); + if (enable) + ctrl |= FPGA_CTRL_DC_BLOCK_EN; + else + ctrl &= ~FPGA_CTRL_DC_BLOCK_EN; + fpga_register_write(FPGA_REG_CTRL, ctrl); + } + + /* RX Functions with mode assertion */ + void fpga_rx_set_digital_gain(uint8_t shift) { + if (current_mode != FPGA_MODE_RX) { + /* Log error or assert - wrong mode! */ + return; + } + fpga_register_write(FPGA_REG_SHARED_3, shift & FPGA_RX_GAIN_SHIFT_MASK); + } + + void fpga_rx_set_dc_block_width(uint8_t width) { + if (current_mode != FPGA_MODE_RX) return; + fpga_register_write(FPGA_REG_SHARED_4, width & FPGA_RX_DC_WIDTH_MASK); + } + + void fpga_rx_set_dc_adapt_rate(uint8_t rate) { + if (current_mode != FPGA_MODE_RX) return; + fpga_register_write(FPGA_REG_SHARED_5, rate); + } + + // ============================================================================ + // TX Mode Functions (with mode assertion) + // ============================================================================ + + /* 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); + if (enable) + val |= FPGA_TX_NCO_EN; + else + val &= ~FPGA_TX_NCO_EN; + fpga_register_write(FPGA_REG_SHARED_3, 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); + } + + 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 Initialization (called after bitstream load) + // ============================================================================ + + // Initialize FPGA registers after bitstream load + // This is equivalent to fpga_init() in the reference HackRF firmware + /* fpga_register_init with consistent values */ + static void fpga_register_init(void) { + + /* Boot into RX mode */ + current_mode = FPGA_MODE_RX; + + fpga_spi_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); + fpga_spi_write(FPGA_REG_DECIM, 0x00); + fpga_spi_write(FPGA_REG_SHARED_3, FPGA_RX_DEFAULT_DIGITAL_GAIN); + fpga_spi_write(FPGA_REG_SHARED_4, FPGA_RX_DEFAULT_DC_WIDTH); + fpga_spi_write(FPGA_REG_SHARED_5, FPGA_RX_DEFAULT_ADAPT_RATE); + + /* Update cache */ + fpga_reg_cache[1] = FPGA_CTRL_DC_BLOCK_EN; + fpga_reg_cache[2] = 0x00; + fpga_reg_cache[3] = FPGA_RX_DEFAULT_DIGITAL_GAIN; + fpga_reg_cache[4] = FPGA_RX_DEFAULT_DC_WIDTH; + fpga_reg_cache[5] = FPGA_RX_DEFAULT_ADAPT_RATE; + } + + // ============================================================================ + // LZ4 Decompression for FPGA Bitstream + // ============================================================================ + // SPIFI-based read callback for LZ4 decompression // Reads from SPIFI memory-mapped address instead of using SPI flash driver static size_t spifi_fpga_read_block_cb(void* _ctx, uint8_t* out_buffer) { @@ -415,6 +563,10 @@ return success; } + // ============================================================================ + // Main Initialization Entry Point + // ============================================================================ + int fpga_bridge_init(void) { // Enable SSP1 clock for FPGA programming // Use PLL1 (204MHz) to match original HackRF - IRC (12MHz) is 17x too slow diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h index 24099d465..e7fb0bf6c 100644 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h @@ -12,15 +12,131 @@ extern "C" { #endif #include +#include #ifdef PRALINE +/* FPGA Register Map Address 0x03 (Dual Purpose) */ +#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) */ + /* - * Initialize the FPGA - loads bitstream from SPIFI flash - * Returns: 0 on success, non-zero on failure + * FPGA Operating Mode */ +typedef enum { + FPGA_MODE_OFF = 0, + FPGA_MODE_RX, + FPGA_MODE_TX +} fpga_mode_t; + +/* + * FPGA Register Addresses + * Note: Registers 3-5 are dual-purpose (meaning depends on RX/TX mode) + */ +#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 */ + +/* + * Register 1 (CTRL) Bit Definitions + */ +#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */ +#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */ +#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */ +#define FPGA_CTRL_TX_MODE (1 << 5) /* TX mode indicator (if applicable) */ +#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */ +#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */ + +/* + * Register 3 Dual-Purpose Definitions + */ +/* RX Mode: Digital gain/shift */ +#define FPGA_REG3_RX_DIGITAL_GAIN 0x03 +#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */ + +/* TX Mode: NCO control */ +#define FPGA_REG3_TX_NCO_CTRL 0x03 +#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */ +#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */ + +/* + * Register 4 Dual-Purpose Definitions + */ +/* RX Mode: DC block notch width */ +#define FPGA_REG4_RX_DC_WIDTH 0x04 +#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */ + +/* TX Mode: Interpolation ratio */ +#define FPGA_REG4_TX_INTERP 0x04 +#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */ + +/* + * Register 5 Dual-Purpose Definitions + */ +/* RX Mode: DC block adaptation rate */ +#define FPGA_REG5_RX_DC_RATE 0x05 +#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_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */ + +/* Export default values so other methods can use them */ +#define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 +#define FPGA_RX_DEFAULT_DC_WIDTH 0x04 +#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 + +/* + * Core Functions + */ + +/* Initialize the FPGA - loads bitstream from SPIFI flash + * Returns: 0 on success, non-zero on failure */ int fpga_bridge_init(void); +/* Set operating mode - MUST be called before using mode-specific functions + * This ensures registers 3-5 are interpreted correctly */ +void fpga_set_mode(fpga_mode_t mode); + +/* Get current operating mode */ +fpga_mode_t fpga_get_mode(void); + +/* + * Low-Level Register Access (use with caution) + */ +uint8_t fpga_register_read(uint8_t reg); +void fpga_register_write(uint8_t reg, uint8_t value); + +/* + * RX Mode Functions (only valid when mode == FPGA_MODE_RX) + */ +void fpga_rx_set_decimation(uint8_t ratio); +void fpga_rx_set_digital_gain(uint8_t shift); +void fpga_rx_set_dc_block_width(uint8_t width); +void fpga_rx_set_dc_adapt_rate(uint8_t rate); +void fpga_rx_enable_dc_block(bool enable); + +/* + * TX Mode Functions (only valid when mode == FPGA_MODE_TX) + */ +void fpga_tx_set_interpolation(uint8_t ratio); +void fpga_tx_set_nco_enable(bool enable); +void fpga_tx_set_phase_step(uint8_t step); + +/* + * Debug Functions + */ + /* * Read an FPGA register via SPI * reg: Register number (1-5) @@ -29,9 +145,9 @@ int fpga_bridge_init(void); * FPGA Register Map: * Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7) * Reg 2 (RX_DECIM): Decimation ratio [2:0] - * Reg 3 (TX_CTRL): NCO_EN(b0) - * Reg 4 (TX_INTRP): Interpolation ratio [2:0] - * Reg 5 (TX_PSTEP): NCO phase step [7:0] + * Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control + * Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0] + * Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0] */ uint8_t fpga_debug_register_read(uint8_t reg); diff --git a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M0/ld/LPC43xx_M0.ld b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M0/ld/LPC43xx_M0.ld index b81e61ed0..03c185ef2 100755 --- a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M0/ld/LPC43xx_M0.ld +++ b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M0/ld/LPC43xx_M0.ld @@ -24,7 +24,15 @@ __process_stack_size__ = 0x1000; /* main() stack */ MEMORY { flash (rx) : org = 0x00000000, len = LD_FLASH_SIZE /* SPIFI flash @ 0x140????? */ - ram (rwx) : org = 0x20000000, len = 64k /* AHB SRAM @ 0x20000000 */ + + /* HackRF One (Standard): 64KB AHB SRAM @ 0x20000000 + * HackRF Pro (Praline): Hardware supports 128KB high-throughput buffers. + * The actual size is controlled by LD_RAM_SIZE (set in rules.cmake). + * AHB SRAM @ 0x20000000 (stacks, data, bss) */ + ram (rwx) : org = 0x20000000, len = LD_RAM_SIZE + + /* Local SRAM slice for extended heap (0 length on HackRF One) */ + ram_local (rwx) : org = LD_M0_LOCAL_HEAP_ORIGIN, len = LD_M0_LOCAL_HEAP_SIZE } __ram_start__ = ORIGIN(ram); @@ -151,5 +159,10 @@ SECTIONS PROVIDE(end = .); _end = .; -__heap_base__ = _end; -__heap_end__ = __ram_end__; +/* + * Heap configuration: + * - If ram_local has size > 0: use Local SRAM for heap (larger, Praline) + * - Otherwise: use remaining AHB SRAM after bss (HackRF One) + */ +__heap_base__ = (LD_M0_LOCAL_HEAP_SIZE > 0) ? ORIGIN(ram_local) : _end; +__heap_end__ = (LD_M0_LOCAL_HEAP_SIZE > 0) ? (ORIGIN(ram_local) + LENGTH(ram_local)) : __ram_end__; diff --git a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4.ld b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4.ld index 145be5c67..c4bafd761 100755 --- a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4.ld +++ b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4.ld @@ -23,12 +23,23 @@ __process_stack_size__ = 0x1000; /* main() stack */ MEMORY { - flash (rx) : org = 0x00000000, len = 32752 /* Local SRAM @ 0x10080000 */ - ram (rwx) : org = 0x10000000, len = 96k /* Local SRAM @ 0x10000000 */ - ram_usb (rwx) : org = 0x20008000, len = 32K + /* flash (rx) is actually Local SRAM Bank 2 (shadowed to 0x0 code/rodata) */ + flash (rx) : org = 0x00000000, len = LD_M4_FLASH_LEN /* Local SRAM @ 0x10080000 */ + + /* ram (rwx) is + * Local SRAM Bank 1 @ 0x10000000 (data/bss/stacks) + * Note: On Praline, M0 local heap uses 0x10018000-0x1001FFFF */ + ram (rwx) : org = 0x10000000, len = LD_M4_RAM_LEN /* Local SRAM @ 0x10000000 */ + + /* ram_usb is AHB SRAM Bank 1 @ 0x20008000 (USB buffer) + * HackRF One: 16KB?? originally set to 32KB, so left alone + * Praline: same configuration as HackRF One, while M0 uses all AHB SRAM) */ + ram_usb (rwx) : org = 0x20008000, len = LD_USB_RAM_LEN } +/* Only meaningful if LD_USB_RAM_LEN > 0 */ usb_bulk_buffer = ORIGIN(ram_usb); + __ram_start__ = ORIGIN(ram); __ram_size__ = LENGTH(ram); __ram_end__ = __ram_start__ + __ram_size__; diff --git a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4_ram.ld b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4_ram.ld index 7ced05a94..e9756405e 100755 --- a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4_ram.ld +++ b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4_ram.ld @@ -23,8 +23,15 @@ __process_stack_size__ = 0x1000; /* main() stack */ MEMORY { - flash : org = 0x00000000, len = 96k /* Local SRAM @ 0x10000000 */ - ram : org = 0x10080000, len = 32k /* Local SRAM @ 0x10080000 */ + /* Local SRAM Bank 1 shadowed to 0x0 (code) + * HackRF One: 96KB + * Praline: 96KB (M0 local heap uses remaining 32KB) */ + flash : org = 0x00000000, len = LD_M4_RAM_LEN /* Local SRAM @ 0x10000000 */ + + /* Local SRAM Bank 2 @ 0x10080000 (data/bss/stacks) + * HackRF One: ~32KB + * Praline: 72KB */ + ram : org = 0x10080000, len = LD_M4_FLASH_LEN /* Local SRAM @ 0x10080000 */ } __ram_start__ = ORIGIN(ram); diff --git a/firmware/chibios/os/ports/GCC/ARMCMx/rules.cmake b/firmware/chibios/os/ports/GCC/ARMCMx/rules.cmake index 2e20d8536..a892fd1e4 100644 --- a/firmware/chibios/os/ports/GCC/ARMCMx/rules.cmake +++ b/firmware/chibios/os/ports/GCC/ARMCMx/rules.cmake @@ -104,4 +104,46 @@ endif() set(CMAKE_C_FLAGS "${CFLAGS} ${TOPT}") set(CMAKE_CXX_FLAGS "${CPPFLAGS} ${TOPT}") set(CMAKE_AS_FLAGS "${ASFLAGS} ${TOPT}") -set(CMAKE_EXE_LINKER_FLAGS "${LDFLAGS}") + +if(BOARD STREQUAL "PRALINE") + # Praline (LPC4330) - Expanded Memory + # AHB SRAM: 32KB + 32KB = 64KB + # Left M0_RAM_SIZE as 64k and USB_RAM_SIZE as 32k for + # backwards consistenct with hackrf one setup. + # Recommend revisit by devs. + # Local SRAM: 128KB (Bank 1) + 72KB (Bank 2) + set(M0_RAM_SIZE "64k") # AHB SRAM Bank 0 for M0 (stacks, data, bss) + set(M0_LOCAL_HEAP_SIZE "32k") # Local SRAM heap for additional 16-bit IQ + set(M0_LOCAL_HEAP_ORIGIN "0x10018000") # After M4's 96KB + set(M4_RAM_SIZE "96k") # Local SRAM Bank 1 (same as HackRF One) + set(M4_FLASH_SIZE "72k") # Local SRAM Bank 2 + set(USB_RAM_SIZE "32k") # AHB SRAM shared with M0 + set(FLASH_SIZE "4M") +else() + # HackRF One (LPC4320) - Standard Memory + # AHB SRAM: 32KB + 16KB = 48KB (split between M0 and USB) + # Left M0_RAM_SIZE as 64k and USB_RAM_SIZE as 32k to keep + # original hackrf one settings intact. + # Recommend revisit by devs. + # Local SRAM: 96KB (Bank 1) + 40KB (Bank 2) + set(M0_RAM_SIZE "64k") # Standard AHB SRAM for M0 + set(M0_LOCAL_HEAP_SIZE "0k") # No local heap available + set(M0_LOCAL_HEAP_ORIGIN "0") # Not used + set(M4_RAM_SIZE "96k") # Standard Local SRAM Bank 1 + set(M4_FLASH_SIZE "32752") # Standard Local SRAM Bank 2 (~32KB) + set(USB_RAM_SIZE "32k") # Standard 32k/32k split for AHB Bank 1 + set(FLASH_SIZE "1M") # Standard SPI Flash limit +endif() + +# Apply all symbols to the linker flags in one command +# Build linker flags +set(CMAKE_EXE_LINKER_FLAGS "${LDFLAGS} \ + -Wl,--defsym=LD_RAM_SIZE=${M0_RAM_SIZE} \ + -Wl,--defsym=LD_M0_LOCAL_HEAP_SIZE=${M0_LOCAL_HEAP_SIZE} \ + -Wl,--defsym=LD_M0_LOCAL_HEAP_ORIGIN=${M0_LOCAL_HEAP_ORIGIN} \ + -Wl,--defsym=LD_M4_RAM_LEN=${M4_RAM_SIZE} \ + -Wl,--defsym=LD_M4_FLASH_LEN=${M4_FLASH_SIZE} \ + -Wl,--defsym=LD_USB_RAM_LEN=${USB_RAM_SIZE} \ + -Wl,--defsym=LD_FLASH_SIZE=${FLASH_SIZE}" +) +