Compare commits

...

13 Commits

Author SHA1 Message Date
Matej Sochan 6dadefe86f Bug/big frequency (#3247)
* fix(ui): BigFrequency ignores set_dirty() when frequency is unchanged

paint() gated its entire body behind a frequency-changed check (_previous_frequency cache), so calling set_dirty() without changing the value (e.g. forcing a repaint after a style/theme change) silently did nothing. Moved the change check into set() instead, so paint() always redraws when called and set_dirty() behaves as expected.

* accidental deletion
2026-08-19 05:57:38 +02:00
Copilot ea40e3a46d Remove dead _previous_frequency state from BigFrequency widget (#3294)
* Initial plan

* Remove unused _previous_frequency member from BigFrequency and remove paint() guard

Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>

* Move frequency dirty-check to set() to avoid redundant repaints

Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2026-08-19 10:15:39 +08:00
rollorentner 86584feb16 Add Superrollo (GW60/HCS361) TX and RX (#3290)
keeloqtx: Superrollo 67-bit transmit mode with rolling counter.
subghzd: GW60 receive decoder (reports as KeeLoq; manufacturer key read from KEELOQKEYS/MFCODES). The manufacturer key is not included; add a Superrollo entry to MFCODES to enable TX/RX.
2026-08-16 20:10:04 +02:00
Bernd Herzog 47c94dbf26 SD Over USB for the Hackrf Pro (#3291)
* enabled sd over usb compilation
* updated hackrf pro usb stack
* fixed hackrf one code path
* fixed hackrf pro code path
* improved performance
* fixed sd card clock
* refactoring
* formatted code
2026-08-16 20:08:19 +02:00
Oleg Belousov b048b8f4f1 Feature/sliding freq (#3280)
* Add sliding-frequency audio receiver tuning
* Move ADS-B receiver to external app
* Move AIS receiver to external app
* Move APRS receiver to external app
* Move APRS transmitter to external app
* Isolate filtered spectrum collection from WFM
* Fixed the issues #3280
* fpga_bridge.c: init registers per reference fpga_init, add quarter-shift mode setter
* radio.hpp: expose cached FPGA quarter-rate shift
* radio.cpp: program FPGA quarter-rate shift together with tuning offset
* tuning.hpp: pass AFE rate and direction to tuning config, add quarter_shift field
* tuning.cpp: import full PRALINE RX/TX tuning tables with quarter-shift offsets
* clock_manager: stop writing bogus RX digital gain, keep quarter shift across rate changes
* receiver_model: port LPF bandwidth from reference auto_bandwidth, use cached quarter shift
* adsb_rx: enable RF amp by default on first run
* ui_geomap.hpp: add hemisphere fields, degrees become magnitude
* ui_geomap: use hemisphere selector for lat/lon sign, fix minute/second wrap carry
* ui_menu: guard select against empty menu
* waterfall_designer: header updates for profile file handling
* waterfall_designer: exception-free profile parsing, CRLF handling, deferred nav callbacks, backup cleanup
* external.ld: scope app section globs to their own object directories
* CMakeLists: relink when external.ld changes
* tools: add external app symbol placement checker
* tools: add guru meditation address lookup script
* tools: add per-function stack usage report script
Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-08-08 21:47:54 +02:00
MsfPablo 9067e007b6 Fix GeoPos losing the sign of coordinates between 0 and -1 (#3283)
GeoPos stored a coordinate's sign only in its degrees NumberField, which
is an int32_t and therefore has no negative zero. Any coordinate whose
integer degrees part is 0 but which is negative (i.e. strictly between
0.0 and -1.0) could not be represented at all:

  - set_lat()/set_lon() passed the raw float to the degrees field, so
    -0.2933 truncated to 0 and the sign was gone before it reached the
    widget.
  - lat()/lon() then decided the sign with `field_lon_degrees.value() < 0`,
    which is false for 0, so the value came back positive.

The reporter's airport is at longitude -0.2933, which was impossible to
enter in the ADSB Tx app.

Give each coordinate an explicit hemisphere field (N/S and E/W) and make
the degrees field an unsigned magnitude:

  - The hemisphere OptionsField is the single source of the sign, so
    "negative with zero degrees" is now representable.
  - set_lat()/set_lon() derive the hemisphere from the sign of the input
    and feed the fields the magnitude.
  - lat()/lon() read the hemisphere instead of inferring the sign from
    the degrees value.
  - The minutes on_wrap handlers no longer need to flip the carry
    direction based on the degrees sign, since degrees is now a
    magnitude; the carry is the same in both hemispheres.
  - The degrees fields no longer loop, so carrying below 0 clamps at 0
    instead of wrapping round to 90/180.

The hemisphere indicator occupies the column that the 4-wide signed
degrees field used for its minus sign, so the row layout, the degree
symbol and the decimal readout all stay where they were.

Fixes #3234
2026-08-08 21:18:18 +02:00
Copilot 367eaf54c0 Fix GeoPos DMS edge carry rollback at clamped degree limits (#3284)
* Initial plan

* Fix geomap DMS wrap carry at clamped bounds

Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2026-08-08 00:40:35 +02:00
qwer123 de30cbfe1b fix ADSB RX weak signal on pro (#3282) 2026-08-05 10:09:55 +02:00
未来方舟 a4d91f12ef Fix waterfall designer crash and also some linker script fixes (#3281)
* init

* _
2026-08-01 12:57:45 +02:00
gullradriel c4f32ac436 update submodule (#3277) 2026-07-25 18:54:33 +02:00
Frederic BORRY efe214167d Added sn0ren's SPECTRUM BMP files (#3276) 2026-07-24 18:37:55 +08:00
Copilot 483a100100 Fix ADSB altitude integer overflow and add on-ground indicator (#3275)
* Initial plan

* Fix ADSB altitude integer overflow and add on-ground indicator

- Fix display overflow: use to_string_dec_int (signed) instead of
  to_string_dec_uint (unsigned) for altitude display - per ICAO Annex 10
  Vol IV 3.1.2.6.5.4, altitude = 25N-1000 can be as low as -1000ft
- Remove incorrect altitude clamp (altitude < 0 => 0) in DF 0/4/20 decoder
- Add on_ground flag to AircraftRecentEntry; set from TC 5-8 (surface
  position) messages, cleared on TC 9-22 (airborne position) messages
- Display 'GND' in altitude column when aircraft is on ground
- Clear altitude to 0 when on_ground is set to prevent stale data in map
  color calculation

Closes #3274

* Revert on-ground indicator; keep only the overflow and clamp fixes

Remove on_ground field, TC 5-8 handling, and "GND" display per review
feedback. Only keep the two minimal bug fixes:
- Signed altitude display (to_string_dec_int vs to_string_dec_uint)
- Remove erroneous altitude < 0 clamp; update comment to show the math
2026-07-23 01:16:21 +02:00
Pezsma 8561ff32b3 Gpio modify v4 (#3266)
* Refactor GPIO handling for RFFC5072: update pin mappings and simplify initialization

* Refactor GPIO handling: replace portapack::gpio_dfu with dfu_button and clean up unused GPIOs

* Refactor MAX283x GPIO handling: update pin mappings and simplify initialization

* Refactor GPIO handling: update MAX2831 shutdown pin configuration

* Refactor GPIO handling: add MAX2831 RXHP and RXTX control, update related configurations

* Refactor MAX2831 frequency setting to include reference divider parameter and improve validation checks

* Refactor MAX2831 frequency setting to remove reference divider parameter and improve frequency validation

* copilot
2026-07-10 21:37:41 +02:00
94 changed files with 5505 additions and 2312 deletions
+5 -6
View File
@@ -279,16 +279,12 @@ set(CPPSRC
ui/ui_tone_key.cpp
ui/ui_transmitter.cpp
ui/ui_bmpview.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/ble_rx_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp
apps/pocsag_app.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_battinfo.cpp
apps/ui_bmp_file_viewer.cpp
apps/ui_btle_rx.cpp
@@ -493,7 +489,10 @@ add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
target_link_options(${PROJECT_NAME}.elf PRIVATE
"LINKER:--defsym,LD_FLASH_SIZE=${FLASH_BYTES_LIMIT_SIZE}"
)
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
# NB: LPC43xx_M0.ld does INCLUDE external.ld, so that one has to be listed too -
# otherwise editing the external app section rules silently does not relink.
set_target_properties(${PROJECT_NAME}.elf PROPERTIES
LINK_DEPENDS "${LDSCRIPT};${CMAKE_CURRENT_SOURCE_DIR}/external/external.ld")
add_definitions(${DEFS})
include_directories(. ${INCDIR})
link_directories(${LLIBDIR})
@@ -513,4 +512,4 @@ add_custom_target(
DEPENDS ${PROJECT_NAME}.bin
)
add_dependencies(${PROJECT_NAME} baseband)
add_dependencies(${PROJECT_NAME} baseband)
+74 -4
View File
@@ -280,6 +280,12 @@ AnalogAudioView::AnalogAudioView(
field_frequency.on_show_options = [this]() {
this->on_show_options_frequency();
};
field_frequency.changing = [this](rf::Frequency frequency) {
return this->on_frequency_changed(frequency);
};
field_frequency.entered = [this](rf::Frequency frequency) {
this->set_frequency_absolute(frequency);
};
field_lna.on_show_options = [this]() {
this->on_show_options_rf_gain();
@@ -308,8 +314,10 @@ AnalogAudioView::AnalogAudioView(
};
waterfall.on_select = [this](int32_t offset) {
field_frequency.set_value(receiver_model.target_frequency() + offset);
field_frequency.set_value(
field_frequency.value() + offset * receiver_model.frequency_step());
};
waterfall.set_live_tuning(true);
#ifdef PRALINE
button_pro.on_select = [this](Button&) { this->on_show_options_praline(); };
@@ -319,6 +327,7 @@ AnalogAudioView::AnalogAudioView(
// This call starts the correct baseband image to run
// and sets the radio up as necessary for the given modulation.
sliding_center_frequency = receiver_model.target_frequency();
on_modulation_changed(modulation);
}
@@ -328,7 +337,7 @@ AnalogAudioView::AnalogAudioView(
: AnalogAudioView(nav) {
// Settings to override when launched from another app (versus from AppSettings .ini file)
// TODO: Which other settings make sense to override?
field_frequency.set_value(override.frequency_app_override);
set_frequency_absolute(override.frequency_app_override);
on_frequency_step_changed(override.frequency_step);
options_modulation.set_by_value(toUType(override.mode));
}
@@ -573,10 +582,14 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
reset_sliding_frequency(modulation);
receiver_model.enable();
if (sliding_enabled) {
baseband::set_audio_ddc_frequency(0);
}
// TODO: This doesn't belong here! There's a better way.
size_t sampling_rate = 0;
switch (modulation) {
@@ -610,7 +623,64 @@ void AnalogAudioView::handle_coded_squelch(uint32_t value) {
}
void AnalogAudioView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
set_frequency_absolute(freq);
}
void AnalogAudioView::set_frequency_absolute(rf::Frequency frequency) {
if (!sliding_enabled) {
field_frequency.set_value(frequency);
return;
}
sliding_center_frequency = frequency;
/* set_value() does not call on_change when the displayed frequency is
* already equal, so reset the hardware and DDC explicitly in that case. */
if (field_frequency.value() == frequency) {
receiver_model.set_target_frequency_with_hidden_offset(frequency, 0);
baseband::set_audio_ddc_frequency(0);
} else {
field_frequency.set_value(frequency);
}
}
int32_t AnalogAudioView::sliding_limit() const {
const bool zoom_x2 =
receiver_model.modulation() == ReceiverModel::Mode::AMAudio &&
previous_zoom != 0;
return zoom_x2 ? sliding_limit_zoom_x2 : sliding_limit_zoom_x1;
}
void AnalogAudioView::reset_sliding_frequency(ReceiverModel::Mode modulation) {
sliding_enabled =
modulation == ReceiverModel::Mode::AMAudio ||
modulation == ReceiverModel::Mode::NarrowbandFMAudio;
sliding_center_frequency = receiver_model.target_frequency();
/* AMFM keeps its existing Wefax offset; sliding applies to AM and NFM. */
receiver_model.set_hidden_offset(
modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0);
}
bool AnalogAudioView::on_frequency_changed(rf::Frequency frequency) {
if (!sliding_enabled)
return false;
const auto limit = sliding_limit();
int64_t offset = frequency - sliding_center_frequency;
if (offset > limit) {
sliding_center_frequency = frequency - limit;
offset = limit;
} else if (offset < -limit) {
sliding_center_frequency = frequency + limit;
offset = -limit;
}
/* Store the displayed frequency and retune the hardware centre atomically. */
receiver_model.set_target_frequency_with_hidden_offset(
frequency, sliding_center_frequency - frequency);
baseband::set_audio_ddc_frequency(static_cast<int32_t>(offset));
return true;
}
#ifdef PRALINE
@@ -269,6 +269,10 @@ class AnalogAudioView : public View {
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
static constexpr int32_t sliding_limit_zoom_x1 = 50000;
static constexpr int32_t sliding_limit_zoom_x2 = 30000;
rf::Frequency sliding_center_frequency{0};
bool sliding_enabled{false};
app_settings::SettingsManager settings_{
"rx_audio",
@@ -354,6 +358,10 @@ class AnalogAudioView : public View {
void handle_coded_squelch(uint32_t value);
void on_freqchg(int64_t freq);
bool on_frequency_changed(rf::Frequency frequency);
void set_frequency_absolute(rf::Frequency frequency);
int32_t sliding_limit() const;
void reset_sliding_frequency(ReceiverModel::Mode modulation);
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
+4 -2
View File
@@ -74,7 +74,7 @@ void SubGhzDRecentEntryDetailView::update_data() {
console.writeln("Action: 0x" + to_string_hex(func_code));
}
if (entry_.sensorType == FPS_KEELOQ) {
if (entry_.sensorType == FPS_KEELOQ || entry_.sensorType == FPS_SUPERROLLO) {
console.writeln("Fix: " + to_string_hex(fix));
console.writeln("Encrypted: " + to_string_hex(encrypted));
console.writeln("Manufacturer: " + mf_name);
@@ -243,6 +243,8 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
return "Ido 11x";
case FPS_INTERTECHNOV3:
return "InterTehcno v3";
case FPS_SUPERROLLO:
return "Superrollo";
case FPS_KEELOQ:
return "KeeLoq";
case FPS_KINGGATESSTYLO4K:
@@ -671,7 +673,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
return;
}
if (entry_.sensorType == FPS_KEELOQ) {
if (entry_.sensorType == FPS_KEELOQ || entry_.sensorType == FPS_SUPERROLLO) {
uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64);
btn = data_rev >> 60;
+9 -8
View File
@@ -32,10 +32,6 @@
#include "core_control.hpp"
/* Set true to enable additional checks to ensure
* M4 and M0 are synchronized before passing messages. */
static constexpr bool enforce_core_sync = true;
/* Set true to enable check for baseband messages getting stuck.
* This implies the baseband thread is not dequeuing and has probably stalled.
* NB: This check adds a small amout of overhead to the message sending code
@@ -343,6 +339,11 @@ void set_spectrum(
send_message(&message);
}
void set_audio_ddc_frequency(int32_t frequency) {
const AudioDDCConfigMessage message{frequency};
send_message(&message);
}
void set_time_sink(
const size_t sampling_rate,
const size_t trigger) {
@@ -469,7 +470,7 @@ bool is_image_running() {
return baseband_image_running;
}
void run_image(const spi_flash::image_tag_t image_tag) {
void run_image(const spi_flash::image_tag_t image_tag, bool enforce_core_sync) {
if (baseband_image_running) {
chDbgPanic("BBRunning");
}
@@ -482,7 +483,7 @@ void run_image(const spi_flash::image_tag_t image_tag) {
creg::m4txevent::enable();
if constexpr (enforce_core_sync) {
if (enforce_core_sync) {
// Wait up to 3 seconds for baseband to start handling events.
auto count = 3'000u;
while (!shared_memory.baseband_ready && --count)
@@ -493,7 +494,7 @@ void run_image(const spi_flash::image_tag_t image_tag) {
}
}
void run_prepared_image(const uint32_t m4_code) {
void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync) {
if (baseband_image_running) {
chDbgPanic("BBRunning");
}
@@ -506,7 +507,7 @@ void run_prepared_image(const uint32_t m4_code) {
creg::m4txevent::enable();
if constexpr (enforce_core_sync) {
if (enforce_core_sync) {
// Wait up to 3 seconds for baseband to start handling events.
auto count = 3'000u;
while (!shared_memory.baseband_ready && --count)
+3 -2
View File
@@ -100,6 +100,7 @@ void set_rds_data(const uint16_t message_length);
void set_spectrum(
const size_t sampling_rate,
const size_t trigger);
void set_audio_ddc_frequency(int32_t frequency);
void set_time_sink(
const size_t sampling_rate,
const size_t trigger);
@@ -130,8 +131,8 @@ void request_beep_stop();
void request_audio_beep(uint32_t freq, uint32_t sample_rate, uint32_t duration_ms);
bool is_image_running();
void run_image(const portapack::spi_flash::image_tag_t image_tag);
void run_prepared_image(const uint32_t m4_code);
void run_image(const portapack::spi_flash::image_tag_t image_tag, bool enforce_core_sync = true);
void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync = true);
void shutdown();
void spectrum_streaming_start();
+17 -5
View File
@@ -907,12 +907,24 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
// Set FPGA RX decimation register
fpga_debug_register_write(FPGA_REG_DECIM, n);
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
* We shift up by (3 * n) to compensate for CIC bit-growth.
/* No RX digital-gain register is written here.
*
* Register 0x03 used to be programmed with (3 * n + 2) as a "CIC
* bit-growth" renormalisation. The gateware has no such register: the
* RX decimator is a chain of unity-gain half-band FIRs selected by
* rx_decim (fpga/top/standard.py), and 0x03 is rx_pstep, whose top two
* bits are the quarter-rate shift. Writing a gain here silently
* cancelled the shift that set_tuning_frequency() had programmed, which
* left the analogue passband offset with no matching rotation.
*
* The shift depends on the AFE rate we just chose, so re-apply it after
* the rate change. ReceiverModel::update_sampling_rate() calls
* update_tuning_frequency() straight after this, which does exactly
* that; the write below only keeps the register consistent in between.
*/
uint8_t ds = (3 * n);
ds += 2;
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
fpga_debug_register_write(
FPGA_REG_RX_PSTEP,
(radio::debug::get_cached_quarter_shift() & 0b11) << FPGA_RX_QUARTER_SHIFT_SHIFT);
// Re-enable FPGA processing with clean state ===
fpga_debug_register_write(1, 0x01);
+4 -5
View File
@@ -61,7 +61,6 @@ uint32_t blink_patterns[] = {
void config_mode_run() {
configure_pins_portapack();
portapack::gpio_dfu.input();
portapack::persistent_memory::cache::init();
if (hackrf_r9) {
@@ -80,14 +79,14 @@ void config_mode_run() {
config_mode_blink_until_dfu();
}
auto last_dfu_btn = portapack::gpio_dfu.read();
auto last_dfu_btn = dfu_button.read();
int32_t counter = 0;
int8_t blink_pattern_value = portapack::persistent_memory::config_cpld() +
(portapack::persistent_memory::config_disable_external_tcxo() ? 5 : 0);
while (true) {
auto dfu_btn = portapack::gpio_dfu.read();
auto dfu_btn = dfu_button.read();
auto dfu_clicked = last_dfu_btn == true && dfu_btn == false;
last_dfu_btn = dfu_btn;
@@ -137,7 +136,7 @@ void config_mode_blink_until_dfu() {
led_usb.setInactive();
chThdSleepMilliseconds(115);
auto dfu_btn = portapack::gpio_dfu.read();
auto dfu_btn = dfu_button.read();
if (dfu_btn)
break;
}
@@ -145,7 +144,7 @@ void config_mode_blink_until_dfu() {
while (true) {
chThdSleepMilliseconds(10);
auto dfu_btn = portapack::gpio_dfu.read();
auto dfu_btn = dfu_button.read();
if (!dfu_btn)
break;
}
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2026 PortaPack Mayhem
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_adsb_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::adsbrx {
void initialize_app(ui::NavigationView& nav) {
nav.push<ADSBRxView>();
}
} // namespace ui::external_app::adsbrx
extern "C" {
__attribute__((section(".external_app.app_adsbrx.application_information"), used)) application_information_t _application_information_adsbrx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::adsbrx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "ADS-B",
/*.bitmap_data = */ {
0x80,
0x01,
0xC0,
0x03,
0xC0,
0x03,
0xC0,
0x03,
0xC0,
0x03,
0xE0,
0x07,
0xF8,
0x1F,
0xFE,
0x7F,
0xFF,
0xFF,
0xFF,
0xFF,
0xC0,
0x03,
0xC0,
0x03,
0xC0,
0x03,
0xE0,
0x07,
0xF0,
0x0F,
0xF8,
0x1F,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_adsbrx */ {'P', 'A', 'D', 'R'},
/*.m4_app_offset = */ 0x00000000,
};
} // extern "C"
@@ -38,7 +38,7 @@ using namespace portapack;
namespace pmem = portapack::persistent_memory;
namespace ui {
namespace ui::external_app::adsbrx {
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
@@ -46,13 +46,19 @@ static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
} // namespace ui::external_app::adsbrx
namespace ui {
template <>
void RecentEntriesTable<AircraftRecentEntries>::draw(
void RecentEntriesTable<external_app::adsbrx::AircraftRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style,
RecentEntriesColumns& columns) {
using namespace external_app::adsbrx;
Color target_color;
std::string entry_string;
@@ -75,7 +81,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
uint8_t firstcolwidth = columns.at(0).second;
ipc.resize(firstcolwidth, ' '); // Make sure this is always match the first column's width that is dynamic.
entry_string += ipc + to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
entry_string += ipc + to_string_dec_int((int32_t)(entry.pos.altitude / 100), 4);
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
// It is generally accepted that for every thousand feet of altitude,
@@ -107,6 +113,10 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
bitmap_target, target_color, style.background);
}
} // namespace ui
namespace ui::external_app::adsbrx {
/* ADSBLogger ********************************************/
void ADSBLogger::log(const ADSBLogEntry& log_entry) {
@@ -532,6 +542,18 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
logger = std::make_unique<ADSBLogger>();
logger->append(logs_dir / u"ADSB.TXT");
/* First run only: start from the configuration that is known to receive
* ADS-B on this hardware -- LNA 32, VGA 32, RF amp ON. The first two are
* already ReceiverModel's defaults; the amp is not, and running without it
* costs about 14 dB, which is the difference between a busy list and an
* empty one. Once the user has saved settings for this app their choice
* wins, so this only sets the starting point.
* Going through the field rather than receiver_model keeps the displayed
* value in step: RFAmpField snapshots rf_amp() in its own constructor,
* which has already run by the time we get here. */
if (!settings_.loaded())
field_rf_amp.set_value(1);
receiver_model.enable();
baseband::set_adsb();
@@ -668,9 +690,8 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
(raw_data[3] & 15);
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
// altitude = 25N - 1000 (ft); minimum is -1000 ft when N=0 (Q=1, M=0 per ICAO Annex 10).
altitude = 25 * n - 1000;
if (altitude < 0)
altitude = 0;
} // else N is an 11 bit Gillham coded altitude
}
@@ -817,4 +838,4 @@ void ADSBRxView::remove_expired_entries() {
recent.erase(it.base(), recent.end());
}
} /* namespace ui */
} // namespace ui::external_app::adsbrx
@@ -39,7 +39,7 @@
using namespace adsb;
namespace ui {
namespace ui::external_app::adsbrx {
#define AIRCRAFT_ID_L 1 // aircraft ID message type (lowest type id)
#define AIRCRAFT_ID_H 4 // aircraft ID message type (highest type id)
@@ -491,4 +491,4 @@ class ADSBRxView : public View {
}};
};
} /* namespace ui */
} // namespace ui::external_app::adsbrx
@@ -35,7 +35,8 @@ using namespace portapack;
namespace pmem = portapack::persistent_memory;
namespace ais {
namespace ui::external_app::ais_rx {
namespace format {
static std::string latlon_abs_normalized(const int32_t normalized, const char suffixes[2]) {
@@ -47,7 +48,7 @@ static std::string latlon_abs_normalized(const int32_t normalized, const char su
return to_string_dec_uint(degrees) + "." + to_string_dec_uint(fraction, 6, '0') + suffix;
}
static std::string latlon(const Latitude latitude, const Longitude longitude) {
static std::string latlon(const ais::Latitude latitude, const ais::Longitude longitude) {
if (latitude.is_valid() && longitude.is_valid()) {
return latlon_abs_normalized(latitude.normalized(), "SN") + " " + latlon_abs_normalized(longitude.normalized(), "WE");
} else if (latitude.is_not_available() && longitude.is_not_available()) {
@@ -128,7 +129,7 @@ static std::string navigational_status(const unsigned int value) {
}
}
static std::string rate_of_turn(const RateOfTurn value) {
static std::string rate_of_turn(const ais::RateOfTurn value) {
switch (value) {
case -128:
return "not available";
@@ -149,7 +150,7 @@ static std::string rate_of_turn(const RateOfTurn value) {
}
}
static std::string speed_over_ground(const SpeedOverGround value) {
static std::string speed_over_ground(const ais::SpeedOverGround value) {
if (value == 1023) {
return "not available";
} else if (value == 1022) {
@@ -159,7 +160,7 @@ static std::string speed_over_ground(const SpeedOverGround value) {
}
}
static std::string course_over_ground(const CourseOverGround value) {
static std::string course_over_ground(const ais::CourseOverGround value) {
if (value > 3600) {
return "invalid";
} else if (value == 3600) {
@@ -169,7 +170,7 @@ static std::string course_over_ground(const CourseOverGround value) {
}
}
static std::string true_heading(const TrueHeading value) {
static std::string true_heading(const ais::TrueHeading value) {
if (value == 511) {
return "not available";
} else if (value > 359) {
@@ -180,7 +181,6 @@ static std::string true_heading(const TrueHeading value) {
}
} /* namespace format */
} /* namespace ais */
void AISLogger::on_packet(const ais::Packet& packet) {
// TODO: Unstuff here, not in baseband!
@@ -265,25 +265,33 @@ void AISRecentEntry::update(const ais::Packet& packet) {
}
}
} // namespace ui::external_app::ais_rx
namespace ui {
template <>
void RecentEntriesTable<AISRecentEntries>::draw(
void RecentEntriesTable<external_app::ais_rx::AISRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style,
RecentEntriesColumns&) {
std::string line = ais::format::mmsi(entry.mmsi) + " ";
using namespace external_app::ais_rx;
std::string line = format::mmsi(entry.mmsi) + " ";
if (!entry.name.empty()) {
line += ais::format::text(entry.name);
line += format::text(entry.name);
} else {
line += ais::format::text(entry.call_sign);
line += format::text(entry.call_sign);
}
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
} // namespace ui
namespace ui::external_app::ais_rx {
AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
add_children({
&button_done,
@@ -298,12 +306,12 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
button_see_map.on_select = [this, &nav](Button&) {
geomap_view = nav.push<GeoMapView>(
ais::format::text(entry_.name),
format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::KNOTS,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
format::latlon_float(entry_.last_position.latitude.normalized()),
format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
[this]() {
send_updates = false;
@@ -324,14 +332,14 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground / 10);
geomap_view->update_position(format::latlon_float(entry_.last_position.latitude.normalized()), format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground / 10);
}
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
if (geomap_view && send_updates) {
GeoMarker marker{};
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
marker.lon = format::latlon_float(entry.last_position.longitude.normalized());
marker.lat = format::latlon_float(entry.last_position.latitude.normalized());
marker.angle = entry.last_position.true_heading;
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
auto markerStored = geomap_view->store_marker(marker);
@@ -377,18 +385,18 @@ void AISRecentEntryDetailView::paint(Painter& painter) {
auto field_rect = Rect{rect.left(), rect.top() + 16, rect.width(), 16};
field_rect = draw_field(painter, field_rect, s, "MMSI", ais::format::mmsi(entry_.mmsi));
field_rect = draw_field(painter, field_rect, s, "Ctry", ais::format::mid(entry_.mmsi));
field_rect = draw_field(painter, field_rect, s, "Name", ais::format::text(entry_.name));
field_rect = draw_field(painter, field_rect, s, "Call", ais::format::text(entry_.call_sign));
field_rect = draw_field(painter, field_rect, s, "Dest", ais::format::text(entry_.destination));
field_rect = draw_field(painter, field_rect, s, "MMSI", format::mmsi(entry_.mmsi));
field_rect = draw_field(painter, field_rect, s, "Ctry", format::mid(entry_.mmsi));
field_rect = draw_field(painter, field_rect, s, "Name", format::text(entry_.name));
field_rect = draw_field(painter, field_rect, s, "Call", format::text(entry_.call_sign));
field_rect = draw_field(painter, field_rect, s, "Dest", format::text(entry_.destination));
field_rect = draw_field(painter, field_rect, s, "Last", to_string_datetime(entry_.last_position.timestamp));
field_rect = draw_field(painter, field_rect, s, "Pos ", ais::format::latlon(entry_.last_position.latitude, entry_.last_position.longitude));
field_rect = draw_field(painter, field_rect, s, "Stat", ais::format::navigational_status(entry_.navigational_status));
field_rect = draw_field(painter, field_rect, s, "RoT ", ais::format::rate_of_turn(entry_.last_position.rate_of_turn));
field_rect = draw_field(painter, field_rect, s, "SoG ", ais::format::speed_over_ground(entry_.last_position.speed_over_ground));
field_rect = draw_field(painter, field_rect, s, "CoG ", ais::format::course_over_ground(entry_.last_position.course_over_ground));
field_rect = draw_field(painter, field_rect, s, "Head", ais::format::true_heading(entry_.last_position.true_heading));
field_rect = draw_field(painter, field_rect, s, "Pos ", format::latlon(entry_.last_position.latitude, entry_.last_position.longitude));
field_rect = draw_field(painter, field_rect, s, "Stat", format::navigational_status(entry_.navigational_status));
field_rect = draw_field(painter, field_rect, s, "RoT ", format::rate_of_turn(entry_.last_position.rate_of_turn));
field_rect = draw_field(painter, field_rect, s, "SoG ", format::speed_over_ground(entry_.last_position.speed_over_ground));
field_rect = draw_field(painter, field_rect, s, "CoG ", format::course_over_ground(entry_.last_position.course_over_ground));
field_rect = draw_field(painter, field_rect, s, "Head", format::true_heading(entry_.last_position.true_heading));
field_rect = draw_field(painter, field_rect, s, "Rx #", to_string_dec_uint(entry_.received_count));
}
@@ -504,4 +512,4 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.update_map_markers(recent);
}
} /* namespace ui */
} // namespace ui::external_app::ais_rx
@@ -50,6 +50,8 @@ using namespace lpc43xx;
#include "recent_entries.hpp"
namespace ui::external_app::ais_rx {
struct AISPosition {
rtc::RTC timestamp{};
ais::Latitude latitude{};
@@ -109,8 +111,6 @@ class AISLogger {
LogFile log_file{};
};
namespace ui {
using AISRecentEntriesView = RecentEntriesView<AISRecentEntries>;
class AISRecentEntryDetailView : public View {
@@ -239,6 +239,6 @@ class AISAppView : public View {
void on_tick_second();
};
} /* namespace ui */
} // namespace ui::external_app::ais_rx
#endif /*__AIS_APP_H__*/
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2026 PortaPack Mayhem
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ais_app.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::ais_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<AISAppView>();
}
} // namespace ui::external_app::ais_rx
extern "C" {
__attribute__((section(".external_app.app_ais_rx.application_information"), used)) application_information_t _application_information_ais_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::ais_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "AIS Boats",
/*.bitmap_data = */ {
0x00,
0x01,
0x80,
0x01,
0xC0,
0x01,
0xC0,
0x0D,
0xE0,
0x3D,
0xF0,
0x3D,
0xF8,
0x7D,
0xFC,
0x7D,
0xFC,
0x7D,
0xFE,
0x7D,
0xFF,
0x7D,
0x00,
0x00,
0xF8,
0x7F,
0xF8,
0x3F,
0xF0,
0x0F,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_ais */ {'P', 'A', 'I', 'S'},
/*.m4_app_offset = */ 0x00000000,
};
} // extern "C"
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2026 PortaPack Mayhem
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_aprs_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::aprs_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<APRSRXView>();
}
} // namespace ui::external_app::aprs_rx
extern "C" {
__attribute__((section(".external_app.app_aprs_rx.application_information"), used)) application_information_t _application_information_aprs_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::aprs_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "APRS",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF0,
0x0F,
0x4C,
0x32,
0xFE,
0x7F,
0x25,
0xA4,
0x25,
0xA4,
0xFF,
0xFF,
0x25,
0xA4,
0x25,
0xA4,
0xFE,
0x7F,
0x4C,
0x32,
0xF0,
0x0F,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_aprs_rx */ {'P', 'A', 'P', 'R'},
/*.m4_app_offset = */ 0x00000000,
};
} // extern "C"
@@ -31,19 +31,25 @@
using namespace portapack;
namespace ui::external_app::aprs_rx {
void APRSLogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
} // namespace ui::external_app::aprs_rx
namespace ui {
template <>
void RecentEntriesTable<APRSRecentEntries>::draw(
void RecentEntriesTable<external_app::aprs_rx::APRSRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style,
RecentEntriesColumns& columns) {
using namespace external_app::aprs_rx;
Color target_color;
// auto entry_age = entry.age;
@@ -68,6 +74,10 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
}
}
} // namespace ui
namespace ui::external_app::aprs_rx {
void APRSRxView::focus() {
options_region.focus();
}
@@ -402,4 +412,4 @@ void APRSRXView::focus() {
APRSRXView::~APRSRXView() {
}
} /* namespace ui */
} // namespace ui::external_app::aprs_rx
@@ -38,6 +38,8 @@
#include "utility.hpp"
#include "file_path.hpp"
namespace ui::external_app::aprs_rx {
class APRSLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
@@ -50,8 +52,6 @@ class APRSLogger {
LogFile log_file{};
};
namespace ui {
struct APRSRecentEntry {
using Key = uint64_t;
@@ -290,6 +290,6 @@ class APRSRXView : public View {
}};
};
} /* namespace ui */
} // namespace ui::external_app::aprs_rx
#endif /*__UI_APRS_RX_H__*/
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2026 PortaPack Mayhem
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_aprs_tx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::aprs_tx {
void initialize_app(ui::NavigationView& nav) {
nav.push<APRSTXView>();
}
} // namespace ui::external_app::aprs_tx
extern "C" {
__attribute__((section(".external_app.app_aprs_tx.application_information"), used)) application_information_t _application_information_aprs_tx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::aprs_tx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "APRS TX",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF0,
0x0F,
0x4C,
0x32,
0xFE,
0x7F,
0x25,
0xA4,
0x25,
0xA4,
0xFF,
0xFF,
0x25,
0xA4,
0x25,
0xA4,
0xFE,
0x7F,
0x4C,
0x32,
0xF0,
0x0F,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
/*.m4_app_offset = */ 0x00000000,
};
} // extern "C"
@@ -37,7 +37,7 @@
using namespace aprs;
using namespace portapack;
namespace ui {
namespace ui::external_app::aprs_tx {
void APRSTXView::focus() {
tx_view.focus();
@@ -242,4 +242,4 @@ APRSTXView::APRSTXView(NavigationView& nav) {
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
}
} /* namespace ui */
} // namespace ui::external_app::aprs_tx
@@ -33,7 +33,7 @@
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
namespace ui::external_app::aprs_tx {
class APRSTXView : public View {
public:
@@ -161,4 +161,4 @@ class APRSTXView : public View {
}};
};
} /* namespace ui */
} // namespace ui::external_app::aprs_tx
+25 -10
View File
@@ -391,6 +391,26 @@ set(EXTCPPSRC
external/tetra_rx/tetra_rcpc.cpp
external/tetra_rx/tetra_viterbi.cpp
#adsb rx
external/adsbrx/main.cpp
external/adsbrx/ui_adsb_rx.cpp
#ais rx
external/ais_rx/main.cpp
external/ais_rx/ais_app.cpp
#aprs rx
external/aprs_rx/main.cpp
external/aprs_rx/ui_aprs_rx.cpp
#aprs tx
external/aprs_tx/main.cpp
external/aprs_tx/ui_aprs_tx.cpp
#sd over usb
external/sdusb/main.cpp
external/sdusb/ui_sd_over_usb.cpp
)
set(EXTAPPLIST
@@ -485,14 +505,9 @@ set(EXTAPPLIST
secplustx
signal_hunter
tetra_rx
adsbrx
ais_rx
aprs_rx
aprs_tx
sdusb
)
# sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds
if(NOT BOARD STREQUAL "PRALINE")
list(APPEND EXTCPPSRC
external/sdusb/main.cpp
external/sdusb/ui_sd_over_usb.cpp
)
list(APPEND EXTAPPLIST sdusb)
endif()
+120 -92
View File
@@ -115,6 +115,10 @@ MEMORY
ram_external_app_vor_tx (rwx) : org = 0xAE0A0000, len = 32k
ram_external_app_signal_hunter (rwx) : org = 0xAE0B0000, len = 32k
ram_external_app_tetra_rx (rwx) : org = 0xAE0C0000, len = 32k
ram_external_app_adsbrx (rwx) : org = 0xAE0D0000, len = 32k
ram_external_app_ais_rx (rwx) : org = 0xAE0E0000, len = 32k
ram_external_app_aprs_rx (rwx) : org = 0xAE0F0000, len = 32k
ram_external_app_aprs_tx (rwx) : org = 0xAE100000, len = 32k
}
SECTIONS
@@ -122,555 +126,579 @@ SECTIONS
.external_app_afsk_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_afsk_rx.application_information));
*(*ui*external_app*afsk_rx*);
*/external/afsk_rx/*(*ui*external_app*afsk_rx*);
} > ram_external_app_afsk_rx
.external_app_calculator : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_calculator.application_information));
*(*ui*external_app*calculator*);
*/external/calculator/*(*ui*external_app*calculator*);
} > ram_external_app_calculator
.external_app_font_viewer : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_font_viewer.application_information));
*(*ui*external_app*font_viewer*);
*/external/font_viewer/*(*ui*external_app*font_viewer*);
} > ram_external_app_font_viewer
.external_app_blespam : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_blespam.application_information));
*(*ui*external_app*blespam*);
*/external/blespam/*(*ui*external_app*blespam*);
} > ram_external_app_blespam
.external_app_analogtv : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_analogtv.application_information));
*(*ui*external_app*analogtv*);
*/external/analogtv/*(*ui*external_app*analogtv*);
} > ram_external_app_analogtv
.external_app_nrf_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_nrf_rx.application_information));
*(*ui*external_app*nrf_rx*);
*/external/nrf_rx/*(*ui*external_app*nrf_rx*);
} > ram_external_app_nrf_rx
.external_app_coasterp : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_coasterp.application_information));
*(*ui*external_app*coasterp*);
*/external/coasterp/*(*ui*external_app*coasterp*);
} > ram_external_app_coasterp
.external_app_lge : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_lge.application_information));
*(*ui*external_app*lge*);
*/external/lge/*(*ui*external_app*lge*);
} > ram_external_app_lge
.external_app_lcr : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_lcr.application_information));
*(*ui*external_app*lcr*);
*/external/lcr/*(*ui*external_app*lcr*);
} > ram_external_app_lcr
.external_app_jammer : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_jammer.application_information));
*(*ui*external_app*jammer*);
*/external/jammer/*(*ui*external_app*jammer*);
} > ram_external_app_jammer
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_gpssim.application_information));
*(*ui*external_app*gpssim*);
*/external/gpssim/*(*ui*external_app*gpssim*);
} > ram_external_app_gpssim
.external_app_spainter : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_spainter.application_information));
*(*ui*external_app*spainter*);
*/external/spainter/*(*ui*external_app*spainter*);
} > ram_external_app_spainter
.external_app_keyfob : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_keyfob.application_information));
*(*ui*external_app*keyfob*);
*/external/keyfob/*(*ui*external_app*keyfob*);
} > ram_external_app_keyfob
.external_app_tetris : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tetris.application_information));
*(*ui*external_app*tetris*);
*/external/tetris/*(*ui*external_app*tetris*);
} > ram_external_app_tetris
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_extsensors.application_information));
*(*ui*external_app*extsensors*);
*/external/extsensors/*(*ui*external_app*extsensors*);
} > ram_external_app_extsensors
.external_app_foxhunt_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_foxhunt_rx.application_information));
*(*ui*external_app*foxhunt_rx*);
*/external/foxhunt/*(*ui*external_app*foxhunt_rx*);
} > ram_external_app_foxhunt_rx
.external_app_audio_test : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_audio_test.application_information));
*(*ui*external_app*audio_test*);
*/external/audio_test/*(*ui*external_app*audio_test*);
} > ram_external_app_audio_test
.external_app_wardrivemap : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_wardrivemap.application_information));
*(*ui*external_app*wardrivemap*);
*/external/wardrivemap/*(*ui*external_app*wardrivemap*);
} > ram_external_app_wardrivemap
.external_app_tpmsrx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tpmsrx.application_information));
*(*ui*external_app*tpmsrx*);
*/external/tpmsrx/*(*ui*external_app*tpmsrx*);
} > ram_external_app_tpmsrx
.external_app_tpmstx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tpmstx.application_information));
*(*ui*external_app*tpmstx*);
*/external/tpmstx/*(*ui*external_app*tpmstx*);
} > ram_external_app_tpmstx
.external_app_protoview : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_protoview.application_information));
*(*ui*external_app*protoview*);
*/external/protoview/*(*ui*external_app*protoview*);
} > ram_external_app_protoview
.external_app_adsbtx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_adsbtx.application_information));
*(*ui*external_app*adsbtx*);
*/external/adsbtx/*(*ui*external_app*adsbtx*);
} > ram_external_app_adsbtx
.external_app_morse_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_morse_tx.application_information));
*(*ui*external_app*morse_tx*);
*/external/morse_tx/*(*ui*external_app*morse_tx*);
} > ram_external_app_morse_tx
.external_app_sstvtx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_sstvtx.application_information));
*(*ui*external_app*sstvtx*);
*/external/sstvtx/*(*ui*external_app*sstvtx*);
} > ram_external_app_sstvtx
.external_app_random_password : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_random_password.application_information));
*(*ui*external_app*random_password*);
*/external/random_password/*(*ui*external_app*random_password*);
} > ram_external_app_random_password
.external_app_acars_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_acars_rx.application_information));
*(*ui*external_app*acars_rx*);
*/external/acars_rx/*(*ui*external_app*acars_rx*);
} > ram_external_app_acars_rx
.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
*(*ui*external_app*shoppingcart_lock*);
*/external/shoppingcart_lock/*(*ui*external_app*shoppingcart_lock*);
} > ram_external_app_shoppingcart_lock
.external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_cvs_spam.application_information));
*(*ui*external_app*cvs_spam*);
*/external/cvs_spam/*(*ui*external_app*cvs_spam*);
} > ram_external_app_cvs_spam
.external_app_ookbrute : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_ookbrute.application_information));
*(*ui*external_app*ookbrute*);
*/external/ookbrute/*(*ui*external_app*ookbrute*);
} > ram_external_app_ookbrute
.external_app_ook_editor : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_ook_editor.application_information));
*(*ui*external_app*ook_editor*);
*/external/ook_editor/*(*ui*external_app*ook_editor*);
} > ram_external_app_ook_editor
.external_app_flippertx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_flippertx.application_information));
*(*ui*external_app*flippertx*);
*/external/flippertx/*(*ui*external_app*flippertx*);
} > ram_external_app_flippertx
.external_app_remote : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_remote.application_information));
*(*ui*external_app*remote*);
*/external/remote/*(*ui*external_app*remote*);
} > ram_external_app_remote
.external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_mcu_temperature.application_information));
*(*ui*external_app*mcu_temperature*);
*/external/mcu_temperature/*(*ui*external_app*mcu_temperature*);
} > ram_external_app_mcu_temperature
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_fmradio.application_information));
*(*ui*external_app*fmradio*);
*/external/fmradio/*(*ui*external_app*fmradio*);
} > ram_external_app_fmradio
.external_app_tuner : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tuner.application_information));
*(*ui*external_app*tuner*);
*/external/tuner/*(*ui*external_app*tuner*);
} > ram_external_app_tuner
.external_app_metronome : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_metronome.application_information));
*(*ui*external_app*metronome*);
*/external/metronome/*(*ui*external_app*metronome*);
} > ram_external_app_metronome
.external_app_app_manager : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_app_manager.application_information));
*(*ui*external_app*app_manager*);
*/external/app_manager/*(*ui*external_app*app_manager*);
} > ram_external_app_app_manager
.external_app_hopper : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_hopper.application_information));
*(*ui*external_app*hopper*);
*/external/hopper/*(*ui*external_app*hopper*);
} > ram_external_app_hopper
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_antenna_length.application_information));
*(*ui*external_app*antenna_length*);
*/external/antenna_length/*(*ui*external_app*antenna_length*);
} > ram_external_app_antenna_length
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_view_wav.application_information));
*(*ui*external_app*view_wav*);
*/external/wav_view/*(*ui*external_app*view_wav*);
} > ram_external_app_view_wav
.external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_sd_wipe.application_information));
*(*ui*external_app*sd_wipe*);
*/external/sd_wipe/*(*ui*external_app*sd_wipe*);
} > ram_external_app_sd_wipe
.external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_playlist_editor.application_information));
*(*ui*external_app*playlist_editor*);
*/external/playlist_editor/*(*ui*external_app*playlist_editor*);
} > ram_external_app_playlist_editor
.external_app_snake : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_snake.application_information));
*(*ui*external_app*snake*);
*/external/snake/*(*ui*external_app*snake*);
} > ram_external_app_snake
.external_app_stopwatch : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_stopwatch.application_information));
*(*ui*external_app*stopwatch*);
*/external/stopwatch/*(*ui*external_app*stopwatch*);
} > ram_external_app_stopwatch
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_wefax_rx.application_information));
*(*ui*external_app*wefax_rx*);
*/external/wefax_rx/*(*ui*external_app*wefax_rx*);
} > ram_external_app_wefax_rx
.external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_noaaapt_rx.application_information));
*(*ui*external_app*noaaapt_rx*);
*/external/noaaapt_rx/*(*ui*external_app*noaaapt_rx*);
} > ram_external_app_noaaapt_rx
.external_app_vor_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_vor_rx.application_information));
*(*ui*external_app*vor_rx*);
*/external/vor_rx/*(*ui*external_app*vor_rx*);
} > ram_external_app_vor_rx
.external_app_vor_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_vor_tx.application_information));
*(*ui*external_app*vor_tx*);
*/external/vor_tx/*(*ui*external_app*vor_tx*);
} > ram_external_app_vor_tx
.external_app_breakout : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_breakout.application_information));
*(*ui*external_app*breakout*);
*/external/breakout/*(*ui*external_app*breakout*);
} > ram_external_app_breakout
.external_app_doom : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_doom.application_information));
*(*ui*external_app*doom*);
*/external/doom/*(*ui*external_app*doom*);
} > ram_external_app_doom
.external_app_debug_pmem : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_debug_pmem.application_information));
*(*ui*external_app*debug_pmem*);
*/external/debug_pmem/*(*ui*external_app*debug_pmem*);
} > ram_external_app_debug_pmem
.external_app_scanner : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_scanner.application_information));
*(*ui*external_app*scanner*);
*/external/scanner/*(*ui*external_app*scanner*);
} > ram_external_app_scanner
.external_app_level : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_level.application_information));
*(*ui*external_app*level*);
*/external/level/*(*ui*external_app*level*);
} > ram_external_app_level
.external_app_gfxeq : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_gfxeq.application_information));
*(*ui*external_app*gfxeq*);
*/external/gfxeq/*(*ui*external_app*gfxeq*);
} > ram_external_app_gfxeq
.external_app_waterfall_designer : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_waterfall_designer.application_information));
*(*ui*external_app*waterfall_designer*);
*/external/waterfall_designer/*(*ui*external_app*waterfall_designer*);
} > ram_external_app_waterfall_designer
.external_app_detector_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_detector_rx.application_information));
*(*ui*external_app*detector_rx*);
*/external/detector_rx/*(*ui*external_app*detector_rx*);
} > ram_external_app_detector_rx
.external_app_dinogame : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_dinogame.application_information));
*(*ui*external_app*dinogame*);
*/external/dinogame/*(*ui*external_app*dinogame*);
} > ram_external_app_dinogame
.external_app_spaceinv : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_spaceinv.application_information));
*(*ui*external_app*spaceinv*);
*/external/spaceinv/*(*ui*external_app*spaceinv*);
} > ram_external_app_spaceinv
.external_app_blackjack : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_blackjack.application_information));
*(*ui*external_app*blackjack*);
*/external/blackjack/*(*ui*external_app*blackjack*);
} > ram_external_app_blackjack
.external_app_battleship : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_battleship.application_information));
*(*ui*external_app*battleship*);
*/external/battleship/*(*ui*external_app*battleship*);
} > ram_external_app_battleship
.external_app_ert : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_ert.application_information));
*(*ui*external_app*ert*);
*/external/ert/*(*ui*external_app*ert*);
} > ram_external_app_ert
.external_app_epirb_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_epirb_rx.application_information));
*(*ui*external_app*epirb_rx*);
*/external/epirb_rx/*(*ui*external_app*epirb_rx*);
} > ram_external_app_epirb_rx
.external_app_soundboard : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_soundboard.application_information));
*(*ui*external_app*soundboard*);
*/external/soundboard/*(*ui*external_app*soundboard*);
} > ram_external_app_soundboard
.external_app_game2048 : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_game2048.application_information));
*(*ui*external_app*game2048*);
*/external/game2048/*(*ui*external_app*game2048*);
} > ram_external_app_game2048
.external_app_bht_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_bht_tx.application_information));
*(*ui*external_app*bht_tx*);
*/external/bht_tx/*(*ui*external_app*bht_tx*);
} > ram_external_app_bht_tx
.external_app_morse_practice : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_morse_practice.application_information));
*(*ui*external_app*morse_practice*);
*/external/morse_practice/*(*ui*external_app*morse_practice*);
} > ram_external_app_morse_practice
.external_app_adult_toys_controller : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_adult_toys_controller.application_information));
*(*ui*external_app*adult_toys_controller*);
*/external/adult_toys_controller/*(*ui*external_app*adult_toys_controller*);
} > ram_external_app_adult_toys_controller
.external_app_flex_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_flex_rx.application_information));
*(*ui*external_app*flex_rx*);
*/external/flex_rx/*(*ui*external_app*flex_rx*);
} > ram_external_app_flex_rx
.external_app_sstvrx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_sstvrx.application_information));
*(*ui*external_app*sstvrx*);
*/external/sstvrx/*(*ui*external_app*sstvrx*);
} > ram_external_app_sstvrx
.external_app_subcarrx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_subcarrx.application_information));
*(*ui*external_app*subcarrx*);
*/external/subcarrx/*(*ui*external_app*subcarrx*);
} > ram_external_app_subcarrx
.external_app_siggen : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_siggen.application_information));
*(*ui*external_app*siggen*);
*/external/siggen/*(*ui*external_app*siggen*);
} > ram_external_app_siggen
.external_app_sdusb : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_sdusb.application_information));
*(*ui*external_app*sdusb*);
*/external/sdusb/*(*ui*external_app*sdusb*);
} > ram_external_app_sdusb
.external_app_morse_radio : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_morse_radio.application_information));
*(*ui*external_app*morse_radio*);
*/external/morse_radio/*(*ui*external_app*morse_radio*);
} > ram_external_app_morse_radio
.external_app_morseradiotx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_morseradiotx.application_information));
*(*ui*external_app*morseradiotx*);
*/external/morseradiotx/*(*ui*external_app*morseradiotx*);
} > ram_external_app_morseradiotx
.external_app_keeloqtx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_keeloqtx.application_information));
*(*ui*external_app*keeloqtx*);
*/external/keeloqtx/*(*ui*external_app*keeloqtx*);
} > ram_external_app_keeloqtx
.external_app_rtty_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_rtty_rx.application_information));
*(*ui*external_app*rtty_rx*);
*/external/rtty_rx/*(*ui*external_app*rtty_rx*);
} > ram_external_app_rtty_rx
.external_app_rtty_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_rtty_tx.application_information));
*(*ui*external_app*rtty_tx*);
*/external/rtty_tx/*(*ui*external_app*rtty_tx*);
} > ram_external_app_rtty_tx
.external_app_pocsag_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_pocsag_tx.application_information));
*(*ui*external_app*pocsag_tx*);
*/external/pocsag_tx/*(*ui*external_app*pocsag_tx*);
} > ram_external_app_pocsag_tx
.external_app_time_sink : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_time_sink.application_information));
*(*ui*external_app*time_sink*);
*/external/time_sink/*(*ui*external_app*time_sink*);
} > ram_external_app_time_sink
.external_app_same_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_same_tx.application_information));
*(*ui*external_app*same_tx*);
*/external/same_tx/*(*ui*external_app*same_tx*);
} > ram_external_app_same_tx
.external_app_kiss_tnc : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_kiss_tnc.application_information));
*(*ui*external_app*kiss_tnc*);
*/external/kiss_tnc/*(*ui*external_app*kiss_tnc*);
} > ram_external_app_kiss_tnc
.external_app_mdc_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_mdc_tx.application_information));
*(*ui*external_app*mdc_tx*);
*/external/mdc_tx/*(*ui*external_app*mdc_tx*);
} > ram_external_app_mdc_tx
.external_app_epirb_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_epirb_tx.application_information));
*(*ui*external_app*epirb_tx*);
*/external/epirb_tx/*(*ui*external_app*epirb_tx*);
} > ram_external_app_epirb_tx
.external_app_fpv_detect : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_fpv_detect.application_information));
*(*ui*external_app*fpv_detect*);
*/external/fpv_detect/*(*ui*external_app*fpv_detect*);
} > ram_external_app_fpv_detect
.external_app_p25_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_p25_tx.application_information));
*(*ui*external_app*p25_tx*);
*/external/p25_tx/*(*ui*external_app*p25_tx*);
} > ram_external_app_p25_tx
.external_app_two_tone_pager : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_two_tone_pager.application_information));
*(*ui*external_app*two_tone_pager*);
*/external/two_tone_pager/*(*ui*external_app*two_tone_pager*);
} > ram_external_app_two_tone_pager
.external_app_two_tone_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_two_tone_rx.application_information));
*(*ui*external_app*two_tone_rx*);
*/external/two_tone_rx/*(*ui*external_app*two_tone_rx*);
} > ram_external_app_two_tone_rx
.external_app_flex_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_flex_tx.application_information));
*(*ui*external_app*flex_tx*);
*/external/flex_tx/*(*ui*external_app*flex_tx*);
} > ram_external_app_flex_tx
.external_app_hard_reset : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_hard_reset.application_information));
*(*ui*external_app*hard_reset*);
*/external/hard_reset/*(*ui*external_app*hard_reset*);
} > ram_external_app_hard_reset
.external_app_secplustx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_secplustx.application_information));
*(*ui*external_app*secplustx*);
*/external/secplustx/*(*ui*external_app*secplustx*);
} > ram_external_app_secplustx
.external_app_signal_hunter : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_signal_hunter.application_information));
*(*ui*external_app*signal_hunter*);
*/external/signal_hunter/*(*ui*external_app*signal_hunter*);
} > ram_external_app_signal_hunter
.external_app_tetra_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tetra_rx.application_information));
*(*ui*external_app*tetra_rx*);
*/external/tetra_rx/*(*ui*external_app*tetra_rx*);
} > ram_external_app_tetra_rx
.external_app_adsbrx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_adsbrx.application_information));
*(*ui*external_app*adsbrx*);
} > ram_external_app_adsbrx
.external_app_ais_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_ais_rx.application_information));
*(*ui*external_app*ais_rx*);
} > ram_external_app_ais_rx
.external_app_aprs_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_aprs_rx.application_information));
*(*ui*external_app*aprs_rx*);
} > ram_external_app_aprs_rx
.external_app_aprs_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_aprs_tx.application_information));
*(*ui*external_app*aprs_tx*);
} > ram_external_app_aprs_tx
}
+38 -2
View File
@@ -34,7 +34,7 @@ void KeeloqTXView::update_hop() {
hop = data.btn << 28 | (apri_serial & 0xFFF) << 16 | data.counter;
} else if (
data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449") {
data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449" || data.mf_name == "Superrollo") {
hop = data.btn << 28 | (data.serial & 0xFFF) << 16 | data.counter;
} else if (
data.mf_name == "NICE_Smilo" || data.mf_name == "NICE_MHOUSE" || data.mf_name == "JCM_Tech") {
@@ -75,6 +75,12 @@ void KeeloqTXView::update_payload() {
}
}
if (data.mf_name == "Superrollo") {
text_payload.set(to_string_hex((uint64_t)(uint32_t)encrypt));
encode_data_gw60((uint32_t)encrypt);
return;
}
payload = (uint64_t)fix << 32 | encrypt;
uint64_t preview_payload = FProtoGeneral::subghz_protocol_blocks_reverse_key(payload, 64);
@@ -83,6 +89,34 @@ void KeeloqTXView::update_payload() {
encode_data();
}
// Superrollo GW60 (HCS361) 67-bit OOK frame at Te=450us.
void KeeloqTXView::encode_data_gw60(uint32_t hop_enc) {
int bits[67];
for (uint32_t i = 0; i < 32; i++) bits[i] = (hop_enc >> i) & 1;
for (uint32_t i = 0; i < 28; i++) bits[32 + i] = (data.serial >> i) & 1;
for (uint32_t i = 0; i < 4; i++) bits[60 + i] = (data.btn >> i) & 1;
bits[64] = 1;
int crc0 = 0, crc1 = 0;
for (uint32_t i = 0; i < 65; i++) {
int new_crc1 = crc0 ^ bits[i];
int new_crc0 = new_crc1 ^ crc1;
crc0 = new_crc0 & 1;
crc1 = new_crc1 & 1;
}
bits[65] = crc0;
bits[66] = crc1;
std::string s{};
for (int i = 0; i < 9; i++) s += "100";
s += "11111111110000000000";
for (int i = 0; i < 67; i++) s += keeloq_fragments[bits[i]];
for (int i = 0; i < 18; i++) s += "0";
encoded_data = s;
pause_duration = 0;
}
void KeeloqTXView::encode_data() {
std::string fragments{};
@@ -206,9 +240,11 @@ void KeeloqTXView::start_tx() {
transmitter_model.enable();
const double te_us = (data.mf_name == "Superrollo") ? 450.0 : 400.0;
baseband::set_ook_data(
bitstream_length,
OOK_SAMPLERATE * (400.0 / 1000000.0),
OOK_SAMPLERATE * (te_us / 1000000.0),
repeat,
pause_duration);
}
@@ -106,6 +106,7 @@ class KeeloqTXView : public View {
std::string encoded_data{};
void encode_data();
void encode_data_gw60(uint32_t hop_enc);
uint32_t repeat = 4;
uint32_t pause_duration = 0;
+1 -1
View File
@@ -46,7 +46,7 @@ SdOverUsbView::SdOverUsbView(NavigationView& nav)
sdcStop(&SDCD1);
portapack::shutdown(true, false);
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
baseband::run_prepared_image(portapack::memory::map::m4_code.base(), false);
m0_halt();
/* will not return*/
};
@@ -28,11 +28,47 @@
#include "ui_fileman.hpp"
#include "file_reader.hpp"
#include "ui_textentry.hpp"
#include "convert.hpp"
#include <algorithm>
using namespace portapack;
namespace ui::external_app::waterfall_designer {
namespace {
/* Parses an "index,R,G,B" profile line.
* NB: std::stoi() must not be used here - the firmware is built with
* -fno-exceptions, so any malformed field in a hand-edited profile makes it
* call std::terminate() instead of throwing. parse_int() is what gradient.cpp
* uses to read the very same file format. */
bool parse_level(std::string_view line, uint8_t& index, uint8_t& r, uint8_t& g, uint8_t& b) {
// Comments are skipped by Gradient::load_file() too; parse_int() ignores
// trailing junk, so "#0,0,0,0" would otherwise read back as a real level.
if (line.empty() || line.front() == '#') return false;
auto cols = split_string(line, ',');
if (cols.size() != 4) return false;
uint8_t* const out[4] = {&index, &r, &g, &b};
for (size_t i = 0; i < 4; i++) {
int32_t value;
if (!parse_int(cols[i], value) || value < 0 || value > 255) return false;
*out[i] = static_cast<uint8_t>(value);
}
return true;
}
bool is_color_level(const std::string& line) {
uint8_t index, r, g, b;
return parse_level(line, index, r, g, b);
}
} // namespace
WaterfallDesignerView::WaterfallDesignerView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
@@ -152,7 +188,11 @@ WaterfallDesignerView::WaterfallDesignerView(
}
WaterfallDesignerView::~WaterfallDesignerView() {
if (!if_apply_setting) restore_current_profile();
if (!if_apply_setting)
restore_current_profile();
else if (profile_backed_up_)
delete_file(waterfalls_dir / u"wtf_des_bk.bk"); // kept the edit, drop the backup
receiver_model.disable();
baseband::shutdown();
}
@@ -176,35 +216,68 @@ void WaterfallDesignerView::on_open_profile() {
auto open_view = nav_.push<FileLoadView>(".txt");
open_view->push_dir(waterfalls_dir);
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_profile_changed(new_file_path);
// NB: FileLoadView calls this and then pops, same as text_prompt does.
// Defer for the same two reasons - see on_create_new_profile().
pending_profile_path = std::move(new_file_path);
nav_.set_on_pop([this]() {
on_profile_changed(pending_profile_path);
});
};
}
void WaterfallDesignerView::on_profile_changed(std::filesystem::path new_profile_path) {
current_profile_path = new_profile_path;
bool WaterfallDesignerView::read_profile_file(const std::filesystem::path& path) {
File profile_file;
if (profile_file.open(path)) return false;
profile_levels.clear();
File playlist_file;
auto error = playlist_file.open(new_profile_path.string());
if (error) return;
menu_view.clear();
auto reader = FileLineReader(playlist_file);
auto reader = FileLineReader(profile_file);
for (const auto& line : reader) {
// remove empty lines
if (line == "\n" || line == "\r\n" || line == "\r") continue;
profile_levels.push_back(line);
auto entry = line;
// Strip the whole line terminator; the old code only dropped one char,
// so CRLF files kept a stray '\r' in every entry.
while (!entry.empty() && (entry.back() == '\n' || entry.back() == '\r'))
entry.pop_back();
if (entry.empty()) continue;
profile_levels.push_back(std::move(entry));
}
for (auto& line : profile_levels) {
// remove line end \n etc
if (line.length() > 0 && (line[line.length() - 1] == '\n' || line[line.length() - 1] == '\r')) {
line = line.substr(0, line.length() - 1);
}
return true;
}
bool WaterfallDesignerView::write_profile_file(const std::filesystem::path& path) {
File profile_file;
if (profile_file.open(path, false, true)) return false;
// FA_OPEN_ALWAYS leaves the position at end-of-file, so rewind before
// truncating or the existing contents are kept and appended to.
if (profile_file.seek(0).is_error()) return false;
if (profile_file.truncate().is_error()) return false;
for (const auto& entry : profile_levels)
if (profile_file.write_line(entry)) return false;
/* ~File() calls f_close(), which flushes, but its result is unreachable.
* Sync explicitly so a full or yanked card is reported to the caller
* instead of being announced as a successful save. */
return !profile_file.sync();
}
void WaterfallDesignerView::on_profile_changed(const std::filesystem::path& new_profile_path) {
// read_profile_file() only touches profile_levels once the open succeeded,
// so a failure here leaves the previously loaded profile intact.
if (!read_profile_file(new_profile_path)) {
nav_.display_modal("Err", "open err");
return;
}
current_profile_path = new_profile_path;
highlighted_index_ = 0;
button_save.hidden(false);
button_add_level.hidden(false);
button_remove_level.hidden(false);
@@ -219,53 +292,52 @@ void WaterfallDesignerView::refresh_menu_view() {
menu_view.clear();
for (const auto& line : profile_levels) {
if (line.length() == 0 || line[0] == '#') {
uint8_t index, r, g, b;
// index,R,G,B - anything else (comment, header, garbage) is shown greyed.
if (parse_level(line, index, r, g, b)) {
menu_view.add_item({line,
ui::Color(r, g, b),
&bitmap_icon_cwgen,
[this](KeyEvent) {
button_remove_level.focus();
}});
} else {
menu_view.add_item({line,
ui::Color::grey(),
&bitmap_icon_notepad,
[this](KeyEvent) {
button_add_level.focus();
}});
} else {
// index,R,G,B
size_t pos = 0;
size_t next_pos = 0;
// pass index
next_pos = line.find(',', pos);
if (next_pos == std::string::npos) continue;
pos = next_pos + 1;
// r
next_pos = line.find(',', pos);
if (next_pos == std::string::npos) continue;
uint8_t r = static_cast<uint8_t>(std::stoi(line.substr(pos, next_pos - pos)));
pos = next_pos + 1;
// g
next_pos = line.find(',', pos);
if (next_pos == std::string::npos) continue;
uint8_t g = static_cast<uint8_t>(std::stoi(line.substr(pos, next_pos - pos)));
pos = next_pos + 1;
// b
uint8_t b = static_cast<uint8_t>(std::stoi(line.substr(pos)));
ui::Color color = ui::Color(r, g, b);
menu_view.add_item({line,
color,
&bitmap_icon_cwgen,
[this](KeyEvent) {
button_remove_level.focus();
}});
}
}
// menu_view.clear() resets its own highlight to 0 but does not fire
// on_highlight, so highlighted_index_ has to be re-synced by hand or the
// edit/remove buttons act on a different row than the one the user sees.
if (highlighted_index_ >= profile_levels.size())
highlighted_index_ = profile_levels.empty() ? 0 : profile_levels.size() - 1;
if (!profile_levels.empty())
menu_view.set_highlighted(highlighted_index_);
set_dirty();
}
void WaterfallDesignerView::on_apply_current_to_wtf() {
std::filesystem::path system_read_path = "waterfall.txt";
copy_file(current_profile_path, system_read_path);
/* Take the backup lazily, right before the first overwrite of the live
* gradient. Doing it in the constructor meant copy_file() (~1.8kB of stack)
* ran underneath run_external_app(), which holds a 776 byte frame with a
* File of its own - about 3.6kB of the 4kB M0 process stack. It also did SD
* I/O even when the user just opened the app and backed straight out. */
backup_current_profile();
/* NB: this used to copy_file(current_profile_path, "waterfall.txt"), which
* costs ~1.8kB of stack (two FILs + a 512 byte block buffer) out of the 4kB
* M0 process stack, on top of whatever UI callback we are nested under.
* profile_levels is the authoritative copy of the profile anyway, so write
* it straight out through a single File instead. */
if (!write_profile_file(u"waterfall.txt")) return;
waterfall.load_gradient();
@@ -281,71 +353,82 @@ void WaterfallDesignerView::on_save_profile() {
return;
}
File profile_file;
auto error = profile_file.open(current_profile_path.string(), false, false);
if (error) {
nav_.display_modal("Err", "open err");
// NB: not just "open err" any more - this now also covers a failed
// truncate/write/flush, i.e. the file may be partially written.
if (!write_profile_file(current_profile_path)) {
nav_.display_modal("Err", "write failed");
return;
}
// clear file
profile_file.seek(0);
profile_file.truncate();
// write new data
for (const auto& entry : profile_levels) {
profile_file.write_line(entry);
}
nav_.display_modal("Save", "Saved profile\n" + current_profile_path.string());
}
void WaterfallDesignerView::on_add_level() {
if (highlighted_index_ >= profile_levels.size()) return;
if (profile_levels[highlighted_index_].empty()) return;
if (profile_levels[highlighted_index_][0] == '#') return;
if (profile_levels[highlighted_index_].find(',') == std::string::npos) return;
size_t insert_pos = highlighted_index_;
std::string new_entry = "0,128,128,128";
profile_levels.insert(profile_levels.begin() + insert_pos, new_entry);
if (current_profile_path.empty()) return;
/* A freshly created profile has no rows at all, and the old guard
* (highlighted_index_ >= size()) then refused with no feedback - so the
* button looked dead on exactly the file you just made. Seed instead. */
size_t insert_pos = std::min<size_t>(highlighted_index_, profile_levels.size());
// Don't push a level above a comment/header row; drop it after instead.
if (insert_pos < profile_levels.size() && !is_color_level(profile_levels[insert_pos]))
insert_pos++;
profile_levels.insert(profile_levels.begin() + insert_pos, "0,128,128,128");
highlighted_index_ = insert_pos;
refresh_menu_view();
on_edit_color();
}
void WaterfallDesignerView::on_remove_level() {
if (highlighted_index_ >= profile_levels.size()) return;
if (profile_levels[highlighted_index_].empty()) return;
if (profile_levels[highlighted_index_][0] == '#') return;
if (profile_levels[highlighted_index_].find(',') == std::string::npos) return;
if (!is_color_level(profile_levels[highlighted_index_])) return;
profile_levels.erase(profile_levels.begin() + highlighted_index_);
refresh_menu_view();
}
void WaterfallDesignerView::on_edit_color() {
if (highlighted_index_ >= profile_levels.size()) return;
if (profile_levels[highlighted_index_].empty()) return;
if (profile_levels[highlighted_index_][0] == '#') return;
if (profile_levels[highlighted_index_].find(',') == std::string::npos) return;
if (!is_color_level(profile_levels[highlighted_index_])) return;
auto color_picker_view = nav_.push<WaterfallDesignerColorPickerView>(profile_levels[highlighted_index_]);
color_picker_view->on_save = [this](std::string new_color) {
profile_levels[highlighted_index_] = new_color;
// Capture the row by value: highlighted_index_ can move (or the list can
// shrink) while the picker is up, and the old code wrote to whatever
// highlighted_index_ happened to be at save time with no bounds check.
const size_t index = highlighted_index_;
auto color_picker_view = nav_.push<WaterfallDesignerColorPickerView>(profile_levels[index]);
color_picker_view->on_save = [this, index](std::string new_color) {
if (index >= profile_levels.size()) return;
profile_levels[index] = std::move(new_color);
refresh_menu_view();
on_apply_current_to_wtf();
};
}
void WaterfallDesignerView::backup_current_profile() {
std::filesystem::path curren_wtf_path = "waterfall.txt";
std::filesystem::path backup_path = waterfalls_dir / "wtf_des_bk.bk";
copy_file(curren_wtf_path, backup_path);
if (backup_attempted_) return;
backup_attempted_ = true;
std::filesystem::path current_wtf_path = u"waterfall.txt";
std::filesystem::path backup_path = waterfalls_dir / u"wtf_des_bk.bk";
if (!file_exists(current_wtf_path)) return;
profile_backed_up_ = copy_file(current_wtf_path, backup_path).ok();
}
void WaterfallDesignerView::restore_current_profile() {
std::filesystem::path backup_path = waterfalls_dir / "wtf_des_bk.bk";
std::filesystem::path put_back_path = "waterfall.txt";
copy_file(backup_path, put_back_path);
// Only touch waterfall.txt if we actually took a backup on entry; otherwise
// this would silently do nothing and leave the edited gradient installed.
if (!profile_backed_up_) return;
std::filesystem::path backup_path = waterfalls_dir / u"wtf_des_bk.bk";
copy_file(backup_path, u"waterfall.txt");
delete_file(backup_path);
}
@@ -365,16 +448,35 @@ void WaterfallDesignerView::on_create_new_profile() {
buffer += ".txt";
}
File new_file;
auto error = new_file.create(waterfalls_dir / buffer);
if (error) {
nav_.display_modal("Err", "create file err");
auto new_path = waterfalls_dir / buffer;
bool created;
{
// Scoped so the FIL is closed (and its 512 byte sector cache is
// off the stack) before anything re-opens the same file - _FS_LOCK
// is 0, so FatFs will not stop us from double-opening it.
File new_file;
created = !new_file.create(new_path);
}
/* NB: everything below has to run *after* the keyboard view is gone.
* text_prompt()'s caller invokes this handler and then pops, so:
* - pushing a modal from here would pop the modal, not the keyboard;
* - and running on_profile_changed() inline stacks its File plus the
* gradient write under the alphanum/button/dispatch frames, which
* is what blows the 4kB M0 process stack ("Stack Overflow" guru).
* set_on_pop() defers it to just after the pop, at a shallow depth. */
if (!created) {
nav_.set_on_pop([this]() {
nav_.display_modal("Err", "create file err");
});
return;
}
profile_levels.clear();
current_profile_path = waterfalls_dir / buffer;
on_profile_changed(current_profile_path);
pending_profile_path = new_path;
nav_.set_on_pop([this]() {
on_profile_changed(pending_profile_path);
});
});
}
@@ -392,32 +494,7 @@ WaterfallDesignerColorPickerView::WaterfallDesignerColorPickerView(NavigationVie
progressbar.set_max(UINT8_MAX);
size_t pos = 0;
size_t next_pos = 0;
// index
next_pos = color_str.find(',', pos);
if (next_pos != std::string::npos) {
index_ = static_cast<uint8_t>(std::stoi(color_str.substr(pos, next_pos - pos)));
pos = next_pos + 1;
}
// r
next_pos = color_str.find(',', pos);
if (next_pos != std::string::npos) {
red_ = static_cast<uint8_t>(std::stoi(color_str.substr(pos, next_pos - pos)));
pos = next_pos + 1;
}
// g
next_pos = color_str.find(',', pos);
if (next_pos != std::string::npos) {
green_ = static_cast<uint8_t>(std::stoi(color_str.substr(pos, next_pos - pos)));
pos = next_pos + 1;
}
// b
blue_ = static_cast<uint8_t>(std::stoi(color_str.substr(pos)));
parse_level(color_str_, index_, red_, green_, blue_);
field_red.set_value(red_);
field_green.set_value(green_);
@@ -466,19 +543,12 @@ void WaterfallDesignerColorPickerView::update_color_index() {
green_ = static_cast<uint8_t>(field_green.value());
blue_ = static_cast<uint8_t>(field_blue.value());
const Rect preview_rect{screen_width - 48, 1 * 16, 40, 40};
Painter painter_instance_2;
painter_instance_2.fill_rectangle(
{preview_rect.left(), preview_rect.top(), preview_rect.width(), preview_rect.height()},
ui::Color(red_, green_, blue_));
// Repaint through the normal dirty pass instead of driving the display from
// inside an on_change handler; paint() below already draws the swatch.
set_dirty();
}
void WaterfallDesignerColorPickerView::paint(Painter& painter) {
// this is not duplicated code.
// because need to display color when enter,
// but it is too early to call update_color() in the constructor.
const Rect preview_rect{screen_width - 48, 1 * 16, 40, 40};
painter.fill_rectangle(
@@ -487,14 +557,12 @@ void WaterfallDesignerColorPickerView::paint(Painter& painter) {
}
std::string WaterfallDesignerColorPickerView::build_color_str() {
size_t index_pos = color_str_.find(',');
if (index_pos != std::string::npos) {
return color_str_.substr(0, index_pos + 1) +
to_string_dec_uint(red_) + "," +
to_string_dec_uint(green_) + "," +
to_string_dec_uint(blue_);
}
return to_string_dec_uint(index_) + "," + to_string_dec_uint(red_) + "," + to_string_dec_uint(green_) + "," + to_string_dec_uint(blue_);
// Always emit index_ - the old version kept the index substring from the
// input line, so edits made with field_index were silently discarded.
return to_string_dec_uint(index_) + "," +
to_string_dec_uint(red_) + "," +
to_string_dec_uint(green_) + "," +
to_string_dec_uint(blue_);
}
} /* namespace ui::external_app::waterfall_designer */
@@ -217,18 +217,31 @@ class WaterfallDesignerView : public View {
void restore_current_profile();
void on_create_new_profile();
void on_open_profile();
void on_profile_changed(std::filesystem::path new_profile_path);
void on_profile_changed(const std::filesystem::path& new_profile_path);
void on_save_profile();
void on_add_level();
void on_remove_level();
void on_edit_color();
/* Each of these owns the only File in its call chain. Keeping them as
* separate frames matters: a File carries a 512 byte FIL sector cache
* (_FS_TINY is 0) and the M0 process stack is 4kB, so if read_profile_file
* merged into on_profile_changed its 784 byte frame would stay live across
* the copy_file() in on_apply_current_to_wtf() - 3.6kB of 4kB instead of
* 2.8kB. GCC does not inline them at -O2 today; noinline just pins that so
* a heuristic change can't quietly eat the headroom. */
__attribute__((noinline)) bool read_profile_file(const std::filesystem::path& path);
__attribute__((noinline)) bool write_profile_file(const std::filesystem::path& path);
void refresh_menu_view();
void on_apply_current_to_wtf(); // will restore if didn't apple, when distruct
void on_apply_setting(); // apply set
bool if_apply_setting{false};
bool backup_attempted_{false};
bool profile_backed_up_{false};
std::filesystem::path pending_profile_path{}; // set by the New dialog, consumed on_pop
/*NB:
this works as:
each time you change color, it apply as file realtime
+83 -20
View File
@@ -28,10 +28,15 @@
#include "max2831.hpp"
#include "portapack_adc.hpp"
#include "hackrf_hal.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "gpio.hpp"
using namespace gpio_control;
#include "ch.h"
#include "hal.h"
@@ -151,7 +156,7 @@ void MAX2831::set_mode(const Mode mode) {
* RX: ENABLE=1, RXTX=0
* TX: ENABLE=1, RXTX=1
*
* Note: gpio_max2831_rx_enable is the RXTX mode select pin.
* Note: max2831_rxtx_enable is the RXTX mode select pin.
* RXTX=0 selects RX, RXTX=1 selects TX.
*/
@@ -174,26 +179,26 @@ void MAX2831::set_mode(const Mode mode) {
switch (mode) {
default:
case Mode::Shutdown:
gpio_max2831_rx_enable.write(0); /* RXTX=0 */
gpio_max283x_enable.write(0); /* ENABLE=0 */
max2831_rxtx_enable.setInactive(); /* RXTX=0 */
max283x_enable.setInactive(); /* ENABLE=0 */
set_rssi_mux(0);
break;
case Mode::Standby:
gpio_max2831_rx_enable.write(1); /* RXTX=1 */
gpio_max283x_enable.write(0); /* ENABLE=0 */
max2831_rxtx_enable.setActive(); /* RXTX=1 */
max283x_enable.setInactive(); /* ENABLE=0 */
set_rssi_mux(0);
break;
case Mode::Transmit:
case Mode::Tx_Calibration:
gpio_max2831_rx_enable.write(1); /* RXTX=1 for TX */
gpio_max283x_enable.write(1); /* ENABLE=1 */
max2831_rxtx_enable.setActive(); /* RXTX=1 for TX */
max283x_enable.setActive(); /* ENABLE=1 */
set_rssi_mux(2); // transmit power
break;
case Mode::Receive:
case Mode::Rx_Calibration:
gpio_max2831_rx_enable.write(0); /* RXTX=0 for RX */
gpio_max283x_enable.write(1); /* ENABLE=1 */
set_rssi_mux(1); // RSSI
max2831_rxtx_enable.setInactive(); /* RXTX=0 for RX */
max283x_enable.setActive(); /* ENABLE=1 */
set_rssi_mux(1); // RSSI
break;
}
@@ -227,8 +232,15 @@ void MAX2831::set_lna_gain(const int_fast8_t db) {
} else {
gain_val = REG11_LNA_GAIN_M33;
}
max2831_rxhp.setActive(); // Fast DC offset compensation
set_reg_field(11, REG11_LNA_GAIN_MASK, gain_val);
flush_reg(11);
chThdSleepMicroseconds(2);
max2831_rxhp.setInactive();
}
void MAX2831::set_vga_gain(const int_fast8_t db) {
@@ -237,10 +249,18 @@ void MAX2831::set_vga_gain(const int_fast8_t db) {
if ((db & 0x1) || db > 62) {
return; /* Invalid: must be even and <= 62 */
}
int_fast8_t db_clipped = std::max(0, std::min(62, (int)db));
uint16_t value = (db_clipped >> 1) & 0x1f;
max2831_rxhp.setActive(); // Fast DC offset compensation
set_reg_field(11, REG11_RXVGA_GAIN_MASK, value);
flush_reg(11);
chThdSleepMicroseconds(2);
max2831_rxhp.setInactive();
}
/*
@@ -365,9 +385,9 @@ void MAX2831::set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) {
void MAX2831::set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) {
_desired_lpf_bw = bandwidth_minimum;
#ifdef PRALINE
gpio_control::aa_en.clear(); // Disable external AA filter for wideband operations
#endif
gpio_control::aa_en.setInactive(); // Disable external AA filter for wideband operations
if (_mode == Mode::Transmit || _mode == Mode::Tx_Calibration) {
set_lpf_bandwidth_internal(bandwidth_minimum);
}
@@ -461,13 +481,56 @@ void MAX2831::set_rx_buff_vcm(const size_t v) {
}
int8_t MAX2831::temp_sense() {
/* MAX2831 temperature sensor can be read via RSSI MUX.
* This would require:
* 1. Switch RSSI_MUX to temperature mode
* 2. Read the ADC
* 3. Switch back to RSSI mode
* For now, return a placeholder value. */
return 25; /* Room temperature placeholder */
bool not_in_rx = (_mode != Mode::Receive && _mode != Mode::Rx_Calibration);
if (not_in_rx) {
max2831_rxtx_enable.setInactive();
max283x_enable.setActive();
chThdSleepMilliseconds(1);
}
set_rssi_mux(3);
chThdSleepMilliseconds(5);
uint32_t saved_cr = LPC_ADC1->CR;
uint32_t cr_temp = saved_cr;
cr_temp &= ~0xFF;
cr_temp |= (1 << portapack::adc1_rssi_input);
if (((cr_temp >> 8) & 0xFF) == 0) {
cr_temp |= (0xFF << 8);
}
cr_temp &= ~(1 << 16);
cr_temp |= (1 << 21);
cr_temp &= ~(7 << 24);
cr_temp |= (1 << 24);
LPC_ADC1->CR = cr_temp;
uint32_t val;
while (((val = LPC_ADC1->DR[portapack::adc1_rssi_input]) & (1UL << 31)) == 0) {
__asm__("nop");
}
uint32_t adc_raw = (val >> 6) & 0x3FF;
LPC_ADC1->CR = saved_cr;
if (not_in_rx) {
set_mode(_mode);
} else {
set_rssi_mux(1);
}
float v_adc = (adc_raw / 1023.0f) * 3.3f;
constexpr float V_25_CELSIUS = 1.185f;
constexpr float SLOPE = 0.003f;
float temp = 25.0f + ((v_adc - V_25_CELSIUS) / SLOPE);
return static_cast<int8_t>(std::max(-128.0f, std::min(127.0f, temp)));
}
reg_t MAX2831::read(const address_t reg_num) {
+8 -19
View File
@@ -19,12 +19,17 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef PRALINE // not praline
#include "max2837.hpp"
#include "hackrf_hal.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "gpio.hpp"
using namespace gpio_control;
#include "ch.h"
#include "hal.h"
@@ -99,12 +104,6 @@ constexpr uint32_t pll_factor = 1.0 / (4.0 / 3.0 / reference_frequency) + 0.5;
void MAX2837::init() {
set_mode(Mode::Shutdown);
#ifndef PRALINE
gpio_max283x_enable.output();
gpio_max2837_rxenable.output();
gpio_max2837_txenable.output();
#endif
_map.r.tx_gain.TXVGA_GAIN_SPI_EN = 1;
_map.r.tx_gain.TXVGA_GAIN_MSB_SPI_EN = 1;
_map.r.tx_gain.TXVGA_GAIN_SPI = 0x00;
@@ -159,12 +158,6 @@ void MAX2837::set_tx_LO_iq_phase_calibration(const size_t v) {
// TX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,0,1 (5dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
#ifndef PRALINE
gpio_max283x_enable.output();
gpio_max2837_rxenable.output();
gpio_max2837_txenable.output();
#endif
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one(Register::SPI_EN);
@@ -214,7 +207,7 @@ void MAX2837::set_mode(const Mode mode) { // We set up the 3 Logic Pins ENABLE,
_mode = mode;
Mask mask = mode_mask(mode);
gpio_max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
gpio_max2837_rxenable.write(toUType(mask) & toUType(Mask::RxEnable));
gpio_max2837_txenable.write(toUType(mask) & toUType(Mask::TxEnable));
}
@@ -352,12 +345,6 @@ void MAX2837::set_rx_LO_iq_phase_calibration(const size_t v) {
// RX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,1,0 (3dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode(Mode::Rx_Calibration); // write to ram 3 LOGIC Pins .
#ifndef PRALINE
gpio_max283x_enable.output();
gpio_max2837_rxenable.output();
gpio_max2837_txenable.output();
#endif
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one(Register::SPI_EN);
@@ -419,3 +406,5 @@ int8_t MAX2837::temp_sense() {
}
} // namespace max2837
#endif // not PRALINE
+7 -17
View File
@@ -19,6 +19,7 @@
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef PRALINE // not praline
#include "max2839.hpp"
@@ -26,6 +27,9 @@
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "gpio.hpp"
using namespace gpio_control;
#include "ch.h"
#include "hal.h"
@@ -100,11 +104,6 @@ static int_fast8_t requested_rx_vga_gain = 0;
void MAX2839::init() {
set_mode(Mode::Shutdown);
#ifndef PRALINE
gpio_max283x_enable.output();
gpio_max2839_rxtx.output();
#endif
_map.r.rxrf_1.MIMOmode = 1; /* enable RXINB */
_map.r.pa_drv.TXVGA_GAIN_SPI_EN = 1;
@@ -154,11 +153,6 @@ void MAX2839::set_tx_LO_iq_phase_calibration(const size_t v) {
// TX calibration , 2 x Logic pins , ENABLE, RXENABLE = 1,0, (2dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
#ifndef PRALINE
gpio_max283x_enable.output(); // max2839 has only 2 x pins + regs to decide mode.
gpio_max2839_rxtx.output(); // Here is combined rx & tx pin in one port.
#endif
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one(Register::SPI_EN);
@@ -207,7 +201,7 @@ void MAX2839::set_mode(const Mode mode) {
_mode = mode;
Mask mask = mode_mask(mode);
gpio_max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
gpio_max2839_rxtx.write(toUType(mask) & toUType(Mask::RxTx));
}
@@ -395,11 +389,6 @@ void MAX2839::set_rx_LO_iq_phase_calibration(const size_t v) {
// RX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,1,0 (3dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode(Mode::Rx_Calibration); // write to ram 3 LOGIC Pins .
#ifndef PRALINE
gpio_max283x_enable.output(); // max2839 has only 2 x pins + regs to decide mode.
gpio_max2839_rxtx.output(); // Here is combined rx & tx pin in one port.
#endif
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one(Register::SPI_EN);
@@ -446,4 +435,5 @@ int8_t MAX2839::temp_sense() {
return std::min(127, (int)(value * 4.31 - 40)); // reg value is 0 to 31; possible return range is -40 C to 127 C
}
} // namespace max2839
} // namespace max2839
#endif // not PRALINE
+9 -7
View File
@@ -26,9 +26,13 @@
#include "utility.hpp"
#include "hackrf_hal.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "gpio.hpp"
using namespace gpio_control;
#include "hal.h"
#ifdef PRALINE
@@ -176,13 +180,11 @@ struct SynthConfig {
void RFFC507x::init() {
#ifdef PRALINE
gpio_control::rf5072_mix_en.setActive(); // RF5072_MIX_EN
rf5072_mix_en.setActive(); // RF5072_MIX_EN
#endif
gpio_rffc5072_resetx.set();
#ifndef PRALINE
gpio_rffc5072_resetx.output();
#endif
rffc5072_resetx.setInactive();
reset();
_bus.init();
@@ -195,9 +197,9 @@ 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.
*/
gpio_rffc5072_resetx.clear();
rffc5072_resetx.setActive();
halPolledDelay(ticks_during_reset);
gpio_rffc5072_resetx.set();
rffc5072_resetx.setInactive();
halPolledDelay(ticks_after_reset);
}
+13 -19
View File
@@ -23,50 +23,44 @@
#include "utility.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "gpio.hpp"
using namespace gpio_control;
namespace rffc507x {
namespace spi {
void SPI::init() {
gpio_rffc5072_select.set();
gpio_rffc5072_clock.clear();
#ifndef PRALINE
gpio_rffc5072_select.output();
gpio_rffc5072_clock.output();
#endif
gpio_rffc5072_data.input();
gpio_rffc5072_data.clear();
rffc5072_select.setInactive();
rffc5072_clock.setInactive();
rffc5072_sdata.input();
rffc5072_sdata.setInactive();
}
inline void SPI::select(const bool active) {
gpio_rffc5072_select.write(!active);
rffc5072_select.setState(active);
}
inline void SPI::direction_out() {
gpio_rffc5072_data.output();
rffc5072_sdata.output();
}
inline void SPI::direction_in() {
gpio_rffc5072_data.input();
rffc5072_sdata.input();
}
inline void SPI::write_bit(const bit_t value) {
gpio_rffc5072_data.write(value);
rffc5072_sdata.write(value);
}
inline bit_t SPI::read_bit() {
return gpio_rffc5072_data.read() & 1;
return rffc5072_sdata.read() & 1;
}
inline bit_t SPI::transfer_bit(const bit_t bit_out) {
gpio_rffc5072_clock.clear();
rffc5072_clock.setInactive();
write_bit(bit_out);
const bit_t bit_in = read_bit();
gpio_rffc5072_clock.set();
rffc5072_clock.setActive();
return bit_in;
}
+1 -3
View File
@@ -57,13 +57,13 @@ using asahi_kasei::ak4951::AK4951;
#include "battery.hpp"
#include "gpio.hpp"
using namespace gpio_control;
extern "C" {
#include "platform_detect.h"
#ifdef PRALINE
#include "fpga_bridge.h"
#include "board.h"
#endif
}
@@ -74,11 +74,9 @@ const char* init_error = nullptr;
portapack::IO io{
portapack::gpio_dir,
portapack::gpio_lcd_rdx,
portapack::gpio_lcd_wrx,
portapack::gpio_io_stbx,
portapack::gpio_addr,
portapack::gpio_lcd_te,
portapack::gpio_dfu,
};
portapack::BacklightCAT4004 backlight_cat4004;
+61 -35
View File
@@ -76,8 +76,8 @@ static constexpr uint32_t ssp_scr(
/* MAX2831 uses 9-bit SPI transfers */
static constexpr SPIConfig ssp_config_max283x = {
.end_cb = NULL,
.ssport = gpio_max283x_select.port(),
.sspad = gpio_max283x_select.pad(),
.ssport = map_max283x_select.gpio_port,
.sspad = map_max283x_select.gpio_pad,
.cr0 =
CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max283x_spi_f) + 3) | CR0_FRFSPI | CR0_DSS9BIT,
.cpsr = ssp1_cpsr,
@@ -95,8 +95,8 @@ void set_rx_buff_vcm(const size_t v) {
/* MAX2837/MAX2839 use 16-bit SPI transfers */
static constexpr SPIConfig ssp_config_max283x = {
.end_cb = NULL,
.ssport = gpio_max283x_select.port(),
.sspad = gpio_max283x_select.pad(),
.ssport = map_max283x_select.gpio_port,
.sspad = map_max283x_select.gpio_pad,
.cr0 =
CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max283x_spi_f) + 3) | CR0_FRFSPI | CR0_DSS16BIT,
.cpsr = ssp1_cpsr,
@@ -144,6 +144,10 @@ static rf::Direction cached_direction = rf::Direction::Receive;
static bool cached_rf_amp = false;
static int_fast8_t cached_lna_gain = 0;
static int_fast8_t cached_vga_gain = 0;
/* FPGA quarter-rate shift mode currently programmed, in gateware encoding
* (0b00 none / 0b11 up / 0b01 down). The baseband filter width depends on it,
* so ReceiverModel reads it back through get_quarter_shift(). */
static uint8_t cached_quarter_shift = 0;
#endif
void init() {
@@ -182,18 +186,15 @@ void init() {
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);
/* Boot register state, matching fpga_init() in hackrf/firmware/common/fpga.c:
* DC block on, no PRBS, no external trigger, no quarter shift, TX NCO off.
* The decimation ratio and the quarter shift are programmed later by
* ClockManager::set_sampling_frequency() and set_tuning_frequency(). */
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM = no decimation
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00); // quarter shift off
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
cached_quarter_shift = 0;
ssp1_arbiter.invalidate();
chThdSleepMilliseconds(10); // Let FPGA registers settle
@@ -224,13 +225,13 @@ void set_direction(const rf::Direction new_direction) {
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);
/* RX Mode: DC block on, TX NCO off. The quarter shift is re-applied by
* set_tuning_frequency(); clear it here so a stale TX/RX transition
* cannot leave a rotation programmed with no matching LO offset. */
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00);
cached_quarter_shift = 0;
}
#endif
@@ -309,7 +310,19 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
final_frequency = final_frequency + portapack::persistent_memory::config_freq_rx_correction();
}
#ifdef PRALINE
/* The PRALINE tuning tables offset the analogue passband by a quarter of
* the ADC sample rate and have the FPGA rotate it back to DC, so the
* planner needs to know the AFE rate. See tuning.cpp. */
const uint32_t afe_rate = portapack::clock_manager.get_sampling_frequency()
<< portapack::clock_manager.get_resampling_n();
const auto tuning_config = tuning::config::create(
final_frequency,
afe_rate,
direction == rf::Direction::Transmit);
#else
const auto tuning_config = tuning::config::create(final_frequency);
#endif
if (tuning_config.is_valid()) {
first_if.disable();
@@ -339,6 +352,22 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
LPC_GPIO->CLR[0] = (1 << 13); // SGPIO12 = 0 (Q normal)
}
/* Program the FPGA's quarter-rate shift to match the offset the tuning
* table just applied to the analogue centre frequency. The gateware
* (hackrf/firmware/fpga/top/standard.py) takes both bits from the top
* of register 0x03 (rx_pstep):
* rx_pstep[6] -> quarter_shift.enable
* rx_pstep[7] -> quarter_shift.up
* which is exactly fpga_set_rx_quarter_shift_mode() in
* hackrf/firmware/common/fpga.c: write (mode & 0b11) << 6.
*
* These two settings MUST be programmed together. Tuning off-centre
* without the rotation puts the signal outside the decimation filter's
* passband and it disappears entirely; rotating without the offset
* moves the wanted signal off DC by the same amount. */
cached_quarter_shift = tuning_config.quarter_shift;
fpga_debug_register_write(FPGA_REG_RX_PSTEP, (cached_quarter_shift & 0b11) << 6);
ssp1_arbiter.invalidate();
#else
baseband_cpld.set_invert(mixer_invert ^ baseband_invert);
@@ -469,6 +498,10 @@ int_fast8_t get_cached_lna_gain() {
int_fast8_t get_cached_vga_gain() {
return cached_vga_gain;
}
uint8_t get_cached_quarter_shift() {
return cached_quarter_shift;
}
#endif
namespace first_if {
@@ -570,18 +603,11 @@ void register_write(const size_t register_number, uint32_t value) {
void init() {
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);
/* Same boot state as fpga_init() in hackrf/firmware/common/fpga.c. */
fpga_debug_register_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN);
fpga_debug_register_write(FPGA_REG_DECIM, 0x00); // RX_DECIM = no decimation
fpga_debug_register_write(FPGA_REG_RX_PSTEP, 0x00); // quarter shift off
fpga_debug_register_write(FPGA_REG_TX_CONTROL, 0x00);
ssp1_arbiter.invalidate(); // Force arbiter to reconfigure on next transfer
}
+4
View File
@@ -126,6 +126,10 @@ rf::Direction get_cached_direction();
bool get_cached_rf_amp();
int_fast8_t get_cached_lna_gain();
int_fast8_t get_cached_vga_gain();
/* FPGA RX quarter-rate shift currently programmed, in gateware encoding:
* 0b00 none, 0b11 up, 0b01 down. */
uint8_t get_cached_quarter_shift();
#endif
namespace sgpio {
+46 -31
View File
@@ -88,6 +88,15 @@ void ReceiverModel::set_target_frequency(rf::Frequency f) {
update_tuning_frequency();
}
void ReceiverModel::set_target_frequency_with_hidden_offset(
rf::Frequency f,
rf::Frequency offset) {
persistent_memory::set_target_frequency(f);
settings_.frequency_app_override = f;
hidden_offset = offset;
update_tuning_frequency();
}
uint32_t ReceiverModel::baseband_bandwidth() const {
return settings_.baseband_bandwidth;
}
@@ -244,10 +253,24 @@ void ReceiverModel::enable() {
radio::set_direction(rf::Direction::Receive);
#ifdef PRALINE
/* Anchor the Common Mode Voltage (VCM) to 1.2V.
* This stabilizes the electrical floor of the I/Q signals.
*/
radio::set_rx_buff_vcm(1);
/* MAX2831 RX IQ common-mode voltage (register 15).
*
* 0 = 1.1 V, 1 = 1.2 V, 2 = 1.3 V, 3 = 1.45 V.
*
* The reference firmware leaves this alone: max2831.c's default register
* table has reg 15 = 0x0145 (1.1 V) and the line that would raise it is
* commented out ("maximum rx output common-mode voltage"). Mayhem used to
* force 1.2 V here on the theory that it "stabilises the electrical floor
* of the I/Q signals" -- plausible, but never measured, and it was the
* last remaining RF-path setting where this branch disagreed with the
* configuration that is proven to receive ADS-B on this board.
*
* Set to 0 to match the reference (writing 1.1 V is a no-op against the
* power-on default), or back to 1 to restore the old Mayhem behaviour.
* If reception measurably worsens, put it back to 1 and say so -- neither
* value has been verified on hardware. */
#define PRALINE_RX_IQ_VCM 0
radio::set_rx_buff_vcm(PRALINE_RX_IQ_VCM);
#endif
update_tuning_frequency();
@@ -326,47 +349,39 @@ void ReceiverModel::update_baseband_bandwidth() {
if (enabled_) {
#ifdef PRALINE
/*
* PRALINE LPF bandwidth calculation from GSG hackrf_usb radio.c
* PRALINE LPF bandwidth, ported from auto_bandwidth() in
* hackrf/firmware/common/radio.c:
*
* The LPF should be set to capture the desired signal bandwidth
* while the FPGA decimation filter handles anti-aliasing.
* bb_bandwidth = sample_rate * 3 / 4
* lpf_bandwidth = bb_bandwidth + offset_hz * 2
*
* For most modes: LPF = (output_sample_rate * 3) / 8
* For quarter-shift: add offset for shifted spectrum
* where offset_hz is the quarter-rate shift, i.e. afe_rate / 4 when a
* shift is in use. The doubling is because the wanted signal sits
* offset from the analogue centre, so the analogue filter has to stay
* open out to that offset on the far side too.
*
* Note: MAX2831 minimum LPF is 11.6 MHz, so for narrow sample rates
* the hardware limit applies and FPGA filter does the real work.
* The previous version used /8 in both places and read the shift from
* bits 2-3 of FPGA register 1, which do not exist in the gateware, so
* it always took the no-shift branch. At the ADS-B rate that asked for
* 750 kHz, which is below the MAX2831's 1.75 MHz floor and therefore
* also switched in the external narrowband AA filter
* (MAX2831::set_lpf_rf_bandwidth_rx), squeezing the RX path shut. The
* reference asks for 17.5 MHz at the same rate and the AA filter stays
* out of circuit.
*/
uint32_t sample_rate = sampling_rate();
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
uint32_t afe_rate = sample_rate << resampling_n;
// Base LPF: enough to capture desired bandwidth
uint32_t lpf_bandwidth = (sample_rate * 3) / 8;
// Check if quarter-shift is enabled (FPGA register 1, bits 2-3)
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
uint32_t lpf_bandwidth = (sample_rate * 3) / 4;
const uint8_t quarter_shift = radio::debug::get_cached_quarter_shift();
if (quarter_shift != 0) {
// Quarter-shift moves spectrum by AFE_rate/4, need wider LPF
uint32_t offset = afe_rate / 8;
const uint32_t offset = afe_rate / 4;
lpf_bandwidth += offset * 2;
}
// For best anti-alias performance, also consider AFE Nyquist
// If our calculated LPF is below MAX2831 minimum, it doesn't matter
// But if we can set LPF to just below AFE Nyquist, that's optimal
uint32_t afe_nyquist = afe_rate / 2;
// Use the larger of: signal bandwidth requirement OR Nyquist protection
// (but MAX2831 driver will clamp to its available settings anyway)
if (lpf_bandwidth < afe_nyquist) {
// Set LPF close to Nyquist for maximum alias rejection
lpf_bandwidth = (afe_nyquist * 9) / 10; // 90% of Nyquist
}
radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth);
#else
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
+1
View File
@@ -66,6 +66,7 @@ class ReceiverModel {
/* The frequency to receive (no offset). */
rf::Frequency target_frequency() const;
void set_target_frequency(rf::Frequency f);
void set_target_frequency_with_hidden_offset(rf::Frequency f, rf::Frequency offset);
uint32_t baseband_bandwidth() const;
void set_baseband_bandwidth(uint32_t v);
+332 -99
View File
@@ -27,8 +27,8 @@ namespace tuning {
namespace config {
// Forward declarations
Config low_band(const rf::Frequency target_frequency);
Config mid_band(const rf::Frequency target_frequency);
Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit);
Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit);
Config high_band(const rf::Frequency target_frequency);
#ifdef PRALINE
@@ -36,133 +36,366 @@ Config high_band(const rf::Frequency target_frequency);
* PRALINE Tuning Configuration
* ============================
*
* Reference: hackrf_usb/common/tune_config.h praline_tune_config_rx[]
* These tables are copied verbatim from the reference firmware,
* hackrf/firmware/common/tune_config.h (praline_tune_config_rx /
* praline_tune_config_tx), and the selection and offset maths below reproduce
* hackrf/firmware/common/radio.c radio_update_frequency() /
* analog_from_digital_rf() / compute_offset().
*
* 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)
* Each entry gives, for target frequencies up to rf_range_end_mhz:
* if_mhz the IF the MAX2831 tunes to (0 = mixer bypassed, IF = RF)
* high_lo true -> LO = IF + analogue RF (mixer inverts the spectrum)
* false -> LO = IF - analogue RF (no inversion)
* shift the FPGA quarter-rate shift mode used for this entry
*
* Key insight: The IF frequency varies to keep the RFFC5072 VCO in a
* safe operating range (ideally 3500-5000 MHz, avoiding extremes).
* The quarter-rate shift is the part Mayhem was previously missing. RX entries
* deliberately place the analogue passband a quarter of the ADC rate away from
* the requested frequency (+8 MHz at the usual 32 Msps AFE rate) so that the
* wanted signal never sits on the DC offset / LO leakage, and then ask the FPGA
* to rotate it back down to DC. Both halves have to be programmed together:
* - tuning to target + offset without asking the FPGA to rotate leaves the
* signal 8 MHz out and the decimation filter deletes it;
* - tuning to target with no offset (what Mayhem did) parks the signal on DC,
* under the LO leakage and the gateware's adaptive DC block.
*
* 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
* "up" and "down" are named for the direction the FPGA rotates, so
* FPGA_QUARTER_SHIFT_MODE_UP means the analogue centre is placed ABOVE the
* requested frequency, and DOWN below it (radio.c analog_from_digital_rf()).
*/
// 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;
namespace {
// 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;
struct PralineTuneConfig {
uint16_t rf_range_end_mhz;
uint16_t if_mhz;
bool high_lo;
uint8_t shift; /* 0b00 none, 0b11 up, 0b01 down */
};
/* The tables below are kept column-aligned to match the reference source, so
* they are exempt from reformatting. */
// clang-format off
/* tuning table optimized for RX */
constexpr PralineTuneConfig praline_tune_config_rx[] = {
{ 0, 2360, true, 0b00},
{ 50, 2320, true, 0b11},
{ 100, 2320, true, 0b01},
{ 140, 2320, true, 0b11},
{ 406, 2560, true, 0b11},
{ 511, 2380, true, 0b11},
{ 578, 2560, true, 0b01},
{ 741, 2340, true, 0b11},
{ 861, 2560, true, 0b01},
{ 921, 2560, true, 0b11},
{ 1049, 2340, true, 0b01},
{ 1169, 2380, true, 0b11},
{ 1360, 2340, true, 0b11},
{ 1544, 2560, true, 0b01},
{ 1675, 2560, true, 0b11},
{ 1992, 2380, true, 0b01},
{ 2070, 2340, true, 0b01},
{ 2150, 2360, true, 0b01},
{ 2168, 2560, false, 0b11},
{ 2185, 2580, false, 0b11},
{ 2202, 2580, false, 0b01},
{ 2205, 2520, false, 0b11},
{ 2216, 2560, false, 0b11},
{ 2223, 2540, false, 0b11},
{ 2234, 2580, false, 0b11},
{ 2240, 2560, false, 0b11},
{ 2251, 2580, false, 0b01},
{ 2258, 2580, false, 0b11},
{ 2265, 2540, false, 0b01},
{ 2271, 2580, false, 0b11},
{ 2273, 2560, false, 0b11},
{ 2275, 2580, false, 0b01},
{ 2280, 2500, false, 0b01},
{ 2284, 2540, false, 0b11},
{ 2289, 2580, false, 0b01},
{ 2293, 2540, false, 0b01},
{ 2298, 2520, false, 0b01},
{ 2300, 2580, false, 0b11},
{ 2302, 2540, false, 0b01},
{ 2309, 2560, false, 0b01},
{ 2311, 2580, false, 0b01},
{ 2314, 2540, false, 0b11},
{ 2315, 2540, false, 0b01},
{ 2320, 2580, false, 0b11},
{ 2380, 0, false, 0b11},
{ 2440, 0, false, 0b01},
{ 2500, 0, false, 0b11},
{ 2580, 0, false, 0b01},
{ 2583, 2360, false, 0b11},
{ 2584, 2380, false, 0b11},
{ 2587, 2340, false, 0b11},
{ 2593, 2340, false, 0b01},
{ 2607, 2340, false, 0b11},
{ 2609, 2360, false, 0b11},
{ 2615, 2360, false, 0b01},
{ 2627, 2340, false, 0b01},
{ 2629, 2360, false, 0b01},
{ 2631, 2380, false, 0b11},
{ 2644, 2340, false, 0b11},
{ 2649, 2380, false, 0b11},
{ 2651, 2380, false, 0b01},
{ 2654, 2500, false, 0b11},
{ 2665, 2360, false, 0b11},
{ 2669, 2380, false, 0b01},
{ 2672, 2360, false, 0b01},
{ 2682, 2340, false, 0b11},
{ 2687, 2380, false, 0b11},
{ 2692, 2340, false, 0b11},
{ 2695, 2500, false, 0b11},
{ 2705, 2360, false, 0b11},
{ 2707, 2380, false, 0b01},
{ 2712, 2340, false, 0b01},
{ 2717, 2520, false, 0b11},
{ 2728, 2380, false, 0b11},
{ 2730, 2560, false, 0b11},
{ 2734, 2500, false, 0b11},
{ 2758, 2340, false, 0b11},
{ 2780, 2360, false, 0b11},
{ 2787, 2520, false, 0b11},
{ 2802, 2380, false, 0b11},
{ 2809, 2540, false, 0b11},
{ 2822, 2380, false, 0b01},
{ 2831, 2560, false, 0b11},
{ 2854, 2340, false, 0b11},
{ 2875, 2360, false, 0b11},
{ 2898, 2380, false, 0b11},
{ 2918, 2380, false, 0b01},
{ 2936, 2520, false, 0b01},
{ 2944, 2380, false, 0b01},
{ 2959, 2560, false, 0b11},
{ 2976, 2340, false, 0b11},
{ 2985, 2500, false, 0b01},
{ 3003, 2340, false, 0b11},
{ 3009, 2540, false, 0b11},
{ 3027, 2380, false, 0b11},
{ 3034, 2560, false, 0b11},
{ 3050, 2380, false, 0b01},
{ 3069, 2500, false, 0b11},
{ 3094, 2520, false, 0b11},
{ 3119, 2540, false, 0b11},
{ 3144, 2560, false, 0b11},
{ 3169, 2560, false, 0b01},
{ 3180, 2500, false, 0b11},
{ 3204, 2340, false, 0b11},
{ 3232, 2360, false, 0b11},
{ 3292, 2340, false, 0b01},
{ 3340, 2380, false, 0b01},
{ 3369, 2340, false, 0b11},
{ 3399, 2360, false, 0b11},
{ 3429, 2380, false, 0b11},
{ 3464, 2500, false, 0b11},
{ 3489, 2520, false, 0b11},
{ 3512, 2540, false, 0b11},
{ 3551, 2500, false, 0b01},
{ 3582, 2540, false, 0b11},
{ 3611, 2560, false, 0b11},
{ 3639, 2520, false, 0b11},
{ 3729, 2340, false, 0b11},
{ 3817, 2380, false, 0b01},
{ 3942, 2360, false, 0b01},
{ 4049, 2540, false, 0b11},
{ 4134, 2500, false, 0b01},
{ 4194, 2560, false, 0b11},
{ 4353, 2520, false, 0b11},
{ 4449, 2360, false, 0b01},
{ 4562, 2500, false, 0b11},
{ 4672, 2560, false, 0b11},
{ 4769, 2540, false, 0b11},
{ 4849, 2560, false, 0b01},
{ 4889, 2560, false, 0b11},
{ 4929, 2560, false, 0b11},
{ 4969, 2560, false, 0b11},
{ 5009, 2560, false, 0b11},
{ 5049, 2560, false, 0b11},
{ 5092, 2360, false, 0b11},
{ 5209, 2340, false, 0b01},
{ 5298, 2380, false, 0b01},
{ 5468, 2340, false, 0b01},
{ 5582, 2520, false, 0b11},
{ 5702, 2340, false, 0b11},
{ 5888, 2520, false, 0b01},
{ 6092, 2340, false, 0b01},
{ 6240, 2560, false, 0b11},
{ 6609, 2340, false, 0b11},
{ 6752, 2380, false, 0b01},
{ 6930, 2520, false, 0b01},
{ 7000, 2560, false, 0b11},
{ 7070, 2560, false, 0b01},
{ 7251, 2580, false, 0b01},
{ 0, 0, false, 0b00},
};
/* tuning table optimized for TX */
constexpr PralineTuneConfig praline_tune_config_tx[] = {
{ 2100, 2375, true, 0b00},
{ 2105, 2375, false, 0b00},
{ 2115, 2425, false, 0b00},
{ 2130, 2375, false, 0b00},
{ 2150, 2425, false, 0b00},
{ 2160, 2475, false, 0b00},
{ 2175, 2425, false, 0b00},
{ 2190, 2475, false, 0b00},
{ 2195, 2425, false, 0b00},
{ 2210, 2375, false, 0b00},
{ 2248, 2425, false, 0b00},
{ 2265, 2525, false, 0b00},
{ 2300, 2425, false, 0b00},
{ 2320, 2525, false, 0b00},
{ 2580, 0, false, 0b00},
{ 3000, 2325, false, 0b00},
{ 3140, 2375, false, 0b00},
{ 3200, 2425, false, 0b00},
{ 3280, 2375, false, 0b00},
{ 3340, 2425, false, 0b00},
{ 3420, 2475, false, 0b00},
{ 3480, 2525, false, 0b00},
{ 3500, 2475, false, 0b00},
{ 3595, 2425, false, 0b00},
{ 3625, 2375, false, 0b00},
{ 3670, 2475, false, 0b00},
{ 3710, 2425, false, 0b00},
{ 3760, 2525, false, 0b00},
{ 3790, 2475, false, 0b00},
{ 3860, 2425, false, 0b00},
{ 3915, 2375, false, 0b00},
{ 4000, 2425, false, 0b00},
{ 4055, 2375, false, 0b00},
{ 4125, 2425, false, 0b00},
{ 4700, 2375, false, 0b00},
{ 4800, 2425, false, 0b00},
{ 5000, 2375, false, 0b00},
{ 5260, 2475, false, 0b00},
{ 5465, 2525, false, 0b00},
{ 5560, 2375, false, 0b00},
{ 5720, 2425, false, 0b00},
{ 5860, 2475, false, 0b00},
{ 5970, 2575, false, 0b00},
{ 6000, 2375, false, 0b00},
{ 6500, 2325, false, 0b00},
{ 6750, 2375, false, 0b00},
{ 6850, 2425, false, 0b00},
{ 6950, 2475, false, 0b00},
{ 7000, 2525, false, 0b00},
{ 7251, 2575, false, 0b00},
{ 0, 0, false, 0b00},
};
// clang-format on
/* radio.c select_tune_config(): first entry whose range end is above the
* requested frequency. The list is terminated by an all-zero entry, which is
* also what a frequency past the end of the table lands on. */
const PralineTuneConfig* select_tune_config(const rf::Frequency target_frequency, const bool transmit) {
const PralineTuneConfig* entry = transmit ? praline_tune_config_tx : praline_tune_config_rx;
const uint32_t freq_mhz = static_cast<uint32_t>(target_frequency / 1'000'000);
while ((entry->rf_range_end_mhz != 0) || (entry->if_mhz != 0)) {
if ((target_frequency == 0) || (entry->rf_range_end_mhz > freq_mhz))
break;
entry++;
}
return entry;
}
// 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;
}
/* radio.c compute_offset(): a quarter of the AFE (ADC) sample rate, or zero if
* no shift is in use or the AFE rate isn't known yet. */
constexpr uint32_t quarter_shift_offset(const uint8_t shift, const uint32_t afe_rate) {
return (shift == 0) ? 0 : (afe_rate / 4);
}
/* radio.c analog_from_digital_rf(). */
rf::Frequency analog_from_digital_rf(const rf::Frequency target_frequency, const uint8_t shift, const uint32_t afe_rate) {
const rf::Frequency offset = quarter_shift_offset(shift, afe_rate);
if (shift == 0b11)
return target_frequency + offset;
if (shift == 0b01)
return (offset > target_frequency) ? (offset - target_frequency)
: (target_frequency - offset);
return target_frequency;
}
} // namespace
#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) {
Config low_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
#ifdef PRALINE
// Use the tune_config lookup for PRALINE
return praline_get_if_frequency(target_frequency);
#else
return 2650'000'000 - (target_frequency / 7);
#endif
}
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
Config low_band(const rf::Frequency target_frequency) {
const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency);
/* Past the end of the table: no usable configuration. */
if ((entry->rf_range_end_mhz == 0) && (entry->if_mhz == 0))
return {};
#ifdef PRALINE
/* afe_rate == 0 means the caller doesn't know the ADC rate, so no LO offset
* is applied. The FPGA rotation has to be dropped with it: rotating without
* the matching offset moves the wanted signal off DC by afe_rate / 4. */
const uint8_t shift = (afe_rate == 0) ? 0 : entry->shift;
const rf::Frequency analog_rf = analog_from_digital_rf(target_frequency, shift, afe_rate);
/* if_mhz == 0 means the mixer is bypassed and the transceiver tunes the RF
* directly; there is no first LO in that case. */
const rf::Frequency second_lo_frequency =
(entry->if_mhz == 0) ? analog_rf : (static_cast<rf::Frequency>(entry->if_mhz) * 1'000'000);
if (entry->if_mhz == 0)
return {0, second_lo_frequency, rf::path::Band::Low, false, shift};
/* The low band always runs through the low-pass image-reject filter, so
* the spectrum is inverted exactly when the first LO ends up above the IF,
* i.e. for high-side injection. This is hackrf_usb.c radio_changed():
* invert = (img_reject == RF_PATH_FILTER_LOW_PASS) && (freq_lo > freq_if)
*/
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;
if (entry->high_lo) {
first_lo_frequency = second_lo_frequency + analog_rf;
mixer_invert = true;
} else {
// Low-side injection: LO = IF - RF
first_lo_frequency = second_lo_frequency - target_frequency;
first_lo_frequency = second_lo_frequency - analog_rf;
mixer_invert = false;
}
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert};
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert, shift};
#else
(void)afe_rate;
(void)transmit;
const rf::Frequency second_lo_frequency = 2650'000'000 - (target_frequency / 7);
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 (HackRF One) or 2320-2580 MHz (PRALINE):
Config mid_band(const rf::Frequency target_frequency) {
// Mid band 2170-2740 Mhz (HackRF One) or 2320-2740 MHz (PRALINE):
Config mid_band(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
#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 - need mixer
return low_band(target_frequency);
}
// Frequencies 2300-2600 MHz can go direct (no RFFC5072)
else if (target_frequency <= 2600'000'000) {
const rf::Frequency second_lo_frequency = target_frequency;
const rf::Frequency first_lo_frequency = 0;
const bool mixer_invert = false;
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Mid, mixer_invert};
}
// Frequencies 2600-2740 MHz need downconversion since they're above MAX2831 maximum
else {
// Treat as high band
return high_band(target_frequency);
/* radio.c select_img_reject() / tuning.c: on PRALINE the MAX2831 tunes
* direct (mixer bypassed) from 2320 to 2580 MHz. band_mid starts at
* TRANSITION = 2320 MHz, so everything below that already went to
* low_band(). */
if (target_frequency <= 2580'000'000) {
const PralineTuneConfig* entry = select_tune_config(target_frequency, transmit);
const uint8_t shift = (afe_rate == 0) ? 0 : entry->shift;
const rf::Frequency analog_rf = analog_from_digital_rf(target_frequency, shift, afe_rate);
/* Mixer bypassed: no first LO, the MAX2831 tunes the (offset)
* analogue RF directly and the FPGA rotates it back. */
return {0, analog_rf, rf::path::Band::Mid, false, shift};
}
/* 2580-2740 MHz: above the bypass window, downconvert. */
return high_band(target_frequency);
#else
(void)afe_rate;
(void)transmit;
const rf::Frequency second_lo_frequency = target_frequency;
const rf::Frequency first_lo_frequency = 0;
const bool mixer_invert = false;
@@ -203,12 +436,12 @@ Config high_band(const rf::Frequency target_frequency) {
return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert};
}
Config create(const rf::Frequency target_frequency) {
Config create(const rf::Frequency target_frequency, const uint32_t afe_rate, const bool transmit) {
/* TODO: This is some lame code. */
if (rf::path::band_low.contains(target_frequency)) {
return low_band(target_frequency);
return low_band(target_frequency, afe_rate, transmit);
} else if (rf::path::band_mid.contains(target_frequency)) {
return mid_band(target_frequency);
return mid_band(target_frequency, afe_rate, transmit);
} else if (rf::path::band_high.contains(target_frequency)) {
return high_band(target_frequency);
} else {
+21 -4
View File
@@ -33,18 +33,21 @@ struct Config {
: first_lo_frequency(0),
second_lo_frequency(0),
rf_path_band(rf::path::Band::Mid),
mixer_invert(false) {
mixer_invert(false),
quarter_shift(0) {
}
constexpr Config(
rf::Frequency first_lo_frequency,
rf::Frequency second_lo_frequency,
rf::path::Band rf_path_band,
bool mixer_invert)
bool mixer_invert,
uint8_t quarter_shift = 0)
: first_lo_frequency(first_lo_frequency),
second_lo_frequency(second_lo_frequency),
rf_path_band(rf_path_band),
mixer_invert(mixer_invert) {
mixer_invert(mixer_invert),
quarter_shift(quarter_shift) {
}
bool is_valid() const {
@@ -55,9 +58,23 @@ struct Config {
const rf::Frequency second_lo_frequency;
const rf::path::Band rf_path_band;
const bool mixer_invert;
/* PRALINE only: FPGA RX quarter-rate shift mode, in the encoding the
* gateware expects in the top two bits of register 0x03 (rx_pstep):
* 0b00 = none, 0b11 = up, 0b01 = down.
* Matches fpga_quarter_shift_mode_t in hackrf/firmware/common/fpga.h.
* Always 0 on HackRF One (no FPGA). */
const uint8_t quarter_shift;
};
Config create(const rf::Frequency target_frequency);
/* afe_rate is the ADC sample rate in Hz (output rate << decimation), needed on
* PRALINE to work out how far off centre the quarter-rate shift places the
* analogue passband. Pass 0 (or leave defaulted) to disable the shift.
* transmit selects the TX tuning table. Both are ignored on HackRF One. */
Config create(
const rf::Frequency target_frequency,
const uint32_t afe_rate = 0,
const bool transmit = false);
} /* namespace config */
} /* namespace tuning */
+9 -3
View File
@@ -39,6 +39,8 @@ class BoundFrequencyField : public FrequencyField {
public:
decltype(FrequencyField::on_change) updated{};
std::function<bool(rf::Frequency)> changing{};
std::function<void(rf::Frequency)> entered{};
BoundFrequencyField(Point parent_pos, NavigationView& nav)
: FrequencyField(parent_pos) {
@@ -47,7 +49,8 @@ class BoundFrequencyField : public FrequencyField {
set_value(model->target_frequency());
on_change = [this](rf::Frequency f) {
model->set_target_frequency(f);
if (!changing || !changing(f))
model->set_target_frequency(f);
if (updated)
updated(f);
};
@@ -57,7 +60,10 @@ class BoundFrequencyField : public FrequencyField {
on_edit_shown();
auto freq_view = nav.push<FrequencyKeypadView>(model->target_frequency());
freq_view->on_changed = [this](rf::Frequency f) {
set_value(f);
if (entered)
entered(f);
else
set_value(f);
};
nav.set_on_pop([this]() {
if (on_edit_hidden)
@@ -76,4 +82,4 @@ using TxFrequencyField = BoundFrequencyField<TransmitterModel, &portapack::trans
} // namespace ui
#endif // __UI_FREQ_FIELD_H__
#endif // __UI_FREQ_FIELD_H__
+44 -18
View File
@@ -46,10 +46,12 @@ GeoPos::GeoPos(
&field_speed,
&text_alt_unit,
&text_speed_unit,
&field_lat_hemisphere,
&field_lat_degrees,
&field_lat_minutes,
&field_lat_seconds,
&text_lat_decimal,
&field_lon_hemisphere,
&field_lon_degrees,
&field_lon_minutes,
&field_lon_seconds,
@@ -73,8 +75,14 @@ GeoPos::GeoPos(
on_change(altitude(), lat_value, lon_value, speed());
};
const auto changed_hemisphere_fn = [changed_fn](size_t, OptionsField::value_t) {
changed_fn(0);
};
field_altitude.on_change = changed_fn;
field_speed.on_change = changed_fn;
field_lat_hemisphere.on_change = changed_hemisphere_fn;
field_lon_hemisphere.on_change = changed_hemisphere_fn;
field_lat_degrees.on_change = changed_fn;
field_lat_minutes.on_change = changed_fn;
field_lat_seconds.on_change = changed_fn;
@@ -83,19 +91,37 @@ GeoPos::GeoPos(
field_lon_seconds.on_change = changed_fn;
const auto wrapped_lat_seconds = [this](int32_t v) {
const auto old_minutes = field_lat_minutes.value();
field_lat_minutes.on_encoder(v);
if (field_lat_minutes.value() == old_minutes) {
field_lat_seconds.set_value((v > 0) ? 59 : 0);
}
};
// Degrees now holds a magnitude, so a minutes wrap always carries in the
// same direction regardless of hemisphere.
const auto wrapped_lat_minutes = [this](int32_t v) {
field_lat_degrees.on_encoder((field_lat_degrees.value() >= 0) ? v : -v);
const auto old_degrees = field_lat_degrees.value();
field_lat_degrees.on_encoder(v);
if (field_lat_degrees.value() == old_degrees) {
field_lat_minutes.set_value((v > 0) ? 59 : 0);
}
};
const auto wrapped_lon_seconds = [this](int32_t v) {
const auto old_minutes = field_lon_minutes.value();
field_lon_minutes.on_encoder(v);
if (field_lon_minutes.value() == old_minutes) {
field_lon_seconds.set_value((v > 0) ? 59 : 0);
}
};
const auto wrapped_lon_minutes = [this](int32_t v) {
field_lon_degrees.on_encoder((field_lon_degrees.value() >= 0) ? v : -v);
const auto old_degrees = field_lon_degrees.value();
field_lon_degrees.on_encoder(v);
if (field_lon_degrees.value() == old_degrees) {
field_lon_minutes.set_value((v > 0) ? 59 : 0);
}
};
field_lat_seconds.on_wrap = wrapped_lat_seconds;
@@ -146,31 +172,31 @@ void GeoPos::set_speed(int32_t speed) {
}
void GeoPos::set_lat(float lat) {
field_lat_degrees.set_value(lat);
field_lat_minutes.set_value((uint32_t)abs(lat / (1.0 / 60)) % 60);
field_lat_seconds.set_value((uint32_t)abs(lat / (1.0 / 3600)) % 60);
bool south = lat < 0;
float magnitude = south ? -lat : lat;
field_lat_hemisphere.set_by_value(south ? 1 : 0);
field_lat_degrees.set_value((int32_t)magnitude);
field_lat_minutes.set_value((uint32_t)(magnitude * 60) % 60);
field_lat_seconds.set_value((uint32_t)(magnitude * 3600) % 60);
}
void GeoPos::set_lon(float lon) {
field_lon_degrees.set_value(lon);
field_lon_minutes.set_value((uint32_t)abs(lon / (1.0 / 60)) % 60);
field_lon_seconds.set_value((uint32_t)abs(lon / (1.0 / 3600)) % 60);
bool west = lon < 0;
float magnitude = west ? -lon : lon;
field_lon_hemisphere.set_by_value(west ? 1 : 0);
field_lon_degrees.set_value((int32_t)magnitude);
field_lon_minutes.set_value((uint32_t)(magnitude * 60) % 60);
field_lon_seconds.set_value((uint32_t)(magnitude * 3600) % 60);
}
float GeoPos::lat() {
if (field_lat_degrees.value() < 0) {
return -1 * (-1 * field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0));
} else {
return field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0);
}
float magnitude = field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0);
return (field_lat_hemisphere.selected_index_value() != 0) ? -magnitude : magnitude;
};
float GeoPos::lon() {
if (field_lon_degrees.value() < 0) {
return -1 * (-1 * field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0));
} else {
return field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0);
}
float magnitude = field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0);
return (field_lon_hemisphere.selected_index_value() != 0) ? -magnitude : magnitude;
};
int32_t GeoPos::altitude() {
+23 -8
View File
@@ -239,12 +239,21 @@ class GeoPos : public View {
{25 * 8, UI_POS_Y(0), 5 * 8, 16},
""};
NumberField field_lat_degrees{
// Sign is held by the hemisphere field, not by the degrees field: an
// int32_t degrees field has no negative zero, so a coordinate in
// (-1, 0) could not otherwise be represented (see issue #3234).
OptionsField field_lat_hemisphere{
{5 * 8, 1 * 16},
4,
{-90, 90},
1,
' '};
{{"N", 0},
{"S", 1}}};
NumberField field_lat_degrees{
{6 * 8, 1 * 16},
3,
{0, 90},
1,
' ',
false};
NumberField field_lat_minutes{
{10 * 8, 1 * 16},
2,
@@ -263,12 +272,18 @@ class GeoPos : public View {
{17 * 8, 1 * 16, 13 * 8, 1 * 16},
""};
NumberField field_lon_degrees{
OptionsField field_lon_hemisphere{
{5 * 8, 2 * 16},
4,
{-180, 180},
1,
' '};
{{"E", 0},
{"W", 1}}};
NumberField field_lon_degrees{
{6 * 8, 2 * 16},
3,
{0, 180},
1,
' ',
false};
NumberField field_lon_minutes{
{10 * 8, 2 * 16},
2,
+4 -2
View File
@@ -344,7 +344,9 @@ bool MenuView::on_key(const KeyEvent key) {
}
[[fallthrough]];
case KeyEvent::Select:
if (menu_items[highlighted_item].on_select) {
// NB: a MenuView can legitimately be empty (e.g. after clear()) while
// still holding focus; indexing menu_items unchecked faults there.
if (highlighted_item < menu_items.size() && menu_items[highlighted_item].on_select) {
menu_items[highlighted_item].on_select(key);
}
return true;
@@ -364,7 +366,7 @@ bool MenuView::on_keyboard(const KeyboardEvent key) {
if (key == '-') return set_highlighted(highlighted_item - 1);
if (key == '+') return set_highlighted(highlighted_item + 1);
if (key == 10) {
if (menu_items[highlighted_item].on_select) {
if (highlighted_item < menu_items.size() && menu_items[highlighted_item].on_select) {
menu_items[highlighted_item].on_select(KeyEvent::Right);
}
return true;
+105 -12
View File
@@ -88,16 +88,29 @@ void FrequencyScale::set_spectrum_sampling_rate(const int new_sampling_rate) {
}
void FrequencyScale::set_channel_filter(
const int offset,
const int low_frequency,
const int high_frequency,
const int transition) {
if ((channel_filter_low_frequency != low_frequency) ||
const bool shape_changed =
(channel_filter_low_frequency != low_frequency) ||
(channel_filter_high_frequency != high_frequency) ||
(channel_filter_transition != transition)) {
(channel_filter_transition != transition);
const bool offset_changed = channel_filter_offset != offset;
if (shape_changed) {
channel_filter_offset = offset;
channel_filter_low_frequency = low_frequency;
channel_filter_high_frequency = high_frequency;
channel_filter_transition = transition;
set_dirty();
} else if (offset_changed) {
const auto old_offset = channel_filter_offset;
channel_filter_offset = offset;
if (live_tuning && spectrum_sampling_rate && drawn())
redraw_filter_cursor(old_offset);
else
set_dirty();
}
}
@@ -123,12 +136,14 @@ void FrequencyScale::paint(Painter& painter) {
draw_filter_ranges(painter, r);
draw_frequency_ticks(painter, r);
const Rect r_cursor{
(screen_width / 2 - 2) + cursor_position, r.bottom() - filter_band_height,
5, filter_band_height};
painter.fill_rectangle(
r_cursor,
Color::red());
if (!live_tuning) {
const Rect r_cursor{
(screen_width / 2 - 2) + cursor_position, r.bottom() - filter_band_height,
5, filter_band_height};
painter.fill_rectangle(
r_cursor,
Color::red());
}
}
void FrequencyScale::clear() {
@@ -184,9 +199,79 @@ void FrequencyScale::draw_frequency_ticks(Painter& painter, const Rect r) {
}
}
void FrequencyScale::redraw_filter_cursor(const int old_offset) {
const auto r = screen_rect();
const auto x_center = r.width() / 2;
const auto trans =
channel_filter_transition * spectrum_bins / spectrum_sampling_rate;
const auto cursor_left = [&](const int offset) {
return r.left() + x_center +
(offset + channel_filter_low_frequency) *
spectrum_bins / spectrum_sampling_rate -
trans;
};
const auto cursor_right = [&](const int offset) {
return r.left() + x_center +
(offset + channel_filter_high_frequency) *
spectrum_bins / spectrum_sampling_rate +
trans;
};
const auto dirty_left =
std::min(cursor_left(old_offset), cursor_left(channel_filter_offset)) - 1;
const auto dirty_right =
std::max(cursor_right(old_offset), cursor_right(channel_filter_offset)) + 1;
const Rect dirty{
dirty_left,
r.bottom() - filter_band_height,
dirty_right - dirty_left,
filter_band_height};
Painter painter;
painter.fill_rectangle(dirty, Theme::getInstance()->bg_darkest->background);
draw_filter_ranges(painter, r);
restore_tick_lines(painter, r, dirty);
}
void FrequencyScale::restore_tick_lines(
Painter& painter,
const Rect r,
const Rect dirty) {
const auto draw_if_dirty = [&](const Coord x) {
if (x >= dirty.left() && x < dirty.right()) {
painter.fill_rectangle(
{x, dirty.top(), 1, dirty.height()},
Theme::getInstance()->bg_darkest->foreground);
}
};
const auto x_center = r.left() + r.width() / 2;
draw_if_dirty(x_center);
constexpr int tick_count_max = 4;
float rough_tick_interval = float(spectrum_sampling_rate) / tick_count_max;
int magnitude = 1;
while (rough_tick_interval >= 10.0f) {
rough_tick_interval /= 10;
magnitude *= 10;
}
const int tick_interval = std::ceil(rough_tick_interval);
auto tick_offset = tick_interval;
while ((tick_offset * magnitude) < spectrum_sampling_rate / 2) {
const Dim pixel_offset =
tick_offset * magnitude * spectrum_bins / spectrum_sampling_rate;
draw_if_dirty(x_center - pixel_offset);
draw_if_dirty(x_center + pixel_offset);
tick_offset += tick_interval;
}
}
void FrequencyScale::draw_filter_ranges(Painter& painter, const Rect r) {
if (channel_filter_low_frequency != channel_filter_high_frequency) {
const auto x_center = r.width() / 2;
const auto x_center = r.width() / 2 +
channel_filter_offset * spectrum_bins / spectrum_sampling_rate;
const auto x_low = x_center + channel_filter_low_frequency * spectrum_bins / spectrum_sampling_rate;
const auto x_high = x_center + channel_filter_high_frequency * spectrum_bins / spectrum_sampling_rate;
@@ -220,6 +305,11 @@ void FrequencyScale::on_blur() {
}
bool FrequencyScale::on_encoder(const EncoderEvent delta) {
if (live_tuning) {
if (on_select) on_select(delta);
return true;
}
cursor_position += delta;
cursor_position = std::min<int32_t>(cursor_position, screen_width / 2 - 1);
@@ -326,9 +416,11 @@ WaterfallView::WaterfallView(const bool cursor) {
frequency_scale.focus(); // focus on frequency scale to show cursor
if (sampling_rate) {
// screen x to frequency scale x, NB we need two widgets align
int32_t cursor_position = x - (screen_width / 2);
frequency_scale.set_cursor_position(cursor_position);
const int32_t cursor_position = x - (screen_width / 2);
if (!frequency_scale.is_live_tuning()) {
// screen x to frequency scale x, NB we need two widgets align
frequency_scale.set_cursor_position(cursor_position);
}
}
};
@@ -405,6 +497,7 @@ void WaterfallView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
sampling_rate = spectrum.sampling_rate;
frequency_scale.set_spectrum_sampling_rate(sampling_rate);
frequency_scale.set_channel_filter(
spectrum.channel_filter_offset,
spectrum.channel_filter_low_frequency,
spectrum.channel_filter_high_frequency,
spectrum.channel_filter_transition);
+10 -1
View File
@@ -72,6 +72,8 @@ class AudioSpectrumView : public View {
class FrequencyScale : public Widget {
public:
/* Receives a frequency offset in Hz for key/touch selection. With live
* tuning enabled, encoder events instead pass the raw encoder delta. */
std::function<void(int32_t offset)> on_select{};
void on_show() override;
@@ -83,8 +85,10 @@ class FrequencyScale : public Widget {
bool on_touch(const TouchEvent touch) override;
void set_spectrum_sampling_rate(const int new_sampling_rate);
void set_channel_filter(const int low_frequency, const int high_frequency, const int transition);
void set_channel_filter(const int offset, const int low_frequency, const int high_frequency, const int transition);
void set_cursor_position(const int32_t position);
void set_live_tuning(const bool enabled) { live_tuning = enabled; }
bool is_live_tuning() const { return live_tuning; }
void paint(Painter& painter) override;
@@ -94,15 +98,19 @@ class FrequencyScale : public Widget {
int32_t cursor_position{0};
int spectrum_sampling_rate{0};
const int spectrum_bins = std::tuple_size<decltype(ChannelSpectrum::db)>::value;
int channel_filter_offset{0};
int channel_filter_low_frequency{0};
int channel_filter_high_frequency{0};
int channel_filter_transition{0};
bool live_tuning{false};
void clear();
void clear_background(Painter& painter, const Rect r);
void draw_frequency_ticks(Painter& painter, const Rect r);
void draw_filter_ranges(Painter& painter, const Rect r);
void redraw_filter_cursor(const int old_offset);
void restore_tick_lines(Painter& painter, const Rect r, const Rect dirty);
};
/* NB: These visualizations rely on having a baseband image running.
@@ -147,6 +155,7 @@ class WaterfallView : public View {
void set_parent_rect(const Rect new_parent_rect) override;
void show_audio_spectrum_view(const bool show);
void load_gradient();
void set_live_tuning(const bool enabled) { frequency_scale.set_live_tuning(enabled); }
private:
void update_widgets_rect();
-8
View File
@@ -30,9 +30,6 @@
#include "portapack.hpp"
#include "ui_about_simple.hpp"
#include "ui_adsb_rx.hpp"
#include "ui_aprs_rx.hpp"
#include "ui_aprs_tx.hpp"
#include "ui_btle_rx.hpp"
#include "ui_debug.hpp"
#include "ui_encoders.hpp"
@@ -60,7 +57,6 @@
#include "ui_battinfo.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "ais_app.hpp"
#include "analog_audio_app.hpp"
#include "ble_rx_app.hpp"
#include "ble_tx_app.hpp"
@@ -99,9 +95,6 @@ const NavigationView::AppList NavigationView::appList = {
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
/* RX ********************************************************************/
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
@@ -110,7 +103,6 @@ const NavigationView::AppList NavigationView::appList = {
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
/* TX ********************************************************************/
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
+22 -11
View File
@@ -124,6 +124,7 @@ set(CPPSRC
dsp_goertzel.cpp
matched_filter.cpp
spectrum_collector.cpp
filtered_spectrum_collector.cpp
tv_collector.cpp
stream_input.cpp
stream_output.cpp
@@ -747,13 +748,6 @@ set(MODE_CPPSRC
)
DeclareTargets(PRTT rtty_tx)
### SD over USB
set(MODE_INCDIR
${HACKRF_PATH}/firmware
${HACKRF_PATH}/firmware/common
${HACKRF_PATH}/firmware/libopencm3/include
)
### Morse TX
@@ -793,6 +787,15 @@ set(MODE_CPPSRC
)
DeclareTargets(PTET tetra_rx)
### SD over USB
set(MODE_INCDIR
${HACKRF_PATH}/firmware
${HACKRF_PATH}/firmware/common
${HACKRF_PATH}/firmware/libopencm3/include
${HACKRF_PATH}/firmware/hackrf_usb
sd_over_usb/
)
set(MODE_CPPSRC
sd_over_usb/proc_sd_over_usb.cpp
@@ -801,7 +804,9 @@ set(MODE_CPPSRC
sd_over_usb/diskio.c
sd_over_usb/sd_over_usb.c
sd_over_usb/usb_descriptor.c
sd_over_usb/hackrf_core.c
sd_over_usb/usb_api_transceiver.c
sd_over_usb/cpu_clock.c
sd_over_usb/pins.c
${HACKRF_PATH}/firmware/common/adc.c
${HACKRF_PATH}/firmware/common/selftest.c
@@ -828,6 +833,15 @@ set(MODE_CPPSRC
${HACKRF_PATH}/firmware/common/rffc5071_spi.c
${HACKRF_PATH}/firmware/common/rffc5071.c
${HACKRF_PATH}/firmware/common/gpdma.c
${HACKRF_PATH}/firmware/common/clock_gen.c
${HACKRF_PATH}/firmware/common/cpld_jtag.c
${HACKRF_PATH}/firmware/common/leds.c
${HACKRF_PATH}/firmware/common/power.c
${HACKRF_PATH}/firmware/common/clock_io.c
${HACKRF_PATH}/firmware/common/rom_iap.c
${HACKRF_PATH}/firmware/common/w25q80bv.c
${HACKRF_PATH}/firmware/common/w25q80bv_target.c
${HACKRF_PATH}/firmware/libopencm3/lib/cm3/nvic.c
${HACKRF_PATH}/firmware/libopencm3/lib/cm3/sync.c
@@ -835,10 +849,7 @@ set(MODE_CPPSRC
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/timer.c
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/i2c.c
)
# sd_over_usb has type conflicts with PRALINE (HackRF Pro) - disable for now
if(NOT BOARD STREQUAL "PRALINE")
DeclareTargets(PUSB sd_over_usb)
endif()
### HackRF "factory" firmware
+71 -26
View File
@@ -337,6 +337,66 @@ buffer_c16_t FIRC16xR16x16Decim2::execute(
src.sampling_rate / decimation_factor};
}
// FIRC16xR16x63HalfbandDecim2 ////////////////////////////////////////////
void FIRC16xR16x63HalfbandDecim2::configure(
const std::array<int16_t, taps_count>& taps) {
std::copy(taps.cbegin(), taps.cend(), taps_.begin());
reset();
}
void FIRC16xR16x63HalfbandDecim2::reset() {
samples_.fill({});
samples_head_ = 0;
}
buffer_c16_t FIRC16xR16x63HalfbandDecim2::execute(
const buffer_c16_t& src,
const buffer_c16_t& dst) {
auto* dst_p = reinterpret_cast<uint32_t*>(dst.p);
for (size_t output = 0; output < src.count / decimation_factor; ++output) {
for (size_t i = 0; i < decimation_factor; ++i) {
const auto sample = src.p[output * decimation_factor + i];
samples_[samples_head_] = sample;
samples_[samples_head_ + taps_count] = sample;
if (++samples_head_ == taps_count)
samples_head_ = 0;
}
int64_t real = 0;
int64_t imag = 0;
for (size_t tap = 0; tap < taps_count; tap += 4) {
const auto sample_0 =
*reinterpret_cast<const uint32_t*>(&samples_[samples_head_ + tap]);
const auto sample_1 =
*reinterpret_cast<const uint32_t*>(&samples_[samples_head_ + tap + 2]);
const auto real_pair = __PKHBT(sample_0, sample_1, 16);
const auto imag_pair = __PKHTB(sample_1, sample_0, 16);
const auto taps_pair = uint32_t(uint16_t(taps_[tap])) |
(uint32_t(uint16_t(taps_[tap + 2])) << 16);
real = __SMLALD(real_pair, taps_pair, real);
imag = __SMLALD(imag_pair, taps_pair, imag);
}
const auto center =
*reinterpret_cast<const uint32_t*>(&samples_[samples_head_ + taps_count / 2]);
real += int16_t(center) * taps_[taps_count / 2];
imag += int16_t(center >> 16) * taps_[taps_count / 2];
const auto real_s16 =
__SSAT((real + (real >= 0 ? 32768 : -32768)) / 65536, 16);
const auto imag_s16 =
__SSAT((imag + (imag >= 0 ? 32768 : -32768)) / 65536, 16);
*(dst_p++) = __PKHBT(real_s16, imag_s16, 16);
}
return {
dst.p,
src.count / decimation_factor,
src.sampling_rate / decimation_factor};
}
// FIRC16xR16x32Decim8 ////////////////////////////////////////////////////
void FIRC16xR16x32Decim8::configure(
@@ -625,10 +685,13 @@ buffer_s16_t FIR64AndDecimateBy2Real::execute(
void FIRAndDecimateComplex::configure_common(
const size_t taps_count,
const size_t decimation_factor) {
samples_ = std::make_unique<samples_t>(taps_count);
/* Mirror the delay line so a convolution always sees one contiguous
* taps_count window, even when the logical head wraps. */
samples_ = std::make_unique<samples_t>(taps_count * 2);
taps_reversed_ = std::make_unique<taps_t>(taps_count);
taps_count_ = taps_count;
decimation_factor_ = decimation_factor;
samples_head_ = 0;
}
buffer_c16_t FIRAndDecimateComplex::execute(
@@ -647,15 +710,18 @@ buffer_c16_t FIRAndDecimateComplex::execute(
const void* src_p = src.p;
size_t outer_count = output_samples;
while (outer_count > 0) {
/* Put new samples into delay buffer */
void* z_new_p = &samples_[taps_count_ - decimation_factor_];
/* Put new samples into both halves of the mirrored ring. */
for (size_t i = 0; i < decimation_factor_; i++) {
*__SIMD32(z_new_p)++ = *__SIMD32(src_p)++;
const uint32_t sample = *__SIMD32(src_p)++;
*reinterpret_cast<uint32_t*>(&samples_[samples_head_]) = sample;
*reinterpret_cast<uint32_t*>(&samples_[samples_head_ + taps_count_]) = sample;
if (++samples_head_ == taps_count_)
samples_head_ = 0;
}
size_t loop_count = taps_count_ / 8;
void* t_p = &taps_reversed_[0];
void* z_p = &samples_[0];
void* z_p = &samples_[samples_head_];
int64_t t_real = 0;
int64_t t_imag = 0;
@@ -712,27 +778,6 @@ buffer_c16_t FIRAndDecimateComplex::execute(
i_sat,
16);
/* Shift sample buffer left/down by decimation factor. */
const size_t unroll_factor = 4;
size_t shift_count = (taps_count_ - decimation_factor_) / unroll_factor;
void* t = &samples_[0];
const void* s = &samples_[decimation_factor_];
while (shift_count > 0) {
*__SIMD32(t)++ = *__SIMD32(s)++;
*__SIMD32(t)++ = *__SIMD32(s)++;
*__SIMD32(t)++ = *__SIMD32(s)++;
*__SIMD32(t)++ = *__SIMD32(s)++;
shift_count--;
}
shift_count = (taps_count_ - decimation_factor_) % unroll_factor;
while (shift_count > 0) {
*__SIMD32(t)++ = *__SIMD32(s)++;
shift_count--;
}
outer_count--;
}
+36
View File
@@ -170,6 +170,24 @@ class FIRC16xR16x16Decim2 {
int32_t output_scale = 0;
};
class FIRC16xR16x63HalfbandDecim2 {
public:
static constexpr size_t taps_count = 63;
static constexpr size_t decimation_factor = 2;
void configure(const std::array<int16_t, taps_count>& taps);
void reset();
buffer_c16_t execute(
const buffer_c16_t& src,
const buffer_c16_t& dst);
private:
alignas(4) std::array<complex16_t, taps_count * 2> samples_{};
alignas(4) std::array<int16_t, taps_count> taps_{};
size_t samples_head_{0};
};
class FIRC16xR16x32Decim8 {
public:
static constexpr size_t taps_count = 32;
@@ -210,6 +228,23 @@ class FIRAndDecimateComplex {
configure(taps.data(), taps.size(), decimation_factor);
}
template <size_t N>
void configure(
const std::array<int16_t, N>& taps,
const size_t decimation_factor) {
configure_common(N, decimation_factor);
for (size_t i = 0; i < N; ++i) {
taps_reversed_[i] = {taps[N - 1 - i], 0};
}
}
template <size_t N>
void set_taps(const std::array<complex16_t, N>& taps) {
if (N == taps_count_) {
std::reverse_copy(taps.begin(), taps.end(), &taps_reversed_[0]);
}
}
buffer_c16_t execute(
const buffer_c16_t& src,
const buffer_c16_t& dst);
@@ -221,6 +256,7 @@ class FIRAndDecimateComplex {
std::unique_ptr<taps_t> taps_reversed_{};
size_t taps_count_{0};
size_t decimation_factor_{1};
size_t samples_head_{0};
template <typename T>
void configure(
+472
View File
@@ -0,0 +1,472 @@
/*
* Copyright (C) 2026
*
* This file is part of PortaPack.
*/
#ifndef __DSP_FREQUENCY_XLATOR_H__
#define __DSP_FREQUENCY_XLATOR_H__
#include "dsp_decimate.hpp"
#include "dsp_types.hpp"
#include <array>
#include <cstdint>
namespace dsp {
/* Fixed-point complex mixer for the Audio RX channelizer. */
class FrequencyTranslator {
public:
FrequencyTranslator() {
for (size_t i = 0; i < oscillator_q15_.size(); ++i) {
oscillator_q15_[i] =
static_cast<uint16_t>(sine_q15_[static_cast<uint8_t>(i + 64)]) |
(static_cast<uint32_t>(
static_cast<uint16_t>(sine_q15_[i]))
<< 16);
}
}
void set_sample_rate(const uint32_t sampling_rate) {
sampling_rate_ = sampling_rate;
update_phase_increment();
}
void set_frequency(const int32_t frequency) {
frequency_ = frequency;
update_phase_increment();
}
buffer_c16_t execute(const buffer_c16_t& src, const buffer_c16_t& dst) {
auto phase = phase_;
for (size_t i = 0; i < src.count; ++i) {
const uint8_t index = phase >> 24;
const uint8_t next = index + 1;
const int32_t fraction = (phase >> 16) & 0xff;
const uint32_t oscillator_first = oscillator_q15_[index];
const uint32_t oscillator_next = oscillator_q15_[next];
const int32_t sine_first =
static_cast<int16_t>(oscillator_first >> 16);
const int32_t cosine_first =
static_cast<int16_t>(oscillator_first);
const int32_t sine =
sine_first +
(((static_cast<int16_t>(oscillator_next >> 16) -
sine_first) *
fraction) >>
8);
const int32_t cosine =
cosine_first +
(((static_cast<int16_t>(oscillator_next) -
cosine_first) *
fraction) >>
8);
const uint32_t oscillator =
static_cast<uint16_t>(cosine) |
(static_cast<uint32_t>(
static_cast<uint16_t>(sine))
<< 16);
const uint32_t sample =
*reinterpret_cast<const uint32_t*>(&src.p[i]);
/* Two packed dual-16-bit multiplies implement
* (I+jQ) * (cos-j sin). */
const int32_t out_i =
rounded_shift(__SMUAD(sample, oscillator), 15);
const int32_t out_q =
rounded_shift(__SMUSDX(oscillator, sample), 15);
*reinterpret_cast<uint32_t*>(&dst.p[i]) =
__PKHBT(__SSAT(out_i, 16), __SSAT(out_q, 16), 16);
phase += phase_increment_;
}
phase_ = phase;
return {dst.p, src.count, src.sampling_rate};
}
private:
friend class FrequencyTranslatingDecimator32By8;
static constexpr int32_t rounded_shift(
const int32_t value,
const uint32_t bits) {
const int32_t rounding = int32_t{1} << (bits - 1);
return value >= 0
? (value + rounding) >> bits
: -((-value + rounding) >> bits);
}
void update_phase_increment() {
if (sampling_rate_) {
phase_increment_ = static_cast<uint32_t>(
(static_cast<int64_t>(frequency_) * (int64_t{1} << 32)) /
sampling_rate_);
}
}
static constexpr std::array<int16_t, 256> sine_q15_{{
0,
804,
1608,
2410,
3212,
4011,
4808,
5602,
6393,
7179,
7962,
8739,
9512,
10278,
11039,
11793,
12539,
13279,
14010,
14732,
15446,
16151,
16846,
17530,
18204,
18868,
19519,
20159,
20787,
21403,
22005,
22594,
23170,
23731,
24279,
24811,
25329,
25832,
26319,
26790,
27245,
27683,
28105,
28510,
28898,
29268,
29621,
29956,
30273,
30571,
30852,
31113,
31356,
31580,
31785,
31971,
32137,
32285,
32412,
32521,
32609,
32678,
32728,
32757,
32767,
32757,
32728,
32678,
32609,
32521,
32412,
32285,
32137,
31971,
31785,
31580,
31356,
31113,
30852,
30571,
30273,
29956,
29621,
29268,
28898,
28510,
28105,
27683,
27245,
26790,
26319,
25832,
25329,
24811,
24279,
23731,
23170,
22594,
22005,
21403,
20787,
20159,
19519,
18868,
18204,
17530,
16846,
16151,
15446,
14732,
14010,
13279,
12539,
11793,
11039,
10278,
9512,
8739,
7962,
7179,
6393,
5602,
4808,
4011,
3212,
2410,
1608,
804,
0,
-804,
-1608,
-2410,
-3212,
-4011,
-4808,
-5602,
-6393,
-7179,
-7962,
-8739,
-9512,
-10278,
-11039,
-11793,
-12539,
-13279,
-14010,
-14732,
-15446,
-16151,
-16846,
-17530,
-18204,
-18868,
-19519,
-20159,
-20787,
-21403,
-22005,
-22594,
-23170,
-23731,
-24279,
-24811,
-25329,
-25832,
-26319,
-26790,
-27245,
-27683,
-28105,
-28510,
-28898,
-29268,
-29621,
-29956,
-30273,
-30571,
-30852,
-31113,
-31356,
-31580,
-31785,
-31971,
-32137,
-32285,
-32412,
-32521,
-32609,
-32678,
-32728,
-32757,
-32767,
-32757,
-32728,
-32678,
-32609,
-32521,
-32412,
-32285,
-32137,
-31971,
-31785,
-31580,
-31356,
-31113,
-30852,
-30571,
-30273,
-29956,
-29621,
-29268,
-28898,
-28510,
-28105,
-27683,
-27245,
-26790,
-26319,
-25832,
-25329,
-24811,
-24279,
-23731,
-23170,
-22594,
-22005,
-21403,
-20787,
-20159,
-19519,
-18868,
-18204,
-17530,
-16846,
-16151,
-15446,
-14732,
-14010,
-13279,
-12539,
-11793,
-11039,
-10278,
-9512,
-8739,
-7962,
-7179,
-6393,
-5602,
-4808,
-4011,
-3212,
-2410,
-1608,
-804,
}};
std::array<uint32_t, 256> oscillator_q15_{};
uint32_t phase_{0};
uint32_t phase_increment_{0};
uint32_t sampling_rate_{192000};
int32_t frequency_{0};
};
/*
* Frequency-translating 32-tap FIR decimator. Frequency translation is
* split between coefficients modulated when tuning changes and a cheap
* output-rate phase rotation. This avoids running an NCO at the 384kHz
* input rate.
*/
class FrequencyTranslatingDecimator32By8 {
public:
static constexpr size_t decimation_factor = 8;
static constexpr size_t taps_count = 32;
void configure(
const std::array<int16_t, taps_count>& taps,
const uint32_t input_sampling_rate) {
taps_ = taps;
input_sampling_rate_ = input_sampling_rate;
decimator_.configure(complex_taps_, decimation_factor);
output_xlator_.set_sample_rate(input_sampling_rate / decimation_factor);
update_taps();
}
void set_frequency(const int32_t frequency) {
frequency_ = frequency;
output_xlator_.set_frequency(frequency);
update_taps();
}
buffer_c16_t execute(
const buffer_c16_t& src,
const buffer_c16_t& dst) {
const auto filtered = decimator_.execute(src, dst);
return output_xlator_.execute(filtered, dst);
}
private:
static void oscillator(
const uint32_t phase,
int32_t& sine,
int32_t& cosine) {
const uint8_t index = phase >> 24;
const uint8_t next = index + 1;
const uint8_t cosine_index = index + 64;
const uint8_t cosine_next = cosine_index + 1;
const int32_t fraction = (phase >> 16) & 0xff;
const int32_t sine_first = FrequencyTranslator::sine_q15_[index];
const int32_t cosine_first = FrequencyTranslator::sine_q15_[cosine_index];
sine = sine_first +
(((FrequencyTranslator::sine_q15_[next] - sine_first) * fraction) >> 8);
cosine = cosine_first +
(((FrequencyTranslator::sine_q15_[cosine_next] - cosine_first) * fraction) >> 8);
}
void update_taps() {
if (!input_sampling_rate_)
return;
const uint32_t tap_phase_increment = static_cast<uint32_t>(
(static_cast<int64_t>(frequency_) * (int64_t{1} << 32)) /
input_sampling_rate_);
/* Centre the modulation on the FIR midpoint. Besides changing
* only a constant output phase, this makes the two coefficients
* in each symmetric pair complex conjugates. That property is
* important after quantization: starting at tap zero accumulated
* a one-sided phase and rounding error across the whole filter. */
uint32_t phase = static_cast<uint32_t>(
-((static_cast<int64_t>(
static_cast<int32_t>(tap_phase_increment)) *
static_cast<int64_t>(taps_count - 1)) /
2));
for (size_t i = 0; i < taps_count; ++i) {
int32_t sine;
int32_t cosine;
oscillator(phase, sine, cosine);
/* FIRAndDecimateComplex uses Q16 coefficients; the source
* real-tap filters use Q15 coefficients. */
const int32_t tap = taps_[i];
complex_taps_[i] = {
static_cast<int16_t>(
FrequencyTranslator::rounded_shift(
tap * cosine, 14)),
static_cast<int16_t>(
FrequencyTranslator::rounded_shift(
tap * sine, 14))};
phase += tap_phase_increment;
}
decimator_.set_taps(complex_taps_);
}
std::array<int16_t, taps_count> taps_{};
std::array<complex16_t, taps_count> complex_taps_{};
decimate::FIRAndDecimateComplex decimator_{};
FrequencyTranslator output_xlator_{};
uint32_t input_sampling_rate_{0};
int32_t frequency_{0};
};
} /* namespace dsp */
#endif /*__DSP_FREQUENCY_XLATOR_H__*/
@@ -0,0 +1,90 @@
/*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*/
#include "filtered_spectrum_collector.hpp"
#include "dsp_fir_taps.hpp"
#include "event_m4.hpp"
#include <algorithm>
void FilteredSpectrumCollector::on_message(const Message* const message) {
if (message->id == Message::ID::UpdateSpectrum) {
update();
}
SpectrumCollector::on_message(message);
}
void FilteredSpectrumCollector::start_capture(
const size_t decimation_factor) {
capture_decimation_ = decimation_factor;
capture_count_ = 0;
capture_ready_ = false;
}
bool FilteredSpectrumCollector::feed(
const buffer_c16_t& channel,
const int32_t filter_low_frequency,
const int32_t filter_high_frequency,
const int32_t filter_transition) {
set_filter(
filter_low_frequency,
filter_high_frequency,
filter_transition);
const size_t required_samples = 256 * capture_decimation_;
const size_t copy_count = std::min(
channel.count, required_samples - capture_count_);
std::copy_n(
channel.p,
copy_count,
capture_.begin() + capture_count_);
capture_count_ += copy_count;
if (capture_count_ == required_samples) {
sampling_rate_ = channel.sampling_rate / capture_decimation_;
capture_ready_ = true;
EventDispatcher::events_flag(EVT_MASK_SPECTRUM);
if (is_streaming()) {
capture_ready_ = true;
EventDispatcher::events_flag(EVT_MASK_SPECTRUM);
} else {
capture_ready_ = false;
}
return true;
}
return false;
}
void FilteredSpectrumCollector::update() {
if (!capture_ready_) {
return;
}
decim_0_.configure(taps_audio_spectrum_halfband.taps);
const buffer_c16_t capture{
capture_.data(),
256 * capture_decimation_,
sampling_rate_ * capture_decimation_};
const buffer_c16_t stage_0{
stage_0_.data(),
stage_0_.size()};
const auto filtered = decim_0_.execute(capture, stage_0);
if (capture_decimation_ == 4) {
decim_1_.configure(taps_audio_spectrum_halfband.taps);
const buffer_c16_t stage_1{
stage_1_.data(),
stage_1_.size()};
post_message(decim_1_.execute(filtered, stage_1));
} else {
post_message(filtered);
}
capture_ready_ = false;
}
@@ -0,0 +1,43 @@
/*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*/
#ifndef __FILTERED_SPECTRUM_COLLECTOR_H__
#define __FILTERED_SPECTRUM_COLLECTOR_H__
#include "dsp_decimate.hpp"
#include "spectrum_collector.hpp"
#include <array>
class FilteredSpectrumCollector : public SpectrumCollector {
public:
void on_message(const Message* const message);
void start_capture(const size_t decimation_factor);
bool feed(
const buffer_c16_t& channel,
const int32_t filter_low_frequency,
const int32_t filter_high_frequency,
const int32_t filter_transition);
private:
std::array<complex16_t, 1024> capture_{};
std::array<complex16_t, 512> stage_0_{};
std::array<complex16_t, 256> stage_1_{};
dsp::decimate::FIRC16xR16x63HalfbandDecim2 decim_0_{};
dsp::decimate::FIRC16xR16x63HalfbandDecim2 decim_1_{};
size_t capture_count_{0};
size_t capture_decimation_{1};
uint32_t sampling_rate_{0};
bool capture_ready_{false};
void update();
};
#endif /*__FILTERED_SPECTRUM_COLLECTOR_H__*/
+124
View File
@@ -0,0 +1,124 @@
#ifndef __FPROTO_SUPERROLLO_H__
#define __FPROTO_SUPERROLLO_H__
#include "subghzdbase.hpp"
// Superrollo GW60 (HCS361) 67-bit OOK decoder. Reports FPS_SUPERROLLO; SubGhzD
// treats FPS_SUPERROLLO like KeeLoq for decrypt/display (see ui_subghzd.cpp), so
// the "Superrollo" keystore key decrypts it while it stays identifiable in logs.
typedef enum : uint8_t {
SuperrolloStepReset = 0,
SuperrolloStepPreambleLow,
SuperrolloStepSyncLow,
SuperrolloStepSaveDuration,
SuperrolloStepCheckDuration,
} SuperrolloDecoderStep;
class FProtoSubGhzDSuperrollo : public FProtoSubGhzDBase {
public:
FProtoSubGhzDSuperrollo() {
sensorType = FPS_SUPERROLLO;
te_short = 450;
te_long = 900;
te_delta = 200;
min_count_bit_for_found = 64;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case SuperrolloStepReset:
if (level) {
if (DURATION_DIFF(duration, te_short) < te_delta) {
header_count++;
parser_step = SuperrolloStepPreambleLow;
} else if (
(header_count >= 4) &&
(DURATION_DIFF(duration, te_short * 10) < te_delta * 10)) {
parser_step = SuperrolloStepSyncLow;
} else {
header_count = 0;
}
} else {
header_count = 0;
}
break;
case SuperrolloStepPreambleLow:
if ((!level) && (DURATION_DIFF(duration, te_long) < te_delta)) {
parser_step = SuperrolloStepReset;
} else {
parser_step = SuperrolloStepReset;
header_count = 0;
}
break;
case SuperrolloStepSyncLow:
if ((!level) && (DURATION_DIFF(duration, te_short * 10) < te_delta * 10)) {
parser_step = SuperrolloStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
} else {
parser_step = SuperrolloStepReset;
header_count = 0;
}
break;
case SuperrolloStepSaveDuration:
if (level) {
te_last = duration;
parser_step = SuperrolloStepCheckDuration;
} else if (duration >= te_short * 12) {
endFrame();
}
break;
case SuperrolloStepCheckDuration:
if (!level) {
if ((DURATION_DIFF(te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_long) < te_delta)) {
if (decode_count_bit < min_count_bit_for_found)
subghz_protocol_blocks_add_bit(1);
else
decode_count_bit++;
parser_step = SuperrolloStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_long) < te_delta) &&
(DURATION_DIFF(duration, te_short) < te_delta)) {
if (decode_count_bit < min_count_bit_for_found)
subghz_protocol_blocks_add_bit(0);
else
decode_count_bit++;
parser_step = SuperrolloStepSaveDuration;
} else if (duration >= te_short * 12) {
endFrame();
} else {
parser_step = SuperrolloStepReset;
header_count = 0;
}
} else {
parser_step = SuperrolloStepReset;
header_count = 0;
}
break;
}
}
uint16_t header_count = 0;
private:
void endFrame() {
if ((decode_count_bit >= min_count_bit_for_found) &&
(decode_count_bit <= min_count_bit_for_found + 3)) {
data_count_bit = min_count_bit_for_found;
if (callback) callback(this);
}
parser_step = SuperrolloStepReset;
decode_data = 0;
decode_count_bit = 0;
header_count = 0;
}
};
#endif
@@ -29,6 +29,7 @@ So include here the .hpp, and add a new element to the protos vector in the cons
#include "s-ido.hpp"
#include "s-intertechnov3.hpp"
#include "s-keeloq.hpp"
#include "s-superrollo.hpp"
#include "s-kinggates_stylo_4k.hpp"
#include "s-linear.hpp"
#include "s-linear_delta3.hpp"
@@ -85,6 +86,7 @@ class SubGhzDProtos : public FProtoListGeneral {
protos[FPS_IDO] = new FProtoSubGhzDIdo();
protos[FPS_INTERTECHNOV3] = new FProtoSubGhzDIntertechnoV3();
protos[FPS_KEELOQ] = new FProtoSubGhzDKeeLoq();
protos[FPS_SUPERROLLO] = new FProtoSubGhzDSuperrollo();
protos[FPS_KINGGATESSTYLO4K] = new FProtoSubGhzDKinggatesStylo4K();
protos[FPS_LINEAR] = new FProtoSubGhzDLinear();
protos[FPS_LINEARDELTA3] = new FProtoSubGhzDLinearDelta3();
@@ -60,6 +60,7 @@ enum FPROTO_SUBGHZD_SENSOR : uint8_t {
FPS_MARANTEC24,
FPS_HOLTEKHT6P20B,
FPS_RESTAURANT_PAGER,
FPS_SUPERROLLO,
FPS_COUNT
};
+38 -8
View File
@@ -45,10 +45,26 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
}
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
const auto audio_decim_0_out = audio_decim_0.execute(decim_0_out, dst_buffer);
channel_spectrum.feed(decim_1_out, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
spectrum_samples += decim_0_out.count;
if (!spectrum_capture_active &&
spectrum_samples >= spectrum_interval_samples) {
spectrum_samples -= spectrum_interval_samples;
channel_spectrum.start_capture(spectrum_zoom_x2 ? 4 : 2);
spectrum_capture_active = true;
}
if (spectrum_capture_active &&
channel_spectrum.feed(
audio_decim_0_out,
channel_filter_low_f,
channel_filter_high_f,
channel_filter_transition)) {
spectrum_capture_active = false;
}
const auto decim_1_out = translating_decim_1.execute(audio_decim_0_out, dst_buffer);
const auto decim_2_out = decim_2.execute(decim_1_out, dst_buffer);
const auto channel_out = channel_filter.execute(decim_2_out, dst_buffer);
@@ -97,6 +113,10 @@ void NarrowbandAMAudio::on_message(const Message* const message) {
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
break;
case Message::ID::AudioDDCConfig:
ddc_config(*reinterpret_cast<const AudioDDCConfigMessage*>(message));
break;
default:
break;
}
@@ -106,17 +126,19 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
constexpr size_t decim_0_input_fs = baseband_fs;
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
constexpr size_t decim_1_input_fs = decim_0_output_fs;
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
constexpr size_t audio_decim_0_output_fs = decim_0_output_fs / 2;
constexpr size_t decim_1_output_fs =
audio_decim_0_output_fs / translating_decim_1.decimation_factor;
constexpr size_t decim_2_input_fs = decim_1_output_fs;
constexpr size_t decim_2_output_fs = decim_2_input_fs / decim_2_decimation_factor;
constexpr size_t channel_filter_input_fs = decim_2_output_fs;
// const size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor;
decim_0.configure(message.decim_0_filter.taps);
decim_1.configure(message.decim_1_filter.taps);
decim_0.configure(message.decim_0_filter.taps, 33554432);
audio_decim_0.configure(taps_audio_wide_halfband_0.taps);
translating_decim_1.configure(
message.decim_1_filter.taps, audio_decim_0_output_fs);
decim_2.configure(message.decim_2_filter.taps, decim_2_decimation_factor);
channel_filter.configure(message.channel_filter.taps, channel_filter_decimation_factor);
channel_filter_low_f = message.channel_filter.low_frequency_normalized * channel_filter_input_fs;
@@ -124,12 +146,20 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs;
modulation_ssb = (int)message.modulation; // now sending by message , 3 types of AM demod : enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
channel_spectrum.set_decimation_factor(message.channel_spectrum_decimation_factor);
spectrum_zoom_x2 = message.channel_spectrum_decimation_factor == 2;
channel_spectrum.set_decimation_factor(1);
spectrum_interval_samples =
decim_0_output_fs / spectrum_rate_hz;
audio_output.configure(message.audio_hpf_lpf_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
configured = true;
}
void NarrowbandAMAudio::ddc_config(const AudioDDCConfigMessage& message) {
translating_decim_1.set_frequency(message.frequency);
channel_spectrum.set_channel_filter_offset(message.frequency);
}
void NarrowbandAMAudio::capture_config(const CaptureConfigMessage& message) {
if (message.config) {
audio_output.set_stream(std::make_unique<StreamInput>(message.config));
+12 -4
View File
@@ -28,10 +28,11 @@
#include "dsp_decimate.hpp"
#include "dsp_demodulate.hpp"
#include "dsp_frequency_xlator.hpp"
#include "audio_compressor.hpp"
#include "audio_output.hpp"
#include "spectrum_collector.hpp"
#include "filtered_spectrum_collector.hpp"
#include <cstdint>
@@ -44,6 +45,7 @@ class NarrowbandAMAudio : public BasebandProcessor {
private:
static constexpr size_t baseband_fs = 3072000;
static constexpr auto spectrum_rate_hz = 30.0f;
static constexpr size_t decim_2_decimation_factor = 4;
static constexpr size_t channel_filter_decimation_factor = 1;
@@ -56,14 +58,19 @@ class NarrowbandAMAudio : public BasebandProcessor {
audio.data(),
audio.size()};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
dsp::decimate::FIRC16xR16x16Decim2 audio_decim_0{};
dsp::FrequencyTranslatingDecimator32By8 translating_decim_1{};
dsp::decimate::FIRAndDecimateComplex decim_2{};
dsp::decimate::FIRAndDecimateComplex channel_filter{};
int32_t channel_filter_low_f = 0;
int32_t channel_filter_high_f = 0;
int32_t channel_filter_transition = 0;
bool configured{false};
size_t spectrum_interval_samples{0};
size_t spectrum_samples{0};
bool spectrum_capture_active{false};
bool spectrum_zoom_x2{false};
// bool modulation_ssb = false; // Origianlly we only had 2 AM demod types {DSB = 0, SSB = 1} , and we could handle it with bool var , 1 bit.
int8_t modulation_ssb = 0; // Now we have 3 AM demod types we will send now index integer {DSB = 0, SSB = 1, SSB_FM = 2}
@@ -73,7 +80,7 @@ class NarrowbandAMAudio : public BasebandProcessor {
FeedForwardCompressor audio_compressor{};
AudioOutput audio_output{};
SpectrumCollector channel_spectrum{};
FilteredSpectrumCollector channel_spectrum{};
/* NB: Threads should be the last members in the class definition. */
#ifdef PRALINE
@@ -86,6 +93,7 @@ class NarrowbandAMAudio : public BasebandProcessor {
#endif
void configure(const AMConfigureMessage& message);
void ddc_config(const AudioDDCConfigMessage& message);
void capture_config(const CaptureConfigMessage& message);
buffer_f32_t demodulate(const buffer_c16_t& channel);
+37 -8
View File
@@ -51,10 +51,26 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
}
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
const auto audio_decim_0_out = audio_decim_0.execute(decim_0_out, dst_buffer);
channel_spectrum.feed(decim_1_out, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
spectrum_samples += decim_0_out.count;
if (!spectrum_capture_active &&
spectrum_samples >= spectrum_interval_samples) {
spectrum_samples -= spectrum_interval_samples;
channel_spectrum.start_capture(2);
spectrum_capture_active = true;
}
if (spectrum_capture_active &&
channel_spectrum.feed(
audio_decim_0_out,
channel_filter_low_f,
channel_filter_high_f,
channel_filter_transition)) {
spectrum_capture_active = false;
}
const auto decim_1_out = translating_decim_1.execute(audio_decim_0_out, dst_buffer);
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
feed_channel_stats(channel_out);
@@ -141,6 +157,10 @@ void NarrowbandFMAudio::on_message(const Message* const message) {
pitch_rssi_config(*reinterpret_cast<const PitchRSSIConfigureMessage*>(message));
break;
case Message::ID::AudioDDCConfig:
ddc_config(*reinterpret_cast<const AudioDDCConfigMessage*>(message));
break;
default:
break;
}
@@ -150,22 +170,26 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) {
constexpr size_t decim_0_input_fs = baseband_fs;
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
constexpr size_t decim_1_input_fs = decim_0_output_fs;
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
constexpr size_t audio_decim_0_output_fs = decim_0_output_fs / 2;
constexpr size_t decim_1_output_fs =
audio_decim_0_output_fs / translating_decim_1.decimation_factor;
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
const size_t channel_filter_output_fs = channel_filter_input_fs / message.channel_decimation;
const size_t demod_input_fs = channel_filter_output_fs;
decim_0.configure(message.decim_0_filter.taps);
decim_1.configure(message.decim_1_filter.taps);
decim_0.configure(message.decim_0_filter.taps, 33554432);
audio_decim_0.configure(taps_audio_wide_halfband_0.taps);
translating_decim_1.configure(
message.decim_1_filter.taps, audio_decim_0_output_fs);
channel_filter.configure(message.channel_filter.taps, message.channel_decimation);
demod.configure(demod_input_fs, message.deviation);
channel_filter_low_f = message.channel_filter.low_frequency_normalized * channel_filter_input_fs;
channel_filter_high_f = message.channel_filter.high_frequency_normalized * channel_filter_input_fs;
channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs;
channel_spectrum.set_decimation_factor(1.0f);
channel_spectrum.set_decimation_factor(1);
spectrum_interval_samples =
decim_0_output_fs / spectrum_rate_hz;
audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, (float)message.squelch_level / 100.0);
hpf.configure(audio_24k_hpf_30hz_config);
@@ -174,6 +198,11 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) {
configured = true;
}
void NarrowbandFMAudio::ddc_config(const AudioDDCConfigMessage& message) {
translating_decim_1.set_frequency(message.frequency);
channel_spectrum.set_channel_filter_offset(message.frequency);
}
void NarrowbandFMAudio::pitch_rssi_config(const PitchRSSIConfigureMessage& message) {
pitch_rssi_enabled = message.enabled;
tone_delta = (message.rssi + 1000) * ((1ULL << 32) / 24000);
+11 -4
View File
@@ -29,10 +29,11 @@
#include "dsp_decimate.hpp"
#include "dsp_demodulate.hpp"
#include "dsp_frequency_xlator.hpp"
#include "dsp_iir.hpp"
#include "audio_output.hpp"
#include "spectrum_collector.hpp"
#include "filtered_spectrum_collector.hpp"
#include <cstdint>
@@ -50,6 +51,7 @@ class NarrowbandFMAudio : public BasebandProcessor {
private:
static constexpr size_t baseband_fs = 3072000;
static constexpr auto spectrum_rate_hz = 30.0f;
std::array<complex16_t, 512> dst{};
const buffer_c16_t dst_buffer{
@@ -69,8 +71,9 @@ class NarrowbandFMAudio : public BasebandProcessor {
(int16_t*)tone.data(),
sizeof(tone) / sizeof(int16_t)};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
dsp::decimate::FIRC16xR16x16Decim2 audio_decim_0{};
dsp::FrequencyTranslatingDecimator32By8 translating_decim_1{};
dsp::decimate::FIRAndDecimateComplex channel_filter{};
int32_t channel_filter_low_f = 0;
int32_t channel_filter_high_f = 0;
@@ -84,7 +87,10 @@ class NarrowbandFMAudio : public BasebandProcessor {
AudioOutput audio_output{};
SpectrumCollector channel_spectrum{};
FilteredSpectrumCollector channel_spectrum{};
size_t spectrum_interval_samples{0};
size_t spectrum_samples{0};
bool spectrum_capture_active{false};
uint32_t tone_phase{0};
uint32_t tone_delta{0};
@@ -113,6 +119,7 @@ class NarrowbandFMAudio : public BasebandProcessor {
void pitch_rssi_config(const PitchRSSIConfigureMessage& message);
void configure(const NBFMConfigureMessage& message);
void capture_config(const CaptureConfigMessage& message);
void ddc_config(const AudioDDCConfigMessage& message);
};
#endif /*__PROC_NFM_AUDIO_H__*/
+263
View File
@@ -0,0 +1,263 @@
/*
* Copyright 2026 Great Scott Gadgets <info@greatscottgadgets.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "cpu_clock.h"
#include <stdint.h>
#include <libopencm3/lpc43xx/cgu.h>
#if defined(IS_JAWBREAKER) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE)
#include <libopencm3/lpc43xx/ccu.h>
#endif
#include "delay.h"
#include "i2c_bus.h"
#include "i2c_lpc.h"
#include "si5351c.h"
/* We start with the CPU clock at 96MHz */
unsigned int cpu_clock_mhz = 96;
/*
Configure PLL1 (Main MCU Clock) to max speed (204MHz).
Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1.
This function shall be called after cpu_clock_init().
*/
static void cpu_clock_pll1_max_speed(void) {
uint32_t reg_val;
/* This function implements the sequence recommended in:
* UM10503 Rev 2.4 (Aug 2018), section 13.2.1.1, page 167. */
/* 1. Select the IRC as BASE_M4_CLK source. */
reg_val = CGU_BASE_M4_CLK;
reg_val &= ~CGU_BASE_M4_CLK_CLK_SEL_MASK;
reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_IRC) | CGU_BASE_M4_CLK_AUTOBLOCK(1);
CGU_BASE_M4_CLK = reg_val;
/* CPU is now at 12MHz */
cpu_clock_mhz = 12;
/* 2. Enable the crystal oscillator. */
CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_ENABLE_MASK;
/* 3. Wait 250us. */
delay_us(250);
/* 4. Set the AUTOBLOCK bit. */
CGU_PLL1_CTRL |= CGU_PLL1_CTRL_AUTOBLOCK(1);
/* 5. Reconfigure PLL1 to produce the final output frequency, with the
* crystal oscillator as clock source. */
reg_val = CGU_PLL1_CTRL;
// clang-format off
reg_val &= ~( CGU_PLL1_CTRL_CLK_SEL_MASK |
CGU_PLL1_CTRL_PD_MASK |
CGU_PLL1_CTRL_FBSEL_MASK |
CGU_PLL1_CTRL_BYPASS_MASK |
CGU_PLL1_CTRL_DIRECT_MASK |
CGU_PLL1_CTRL_PSEL_MASK |
CGU_PLL1_CTRL_MSEL_MASK |
CGU_PLL1_CTRL_NSEL_MASK );
/* Set PLL1 up to 12MHz * 17 = 204MHz.
* Direct mode: FCLKOUT = FCCO = M*(FCLKIN/N) */
reg_val |= CGU_PLL1_CTRL_CLK_SEL(CGU_SRC_XTAL) |
CGU_PLL1_CTRL_PSEL(0) |
CGU_PLL1_CTRL_NSEL(0) |
CGU_PLL1_CTRL_MSEL(16) |
CGU_PLL1_CTRL_FBSEL(0) |
CGU_PLL1_CTRL_DIRECT(1);
// clang-format on
CGU_PLL1_CTRL = reg_val;
/* 6. Wait for PLL1 to lock. */
while (!(CGU_PLL1_STAT & CGU_PLL1_STAT_LOCK_MASK)) {
}
/* 7. Set the PLL1 P-divider to divide by 2 (DIRECT=0, PSEL=0). */
CGU_PLL1_CTRL &= ~CGU_PLL1_CTRL_DIRECT_MASK;
/* 8. Select PLL1 as BASE_M4_CLK source. */
reg_val = CGU_BASE_M4_CLK;
reg_val &= ~CGU_BASE_M4_CLK_CLK_SEL_MASK;
reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_PLL1);
CGU_BASE_M4_CLK = reg_val;
/* CPU is now at 102MHz */
cpu_clock_mhz = 102;
/* 9. Wait 50us. */
delay_us(50);
/* 10. Set the PLL1 P-divider to direct output mode (DIRECT=1). */
CGU_PLL1_CTRL |= CGU_PLL1_CTRL_DIRECT_MASK;
/* CPU is now at 204MHz */
cpu_clock_mhz = 204;
}
/* clock startup for LPC4320 configure PLL1 to max speed (204MHz).
Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. */
void cpu_clock_init(void) {
/* use IRC as clock source for APB1 (including I2C0) */
CGU_BASE_APB1_CLK = CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_IRC);
/* use IRC as clock source for APB3 */
CGU_BASE_APB3_CLK = CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_IRC);
// FIXME disable I2C
/* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */
i2c_bus_start(si5351c.bus, &i2c_config_fast_clock);
/*
* 12MHz clock is entering LPC XTAL1/OSC input now.
* On HackRF One and Jawbreaker, there is a 12 MHz crystal at the LPC.
* Set up PLL1 to run from XTAL1 input.
*/
// FIXME a lot of the details here should be in a CGU driver
/* set xtal oscillator to low frequency mode */
CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_HF_MASK;
cpu_clock_pll1_max_speed();
/* use XTAL_OSC as clock source for APB1 */
CGU_BASE_APB1_CLK =
CGU_BASE_APB1_CLK_AUTOBLOCK(1) | CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_XTAL);
/* use XTAL_OSC as clock source for APB3 */
CGU_BASE_APB3_CLK =
CGU_BASE_APB3_CLK_AUTOBLOCK(1) | CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_XTAL);
/* use XTAL_OSC as clock source for PLL0USB */
CGU_PLL0USB_CTRL = CGU_PLL0USB_CTRL_PD(1) | CGU_PLL0USB_CTRL_AUTOBLOCK(1) |
CGU_PLL0USB_CTRL_CLK_SEL(CGU_SRC_XTAL);
while (CGU_PLL0USB_STAT & CGU_PLL0USB_STAT_LOCK_MASK) {
}
/* configure PLL0USB to produce 480 MHz clock from 12 MHz XTAL_OSC */
/* Values from User Manual v1.4 Table 94, for 12MHz oscillator. */
CGU_PLL0USB_MDIV = 0x06167FFA;
CGU_PLL0USB_NP_DIV = 0x00302062;
CGU_PLL0USB_CTRL |=
(CGU_PLL0USB_CTRL_PD(1) | CGU_PLL0USB_CTRL_DIRECTI(1) |
CGU_PLL0USB_CTRL_DIRECTO(1) | CGU_PLL0USB_CTRL_CLKEN(1));
/* power on PLL0USB and wait until stable */
CGU_PLL0USB_CTRL &= ~CGU_PLL0USB_CTRL_PD_MASK;
while (!(CGU_PLL0USB_STAT & CGU_PLL0USB_STAT_LOCK_MASK)) {
}
/* use PLL0USB as clock source for USB0 */
CGU_BASE_USB0_CLK = CGU_BASE_USB0_CLK_AUTOBLOCK(1) |
CGU_BASE_USB0_CLK_CLK_SEL(CGU_SRC_PLL0USB);
/* Switch peripheral clock over to use PLL1 (204MHz) */
CGU_BASE_PERIPH_CLK = CGU_BASE_PERIPH_CLK_AUTOBLOCK(1) |
CGU_BASE_PERIPH_CLK_CLK_SEL(CGU_SRC_PLL1);
/* Switch APB1 clock over to use PLL1 (204MHz) */
CGU_BASE_APB1_CLK =
CGU_BASE_APB1_CLK_AUTOBLOCK(1) | CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_PLL1);
/* Switch APB3 clock over to use PLL1 (204MHz) */
CGU_BASE_APB3_CLK =
CGU_BASE_APB3_CLK_AUTOBLOCK(1) | CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_PLL1);
CGU_BASE_SSP0_CLK =
CGU_BASE_SSP0_CLK_AUTOBLOCK(1) | CGU_BASE_SSP0_CLK_CLK_SEL(CGU_SRC_PLL1);
CGU_BASE_SSP1_CLK =
CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1);
#ifdef IS_NOT_RAD1O
if (IS_NOT_RAD1O) {
/* Disable unused clocks */
/* Start with PLLs */
CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1);
/* Dividers */
CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1);
CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1);
CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1);
CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1);
CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1);
/* Base clocks */
CGU_BASE_SPIFI_CLK =
CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */
CGU_BASE_USB1_CLK =
CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */
CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1);
CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1);
CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1);
CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1);
CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(0) | CGU_BASE_SDIO_CLK_AUTOBLOCK(1) | CGU_BASE_SDIO_CLK_CLK_SEL(0x09);
CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1);
CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1);
CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1);
CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1);
CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1);
CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1);
CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1);
CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1);
/* Disable unused peripheral clocks */
CCU1_CLK_APB1_CAN1_CFG = 0;
CCU1_CLK_APB1_I2S_CFG = 0;
CCU1_CLK_APB1_MOTOCONPWM_CFG = 0;
// CCU1_CLK_APB3_ADC0_CFG = 0;
CCU1_CLK_APB3_ADC1_CFG = 0;
CCU1_CLK_APB3_CAN0_CFG = 0;
CCU1_CLK_APB3_DAC_CFG = 0;
// CCU1_CLK_M4_DMA_CFG = 0;
CCU1_CLK_M4_EMC_CFG = 0;
CCU1_CLK_M4_EMCDIV_CFG = 0;
CCU1_CLK_M4_ETHERNET_CFG = 0;
CCU1_CLK_M4_LCD_CFG = 0;
CCU1_CLK_M4_QEI_CFG = 0;
CCU1_CLK_M4_RITIMER_CFG = 0;
// CCU1_CLK_M4_SCT_CFG = 0;
// CCU1_CLK_M4_SDIO_CFG = 1;
CCU1_CLK_M4_SPIFI_CFG = 0;
CCU1_CLK_M4_TIMER0_CFG = 0;
// CCU1_CLK_M4_TIMER1_CFG = 0;
// CCU1_CLK_M4_TIMER2_CFG = 0;
CCU1_CLK_M4_TIMER3_CFG = 0;
CCU1_CLK_M4_UART1_CFG = 0;
CCU1_CLK_M4_USART0_CFG = 0;
CCU1_CLK_M4_USART2_CFG = 0;
CCU1_CLK_M4_USART3_CFG = 0;
CCU1_CLK_M4_USB1_CFG = 0;
CCU1_CLK_M4_VADC_CFG = 0;
// CCU1_CLK_SPIFI_CFG = 0;
// CCU1_CLK_USB1_CFG = 0;
// CCU1_CLK_VADC_CFG = 0;
// CCU2_CLK_APB0_UART1_CFG = 0;
// CCU2_CLK_APB0_USART0_CFG = 0;
// CCU2_CLK_APB2_USART2_CFG = 0;
// CCU2_CLK_APB2_USART3_CFG = 0;
// CCU2_CLK_APLL_CFG = 0;
// CCU2_CLK_SDIO_CFG = 0;
}
#endif
}
@@ -0,0 +1 @@
#pragma once
File diff suppressed because it is too large Load Diff
+254
View File
@@ -0,0 +1,254 @@
/*
* Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone <jared@sharebrained.com>
* Copyright 2013 Benjamin Vernoux <titanmkd@gmail.com>
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "pins.h"
#include <libopencm3/lpc43xx/scu.h>
#include "gpio.h"
#include "leds.h"
#include "platform_detect.h"
#include "platform_gpio.h"
#include "platform_scu.h"
#include "power.h"
#ifdef IS_PRALINE
#include "clock_io.h"
#endif
void pins_shutdown(void) {
/* Configure all GPIO as Input (safe state) */
// gpio_init();
/* Detect Platform */
const platform_gpio_t* gpio = platform_gpio();
const platform_scu_t* scu = platform_scu();
/* TDI and TMS pull-ups are required in all JTAG-compliant devices.
*
* The HackRF CPLD is always present, so let the CPLD pull up its TDI and TMS.
*
* The PortaPack may not be present, so pull up the PortaPack TMS pin from the
* microcontroller.
*
* TCK is recommended to be held low, so use microcontroller pull-down.
*
* TDO is undriven except when in Shift-IR or Shift-DR phases.
* Use the microcontroller to pull down to keep from floating.
*
* LPC43xx pull-up and pull-down resistors are approximately 53K.
*/
#ifdef IS_EXPANSION_COMPATIBLE
if (IS_EXPANSION_COMPATIBLE) {
scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
}
#endif
scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
#ifdef IS_NOT_PRALINE
if (IS_NOT_PRALINE) {
scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
}
#endif
/* Configure SCU Pin Mux as GPIO */
scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL);
scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL);
scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL);
#ifdef IS_RAD1O
if (IS_RAD1O) {
scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4);
}
#endif
#ifdef IS_PRALINE
if (IS_PRALINE) {
scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
}
#endif
/* Configure USB indicators */
#ifdef IS_JAWBREAKER
if (IS_JAWBREAKER) {
scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3);
scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3);
}
#endif
#ifdef IS_PRALINE
if (IS_PRALINE) {
// disable_1v2_power();
// disable_3v3aux_power();
// gpio_output(gpio->gpio_1v2_enable);
// gpio_output(gpio->gpio_3v3aux_enable_n);
// scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
// scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
}
#endif
#ifdef IS_NOT_PRALINE
if (IS_NOT_PRALINE) {
disable_1v8_power();
#ifdef IS_H1_R9
if (IS_H1_R9) {
gpio_output(gpio->h1r9_1v8_enable);
scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
}
#endif
#ifdef IS_NOT_H1_R9
if (IS_NOT_H1_R9) {
gpio_output(gpio->gpio_1v8_enable);
scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
}
#endif
}
#endif
#ifdef IS_H1_OR_PRALINE
if (IS_H1_OR_PRALINE) {
/* Safe state: start with VAA turned off: */
disable_rf_power();
/* Configure RF power supply (VAA) switch control signal as output */
#ifdef IS_H1_R9
if (IS_H1_R9) {
gpio_output(gpio->h1r9_vaa_disable);
}
#endif
#ifdef IS_NOT_H1_R9
if (IS_NOT_H1_R9) {
gpio_output(gpio->vaa_disable);
}
#endif
}
#endif
#ifdef IS_RAD1O
if (IS_RAD1O) {
/* Safe state: start with VAA turned off: */
disable_rf_power();
/* Configure RF power supply (VAA) switch control signal as output */
gpio_output(gpio->vaa_enable);
/* Disable unused clock outputs. They generate noise. */
scu_pinmux(CLK0, SCU_CLK_IN | SCU_CONF_FUNCTION7);
scu_pinmux(CLK2, SCU_CLK_IN | SCU_CONF_FUNCTION7);
scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
}
#endif
#ifdef IS_PRALINE
if (IS_PRALINE) {
scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG);
scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG);
scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG);
scu_pinmux(scu->P1_CTRL1, scu->P1_CTRL1_PINCFG);
scu_pinmux(scu->P1_CTRL2, scu->P1_CTRL2_PINCFG);
scu_pinmux(scu->CLKIN_CTRL, scu->CLKIN_CTRL_PINCFG);
scu_pinmux(scu->AA_EN, scu->AA_EN_PINCFG);
scu_pinmux(scu->TRIGGER_IN, scu->TRIGGER_IN_PINCFG);
scu_pinmux(scu->TRIGGER_OUT, scu->TRIGGER_OUT_PINCFG);
scu_pinmux(scu->PPS_OUT, scu->PPS_OUT_PINCFG);
scu_pinmux(scu->SCT_CLK, scu->SCT_CLK_PINCFG);
scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->SSP1_CIPO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->SSP1_COPI, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->SSP1_SCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION2);
scu_pinmux(scu->XCVR_ENABLE, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->XCVR_RXENABLE, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->XCVR_CS, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->XCVR_RXHP, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->XCVR_LD, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->MIXER_LD, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->MIXER_SCLK, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
scu_pinmux(scu->MIXER_SDATA, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->MIXER_ENX, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->MIXER_RESETX, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->MIXER_ENBL, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
scu_pinmux(scu->AD_CS, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
p2_ctrl_set(P2_SIGNAL_CLK3);
p1_ctrl_set(P1_SIGNAL_CLKIN);
clkin_ctrl_set(CLKIN_SIGNAL_P1);
gpio_output(gpio->p2_ctrl0);
gpio_output(gpio->p2_ctrl1);
gpio_output(gpio->p1_ctrl0);
gpio_output(gpio->p1_ctrl1);
gpio_output(gpio->p1_ctrl2);
gpio_output(gpio->clkin_ctrl);
gpio_output(gpio->pps_out);
gpio_input(gpio->trigger_in);
gpio_input(gpio->trigger_out);
gpio_clear(gpio->fpga_cfg_spi_cs);
gpio_output(gpio->fpga_cfg_spi_cs);
gpio_clear(gpio->fpga_cfg_creset);
gpio_output(gpio->fpga_cfg_creset);
gpio_input(gpio->fpga_cfg_cdone);
gpio_input(gpio->max5864_select);
}
#endif
/* enable input on SCL and SDA pins */
SCU_SFSI2C0 = SCU_I2C0_NOMINAL;
}
/* Run after pins_shutdown() and prior to enabling power supplies. */
void pins_setup(void) {
/* Detect Platform */
const platform_gpio_t* gpio = platform_gpio();
const platform_scu_t* scu = platform_scu();
/* Configure LEDs */
led_off(0);
led_off(1);
led_off(2);
#ifdef IS_FOUR_LEDS
if (IS_FOUR_LEDS) {
led_off(3);
}
#endif
gpio_output(gpio->led[0]);
gpio_output(gpio->led[1]);
gpio_output(gpio->led[2]);
#ifdef IS_FOUR_LEDS
if (IS_FOUR_LEDS) {
gpio_output(gpio->led[3]);
}
#endif
/* Configure external clock in */
scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1);
}
@@ -44,11 +44,11 @@ CH_IRQ_HANDLER(Vector60) {
}
int main() {
start_usb();
sdcStart(&SDCD1, nullptr);
if (sdcConnect(&SDCD1) == CH_FAILED) chDbgPanic("no sd card #1");
start_usb();
while (true) {
usb_transfer();
}
+145 -8
View File
@@ -23,14 +23,35 @@
#include "scsi.h"
#include "diskio.h"
#include "gpio_lpc.h"
#include "delay.h"
#include <libopencm3/lpc43xx/scu.h>
#include <libopencm3/lpc43xx/rgu.h>
#include <libopencm3/lpc43xx/wwdt.h>
#include "string.h"
/* Maximum number of 512-byte blocks transferred per SD command / USB bulk
* transfer. The data region of usb_bulk_buffer is 16 KiB (0x0000..0x3FFF),
* so this must be <= 32. It is split into two equal halves for double
* buffering. */
#define MAX_BLOCKS_PER_TRANSFER 32
#define HALF_BLOCKS (MAX_BLOCKS_PER_TRANSFER / 2)
volatile bool usb_bulk_block_done = false;
void delay(uint32_t duration);
/* Per-buffer-half completion flags for the asynchronous (double-buffered)
* bulk transfers. Index 0 = usb_bulk_buffer[0], index 1 = second half. */
volatile bool usb_bulk_block_done_async[2] = {false, false};
static uint32_t usb_bulk_buffer_index(const void* const data) {
return (data == &usb_bulk_buffer[HALF_BLOCKS * 512]) ? 1 : 0;
}
void usb_bulk_block_cb_async(void* user_data, unsigned int bytes_transferred) {
const uint32_t idx = (uint32_t)(uintptr_t)user_data;
usb_bulk_block_done_async[idx] = true;
(void)bytes_transferred;
}
void usb_bulk_block_cb(void* user_data, unsigned int bytes_transferred) {
usb_bulk_block_done = true;
@@ -65,6 +86,49 @@ void usb_receive_bulk(void* const data, const uint32_t maximum_length) {
while (!usb_bulk_block_done);
}
/* Schedule a bulk IN transfer without waiting for it to complete. The caller
* must later call usb_send_bulk_wait_finish() with the same buffer before
* reusing it. */
void usb_send_bulk_start(void* const data, const uint32_t maximum_length) {
const uint32_t idx = usb_bulk_buffer_index(data);
usb_bulk_block_done_async[idx] = false;
usb_transfer_schedule_block(
&usb_endpoint_bulk_in,
data,
maximum_length,
usb_bulk_block_cb_async,
(void*)(uintptr_t)idx);
}
/* Wait for a bulk IN transfer scheduled by usb_send_bulk_start() to finish. */
void usb_send_bulk_wait_finish(void* const data) {
const uint32_t idx = usb_bulk_buffer_index(data);
while (!usb_bulk_block_done_async[idx]);
}
/* Schedule a bulk OUT transfer without waiting for it to complete. The caller
* must later call usb_receive_bulk_finish() with the same buffer before
* reading from it. */
void usb_receive_bulk_start(void* const data, const uint32_t maximum_length) {
const uint32_t idx = usb_bulk_buffer_index(data);
usb_bulk_block_done_async[idx] = false;
usb_transfer_schedule_block(
&usb_endpoint_bulk_out,
data,
maximum_length,
usb_bulk_block_cb_async,
(void*)(uintptr_t)idx);
}
/* Wait for a bulk OUT transfer scheduled by usb_receive_bulk_start() to
* finish. */
void usb_receive_bulk_finish(void* const data) {
const uint32_t idx = usb_bulk_buffer_index(data);
while (!usb_bulk_block_done_async[idx]);
}
void usb_send_csw(msd_cbw_t* msd_cbw_data, uint8_t status) {
msd_csw_t csw = {
.signature = MSD_CSW_SIGNATURE,
@@ -213,22 +277,95 @@ static data_request_t decode_data_request(const uint8_t* cmd) {
uint8_t data_read10(msd_cbw_t* msd_cbw_data) {
data_request_t req = decode_data_request(msd_cbw_data->cmd_data);
for (size_t block_index = 0; block_index < req.blk_cnt; block_index++) {
read_block(req.first_lba + block_index, &usb_bulk_buffer[0], 1 /* n blocks */);
usb_send_bulk(&usb_bulk_buffer[0], 512);
uint32_t lba = req.first_lba;
uint32_t remaining = req.blk_cnt;
uint8_t* buf[2] = {&usb_bulk_buffer[0], &usb_bulk_buffer[HALF_BLOCKS * 512]};
uint32_t buf_idx = 0;
uint8_t* in_flight = NULL;
if (remaining == 0)
return 0;
/* Read the first chunk and start sending it. */
uint32_t n = (remaining > HALF_BLOCKS) ? HALF_BLOCKS : remaining;
if (read_block(lba, buf[0], n))
return 1;
usb_send_bulk_start(buf[0], n * 512);
in_flight = buf[0];
lba += n;
remaining -= n;
/* While USB sends the previous chunk, read the next one into the other
* half of the buffer. */
while (remaining > 0) {
buf_idx ^= 1;
n = (remaining > HALF_BLOCKS) ? HALF_BLOCKS : remaining;
if (read_block(lba, buf[buf_idx], n)) {
usb_send_bulk_wait_finish(in_flight);
return 1;
}
usb_send_bulk_wait_finish(in_flight);
usb_send_bulk_start(buf[buf_idx], n * 512);
in_flight = buf[buf_idx];
lba += n;
remaining -= n;
}
usb_send_bulk_wait_finish(in_flight);
return 0;
}
uint8_t data_write10(msd_cbw_t* msd_cbw_data) {
data_request_t req = decode_data_request(msd_cbw_data->cmd_data);
for (size_t block_index = 0; block_index < req.blk_cnt; block_index++) {
usb_receive_bulk(&usb_bulk_buffer[0], 512);
write_block(req.first_lba + block_index, &usb_bulk_buffer[0], 1 /* n blocks */);
uint32_t lba = req.first_lba;
uint32_t remaining = req.blk_cnt;
uint8_t* buf[2] = {&usb_bulk_buffer[0], &usb_bulk_buffer[HALF_BLOCKS * 512]};
uint32_t buf_idx = 0;
uint8_t* pending_buf = NULL;
uint32_t pending_lba = 0;
uint32_t pending_n = 0;
if (remaining == 0)
return 0;
/* Start receiving the first chunk. */
uint32_t n = (remaining > HALF_BLOCKS) ? HALF_BLOCKS : remaining;
usb_receive_bulk_start(buf[0], n * 512);
pending_buf = buf[0];
pending_lba = lba;
pending_n = n;
lba += n;
remaining -= n;
/* While USB receives the next chunk, write the previous one to the SD
* card. */
while (remaining > 0) {
buf_idx ^= 1;
n = (remaining > HALF_BLOCKS) ? HALF_BLOCKS : remaining;
usb_receive_bulk_start(buf[buf_idx], n * 512);
usb_receive_bulk_finish(pending_buf);
if (write_block(pending_lba, pending_buf, pending_n)) {
usb_receive_bulk_finish(buf[buf_idx]);
return 1;
}
pending_buf = buf[buf_idx];
pending_lba = lba;
pending_n = n;
lba += n;
remaining -= n;
}
usb_receive_bulk_finish(pending_buf);
if (write_block(pending_lba, pending_buf, pending_n))
return 1;
return 0;
}
@@ -293,7 +430,7 @@ void scsi_command(msd_cbw_t* msd_cbw_data) {
gpio_output(&dfu);
gpio_clear(&dfu);
delay(50 * 40800);
delay_ms(50);
RESET_CTRL0 = (1 << 0);
break;
+79 -7
View File
@@ -23,6 +23,8 @@
#include "sd_over_usb.h"
#include "scsi.h"
#include "usb_descriptor.h"
#include <rom_iap.h>
#include "delay.h"
#include <string.h>
@@ -36,8 +38,14 @@
extern usb_configuration_t* usb_configurations[];
static const usb_device_t usb_device_sd_over_usb = {
.descriptor = usb_descriptor_device,
.descriptor_strings = usb_descriptor_strings,
#ifdef IS_NOT_PRALINE
.descriptor = usb_descriptor_device_hackrf,
.descriptor_strings = usb_descriptor_strings_hackrf_one,
#endif
#ifdef IS_PRALINE
.descriptor = usb_descriptor_device_hackrf,
.descriptor_strings = usb_descriptor_strings_praline,
#endif
.qualifier_descriptor = usb_descriptor_device_qualifier,
.configurations = &usb_configurations,
.configuration = 0,
@@ -91,16 +99,83 @@ void usb_configuration_changed(usb_device_t* const device) {
usb_endpoint_init(&usb_endpoint_bulk_out, false);
}
void usb_set_descriptor_by_serial_number(void) {
iap_cmd_res_t iap_cmd_res;
/* Read IAP Serial Number Identification */
iap_cmd_res.cmd_param.command_code = IAP_CMD_READ_SERIAL_NO;
iap_cmd_call(&iap_cmd_res);
if (iap_cmd_res.status_res.status_ret == CMD_SUCCESS) {
usb_descriptor_string_serial_number[0] =
USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN;
usb_descriptor_string_serial_number[1] = USB_DESCRIPTOR_TYPE_STRING;
/* 32 characters of serial number, convert to UTF-16LE */
for (size_t i = 0; i < USB_DESCRIPTOR_STRING_SERIAL_LEN; i++) {
const uint_fast8_t nibble =
(iap_cmd_res.status_res.iap_result[i >> 3] >>
(28 - (i & 7) * 4)) &
0xf;
const char c =
(nibble > 9) ? ('a' + nibble - 10) : ('0' + nibble);
usb_descriptor_string_serial_number[2 + i * 2] = c;
usb_descriptor_string_serial_number[3 + i * 2] = 0x00;
}
} else {
usb_descriptor_string_serial_number[0] = 2;
usb_descriptor_string_serial_number[1] = USB_DESCRIPTOR_TYPE_STRING;
}
}
void start_usb(void) {
// Detect hardware platform before we do anything else.
detect_hardware_platform();
pin_setup();
board_id_t board_id = detected_platform();
pins_shutdown();
sgpio_pin_shutdown(&sgpio_config);
rf_path_pin_shutdown();
if (board_id != BOARD_ID_RAD1O) {
clock_gen_shutdown();
}
delay_ms(10);
pins_setup();
cpld_jtag_pin_setup();
cpu_clock_init();
memcpy(&usb_device, &usb_device_sd_over_usb, sizeof(usb_device_sd_over_usb));
#ifndef DFU_MODE
usb_set_descriptor_by_serial_number();
#endif
usb_set_configuration_changed_cb(usb_configuration_changed);
usb_peripheral_reset();
#ifdef IS_HACKRF_ONE
if (IS_HACKRF_ONE) {
memcpy(&usb_device,
&usb_device_sd_over_usb,
sizeof(usb_device_sd_over_usb));
}
#endif
#ifdef IS_JAWBREAKER
if (IS_JAWBREAKER) {
memcpy(&usb_device,
&usb_device_jawbreaker,
sizeof(usb_device_jawbreaker));
}
#endif
#ifdef IS_RAD1O
if (IS_RAD1O) {
memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o));
}
#endif
#ifdef IS_PRALINE
if (IS_PRALINE) {
memcpy(&usb_device, &usb_device_sd_over_usb, sizeof(usb_device_sd_over_usb));
}
#endif
usb_device_init(0, &usb_device);
usb_queue_init(&usb_endpoint_control_out_queue);
@@ -109,9 +184,6 @@ void start_usb(void) {
usb_queue_init(&usb_endpoint_bulk_in_queue);
usb_endpoint_init(&usb_endpoint_control_out, false);
/* Match the new usb_endpoint_init() contract introduced upstream by
* db73ecbf, control IN needs ZLP for transfers whose length is a
* multiple of the EP0 max packet size, otherwise the host hangs. */
usb_endpoint_init(&usb_endpoint_control_in, true);
nvic_set_priority(NVIC_USB0_IRQ, 255);
@@ -0,0 +1,766 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
* Copyright 2013 Benjamin Vernoux
* Copyright 2024 Bernd Herzog
*
* This file is part of HackRF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "usb_api_transceiver.h"
#include <stdbool.h>
#include <stddef.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/lpc43xx/gpdma.h>
#include <libopencm3/lpc43xx/usb.h>
#include <clock_gen.h>
#include <fixed_point.h>
#include <gpdma.h>
#include <hackrf_ui.h>
#include <leds.h>
#include <m0_state.h>
#include <operacake_sctimer.h>
#include <platform_detect.h>
#include <radio.h>
#include <sgpio.h>
#include <streaming.h>
#include <transceiver_mode.h>
#include "common/usb.h"
#include <usb_queue.h>
#include <usb_request.h>
#include <usb_type.h>
#include "usb_buffer.h"
#include "usb_endpoint.h"
#define USB_TRANSFER_SIZE 0x4000
#define DMA_TRANSFER_SIZE 0x2000
#define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1)
// Unless we know the host knows our buffer size, we'll avoid leaving TX
// until we've transmitted all bytes sent by the host. This flag is cleared
// when the host requests our buffer size.
bool auto_tx_flush = true;
volatile uint32_t dma_started, dma_pending, usb_started, usb_completed;
typedef struct {
uint32_t freq_mhz;
uint32_t freq_hz;
} set_freq_params_t;
set_freq_params_t set_freq_params;
struct set_freq_explicit_params {
uint64_t if_freq_hz; /* intermediate frequency */
uint64_t lo_freq_hz; /* front-end local oscillator frequency */
uint8_t path; /* image rejection filter path */
};
struct set_freq_explicit_params explicit_params;
typedef struct {
uint32_t freq_hz;
uint32_t divider;
} set_sample_r_params_t;
set_sample_r_params_t set_sample_r_params;
void transceiver_dma_setup(void);
usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
const uint32_t bandwidth =
(endpoint->setup.index << 16) | endpoint->setup.value;
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_BB_BANDWIDTH_TX,
bandwidth);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_BB_BANDWIDTH_RX,
bandwidth);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_freq(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&set_freq_params,
sizeof(set_freq_params_t),
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
const uint64_t freq =
set_freq_params.freq_mhz * 1000000ULL + set_freq_params.freq_hz;
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_FREQUENCY_RF,
freq * FP_ONE_HZ);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_FREQUENCY_IF,
RADIO_UNSET);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_FREQUENCY_LO,
RADIO_UNSET);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_IMAGE_REJECT,
RADIO_UNSET);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_freq_explicit(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&explicit_params,
sizeof(struct set_freq_explicit_params),
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_FREQUENCY_IF,
explicit_params.if_freq_hz * FP_ONE_HZ);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_FREQUENCY_LO,
explicit_params.lo_freq_hz * FP_ONE_HZ);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_IMAGE_REJECT,
explicit_params.path);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
/*
* Convert fractional sample rate to units of 1/(2**36) Hz.
*/
static inline fp_28_36_t round_sample_rate(uint64_t num, uint32_t denom) {
uint64_t q1, r1, q2, r2, q3;
if (denom == 0) {
denom = 1;
}
q1 = num / denom;
r1 = num % denom;
q2 = (r1 << 32) / denom;
r2 = (r1 << 32) % denom;
q3 = ((r2 << 4) + (denom >> 1)) / denom;
return (q1 << 36) + (q2 << 4) + q3;
}
usb_request_status_t usb_vendor_request_set_sample_rate_frac(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&set_sample_r_params,
sizeof(set_sample_r_params_t),
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
uint32_t numerator = set_sample_r_params.freq_hz;
uint32_t denominator = set_sample_r_params.divider;
uint64_t value = round_sample_rate(numerator, denominator);
radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_SAMPLE_RATE, value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_amp_enable(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_GAIN_TX_RF,
endpoint->setup.value);
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_GAIN_RX_RF,
endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_lna_gain(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t gain = endpoint->setup.index;
radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_GAIN_RX_IF, gain);
endpoint->buffer[0] = RADIO_OK;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
1,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_vga_gain(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t gain = endpoint->setup.index;
radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_GAIN_RX_BB, gain);
endpoint->buffer[0] = RADIO_OK;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
1,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_txvga_gain(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t gain = endpoint->setup.index;
radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_GAIN_TX_IF, gain);
endpoint->buffer[0] = RADIO_OK;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
1,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_antenna_enable(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
switch (detected_platform()) {
case BOARD_ID_HACKRF1_OG:
case BOARD_ID_HACKRF1_R9:
case BOARD_ID_PRALINE:
// supported
break;
default:
return USB_REQUEST_STATUS_STALL;
}
if (stage == USB_TRANSFER_STAGE_SETUP) {
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_BIAS_TEE,
endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
static volatile uint32_t _tx_underrun_limit;
static volatile uint32_t _rx_overrun_limit;
volatile transceiver_request_t transceiver_request = {
.mode = TRANSCEIVER_MODE_OFF,
.seq = 0,
};
void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint) {
if (transceiver_request.mode == TRANSCEIVER_MODE_TX &&
endpoint->setup.request == 1 && auto_tx_flush) {
// This is a request to leave TX mode. Do so but NAK for now.
request_transceiver_mode(endpoint->setup.value);
} else {
usb_setup_complete(endpoint);
}
}
// Must be called from an atomic context (normally USB ISR)
void request_transceiver_mode(transceiver_mode_t mode) {
usb_endpoint_flush(&usb_endpoint_bulk_in);
usb_endpoint_flush(&usb_endpoint_bulk_out);
transceiver_request.mode = mode;
transceiver_request.seq++;
}
void transceiver_shutdown(void) {
baseband_streaming_disable(&sgpio_config);
operacake_sctimer_reset_state();
usb_endpoint_flush(&usb_endpoint_bulk_in);
usb_endpoint_flush(&usb_endpoint_bulk_out);
led_off(LED2);
led_off(LED3);
radio_switch_opmode(&radio, TRANSCEIVER_MODE_OFF);
m0_set_mode(M0_MODE_IDLE);
}
void transceiver_startup(const transceiver_mode_t mode) {
dma_started = 0;
dma_pending = 0;
usb_started = 0;
usb_completed = 0;
transceiver_dma_setup();
radio_switch_opmode(&radio, mode);
switch (mode) {
case TRANSCEIVER_MODE_RX_SWEEP:
case TRANSCEIVER_MODE_RX:
led_off(LED3);
led_on(LED2);
m0_set_mode(M0_MODE_RX);
m0_state.shortfall_limit = _rx_overrun_limit;
break;
case TRANSCEIVER_MODE_TX:
led_off(LED2);
led_on(LED3);
m0_set_mode(M0_MODE_TX_START);
m0_state.shortfall_limit = _tx_underrun_limit;
break;
default:
break;
}
activate_best_clock_source();
}
usb_request_status_t usb_vendor_request_set_transceiver_mode(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
switch (endpoint->setup.value) {
case TRANSCEIVER_MODE_OFF:
case TRANSCEIVER_MODE_RX:
case TRANSCEIVER_MODE_TX:
case TRANSCEIVER_MODE_RX_SWEEP:
case TRANSCEIVER_MODE_CPLD_UPDATE:
request_transceiver_mode(endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
return USB_REQUEST_STATUS_OK;
default:
return USB_REQUEST_STATUS_STALL;
}
} else {
return USB_REQUEST_STATUS_OK;
}
}
usb_request_status_t usb_vendor_request_set_hw_sync_mode(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
radio_reg_write(
&radio,
RADIO_BANK_REQUESTED,
RADIO_TRIGGER,
endpoint->setup.value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_tx_underrun_limit(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint32_t value = (endpoint->setup.index << 16) + endpoint->setup.value;
_tx_underrun_limit = value;
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_set_rx_overrun_limit(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint32_t value = (endpoint->setup.index << 16) + endpoint->setup.value;
_rx_overrun_limit = value;
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_get_buffer_size(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage) {
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint32_t value = USB_SAMP_BUFFER_SIZE + USB_BULK_BUFFER_SIZE;
endpoint->buffer[0] = value & 0xff;
endpoint->buffer[1] = (value & 0xff00) >> 8;
endpoint->buffer[2] = (value & 0xff0000) >> 16;
endpoint->buffer[3] = (value & 0xff000000) >> 24;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
4,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
// We now know the host is aware of our buffer size, so it
// can make its own decisions about flushing the buffer.
auto_tx_flush = false;
return USB_REQUEST_STATUS_OK;
}
return USB_REQUEST_STATUS_OK;
}
/* clang-format off */
// Which GPDMA channel to use.
const uint32_t DMA_CHANNEL = 1;
// GPDMA CCONFIG register setting.
const uint32_t DMA_CONFIG =
GPDMA_CCONFIG_FLOWCNTRL(0) // memory-to-memory
| GPDMA_CCONFIG_IE(0) // no error interrupt
| GPDMA_CCONFIG_ITC(1) // terminal count interrupt
| GPDMA_CCONFIG_L(0) // do not lock
| GPDMA_CCONFIG_H(0); // do not halt
// GPDMA CCONTROL register setting (excluding TRANSFERSIZE field).
const uint32_t DMA_CONTROL =
GPDMA_CCONTROL_SBSIZE(7) // 256-transfer src bursts
| GPDMA_CCONTROL_DBSIZE(7) // 256-transfer dst bursts
| GPDMA_CCONTROL_SWIDTH(2) // 32-bit src transfers
| GPDMA_CCONTROL_DWIDTH(2) // 32-bit dst transfers
| GPDMA_CCONTROL_S(0) // AHB Master 0
| GPDMA_CCONTROL_D(1) // AHB Master 1
| GPDMA_CCONTROL_SI(1) // increment source
| GPDMA_CCONTROL_DI(1) // increment destination
| GPDMA_CCONTROL_PROT1(0) // user mode
| GPDMA_CCONTROL_PROT2(0) // not bufferable
| GPDMA_CCONTROL_PROT3(0) // not cacheable
| GPDMA_CCONTROL_I(1); // interrupt enabled
/* clang-format on */
// Called before any sequence of DMA transfers.
void transceiver_dma_setup(void) {
gpdma_controller_enable();
GPDMA_CCONFIG(DMA_CHANNEL) = DMA_CONFIG;
GPDMA_CCONTROL(DMA_CHANNEL) = DMA_CONTROL;
GPDMA_CLLI(DMA_CHANNEL) = 0;
GPDMA_INTTCCLEAR = (1 << DMA_CHANNEL);
nvic_enable_irq(NVIC_DMA_IRQ);
}
// Called to start each DMA transfer.
void transceiver_start_dma(void* src, void* dest, size_t size) {
uint32_t num_transfers = size >> 2;
GPDMA_CCONTROL(DMA_CHANNEL) = DMA_CONTROL | num_transfers;
GPDMA_CSRCADDR(DMA_CHANNEL) = (uint32_t)src;
GPDMA_CDESTADDR(DMA_CHANNEL) = (uint32_t)dest;
dma_pending = size;
gpdma_channel_enable(DMA_CHANNEL);
}
// Called when a DMA transfer completes.
void dma_isr(void) {
gpdma_channel_disable(DMA_CHANNEL);
GPDMA_INTTCCLEAR = (1 << DMA_CHANNEL);
m0_state.m4_count += dma_pending;
dma_pending = 0;
}
void transceiver_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
(void)user_data;
usb_completed += bytes_transferred;
}
typedef enum {
DIRECTION_RX,
DIRECTION_TX,
} direction_t;
void start_dma_if_possible(direction_t direction, size_t size) {
if (dma_pending) {
return;
}
uint32_t sampling_completed = m0_state.m0_count;
uint32_t dma_completed = m0_state.m4_count;
uint32_t samp_offset = dma_started & USB_SAMP_BUFFER_MASK;
uint32_t bulk_offset = dma_started & USB_BULK_BUFFER_MASK;
uint32_t data_available, space_in_use, space_available, samp_buf_margin;
uint8_t *dest, *src;
if (direction == DIRECTION_RX) {
data_available = sampling_completed - dma_started;
space_in_use = usb_completed - dma_completed;
space_available = USB_BULK_BUFFER_SIZE - space_in_use;
samp_buf_margin = USB_SAMP_BUFFER_SIZE - data_available;
src = &usb_samp_buffer[samp_offset];
dest = &usb_bulk_buffer[bulk_offset];
} else {
data_available = usb_completed - dma_started;
space_in_use = dma_completed - sampling_completed;
space_available = USB_SAMP_BUFFER_SIZE - space_in_use;
samp_buf_margin = space_in_use;
src = &usb_bulk_buffer[bulk_offset];
dest = &usb_samp_buffer[samp_offset];
}
if (data_available < size || size > space_available) {
return;
}
uint32_t m0_buf_half = sampling_completed & BUF_HALF_MASK;
uint32_t dma_buf_half = dma_started & BUF_HALF_MASK;
bool same_buf_half = m0_buf_half == dma_buf_half;
if (same_buf_half && samp_buf_margin >= (USB_SAMP_BUFFER_SIZE / 2)) {
return;
}
transceiver_start_dma(src, dest, size);
dma_started += size;
}
void start_usb_if_possible(direction_t direction) {
uint32_t bulk_offset = usb_started & USB_BULK_BUFFER_MASK;
uint32_t dma_completed = m0_state.m4_count;
uint32_t bytes_available;
usb_endpoint_t* usb_endpoint;
if (direction == DIRECTION_RX) {
bytes_available = dma_completed - usb_started;
usb_endpoint = &usb_endpoint_bulk_in;
} else {
uint32_t space_used = usb_started - dma_completed;
bytes_available = USB_BULK_BUFFER_SIZE - space_used;
usb_endpoint = &usb_endpoint_bulk_out;
}
if (bytes_available < USB_TRANSFER_SIZE) {
return;
}
usb_transfer_schedule_block(
usb_endpoint,
&usb_bulk_buffer[bulk_offset],
USB_TRANSFER_SIZE,
transceiver_bulk_transfer_complete,
NULL);
usb_started += USB_TRANSFER_SIZE;
}
int8_t saturation_buffer = 0;
uint64_t saturation_buffer_time = 0;
volatile uint64_t systick_counter = 0;
void sys_tick_handler(void) {
systick_counter++;
}
void rx_mode(uint32_t seq) {
transceiver_startup(TRANSCEIVER_MODE_RX);
baseband_streaming_enable(&sgpio_config);
while (transceiver_request.seq == seq) {
start_dma_if_possible(DIRECTION_RX, DMA_TRANSFER_SIZE);
start_usb_if_possible(DIRECTION_RX);
int8_t sample_value = *(
int8_t*)&usb_samp_buffer[m0_state.m0_count & USB_SAMP_BUFFER_MASK];
if (sample_value > saturation_buffer)
saturation_buffer = sample_value;
if (-sample_value > saturation_buffer)
saturation_buffer = -sample_value;
if (saturation_buffer_time + 4 < systick_counter) {
saturation_buffer_time = systick_counter;
hackrf_ui()->set_saturation(saturation_buffer);
saturation_buffer = 0;
}
radio_update(&radio);
}
transceiver_shutdown();
}
void tx_mode(uint32_t seq) {
transceiver_startup(TRANSCEIVER_MODE_TX);
// First, make transfers directly into the sample buffer to fill it.
for (int i = 0; i < (USB_SAMP_BUFFER_SIZE / USB_TRANSFER_SIZE); i++) {
// Set up transfer.
usb_transfer_schedule_block(
&usb_endpoint_bulk_out,
&usb_samp_buffer[usb_started],
USB_TRANSFER_SIZE,
transceiver_bulk_transfer_complete,
NULL);
usb_started += USB_TRANSFER_SIZE;
// Wait for the transfer to complete.
while (usb_completed < usb_started) {
// Handle the host switching modes before filling the buffer.
if (transceiver_request.seq != seq) {
transceiver_shutdown();
return;
}
radio_update(&radio);
}
}
// Sample buffer is now full. Update DMA counters accordingly.
dma_started = USB_SAMP_BUFFER_SIZE;
m0_state.m4_count = USB_SAMP_BUFFER_SIZE;
// Start transmitting samples.
baseband_streaming_enable(&sgpio_config);
// Continue feeding samples to the sample buffer.
while (transceiver_request.seq == seq) {
start_dma_if_possible(DIRECTION_TX, DMA_TRANSFER_SIZE);
start_usb_if_possible(DIRECTION_TX);
radio_update(&radio);
}
// Host has now requested to stop TX. If we're not auto-flushing, we
// should now stop TX immediately.
if (!auto_tx_flush) {
transceiver_shutdown();
return;
}
// Otherwise, we should now ensure all bytes sent by the host are
// transmitted before we leave TX. First, we should make sure all data
// currently in the USB bulk buffer reaches the sample buffer.
if ((usb_started - usb_completed) > 0) {
// We were part way through a 16KB firmware-side transfer when
// the transceiver mode change request to stop TX was received.
//
// We want to include the contents of that partial transfer in
// the data we move to the sample buffer.
//
// The transfer was already stopped by usb_endpoint_flush(),
// which was called from request_transceiver_mode().
//
// We will not have had a callback, and the transfer descriptor
// (dTD) will not have been updated, since the transfer did not
// complete.
//
// However, as long as we haven't started a new transfer, we
// can retrieve the partial byte count from the transfer
// overlay in the endpoint queue head (dQH) (UM10503 25.9.1).
usb_queue_head_t* const qh =
usb_queue_head(usb_endpoint_bulk_out.address);
unsigned int bytes_remaining =
(qh->total_bytes & USB_TD_DTD_TOKEN_TOTAL_BYTES_MASK) >>
USB_TD_DTD_TOKEN_TOTAL_BYTES_SHIFT;
unsigned int bytes_transferred = USB_TRANSFER_SIZE - bytes_remaining;
usb_completed += bytes_transferred;
}
// Feed the remaining data from the bulk buffer to the sample buffer.
// At this point, we also need to handle the case where there is less data
// to be transferred to the sample buffer than a full-sized DMA transfer.
// Any remainder of less than 4 bytes will be ignored; this is the chunk
// size of our DMA transfers.
while ((usb_completed - m0_state.m4_count) >= 4) {
uint32_t data_available = usb_completed - dma_started;
if (data_available > DMA_TRANSFER_SIZE) {
start_dma_if_possible(DIRECTION_TX, DMA_TRANSFER_SIZE);
} else {
start_dma_if_possible(DIRECTION_TX, data_available);
}
radio_update(&radio);
}
// Wait for the data in the sample buffer to be transmitted.
// Any remainder of less than 32 bytes will be ignored; this is
// the chunk size used by the M0 core to transfer samples to SGPIO.
while ((m0_state.m4_count - m0_state.m0_count) >= 32) {
radio_update(&radio);
}
// All data received from the host has now been transmitted.
// Now we can ACK the control request that took us out of TX mode.
usb_transfer_schedule_ack(usb_endpoint_control_in.in);
transceiver_shutdown();
}
void off_mode(uint32_t seq) {
while (transceiver_request.seq == seq) {
radio_update(&radio);
}
}
+233 -153
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
*
* This file is part of HackRF.
@@ -20,22 +20,13 @@
* Boston, MA 02110-1301, USA.
*/
#include <stdint.h>
#include "usb_type.h"
#include "usb_descriptor.h"
#define USB_VENDOR_ID (0x0781) /* SanDisk Corp. */
#include <stdint.h>
#ifdef HACKRF_ONE
#define USB_PRODUCT_ID (0xa7a8) /* SD card reader */
#elif JAWBREAKER
#define USB_PRODUCT_ID (0x604B)
#elif RAD1O
#define USB_PRODUCT_ID (0xCC15)
#else
#define USB_PRODUCT_ID (0xFFFF)
#endif
#include <usb_type.h>
#define USB_VENDOR_ID (0x0781) /* SanDisk Corp. */
#define USB_API_VERSION (0x0127) /* hardware revision */
@@ -50,7 +41,8 @@
#define USB_STRING_LANGID (0x0409)
uint8_t usb_descriptor_device[] = {
#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE)
uint8_t usb_descriptor_device_hackrf[] = {
18, // bLength
USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType
USB_WORD(0x0200), // bcdUSB
@@ -59,13 +51,50 @@ uint8_t usb_descriptor_device[] = {
0x00, // bDeviceProtocol
USB_MAX_PACKET0, // bMaxPacketSize0
USB_WORD(USB_VENDOR_ID), // idVendor
USB_WORD(USB_PRODUCT_ID), // idProduct
USB_WORD(0xa7a8), // idProduct /* SD card reader */
USB_WORD(USB_API_VERSION), // bcdDevice
0x01, // iManufacturer
0x02, // iProduct
0x04, // iSerialNumber
0x01 // bNumConfigurations
};
#endif
#ifdef IS_JAWBREAKER
uint8_t usb_descriptor_device_jawbreaker[] = {
18, // bLength
USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType
USB_WORD(0x0200), // bcdUSB
0x00, // bDeviceClass
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
USB_MAX_PACKET0, // bMaxPacketSize0
USB_WORD(USB_VENDOR_ID), // idVendor
USB_WORD(0x604B), // idProduct
USB_WORD(USB_API_VERSION), // bcdDevice
0x01, // iManufacturer
0x02, // iProduct
0x04, // iSerialNumber
0x01 // bNumConfigurations
};
#endif
#ifdef IS_RAD1O
uint8_t usb_descriptor_device_rad1o[] = {
18, // bLength
USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType
USB_WORD(0x0200), // bcdUSB
0x00, // bDeviceClass
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
USB_MAX_PACKET0, // bMaxPacketSize0
USB_WORD(USB_VENDOR_ID), // idVendor
USB_WORD(0xCC15), // idProduct
USB_WORD(USB_API_VERSION), // bcdDevice
0x01, // iManufacturer
0x02, // iProduct
0x04, // iSerialNumber
0x01 // bNumConfigurations
};
#endif
uint8_t usb_descriptor_device_qualifier[] = {
10, // bLength
@@ -161,162 +190,213 @@ uint8_t usb_descriptor_string_languages[] = {
// clang-format off
uint8_t usb_descriptor_string_manufacturer[] = {
40, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'G', 0x00,
'r', 0x00,
'e', 0x00,
'a', 0x00,
't', 0x00,
' ', 0x00,
'S', 0x00,
'c', 0x00,
'o', 0x00,
't', 0x00,
't', 0x00,
' ', 0x00,
'G', 0x00,
'a', 0x00,
'd', 0x00,
'g', 0x00,
'e', 0x00,
't', 0x00,
's', 0x00,
40, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'G', 0x00,
'r', 0x00,
'e', 0x00,
'a', 0x00,
't', 0x00,
' ', 0x00,
'S', 0x00,
'c', 0x00,
'o', 0x00,
't', 0x00,
't', 0x00,
' ', 0x00,
'G', 0x00,
'a', 0x00,
'd', 0x00,
'g', 0x00,
'e', 0x00,
't', 0x00,
's', 0x00,
};
uint8_t usb_descriptor_string_product[] = {
#ifdef HACKRF_ONE
34, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'P', 0x00,
'o', 0x00,
'r', 0x00,
't', 0x00,
'a', 0x00,
'P', 0x00,
'a', 0x00,
'c', 0x00,
'k', 0x00,
' ', 0x00,
#ifdef IS_HACKRF_ONE
uint8_t usb_descriptor_string_product_hackrf_one[] = {
34, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'P', 0x00,
'o', 0x00,
'r', 0x00,
't', 0x00,
'a', 0x00,
'P', 0x00,
'a', 0x00,
'c', 0x00,
'k', 0x00,
' ', 0x00,
'M', 0x00,
'a', 0x00,
'y', 0x00,
'h', 0x00,
'e', 0x00,
'm', 0x00,
#elif JAWBREAKER
36, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'H', 0x00,
'a', 0x00,
'c', 0x00,
'k', 0x00,
'R', 0x00,
'F', 0x00,
' ', 0x00,
'J', 0x00,
'a', 0x00,
'w', 0x00,
'b', 0x00,
'r', 0x00,
'e', 0x00,
'a', 0x00,
'k', 0x00,
'e', 0x00,
'r', 0x00,
#elif RAD1O
12, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'r', 0x00,
'a', 0x00,
'd', 0x00,
'1', 0x00,
'o', 0x00,
#else
14, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'H', 0x00,
'a', 0x00,
'c', 0x00,
'k', 0x00,
'R', 0x00,
'F', 0x00,
'M', 0x00,
'a', 0x00,
'y', 0x00,
'h', 0x00,
'e', 0x00,
'm', 0x00,
};
#endif
#ifdef IS_PRALINE
uint8_t usb_descriptor_string_product_praline[] = {
34, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'P', 0x00,
'o', 0x00,
'r', 0x00,
't', 0x00,
'a', 0x00,
'P', 0x00,
'a', 0x00,
'c', 0x00,
'k', 0x00,
' ', 0x00,
'M', 0x00,
'a', 0x00,
'y', 0x00,
'h', 0x00,
'e', 0x00,
'm', 0x00,
};
#endif
#ifdef IS_JAWBREAKER
uint8_t usb_descriptor_string_product_jawbreaker[] = {
36, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'H', 0x00,
'a', 0x00,
'c', 0x00,
'k', 0x00,
'R', 0x00,
'F', 0x00,
' ', 0x00,
'J', 0x00,
'a', 0x00,
'w', 0x00,
'b', 0x00,
'r', 0x00,
'e', 0x00,
'a', 0x00,
'k', 0x00,
'e', 0x00,
'r', 0x00,
};
#endif
#ifdef IS_RAD1O
uint8_t usb_descriptor_string_product_rad1o[] = {
12, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'r', 0x00,
'a', 0x00,
'd', 0x00,
'1', 0x00,
'o', 0x00,
};
#endif
uint8_t usb_descriptor_string_config_description[] = {
24, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'T', 0x00,
'r', 0x00,
'a', 0x00,
'n', 0x00,
's', 0x00,
'c', 0x00,
'e', 0x00,
'i', 0x00,
'v', 0x00,
'e', 0x00,
'r', 0x00,
24, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'T', 0x00,
'r', 0x00,
'a', 0x00,
'n', 0x00,
's', 0x00,
'c', 0x00,
'e', 0x00,
'i', 0x00,
'v', 0x00,
'e', 0x00,
'r', 0x00,
};
#ifdef DFU_MODE
uint8_t usb_descriptor_string_serial_number[] = {
30, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'R', 0x00,
'u', 0x00,
'n', 0x00,
'n', 0x00,
'i', 0x00,
'n', 0x00,
'g', 0x00,
'F', 0x00,
'r', 0x00,
'o', 0x00,
'm', 0x00,
'R', 0x00,
'A', 0x00,
'M', 0x00,
30, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'R', 0x00,
'u', 0x00,
'n', 0x00,
'n', 0x00,
'i', 0x00,
'n', 0x00,
'g', 0x00,
'F', 0x00,
'r', 0x00,
'o', 0x00,
'm', 0x00,
'R', 0x00,
'A', 0x00,
'M', 0x00,
};
#else
uint8_t usb_descriptor_string_serial_number[USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN];
#endif
uint8_t* usb_descriptor_strings[] = {
usb_descriptor_string_languages,
usb_descriptor_string_manufacturer,
usb_descriptor_string_product,
usb_descriptor_string_config_description,
usb_descriptor_string_serial_number,
0, // TERMINATOR
#ifdef IS_HACKRF_ONE
uint8_t* usb_descriptor_strings_hackrf_one[] = {
usb_descriptor_string_languages,
usb_descriptor_string_manufacturer,
usb_descriptor_string_product_hackrf_one,
usb_descriptor_string_config_description,
usb_descriptor_string_serial_number,
0, // TERMINATOR
};
#endif
#ifdef IS_PRALINE
uint8_t* usb_descriptor_strings_praline[] = {
usb_descriptor_string_languages,
usb_descriptor_string_manufacturer,
usb_descriptor_string_product_praline,
usb_descriptor_string_config_description,
usb_descriptor_string_serial_number,
0, // TERMINATOR
};
#endif
#ifdef IS_JAWBREAKER
uint8_t* usb_descriptor_strings_jawbreaker[] = {
usb_descriptor_string_languages,
usb_descriptor_string_manufacturer,
usb_descriptor_string_product_jawbreaker,
usb_descriptor_string_config_description,
usb_descriptor_string_serial_number,
0, // TERMINATOR
};
#endif
#ifdef IS_RAD1O
uint8_t* usb_descriptor_strings_rad1o[] = {
usb_descriptor_string_languages,
usb_descriptor_string_manufacturer,
usb_descriptor_string_product_rad1o,
usb_descriptor_string_config_description,
usb_descriptor_string_serial_number,
0, // TERMINATOR
};
#endif
uint8_t wcid_string_descriptor[] = {
18, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'M', 0x00,
'S', 0x00,
'F', 0x00,
'T', 0x00,
'1', 0x00,
'0', 0x00,
'0', 0x00,
USB_WCID_VENDOR_REQ, // vendor request code for further descriptor
0x00
18, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'M', 0x00,
'S', 0x00,
'F', 0x00,
'T', 0x00,
'1', 0x00,
'0', 0x00,
'0', 0x00,
USB_WCID_VENDOR_REQ, // vendor request code for further descriptor
0x00
};
uint8_t wcid_feature_descriptor[] = {
0x28, 0x00, 0x00, 0x00, // bLength
USB_WORD(0x0100), // WCID version
USB_WORD(0x0004), // WICD descriptor index
0x01, // bNumSections
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Reserved
0x00, // bInterfaceNumber
0x01, // Reserved
'W', 'I', 'N', 'U', 'S', 'B', 0x00,0x00, // Compatible ID, padded with zeros
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Sub-compatible ID
0x00,0x00,0x00,0x00,0x00,0x00 // Reserved
};
0x28, 0x00, 0x00, 0x00, // bLength
USB_WORD(0x0100), // WCID version
USB_WORD(0x0004), // WICD descriptor index
0x01, // bNumSections
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Reserved
0x00, // bInterfaceNumber
0x01, // Reserved
'W', 'I', 'N', 'U', 'S', 'B', 0x00,0x00, // Compatible ID, padded with zeros
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Sub-compatible ID
0x00,0x00,0x00,0x00,0x00,0x00 // Reserved
};
+27 -3
View File
@@ -20,22 +20,46 @@
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <stdint.h>
extern uint8_t usb_descriptor_device[];
#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE)
extern uint8_t usb_descriptor_device_hackrf[];
#endif
#ifdef IS_JAWBREAKER
extern uint8_t usb_descriptor_device_jawbreaker[];
#endif
#ifdef IS_RAD1O
extern uint8_t usb_descriptor_device_rad1o[];
#endif
extern uint8_t usb_descriptor_device_qualifier[];
extern uint8_t usb_descriptor_configuration_full_speed[];
extern uint8_t usb_descriptor_configuration_high_speed[];
extern uint8_t usb_descriptor_string_languages[];
extern uint8_t usb_descriptor_string_manufacturer[];
extern uint8_t usb_descriptor_string_product[];
#ifdef IS_HACKRF_ONE
extern uint8_t usb_descriptor_string_product_hackrf_one[];
#endif
#ifdef IS_PRALINE
extern uint8_t usb_descriptor_string_product_praline[];
#endif
#ifdef IS_JAWBREAKER
extern uint8_t usb_descriptor_string_product_jawbreaker[];
#endif
#ifdef IS_RAD1O
extern uint8_t usb_descriptor_string_product_rad1o[];
#endif
#define USB_DESCRIPTOR_STRING_SERIAL_LEN 32
#define USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN \
(USB_DESCRIPTOR_STRING_SERIAL_LEN * 2 + 2) /* UTF-16LE */
extern uint8_t usb_descriptor_string_serial_number[];
extern uint8_t* usb_descriptor_strings[];
extern uint8_t* usb_descriptor_strings_hackrf_one[];
extern uint8_t* usb_descriptor_strings_jawbreaker[];
extern uint8_t* usb_descriptor_strings_rad1o[];
extern uint8_t* usb_descriptor_strings_praline[];
#define USB_WCID_VENDOR_REQ 0x19
extern uint8_t wcid_string_descriptor[];
+21 -7
View File
@@ -73,21 +73,34 @@ void SpectrumCollector::set_decimation_factor(
* perform the deferred task on the buffer of data we prepared.
*/
void SpectrumCollector::feed(
bool SpectrumCollector::feed(
const buffer_c16_t& channel,
const int32_t filter_low_frequency,
const int32_t filter_high_frequency,
const int32_t filter_transition) {
// Called from baseband processing thread.
set_filter(
filter_low_frequency,
filter_high_frequency,
filter_transition);
bool block_completed = false;
channel_spectrum_decimator.feed(
channel,
[this, &block_completed](const buffer_c16_t& data) {
this->post_message(data);
block_completed = true;
});
return block_completed;
}
void SpectrumCollector::set_filter(
const int32_t filter_low_frequency,
const int32_t filter_high_frequency,
const int32_t filter_transition) {
channel_filter_low_frequency = filter_low_frequency;
channel_filter_high_frequency = filter_high_frequency;
channel_filter_transition = filter_transition;
channel_spectrum_decimator.feed(
channel,
[this](const buffer_c16_t& data) {
this->post_message(data);
});
}
void SpectrumCollector::post_message(const buffer_c16_t& data) {
@@ -136,6 +149,7 @@ void SpectrumCollector::update() {
ChannelSpectrum spectrum;
spectrum.sampling_rate = channel_spectrum_sampling_rate;
spectrum.channel_filter_offset = channel_filter_offset;
spectrum.channel_filter_low_frequency = channel_filter_low_frequency;
spectrum.channel_filter_high_frequency = channel_filter_high_frequency;
spectrum.channel_filter_transition = channel_filter_transition;
+13 -3
View File
@@ -40,13 +40,24 @@ class SpectrumCollector {
void on_message(const Message* const message);
void set_decimation_factor(const size_t decimation_factor);
void set_channel_filter_offset(const int32_t offset) {
channel_filter_offset = offset;
}
void feed(
bool feed(
const buffer_c16_t& channel,
const int32_t filter_low_frequency,
const int32_t filter_high_frequency,
const int32_t filter_transition);
protected:
bool is_streaming() const { return streaming; }
void set_filter(
const int32_t filter_low_frequency,
const int32_t filter_high_frequency,
const int32_t filter_transition);
void post_message(const buffer_c16_t& data);
private:
BlockDecimator<complex16_t, 256> channel_spectrum_decimator{1};
ChannelSpectrum fifo_data[1 << ChannelSpectrumConfigMessage::fifo_k]{};
@@ -59,8 +70,7 @@ class SpectrumCollector {
int32_t channel_filter_low_frequency{0};
int32_t channel_filter_high_frequency{0};
int32_t channel_filter_transition{0};
void post_message(const buffer_c16_t& data);
int32_t channel_filter_offset{0};
void set_state(const SpectrumStreamingConfigMessage& message);
void start();
@@ -60,8 +60,8 @@ const PALConfig pal_default_config = {
= boot_bit(p1_ctrl0, 0) // P2_10: P1_CTRL0, start low
| boot_bit(clkin_ctrl, 0) // P1_20: CLKIN_CTRL - start low
#else
= boot_bit(amp_bypass, 1) // P2_10: AMP_BYPASS
| (1 << 15) // P1_20: CS_XCVR
= boot_bit(amp_bypass, 1) // P2_10: AMP_BYPASS
| boot_bit(max283x_select, 1) // P1_20: !CS_XCVR
#endif
| boot_bit(dfu_boot_0, 0) // P1_1: 10K PU, BOOT0
| boot_bit(dfu_boot_1, 0) // P1_2: 10K PD, BOOT1
@@ -83,8 +83,8 @@ const PALConfig pal_default_config = {
= boot_bit(p1_ctrl0, 1) // P2_10: P1_CTRL0
| boot_bit(clkin_ctrl, 1) // P1_20: CLKIN_CTRL
#else
= boot_bit(amp_bypass, 1) // P2_10: AMP_BYPASS
| (1 << 15) // P1_20: CS_XCVR
= boot_bit(amp_bypass, 1) // P2_10: AMP_BYPASS
| boot_bit(max283x_select, 1) // P1_20: !CS_XCVR
#endif
| boot_bit(dfu_boot_0, 0) // P1_1: 10K PU, BOOT0
| boot_bit(dfu_boot_1, 0) // P1_2: 10K PD, BOOT1
@@ -153,10 +153,9 @@ const PALConfig pal_default_config = {
},
{
// GPIO2
.data = (1 << 14) // P5_5: MIXER_RESETX, 10K PU
.data = boot_bit(rffc5072_resetx, 1) // P5_5: !MIXER_RESETX
#ifdef PRALINE
| (0 << 15) // P5_6: unused on PRALINE
| (1 << 13) // P5_4: RFFC5072 ENX
| (0 << 12) // P5_3: unused on PRALINE
| (0 << 11) // P5_2: FPGA_CRESET
| (1 << 10) // P5_1: FPGA_SPI_CS
@@ -169,9 +168,7 @@ const PALConfig pal_default_config = {
| boot_bit(led_rx, 1) // P4_2: LED2 (RX)
| boot_bit(led_usb, 1) // P4_1: LED1 (USB)
#else
| boot_bit(tx_amp, 0) // P5_6: TX_AMP
| (1 << 13) // P5_4: MIXER_ENX, 10K PU
| boot_bit(tx_amp, 0) // P5_6: TX_AMP
| boot_bit(rx_mix_bp, 1) // P5_3: RX_MIX_BP
| boot_bit(tx_mix_bp, 0) // P5_2: TX_MIX_BP
| boot_bit(lpf, 0) // P5_1: LPF
@@ -184,13 +181,13 @@ const PALConfig pal_default_config = {
| boot_bit(led_usb, 0) // P4_1: LED1 (USB)
#endif
| (1 << 7) // P5_7: CS_AD
| (0 << 5) // P4_5: RXENABLE
| boot_bit(rffc5072_select, 1) // P5_4: !MIXER_ENX
| (1 << 7) // P5_7: CS_AD
| (0 << 5) // P4_5: RXENABLE
,
.dir =
#ifdef PRALINE
(1 << 15) // P5_6: unused on PRALINE
| (1 << 13) // P5_4: RFFC5072 ENX
| (1 << 12) // P5_3: unused on PRALINE
| (1 << 11) // P5_2: FPGA_CRESET
| (1 << 10) // P5_1: FPGA_SPI_CS
@@ -200,22 +197,22 @@ const PALConfig pal_default_config = {
| (0 << 3) // P4_3: VBUSCTRL input
| (0 << 6) // P4_6: Input
#else
boot_bit(tx_amp, 1) // P5_6: TX_AMP
| (1 << 13) // P5_4: MIXER_ENX, 10K PU
| boot_bit(rx_mix_bp, 1) // P5_3: RX_MIX_BP
| boot_bit(tx_mix_bp, 1) // P5_2: TX_MIX_BP
| boot_bit(lpf, 1) // P5_1: LPF
| (0 << 9) // P5_0: Varies by revision
| boot_bit(hpf, 1) // P4_0: HPF
| boot_bit(sgpio_9, 0) // P4_3: SGPIO9, HOST_CAPTURE
| (1 << 6) // P4_6: XCVR_EN, 10K PD
boot_bit(tx_amp, 1) // P5_6: TX_AMP
| boot_bit(rx_mix_bp, 1) // P5_3: RX_MIX_BP
| boot_bit(tx_mix_bp, 1) // P5_2: TX_MIX_BP
| boot_bit(lpf, 1) // P5_1: LPF
| (0 << 9) // P5_0: Varies by revision
| boot_bit(hpf, 1) // P4_0: HPF
| boot_bit(sgpio_9, 0) // P4_3: SGPIO9, HOST_CAPTURE
| boot_bit(max283x_enable, 1) // P4_6: XCVR_EN, 10K PD
#endif
| (1 << 14) // P5_5: MIXER_RESETX, 10K PU
| boot_bit(led_tx, 1) // P6_12: LED3 (TX)
| (1 << 7) // P5_7: CS_AD
| (1 << 5) // P4_5: RXENABLE
| boot_bit(led_rx, 1) // P4_2: LED2 (RX)
| boot_bit(led_usb, 1) // P4_1: LED1 (USB)
| boot_bit(rffc5072_select, 1) // P5_4: !MIXER_ENX
| boot_bit(rffc5072_resetx, 1) // P5_5: !MIXER_RESETX
| boot_bit(led_tx, 1) // P6_12: LED3 (TX)
| (1 << 7) // P5_7: CS_AD
| (1 << 5) // P4_5: RXENABLE
| boot_bit(led_rx, 1) // P4_2: LED2 (RX)
| boot_bit(led_usb, 1) // P4_1: LED1 (USB)
},
{
// GPIO3
@@ -233,14 +230,15 @@ const PALConfig pal_default_config = {
| (0 << 6) // P6_10: unused on PRALINE
| boot_bit(p1_ctrl2, 0) // P6_9: P1_CTRL2
| boot_bit(aux_power_oc, 0) // P6_11: AUX overcurrent
| boot_bit(sct_clk_in, 0) // P6_4: SCT clock input
#else
| (1 << 4) // P6_5: HackRF CPLD.TMS(I)
| boot_bit(vregmode, 1) // P6_11: VREGMODE
| (0 << 6) // P6_10: Varies by revision
| boot_bit(sgpio_4, 1) // P6_3: SGPIO4
| boot_bit(tx_amp_pwr, 1) // P6_9: !TX_AMP_PWR, 10K PU
| (1 << 4) // P6_5: HackRF CPLD.TMS(I)
| boot_bit(vregmode, 1) // P6_11: VREGMODE
| (0 << 6) // P6_10: Varies by revision
| boot_bit(sgpio_4, 1) // P6_3: SGPIO4
| boot_bit(tx_amp_pwr, 1) // P6_9: !TX_AMP_PWR, 10K PU
| boot_bit(rffc5072_sdata, 0) // P6_4: MIXER_SDATA
#endif
| (1 << 3) // P6_4: MIXER_SDATA
| (1 << 1) // P6_2: HackRF CPLD.TDI(I)
| (1 << 0) // P6_1: HackRF CPLD.TCK(I)
,
@@ -258,14 +256,15 @@ const PALConfig pal_default_config = {
| boot_bit(mix_bypass, 1) // P6_3: MIX_ENABLE_N
| (0 << 6) // P6_10: unused on PRALINE
| boot_bit(p1_ctrl2, 1) // P6_9: P1_CTRL2
| boot_bit(sct_clk_in, 0) // P6_4: SCT clock input
#else
| boot_bit(vregmode, 1) // P6_11: VREGMODE
| (0 << 6) // P6_10: Varies by revision
| boot_bit(sgpio_4, 0) // P6_3: SGPIO4
| boot_bit(tx_amp_pwr, 1) // P6_9: !TX_AMP_PWR, 10K PU
| boot_bit(vregmode, 1) // P6_11: VREGMODE
| (0 << 6) // P6_10: Varies by revision
| boot_bit(sgpio_4, 0) // P6_3: SGPIO4
| boot_bit(tx_amp_pwr, 1) // P6_9: !TX_AMP_PWR, 10K PU
| boot_bit(rffc5072_sdata, 1) // P6_4: MIXER_SDATA
#endif
| (0 << 4) // P6_5: HackRF CPLD.TMS(I)
| (0 << 3) // P6_4: MIXER_SDATA
| (0 << 1) // P6_2: HackRF CPLD.TDI(I)
| (0 << 0) // P6_1: HackRF CPLD.TCK(I)
},
@@ -274,48 +273,48 @@ const PALConfig pal_default_config = {
.data =
boot_bit(sgpio_8, 0) // P9_6: SGPIO8, SGPIO_CLK, PRALINE: P8_0
#ifdef PRALINE
| boot_bit(rf_amp_enable, 0) // PA_2: RF_AMP_EN
| boot_bit(lpf, 0) // PA_1: LPF_EN
| boot_bit(en_1v2, 0) // P8_7: EN_1V2
| boot_bit(led_mcu, 1) // P8_6: LED4
| boot_bit(sgpio_10, 1) // P8_2: SGPIO10, HOST_DISABLE
| (0 << 5) // P8_5: VIN_IN_EN
| (0 << 4) // P8_4: VBUS_IN_EN
| boot_bit(VAA_en, 1) // P8_1: !VAA_EN
| (0 << 10) // PA_3: Output GND
| (0 << 11) // P9_6: MAX2831 LD
| boot_bit(rf5072_mix_en, 0) // P9_0: RF5072 enable
| (0 << 13) // P9_1: Output GND
| (0 << 14) // P9_2: RFFC5072 SDATA
| (0 << 3) // P8_3: Output GND
| boot_bit(sgpio_9, 1) // P9_3: Capture Input
| boot_bit(rf_amp_enable, 0) // PA_2: RF_AMP_EN
| boot_bit(lpf, 0) // PA_1: LPF_EN
| boot_bit(en_1v2, 0) // P8_7: EN_1V2
| boot_bit(led_mcu, 1) // P8_6: LED4
| boot_bit(sgpio_10, 1) // P8_2: SGPIO10, HOST_DISABLE
| (0 << 5) // P8_5: VIN_IN_EN
| (0 << 4) // P8_4: VBUS_IN_EN
| boot_bit(VAA_en, 1) // P8_1: !VAA_EN
| (0 << 10) // PA_3: Output GND
| (0 << 11) // P9_6: MAX2831 LD
| boot_bit(rf5072_mix_en, 0) // P9_0: RF5072 enable
| (0 << 13) // P9_1: Output GND
| boot_bit(rffc5072_sdata, 0) // P9_2: RFFC5072 SDATA
| (0 << 3) // P8_3: Output GND
| boot_bit(sgpio_9, 1) // P9_3: Capture Input
#endif
,
.dir =
boot_bit(sgpio_8, 0) // P9_6: SGPIO8, SGPIO_CLK, PRALINE: P8_0
#ifdef PRALINE
| boot_bit(rf_amp_enable, 1) // PA_2: RF_AMP_EN
| boot_bit(lpf, 1) // PA_1: LPF_EN
| boot_bit(en_1v2, 1) // P8_7: EN_1V2
| boot_bit(led_mcu, 1) // P8_6: LED4
| boot_bit(sgpio_10, 0) // P8_2: SGPIO10, HOST_DISABLE
| (1 << 5) // P8_5: VIN_IN_EN
| (1 << 4) // P8_4: VBUS_IN_EN
| boot_bit(VAA_en, 1) // P8_1: !VAA_EN
| (1 << 10) // PA_3: Output
| (0 << 11) // P9_6: MAX2831 LD
| boot_bit(rf5072_mix_en, 1) // P9_0: RF5072 enable
| (1 << 13) // P9_1: Output
| (0 << 14) // P9_2: RFFC5072 SDATA (Bidirectional)
| (1 << 3) // P8_3: Output
| boot_bit(sgpio_9, 0) // P9_3: Capture Input
| boot_bit(rf_amp_enable, 1) // PA_2: RF_AMP_EN
| boot_bit(lpf, 1) // PA_1: LPF_EN
| boot_bit(en_1v2, 1) // P8_7: EN_1V2
| boot_bit(led_mcu, 1) // P8_6: LED4
| boot_bit(sgpio_10, 0) // P8_2: SGPIO10, HOST_DISABLE
| (1 << 5) // P8_5: VIN_IN_EN
| (1 << 4) // P8_4: VBUS_IN_EN
| boot_bit(VAA_en, 1) // P8_1: !VAA_EN
| (1 << 10) // PA_3: Output
| (0 << 11) // P9_6: MAX2831 LD
| boot_bit(rf5072_mix_en, 1) // P9_0: RF5072 enable
| (1 << 13) // P9_1: Output
| boot_bit(rffc5072_sdata, 1) // P9_2: RFFC5072 SDATA (Bidirectional)
| (1 << 3) // P8_3: Output
| boot_bit(sgpio_9, 0) // P9_3: Capture Input
#endif
},
{
// GPIO5
.data =
#ifdef PRALINE
(0 << 18) // P9_5: RFF5072 SCLK
boot_bit(rffc5072_clock, 0) // P9_5: RFF5072 SCLK
| boot_bit(trigger_out, 0) // P2_6: Trigger out
| (0 << 14) // P4_10: FPGA CDONE
| boot_bit(aux_power_enable, 1) // P6_7: !3V3 AUX_ENABLE
@@ -332,12 +331,12 @@ const PALConfig pal_default_config = {
| (0 << 12) // P4_8: Output GND
| boot_bit(vregmode, 1) // P4_9: TPS62410 VREGMODE
#else
(1 << 18) // P9_5: HackRF CPLD.TDO(O)
| (0 << 6) // P2_6: MIXER_SCLK
| (1 << 14) // P4_10: SGPIO15, CPLD (unused)
| boot_bit(rx_mix_bypass, 1) // P6_8: RX MIX BYPASS
| (1 << 13) // P4_9: SGPIO14, CPLD (unused)
| (0 << 12) // P4_8: Varies by revision
(1 << 18) // P9_5: HackRF CPLD.TDO(O)
| boot_bit(rffc5072_clock, 0) // P2_6: MIXER_SCLK
| (1 << 14) // P4_10: SGPIO15, CPLD (unused)
| boot_bit(rx_mix_bypass, 1) // P6_8: RX MIX BYPASS
| (1 << 13) // P4_9: SGPIO14, CPLD (unused)
| (0 << 12) // P4_8: Varies by revision
#endif
| (1 << 11) // P3_8: SPIFI_CS
| (1 << 10) // P3_7: SPIFI_MOSI
@@ -352,7 +351,7 @@ const PALConfig pal_default_config = {
,
.dir =
#ifdef PRALINE
(1 << 18) // P9_5: RFF5072 SCLK
boot_bit(rffc5072_clock, 1) // P9_5: RFF5072 SCLK
| boot_bit(trigger_out, 1) // P2_6: Trigger out
| (0 << 14) // P4_10: FPGA CDONE
| boot_bit(aux_power_enable, 1) // P6_7: !3V3 AUX_ENABLE
@@ -369,12 +368,12 @@ const PALConfig pal_default_config = {
| (1 << 12) // P4_8: Output
| boot_bit(vregmode, 1) // P4_9: TPS62410 VREGMODE
#else
(0 << 18) // P9_5: HackRF CPLD.TDO(O)
| (1 << 6) // P2_6: MIXER_SCLK
| (0 << 14) // P4_10: SGPIO15, CPLD
| boot_bit(rx_mix_bypass, 1) // P6_8: RX MIX BYPASS
| (0 << 13) // P4_9: SGPIO14, CPLD
| (0 << 12) // P4_8: Varies by revision
(0 << 18) // P9_5: HackRF CPLD.TDO(O)
| boot_bit(rffc5072_clock, 1) // P2_6: MIXER_SCLK
| (0 << 14) // P4_10: SGPIO15, CPLD
| boot_bit(rx_mix_bypass, 1) // P6_8: RX MIX BYPASS
| (0 << 13) // P4_9: SGPIO14, CPLD
| (0 << 12) // P4_8: Varies by revision
#endif
| (0 << 11) // P3_8: SPIFI_CS
| (0 << 10) // P3_7: SPIFI_MOSI
@@ -390,17 +389,17 @@ const PALConfig pal_default_config = {
{
// GPIO6
#ifdef PRALINE
.data = (1 << 28) // PD_14: MAX2831 chip select
| (0 << 29) // PD_15: MAX2831 RXHP control RXHP low = 100 Hz HPF
| (1 << 30) // PD_16: MAX5865 chip select
| (1 << 25) // PD_11: RFFC5072 Lock Detect
| boot_bit(trigger_in, 0) // PD_12: TRIGGER IN
.data = boot_bit(max283x_select, 1) // PD_14: !MAX2831 chip select
| boot_bit(max2831_rxhp, 0) // PD_15: MAX2831 RXHP control RXHP low = 100 Hz HPF
| (1 << 30) // PD_16: MAX5865 chip select
| (1 << 25) // PD_11: RFFC5072 Lock Detect
| boot_bit(trigger_in, 0) // PD_12: TRIGGER IN
,
.dir = (1 << 28) // PD_14: MAX2831 chip select
| (1 << 29) // PD_15: MAX2831 RXHP control
| (1 << 30) // PD_16: MAX5865 chip select
| (0 << 25) // PD_11: RFFC5072 Lock Detect
| boot_bit(trigger_in, 0) // PD_12: TRIGGER IN
.dir = boot_bit(max283x_select, 1) // PD_14: !MAX2831 chip select
| boot_bit(max2831_rxhp, 1) // PD_15: MAX2831 RXHP control
| (1 << 30) // PD_16: MAX5865 chip select
| (0 << 25) // PD_11: RFFC5072 Lock Detect
| boot_bit(trigger_in, 0) // PD_12: TRIGGER IN
#else
.data = 0,
.dir = 0
@@ -409,17 +408,17 @@ const PALConfig pal_default_config = {
{
// GPIO7
#ifdef PRALINE
.data = (0 << 0) // PE_0: Output
| (0 << 1) // PE_1: MAX2831 !SHDN
| (0 << 2) // PE_2: MAX2831 RXTX
| boot_bit(p2_ctrl0, 0) // PE_3: P2_ctrl0
| boot_bit(p2_ctrl1, 0) // PE_4: P2_ctrl1
.data = (0 << 0) // PE_0: Output
| boot_bit(max283x_enable, 0) // PE_1: MAX2831 !SHDN
| boot_bit(max2831_rxtx_enable, 0) // PE_2: MAX2831 RXTX
| boot_bit(p2_ctrl0, 0) // PE_3: P2_ctrl0
| boot_bit(p2_ctrl1, 0) // PE_4: P2_ctrl1
,
.dir = (1 << 0) // PE_0: Output
| (1 << 1) // PE_1: MAX2831 !SHDN
| (1 << 2) // PE_2: MAX2831 RXTX
| boot_bit(p2_ctrl0, 1) // PE_3: P2_ctrl0
| boot_bit(p2_ctrl1, 1) // PE_4: P2_ctrl1
.dir = (1 << 0) // PE_0: Output
| boot_bit(max283x_enable, 1) // PE_1: MAX2831 !SHDN
| boot_bit(max2831_rxtx_enable, 1) // PE_2: MAX2831 RXTX
| boot_bit(p2_ctrl0, 1) // PE_3: P2_ctrl0
| boot_bit(p2_ctrl1, 1) // PE_4: P2_ctrl1
#else
.data = 0,
.dir = 0
@@ -494,7 +493,7 @@ const PALConfig pal_default_config = {
{map_sgpio_12.scu_port, map_sgpio_12.scu_pin, scu_config_normal_drive_t{.mode = map_sgpio_12.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // SGPIO12: HOST_INVERT(I)
#ifdef PRALINE
{6, 4, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // P6_4: SCT_CLK IN (Fast clock input, Mode 1)
{map_sct_clk_in.scu_port, map_sct_clk_in.scu_pin, scu_config_normal_drive_t{.mode = map_sct_clk_in.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // P6_4: SCT_CLK IN (Fast clock input, Mode 1)
{4, 10, scu_config_normal_drive_t{.mode = 7, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // P4_10: FPGA Config Done (Input GND)
{map_trigger_in.scu_port, map_trigger_in.scu_pin, scu_config_normal_drive_t{.mode = map_trigger_in.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // PD_12: TRIGGER IN (Input GND, Mode 4)
{map_trigger_out.scu_port, map_trigger_out.scu_pin, scu_config_normal_drive_t{.mode = map_trigger_out.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // P2_6: TRIGGER
@@ -504,10 +503,7 @@ const PALConfig pal_default_config = {
{4, 9, scu_config_normal_drive_t{.mode = 7, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // SGPIO14/BANK2F3M4: CPLD_P81
{4, 10, scu_config_normal_drive_t{.mode = 7, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // SGPIO15/BANK2F3M6: CPLD_P78
{2, 6, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 1}}, // MIXER_SCLK/P31: 33pF, RFFC5072.SCLK(I)
{4, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // RXENABLE
{4, 6, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // XCVR_EN: 10K PD
{1, 20, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_20 CS_XCVR: MAX2837.CS(I)
{map_hpf.scu_port, map_hpf.scu_pin, scu_config_normal_drive_t{.mode = map_hpf.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P4_0 HPF
{map_rx_amp.scu_port, map_rx_amp.scu_pin, scu_config_normal_drive_t{.mode = map_rx_amp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_11 RX_AMP
{map_rx_amp_pwr.scu_port, map_rx_amp_pwr.scu_pin, scu_config_normal_drive_t{.mode = map_rx_amp_pwr.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_12 !RX_AMP_PWR
@@ -515,43 +511,44 @@ const PALConfig pal_default_config = {
#endif
// RADIO & RF PATH (MAX2831, RFFC5072, Mixers, Switches, Amps)
{1, 3, scu_config_normal_drive_t{.mode = 5, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // P1_3 SSP1_MISO: MAX2837.DOUT(O)
{1, 4, scu_config_normal_drive_t{.mode = 5, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_4 SSP1_MOSI: MAX2837.DIN(I)
{1, 19, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 1}}, // P1_19 SSP1_SCK: MAX2837.SCLK(I)
{map_lpf.scu_port, map_lpf.scu_pin, scu_config_normal_drive_t{.mode = map_lpf.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_1 LPF
{1, 3, scu_config_normal_drive_t{.mode = 5, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // P1_3 SSP1_MISO: MAX2837.DOUT(O)
{1, 4, scu_config_normal_drive_t{.mode = 5, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_4 SSP1_MOSI: MAX2837.DIN(I)
{1, 19, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 1}}, // P1_19 SSP1_SCK: MAX2837.SCLK(I)
{map_lpf.scu_port, map_lpf.scu_pin, scu_config_normal_drive_t{.mode = map_lpf.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_1 LPF
{map_rffc5072_clock.scu_port, map_rffc5072_clock.scu_pin, scu_config_normal_drive_t{.mode = map_rffc5072_clock.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 1}}, // P2_6: MIXER_SCLK/P31: 33pF, RFFC5072.SCLK(I) PRALINE: P9_5: RFFC5072 SCLK
{map_max283x_select.scu_port, map_max283x_select.scu_pin, scu_config_normal_drive_t{.mode = map_max283x_select.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // P1_20 CS_XCVR: MAX2837.CS(I), PRALINE: PD_14: MAX2831 CS
{map_max283x_enable.scu_port, map_max283x_enable.scu_pin, scu_config_normal_drive_t{.mode = map_max283x_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // P4_6 max283x enable, PRALINE: PE_1: MAX2831 !SHDN
#ifdef PRALINE
{5, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_4: RFFC ENX
{5, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_5: RFFC RESETX
{9, 5, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // P9_5: RFFC5072 SCLK
{9, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 1, .ezi = 1, .zif = 0}}, // P9_2: RFFC5072 DATA (Bidirectional)
{13, 11, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // PD_11: RFFC Lock Detect
{13, 14, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PD_14: MAX2831 CS
{13, 15, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // PD_15: MAX2831 RXHP
{13, 16, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PD_16: MAX5864 CS
{14, 1, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PE_1: MAX2831 !SHDN
{14, 2, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // PE_2: MAX2831 RXTX
{map_p1_ctrl1.scu_port, map_p1_ctrl1.scu_pin, scu_config_normal_drive_t{.mode = map_p1_ctrl1.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_8 P1_CTRL1 (Output GND, Mode 4)
{map_aa_en.scu_port, map_aa_en.scu_pin, scu_config_normal_drive_t{.mode = map_aa_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 0}}, // P1_14: AA_EN
{map_rf5072_mix_en.scu_port, map_rf5072_mix_en.scu_pin, scu_config_normal_drive_t{.mode = map_rf5072_mix_en.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P9_0: RF5072 MIX EN
{9, 6, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P9_6: MAX2831 LD Input
{map_tx_enable.scu_port, map_tx_enable.scu_pin, scu_config_normal_drive_t{.mode = map_tx_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_5: TX enable
{10, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PA_1: LPF enable
{10, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PA_2: RF amp enable
{map_mix_bypass.scu_port, map_mix_bypass.scu_pin, scu_config_normal_drive_t{.mode = map_mix_bypass.gpio_mode, .epd = 0, .epun = 0, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_3: MIX_ENABLE_N
{map_rf_amp_enable.scu_port, map_rf_amp_enable.scu_pin, scu_config_normal_drive_t{.mode = map_rf_amp_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // PA_2 RF_AMP_EN
{13, 11, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // PD_11: RFFC Lock Detect
{map_max2831_rxhp.scu_port, map_max2831_rxhp.scu_pin, scu_config_normal_drive_t{.mode = map_max2831_rxhp.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // PD_15: MAX2831 RXHP
{13, 16, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PD_16: MAX5864 CS
{map_max2831_rxtx_enable.scu_port, map_max2831_rxtx_enable.scu_pin, scu_config_normal_drive_t{.mode = map_max2831_rxtx_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 1}}, // PE_2: MAX2831 RXTX
{map_p1_ctrl1.scu_port, map_p1_ctrl1.scu_pin, scu_config_normal_drive_t{.mode = map_p1_ctrl1.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_8 P1_CTRL1 (Output GND, Mode 4)
{map_aa_en.scu_port, map_aa_en.scu_pin, scu_config_normal_drive_t{.mode = map_aa_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 1, .zif = 0}}, // P1_14: AA_EN
{map_rf5072_mix_en.scu_port, map_rf5072_mix_en.scu_pin, scu_config_normal_drive_t{.mode = map_rf5072_mix_en.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P9_0: RF5072 MIX EN
{9, 6, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P9_6: MAX2831 LD Input
{map_tx_enable.scu_port, map_tx_enable.scu_pin, scu_config_normal_drive_t{.mode = map_tx_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_5: TX enable
{10, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PA_1: LPF enable
{10, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // PA_2: RF amp enable
{map_mix_bypass.scu_port, map_mix_bypass.scu_pin, scu_config_normal_drive_t{.mode = map_mix_bypass.gpio_mode, .epd = 0, .epun = 0, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_3: MIX_ENABLE_N
{map_rf_amp_enable.scu_port, map_rf_amp_enable.scu_pin, scu_config_normal_drive_t{.mode = map_rf_amp_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // PA_2 RF_AMP_EN
#else
{map_rx_mix_bp.scu_port, map_rx_mix_bp.scu_pin, scu_config_normal_drive_t{.mode = map_rx_mix_bp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_3 RX_MIX_BP
{5, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // MIXER_ENX
{map_tx_amp.scu_port, map_tx_amp.scu_pin, scu_config_normal_drive_t{.mode = map_tx_amp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_6 TX_AMP
{5, 7, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_7 CS_AD, PRALINE: RFFC5072 CS
{5, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // MIXER_RESETX
{6, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // MIXER_SDATA
{map_rx_mix_bp.scu_port, map_rx_mix_bp.scu_pin, scu_config_normal_drive_t{.mode = map_rx_mix_bp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_3 RX_MIX_BP
{map_tx_amp.scu_port, map_tx_amp.scu_pin, scu_config_normal_drive_t{.mode = map_tx_amp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_6 TX_AMP
{5, 7, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_7 CS_AD, PRALINE: RFFC5072 CS
{map_tx_mix_bypass.scu_port, map_tx_mix_bypass.scu_pin, scu_config_normal_drive_t{.mode = map_tx_mix_bypass.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_8 !TX MIX BYPASS
{map_amp_bypass.scu_port, map_amp_bypass.scu_pin, scu_config_normal_drive_t{.mode = map_amp_bypass.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_10 AMP_BYPASS
{map_tx_amp_pwr.scu_port, map_tx_amp_pwr.scu_pin, scu_config_normal_drive_t{.mode = map_tx_amp_pwr.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // !TX_AMP_PWR
{map_rx_mix_bypass.scu_port, map_rx_mix_bypass.scu_pin, scu_config_normal_drive_t{.mode = map_rx_mix_bypass.gpio_mode, .epd = 0, .epun = 0, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_8 RX MIX BYPASS
#endif
{map_rffc5072_select.scu_port, map_rffc5072_select.scu_pin, scu_config_normal_drive_t{.mode = map_rffc5072_select.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_4: MIXER_ENX
{map_rffc5072_resetx.scu_port, map_rffc5072_resetx.scu_pin, scu_config_normal_drive_t{.mode = map_rffc5072_resetx.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // MIXER_RESETX
{map_rffc5072_sdata.scu_port, map_rffc5072_sdata.scu_pin, scu_config_normal_drive_t{.mode = map_rffc5072_sdata.gpio_mode, .epd = 0, .epun = 0, .ehs = 1, .ezi = 1, .zif = 0}}, // P6_4: RFFC5072 DATA (Bidirectional) PRALINE P9_2
// SAFE TERMINATION (Unused pins grounded to prevent noise & save power)
#ifdef PRALINE
@@ -584,7 +581,7 @@ const PALConfig pal_default_config = {
{map_isp.scu_port, map_isp.scu_pin, scu_config_normal_drive_t{.mode = map_isp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_7: ISP: 10K PU, Unused
{map_dfu_boot_0.scu_port, map_dfu_boot_0.scu_pin, scu_config_normal_drive_t{.mode = map_dfu_boot_0.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_1: 10K PU, BOOT0
{map_dfu_boot_1.scu_port, map_dfu_boot_1.scu_pin, scu_config_normal_drive_t{.mode = map_dfu_boot_1.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_2: 10K PD, BOOT1
{map_dfu_button.scu_port, map_dfu_button.scu_pin, scu_config_normal_drive_t{.mode = map_dfu_button.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 1}}, // P2_8: BOOT2, DFU button
{map_dfu_button.scu_port, map_dfu_button.scu_pin, scu_config_normal_drive_t{.mode = map_dfu_button.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 1}}, // P2_8: 10K PD, BOOT2, DFU button
{map_lcd_wrx.scu_port, map_lcd_wrx.scu_pin, scu_config_normal_drive_t{.mode = map_lcd_wrx.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // P2_9: 10K PD, BOOT3, PortaPack LCD_WRX
}};
@@ -125,10 +125,11 @@
// ============================================================================
// 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 the canonical RX defaults in ONE place.
* Matches fpga_init() in hackrf/firmware/common/fpga.c: the gateware only has
* rx_decim and rx_pstep on the RX side, both starting at zero. */
#define FPGA_RX_DEFAULT_DECIM 0x00 /* No decimation initially */
#define FPGA_RX_DEFAULT_PSTEP 0x00 /* No quarter-rate shift initially */
/* Define TX defaults */
#define FPGA_TX_DEFAULT_NCO_CTRL 0x00 /* NCO disabled */
@@ -221,12 +222,13 @@ static bool fpga_cdone_read(void) {
// These functions allow reading/writing FPGA internal registers via SPI.
// The FPGA bitstream implements a simple SPI register interface.
//
// 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 (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]
// FPGA Register Map (hackrf/firmware/fpga/top/standard.py):
// Reg 1 (CTRL): DC_BLOCK(b0), PRBS(b6), TRIGGER_EN(b7)
// Reg 2 (RX_DECIM): Decimation ratio, log2 [2:0]
// Reg 3 (RX_PSTEP): QUARTER_SHIFT_EN(b6), QUARTER_SHIFT_UP(b7)
// Reg 4 (TX_CTRL): NCO enable (b0)
// Reg 5 (TX_INTRP): Interpolation ratio [2:0]
// Reg 6 (TX_PSTEP): NCO phase step [7:0]
//
// SPI Protocol:
// Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3
@@ -369,22 +371,16 @@ void fpga_rx_enable_dc_block(bool enable) {
}
/* RX Functions with mode assertion */
void fpga_rx_set_digital_gain(uint8_t shift) {
/* Quarter-rate shift, register 0x03 bits [7:6]. Equivalent to
* fpga_set_rx_quarter_shift_mode() in hackrf/firmware/common/fpga.c.
* mode: 0b00 none, 0b11 up, 0b01 down. */
void fpga_rx_set_quarter_shift_mode(uint8_t mode) {
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);
fpga_register_write(FPGA_REG_RX_PSTEP, (uint8_t)((mode & 0x03) << FPGA_RX_QUARTER_SHIFT_SHIFT));
}
// ============================================================================
@@ -394,12 +390,12 @@ void fpga_rx_set_dc_adapt_rate(uint8_t rate) {
/* TX Functions with mode assertion */
void fpga_tx_set_nco_enable(bool enable) {
if (current_mode != FPGA_MODE_TX) return;
uint8_t val = fpga_register_read(FPGA_REG3_TX_NCO_CTRL);
uint8_t val = fpga_register_read(FPGA_REG_TX_CONTROL);
if (enable)
val |= FPGA_TX_NCO_EN;
else
val &= ~FPGA_TX_NCO_EN;
fpga_register_write(FPGA_REG3_TX_NCO_CTRL, val);
fpga_register_write(FPGA_REG_TX_CONTROL, val);
}
void fpga_tx_set_interpolation(uint8_t ratio) {
@@ -424,17 +420,17 @@ static void fpga_register_init(void) {
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);
fpga_spi_write(FPGA_REG_DECIM, FPGA_RX_DEFAULT_DECIM);
fpga_spi_write(FPGA_REG_RX_PSTEP, FPGA_RX_DEFAULT_PSTEP);
fpga_spi_write(FPGA_REG_TX_CONTROL, FPGA_TX_DEFAULT_NCO_CTRL);
fpga_spi_write(FPGA_REG_TX_INTERP, FPGA_TX_DEFAULT_INTERP);
/* 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;
fpga_reg_cache[2] = FPGA_RX_DEFAULT_DECIM;
fpga_reg_cache[3] = FPGA_RX_DEFAULT_PSTEP;
fpga_reg_cache[4] = FPGA_TX_DEFAULT_NCO_CTRL;
fpga_reg_cache[5] = FPGA_TX_DEFAULT_INTERP;
}
// ============================================================================
@@ -16,10 +16,23 @@ extern "C" {
#ifdef PRALINE
/* RX path (legacy PRALINE software map kept for compatibility) */
#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */
#define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */
#define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */
/*
* RX path.
*
* NOTE: the register map below is the one the loaded bitstream actually
* implements. Ground truth is hackrf/firmware/fpga/top/standard.py plus
* hackrf/firmware/common/fpga_regs.def; Mayhem, hackrf_usb and debug_for_adsb
* all load a byte-identical praline_fpga.bin, so that map applies here too.
*
* An earlier "legacy PRALINE software map" claimed register 0x03 was an RX
* digital gain and 0x04/0x05 were DC-block width / adaptation rate. The
* gateware has none of those: 0x03 is rx_pstep (whose top two bits are the
* quarter-rate shift) and 0x04/0x05 are TX registers. Writing the old "RX DC
* width" value of 0x01 to 0x04 actually set tx_ctrl[0] and switched the TX NCO
* on, and every write of the fictional digital gain to 0x03 cleared the
* quarter shift.
*/
#define FPGA_REG_RX_PSTEP 0x03 /* RX phase step; bits [7:6] = quarter shift */
/*
* TX path must match the currently built PRALINE standard gateware in
@@ -43,70 +56,55 @@ typedef enum {
/*
* FPGA Register Addresses
* NOTE:
* The currently loaded PRALINE standard gateware uses:
* 0x01 CTRL
* 0x02 RX_DECIM
* 0x03 RX_DIGITAL_GAIN (RX) / TX_NCO_CTRL (TX)
* 0x04 TX_CTRL
* 0x05 TX_INTRP
* 0x06 TX_PSTEP
*
* From hackrf/firmware/fpga/top/standard.py (spi_regs.add_register):
* 0x01 ctrl 8 bits
* 0x02 rx_decim 3 bits
* 0x03 rx_pstep 8 bits
* 0x04 tx_ctrl 1 bit
* 0x05 tx_intrp 3 bits
* 0x06 tx_pstep 8 bits
*
* There is no RX gain register: rx_decim selects half-band FIR stages
* (hbfir1..hbfir5), which are unity-gain, so there is no CIC bit growth to
* renormalise.
*/
#define FPGA_REG_CTRL 0x01 /* Control register */
#define FPGA_REG_DECIM 0x02 /* RX decimation */
#define FPGA_REG_SHARED_3 0x03 /* Legacy shared register */
#define FPGA_REG_SHARED_4 0x04 /* Legacy shared register */
#define FPGA_REG_SHARED_5 0x05 /* Legacy shared register */
#define FPGA_REG_SHARED_6 0x06 /* TX phase step */
#define FPGA_REG_CTRL 0x01 /* Control register */
#define FPGA_REG_DECIM 0x02 /* RX decimation (log2, bits [2:0]) */
/* 0x03..0x06 are FPGA_REG_RX_PSTEP / FPGA_REG_TX_CONTROL /
* FPGA_REG_TX_INTERP / FPGA_REG_TX_PHASE_STEP, defined above. */
/*
* Register 1 (CTRL) Bit Definitions
* standard.py: ctrl[0] -> dc_block.enable, ctrl[6] -> prbs, ctrl[7] -> trigger_en.
* Nothing else in this register is decoded.
*/
#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 */
#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */
#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
* Register 3 (RX_PSTEP) Bit Definitions
* standard.py: rx_pstep[6] -> quarter_shift.enable, rx_pstep[7] -> quarter_shift.up.
* Same encoding as fpga_quarter_shift_mode_t << 6 in hackrf/firmware/common/fpga.c.
*/
/* RX Mode: Digital gain/shift */
#define FPGA_REG3_RX_DIGITAL_GAIN 0x03
#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */
/* Position of that 2-bit field inside rx_pstep, for direct register writes. */
#define FPGA_RX_QUARTER_SHIFT_SHIFT 6
#define FPGA_RX_QUARTER_SHIFT_MASK 0xC0
/* 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 */
/* Mode values, matching fpga_quarter_shift_mode_t in
* hackrf/firmware/common/fpga.h. Pass these to
* fpga_rx_set_quarter_shift_mode(), which shifts them into place. */
#define FPGA_QUARTER_SHIFT_MODE_NONE 0b00
#define FPGA_QUARTER_SHIFT_MODE_UP 0b11
#define FPGA_QUARTER_SHIFT_MODE_DOWN 0b01
/*
* Register 4 Dual-Purpose Definitions
* Register 4 (TX_CTRL) / 5 (TX_INTRP) / 6 (TX_PSTEP) Bit 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 0x05
#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 0x06
#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
#define FPGA_TX_NCO_EN (1 << 0) /* tx_ctrl[0]: NCO enable */
#define FPGA_TX_INTERP_MASK 0x07 /* tx_intrp bits [2:0] */
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* tx_pstep bits [7:0] */
/*
* Core Functions
@@ -133,9 +131,8 @@ 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);
/* mode is the gateware encoding: 0b00 none, 0b11 up, 0b01 down. */
void fpga_rx_set_quarter_shift_mode(uint8_t mode);
void fpga_rx_enable_dc_block(bool enable);
/*
@@ -154,12 +151,13 @@ void fpga_tx_set_phase_step(uint8_t step);
* reg: Register number (1-5)
* Returns: Register value, or 0xFF if invalid register
*
* 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 (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]
* FPGA Register Map (hackrf/firmware/fpga/top/standard.py):
* Reg 1 (CTRL): DC_BLOCK(b0), PRBS(b6), TRIGGER_EN(b7)
* Reg 2 (RX_DECIM): Decimation ratio, log2 [2:0]
* Reg 3 (RX_PSTEP): QUARTER_SHIFT_EN(b6), QUARTER_SHIFT_UP(b7)
* Reg 4 (TX_CTRL): NCO_EN(b0)
* Reg 5 (TX_INTRP): Interpolation ratio, log2 [2:0]
* Reg 6 (TX_PSTEP): NCO phase step [7:0]
*/
uint8_t fpga_debug_register_read(uint8_t reg);
+105
View File
@@ -44,6 +44,111 @@ struct fir_taps_complex {
std::array<complex16_t, N> taps;
};
/*
* 768kHz -> 384kHz half-band prefilter. The broad 80...304kHz
* transition makes this stage inexpensive while protecting the useful band.
*/
constexpr fir_taps_real<16> taps_audio_wide_halfband_0{
.low_frequency_normalized = -80000.0f / 768000.0f,
.high_frequency_normalized = 80000.0f / 768000.0f,
.transition_normalized = 224000.0f / 768000.0f,
.taps = {{
-171,
0,
1144,
0,
-4481,
0,
19892,
32767,
19892,
0,
-4481,
0,
1144,
0,
-171,
0,
}},
};
/*
* Spectrum capture anti-alias half-band filter. It is run only while a
* contiguous 256-sample FFT frame is being collected:
* 384kHz -> 192kHz (Zoom x1)
* 192kHz -> 96kHz (additional stage for Zoom x2)
*/
constexpr fir_taps_real<63> taps_audio_spectrum_halfband{
.low_frequency_normalized = -0.23f,
.high_frequency_normalized = 0.23f,
.transition_normalized = 0.04f,
.taps = {{
-15,
0,
37,
0,
-70,
0,
117,
0,
-184,
0,
274,
0,
-393,
0,
548,
0,
-751,
0,
1018,
0,
-1374,
0,
1872,
0,
-2622,
0,
3910,
0,
-6794,
0,
20812,
32767,
20812,
0,
-6794,
0,
3910,
0,
-2622,
0,
1872,
0,
-1374,
0,
1018,
0,
-751,
0,
548,
0,
-393,
0,
274,
0,
-184,
0,
117,
0,
-70,
0,
37,
0,
-16,
}},
};
// NBFM 16K0F3E emission type /////////////////////////////////////////////
// IFIR image-reject filter: fs=3072000, pass=8000, stop=344000, decim=8, fout=384000
+49
View File
@@ -394,6 +394,17 @@ constexpr PinMap map_lpf{10, 1, 4, 8, 4};
constexpr PinMap map_tx_enable{6, 5, 3, 4, 0};
constexpr PinMap map_rf_amp_enable{10, 2, 4, 9, 0};
constexpr PinMap map_pps_in_out{2, 5, 5, 5, 4};
constexpr PinMap map_rffc5072_clock{9, 5, 5, 18, 4};
constexpr PinMap map_rffc5072_sdata{9, 2, 4, 14, 0};
constexpr PinMap map_sct_clk_in{6, 4, 3, 3, 1};
constexpr PinMap map_max283x_select{13, 14, 6, 28, 4};
constexpr PinMap map_max283x_enable{14, 1, 7, 1, 4};
constexpr PinMap map_max2831_rxtx_enable{14, 2, 7, 2, 4};
constexpr PinMap map_max2831_rxhp{13, 15, 6, 29, 4};
// constexpr PinMap map_unused_1{2, 7, 0, 7, 0};
#else
@@ -425,6 +436,13 @@ constexpr PinMap map_ant_bias{4, 4, 2, 4, 0};
constexpr PinMap map_r9_mcu_clk_en{1, 1, 0, 8, 0};
constexpr PinMap map_r9_clkout_en{1, 2, 0, 9, 0};
constexpr PinMap map_r9_clkin_en{6, 7, 5, 15, 4};
constexpr PinMap map_rffc5072_clock{2, 6, 5, 6, 4};
constexpr PinMap map_rffc5072_sdata{6, 4, 3, 3, 0};
constexpr PinMap map_max283x_select{1, 20, 0, 15, 0};
constexpr PinMap map_max283x_enable{4, 6, 2, 6, 0};
#endif
constexpr PinMap map_sgpio_0{0, 0, 0, 0, 3};
@@ -445,6 +463,10 @@ constexpr PinMap map_dfu_boot_1{1, 2, 0, 9, 0};
constexpr PinMap map_dfu_button{2, 8, 5, 7, 4};
constexpr PinMap map_lcd_wrx{2, 9, 1, 10, 0};
constexpr PinMap map_isp{2, 7, 0, 7, 0};
constexpr PinMap map_rffc5072_select{5, 4, 2, 13, 0};
constexpr PinMap map_rffc5072_resetx{5, 5, 2, 14, 0};
#ifdef PRALINE
// P1 Multiplexer control pins
@@ -522,6 +544,15 @@ constexpr GPIO ant_bias_oc{pin_ant_bias_oc, map_ant_bias_oc.gpio_port, map_ant_b
constexpr Pin pin_pps_in_out{map_pps_in_out.scu_port, map_pps_in_out.scu_pin}; // SCU: P2_5
constexpr GPIO pps_in_out{pin_pps_in_out, map_pps_in_out.gpio_port, map_pps_in_out.gpio_pad, map_pps_in_out.gpio_mode}; // GPIO[5]5
constexpr Pin pin_sct_clk_in{map_sct_clk_in.scu_port, map_sct_clk_in.scu_pin}; // SCU: P6_4
constexpr GPIO sct_clk_in{pin_sct_clk_in, map_sct_clk_in.gpio_port, map_sct_clk_in.gpio_pad, map_sct_clk_in.gpio_mode}; // GPIO[3]3
constexpr Pin pin_max2831_rxtx_enable{map_max2831_rxtx_enable.scu_port, map_max2831_rxtx_enable.scu_pin}; // SCU: PE_2
constexpr GPIO max2831_rxtx_enable{pin_max2831_rxtx_enable, map_max2831_rxtx_enable.gpio_port, map_max2831_rxtx_enable.gpio_pad, map_max2831_rxtx_enable.gpio_mode}; // GPIO[7]2
constexpr Pin pin_max2831_rxhp{map_max2831_rxhp.scu_port, map_max2831_rxhp.scu_pin}; // SCU: PD_15
constexpr GPIO max2831_rxhp{pin_max2831_rxhp, map_max2831_rxhp.gpio_port, map_max2831_rxhp.gpio_pad, map_max2831_rxhp.gpio_mode}; // GPIO[6]29
#else
constexpr Pin pin_sgpio_13{map_sgpio_13.scu_port, map_sgpio_13.scu_pin}; // SCU: P4_8
constexpr GPIO sgpio_13{pin_sgpio_13, map_sgpio_13.gpio_port, map_sgpio_13.gpio_pad, map_sgpio_13.gpio_mode}; // GPIO[5]12
@@ -671,6 +702,24 @@ constexpr GPIO lcd_wrx{pin_lcd_wrx, map_lcd_wrx.gpio_port, map_lcd_wrx.gpio_pad,
constexpr Pin pin_isp{map_isp.scu_port, map_isp.scu_pin}; // SCU: P2_7
constexpr GPIO dfu_isp{pin_isp, map_isp.gpio_port, map_isp.gpio_pad, map_isp.gpio_mode}; // GPIO[0]7
constexpr Pin pin_rffc5072_clock{map_rffc5072_clock.scu_port, map_rffc5072_clock.scu_pin}; // SCU: P2_6, PRALINE: P9_5
constexpr GPIO rffc5072_clock{pin_rffc5072_clock, map_rffc5072_clock.gpio_port, map_rffc5072_clock.gpio_pad, map_rffc5072_clock.gpio_mode}; // GPIO[5]6, PRALINE: GPIO[5]18
constexpr Pin pin_rffc5072_sdata{map_rffc5072_sdata.scu_port, map_rffc5072_sdata.scu_pin}; // SCU: P6_4, PRALINE: P9_2
constexpr GPIO rffc5072_sdata{pin_rffc5072_sdata, map_rffc5072_sdata.gpio_port, map_rffc5072_sdata.gpio_pad, map_rffc5072_sdata.gpio_mode}; // GPIO[3]3, PRALINE: GPIO[4]14
constexpr Pin pin_rffc5072_select{map_rffc5072_select.scu_port, map_rffc5072_select.scu_pin}; // SCU: P5_4
constexpr GPIO rffc5072_select{pin_rffc5072_select, map_rffc5072_select.gpio_port, map_rffc5072_select.gpio_pad, map_rffc5072_select.gpio_mode, Polarity::ActiveLow}; // GPIO[2]13
constexpr Pin pin_rffc5072_resetx{map_rffc5072_resetx.scu_port, map_rffc5072_resetx.scu_pin}; // SCU: P5_5
constexpr GPIO rffc5072_resetx{pin_rffc5072_resetx, map_rffc5072_resetx.gpio_port, map_rffc5072_resetx.gpio_pad, map_rffc5072_resetx.gpio_mode, Polarity::ActiveLow}; // GPIO[2]14
constexpr Pin pin_max283x_select{map_max283x_select.scu_port, map_max283x_select.scu_pin}; // SCU: P1_20, PRALINE: PD_14
constexpr GPIO max283x_select{pin_max283x_select, map_max283x_select.gpio_port, map_max283x_select.gpio_pad, map_max283x_select.gpio_mode, Polarity::ActiveLow}; // GPIO[0]15, PRALINE: GPIO[6]28
constexpr Pin pin_max283x_enable{map_max283x_enable.scu_port, map_max283x_enable.scu_pin}; // SCU: P4_6, PRALINE: PE_1
constexpr GPIO max283x_enable{pin_max283x_enable, map_max283x_enable.gpio_port, map_max283x_enable.gpio_pad, map_max283x_enable.gpio_mode}; // GPIO[2]6, PRALINE: GPIO[7]1
} // namespace gpio_control
namespace power_control {
+2 -32
View File
@@ -34,41 +34,11 @@ namespace one {
/* GPIO */
#ifdef PRALINE
// 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
constexpr GPIO gpio_rffc5072_clock = gpio[GPIO5_18];
constexpr GPIO gpio_rffc5072_data = gpio[GPIO4_14];
#else
constexpr GPIO gpio_rffc5072_clock = gpio[GPIO5_6];
constexpr GPIO gpio_rffc5072_data = gpio[GPIO3_3];
#endif
#ifdef PRALINE
constexpr GPIO gpio_max283x_select = gpio[GPIO6_28];
#else
constexpr GPIO gpio_max283x_select = gpio[GPIO0_15];
#endif
#ifdef PRALINE
// PRALINE uses MAX2831 transceiver with different control pins
constexpr GPIO gpio_max283x_enable = gpio[GPIO7_1]; // MAX2831 ENABLE (PE_1)
// constexpr GPIO gpio_max2831_enable = gpio[GPIO7_1]; // Alias
constexpr GPIO gpio_max2831_rx_enable = gpio[GPIO7_2]; // MAX2831 RX_ENABLE (PE_2)
// constexpr GPIO gpio_max2831_rxhp = gpio[GPIO6_29]; // MAX2831 RXHP (PD_15)
constexpr GPIO gpio_max2831_ld = gpio[GPIO4_11]; // MAX2831 Lock Detect (P9_6)
// Legacy aliases for code compatibility
constexpr GPIO gpio_max2837_rxenable = gpio[GPIO7_2];
constexpr GPIO gpio_max2837_txenable = gpio[GPIO7_2]; // MAX2831 uses single RX/TX control
constexpr GPIO gpio_max2839_rxtx = gpio[GPIO7_2];
#else
constexpr GPIO gpio_max283x_enable = gpio[GPIO2_6];
constexpr GPIO gpio_max2837_rxenable = gpio[GPIO2_5];
constexpr GPIO gpio_max2837_txenable = gpio[GPIO2_4];
constexpr GPIO gpio_max2839_rxtx = gpio[GPIO2_5];
+12
View File
@@ -170,6 +170,7 @@ class Message {
HunterStop = 112,
TetraBsch = 113,
TetraDnb = 114,
AudioDDCConfig = 115,
MAX
};
@@ -310,6 +311,16 @@ class SpectrumStreamingConfigMessage : public Message {
Mode mode{Mode::Stopped};
};
class AudioDDCConfigMessage : public Message {
public:
constexpr AudioDDCConfigMessage(int32_t frequency)
: Message{ID::AudioDDCConfig},
frequency{frequency} {
}
int32_t frequency{0};
};
class WidebandSpectrumConfigMessage : public Message {
public:
constexpr WidebandSpectrumConfigMessage(
@@ -357,6 +368,7 @@ class AudioSpectrumMessage : public Message {
struct ChannelSpectrum {
std::array<uint8_t, 256> db{{0}};
uint32_t sampling_rate{0};
int32_t channel_filter_offset{0};
int32_t channel_filter_low_frequency{0};
int32_t channel_filter_high_frequency{0};
int32_t channel_filter_transition{0};
+5 -7
View File
@@ -33,13 +33,11 @@ namespace portapack {
/* TODO: Make these GPIOs private and expose via appropriate functions. */
constexpr GPIO gpio_io_stbx = gpio[GPIO5_0]; /* P2_0 */
constexpr GPIO gpio_addr = gpio[GPIO5_1]; /* P2_1 */
constexpr GPIO gpio_lcd_te = gpio[GPIO5_3]; /* P2_3 */
constexpr GPIO gpio_dfu = gpio[GPIO5_7]; /* P2_8 */
constexpr GPIO gpio_lcd_rdx = gpio[GPIO5_4]; /* P2_4 */
constexpr GPIO gpio_lcd_wrx = gpio[GPIO1_10]; /* P2_9 */
constexpr GPIO gpio_dir = gpio[GPIO1_13]; /* P2_13 */
constexpr GPIO gpio_io_stbx = gpio[GPIO5_0]; /* P2_0 */
constexpr GPIO gpio_addr = gpio[GPIO5_1]; /* P2_1 */
constexpr GPIO gpio_lcd_te = gpio[GPIO5_3]; /* P2_3 */
constexpr GPIO gpio_lcd_rdx = gpio[GPIO5_4]; /* P2_4 */
constexpr GPIO gpio_dir = gpio[GPIO1_13]; /* P2_13 */
constexpr std::array<GPIO, 8> gpios_data{
gpio[GPIO3_8],
gpio[GPIO3_9],
+1 -3
View File
@@ -82,11 +82,9 @@ void IO::init() {
gpio_dir.output();
gpio_lcd_rdx.output();
gpio_lcd_wrx.output();
gpio_io_stbx.output();
gpio_addr.output();
gpio_rot_a.input();
gpio_rot_b.input();
}
void IO::lcd_backlight(const bool value) {
@@ -166,7 +164,7 @@ uint32_t IO::io_update(const TouchPinsConfig write_value) {
}
gpio_addr.write(addr);
auto dfu_btn = portapack::io.dfu_read() & 0x01;
uint32_t dfu_btn = gpio_control::dfu_button.read();
return (switches_raw & 0x7f) | (dfu_btn << 7);
}
+6 -14
View File
@@ -33,6 +33,8 @@
#include "gpio.hpp"
#include "ui.hpp"
#include "gpio.hpp"
// #include "portapack_persistent_memory.hpp"
// Darkened pixel bit mask for each possible shift value.
@@ -108,18 +110,14 @@ class IO {
constexpr IO(
GPIO gpio_dir,
GPIO gpio_lcd_rdx,
GPIO gpio_lcd_wrx,
GPIO gpio_io_stbx,
GPIO gpio_addr,
GPIO gpio_rot_a,
GPIO gpio_rot_b)
GPIO gpio_rot_a)
: gpio_dir{gpio_dir},
gpio_lcd_rdx{gpio_lcd_rdx},
gpio_lcd_wrx{gpio_lcd_wrx},
gpio_io_stbx{gpio_io_stbx},
gpio_addr{gpio_addr},
gpio_rot_a{gpio_rot_a},
gpio_rot_b{gpio_rot_b} {};
gpio_rot_a{gpio_rot_a} {};
void init();
@@ -270,18 +268,12 @@ class IO {
return gpio_rot_a.read();
}
uint32_t dfu_read() {
return gpio_rot_b.read();
}
private:
const GPIO gpio_dir;
const GPIO gpio_lcd_rdx;
const GPIO gpio_lcd_wrx;
const GPIO gpio_io_stbx;
const GPIO gpio_addr;
const GPIO gpio_rot_a;
const GPIO gpio_rot_b;
static constexpr ioportid_t gpio_data_port_id = 3;
static constexpr size_t gpio_data_shift = 8;
@@ -298,11 +290,11 @@ class IO {
}
void lcd_wr_assert() {
gpio_lcd_wrx.clear();
gpio_control::lcd_wrx.setInactive();
}
void lcd_wr_deassert() {
gpio_lcd_wrx.set();
gpio_control::lcd_wrx.setActive();
}
void io_stb_assert() {
+49 -55
View File
@@ -564,16 +564,14 @@ void LiveDateTime::set_seconds_enabled(bool new_value) {
/* BigFrequency **********************************************************/
BigFrequency::BigFrequency(
Rect parent_rect,
rf::Frequency frequency)
: Widget{parent_rect},
_frequency{frequency} {
}
BigFrequency::BigFrequency(Rect parent_rect, rf::Frequency frequency)
: Widget{parent_rect}, _frequency{frequency} {}
void BigFrequency::set(const rf::Frequency frequency) {
_frequency = frequency;
set_dirty();
if (_frequency != frequency) {
_frequency = frequency;
set_dirty();
}
}
void BigFrequency::paint(Painter& painter) {
@@ -583,63 +581,59 @@ void BigFrequency::paint(Painter& painter) {
Point digit_pos;
ui::Color segment_color;
if (_frequency != _previous_frequency) {
_previous_frequency = _frequency;
rf::Frequency frequency{_frequency};
const auto rect = screen_rect(); // why not use screen_rect() directly for width, ...? it may be too small, but ...
rf::Frequency frequency{_frequency};
const auto rect = screen_rect(); // why not use screen_rect() directly for width, ...? it may be too small, but ...
// Erase
painter.fill_rectangle(
{{0, rect.location().y()}, {screen_width, 52}},
Theme::getInstance()->bg_darkest->background);
// Erase
painter.fill_rectangle(
{{0, rect.location().y()}, {screen_width, 52}},
Theme::getInstance()->bg_darkest->background);
// Prepare digits
if (!frequency) {
digits.fill(10); // ----.---
digit_pos = {(screen_width - ((7 * digit_width) + 8)) / 2, rect.location().y()};
} else {
frequency /= 1000; // GMMM.KKK(uuu)
// Prepare digits
if (!frequency) {
digits.fill(10); // ----.---
digit_pos = {(screen_width - ((7 * digit_width) + 8)) / 2, rect.location().y()};
} else {
frequency /= 1000; // GMMM.KKK(uuu)
for (i = 0; i < 7; i++) {
digits[6 - i] = frequency % 10;
frequency /= 10;
}
// Remove leading zeros
for (i = 0; i < 3; i++) {
if (!digits[i])
digits[i] = 16; // "Don't draw" code
else
break;
}
digit_pos = {(Coord)(screen_width - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()};
for (i = 0; i < 7; i++) {
digits[6 - i] = frequency % 10;
frequency /= 10;
}
segment_color = style().foreground;
// Remove leading zeros
for (i = 0; i < 3; i++) {
if (!digits[i])
digits[i] = 16; // "Don't draw" code
else
break;
}
// Draw
for (i = 0; i < 7; i++) {
digit = digits[i];
digit_pos = {(Coord)(screen_width - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()};
}
if (digit < 16) {
digit_def = segment_font[(uint8_t)digit];
segment_color = style().foreground;
for (size_t s = 0; s < 7; s++) {
if (digit_def & 1)
painter.fill_rectangle({digit_pos + segments[s].location(), segments[s].size()}, segment_color);
digit_def >>= 1;
}
// Draw
for (i = 0; i < 7; i++) {
digit = digits[i];
if (digit < 16) {
digit_def = segment_font[(uint8_t)digit];
for (size_t s = 0; s < 7; s++) {
if (digit_def & 1)
painter.fill_rectangle({digit_pos + segments[s].location(), segments[s].size()}, segment_color);
digit_def >>= 1;
}
}
if (i == 3) {
// Dot
painter.fill_rectangle({digit_pos + Point(34, 48), {4, 4}}, segment_color);
digit_pos += {(digit_width + 8), 0};
} else {
digit_pos += {digit_width, 0};
}
if (i == 3) {
// Dot
painter.fill_rectangle({digit_pos + Point(34, 48), {4, 4}}, segment_color);
digit_pos += {(digit_width + 8), 0};
} else {
digit_pos += {digit_width, 0};
}
}
}
-1
View File
@@ -297,7 +297,6 @@ class BigFrequency : public Widget {
private:
rf::Frequency _frequency;
rf::Frequency _previous_frequency{~0LL};
static constexpr Dim digit_width = 32;
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
#
# copyleft 2026 zxkmm co author with AI
#
# This file is part of PortaPack.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
"""Check that every external app symbol landed in its own app's memory region.
The rules in external.ld match input *section names*, so a glob like
`*(*ui*external_app*level*)` also matches
.text._ZN2ui12external_app18waterfall_designer...12on_add_levelEv
^^^^^ contains "level"
Section assignment is first-match-wins, so a symbol whose mangled name merely
contains another app's name gets linked into that app's region. Only one
external app is resident at a time, so calling it jumps into unmapped memory
and hard faults.
export_external_apps.py already warns about *data* words that point at another
app, but a `bl` is PC-relative: the target never appears as a literal, so that
check cannot see it. This one works on symbol addresses instead and does.
Usage:
check_external_symbol_placement.py [build/firmware/application/application.elf]
"""
import os
import re
import subprocess
import sys
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
DEFAULT_ELF = os.path.join(REPO, "build", "firmware", "application", "application.elf")
DEFAULT_LD = os.path.join(REPO, "firmware", "application", "external", "external.ld")
def find_toolchain():
for prefix in (os.environ.get("ARM_TOOLCHAIN"),
os.path.join(REPO, "armbin", "bin", "arm-none-eabi-"),
"arm-none-eabi-"):
if not prefix:
continue
try:
subprocess.run([prefix + "nm", "--version"], capture_output=True, check=True)
return prefix
except (OSError, subprocess.CalledProcessError):
continue
sys.exit("error: no arm-none-eabi toolchain found (set $ARM_TOOLCHAIN)")
def parse_regions(ld_path):
with open(ld_path) as f:
ld = f.read()
regions = {}
for m in re.finditer(r'ram_external_app_(\w+)\s+\(rwx\)\s*:\s*org\s*=\s*'
r'(0x[0-9A-Fa-f]+),\s*len\s*=\s*(\d+)k', ld):
regions[m.group(1)] = (int(m.group(2), 16), int(m.group(3)) * 1024)
return regions
def main():
elf = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_ELF
if not os.path.exists(elf):
print("skipping symbol placement check, no ELF at %s" % elf)
return 0
tc = find_toolchain()
regions = parse_regions(DEFAULT_LD)
if not regions:
print("skipping symbol placement check, could not parse external.ld")
return 0
def owner(addr):
for name, (base, size) in regions.items():
if base <= addr < base + size:
return name
return None
nm = subprocess.run([tc + "nm", "-C", "--defined-only", elf],
capture_output=True, text=True).stdout
misplaced = []
total = 0
for line in nm.splitlines():
parts = line.split(" ", 2)
if len(parts) < 3 or not re.fullmatch(r'[0-9a-f]{8}', parts[0]):
continue
addr, sym = int(parts[0], 16), parts[2]
if "_veneer" in sym:
continue
m = re.search(r'external_app::(\w+)::', sym)
if not m:
continue
ns, host = m.group(1), owner(addr)
total += 1
if host is None:
continue # inlined into main firmware, harmless
# Namespace and region name need not be identical: ert/ert_app,
# keeloqtx/ui_keeloqtx, secplustx/ui_secplustx. Substring either way is fine.
if ns not in host and host not in ns:
misplaced.append((ns, host, sym))
print("\nchecking placement of %d external app symbols across %d regions"
% (total, len(regions)))
if not misplaced:
print("all external app symbols are in their own app's region")
return 0
print("\nERROR: %d symbol(s) linked into the wrong app's region." % len(misplaced))
print("These will hard fault when called - the owning app is not resident.\n")
for ns, host, sym in misplaced:
print(" %s -> landed in '%s' region" % (ns, host))
print(" %s" % sym)
print("\nFix: make the rule in external.ld specific to the app's own sources, e.g.")
print(" */external/<app>/*(*ui*external_app*<app>*);")
return 1
if __name__ == "__main__":
sys.exit(main())
+3 -2
View File
@@ -21,7 +21,8 @@
# Boston, MA 02110-1301, USA.
#
# external app address ranges below must match those in linker file "external.ld"
# External app address ranges below must match those in linker file "external.ld".
# The end address is exclusive.
maximum_application_size = 32*1024
external_apps_address_start = 0xADB00000
external_apps_address_end = 0xAE0B0000
external_apps_address_end = 0xAE108000
+187
View File
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
#
# copyleft 2026 zxkmm co author with AI
#
# This file is part of PortaPack.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
"""Translate a guru meditation pc/lr into a symbol, source line and disassembly.
Plain `arm-none-eabi-gdb -ex "x/3i 0xADDR" application.elf` only works for
addresses in the main M0 firmware. External apps are linked at a placeholder
0xADxxxxxx address but *run* from ~0x1008xxxx, so the guru shows a runtime
address that does not exist in the ELF. This script does the translation:
link_addr = section_vma + (runtime_addr - memory_location)
where memory_location is the first word of the app's .ppma header.
Usage:
guru_lookup.py 0x10085CF9 0x0FF36284
guru_lookup.py --app waterfall_designer 0x10085CF9
guru_lookup.py --baseband adsbrx 0x10081234
"""
import argparse
import os
import re
import struct
import subprocess
import sys
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
def find_toolchain(explicit):
"""Locate the arm-none-eabi- prefix: --toolchain, $ARM_TOOLCHAIN, ./armbin/bin, PATH."""
candidates = []
if explicit:
candidates.append(explicit)
if os.environ.get("ARM_TOOLCHAIN"):
candidates.append(os.environ["ARM_TOOLCHAIN"])
candidates.append(os.path.join(REPO, "armbin", "bin", "arm-none-eabi-"))
candidates.append("arm-none-eabi-")
for prefix in candidates:
try:
subprocess.run([prefix + "addr2line", "--version"],
capture_output=True, check=True)
return prefix
except (OSError, subprocess.CalledProcessError):
continue
sys.exit("error: no arm-none-eabi toolchain found (try --toolchain /path/to/arm-none-eabi-)")
def run(*cmd):
return subprocess.run(cmd, capture_output=True, text=True).stdout
def section_vmas(tc, elf):
"""section name -> (vma, size) for every .external_app_* section."""
out = run(tc + "objdump", "-h", elf)
vmas = {}
for m in re.finditer(r'\.external_app_(\S+)\s+([0-9a-f]{8})\s+([0-9a-f]{8})', out):
vmas[m.group(1)] = (int(m.group(3), 16), int(m.group(2), 16))
return vmas
def app_load_addresses(build_dir):
"""app name -> memory_location, read from the first word of each .ppma."""
app_dir = os.path.join(build_dir, "firmware", "application")
loads = {}
if not os.path.isdir(app_dir):
return loads
for name in os.listdir(app_dir):
if not name.endswith(".ppma"):
continue
path = os.path.join(app_dir, name)
try:
with open(path, "rb") as f:
(mem,) = struct.unpack("<I", f.read(4))
loads[name[:-5]] = mem
except (OSError, struct.error):
pass
return loads
def describe(tc, elf, addr, label=""):
"""Print addr2line (with inline frames) plus a short disassembly window."""
a = "0x%08x" % addr
src = run(tc + "addr2line", "-f", "-C", "-i", "-e", elf, a).strip()
print(" %s%s" % (label, a))
for i, line in enumerate(src.splitlines()):
print(" %s %s" % ("in" if i % 2 == 0 else " at", line))
dis = run(tc + "objdump", "-d", "--start-address=0x%x" % (addr - 8),
"--stop-address=0x%x" % (addr + 8), elf)
body = [l for l in dis.splitlines() if re.match(r'\s*[0-9a-f]+:\t', l)]
if body:
print(" --")
for l in body:
here = re.match(r'\s*0*%x:' % addr, l)
print(" %s%s" % (" >> " if here else " ", l.strip()))
def main():
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("addresses", nargs="+", help="pc / lr values from the guru screen")
p.add_argument("--build", default=os.path.join(REPO, "build"), help="build directory")
p.add_argument("--app", help="external app name, if auto-detection is ambiguous")
p.add_argument("--baseband", help="resolve against firmware/baseband/baseband_<name>.elf")
p.add_argument("--toolchain", help="arm-none-eabi- prefix")
args = p.parse_args()
tc = find_toolchain(args.toolchain)
app_elf = os.path.join(args.build, "firmware", "application", "application.elf")
if args.baseband:
elf = os.path.join(args.build, "firmware", "baseband",
"baseband_%s.elf" % args.baseband)
if not os.path.exists(elf):
sys.exit("error: no such baseband image: " + elf)
for a in args.addresses:
addr = int(a, 16) & ~1
print("\n%s (M4 baseband: %s)" % (a, args.baseband))
describe(tc, elf, addr)
return
if not os.path.exists(app_elf):
sys.exit("error: %s not found (pass --build)" % app_elf)
vmas = section_vmas(tc, app_elf)
loads = app_load_addresses(args.build)
for a in args.addresses:
addr = int(a, 16) & ~1 # drop the Thumb bit
print("\n=== %s ===" % a)
# Main M0 firmware is linked at 0 (SPIFI shadow), so it resolves directly.
if addr < 0x00100000:
print(" main M0 firmware")
describe(tc, app_elf, addr)
continue
# Otherwise it is an external app running from local SRAM. Every app has
# its own memory_location, so several can plausibly contain the address;
# list each candidate and let the caller pick the app they actually ran.
names = [args.app] if args.app else sorted(loads)
hits = 0
for name in names:
if name not in loads or name not in vmas:
continue
off = addr - loads[name]
vma, size = vmas[name]
if not (0 <= off < size):
continue
hits += 1
print(" candidate: %s load=0x%08X offset=0x%X" % (name, loads[name], off))
describe(tc, app_elf, vma + off, "link ")
if hits == 0:
print(" no external app contains this address.")
print(" If the guru header said M4, re-run with --baseband <image>.")
print(" A wild pc with a sane lr usually means an indirect call through")
print(" a bad pointer - look up the lr instead, that is the caller.")
elif hits > 1 and not args.app:
print(" (%d candidates - narrow it with --app <name>)" % hits)
if __name__ == "__main__":
main()
+214
View File
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
#
# copyleft 2026 zxkmm co author with AI
#
# This file is part of PortaPack.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
"""Report per-function stack frame sizes for an ELF or object file.
The M0 process stack is 4 kB total (__process_stack_size__ in LPC43xx_M0.ld),
shared by the whole UI thread including external apps. A guru that says
"Stack Overflow" is a hard fault where get_free_stack_space() < 16, i.e. the
4 kB really was consumed - it is not a heap problem.
Watch out for anything holding a File: _FS_TINY is 0 and _MAX_SS is 512 in
ffconf.h, so every FIL carries a 512 byte sector cache and a File local costs
~560 bytes of stack. copy_file() holds two of them plus a 512 byte block
buffer, ~1.8 kB in one frame.
Thumb-1 cannot encode `sub sp, #imm` above 508 bytes, so large frames appear as
a negative constant loaded from the literal pool:
ldr r4, [pc, #728] ; (2e0 <...>)
add sp, r4 ; r4 = 0xfffffcfc = -772
Naive greps for `sub sp` miss exactly the frames that matter. This resolves the
literal.
Usage:
stack_usage.py build/firmware/application/application.elf # top 40
stack_usage.py <file> --min 512 # everything >= 512
stack_usage.py <file> --grep copy_file # filter by name
stack_usage.py <file> --chain main run_external_app copy_file # sum a call chain
"""
import argparse
import os
import re
import subprocess
import sys
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
M0_STACK_BYTES = 4096
REG_ORDER = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'sl', 'fp', 'ip', 'sp', 'lr', 'pc']
def find_toolchain(explicit):
for prefix in (explicit, os.environ.get("ARM_TOOLCHAIN"),
os.path.join(REPO, "armbin", "bin", "arm-none-eabi-"),
"arm-none-eabi-"):
if not prefix:
continue
try:
subprocess.run([prefix + "objdump", "--version"],
capture_output=True, check=True)
return prefix
except (OSError, subprocess.CalledProcessError):
continue
sys.exit("error: no arm-none-eabi toolchain found (set $ARM_TOOLCHAIN)")
def push_bytes(reglist):
n = 0
for part in reglist.split(','):
part = part.strip()
if '-' in part:
a, b = part.split('-')
if a in REG_ORDER and b in REG_ORDER:
n += REG_ORDER.index(b) - REG_ORDER.index(a) + 1
else:
n += 2
else:
n += 1
return n * 4
def frame_sizes(tc, path):
"""function name (mangled) -> stack bytes reserved in its prologue."""
out = subprocess.run([tc + "objdump", "-d", path],
capture_output=True, text=True).stdout
bodies, cur = {}, None
for line in out.splitlines():
m = re.match(r'^[0-9a-f]+ <(.+)>:', line)
if m:
cur = m.group(1)
bodies[cur] = []
elif cur is not None:
bodies[cur].append(line)
sizes = {}
for func, body in bodies.items():
# Literal pool words, so `add sp, rN` can be resolved.
literals = {}
for line in body:
m = re.match(r'\s*([0-9a-f]+):\s+([0-9a-f]{8})\s+\.word\s+0x([0-9a-f]+)', line)
if m:
literals[int(m.group(1), 16)] = int(m.group(3), 16)
total, regval = 0, {}
for line in body:
m = re.search(r'ldr\s+(\w+), \[pc, #\d+\].*;\s*\(([0-9a-f]+)', line)
if m:
regval[m.group(1)] = literals.get(int(m.group(2), 16))
m = re.search(r'\bsub(?:\.w)?\s+sp, (?:sp, )?#(\d+)', line)
if m:
total += int(m.group(1))
# Thumb-1 large frame: add sp, rN where rN holds a negative literal.
# Bound it: the same register may instead hold a data address, and
# external app addresses (0xADxxxxxx) also look "negative" here.
m = re.search(r'\badd\s+sp, (r\d+|sl|fp|ip)\b', line)
if m:
v = regval.get(m.group(1))
if v and v > 0x80000000:
adjust = 0x100000000 - v
if adjust <= 2 * M0_STACK_BYTES:
total += adjust
m = re.search(r'\bpush(?:\.w)?\s+\{(.+)\}', line)
if m:
total += push_bytes(m.group(1))
sizes[func] = total
return sizes
def demangle(tc, names):
if not names:
return []
out = subprocess.run([tc + "c++filt"], input="\n".join(names),
capture_output=True, text=True).stdout
return out.splitlines()
def main():
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("path", help="ELF or .obj to analyse")
p.add_argument("--min", type=int, default=0, help="only show frames >= this many bytes")
p.add_argument("--top", type=int, default=40, help="max rows to print (0 = all)")
p.add_argument("--grep", help="only functions whose demangled name matches this regex")
p.add_argument("--chain", nargs="+",
help="sum the frames of these functions (substring match) as a call chain")
p.add_argument("--toolchain", help="arm-none-eabi- prefix")
args = p.parse_args()
if not os.path.exists(args.path):
sys.exit("error: no such file: " + args.path)
tc = find_toolchain(args.toolchain)
sizes = frame_sizes(tc, args.path)
names = list(sizes)
pretty = dict(zip(names, demangle(tc, names)))
if args.chain:
print("\ncall chain, M0 process stack is %d bytes\n" % M0_STACK_BYTES)
total = 0
for want in args.chain:
hit = None
for n in names:
if want in pretty[n] or want in n:
if hit is None or sizes[n] > sizes[hit]:
hit = n
if hit is None:
print(" %6s %s (not found)" % ("?", want))
continue
total += sizes[hit]
print(" %6d %s" % (sizes[hit], pretty[hit][:96]))
pct = 100.0 * total / M0_STACK_BYTES
print(" " + "-" * 60)
print(" %6d total (%.0f%% of the 4 kB stack, %d bytes free)"
% (total, pct, M0_STACK_BYTES - total))
if total > M0_STACK_BYTES:
print("\n OVERFLOWS - this chain cannot fit.")
elif pct > 75:
print("\n Tight. Anything the callees add on top may fault.")
return
rows = [(v, pretty[k]) for k, v in sizes.items() if v >= args.min]
if args.grep:
rx = re.compile(args.grep)
rows = [r for r in rows if rx.search(r[1])]
rows.sort(key=lambda r: -r[0])
if args.top:
rows = rows[:args.top]
print("\n%6s %s" % ("bytes", "function"))
for v, n in rows:
flag = " <-- over half the stack" if v > M0_STACK_BYTES // 2 else ""
print("%6d %s%s" % (v, n[:100], flag))
if __name__ == "__main__":
main()
+1 -1
Submodule hackrf updated: 365dfbc8c4...749bca5f10
+1
View File
@@ -0,0 +1 @@
Superrollo;BADBEEF;1;5
+1
View File
@@ -0,0 +1 @@
Superrollo;BADBEEF;1;7
+1
View File
@@ -0,0 +1 @@
Superrollo;BADBEEF;1;3
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB