From 9a3c2cc6aca697c539721856c4c73e3b63960c07 Mon Sep 17 00:00:00 2001 From: stafur Date: Tue, 10 Mar 2026 21:40:43 -0400 Subject: [PATCH] Next praline clean up (fixed BLE RX crashes) (#3080) * Cleaned up #ifndef PRALINE and updated logic to being with #ifdef PRALINE entries where possible to make logic flow for PRALINE code execution pipeline clearer. Cleaned up compiletime warnings for PRALINE related codebase updates. * Addressed comments provided by copilot during PR review. Combed through frequency definitions for consistency between PLL A and PLL B register definitions for CLKs 0-7. Ensured CLK3/LK6 <- SMA PORTs and CLK7 <- not utiliized are disabled during core development phase to support root cause analysis of any spectral artifacts. Updated MCU frequency to 40MHz to ensure audio harmonics are outside FM radio band range (< 80 MHz, >120MHz) and added comments clarifying choice of 40 over 10 MHz for potential future root cause analysis in other bands where audio may be expected as needed. Added CLK6 and CLK7 to Clocks Status View Debug display. Moved CLK defintions and PLL instantiations for components that are most RF sensitive to PLL A. Left others in PLL B. That is move FPGA CLK1 to PLL B, while moving CLK2, CLK4, and CLK5 to PLL A. * Cleaned up PLL A and B XTAL reference checks relative to 800 MHz. * Encapsulated HackRF Pro Praline debug and status vies into a single Pro Debug submenu as part of clean up. * Fixed BLE RX Out of Memory error. Updated LPC43xx ld scripts to accouint for additional HackRF Pro praline memory. * Addressed copilot comments for ble_rx_app by adding recent_entries_view.set_dirty. Updated ble_rx_app for easier use with heap limit set to one less than recent entries max limit. * Addressed copilot comments by updating comment clarity in source files. Updated ui_debug to allow for return reference if set for PRO debug menu item. * Improved readability of intialization parameters for the FPGA registers, and addressed 20Mhz nulls by initializing DC Notch width with standard setting, and DC Adaptiation rate with a balanced setting. * Ran format-code.sh * Added option to allow for user to set number if entries in recent list. Default is set to a relatively stable 32. * Removed #ifdef PRALINE pragmas from ble_rx_app such that HackRF One can also use the updated UI widget to allow for user to set number of entries in recent list. * Ran format-code.sh --- firmware/application/apps/ble_rx_app.cpp | 43 +++- firmware/application/apps/ble_rx_app.hpp | 21 ++ firmware/application/apps/ui_debug.cpp | 54 +++-- firmware/application/apps/ui_debug.hpp | 10 + firmware/application/clock_manager.cpp | 21 +- firmware/application/radio.cpp | 61 +++-- .../PORTAPACK_APPLICATION/fpga_bridge.c | 208 +++++++++++++++--- .../PORTAPACK_APPLICATION/fpga_bridge.h | 126 ++++++++++- .../GCC/ARMCMx/LPC43xx_M0/ld/LPC43xx_M0.ld | 19 +- .../GCC/ARMCMx/LPC43xx_M4/ld/LPC43xx_M4.ld | 17 +- .../ARMCMx/LPC43xx_M4/ld/LPC43xx_M4_ram.ld | 11 +- .../chibios/os/ports/GCC/ARMCMx/rules.cmake | 44 +++- 12 files changed, 557 insertions(+), 78 deletions(-) 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}" +) +