Compare commits

...

16 Commits

Author SHA1 Message Date
未来方舟 e3eafbeb66 Add benchmark function for mbt (#3255) 2026-07-07 11:32:00 +02:00
未来方舟 e3b3fa2fc8 fine tune app search ui (#3256) 2026-07-07 11:06:31 +02:00
未来方舟 5f0ae22f66 remove my dumb nicknames in files (#3254) 2026-07-07 11:04:34 +02:00
Totoo f9dd28f31d fix dfu button (#3252) 2026-07-07 00:31:44 +02:00
Matej Sochan b99cec5111 Feature/signal hunter (#3245) 2026-07-06 11:05:49 +08:00
Pezsma fe2ba80a10 refactor: simplify audio volume handling in AudioVolumeField (#3246) 2026-07-04 08:53:28 +02:00
Luca b5d84fcc4d Airband VOR receiver and trasmitter (#3244)
* First test

* Reorder includes for consistency and fix string dereference in VorCdiIndicator

* Add memory section and linker script entry for vor_rx application

* Testing VOR TX

* Moving VOR TX to SD

* Code refactoring to align with guidelines

* Add TODO comment to verify radial sign convention in vor_tx_config

* Remove unnecessary set_dirty() calls in VorRxView methods

* Update Course Deviation Indicator label and adjust UI element positions

* Update VorTxView to run prepared image from memory map

* Update build condition in nightly release workflow to include workflow_dispatch event

* Reverting action changes

* Adjust Course Deviation Indicator layout and refine painting logic

* Refactor VorRxView UI elements for improved layout and clarity

* Add calibration field and update radial calculation logic in VorRxView

* Enhance VOR processing: add renormalization to phase oscillator, improve signal handling, and update configuration parameters

* Implement radial smoothing and TO/FROM state handling in VorRxView; update VOR processing logic

* Setting comments inline

* Refactor VorCdiIndicator and VorRxView: change normalize_signed_degrees to use int32_t, update radial smoothing to use fixed point arithmetic to reduce flash usage

* Update external app address end to 0xAE0B0000

* Update set_vor_tx_config to include enabled parameter for consistency

* Refactor VorRx to omit decim_2 stage and update comments for clarity on VOR processing

* Add RSSI, Channel, and Audio fields to VorRxView to mimic existing apps

* Refactor VorRxView labels and status messages

* Update VorRxView calibration field range to allow full circle input

* Completing Ident transmission logic

* Fixing TX gain and moving log label

* Refactor VOR phase calculations and update radial offset handling for accurate bearing representation

* Add low-pass filters to strip 9960 Hz subcarrier from audio output
2026-07-04 08:49:21 +02:00
Sandbox 7481aefe9f customizable name attack option "CUSTOM" for the BLESPAM App (#3239) 2026-07-02 16:14:17 +02:00
未来方舟 6b641c8a5d You can search app now (#3241) 2026-07-02 10:19:53 +02:00
Pezsma 2ad34bbc09 Gpio modify v3 (#3238)
* Refactor GPIO mappings and definitions for improved clarity and functionality

- Updated SCU_ARRAY_SIZE definitions in pal_lld.h for PRALINE and non-PRALINE configurations.
- Modified pin mappings in gpio.hpp to reflect new hardware configurations, including additional control pins for RF and audio components.
- Removed obsolete GPIO definitions from hackrf_gpio.hpp and streamlined the code for better maintainability.
- Added new GPIO definitions for mix bypass, amplifier control, and other RF-related functionalities to enhance the system's capabilities.

* Refactor GPIO mappings and update SCU_ARRAY_SIZE for LPC43xx platform

- Updated SCU_ARRAY_SIZE to 80 for PRALINE configuration and 58 for others.
- Added new GPIO mappings for auxiliary power control, antenna bias, and R9 clock enable signals in gpio.hpp.
- Removed commented-out PPS output mapping in hackrf_gpio.hpp.
- Adjusted GPIO definitions for R9 clock signals to ensure proper functionality.

* Refactor RF path initialization and configuration for improved clarity and functionality

* Refactor RF path configuration and GPIO mappings for improved clarity and functionality

* Potential fix for pull request finding

* comment

* aux power polarrity

* Fix formatting of pin_aux_power_enable declaration
2026-07-02 10:05:34 +02:00
Totoo 9c74a1f392 fix mbt build on rare errors (#3242) 2026-07-01 14:42:25 +02:00
gullradriel abadf35e06 Lna investigations and fix (#3240)
* The LNA gain field 'L' lives in LPF_VGA_2 (reg 6), not RXRF_2 (reg 2). RXRF_2 only holds LNAgain_SPI_EN. The configure_rx_gain() function wrote 'L' into lpf_vga_2 but marked RXRF_2 dirty => the LNA value only got flushed incidentally because the following VGA write marks LPF_VGA_2 dirty. Drop the stray RXRF_2 dirty flag and rely on the LPF_VGA_2 flag, which carries both L and VGA.

* The baseband build defined IS_H1_R9 as the compile-time constant 0, on the (now incorrect) assumption that detected_platform() isn't linked. The sd_over_usb image is the only baseband target that compiles the IS_H1_R9-gated upstream files (rf_path.c, si5351c.c, sgpio.c, platform_gpio.c, platform_scu.c), and it *does* link platform_detect.c and call detect_hardware_platform() at startup. Forcing IS_H1_R9=0 dead-stripped every 'if (IS_H1_R9)' branch, so a real HackRF One r9 was set up with the OG clock/GPIO/RF-path config in that image.
Define IS_H1_R9 as the runtime '(detected_platform()==BOARD_ID_HACKRF1_R9)', mirroring upstream platform-detect.cmake for HACKRF_ONE. All usages are '#ifdef IS_H1_R9' + 'if (IS_H1_R9)' (no arithmetic '#if'), so a function-call expression is safe, single-quoted -D with no internal spaces keeps it intact through the shell-split DDEFS blob.

---------

Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-07-01 08:47:07 +02:00
Totoo 288e87537a MAX 17055 sleep bug fixes. 2026-06-25 09:08:44 +02:00
gullradriel d25899de10 Upgrade subm plus warning fix (#3235) 2026-06-25 08:46:36 +02:00
Totoo 8a16ec829c fix clock source change on praline (#3233)
* fix clock source change on praline

* whoops
2026-06-22 16:50:16 +02:00
Totoo f6d6e3be4c Fix battery display bug, and the flash time (#3232)
* battfix

* por again, but nicer
2026-06-22 16:20:49 +02:00
92 changed files with 4032 additions and 733 deletions
+1
View File
@@ -83,3 +83,4 @@ venv/
# generated bitmap arr file
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
/firmware/tools/bitmap.hpp
tmp/*
+2
View File
@@ -513,3 +513,5 @@ add_custom_target(
${PROJECT_NAME}
DEPENDS ${PROJECT_NAME}.bin
)
add_dependencies(${PROJECT_NAME} baseband)
@@ -168,7 +168,7 @@ bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
{0, 0, portapack::display.width(), portapack::display.height()},
Theme::getInstance()->bg_darkest->background);
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15-45 sec.");
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
+6 -2
View File
@@ -6,7 +6,7 @@
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyright (C) 2024 HTotoo
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -1174,13 +1174,17 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
pmem::set_battery_replaceable(checkbox_battery_replaceable.value());
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
if (dev) dev->resetChangedFlag();
if ((((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) || (dev && dev->getIsBattChanged())) {
pmem::set_battery_cap_mah(field_battcap.value());
if (dev) dev->resetChangedFlag();
if (dev && !dev->reInit()) {
nav.display_modal("Error", "Battery gauge re-init failed");
return;
}
if (dev) {
dev->resetChangedFlag();
dev->update(); // resend the new data
}
}
send_system_refresh();
nav.pop();
+1 -1
View File
@@ -5,7 +5,7 @@
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+18
View File
@@ -361,6 +361,16 @@ void set_noaaapt_config() {
send_message(&message);
}
void set_vor_config(bool enabled) {
const VorRxConfigureMessage message{enabled};
send_message(&message);
}
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled, const std::string& ident, bool enabled) {
const VorTxConfigureMessage message{radial_deg, ident_enabled, ident.c_str(), enabled};
send_message(&message);
}
void set_flex_config() {
const FlexConfigureMessage message{};
send_message(&message);
@@ -445,6 +455,14 @@ void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length) {
send_message(&msg);
}
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start) {
HunterConfigMessage message{};
message.energy_threshold = threshold;
message.hangtime_ms = hangtime_ms;
message.start = start;
send_message(&message);
}
static bool baseband_image_running = false;
bool is_image_running() {
+3
View File
@@ -113,6 +113,8 @@ void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta);
void set_morsetx_key(bool key_down);
void set_wefax_config(uint8_t lpm, uint8_t ioc);
void set_noaaapt_config();
void set_vor_config(bool enabled = true);
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled = true, const std::string& ident = "", bool enabled = true);
void set_flex_config();
void set_bitstream_config(uint32_t deviation, uint8_t mode); // mode 0 for am, 1 for 2fsk
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload = nullptr, uint16_t payload_length = 0); // baud*100
@@ -120,6 +122,7 @@ void set_rtty_config(RTTYDataMessage& message);
void set_epirb_tx_config(EPIRBTXDataMessage& message);
void set_epirb_rx_config(EPIRBRXConfig& message);
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length);
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start);
void request_roger_beep();
void request_rssi_beep();
+12 -11
View File
@@ -419,7 +419,11 @@ std::string ClockManager::get_freq() {
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
}
static void portapack_tcxo_enable() {
void ClockManager::portapack_tcxo_enable() {
#ifdef PRALINE
gpio_control::clkin_ctrl.setActive();
set_p1_control(P1_Function::P22_ClkIn);
#endif
portapack::io.reference_oscillator(true);
/* Delay >10ms at 96MHz clock speed for reference oscillator to start. */
@@ -428,7 +432,10 @@ static void portapack_tcxo_enable() {
while (delay--);
}
static void portapack_tcxo_disable() {
void ClockManager::portapack_tcxo_disable() {
#ifdef PRALINE
gpio_control::clkin_ctrl.setInactive();
#endif
portapack::io.reference_oscillator(false);
}
@@ -436,11 +443,6 @@ static void portapack_tcxo_disable() {
using namespace hackrf::one;
void ClockManager::init_clock_generator() {
if (hackrf_r9) {
gpio_r9_mcu_clk_en.output();
gpio_r9_mcu_clk_en.write(1);
}
clock_generator.reset();
clock_generator.set_crystal_internal_load_capacitance(CrystalInternalLoadCapacitance::XTAL_CL_8pF);
clock_generator.enable_fanout();
@@ -678,7 +680,7 @@ ClockManager::Reference ClockManager::choose_reference() {
}
#else
if (hackrf_r9) {
gpio_r9_clkin_en.write(1);
gpio_control::r9_clkin_en.setActive();
volatile uint32_t delay = 240000 + 24000;
while (delay--);
}
@@ -693,7 +695,7 @@ ClockManager::Reference ClockManager::choose_reference() {
}
if (hackrf_r9) {
gpio_r9_clkin_en.write(0);
gpio_control::r9_clkin_en.setInactive();
}
#endif
@@ -1180,8 +1182,7 @@ void ClockManager::enable_clock_output(bool enable) {
}
#else
if (hackrf_r9) {
gpio_r9_clkout_en.output();
gpio_r9_clkout_en.write(enable);
gpio_control::r9_clkout_en.setState(enable);
// NOTE: RETURNING HERE IF HACKRF_R9 TO PREVENT CLK2 FROM BEING DISABLED OR FREQ MODIFIED SINCE CLK2 ON R9 IS
// USED FOR BOTH CLKOUT AND FOR THE MCU_CLOCK (== GP_CLKIN) WHICH OTHER LP43XX CLOCKS CURRENTLY RELY ON.
+3
View File
@@ -116,6 +116,9 @@ class ClockManager {
void enable_clock_output(bool enable);
void portapack_tcxo_enable();
void portapack_tcxo_disable();
private:
I2C& i2c0;
si5351::Si5351& clock_generator;
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+66 -4
View File
@@ -294,11 +294,11 @@ void BLESpamView::createWindowsPacket() {
std::copy(res.begin(), res.end(), advertisementData);
}
void BLESpamView::createNameSpamPacket() {
const char* names[] = {"PortaHack", "PwnBt", "iSpam", "GenericFoodVagon", "SignalSnoop", "ByteBandit", "RadioRogue", "RadioRebel", "ByteBlast"};
const char* name = names[rand() % 9]; //"PortaHack";
// Builds a "Complete Local Name" HID advertisement for the given name.
// Shared by NameSpam (built-in list) and Custom (SD card list) modes.
void BLESpamView::buildNamePacket(const char* name) {
uint8_t name_len = strlen(name);
if (name_len > 19) name_len = 19; // clamp so the hex string fits advertisementData[63]
uint8_t size = 12 + name_len;
uint8_t i = 0;
@@ -327,6 +327,65 @@ void BLESpamView::createNameSpamPacket() {
std::copy(res.begin(), res.end(), advertisementData);
}
void BLESpamView::createNameSpamPacket() {
const char* names[] = {"PortaHack", "PwnBt", "iSpam", "GenericFoodVagon", "SignalSnoop", "ByteBandit", "RadioRogue", "RadioRebel", "ByteBlast"};
buildNamePacket(names[rand() % 9]);
}
// Opens /BLESPAM/NAME.txt once and keeps it open for the session.
void BLESpamView::open_custom_file() {
custom_loaded_ = true; // attempt only once
auto open_error = custom_file_.open(u"/BLESPAM/NAME.txt");
custom_file_valid_ = !open_error; // false -> caller falls back to NameSpam
}
// Reads the next line into custom_name_ (clamped to 19 chars), advancing the
// file position. At end of file it seeks back to the start to cycle. Returns
// false if the file holds no usable name. Only one line is ever held in RAM.
bool BLESpamView::read_next_custom_name() {
if (!custom_file_valid_) return false;
custom_name_.clear();
bool wrapped = false;
char c;
while (true) {
auto read_result = custom_file_.read(&c, 1);
if (read_result.is_error()) return false;
if (read_result.value() == 0) {
// end of file
if (!custom_name_.empty()) break;
// use the final line
if (wrapped) return false;
// no usable name in file
if (custom_file_.seek(0).is_error()) return false;
// loop back to the first line
wrapped = true;
continue;
}
if (c == '\n' || c == '\r') {
if (!custom_name_.empty()) break; // got a complete line
continue; // skip blank lines / CRLF pairs
}
if (custom_name_.size() < 19) // clamp; keep scanning rest of long line
custom_name_.push_back(c);
}
return true;
}
void BLESpamView::createCustomPacket() {
if (!custom_loaded_) open_custom_file();
if (!read_next_custom_name()) { // no file / unreadable / empty -> fall back
createNameSpamPacket();
return;
}
buildNamePacket(custom_name_.c_str());
}
char BLESpamView::randomNameChar() {
int randVal = rand() % 62; // 26 uppercase + 26 lowercase + 10 digits
if (randVal < 26) {
@@ -622,6 +681,9 @@ void BLESpamView::createPacket(ATK_TYPE attackType) {
case ATK_NAMERANDOM:
createNameRandomPacket();
break;
case ATK_CUSTOM:
createCustomPacket();
break;
case ATK_ALL_SAFE:
createAnyPacket(true);
break;
+14 -2
View File
@@ -39,6 +39,7 @@
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "file.hpp"
using namespace ui;
@@ -52,6 +53,7 @@ enum ATK_TYPE {
ATK_SAMSUNG,
ATK_NAMESPAM,
ATK_NAMERANDOM,
ATK_CUSTOM,
ATK_ALL_SAFE,
ATK_ALL
};
@@ -131,8 +133,9 @@ class BLESpamView : public View {
{"Samsung", 4},
{"NameSpam", 5},
{"NameRandom", 6},
{"All-Safe", 7},
{"All", 8}}};
{"NameCustom", 7},
{"All-Safe", 8},
{"All", 9}}};
bool is_running{false};
@@ -144,6 +147,11 @@ class BLESpamView : public View {
bool randomMac{true};
bool randomDev{true};
File custom_file_{}; // kept open while in Custom mode
std::string custom_name_{}; // holds only the current name (clamped)
bool custom_loaded_{false}; // true once an open attempt has been made
bool custom_file_valid_{false}; // true if /BLESPAM/NAME.txt opened successfully
uint8_t channel_number = 37;
char mac[13] = "010203040407";
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
@@ -156,7 +164,11 @@ class BLESpamView : public View {
void createIosPacket(bool crash);
void createSamsungPacket();
void createWindowsPacket();
void buildNamePacket(const char* name); // shared name advertisement builder (NameSpam / Custom)
void createNameSpamPacket();
void createCustomPacket();
void open_custom_file(); // open /BLESPAM/NAME.txt once (one name per line)
bool read_next_custom_name(); // read next line into custom_name_, looping at EOF
void createNameRandomPacket();
void createAnyPacket(bool safe);
void createPacket(ATK_TYPE attackType);
+15
View File
@@ -133,6 +133,14 @@ set(EXTCPPSRC
external/noaaapt_rx/main.cpp
external/noaaapt_rx/ui_noaaapt_rx.cpp
#vor_rx
external/vor_rx/main.cpp
external/vor_rx/ui_vor_rx.cpp
#vor_tx
external/vor_tx/main.cpp
external/vor_tx/ui_vor_tx.cpp
#shoppingcart_lock 272 bytes
external/shoppingcart_lock/main.cpp
external/shoppingcart_lock/shoppingcart_lock.cpp
@@ -367,6 +375,10 @@ set(EXTCPPSRC
external/secplustx/main.cpp
external/secplustx/ui_secplustx.cpp
external/secplustx/secplustx.cpp
#signal_hunter
external/signal_hunter/main.cpp
external/signal_hunter/ui_signal_hunter.cpp
)
set(EXTAPPLIST
@@ -401,6 +413,8 @@ set(EXTAPPLIST
acars_rx
wefax_rx
noaaapt_rx
vor_rx
vor_tx
shoppingcart_lock
ookbrute
ook_editor
@@ -457,6 +471,7 @@ set(EXTAPPLIST
two_tone_rx
hard_reset
secplustx
signal_hunter
)
# sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds
+21
View File
@@ -111,6 +111,9 @@ MEMORY
ram_external_app_flex_tx (rwx) : org = 0xAE060000, len = 32k
ram_external_app_hard_reset (rwx) : org = 0xAE070000, len = 32k
ram_external_app_secplustx (rwx) : org = 0xAE080000, len = 32k
ram_external_app_vor_rx (rwx) : org = 0xAE090000, len = 32k
ram_external_app_vor_tx (rwx) : org = 0xAE0A0000, len = 32k
ram_external_app_signal_hunter (rwx) : org = 0xAE0B0000, len = 32k
}
SECTIONS
@@ -391,6 +394,18 @@ SECTIONS
*(*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*);
} > 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*);
} > ram_external_app_vor_tx
.external_app_breakout : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_breakout.application_information));
@@ -642,4 +657,10 @@ SECTIONS
KEEP(*(.external_app.app_secplustx.application_information));
*(*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*);
} > ram_external_app_signal_hunter
}
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2023 Mark Thompson
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2023 Mark Thompson
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2020 euquiq
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2020 euquiq
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -1,5 +1,5 @@
/*
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
@@ -1,5 +1,5 @@
/*
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2026 Matej Sochan
*
* 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.hpp"
#include "ui_signal_hunter.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::signal_hunter {
void initialize_app(ui::NavigationView& nav) {
nav.push<SignalHunterAppView>();
}
} // namespace ui::external_app::signal_hunter
extern "C" {
__attribute__((section(".external_app.app_signal_hunter.application_information"), used)) application_information_t _application_information_signal_hunter = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::signal_hunter::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "SigHunter",
/*.bitmap_data = */ {0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0x98, 0x19, 0x8C, 0x31, 0x84, 0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x21, 0x8C, 0x31, 0x98, 0x19, 0xF0, 0x0F, 0xE0, 0x07, 0x80, 0x01, 0x80, 0x01},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = */ {'H', 'U', 'N', 'T'},
/*.m4_app_offset = */ 0x00000000,
};
}
@@ -0,0 +1,403 @@
/*
* Copyright (C) 2026 Matej Sochan
*
* 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_signal_hunter.hpp"
#include "receiver_model.hpp"
#include "baseband_api.hpp"
#include "ui_fileman.hpp"
#include "freqman_db.hpp"
#include "string_format.hpp"
#include "io_file.hpp"
#include "rtc_time.hpp"
using namespace portapack;
namespace ui::external_app::signal_hunter {
// --- TAB 1: Main View ---
HunterMainView::HunterMainView(Rect parent_rect, SignalHunterAppView& parent)
: View(parent_rect), parent_app(parent) {
add_children({&text_current_freq, &text_status, &button_start_stop, &text_hits});
update_frequency(433920000);
button_start_stop.on_select = [this](Button&) {
parent_app.is_hunting = !parent_app.is_hunting;
if (parent_app.is_hunting) {
button_start_stop.set_text("STOP");
// Set initial frequency based on set mode (single freq / freq hopping)
if (parent_app.freq_hop_mode && !parent_app.frequency_list.empty()) {
parent_app.current_freq_index = 0;
auto freq = parent_app.frequency_list[0];
receiver_model.set_target_frequency(freq);
update_frequency(freq);
} else {
receiver_model.set_target_frequency(parent_app.single_frequency);
update_frequency(parent_app.single_frequency);
}
update_status("HUNTING...", Theme::getInstance()->fg_green);
parent_app.send_hunter_config(true);
} else {
button_start_stop.set_text("START");
update_status("IDLE", Theme::getInstance()->fg_light);
set_recording_state(false);
parent_app.send_hunter_config(false);
}
};
}
void HunterMainView::on_show() {
if (!parent_app.freq_hop_mode) {
current_freq_ = receiver_model.target_frequency();
}
update_frequency(current_freq_); // force repaint
}
void HunterMainView::focus() {
if (!parent_app.freq_hop_mode) {
current_freq_ = receiver_model.target_frequency();
}
update_frequency(current_freq_);
button_start_stop.focus();
}
void HunterMainView::set_recording_state(bool recording) {
if (recording)
text_current_freq.set_style(Theme::getInstance()->fg_red);
else
text_current_freq.set_style(Theme::getInstance()->fg_light);
text_current_freq.set_dirty(); // force ui update
update_frequency(current_freq_); // repaint with the new color
}
void HunterMainView::update_frequency(rf::Frequency freq) {
current_freq_ = freq;
text_current_freq.set(to_string_dec_uint(freq) + " Hz");
}
void HunterMainView::update_status(const std::string& status, const Style* style) {
text_status.set(status);
text_status.set_style(style);
}
void HunterMainView::update_hits(uint32_t hits) {
text_hits.set("Hits: " + to_string_dec_uint(hits));
}
// --- TAB 2: Freqs View ---
HunterFreqsView::HunterFreqsView(Rect parent_rect, SignalHunterAppView& parent)
: View(parent_rect), parent_app(parent) {
add_children({&button_load_file,
&button_clear,
&labels,
&field_dwell,
&text_loaded_info});
field_dwell.set_value(parent_app.hop_dwell_ms);
field_dwell.on_change = [this](int32_t v) { parent_app.hop_dwell_ms = v; };
button_load_file.on_select = [this](Button&) {
auto* view = parent_app.get_nav().push<FileLoadView>(".TXT");
if (view) {
view->on_changed = [this](std::filesystem::path path) {
parent_app.frequency_list.clear();
parent_app.current_freq_index = 0;
FreqmanDB db{};
db.open(path);
for (const auto& entry : db) {
if (entry.frequency_a > 0)
parent_app.frequency_list.push_back(entry.frequency_a);
}
parent_app.freqman_file = path.stem().string();
// Switch to HOP mode if a file was loaded
if (!parent_app.frequency_list.empty())
parent_app.freq_hop_mode = true;
update_list_count();
// Update the mode display in the Config tab
if (parent_app.get_config_view()) {
parent_app.get_config_view()->update_mode_display();
}
};
}
};
button_clear.on_select = [this](Button&) {
parent_app.frequency_list.clear();
parent_app.current_freq_index = 0;
parent_app.freq_hop_mode = false;
update_list_count();
// Update the mode display in the Config tab
if (parent_app.get_config_view()) {
parent_app.get_config_view()->update_mode_display();
}
};
}
void HunterFreqsView::update_list_count() {
text_loaded_info.set("Loaded: " +
to_string_dec_uint(parent_app.frequency_list.size()) + " freqs");
}
void HunterFreqsView::focus() {
button_load_file.focus();
}
// --- TAB 3: Config View ---
HunterConfigView::HunterConfigView(Rect parent_rect, SignalHunterAppView& parent)
: View(parent_rect), parent_app(parent), field_single_freq{{UI_POS_X(2), UI_POS_Y(4)}, parent.get_nav()} {
add_children({&button_mode,
&field_single_freq,
&labels,
&field_threshold,
&field_hang_time,
&text_info_config});
button_mode.on_select = [this](Button&) {
parent_app.freq_hop_mode = !parent_app.freq_hop_mode;
update_mode_display();
};
field_single_freq.set_value(parent_app.single_frequency);
field_threshold.set_value(parent_app.energy_threshold);
field_threshold.on_change = [this](int32_t v) { parent_app.energy_threshold = v; };
field_hang_time.set_value(parent_app.hangtime_ms);
field_hang_time.on_change = [this](int32_t v) { parent_app.hangtime_ms = v; };
update_mode_display();
}
void HunterConfigView::on_show() {
auto current = receiver_model.target_frequency();
parent_app.single_frequency = current;
field_single_freq.set_value(current);
}
void HunterConfigView::update_mode_display() {
if (parent_app.freq_hop_mode)
button_mode.set_text("MODE: HOP (" +
to_string_dec_uint(parent_app.frequency_list.size()) + " freqs)");
else
button_mode.set_text("MODE: SINGLE");
}
void HunterConfigView::focus() {
field_threshold.focus();
}
// --- Main App View ---
SignalHunterAppView::SignalHunterAppView(ui::NavigationView& nav)
: frequency_list{},
nav_(nav) {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
receiver_model.set_target_frequency(433920000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_sampling_rate(2000000);
receiver_model.enable();
view_main = std::make_unique<HunterMainView>(content_rect, *this);
view_freqs = std::make_unique<HunterFreqsView>(content_rect, *this);
view_config = std::make_unique<HunterConfigView>(content_rect, *this);
tab_view = std::make_unique<TabView>(std::initializer_list<TabView::TabDef>{
{"Target", Color::cyan(), view_main.get()},
{"Freqs", Color::green(), view_freqs.get()},
{"Config", Color::yellow(), view_config.get()}});
tab_view->set_parent_rect(view_rect);
add_children({&field_lna,
&field_vga,
&field_rf_amp,
&rssi,
tab_view.get(),
view_main.get(),
view_freqs.get(),
view_config.get()});
view_freqs->hidden(true);
view_config->hidden(true);
using namespace app_settings;
settings_ = std::make_unique<SettingsManager>(
"ext_signal_hunter", Mode::RX,
SettingBindings{
{"threshold"sv, &energy_threshold},
{"hangtime_ms"sv, &hangtime_ms},
{"hop_dwell_ms"sv, &hop_dwell_ms},
{"single_freq"sv, &single_frequency},
{"file"sv, &freqman_file}});
}
SignalHunterAppView::~SignalHunterAppView() {
if (!freq_hop_mode) {
single_frequency = receiver_model.target_frequency();
}
// Safely shutdown recording
stop_recording();
receiver_model.disable();
baseband::shutdown();
}
void SignalHunterAppView::focus() {
if (tab_view)
tab_view->focus();
}
// TIMER LOGIC: Based on display refresh rate
void SignalHunterAppView::on_frame_sync() {
// Timer running only if, HUNTING, in HOPPING mode, not recording + we have the freqs list
if (is_hunting && freq_hop_mode && !capture_thread && !frequency_list.empty()) {
hop_timer_ms += 17; // 1 frame in a 60 Hz display ~ 16.6 ms
// if Dwell Time has passed, change frequency
if (hop_timer_ms >= hop_dwell_ms) {
hop_timer_ms = 0;
current_freq_index = (current_freq_index + 1) % frequency_list.size();
auto next_freq = frequency_list[current_freq_index];
receiver_model.set_target_frequency(next_freq);
if (view_main) {
view_main->update_frequency(next_freq);
}
}
} else {
hop_timer_ms = 0; // if not hunting / recording -> timer = 0
}
}
// Recording control logic via M0 <-> M4 IPC
void SignalHunterAppView::send_hunter_config(bool start) {
// Send config to baseband processor via IPC mechanism
baseband::set_hunter_config(energy_threshold, hangtime_ms, start);
if (!start) {
stop_recording();
}
}
void SignalHunterAppView::on_hunter_trigger(const HunterTriggerMessage* /*message*/) {
// M4 has detected signal above threshold - start I/Q capture
if (!capture_thread && is_hunting) {
trigger_hits++;
view_main->update_hits(trigger_hits);
view_main->update_status("RECORDING!", Theme::getInstance()->fg_red);
view_main->set_recording_state(true);
start_recording();
}
}
void SignalHunterAppView::on_hunter_stop(const HunterStopMessage*) {
if (capture_thread) {
stop_recording();
view_main->set_recording_state(false);
if (is_hunting) {
if (freq_hop_mode && !frequency_list.empty()) {
// Next frequency
current_freq_index = (current_freq_index + 1) % frequency_list.size();
auto next_freq = frequency_list[current_freq_index];
receiver_model.set_target_frequency(next_freq);
view_main->update_frequency(next_freq);
// Reset the timer
hop_timer_ms = 0;
}
view_main->update_status("HUNTING...", Theme::getInstance()->fg_green);
}
}
}
void SignalHunterAppView::start_recording() {
rtc::RTC datetime{};
rtc_time::now(datetime);
// Generate timestamped filename for capture session
const std::string capture_filename = "HNT_" +
to_string_dec_uint(datetime.year(), 4, '0') +
to_string_dec_uint(datetime.month(), 2, '0') +
to_string_dec_uint(datetime.day(), 2, '0') + "T" +
to_string_dec_uint(datetime.hour(), 2, '0') +
to_string_dec_uint(datetime.minute(), 2, '0') +
to_string_dec_uint(datetime.second(), 2, '0');
// Use FileWriter for direct raw I/Q dump to avoid M0 CPU bottlenecks.
// FileConvertWriter is heavier
auto writer = std::make_unique<FileWriter>();
auto create_error = writer->create(std::filesystem::path(u"/CAPTURES") / (capture_filename + ".C16"));
if (create_error.is_valid()) {
if (view_main) view_main->update_status("SD ERROR", Theme::getInstance()->fg_red);
send_hunter_config(false);
is_hunting = false;
if (view_main) view_main->set_start_button_text("START");
return;
}
current_capture_filename = capture_filename;
capture_thread = std::make_unique<CaptureThread>(
std::move(writer),
4096,
4,
[]() {},
[](File::Error) {});
if (view_main) view_main->update_status("RECORDING!", Theme::getInstance()->fg_red);
}
void SignalHunterAppView::stop_recording() {
// Stop I/Q capture and release SD card for metadata write
capture_thread.reset();
// Generate metadata file after capture completes
if (!current_capture_filename.empty()) {
File txt_file;
auto txt_error = txt_file.create(std::filesystem::path(u"/CAPTURES") / (current_capture_filename + ".TXT"));
// is_valid() returns true only if error is present (counter-intuitive)
if (!txt_error.is_valid()) {
std::string metadata =
"center_frequency=" + to_string_dec_uint(receiver_model.target_frequency()) + "\n" +
// Divide main sample rate by decimation factor (8) to match C16 sample rate
"sample_rate=" + to_string_dec_uint(receiver_model.sampling_rate() / 8) + "\n";
txt_file.write(metadata.c_str(), metadata.length());
}
// Clear filename for next capture cycle
current_capture_filename.clear();
}
}
} // namespace ui::external_app::signal_hunter
@@ -0,0 +1,192 @@
/*
* Copyright (C) 2026 Matej Sochan
*
* 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.
*/
#ifndef __UI_SIGNAL_HUNTER_H__
#define __UI_SIGNAL_HUNTER_H__
#include "app_settings.hpp"
#include "rf_path.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_tabview.hpp"
#include "ui_freq_field.hpp"
#include "capture_thread.hpp"
#include "message.hpp"
#include <vector>
#include <memory>
namespace ui::external_app::signal_hunter {
class SignalHunterAppView;
// --- TAB 1: Main View ---
class HunterMainView : public View {
public:
HunterMainView(Rect parent_rect, SignalHunterAppView& parent);
void on_show() override;
void focus() override;
void update_status(const std::string& status, const Style* style);
void update_hits(uint32_t hits);
void update_frequency(rf::Frequency freq);
void set_recording_state(bool recording);
void set_start_button_text(const std::string& text) { button_start_stop.set_text(text); }
private:
SignalHunterAppView& parent_app;
rf::Frequency current_freq_{433920000};
Text text_current_freq{{UI_POS_X_CENTER(14), UI_POS_Y(3), 112, 16}, ""};
Text text_status{{UI_POS_X_CENTER(12), UI_POS_Y(6), 96, 16}, "IDLE"};
Button button_start_stop{{UI_POS_X_CENTER(10), UI_POS_Y(8), 80, 32}, "START"};
Text text_hits{{UI_POS_X_CENTER(10), UI_POS_Y(11), 80, 16}, "Hits: 0"};
};
// --- TAB 2: Freqs View ---
class HunterFreqsView : public View {
public:
HunterFreqsView(Rect parent_rect, SignalHunterAppView& parent);
void focus() override;
void update_list_count();
private:
SignalHunterAppView& parent_app;
Button button_load_file{{UI_POS_X(2), UI_POS_Y(1), 80, 28}, "LOAD FILE"};
Button button_clear{{UI_POS_X_RIGHT(12), UI_POS_Y(1), 80, 28}, "CLEAR"};
Labels labels{
{{UI_POS_X(2), UI_POS_Y(4)}, "Dwell Time (ms):", Color::light_grey()}};
NumberField field_dwell{{UI_POS_X_RIGHT(8), UI_POS_Y(4)}, 4, {10, 9999}, 10, ' '};
Text text_loaded_info{{UI_POS_X(2), UI_POS_Y(6), 208, 16}, "Loaded: 0 freqs"};
};
// --- TAB 3: Config View ---
class HunterConfigView : public View {
public:
HunterConfigView(Rect parent_rect, SignalHunterAppView& parent);
void update_mode_display();
void focus() override;
void on_show() override;
private:
SignalHunterAppView& parent_app;
Button button_mode{
{UI_POS_X(2), UI_POS_Y(2), 208, 28},
"MODE: SINGLE"};
RxFrequencyField field_single_freq; // UI_POS_Y(4)
Labels labels{
{{UI_POS_X(2), UI_POS_Y(6)}, "Energy Threshold:", Color::light_grey()},
{{UI_POS_X(2), UI_POS_Y(8)}, "Hang-Time (ms):", Color::light_grey()}};
NumberField field_threshold{{UI_POS_X_RIGHT(8), UI_POS_Y(6)}, 5, {100, 99999}, 100, ' '};
NumberField field_hang_time{{UI_POS_X_RIGHT(8), UI_POS_Y(8)}, 4, {10, 5000}, 10, ' '};
Text text_info_config{
{UI_POS_X(2), UI_POS_Y(10), 208, 16},
"Restart HUNT after change"};
};
// --- MAIN APP VIEW ---
class SignalHunterAppView final : public ui::View {
public:
SignalHunterAppView(ui::NavigationView& nav);
~SignalHunterAppView();
void focus() override;
std::string title() const override { return "SignalHunter"; }
ui::NavigationView& get_nav() { return nav_; }
// Application state shared across all views
std::vector<rf::Frequency> frequency_list;
uint32_t current_freq_index{0};
uint32_t energy_threshold{5000};
uint32_t hangtime_ms{500};
uint32_t hop_dwell_ms{200};
std::string freqman_file{"TARGETS"};
bool is_hunting{false};
uint32_t trigger_hits{0};
rf::Frequency single_frequency{433920000};
bool freq_hop_mode{false};
void send_hunter_config(bool start);
HunterMainView* get_main_view() { return view_main.get(); }
HunterConfigView* get_config_view() { return view_config.get(); }
private:
ui::NavigationView& nav_;
std::unique_ptr<app_settings::SettingsManager> settings_{};
std::unique_ptr<HunterMainView> view_main{};
std::unique_ptr<HunterFreqsView> view_freqs{};
std::unique_ptr<HunterConfigView> view_config{};
std::unique_ptr<TabView> tab_view{};
std::string current_capture_filename{};
std::unique_ptr<CaptureThread> capture_thread{};
ui::LNAGainField field_lna{{UI_POS_X(0), UI_POS_Y(0)}};
ui::VGAGainField field_vga{{UI_POS_X(7), UI_POS_Y(0)}};
ui::RFAmpField field_rf_amp{{UI_POS_X(14), UI_POS_Y(0)}};
ui::RSSI rssi{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, 4}};
static constexpr Dim tab_bar_h = 20;
Rect view_rect{
0, UI_POS_Y(2) + 4, UI_POS_MAXWIDTH,
screen_height - (UI_POS_Y(2) + 4) - UI_POS_HEIGHT(1)};
Rect content_rect{
0, UI_POS_Y(2) + 4 + tab_bar_h, UI_POS_MAXWIDTH,
screen_height - (UI_POS_Y(2) + 4 + tab_bar_h) - UI_POS_HEIGHT(1)};
void on_hunter_trigger(const HunterTriggerMessage* message);
void on_hunter_stop(const HunterStopMessage* message);
void start_recording();
void stop_recording();
// timer variables
uint32_t hop_timer_ms{0};
void on_frame_sync();
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_frame_sync();
}};
MessageHandlerRegistration message_handler_trigger{
Message::ID::HunterTrigger,
[this](const Message* const p) {
this->on_hunter_trigger(static_cast<const HunterTriggerMessage*>(p));
}};
MessageHandlerRegistration message_handler_stop{
Message::ID::HunterStop,
[this](const Message* const p) {
this->on_hunter_stop(static_cast<const HunterStopMessage*>(p));
}};
};
} // namespace ui::external_app::signal_hunter
#endif /*__UI_SIGNAL_HUNTER_H__*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright 2025 Mark Thompson
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright 2025 Mark Thompson
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_vor_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::vor_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<VorRxView>();
}
} // namespace ui::external_app::vor_rx
extern "C" {
__attribute__((section(".external_app.app_vor_rx.application_information"), used)) application_information_t _application_information_vor_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::vor_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "VOR RX",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x80,
0x01,
0xC0,
0x03,
0xE0,
0x07,
0xF0,
0x0F,
0xF8,
0x1F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_vor_rx */ {'P', 'V', 'R', 'X'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+341
View File
@@ -0,0 +1,341 @@
/*
* 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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_vor_rx.hpp"
#include "audio.hpp"
#include "string_format.hpp"
#include <algorithm>
#include <cstdint>
using namespace portapack;
using namespace ui;
namespace ui::external_app::vor_rx {
void VorLogger::write_header() {
log_file.write_raw("Time;Course;Radial;Deviation;Quality;Flag;");
}
void VorLogger::log_status(const VorRxStatusDataMessage& message, uint16_t course_deg) {
const int16_t deviation = static_cast<int16_t>((static_cast<int32_t>(message.radial_deg) - static_cast<int32_t>(course_deg) + 540) % 360 - 180);
std::string row = ";";
row += to_string_dec_uint(course_deg);
row += ";" + to_string_dec_uint(message.radial_deg);
row += ";" + std::to_string(deviation);
row += ";" + to_string_dec_uint(message.quality);
row += ";" + std::string(message.valid ? (message.to_from ? "TO" : "FROM") : "--");
log_file.write_entry(rtc_time::now(), row);
}
VorCdiIndicator::VorCdiIndicator(Point position)
: Widget{{position, {screen_width, 32}}} {
}
void VorCdiIndicator::set_course(uint16_t course_deg) {
if (course_deg_ != course_deg) {
course_deg_ = course_deg;
set_dirty();
}
}
void VorCdiIndicator::set_radial(uint16_t radial_deg) {
if (radial_deg_ != radial_deg) {
radial_deg_ = radial_deg;
set_dirty();
}
}
void VorCdiIndicator::set_valid(bool valid) {
if (valid_ != valid) {
valid_ = valid;
set_dirty();
}
}
int32_t VorCdiIndicator::normalize_signed_degrees(int32_t degrees) {
while (degrees <= -180) {
degrees += 360;
}
while (degrees > 180) {
degrees -= 360;
}
return degrees;
}
void VorCdiIndicator::paint(Painter& painter) {
const auto r = screen_rect();
const auto theme = Theme::getInstance();
const auto line_color = valid_ ? theme->fg_light->foreground : theme->bg_medium->foreground;
const auto needle_color = valid_ ? theme->fg_red->foreground : theme->bg_medium->foreground;
// Clear background so old needle positions don't linger.
painter.fill_rectangle(r, theme->bg_darkest->background);
const auto center_x = static_cast<Coord>(r.left() + r.width() / 2);
const auto center_y = static_cast<Coord>(r.top() + r.height() / 2);
// Horizontal scale line.
painter.draw_hline({static_cast<Coord>(r.left() + 8), center_y}, r.width() - 16, line_color);
// Evenly spaced tick marks, longer at center.
constexpr int32_t tick_spacing = 40;
for (int32_t tick = -2; tick <= 2; ++tick) {
const auto x = static_cast<Coord>(center_x + tick * tick_spacing);
const auto len = (tick == 0) ? 14 : 8;
painter.draw_vline({x, static_cast<Coord>(center_y - len / 2)}, len, line_color);
}
// Deviation needle, clamped to +/-10 degrees full-scale.
const int32_t deviation = normalize_signed_degrees(static_cast<int32_t>(radial_deg_) - static_cast<int32_t>(course_deg_));
const int32_t scaled = std::max<int32_t>(-10, std::min<int32_t>(10, deviation));
// +/-10 deg full-scale maps to +/-2 ticks (2 * tick_spacing); the division
// is exact for the clamped range so no rounding is needed.
const int16_t needle_offset = static_cast<int16_t>((scaled * (2 * tick_spacing)) / 10);
const auto needle_x = static_cast<Coord>(center_x + needle_offset);
painter.draw_vline({needle_x, static_cast<Coord>(r.top() + 4)}, r.height() - 8, needle_color);
}
VorRxView::VorRxView(NavigationView& nav)
: nav_{nav} {
add_children({&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&rssi,
&channel,
&audio,
&labels,
&text_status,
&text_band,
&text_next,
&field_course,
&text_course_unit,
&field_calibration,
&text_calib_unit,
&text_radial,
&text_flag,
&text_cdi_title,
&cdi_indicator,
&check_log,
&button_start_stop});
field_frequency.set_step(8333);
if (field_frequency.value() == 0) {
field_frequency.set_value(110'000'000);
}
field_course.set_value(0);
field_course.on_change = [this](int32_t) {
update_cdi();
};
field_calibration.set_value(0);
field_calibration.on_change = [this](int32_t) {
refresh_radial();
};
logger = std::make_unique<VorLogger>();
check_log.set_value(logging_);
check_log.on_select = [this](Checkbox&, bool v) {
logging_ = v;
update_logging();
};
update_logging();
button_start_stop.on_select = [this](Button&) {
if (running_) {
stop_receiver();
} else {
start_receiver();
}
};
start_receiver();
}
VorRxView::~VorRxView() {
stop_receiver();
}
void VorRxView::focus() {
field_frequency.focus();
}
void VorRxView::start_receiver() {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
baseband::set_vor_config(true);
radial_filter_valid_ = false;
flag_state_ = 0;
receiver_model.set_hidden_offset(0);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
receiver_model.set_frequency_step(8333);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
// enable() applies the AM configuration, which forces the audio codec to
// 12 kHz. The VOR baseband keeps its channel at 48 kHz (so the 9960 Hz
// subcarrier stays representable), so re-assert 48 kHz audio afterwards.
audio::set_rate(audio::Rate::Hz_48000);
audio::output::start();
running_ = true;
update_status();
}
void VorRxView::stop_receiver() {
if (!running_) {
return;
}
running_ = false;
baseband::set_vor_config(false);
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
update_status();
}
void VorRxView::update_status() {
text_status.set(running_ ? "Running" : "Idle");
button_start_stop.set_text(running_ ? "Stop" : "Start");
}
void VorRxView::update_logging() {
if (logger && logging_) {
logger->append(logs_dir / (std::string("VOR_") + to_string_timestamp(rtc_time::now()) + ".CSV"));
logger->write_header();
}
}
void VorRxView::on_vor_status(const VorRxStatusDataMessage& message) {
if (!running_) {
return;
}
last_radial_deg_ = message.radial_deg;
last_valid_ = message.valid;
have_status_ = true;
if (message.valid) {
last_radial_deg_ = smooth_radial(message.radial_deg);
} else {
// Drop the filter history so a fresh lock doesn't drag from stale data.
radial_filter_valid_ = false;
}
text_next.set(message.valid ? "Locked" : "Searching");
refresh_radial();
if (logger && logging_) {
VorRxStatusDataMessage calibrated = message;
calibrated.radial_deg = calibrated_radial(last_radial_deg_);
calibrated.to_from = (flag_state_ == 2);
logger->log_status(calibrated, field_course.value());
}
}
uint16_t VorRxView::smooth_radial(uint16_t radial_deg) {
// Wrapped exponential moving average. At ~10 updates/s blending 20% of each
// new reading (alpha = 0.8) gives a ~0.5 s time constant while cutting the
// jitter by ~3x. Stepping along the shortest arc in 1/64 deg fixed point
// handles the 0/360 deg wrap without any trig (which would otherwise pull
// newlib's float sin/cos/atan2 argument-reduction code into flash ROM).
constexpr int32_t scale = 64;
constexpr int32_t full_circle = 360 * scale;
const int32_t target = static_cast<int32_t>(radial_deg) * scale;
if (!radial_filter_valid_) {
radial_smoothed_fp_ = target;
radial_filter_valid_ = true;
} else {
int32_t diff = (target - radial_smoothed_fp_) % full_circle;
if (diff < -full_circle / 2) {
diff += full_circle;
} else if (diff > full_circle / 2) {
diff -= full_circle;
}
// 20% step toward the new reading (1 - alpha).
radial_smoothed_fp_ += diff / 5;
radial_smoothed_fp_ %= full_circle;
if (radial_smoothed_fp_ < 0) {
radial_smoothed_fp_ += full_circle;
}
}
return static_cast<uint16_t>((radial_smoothed_fp_ + scale / 2) / scale) % 360;
}
uint16_t VorRxView::calibrated_radial(uint16_t radial_deg) const {
int32_t value = (static_cast<int32_t>(radial_deg) + field_calibration.value()) % 360;
if (value < 0) {
value += 360;
}
return static_cast<uint16_t>(value);
}
void VorRxView::refresh_radial() {
if (!have_status_) {
return;
}
const uint16_t radial = calibrated_radial(last_radial_deg_);
text_radial.set(to_string_dec_uint(radial, 3) + " deg");
text_flag.set(to_from_label(radial));
cdi_indicator.set_radial(radial);
cdi_indicator.set_valid(last_valid_);
}
const char* VorRxView::to_from_label(uint16_t radial_deg) {
if (!last_valid_) {
flag_state_ = 0;
return "--";
}
// TO/FROM is set by the selected OBS course relative to the received radial,
// not by the radial alone. Take the difference wrapped to [-180, 180]: the
// switch is at the 90 deg abeam point. |diff| < 90 means the selected course
// leads away from the station (FROM); |diff| > 90 means toward it (TO).
int32_t diff = (static_cast<int32_t>(radial_deg) - field_course.value() + 540) % 360 - 180;
const int32_t adiff = (diff < 0) ? -diff : diff;
// Hysteresis: hold the current flag within a +/-5 deg dead zone around the
// 90 deg boundary so noise near abeam doesn't rapidly toggle TO/FROM.
if (adiff < 85) {
flag_state_ = 1; // FROM
} else if (adiff > 95) {
flag_state_ = 2; // TO
}
if (flag_state_ == 1) return "FROM";
if (flag_state_ == 2) return "TO";
return "--"; // abeam / ambiguous
}
void VorRxView::update_cdi() {
cdi_indicator.set_course(field_course.value());
}
} // namespace ui::external_app::vor_rx
+200
View File
@@ -0,0 +1,200 @@
/*
* 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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_VOR_RX_H__
#define __UI_VOR_RX_H__
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file_path.hpp"
#include "log_file.hpp"
#include "rtc_time.hpp"
#include "portapack.hpp"
#include "ui.hpp"
#include "ui_audio.hpp"
#include "ui_channel.hpp"
#include "ui_freq_field.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_rssi.hpp"
namespace ui::external_app::vor_rx {
class VorLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void write_header();
void log_status(const VorRxStatusDataMessage& message, uint16_t course_deg);
private:
LogFile log_file{};
};
class VorCdiIndicator : public Widget {
public:
VorCdiIndicator(Point position);
void set_course(uint16_t course_deg);
void set_radial(uint16_t radial_deg);
void set_valid(bool valid);
void paint(Painter& painter) override;
private:
static int32_t normalize_signed_degrees(int32_t degrees);
uint16_t course_deg_{0};
uint16_t radial_deg_{0};
bool valid_{false};
};
class VorRxView : public View {
public:
VorRxView(NavigationView& nav);
~VorRxView();
void focus() override;
std::string title() const override { return "VOR RX"; }
private:
void start_receiver();
void stop_receiver();
void update_status();
void on_vor_status(const VorRxStatusDataMessage& message);
void update_cdi();
void refresh_radial();
uint16_t calibrated_radial(uint16_t radial_deg) const;
uint16_t smooth_radial(uint16_t radial_deg);
const char* to_from_label(uint16_t radial_deg);
void update_logging();
NavigationView& nav_;
bool running_{false};
bool logging_{false};
bool have_status_{false};
uint16_t last_radial_deg_{0};
bool last_valid_{false};
uint8_t flag_state_{0}; // TO/FROM hysteresis: 0=unknown, 1=FROM, 2=TO
// Circular exponential moving average of the radial. Each 100 ms estimate
// is noisy (~10 deg std even when locked), so blend it along the shortest
// arc in 1/64 deg fixed point to average correctly across the 0/360 deg
// wrap without trig.
bool radial_filter_valid_{false};
int32_t radial_smoothed_fp_{0};
RxFrequencyField field_frequency{
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{UI_POS_X(18), UI_POS_Y(0)}};
AudioVolumeField field_volume{
{screen_width - 2 * 8, UI_POS_Y(0)}};
RSSI rssi{
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Channel channel{
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Audio audio{
{UI_POS_X(21), 10, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
NumberField field_course{
{UI_POS_X(14), UI_POS_Y(4)},
3,
{0, 359},
1,
'0'};
NumberField field_calibration{
{UI_POS_X(14), UI_POS_Y(7)},
4,
{-359, 359},
1,
' '};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(1)}, "Status:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(2)}, "VOR Band:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(3)}, "Decoder:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(4)}, "Course (OBS):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(5)}, "Rec. Radial:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(6)}, "Flag:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(7)}, "Calibration:", Theme::getInstance()->fg_light->foreground}};
Text text_status{
{UI_POS_X(14), UI_POS_Y(1), UI_POS_WIDTH_REMAINING(14), UI_POS_HEIGHT(1)},
"Idle"};
Text text_band{
{UI_POS_X(14), UI_POS_Y(2), UI_POS_WIDTH_REMAINING(14), UI_POS_HEIGHT(1)},
"108.00-117.95MHz"};
Text text_next{
{UI_POS_X(14), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(14), UI_POS_HEIGHT(1)},
"Pending"};
Text text_course_unit{
{UI_POS_X(18), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(18), UI_POS_HEIGHT(1)},
"deg"};
Text text_radial{
{UI_POS_X(14), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(14), UI_POS_HEIGHT(1)},
"--"};
Text text_flag{
{UI_POS_X(14), UI_POS_Y(6), UI_POS_WIDTH_REMAINING(14), UI_POS_HEIGHT(1)},
"--"};
Text text_calib_unit{
{UI_POS_X(19), UI_POS_Y(7), UI_POS_WIDTH_REMAINING(19), UI_POS_HEIGHT(1)},
"deg"};
Text text_cdi_title{
{UI_POS_X(0), UI_POS_Y(9), UI_POS_WIDTH_REMAINING(0), UI_POS_HEIGHT(1)},
"Course Deviation Indicator"};
VorCdiIndicator cdi_indicator{
{UI_POS_X(0), UI_POS_Y(10)}};
std::unique_ptr<VorLogger> logger{};
Button button_start_stop{
{UI_POS_X(9), UI_POS_Y(14), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Start"};
Checkbox check_log{
{UI_POS_X(7), UI_POS_Y(17)},
3,
"LOG to SD Card",
true};
MessageHandlerRegistration message_handler_vor_status{
Message::ID::VorRxStatusData,
[this](const Message* p) {
const auto message = *reinterpret_cast<const VorRxStatusDataMessage*>(p);
on_vor_status(message);
}};
};
} // namespace ui::external_app::vor_rx
#endif // __UI_VOR_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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_vor_tx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::vor_tx {
void initialize_app(ui::NavigationView& nav) {
nav.push<VorTxView>();
}
} // namespace ui::external_app::vor_tx
extern "C" {
__attribute__((section(".external_app.app_vor_tx.application_information"), used)) application_information_t _application_information_vor_tx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::vor_tx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "VOR TX",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x80,
0x01,
0xC0,
0x03,
0xE0,
0x07,
0xF0,
0x0F,
0xF8,
0x1F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
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_vor_tx */ {'P', 'V', 'T', 'X'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+133
View File
@@ -0,0 +1,133 @@
/*
* 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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_vor_tx.hpp"
#include "string_format.hpp"
#include "ui_textentry.hpp"
using namespace portapack;
using namespace ui;
namespace ui::external_app::vor_tx {
VorTxView::VorTxView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&labels,
&field_radial,
&text_radial_unit,
&check_ident,
&button_ident_text,
&text_status,
&tx_view});
field_radial.set_value(radial_);
field_radial.on_change = [this](int32_t v) {
radial_ = v;
if (transmitting_) {
update_config();
}
};
check_ident.set_value(ident_);
check_ident.on_select = [this](Checkbox&, bool v) {
ident_ = v;
if (transmitting_) {
update_config();
}
};
button_ident_text.set_text(ident_text_);
button_ident_text.on_select = [this](Button&) {
text_prompt(nav_, ident_text_, 7, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string&) {
button_ident_text.set_text(ident_text_);
if (transmitting_) {
update_config();
}
});
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_target_frequency(f);
};
};
tx_view.on_start = [this]() {
if (tx_acknowledged_) {
start_tx();
return;
}
nav_.display_modal(
"TX WARNING",
"VOR is an aviation nav\nband! TX only into a\ndummy load.",
YESNO,
[this](bool choice) {
if (choice) {
tx_acknowledged_ = true;
start_tx();
} else {
tx_view.set_transmitting(false);
}
});
};
tx_view.on_stop = [this]() {
stop_tx();
};
}
VorTxView::~VorTxView() {
transmitter_model.disable();
baseband::shutdown();
}
void VorTxView::focus() {
tx_view.focus();
}
void VorTxView::update_config() {
baseband::set_vor_tx_config(static_cast<uint16_t>(radial_), ident_, ident_text_);
}
void VorTxView::start_tx() {
transmitter_model.set_sampling_rate(1536000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
update_config();
transmitting_ = true;
tx_view.set_transmitting(true);
text_status.set("TX radial " + to_string_dec_uint(radial_));
set_dirty();
}
void VorTxView::stop_tx() {
transmitter_model.disable();
transmitting_ = false;
tx_view.set_transmitting(false);
text_status.set("Idle");
set_dirty();
}
} // namespace ui::external_app::vor_tx
+113
View File
@@ -0,0 +1,113 @@
/*
* 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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_VOR_TX_H__
#define __UI_VOR_TX_H__
#include "app_settings.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
#include "radio_state.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
namespace ui::external_app::vor_tx {
class VorTxView : public View {
public:
VorTxView(NavigationView& nav);
~VorTxView();
void focus() override;
std::string title() const override { return "VOR TX"; }
private:
void start_tx();
void stop_tx();
void update_config();
NavigationView& nav_;
bool transmitting_{false};
bool tx_acknowledged_{false};
uint32_t radial_{0};
bool ident_{true};
std::string ident_text_{"VOR"};
TxRadioState radio_state_{
113'500'000 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_vor",
app_settings::Mode::TX,
{
{"radial"sv, &radial_},
{"ident"sv, &ident_},
{"ident_text"sv, &ident_text_},
}};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(1)}, "Radial:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(5)}, "Ident:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(8)}, "Status:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(10)}, "Lab use: set any freq &", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(11)}, "TX into a dummy load.", Theme::getInstance()->fg_light->foreground}};
NumberField field_radial{
{UI_POS_X(8), UI_POS_Y(1)},
3,
{0, 359},
1,
'0'};
Text text_radial_unit{
{UI_POS_X(12), UI_POS_Y(1), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
"deg"};
Checkbox check_ident{
{UI_POS_X(0), UI_POS_Y(3)},
13,
"CW identifier",
true};
Button button_ident_text{
{UI_POS_X(8), UI_POS_Y(5), UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
"VOR"};
Text text_status{
{UI_POS_X(8), UI_POS_Y(8), UI_POS_WIDTH_REMAINING(8), UI_POS_HEIGHT(1)},
"Idle"};
TransmitterView tx_view{
(int16_t)UI_POS_Y_BOTTOM(4),
8333,
1750000};
};
} // namespace ui::external_app::vor_tx
#endif // __UI_VOR_TX_H__
+1 -2
View File
@@ -307,9 +307,8 @@ void MAX2839::configure_rx_gain() {
}
_map.r.lpf_vga_2.L = lna::gain_ordinal(lna_gain);
_dirty[Register::RXRF_2] = 1;
_map.r.lpf_vga_2.VGA = vga::gain_ordinal(vga_gain);
_dirty[Register::LPF_VGA_2] = 1;
_dirty[Register::LPF_VGA_2] = 1; /* both L (LNA) and VGA live in LPF_VGA_2 */
flush();
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2023 zxkmm AKA zix aka sommermorgentraum
* copyleft 2023 zxkmm
* Copyright (C) 2023 u-foka
*
* This file is part of PortaPack.
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2023 zxkmm AKA zix aka sommermorgentraum
* copyleft 2023 zxkmm
* Copyright (C) 2023 u-foka
*
* This file is part of PortaPack.
+1 -5
View File
@@ -151,10 +151,6 @@ void init() {
/* PRALINE uses MAX2831 transceiver */
second_if = (max283x::MAX283x*)&second_if_max2831;
#else
if (hackrf_r9) {
gpio_r9_not_ant_pwr.write(1);
gpio_r9_not_ant_pwr.output();
}
second_if = hackrf_r9
? (max283x::MAX283x*)&second_if_max2839
: (max283x::MAX283x*)&second_if_max2837;
@@ -398,7 +394,7 @@ void set_antenna_bias(const bool on) {
rf_path.set_ant_bias(on);
#else
if (hackrf_r9) {
gpio_r9_not_ant_pwr.write(on ? 0 : 1);
rf_path.set_ant_bias(on);
} else {
first_if.set_gpo1(on ? 0 : 1);
}
+63 -249
View File
@@ -21,221 +21,26 @@
#include "rf_path.hpp"
#include "platform.hpp"
#include <array>
#include <initializer_list>
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "utility.hpp"
#include "gpio.hpp"
using namespace gpio_control;
namespace rf {
namespace path {
namespace {
#ifdef PRALINE
/* PRALINE uses a simplified RF path with only 5 control signals.
* The RF path architecture is completely different from HackRF One.
*/
struct PralineConfig {
bool tx_en;
bool mix_bypass; // RF path mixer bypass (GPIO3[2])
bool lpf_en;
bool rf_amp_en;
bool ant_bias_en_n; // Inverted: 0 = bias enabled
void apply() const {
gpio_tx_enable.write(tx_en);
gpio_mix_bypass.write(mix_bypass); // Control RF path mixer
gpio_lpf_enable.write(lpf_en);
gpio_rf_amp_enable.write(rf_amp_en);
gpio_ant_bias_disable.write(ant_bias_en_n);
}
};
#else
/* HackRF One uses 11 GPIOs for RF path control */
using GPIOs = std::array<GPIO, 11>;
/* TODO: ARM GCC 4.8 2014q3 doesn't like this array inside struct Config.
* No idea why.
*/
constexpr GPIOs gpios{
gpio_mix_bypass,
gpio_not_mix_bypass,
gpio_tx_mix_bp,
gpio_rx_mix_bp,
gpio_hp,
gpio_lp,
gpio_amp_bypass,
gpio_tx_amp,
gpio_not_tx_amp_pwr,
gpio_rx_amp,
gpio_not_rx_amp_pwr,
};
/* HackRF One Config struct - not used on PRALINE */
struct Config {
using base_type = uint16_t;
union {
struct {
bool tx : 1;
bool rx : 1;
bool mix_bypass : 1;
bool not_mix_bypass : 1;
bool tx_mix_bp : 1;
bool rx_mix_bp : 1;
bool hp : 1;
bool lp : 1;
bool amp_bypass : 1;
bool tx_amp : 1;
bool not_tx_amp : 1;
bool rx_amp : 1;
bool not_rx_amp : 1;
};
base_type w;
};
constexpr Config(
const Direction direction,
const Band band,
const bool amplify)
: tx(direction == Direction::Transmit),
rx(direction == Direction::Receive),
mix_bypass(band == Band::Mid),
not_mix_bypass(band != Band::Mid),
tx_mix_bp((direction == Direction::Transmit) && (band == Band::Mid)),
rx_mix_bp((direction == Direction::Receive) && (band == Band::Mid)),
hp(band == Band::High),
lp(band == Band::Low),
amp_bypass(!amplify),
tx_amp((direction == Direction::Transmit) && amplify),
not_tx_amp(!tx_amp),
rx_amp((direction == Direction::Receive) && amplify),
not_rx_amp(!rx_amp) {
}
constexpr Config()
: Config(Direction::Receive, Band::Mid, false) {
}
constexpr Config(
const base_type w)
: w(w) {
}
constexpr Config operator^(const Config& r) const {
return w ^ r.w;
}
constexpr Config operator&(const Config& r) const {
return w & r.w;
}
constexpr bool operator[](const size_t n) const {
return (w >> n) & 1;
}
static void gpio_init() {
if (hackrf_r9) {
gpio_r9_rx.output();
} else {
gpio_og_tx.output();
gpio_og_rx.output();
}
for (auto gpio : gpios) {
gpio.output();
}
}
void apply() const {
/* NOTE: Assumes order in gpios[] and Config bitfield match,
* after the 'tx' and 'rx' fields which are handled specially. */
for (size_t n = 0; n < gpios.size() + 2; n++) {
bool value = (*this)[n];
switch (n) {
case 0:
if (!hackrf_r9) {
gpio_og_tx.write(value);
}
break;
case 1:
if (hackrf_r9) {
gpio_r9_rx.write(value);
} else {
gpio_og_rx.write(value);
}
break;
default:
gpios[n - 2].write(value);
break;
}
}
}
};
/* HackRF One config table - not used on PRALINE */
using ConfigAmp = std::array<Config, 2>;
using ConfigDirection = std::array<ConfigAmp, 2>;
using ConfigBand = std::array<ConfigDirection, 3>;
constexpr ConfigAmp config_amp(
const Direction direction,
const Band band) {
return {{
Config(direction, band, false),
Config(direction, band, true),
}};
}
constexpr ConfigDirection config_rx_tx(
const Band band) {
return {
config_amp(Direction::Receive, band),
config_amp(Direction::Transmit, band),
};
}
constexpr ConfigBand config_band() {
return {
config_rx_tx(Band::Low),
config_rx_tx(Band::Mid),
config_rx_tx(Band::High),
};
}
constexpr ConfigBand config_table = config_band();
static_assert(sizeof(config_table) == sizeof(Config::base_type) * 3 * 2 * 2, "rf path config table unexpected size");
constexpr Config get_config(
const Direction direction,
const Band band,
const bool amplify) {
return config_table[toUType(band)][toUType(direction)][amplify ? 1 : 0];
}
#endif /* PRALINE */
} /* namespace */
void Path::init() {
/* Set safe initial default states */
direction = Direction::Receive;
rf_amp_en = false;
ant_bias_en = false;
#ifdef PRALINE
/* Set safe initial state: RX mode, mixer enabled, LPF on, amp off, no bias */
PralineConfig config = {
.tx_en = false,
.mix_bypass = false, // RF path mixer bypass (GPIO3[2])
.lpf_en = true, // LPF on for low band
.rf_amp_en = false, // Amp off
.ant_bias_en_n = true // Bias off (inverted)
};
config.apply();
band = Band::Low;
#else
update();
Config::gpio_init();
band = Band::Mid;
#endif
update();
}
void Path::set_direction(const Direction new_direction) {
@@ -245,73 +50,82 @@ void Path::set_direction(const Direction new_direction) {
void Path::set_band(const Band new_band) {
band = new_band;
_band = new_band;
update();
}
void Path::set_rf_amp(const bool new_rf_amp) {
rf_amp = new_rf_amp;
rf_amp_en = new_rf_amp;
update();
}
void Path::set_ant_bias(const bool new_ant_bias) {
ant_bias = new_ant_bias;
ant_bias_en = new_ant_bias;
update();
}
bool Path::get_ant_bias() const {
return ant_bias;
return ant_bias_en;
}
void Path::update() {
/* 0 ^ 0 => 0 & 0 = 0 ^ 0 = 0 (no change)
* 0 ^ 1 => 1 & 0 = 0 ^ 0 = 0 (ignore change to 1)
* 1 ^ 0 => 1 & 1 = 1 ^ 1 = 0 (allow change to 0)
* 1 ^ 1 => 0 & 1 = 0 ^ 1 = 1 (no change) */
const bool is_tx = (direction == Direction::Transmit);
#ifdef PRALINE
/* PRALINE RF path control:
* - tx_en: 1 for TX, 0 for RX
* - mix_bypass: 0 to enable RF path mixer bypass (GPIO3[2])
* - lpf_en: 1 for low band (< 2.4 GHz), 0 for high band
* - rf_amp_en: 1 to enable RF amplifier
* - ant_bias_en_n: 0 to enable antenna bias (inverted)
*/
// const Config changed = _config ^ config_next;
// const Config turned_off = _config & changed;
// PRALINE specific RF path control directly applied to pins.
// Active-low pin inversion is handled internally inside setState().
PralineConfig config;
tx_enable.setState(is_tx);
/* In transition, ignore the bits that are turning on. So this transition phase
* only turns off signals. It doesn't turn on signals.
*/
// const Config transition_config = _config ^ turned_off;
// update_signals(transition_config);
// On the PRALINE board, the mixer is used ONLY on the Low band.
// Since setState() internally handles the active-low (MIX_ENABLE_N) hardware inversion,
// we simply pass 'true' to enable the mixer on Low band, and 'false' for Mid/High bands.
config.tx_en = (direction == Direction::Transmit);
mix_bypass.setState(band == Band::Low);
// RF path mixer bypass: 0=enabled, 1=bypassed
config.mix_bypass = (band == Band::Mid);
/* Move to the final state by turning on required signals. */
/* LPF for low band */
config.lpf_en = (band == Band::Low);
/* RF amp when amplification requested */
config.rf_amp_en = rf_amp;
/* Antenna bias */
config.ant_bias_en_n = !ant_bias;
config.apply();
lpf.setState(band == Band::Low);
rf_amp_enable.setState(rf_amp_en);
ant_bias.setState(ant_bias_en);
#else
/* HackRF One RF path control */
const auto config = get_config(direction, band, rf_amp);
config.apply();
const bool is_rx = (direction == Direction::Receive);
// HackRF One (OG & R9) RF path control
const bool mix_bypass_en = (band == Band::Mid);
const bool amplify = rf_amp_en;
// Primary TX/RX routing switches
if (!hackrf_r9) {
og_tx.setState(is_tx);
}
if (hackrf_r9) {
r9_rx.setState(is_rx); // Single pin handles directional switching on R9
} else {
og_rx.setState(is_rx);
}
// RF path switch configuration matrix
rx_mix_bypass.setState(mix_bypass_en);
tx_mix_bp.setState(is_tx && mix_bypass_en);
rx_mix_bp.setState(is_rx && mix_bypass_en);
hpf.setState(band == Band::High);
lpf.setState(band == Band::Low);
amp_bypass.setState(!amplify);
tx_amp.setState(is_tx && amplify);
rx_amp.setState(is_rx && amplify);
tx_mix_bypass.setState(mix_bypass_en);
tx_amp_pwr.setState(is_tx && amplify);
rx_amp_pwr.setState(is_rx && amplify);
if (hackrf_r9) {
ant_bias.setState(ant_bias_en);
}
#endif
}
} // namespace path
} // namespace rf
} // namespace rf
+8 -11
View File
@@ -41,15 +41,15 @@ namespace path {
#ifdef PRALINE
/* PRALINE: MAX2831 direct path is 2320-2740 MHz */
constexpr FrequencyRange band_low{0, 2320'000'000};
constexpr FrequencyRange band_high{2740'000'000, 7250'000'000};
constexpr FrequencyRange band_mid{band_low.maximum, band_high.minimum};
constexpr Frequency TRANSITION = 2320'000'000;
#else
/* HackRF One: Original band boundaries */
constexpr FrequencyRange band_low{0, 2170'000'000};
constexpr Frequency TRANSITION = 2170'000'000;
#endif
constexpr FrequencyRange band_low{0, TRANSITION};
constexpr FrequencyRange band_high{2740'000'000, 7250'000'000};
constexpr FrequencyRange band_mid{band_low.maximum, band_high.minimum};
#endif
enum class Band {
/* Zero-based, used as index into frequency_bands table */
@@ -61,23 +61,20 @@ enum class Band {
class Path {
public:
void init();
void set_direction(const Direction direction);
void set_band(const Band band);
void set_rf_amp(const bool rf_amp);
void set_ant_bias(const bool ant_bias);
bool get_ant_bias() const;
Band get_band() const { return _band; } //_band is used solely for debugging purposes.
Band get_band() const { return band; }
private:
Direction direction{Direction::Receive};
Band band{Band::Mid};
bool rf_amp{false};
bool ant_bias{false};
bool rf_amp_en{false};
bool ant_bias_en{false};
void update();
Band _band{Band::Mid}; //_band is solely used of debugging purposes
};
} // namespace path
+1 -1
View File
@@ -4,7 +4,7 @@
* Copyright (C) 2019 Elia Yehuda (z4ziggy)
* Copyright (C) 2023 Mark Thompson
* Copyright (C) 2024 u-foka
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+2 -10
View File
@@ -26,10 +26,7 @@
#include "string_format.hpp"
#include "ui_receiver.hpp"
#include "ui_freqman.hpp"
#ifndef PRALINE
#include "audio.hpp"
#endif
using namespace portapack;
@@ -595,18 +592,13 @@ AudioVolumeField::AudioVolumeField(
/* fill char */ ' '} {
set_value(receiver_model.normalized_headphone_volume());
#ifdef PRALINE
on_change = [](int32_t v) {
receiver_model.set_normalized_headphone_volume(v);
#else
on_change = [](int32_t vol) {
// don't call receiver model, because this widget shuld be able to handle volume settinsg from any app, regardless of the receiver model's enables state. like the tx apps should be able to set volume too.
// this is identical to the receiver model's method
// Don't call receiver_model here so volume can be adjusted even when ReceiverModel is disabled (e.g. TX apps/games).
// The dB mapping mirrors ReceiverModel::set_normalized_headphone_volume().
uint8_t v = clip<uint8_t>(vol, 0, 99);
auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
persistent_memory::set_headphone_volume(new_volume);
audio::headphone::set_volume(new_volume);
#endif
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2025 zxkmm AKA zix aka sommermorgentraum
* copyleft 2025 zxkmm
*
* This file is part of PortaPack.
*
+151 -1
View File
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 u-foka
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -49,6 +49,7 @@
#include "ui_recon.hpp"
#include "ui_search.hpp"
#include "ui_settings.hpp"
#include "ui_textentry.hpp"
#include "ui_sonde.hpp"
#include "ui_ss_viewer.hpp"
// #include "ui_test.hpp"
@@ -139,6 +140,115 @@ bool NavigationView::StartAppByName(const char* name) {
return false;
}
/* App search ***********************************************************/
namespace {
// ASCII, allocation-free, case-insensitive lowering. Kept local so this TU
// doesn't need to include <cctype> just for one small comparison.
char ascii_lower(char c) {
return (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
}
// True when 'needle' occurs anywhere in 'haystack', case-insensitive.
bool name_contains(const char* haystack, const std::string& needle) {
const size_t n = needle.size();
if (n == 0)
return false;
for (size_t i = 0; haystack[i] != '\0'; ++i) {
size_t j = 0;
while (j < n && haystack[i + j] != '\0' &&
ascii_lower(haystack[i + j]) == ascii_lower(needle[j]))
++j;
if (j == n)
return true;
}
return false;
}
} // namespace
void NavigationView::start_app_search() {
app_search_query_.clear();
app_search_committed_ = false;
// Reuse the shared keyboard view; it is destroyed as soon as the user
// confirms or cancels, so no search UI ever lingers in RAM.
text_prompt(*this, app_search_query_, 20, ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& query) {
// Runs while the keyboard is still alive: only record that a
// query was confirmed; results are built once it has popped.
app_search_committed_ = !query.empty();
});
// Defer building/showing the results until the keyboard view has popped.
set_on_pop([this]() { open_app_search_results(); });
}
void NavigationView::open_app_search_results() {
if (!app_search_committed_)
return; // user cancelled with Back, or the query was empty
app_search_committed_ = false;
std::vector<AppSearchEntry> matches;
// Internal apps: match on the name shown in the menus.
for (const auto& app : appList) {
if (app.displayName != nullptr && app.viewFactory != nullptr &&
name_contains(app.displayName, app_search_query_))
matches.push_back({app.displayName, app.viewFactory, {}});
}
// External (.ppma) and standalone (.ppmp) apps on the SD card. The
// enumerator hands back pointers to short-lived buffers, so the names are
// copied into owned strings here. Match on both the friendly name and the
// file (call) name. module_included=false: PPmod apps can't be launched by
// path, so they're intentionally excluded.
ExternalItemsMenuLoader::load_all_external_items_callback(
[this, &matches](AppInfoConsole& info) {
if (name_contains(info.appFriendlyName, app_search_query_) ||
name_contains(info.appCallName, app_search_query_))
matches.push_back({info.appFriendlyName, nullptr, info.appCallName});
},
false);
if (matches.empty()) {
display_modal("Search", "No matching app found.");
return;
}
push<AppSearchResultsView>(std::move(matches));
}
void NavigationView::launch_search_entry(ViewFactoryBase* factory, std::string call_name) {
// Same close-then-open contract as StartAppByName: drop the search UI
// (freeing the results view and the very button that called us) before
// starting the target, so only one app is ever resident. Everything used
// below is a by-value argument or this resident NavigationView.
home(false);
if (factory != nullptr) {
push_view(factory->produce(*this));
return;
}
// External/standalone: AppInfoConsole doesn't record which kind it is, so
// try both extensions the way handle_autostart() does.
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
std::string appwithpath = "/" + apps_dir.string() + "/" + call_name + ".ppma";
std::filesystem::path pth = conv.from_bytes(appwithpath.c_str());
if (ExternalItemsMenuLoader::run_external_app(*this, pth))
return;
appwithpath = "/" + apps_dir.string() + "/" + call_name + ".ppmp";
pth = conv.from_bytes(appwithpath.c_str());
if (ExternalItemsMenuLoader::run_standalone_app(*this, pth))
return;
display_modal("Search", "Failed to start:\n" + call_name);
}
/* StatusTray ************************************************************/
StatusTray::StatusTray(Point pos)
@@ -561,6 +671,7 @@ InformationView::InformationView(
NavigationView& nav)
: nav_(nav) {
add_children({&backdrop,
&search_icon,
&version,
&ltime});
@@ -604,6 +715,19 @@ bool InformationView::firmware_checksum_error() {
return fw_checksum_error;
}
bool InformationView::on_touch(const TouchEvent event) {
// The whole info bar is one touch target. Capture on Start so the End
// event is delivered here, then act on release like a normal button tap.
switch (event.type) {
case TouchEvent::Type::End:
if (nav_.is_valid())
nav_.start_app_search();
return true;
default:
return true;
}
}
/* Navigation ************************************************************/
bool NavigationView::is_top() const {
@@ -873,6 +997,32 @@ void GamesMenuView::on_populate() {
add_external_items(nav_, app_location_t::GAMES, *this, return_icon ? 1 : 0);
}
/* AppSearchResultsView *************************************************/
AppSearchResultsView::AppSearchResultsView(NavigationView& nav, std::vector<AppSearchEntry>&& entries)
: nav_(nav), entries_(std::move(entries)) {
set_max_rows(2); // wider buttons: app names need the room
}
void AppSearchResultsView::on_populate() {
for (size_t i = 0; i < entries_.size(); ++i) {
const auto& entry = entries_[i];
// Internal apps are green, external/standalone orange, matching the
// "yellow-ish external" convention used elsewhere. No icon: the cheap
// enumerator doesn't decode external bitmaps.
add_item({entry.display,
entry.factory ? Color::green() : Color::orange(),
nullptr,
[this, i]() {
// Read the target off entries_ and pass it by value: the
// call below frees this view via home(), so nothing after
// it may touch 'this' or its members.
nav_.launch_search_entry(entries_[i].factory, entries_[i].call_name);
}},
true);
}
}
/* SystemMenuView ********************************************************/
void SystemMenuView::hackrf_mode(NavigationView& nav) {
+55 -2
View File
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 u-foka
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -86,6 +86,14 @@ struct AppInfoConsole {
const app_location_t appLocation;
};
// One row in the app-search results list. Strings are owned copies: the
// external-app enumerator hands back pointers to short-lived buffers.
struct AppSearchEntry {
std::string display; // friendly name shown to the user
ViewFactoryBase* factory; // internal app factory, or nullptr for external/standalone
std::string call_name; // external/standalone file name (no extension); unused when internal
};
class NavigationView : public View {
public:
std::function<void(const View&)> on_view_changed{};
@@ -138,12 +146,34 @@ class NavigationView : public View {
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
void handle_autostart();
// Opens a keyboard; once confirmed, shows a pick-list of every internal,
// external (.ppma) and standalone (.ppmp) app whose name matches the query.
// Reuses the transient text-entry view, so the only steady-state cost is
// the two members below; the keyboard and the results list are allocated
// only while a search is in progress.
void start_app_search();
// Launches one search result and drops the search UI first, so only one app
// is ever resident. 'factory' non-null selects an internal app; otherwise
// 'call_name' is an external/standalone app file name (no extension).
// Arguments are taken by value on purpose: home() below frees the results
// view (and the button that called this), so nothing here may touch it.
void launch_search_entry(ViewFactoryBase* factory, std::string call_name);
private:
struct ViewState {
std::unique_ptr<View> view;
std::function<void()> on_pop;
};
// Builds and pushes the results list. Runs from the keyboard view's on_pop,
// i.e. after that view is fully destroyed, so pushing here can't be
// clobbered by the keyboard's own trailing pop().
void open_app_search_results();
std::string app_search_query_{}; // keyboard buffer, owned across its lifetime
bool app_search_committed_{false}; // true only when the user confirmed a non-empty query
std::vector<ViewState> view_stack{};
Widget* view() const;
@@ -329,6 +359,9 @@ class InformationView : public View {
void refresh();
bool firmware_checksum_error();
// Whole info bar is a touch target that opens the app search.
bool on_touch(const TouchEvent event) override;
private:
// static constexpr auto version_string = "v1.4.4"; // This is commented out as we are now setting the version via ENV (VERSION_STRING=v1.0.0)
NavigationView& nav_;
@@ -337,8 +370,15 @@ class InformationView : public View {
{0, 0, screen_width, 16},
Theme::getInstance()->bg_darker->background};
// Visual hint that the info bar starts an app search when tapped.
Image search_icon{
{2, 0, 16, 16},
&bitmap_icon_search,
Theme::getInstance()->fg_light->foreground,
Theme::getInstance()->bg_darker->background};
Text version{
{2, 0, 11 * 8, 16},
{18, 0, 9 * 8, 16}, // 9 chars keeps room for the "FLASH ERR" indicator; right edge unchanged from before the icon
VERSION_STRING};
LiveDateTime ltime{
@@ -422,6 +462,19 @@ class SystemMenuView : public BtnGridView {
void hackrf_mode(NavigationView& nav);
};
// Pick-list of apps matching an app-search query. Reuses the button grid so it
// behaves (and launches on tap) exactly like the normal app menus.
class AppSearchResultsView : public BtnGridView {
public:
AppSearchResultsView(NavigationView& nav, std::vector<AppSearchEntry>&& entries);
std::string title() const override { return "Search"; };
private:
NavigationView& nav_;
std::vector<AppSearchEntry> entries_;
void on_populate() override;
};
class SystemView : public View {
public:
SystemView(
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
+31 -4
View File
@@ -220,11 +220,17 @@ endif()
# Upstream hackrf headers now gate on IS_* macros normally populated by
# hackrf/firmware/platform-detect.cmake. We don't include that here, so derive
# the IS_* set from BOARD ourselves. IS_H1_R9 is forced to 0 (compile-time
# constant) so `if (IS_H1_R9)` branches resolve without needing the runtime
# detected_platform() symbol, which mayhem doesn't link in.
# the IS_* set from BOARD ourselves. IS_H1_R9 must mirror the upstream
# HACKRF_ONE definition: a *runtime* check on detected_platform(). The only
# baseband image that compiles IS_H1_R9-gated upstream files (rf_path.c,
# si5351c.c, sgpio.c, platform_gpio.c, platform_scu.c) is sd_over_usb, which
# does link platform_detect.c and calls detect_hardware_platform() at startup.
# Forcing IS_H1_R9=0 here silently compiled out all H1R9 behaviour, so a real
# HackRF One r9 was configured with the OG clock/GPIO/RF-path setup in that
# image. All usages are `#ifdef IS_H1_R9` + `if (IS_H1_R9)` (never arithmetic
# `#if`), so a function-call expression is safe.
if(BOARD STREQUAL "HACKRF_ONE")
set(BOARD_IS_DEFS "-DIS_HACKRF_ONE=1 -DIS_NOT_PRALINE=1 -DIS_H1_R9=0 -DIS_NOT_H1_R9=1 -DIS_NOT_RAD1O=1 -DIS_NOT_JAWBREAKER=1 -DIS_H1_OR_PRALINE=1 -DIS_H1_OR_RAD1O=1 -DIS_H1_OR_JAWBREAKER=1 -DIS_EXPANSION_COMPATIBLE=1")
set(BOARD_IS_DEFS "-DIS_HACKRF_ONE=1 -DIS_NOT_PRALINE=1 -D'IS_H1_R9=(detected_platform()==BOARD_ID_HACKRF1_R9)' -D'IS_NOT_H1_R9=(!IS_H1_R9)' -DIS_NOT_RAD1O=1 -DIS_NOT_JAWBREAKER=1 -DIS_H1_OR_PRALINE=1 -DIS_H1_OR_RAD1O=1 -DIS_H1_OR_JAWBREAKER=1 -DIS_EXPANSION_COMPATIBLE=1")
elseif(BOARD STREQUAL "PRALINE")
set(BOARD_IS_DEFS "-DIS_PRALINE=1 -DIS_NOT_HACKRF_ONE=1 -DIS_NOT_H1_R9=1 -DIS_NOT_RAD1O=1 -DIS_NOT_JAWBREAKER=1 -DIS_H1_OR_PRALINE=1 -DIS_FOUR_LEDS=1 -DIS_EXPANSION_COMPATIBLE=1")
elseif(BOARD STREQUAL "RAD1O")
@@ -510,6 +516,20 @@ DeclareTargets(PFUT flash_utility)
set(add_to_firmware FALSE)
set(MODE_FLAGS "-O3")
### VOR TX
set(MODE_CPPSRC
proc_vor_tx.cpp
)
DeclareTargets(PVTX vor_tx)
### VOR RX
set(MODE_CPPSRC
proc_vor_rx.cpp
)
DeclareTargets(PVRX vor_rx)
### FLEX RX
set(MODE_CPPSRC
@@ -749,6 +769,13 @@ set(MODE_CPPSRC
)
DeclareTargets(PTON tones)
### Signal Hunter
set(MODE_CPPSRC
proc_signal_hunter.cpp
)
DeclareTargets(HUNT signal_hunter)
### Time Sink
+189
View File
@@ -0,0 +1,189 @@
/*
* Copyright (C) 2026 Matej Sochan
*
* 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 "proc_signal_hunter.hpp"
#include "event_m4.hpp"
#include "portapack_shared_memory.hpp"
#include "dsp_fir_taps.hpp"
void SignalHunterProcessor::configure() {
decim_0.configure(taps_200k_decim_0.taps);
window_idx = 0;
window_sum = 0;
for (auto& v : window_buf) v = 0;
reset_hunt_state();
configured = true;
}
void SignalHunterProcessor::reset_hunt_state() {
iq_ring_idx = 0;
for (auto& v : iq_ring) v = complex16_t{0, 0};
hangtime_counter = 0;
hunt_state = HuntState::IDLE;
flush_pending = false;
}
void SignalHunterProcessor::execute(const buffer_c8_t& buffer) {
// Process stream closure.
// Deferred teardown ensures exclusive mutation of the hunt state by the BasebandThread,
// preventing data races with on_message().
if (stream_close_requested.exchange(false)) {
stream.reset();
reset_hunt_state();
}
// CRITICAL IPC FIX: If M0 is tearing down the capture thread, it needs one last buffer
// to unblock buffers.get() and exit gracefully. We MUST continue decimating and writing
// as long as the stream exists, even if hunting was set to false by a manual UI stop.
if (!hunting && !stream) return;
const auto out = decim_0.execute(buffer, dst_buffer);
feed_channel_stats(out);
// Pre-roll flush: first execute() call after CaptureConfigMessage creates stream.
if (flush_pending && stream_active && stream) {
// Write ring buffer from oldest to newest sample (2 chunks due to wrap-around)
size_t first = IQ_RING_SAMPLES - flush_start_idx;
stream->write(&iq_ring[flush_start_idx], first * sizeof(complex16_t));
if (flush_start_idx > 0)
stream->write(&iq_ring[0], flush_start_idx * sizeof(complex16_t));
flush_pending = false;
}
for (size_t i = 0; i < out.count; i++) {
auto s = out.p[i];
iq_ring[iq_ring_idx] = s;
iq_ring_idx = (iq_ring_idx + 1) % IQ_RING_SAMPLES;
uint32_t energy = ((int32_t)s.real() * s.real() + (int32_t)s.imag() * s.imag()) >> 16;
window_sum -= window_buf[window_idx];
window_buf[window_idx] = energy;
window_sum += energy;
window_idx = (window_idx + 1) % WINDOW_SIZE;
uint32_t avg = window_sum / WINDOW_SIZE;
switch (hunt_state.load()) {
case HuntState::IDLE:
// Only trigger new recordings if we are actively hunting.
// Prevents a stray trigger from starting a new capture right after manual STOP.
if (hunting && (avg > energy_threshold)) {
HunterTriggerMessage msg{};
msg.energy = avg;
shared_memory.application_queue.push(msg);
hunt_state = HuntState::AWAITING_STREAM;
}
break;
case HuntState::AWAITING_STREAM:
break;
case HuntState::RECORDING:
if (avg < energy_threshold) {
hangtime_counter = hangtime_samples_limit;
hunt_state = HuntState::HANGTIME;
}
break;
case HuntState::HANGTIME:
if (avg > energy_threshold) {
hunt_state = HuntState::RECORDING;
} else if (--hangtime_counter == 0) {
HunterStopMessage stop_msg{};
shared_memory.application_queue.push(stop_msg);
hunt_state = HuntState::AWAITING_CLOSE;
}
break;
case HuntState::AWAITING_CLOSE:
// Do not transition to IDLE by ourselves — wait for CaptureConfigMessage(nullptr)
// which arrives via BasebandCapture destructor after CaptureThread is destroyed.
break;
}
}
// CRITICAL: Continue writing to stream whenever it exists, even in AWAITING_CLOSE state,
// and even after a manual STOP — M0's CaptureThread needs this final data to unblock.
if (stream_active && stream) {
stream->write(out.p, sizeof(complex16_t) * out.count);
}
}
void SignalHunterProcessor::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::HunterConfig: {
const auto& m = *reinterpret_cast<const HunterConfigMessage*>(message);
energy_threshold = m.energy_threshold;
// Convert hangtime to post-decimation samples:
// 1 ms = 250 samples @ 250 kHz post-decimation rate (from 2 MHz baseband / 8x decimator)
// This dynamic hangtime allows configurable silence tolerance (e.g., 500 ms)
hangtime_samples_limit = m.hangtime_ms * 250;
// Addresses "WHAT-IF" integer underflow
if (hangtime_samples_limit == 0) {
hangtime_samples_limit = 1;
}
if (!configured) configure();
// DO NOT close the stream here if (!m.start).
// M4 must keep producing buffers until M0 explicitly sends CaptureConfig(nullptr)
// to prevent the M0 CaptureThread from deadlocking in buffers.get().
hunting = m.start;
break;
}
case Message::ID::CaptureConfig: {
const auto& m = *reinterpret_cast<const CaptureConfigMessage*>(message);
if (m.config) {
// Synchronous allocation guarantees validity of m.config pointer
// (must not be deferred into execute(), see prior HardFault).
stream = std::make_unique<StreamInput>(m.config);
flush_start_idx = iq_ring_idx; // Snapshot: current write position
flush_pending = true; // execute() will process ring buffer pre-roll
stream_active = true;
hunt_state = HuntState::RECORDING;
} else {
// This is the ONLY safe place to initiate stream teardown —
// it arrives after M0's CaptureThread has already fully drained
// and exited, so it's safe for execute() to reset() the stream.
stream_active = false;
stream_close_requested = true;
}
break;
}
default:
break;
}
}
int main() {
EventDispatcher event_dispatcher{std::make_unique<SignalHunterProcessor>()};
event_dispatcher.run();
return 0;
}
+111
View File
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2026 Matej Sochan
*
* 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.
*/
#ifndef __PROC_SIGNAL_HUNTER_H__
#define __PROC_SIGNAL_HUNTER_H__
#include <atomic>
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "message.hpp"
#include "dsp_decimate.hpp"
#include "stream_input.hpp"
#include <cstdint>
#include <cstddef>
#include <array>
#include <memory>
class SignalHunterProcessor : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
static constexpr size_t baseband_fs = 2000000;
// Hardcoded Decimate-by-8 FIR filter.
// 2,000,000 Hz / 8 = 250,000 Hz target sample rate.
// Used to avoid SD Card bottlenecks
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
std::array<complex16_t, 512> dst_buffer_data{};
const buffer_c16_t dst_buffer{dst_buffer_data.data(), dst_buffer_data.size()};
// --- Energy sliding window for threshold detection ---
static constexpr size_t WINDOW_SIZE = 64;
uint32_t window_buf[WINDOW_SIZE]{};
size_t window_idx{0};
uint32_t window_sum{0};
std::atomic<bool> flush_pending{false};
size_t flush_start_idx{0};
// Pre-trigger IQ ring buffer
// IQ_RING_SAMPLES = 2048 provides ~8ms pre-roll buffer (2048 / 250kHz).
// This captures OOK preambles efficiently while maintaining strict 8KB flush size to prevent
// Out-Of-Memory (OOM) / HardFault crashes on M0 CaptureThread, which has only 16KB buffer constraint.
static constexpr size_t IQ_RING_SAMPLES = 2048;
std::array<complex16_t, IQ_RING_SAMPLES> iq_ring{};
size_t iq_ring_idx{0};
uint32_t energy_threshold{5000};
std::atomic<bool> hunting{false};
std::atomic<bool> stream_active{false};
std::atomic<bool> stream_close_requested{false};
bool configured{false};
// State machine tracking signal detection lifecycle
enum class HuntState {
// Awaiting energy threshold crossing
IDLE,
// Trigger sent; waiting for CaptureConfigMessage from M0 to create stream
AWAITING_STREAM,
// Stream active; writing live decimated samples
RECORDING,
// Energy dropped below threshold; checking if signal returns
HANGTIME,
// Stop sent; waiting for CaptureConfigMessage(nullptr) from M0 to destroy stream
AWAITING_CLOSE
};
std::atomic<HuntState> hunt_state{HuntState::IDLE};
uint32_t hangtime_counter{0};
// Dynamic hangtime configuration: holds sample count in post-decimation domain (250 kHz rate).
// Converted from milliseconds: hangtime_ms * 250 samples/ms.
// Set dynamically via HunterConfigMessage to allow configurable silence tolerance.
uint32_t hangtime_samples_limit{0};
std::unique_ptr<StreamInput> stream{};
void configure();
void reset_hunt_state();
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
RSSIThread rssi_thread{};
};
#endif /*__PROC_SIGNAL_HUNTER_H__*/
+272
View File
@@ -0,0 +1,272 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* 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 "proc_vor_rx.hpp"
#include "audio_output.hpp"
#include "audio_dma.hpp"
#include "dsp_iir_config.hpp"
#include "portapack_shared_memory.hpp"
#include "event_m4.hpp"
#include <algorithm>
namespace {
constexpr float kPi = 3.14159265358979323846f;
}
VorRx::VorRx() {
channel_spectrum.set_decimation_factor(1);
baseband_thread.start();
rssi_thread.start();
}
void VorRx::execute(const buffer_c8_t& buffer) {
if (!configured) {
return;
}
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
channel_spectrum.feed(decim_1_out, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
// No decim_2 stage: the standard AM decim_2 filter is a ~4.5 kHz low-pass
// that would attenuate the 9960 Hz VOR subcarrier by ~60 dB and destroy the
// reference phase. The channel stays at 48 kHz so the subcarrier survives.
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
feed_channel_stats(channel_out);
auto audio = demod_am.execute(channel_out, audio_buffer);
if (vor_enabled) {
process_vor_metrics(audio);
// Strip the 9960 Hz subcarrier from the audible signal (the metrics
// above still ran on the unfiltered audio so the decode is unaffected).
audio_lpf_1.execute_in_place(audio);
audio_lpf_2.execute_in_place(audio);
}
audio_compressor.execute_in_place(audio);
audio_output.write(audio);
}
void VorRx::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::UpdateSpectrum:
case Message::ID::SpectrumStreamingConfig:
channel_spectrum.on_message(message);
break;
case Message::ID::VorRxConfigure:
configure_vor(*reinterpret_cast<const VorRxConfigureMessage*>(message));
break;
case Message::ID::AMConfigure:
configure(*reinterpret_cast<const AMConfigureMessage*>(message));
break;
default:
break;
}
}
void VorRx::configure(const AMConfigureMessage& message) {
decim_0.configure(message.decim_0_filter.taps);
decim_1.configure(message.decim_1_filter.taps);
channel_filter.configure(message.channel_filter.taps, channel_filter_decimation_factor);
constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor;
constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor;
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
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(message.channel_spectrum_decimation_factor);
audio_output.configure(message.audio_hpf_lpf_config);
configured = true;
}
void VorRx::PhaseOscillator::configure(float frequency_hz, float sample_rate_hz) {
const float step = 2.0f * kPi * frequency_hz / sample_rate_hz;
step_sin = sinf(step);
step_cos = cosf(step);
sin_v = 0.0f;
cos_v = 1.0f;
}
void VorRx::PhaseOscillator::advance() {
const float next_cos = (cos_v * step_cos) - (sin_v * step_sin);
const float next_sin = (sin_v * step_cos) + (cos_v * step_sin);
cos_v = next_cos;
sin_v = next_sin;
}
void VorRx::PhaseOscillator::renormalize() {
// The recursive rotator slowly drifts in amplitude over long runs;
// rescale to the unit circle so accumulated tone levels stay meaningful.
const float mag = sqrtf((cos_v * cos_v) + (sin_v * sin_v));
if (mag > 0.0f) {
const float inv = 1.0f / mag;
cos_v *= inv;
sin_v *= inv;
}
}
void VorRx::configure_vor(const VorRxConfigureMessage& message) {
vor_enabled = message.enabled;
vor_reference_osc.configure(vor_reference_hz, 48000.0f);
vor_subcarrier_osc.configure(vor_subcarrier_hz, 48000.0f);
audio_lpf_1.configure(audio_48k_lpf_3200hz_config);
audio_lpf_2.configure(audio_48k_lpf_3200hz_config);
vor_ref_i = 0.0f;
vor_ref_q = 0.0f;
vor_var_i = 0.0f;
vor_var_q = 0.0f;
vor_dc_sum = 0.0f;
vor_sub_lp_i = 0.0f;
vor_sub_lp_q = 0.0f;
vor_sub_prev_i = 0.0f;
vor_sub_prev_q = 0.0f;
vor_sample_count = 0;
}
uint16_t VorRx::normalize_degrees(float degrees) {
while (degrees < 0.0f) {
degrees += 360.0f;
}
while (degrees >= 360.0f) {
degrees -= 360.0f;
}
return static_cast<uint16_t>(degrees + 0.5f);
}
void VorRx::send_vor_status(uint16_t phase_deg, uint16_t radial_deg, uint16_t ref_level, uint16_t var_level, uint8_t quality, bool valid, bool to_from) {
const VorRxStatusDataMessage message{phase_deg, radial_deg, ref_level, var_level, quality, valid, to_from};
shared_memory.application_queue.push(message);
}
void VorRx::process_vor_metrics(const buffer_f32_t& audio) {
for (size_t i = 0; i < audio.count; ++i) {
const float sample = audio.p[i];
const float ref_cos = vor_reference_osc.cosine();
const float ref_sin = vor_reference_osc.sine();
const float sub_cos = vor_subcarrier_osc.cosine();
const float sub_sin = vor_subcarrier_osc.sine();
// Variable signal: 30 Hz amplitude modulation of the carrier envelope.
// Correlate the envelope against a local 30 Hz tone (DC rejected because
// the window spans an exact integer number of 30 Hz cycles).
vor_dc_sum += sample;
vor_var_i += sample * ref_cos;
vor_var_q -= sample * ref_sin;
// Reference signal: 30 Hz FM on the 9960 Hz subcarrier. Quadrature
// down-convert the subcarrier, low-pass to its +/-480 Hz baseband, then
// FM-demodulate (phase difference between successive samples).
const float sc_i = sample * sub_cos;
const float sc_q = -sample * sub_sin;
vor_sub_lp_i += (1.0f - vor_sub_lp_alpha) * (sc_i - vor_sub_lp_i);
vor_sub_lp_q += (1.0f - vor_sub_lp_alpha) * (sc_q - vor_sub_lp_q);
const float num = (vor_sub_lp_q * vor_sub_prev_i) - (vor_sub_lp_i * vor_sub_prev_q);
const float den = (vor_sub_lp_i * vor_sub_prev_i) + (vor_sub_lp_q * vor_sub_prev_q);
const float fm = atan2f(num, den);
vor_sub_prev_i = vor_sub_lp_i;
vor_sub_prev_q = vor_sub_lp_q;
vor_ref_i += fm * ref_cos;
vor_ref_q -= fm * ref_sin;
vor_reference_osc.advance();
vor_subcarrier_osc.advance();
++vor_sample_count;
if (vor_sample_count < vor_window_samples) {
continue;
}
const float inv_n = 1.0f / static_cast<float>(vor_window_samples);
const float variable_phase = atan2f(vor_var_q, vor_var_i);
const float reference_phase = atan2f(vor_ref_q, vor_ref_i);
// Real VOR radials increase clockwise, i.e. the variable tone lags the
// reference by the bearing. The decoder therefore reports
// (reference - variable); the opposite order mirrors every bearing to
// 360 - true. Add back the receive-chain phase lag (see
// vor_ref_phase_lag_deg): with this subtraction order the delayed
// reference makes the raw radial read low, so the correction is added.
float phase_diff = ((reference_phase - variable_phase) * 180.0f / kPi) + vor_ref_phase_lag_deg;
while (phase_diff < 0.0f) {
phase_diff += 360.0f;
}
while (phase_diff >= 360.0f) {
phase_diff -= 360.0f;
}
// Scale-invariant lock metrics (independent of RF/AGC level):
// - var_ratio: 30 Hz AM depth vs. carrier DC (~0.30 for a real VOR).
// - ref_amp: FM-demodulated 30 Hz amplitude (phase-only, so it does
// not depend on signal amplitude, ~0.065 when locked).
const float dc_level = vor_dc_sum * inv_n;
const float var_amp = 2.0f * sqrtf((vor_var_i * vor_var_i) + (vor_var_q * vor_var_q)) * inv_n;
const float ref_amp = 2.0f * sqrtf((vor_ref_i * vor_ref_i) + (vor_ref_q * vor_ref_q)) * inv_n;
const float var_ratio = (dc_level > 0.0f) ? (var_amp / dc_level) : 0.0f;
const auto phase_deg = normalize_degrees(phase_diff);
const auto radial_deg = phase_deg;
const auto ref_level = static_cast<uint16_t>(std::min(65535.0f, ref_amp * 10000.0f));
const auto var_level = static_cast<uint16_t>(std::min(65535.0f, var_ratio * 1000.0f));
const bool valid = (ref_amp > 0.03f) && (var_ratio > 0.15f);
// TO/FROM depends on the operator-selected OBS course, which only the
// application knows, so it is derived there (see VorRxView). Emit a
// stable placeholder here rather than a radial-only guess.
const bool to_from = false;
uint8_t quality = 0;
if (valid) {
const float q = (ref_amp / 0.065f) * 100.0f;
quality = static_cast<uint8_t>(std::min(100.0f, std::max(0.0f, q)));
}
send_vor_status(phase_deg, radial_deg, ref_level, var_level, quality, valid, to_from);
vor_reference_osc.renormalize();
vor_subcarrier_osc.renormalize();
vor_sample_count = 0;
vor_ref_i = 0.0f;
vor_ref_q = 0.0f;
vor_var_i = 0.0f;
vor_var_q = 0.0f;
vor_dc_sum = 0.0f;
}
}
int main() {
audio::dma::init_audio_out();
EventDispatcher event_dispatcher{std::make_unique<VorRx>()};
event_dispatcher.run();
return 0;
}
+150
View File
@@ -0,0 +1,150 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* 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.
*/
#ifndef __PROC_VOR_RX_H__
#define __PROC_VOR_RX_H__
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "dsp_decimate.hpp"
#include "dsp_demodulate.hpp"
#include "dsp_iir.hpp"
#include "audio_compressor.hpp"
#include "audio_output.hpp"
#include "spectrum_collector.hpp"
#include <array>
#include <cstdint>
#include <cmath>
// Standalone VOR receiver baseband.
// Demodulates the AM channel at 48 kHz (so the 9960 Hz subcarrier stays
// representable) and decodes the 30 Hz reference vs. variable phase to derive
// the radial.
class VorRx : public BasebandProcessor {
public:
VorRx();
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
static constexpr size_t baseband_fs = 3072000;
// VOR keeps the channel at 48 kHz instead of the AM audio 12 kHz, so the
// 9960 Hz subcarrier stays below Nyquist. The standard AM decim_2 stage is
// therefore omitted (its ~4.5 kHz low-pass would remove the subcarrier).
static constexpr size_t channel_filter_decimation_factor = 1;
std::array<complex16_t, 512> dst{};
const buffer_c16_t dst_buffer{
dst.data(),
dst.size()};
std::array<float, 32> audio{};
const buffer_f32_t audio_buffer{
audio.data(),
audio.size()};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
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};
bool vor_enabled{false};
static constexpr float vor_reference_hz{30.0f};
static constexpr float vor_subcarrier_hz{9960.0f};
// 100 ms at 48 kHz (exactly 3 cycles of 30 Hz)
static constexpr uint32_t vor_window_samples{4800};
// One-pole low-pass to isolate the +/-480 Hz FM subcarrier baseband
// after quadrature down-conversion (corner ~600 Hz at 48 kHz).
static constexpr float vor_sub_lp_alpha{0.9245f};
// Fixed phase lag the receive chain adds to the recovered 30 Hz reference
// tone, expressed in degrees at 30 Hz. It comes almost entirely from the
// one-pole subcarrier low-pass above (~2.75 deg of lag at 30 Hz for
// alpha = 0.9245) plus a ~0.1 deg half-sample delay in the FM
// differentiator. Because the reference tone is delayed, the raw radial
// (reference - variable) reads low by this amount, so we add it back.
// Value verified by a TX->RX loopback (proc_vor_tx fed through this
// decoder): every transmitted radial decoded ~3.2 deg low before this
// correction, and on-bearing afterwards.
static constexpr float vor_ref_phase_lag_deg{3.2f};
struct PhaseOscillator {
float sin_v{0.0f};
float cos_v{1.0f};
float step_sin{0.0f};
float step_cos{1.0f};
void configure(float frequency_hz, float sample_rate_hz);
float cosine() const { return cos_v; }
float sine() const { return sin_v; }
void advance();
void renormalize();
} vor_reference_osc{}, vor_subcarrier_osc{};
// 30 Hz DFT accumulators for the variable (AM) and reference (FM) tones.
float vor_ref_i{0.0f};
float vor_ref_q{0.0f};
float vor_var_i{0.0f};
float vor_var_q{0.0f};
float vor_dc_sum{0.0f};
// Subcarrier quadrature down-converter / FM-demodulator state.
float vor_sub_lp_i{0.0f};
float vor_sub_lp_q{0.0f};
float vor_sub_prev_i{0.0f};
float vor_sub_prev_q{0.0f};
uint32_t vor_sample_count{0};
dsp::demodulate::AM demod_am{};
// Two cascaded 2nd-order Butterworth low-passes (4th-order total) that strip
// the 9960 Hz FM subcarrier out of the audio before it reaches the speaker.
// The channel is kept wide (48 kHz) so the subcarrier survives for the
// radial decode, so it must be filtered out of the audible path here.
IIRBiquadFilter audio_lpf_1{};
IIRBiquadFilter audio_lpf_2{};
FeedForwardCompressor audio_compressor{};
AudioOutput audio_output{};
SpectrumCollector channel_spectrum{};
/* NB: Threads should be the last members in the class definition. */
#ifdef PRALINE
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive,
/*auto_start*/ false};
RSSIThread rssi_thread{/*auto_start*/ false};
#else
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
RSSIThread rssi_thread{};
#endif
void configure(const AMConfigureMessage& message);
void configure_vor(const VorRxConfigureMessage& message);
void process_vor_metrics(const buffer_f32_t& audio);
void send_vor_status(uint16_t phase_deg, uint16_t radial_deg, uint16_t ref_level, uint16_t var_level, uint8_t quality, bool valid, bool to_from);
static uint16_t normalize_degrees(float degrees);
};
#endif /*__PROC_VOR_RX_H__*/
+169
View File
@@ -0,0 +1,169 @@
/*
* 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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "proc_vor_tx.hpp"
#include "morse.hpp"
#include "sine_table_int8.hpp"
#include "event_m4.hpp"
void VorTxProcessor::execute(const buffer_c8_t& buffer) {
for (size_t i = 0; i < buffer.count; i++) {
if (!configured) {
buffer.p[i] = {0, 0};
continue;
}
// 30 Hz reference and variable tones (same frequency, offset by the radial).
const int32_t ref_sine = sine_table_i8[(phase_30 >> 24) & 0xFF];
const int32_t var_sine = sine_table_i8[((phase_30 - radial_offset) >> 24) & 0xFF];
phase_30 += delta_30;
// 9960 Hz subcarrier, FM-modulated +/- 480 Hz by the 30 Hz reference tone.
const int32_t sub_inc = static_cast<int32_t>(delta_sub) + ((ref_sine * static_cast<int32_t>(delta_480)) >> 7);
phase_sub += static_cast<uint32_t>(sub_inc);
const int32_t sub_sine = sine_table_i8[(phase_sub >> 24) & 0xFF];
// Optional 1020 Hz identification tone.
int32_t id_sine = 0;
if (ident_enabled && ident_tone_active()) {
id_sine = sine_table_i8[(phase_id >> 24) & 0xFF];
phase_id += delta_1020;
}
// DSB-AM envelope (carrier kept at LO -> Q = 0).
int32_t env = carrier_level + (((var_sine * var_depth) + (sub_sine * sub_depth) + (id_sine * id_depth)) >> 7);
if (env < 0) env = 0;
if (env > 127) env = 127;
buffer.p[i] = {static_cast<int8_t>(env), 0};
}
}
void VorTxProcessor::vor_tx_config(const VorTxConfigureMessage& message) {
// Real VOR radials increase clockwise: the variable tone lags the 30 Hz
// reference by the bearing, so the offset is subtracted (see the matching
// (reference - variable) decode in proc_vor_rx). A TX->RX loopback with this
// convention still decodes a transmitted radial of N degrees as N degrees,
// and it now matches real ground-station VOR bearings.
radial_offset = static_cast<uint32_t>(static_cast<uint64_t>(message.radial_deg % 360) * phase_period / 360);
ident_enabled = message.ident_enabled;
for (size_t i = 0; i < sizeof(ident_text); ++i)
ident_text[i] = message.ident_text[i];
ident_text[sizeof(ident_text) - 1] = '\0';
build_ident_schedule();
configured = message.enabled;
}
// Render the ident text into a repeating sequence of keyed-on / keyed-off
// sample runs using the shared ITU Morse table. Timing is derived from the
// standard "dot = 1200 / wpm" relation; letters are separated by a 3-unit gap,
// words by 7 units, and a trailing gap pads each cycle to a fixed 10 s period.
void VorTxProcessor::build_ident_schedule() {
ident_segment_count = 0;
ident_index = 0;
ident_sample_counter = 0;
const uint32_t dot_samples = static_cast<uint32_t>(1200ULL * baseband_fs / (ident_wpm * 1000ULL));
uint32_t total_samples = 0;
auto push_samples = [&](bool on, uint32_t samples) {
if (ident_segment_count >= max_ident_segments) return;
ident_segments[ident_segment_count++] = {on, samples};
total_samples += samples;
};
auto push = [&](bool on, uint32_t units) {
push_samples(on, units * dot_samples);
};
bool any = false;
for (size_t n = 0; n < sizeof(ident_text) && ident_text[n]; ++n) {
char ch = ident_text[n];
if (ch >= 'a' && ch <= 'z') ch -= 32;
uint16_t code = 0;
if (ch >= '!' && ch <= '_') code = morse::morse_ITU[ch - '!'];
if (!code) {
// Space or unsupported character: word gap between symbols.
if (any) push(false, MORSE_WORD_SPACE);
continue;
}
const uint16_t code_size = code & 7;
for (uint16_t c = 0; c < code_size; ++c) {
const bool dash = ((code << c) & 0x8000) != 0;
push(true, dash ? MORSE_DASH : MORSE_DOT);
if (c + 1 < code_size)
push(false, MORSE_SYMBOL_SPACE);
}
push(false, MORSE_LETTER_SPACE);
any = true;
}
if (!any) {
// No sendable characters: disable keying entirely.
ident_segment_count = 0;
return;
}
// Pad the trailing silence so ident-start to ident-start is a full 10 s.
// If the keyed sequence is somehow longer than the period, fall back to a
// one word-space gap so repeats never run back-to-back.
const uint32_t min_gap = MORSE_WORD_SPACE * dot_samples;
const uint32_t gap = (ident_period_samples > total_samples + min_gap)
? ident_period_samples - total_samples
: min_gap;
push_samples(false, gap);
}
// Advances the keying schedule by one sample and returns whether the ident
// tone should be sounding right now. Returns false when there is nothing to
// send so the caller leaves the ident tone off.
bool VorTxProcessor::ident_tone_active() {
if (ident_segment_count == 0) return false;
if (ident_sample_counter == 0)
ident_sample_counter = ident_segments[ident_index].length;
const bool on = ident_segments[ident_index].on;
if (ident_sample_counter == 0 || --ident_sample_counter == 0) {
++ident_index;
if (ident_index >= ident_segment_count) ident_index = 0;
}
return on;
}
void VorTxProcessor::on_message(const Message* const msg) {
if (msg->id == Message::ID::VorTxConfigure) {
vor_tx_config(*reinterpret_cast<const VorTxConfigureMessage*>(msg));
}
}
int main() {
EventDispatcher event_dispatcher{std::make_unique<VorTxProcessor>()};
event_dispatcher.run();
return 0;
}
+90
View File
@@ -0,0 +1,90 @@
/*
* 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; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PROC_VOR_TX_H__
#define __PROC_VOR_TX_H__
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
// Generates a conventional VOR composite signal as DSB-AM (carrier at LO, Q = 0):
// - 30 Hz "variable" tone, amplitude-modulated directly (phase = transmitted radial)
// - 9960 Hz subcarrier, FM-modulated (+/- 480 Hz) by a 30 Hz "reference" tone
// - optional 1020 Hz identification tone
// The radial encoded is the phase difference between the variable and reference 30 Hz tones.
class VorTxProcessor : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const msg) override;
private:
static constexpr size_t baseband_fs = 1536000;
static constexpr uint64_t phase_period = 1ULL << 32;
// Per-sample phase increments (top byte indexes the 256-entry sine table).
static constexpr uint32_t delta_30 = static_cast<uint32_t>(30ULL * phase_period / baseband_fs);
static constexpr uint32_t delta_sub = static_cast<uint32_t>(9960ULL * phase_period / baseband_fs);
static constexpr uint32_t delta_1020 = static_cast<uint32_t>(1020ULL * phase_period / baseband_fs);
static constexpr uint32_t delta_480 = static_cast<uint32_t>(480ULL * phase_period / baseband_fs);
// AM levels (int8 scale). carrier_level + sum of contributions must stay <= 127.
static constexpr int32_t carrier_level = 60;
static constexpr int32_t var_depth = 18; // ~30% of carrier
static constexpr int32_t sub_depth = 18; // ~30% of carrier
static constexpr int32_t id_depth = 6; // ~10% of carrier
// CW identifier keying. Real VOR stations key the 1020 Hz ident tone in
// Morse at ~7 words per minute, so the schedule below is expressed in
// "dot" time units and rendered to on/off sample runs at config time.
static constexpr uint32_t ident_wpm = 7;
static constexpr size_t max_ident_segments = 128;
// VOR ident is transmitted at least every 10 s; repeat the keyed identifier
// on a fixed 10 s cycle (ident start to next ident start).
static constexpr uint32_t ident_period_samples = 10 * baseband_fs;
struct IdentSegment {
bool on;
uint32_t length; // duration in samples
};
uint32_t phase_30{0};
uint32_t phase_sub{0};
uint32_t phase_id{0};
uint32_t radial_offset{0};
bool ident_enabled{true};
bool configured{false};
char ident_text[8]{};
IdentSegment ident_segments[max_ident_segments]{};
size_t ident_segment_count{0};
size_t ident_index{0};
uint32_t ident_sample_counter{0};
void vor_tx_config(const VorTxConfigureMessage& message);
void build_ident_schedule();
bool ident_tone_active();
/* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Transmit};
};
#endif /* __PROC_VOR_TX_H__ */
+2 -2
View File
@@ -772,7 +772,7 @@ void cpu_clock_init(void) {
clock_source_t activate_best_clock_source(void) {
#ifdef HACKRF_ONE
/* Ensure PortaPack reference oscillator is off while checking for external clock input. */
if (portapack_reference_oscillator && portapack()) {
if (portapack_present()) {
portapack_reference_oscillator(false);
}
#endif
@@ -785,7 +785,7 @@ clock_source_t activate_best_clock_source(void) {
} else {
#ifdef HACKRF_ONE
/* Enable PortaPack reference oscillator (if present), and check for valid clock. */
if (portapack_reference_oscillator && portapack()) {
if (portapack_present()) {
portapack_reference_oscillator(true);
delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */
if (si5351c_clkin_signal_valid(&clock_gen)) {
@@ -57,55 +57,49 @@ const PALConfig pal_default_config = {
// GPIO0
.data
#ifdef PRALINE
= (0 << 7) // P2_7: Input GND
| (0 << 8) // P1_1: Input GND
| (1 << 9) // P1_2: Input VCC
| boot_bit(p1_ctrl0, 0) // P2_10: P1_CTRL0, start low
= boot_bit(p1_ctrl0, 0) // P2_10: P1_CTRL0, start low
| boot_bit(clkin_ctrl, 0) // P1_20: CLKIN_CTRL - start low
#else
= (1 << 14) // P2_10: AMP_BYPASS
| (1 << 15) // P1_20: CS_XCVR
= boot_bit(amp_bypass, 1) // P2_10: AMP_BYPASS
| (1 << 15) // P1_20: CS_XCVR
#endif
| (1 << 11) // P1_4: SSP1_MOSI
| boot_bit(sgpio_12, 0) // P1_18: SGPIO12, HOST_Q_INVERT
| boot_bit(sgpio_11, 0) // P1_17: SGPIO11, HOST_DIRECTION, Praline: FPGA HOST_DIRECTION
| (1 << 10) // P1_3: SSP1_MISO
| (0 << 9) // P1_2: Varies by revision, float until detection
| (0 << 8) // P1_1: Varies by revision, float until detection
| (0 << 7) // P2_7: Varies by revision, float until detection
| (0 << 6) // P3_6: SPIFI_MISO
| boot_bit(sgpio_5, 1) // P6_6: SGPIO5, HOST_DATA5, Praline: FPGA HOST_DATA5
| boot_bit(sgpio_7, 1) // P1_0: SGPIO7, HOST_DATA7, Praline: FPGA HOST_DATA7
| boot_bit(sgpio_3, 1) // P1_16: SGPIO3, HOST_DATA3, Praline: FPGA HOST_DATA3
| boot_bit(sgpio_2, 1) // P1_15: SGPIO2, HOST_DATA2, Praline: FPGA HOST_DATA2
| boot_bit(sgpio_1, 1) // P0_1: SGPIO1, HOST_DATA1, Praline: FPGA HOST_DATA1
| boot_bit(sgpio_0, 1) // P0_0: SGPIO0, HOST_DATA0, Praline: FPGA HOST_DATA0
| boot_bit(dfu_boot_0, 0) // P1_1: 10K PU, BOOT0
| boot_bit(dfu_boot_1, 0) // P1_2: 10K PD, BOOT1
| boot_bit(dfu_isp, 0) // P2_7: 10K PU, ISP
| (1 << 11) // P1_4: SSP1_MOSI
| boot_bit(sgpio_12, 0) // P1_18: SGPIO12, HOST_Q_INVERT
| boot_bit(sgpio_11, 0) // P1_17: SGPIO11, HOST_DIRECTION, Praline: FPGA HOST_DIRECTION
| (1 << 10) // P1_3: SSP1_MISO
| (0 << 6) // P3_6: SPIFI_MISO
| boot_bit(sgpio_5, 1) // P6_6: SGPIO5, HOST_DATA5, Praline: FPGA HOST_DATA5
| boot_bit(sgpio_7, 1) // P1_0: SGPIO7, HOST_DATA7, Praline: FPGA HOST_DATA7
| boot_bit(sgpio_3, 1) // P1_16: SGPIO3, HOST_DATA3, Praline: FPGA HOST_DATA3
| boot_bit(sgpio_2, 1) // P1_15: SGPIO2, HOST_DATA2, Praline: FPGA HOST_DATA2
| boot_bit(sgpio_1, 1) // P0_1: SGPIO1, HOST_DATA1, Praline: FPGA HOST_DATA1
| boot_bit(sgpio_0, 1) // P0_0: SGPIO0, HOST_DATA0, Praline: FPGA HOST_DATA0
,
.dir
#ifdef PRALINE
= (0 << 7) // P2_7: Input
| (0 << 8) // P1_1: Input
| (0 << 9) // P1_2: Input
| boot_bit(p1_ctrl0, 1) // P2_10: P1_CTRL0
= boot_bit(p1_ctrl0, 1) // P2_10: P1_CTRL0
| boot_bit(clkin_ctrl, 1) // P1_20: CLKIN_CTRL
#else
= (1 << 14) // P2_10: AMP_BYPASS
| (1 << 15) // P1_20: CS_XCVR
= boot_bit(amp_bypass, 1) // P2_10: AMP_BYPASS
| (1 << 15) // P1_20: CS_XCVR
#endif
| (0 << 11) // P1_4: SSP1_MOSI
| boot_bit(sgpio_12, 1) // P1_18: SGPIO12, HOST_Q_INVERT
| boot_bit(sgpio_11, 0) // P1_17: SGPIO11, HOST_DIRECTION, Praline: FPGA HOST_DIRECTION
| (0 << 10) // P1_3: SSP1_MISO
| (0 << 9) // P1_2: Varies by revision, float until detection
| (0 << 8) // P1_1: Varies by revision, float until detection
| (0 << 7) // P2_7: Varies by revision, float until detection
| (0 << 6) // P3_6: SPIFI_MISO
| boot_bit(sgpio_5, 1) // P6_6: SGPIO5, HOST_DATA5
| boot_bit(sgpio_7, 1) // P1_0: SGPIO7, HOST_DATA7
| boot_bit(sgpio_3, 0) // P1_16: SGPIO3, HOST_DATA3
| boot_bit(sgpio_2, 0) // P1_15: SGPIO2, HOST_DATA2
| boot_bit(sgpio_1, 0) // P0_1: SGPIO1, HOST_DATA1
| boot_bit(sgpio_0, 0) // P0_0: SGPIO0, HOST_DATA0
| boot_bit(dfu_boot_0, 0) // P1_1: 10K PU, BOOT0
| boot_bit(dfu_boot_1, 0) // P1_2: 10K PD, BOOT1
| boot_bit(dfu_isp, 0) // P2_7: 10K PU, ISP
| (0 << 10) // P1_3: SSP1_MISO
| (0 << 11) // P1_4: SSP1_MOSI
| boot_bit(sgpio_12, 1) // P1_18: SGPIO12, HOST_Q_INVERT
| boot_bit(sgpio_11, 0) // P1_17: SGPIO11, HOST_DIRECTION, Praline: FPGA HOST_DIRECTION
| (0 << 6) // P3_6: SPIFI_MISO
| boot_bit(sgpio_5, 1) // P6_6: SGPIO5, HOST_DATA5
| boot_bit(sgpio_7, 1) // P1_0: SGPIO7, HOST_DATA7
| boot_bit(sgpio_3, 0) // P1_16: SGPIO3, HOST_DATA3
| boot_bit(sgpio_2, 0) // P1_15: SGPIO2, HOST_DATA2
| boot_bit(sgpio_1, 0) // P0_1: SGPIO1, HOST_DATA1
| boot_bit(sgpio_0, 0) // P0_0: SGPIO0, HOST_DATA0
},
{
// GPIO1
@@ -113,55 +107,55 @@ const PALConfig pal_default_config = {
| (1 << 14) // P3_4: SPIFI_SIO3
| (1 << 13) // P2_13: PortaPack DIR
#ifdef PRALINE
| (1 << 12) // P2_12: BIAS_EN
| (0 << 11) // P2_11: BIAS_OC
| boot_bit(aa_en, 0) // P1_14: AA_EN
| (0 << 0) // P1_7: Output GND
| boot_bit(ant_bias, 1) // P2_12: !BIAS_EN
| boot_bit(ant_bias_oc, 0) // P2_11: BIAS_OC
| boot_bit(aa_en, 0) // P1_14: AA_EN
| (0 << 0) // P1_7: Output GND
#else
| (1 << 12) // P2_12: !RX_AMP_PWR
| (0 << 11) // P2_11: RX_AMP
| boot_bit(sgpio_10, 1) // P1_14: SGPIO10, HOST_DISABLE
| (0 << 0) // P1_7: !MIX_BYPASS
| boot_bit(rx_amp_pwr, 1) // P2_12: !RX_AMP_PWR
| boot_bit(rx_amp, 0) // P2_11: RX_AMP
| boot_bit(sgpio_10, 1) // P1_14: SGPIO10, HOST_DISABLE
| boot_bit(tx_mix_bypass, 1) // P1_7: !TX MIX BYPASS
#endif
| (0 << 10) // P2_9: 10K PD, BOOT3, PortaPack LCD_WRX
| (1 << 9) // P1_6: SD_CMD
| (1 << 8) // P1_5: SD_POW, PortaPack CPLD.TDO(O)
| (1 << 6) // P1_13: SD_CD
| (1 << 5) // P1_12: SD_DAT3
| (1 << 4) // P1_11: SD_DAT2
| (1 << 3) // P1_10: SD_DAT1
| (1 << 2) // P1_9: SD_DAT0
| (1 << 1) // P1_8: PortaPack CPLD.TMS(I)
| boot_bit(lcd_wrx, 0) // P2_9: 10K PD, BOOT3, PortaPack LCD_WRX
| (1 << 9) // P1_6: SD_CMD
| (1 << 8) // P1_5: SD_POW, PortaPack CPLD.TDO(O)
| (1 << 6) // P1_13: SD_CD
| (1 << 5) // P1_12: SD_DAT3
| (1 << 4) // P1_11: SD_DAT2
| (1 << 3) // P1_10: SD_DAT1
| (1 << 2) // P1_9: SD_DAT0
| (1 << 1) // P1_8: PortaPack CPLD.TMS(I)
,
.dir = (0 << 15) // P3_5: SPIFI_SIO2
| (0 << 14) // P3_4: SPIFI_SIO3
| (1 << 13) // P2_13: PortaPack DIR
#ifdef PRALINE
| (1 << 12) // P2_12: !BIAS_EN
| (0 << 11) // P2_11: BIAS_OC
| boot_bit(aa_en, 1) // P1_14: AA_EN
| (1 << 0) // P1_7: Output
| boot_bit(ant_bias, 1) // P2_12: !BIAS_EN
| boot_bit(ant_bias_oc, 0) // P2_11: BIAS_OC
| boot_bit(aa_en, 1) // P1_14: AA_EN
| (1 << 0) // P1_7: Output
#else
| (1 << 12) // P2_12: !RX_AMP_PWR
| (1 << 11) // P2_11: RX_AMP
| boot_bit(sgpio_10, 0) // P1_14: SGPIO10, HOST_DISABLE
| (1 << 0) // P1_7: !MIX_BYPASS
| boot_bit(rx_amp_pwr, 1) // P2_12: !RX_AMP_PWR
| boot_bit(rx_amp, 1) // P2_11: RX_AMP
| boot_bit(sgpio_10, 0) // P1_14: SGPIO10, HOST_DISABLE
| boot_bit(tx_mix_bypass, 1) // P1_7: !TX MIX BYPASS
#endif
| (1 << 10) // P2_9: 10K PD, BOOT3, PortaPack LCD_WRX
| (0 << 9) // P1_6: SD_CMD
| (0 << 8) // P1_5: SD_POW, PortaPack CPLD.TDO(O)
| (0 << 6) // P1_13: SD_CD
| (0 << 5) // P1_12: SD_DAT3
| (0 << 4) // P1_11: SD_DAT2
| (0 << 3) // P1_10: SD_DAT1
| (0 << 2) // P1_9: SD_DAT0
| (0 << 1) // P1_8: PortaPack CPLD.TMS(I)
| boot_bit(lcd_wrx, 1) // P2_9: 10K PD, BOOT3, PortaPack LCD_WRX
| (0 << 9) // P1_6: SD_CMD
| (0 << 8) // P1_5: SD_POW, PortaPack CPLD.TDO(O)
| (0 << 6) // P1_13: SD_CD
| (0 << 5) // P1_12: SD_DAT3
| (0 << 4) // P1_11: SD_DAT2
| (0 << 3) // P1_10: SD_DAT1
| (0 << 2) // P1_9: SD_DAT0
| (0 << 1) // P1_8: PortaPack CPLD.TMS(I)
},
{
// GPIO2
.data = (0 << 15) // P5_6: TX_AMP, unused on PRALINE
| (1 << 14) // P5_5: MIXER_RESETX, 10K PU
.data = (1 << 14) // P5_5: MIXER_RESETX, 10K PU
#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
@@ -175,51 +169,53 @@ const PALConfig pal_default_config = {
| boot_bit(led_rx, 1) // P4_2: LED2 (RX)
| boot_bit(led_usb, 1) // P4_1: LED1 (USB)
#else
| (1 << 13) // P5_4: MIXER_ENX, 10K PU
| (1 << 12) // P5_3: RX_MIX_BP
| (0 << 11) // P5_2: TX_MIX_BP
| (0 << 10) // P5_1: LP
| (0 << 4) // P4_4: Varies by revision
| (0 << 9) // P5_0: Varies by revision
| (1 << 0) // P4_0: HP
| boot_bit(sgpio_9, 1) // P4_3: SGPIO9, HOST_CAPTURE
| (0 << 6) // P4_6: XCVR_EN, 10K PD
| boot_bit(led_tx, 0) // P6_12: LED3 (TX)
| boot_bit(led_rx, 0) // P4_2: LED2 (RX)
| boot_bit(led_usb, 0) // P4_1: LED1 (USB)
| boot_bit(tx_amp, 0) // 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, 0) // P5_2: TX_MIX_BP
| boot_bit(lpf, 0) // P5_1: LPF
| (0 << 9) // P5_0: Varies by revision
| boot_bit(hpf, 0) // P4_0: HPF
| boot_bit(sgpio_9, 1) // P4_3: SGPIO9, HOST_CAPTURE
| (0 << 6) // P4_6: XCVR_EN, 10K PD
| boot_bit(led_tx, 0) // P6_12: LED3 (TX)
| boot_bit(led_rx, 0) // P4_2: LED2 (RX)
| boot_bit(led_usb, 0) // P4_1: LED1 (USB)
#endif
| (1 << 7) // P5_7: CS_AD
| (0 << 5) // P4_5: RXENABLE
,
.dir = (1 << 15) // P5_6: TX_AMP, unused on PRALINE
.dir =
#ifdef 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
| (1 << 4) // P4_4: unused on PRALINE
| (1 << 9) // P5_0: unused on PRALINE
| (1 << 0) // P4_0: unused on PRALINE
| (0 << 3) // P4_3: VBUSCTRL input
| (0 << 6) // P4_6: Input
(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
| (1 << 4) // P4_4: unused on PRALINE
| (1 << 9) // P5_0: unused on PRALINE
| (1 << 0) // P4_0: unused on PRALINE
| (0 << 3) // P4_3: VBUSCTRL input
| (0 << 6) // P4_6: Input
#else
| (1 << 13) // P5_4: MIXER_ENX, 10K PU
| (1 << 12) // P5_3: RX_MIX_BP
| (1 << 11) // P5_2: TX_MIX_BP
| (1 << 10) // P5_1: LP
| (0 << 4) // P4_4: Varies by revision
| (0 << 9) // P5_0: Varies by revision
| (1 << 0) // P4_0: HP
| 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
| (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
#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)
| (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)
},
{
// GPIO3
@@ -232,16 +228,17 @@ const PALConfig pal_default_config = {
| (1 << 9) // P7_1: PortaPack GPIO3_9(IO)
| (1 << 8) // P7_0: PortaPack GPIO3_8(IO)
#ifdef PRALINE
| (0 << 4) // P6_5: TX_ENABLE
| (1 << 2) // P6_3: MIX_ENABLE_N
| (0 << 6) // P6_10: unused on PRALINE
| boot_bit(p1_ctrl2, 0) // P6_9: P1_CTRL2
| boot_bit(tx_enable, 0) // P6_5: TX_ENABLE
| boot_bit(mix_bypass, 1) // P6_3: MIX_ENABLE_N
| (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
#else
| (1 << 4) // P6_5: HackRF CPLD.TMS(I)
| boot_bit(vregmode, 1) // P6_11: VREGMODE
| (0 << 6) // P6_10: Varies by revision
| (1 << 2) // P6_3: SGPIO4
| (1 << 5) // 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
#endif
| (1 << 3) // P6_4: MIXER_SDATA
| (1 << 1) // P6_2: HackRF CPLD.TDI(I)
@@ -256,16 +253,16 @@ const PALConfig pal_default_config = {
| (1 << 9) // P7_1: PortaPack GPIO3_9(IO)
| (1 << 8) // P7_0: PortaPack GPIO3_8(IO)
#ifdef PRALINE
| (0 << 7) // P6_11: 3V3AUX_OC
| (1 << 4) // P6_5: TX_ENABLE
| (1 << 2) // P6_3: MIX_ENABLE_N
| (0 << 6) // P6_10: unused on PRALINE
| boot_bit(p1_ctrl2, 1) // P6_9: P1_CTRL2
| boot_bit(aux_power_oc, 0) // P6_11: AUX overcurrent
| boot_bit(tx_enable, 1) // P6_5: TX_ENABLE
| 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
#else
| boot_bit(vregmode, 1) // P6_11: VREGMODE
| (0 << 6) // P6_10: Varies by revision
| (0 << 2) // P6_3: SGPIO4
| (1 << 5) // 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
#endif
| (0 << 4) // P6_5: HackRF CPLD.TMS(I)
| (0 << 3) // P6_4: MIXER_SDATA
@@ -277,8 +274,8 @@ const PALConfig pal_default_config = {
.data =
boot_bit(sgpio_8, 0) // P9_6: SGPIO8, SGPIO_CLK, PRALINE: P8_0
#ifdef PRALINE
| (0 << 9) // PA_2: RF_AMP_EN
| (0 << 8) // PA_1: LPF_EN
| 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
@@ -297,8 +294,8 @@ const PALConfig pal_default_config = {
.dir =
boot_bit(sgpio_8, 0) // P9_6: SGPIO8, SGPIO_CLK, PRALINE: P8_0
#ifdef PRALINE
| (1 << 9) // PA_2: RF_AMP_EN
| (1 << 8) // PA_1: LPF_EN
| 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
@@ -318,81 +315,77 @@ const PALConfig pal_default_config = {
// GPIO5
.data =
#ifdef PRALINE
(0 << 18) // P9_5: RFF5072 SCLK
| boot_bit(trigger_out, 0) // P2_6: Trigger out
| (0 << 14) // P4_10: FPGA CDONE
| (1 << 15) // P6_7: 3V3 AUX_ENABLE
| boot_bit(p1_ctrl1, 0) // P6_8: P1_CTRL1
| boot_bit(sgpio_4, 1) // P9_4: FPGA SGPIO4
| (0 << 19) // PA_4: Output GND
| (0 << 20) // PB_0: Output GND
| (0 << 21) // PB_1: Output GND
| (0 << 22) // PB_2: Unused IN
| (0 << 23) // PB_3: Output GND
| (0 << 24) // PB_4: Unused IN
| (0 << 25) // PB_5: Output GND
| (0 << 5) // P2_5: PPS OUT/IN
| (0 << 12) // P4_8: Output GND
| boot_bit(vregmode, 1) // P4_9: TPS62410 VREGMODE
(0 << 18) // 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
| boot_bit(p1_ctrl1, 0) // P6_8: P1_CTRL1
| boot_bit(sgpio_4, 1) // P9_4: FPGA SGPIO4
| (0 << 19) // PA_4: Output GND
| (0 << 20) // PB_0: Output GND
| (0 << 21) // PB_1: Output GND
| (0 << 22) // PB_2: Unused IN
| (0 << 23) // PB_3: Output GND
| (0 << 24) // PB_4: Unused IN
| (0 << 25) // PB_5: Output GND
| boot_bit(pps_in_out, 0) // P2_5: PPS OUT/IN
| (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)
| (0 << 15) // P6_7: Varies by revision
| (0 << 5) // P2_5: Varies by revision
| (1 << 16) // P6_8: MIX_BYPASS
| (1 << 13) // P4_9: SGPIO14, CPLD (unused)
| (0 << 12) // P4_8: Varies by revision
(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
#endif
| (1 << 11) // P3_8: SPIFI_CS
| (1 << 10) // P3_7: SPIFI_MOSI
| (1 << 9) // P3_2: I2S0_RX_SDA
| (1 << 8) // P3_1: I2S0_RX_WS
| (0 << 7) // P2_8: BOOT2
| (1 << 4) // P2_4: PortaPack LCD_RDX
| (0 << 3) // P2_3: PortaPack LCD_TE
| boot_bit(sgpio_6, 1) // P2_2: SGPIO6, HOST_DATA6
| (1 << 1) // P2_1: PortaPack ADDR
| (1 << 0) // P2_0: PortaPack IO_STBX
| (1 << 11) // P3_8: SPIFI_CS
| (1 << 10) // P3_7: SPIFI_MOSI
| (1 << 9) // P3_2: I2S0_RX_SDA
| (1 << 8) // P3_1: I2S0_RX_WS
| boot_bit(dfu_button, 0) // P2_8: 10K PD, BOOT2, DFU button
| (1 << 4) // P2_4: PortaPack LCD_RDX
| (0 << 3) // P2_3: PortaPack LCD_TE
| boot_bit(sgpio_6, 1) // P2_2: SGPIO6, HOST_DATA6
| (1 << 1) // P2_1: PortaPack ADDR
| (1 << 0) // P2_0: PortaPack IO_STBX
,
.dir =
#ifdef PRALINE
(1 << 18) // P9_5: RFF5072 SCLK
| boot_bit(trigger_out, 1) // P2_6: Trigger out
| (0 << 14) // P4_10: FPGA CDONE
| (1 << 15) // P6_7: 3V3 AUX_ENABLE
| boot_bit(p1_ctrl1, 1) // P6_8: P1_CTRL1
| boot_bit(sgpio_4, 0) // P9_4: FPGA SGPIO
| (1 << 19) // PA_4: Output
| (1 << 20) // PB_0: Output GND
| (1 << 21) // PB_1: Output GND
| (0 << 22) // PB_2: Unused
| (1 << 23) // PB_3: Output
| (0 << 24) // PB_4: Unused
| (1 << 25) // PB_5: Output
| (0 << 5) // P2_5: PPS (Bidirectional)
| (1 << 12) // P4_8: Output
| boot_bit(vregmode, 1) // P4_9: TPS62410 VREGMODE
(1 << 18) // 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
| boot_bit(p1_ctrl1, 1) // P6_8: P1_CTRL1
| boot_bit(sgpio_4, 0) // P9_4: FPGA SGPIO
| (1 << 19) // PA_4: Output
| (1 << 20) // PB_0: Output GND
| (1 << 21) // PB_1: Output GND
| (0 << 22) // PB_2: Unused
| (1 << 23) // PB_3: Output
| (0 << 24) // PB_4: Unused
| (1 << 25) // PB_5: Output
| boot_bit(pps_in_out, 0) // P2_5: PPS (Bidirectional)
| (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
| (0 << 15) // P6_7: Varies by revision
| (0 << 5) // P2_5: Varies by revision
| (1 << 16) // P6_8: MIX_BYPASS
| (0 << 13) // P4_9: SGPIO14, CPLD
| (0 << 12) // P4_8: Varies by revision
(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
#endif
| (0 << 11) // P3_8: SPIFI_CS
| (0 << 10) // P3_7: SPIFI_MOSI
| (0 << 9) // P3_2: I2S0_RX_SDA
| (0 << 8) // P3_1: I2S0_RX_WS
| (0 << 7) // P2_8: BOOT2
| (0 << 4) // P2_4: PortaPack LCD_RDX
| (0 << 3) // P2_3: PortaPack LCD_TE
| boot_bit(sgpio_6, 0) // P2_2: SGPIO6, HOST_DATA6
| (0 << 1) // P2_1: PortaPack ADDR
| (0 << 0) // P2_0: PortaPack IO_STBX
| (0 << 11) // P3_8: SPIFI_CS
| (0 << 10) // P3_7: SPIFI_MOSI
| (0 << 9) // P3_2: I2S0_RX_SDA
| (0 << 8) // P3_1: I2S0_RX_WS
| boot_bit(dfu_button, 0) // P2_8: 10K PD, BOOT2, DFU button
| (0 << 4) // P2_4: PortaPack LCD_RDX
| (0 << 3) // P2_3: PortaPack LCD_TE
| boot_bit(sgpio_6, 0) // P2_2: SGPIO6, HOST_DATA6
| (0 << 1) // P2_1: PortaPack ADDR
| (0 << 0) // P2_0: PortaPack IO_STBX
},
{
// GPIO6
@@ -449,16 +442,19 @@ const PALConfig pal_default_config = {
{map_vregmode.scu_port, map_vregmode.scu_pin, scu_config_normal_drive_t{.mode = map_vregmode.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // VREGMODE: TPS62410
#ifdef PRALINE
{map_VAA_en.scu_port, map_VAA_en.scu_pin, scu_config_normal_drive_t{.mode = map_VAA_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_1: !VAA_EN
{8, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_4: VBUS_IN_EN
{8, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_5: VIN_IN_EN
{map_en_1v2.scu_port, map_en_1v2.scu_pin, scu_config_normal_drive_t{.mode = map_en_1v2.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_7: EN_1V2
{6, 7, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_7: 3.3V Aux Enable
{4, 9, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P4_9: TPS62410 mode
{map_p1_ctrl0.scu_port, map_p1_ctrl0.scu_pin, scu_config_normal_drive_t{.mode = map_p1_ctrl0.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_10: P1_CTRL0
{map_p1_ctrl2.scu_port, map_p1_ctrl2.scu_pin, scu_config_normal_drive_t{.mode = map_p1_ctrl2.gpio_mode, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_9: P1_CTRL2 (Output VCC, PU ON,)
{map_p2_ctrl0.scu_port, map_p2_ctrl0.scu_pin, scu_config_normal_drive_t{.mode = map_p2_ctrl0.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // PE_3: P2_CTRL0
{map_p2_ctrl1.scu_port, map_p2_ctrl1.scu_pin, scu_config_normal_drive_t{.mode = map_p2_ctrl1.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // PE_4: P2_CTRL1
{map_VAA_en.scu_port, map_VAA_en.scu_pin, scu_config_normal_drive_t{.mode = map_VAA_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_1: !VAA_EN
{8, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_4: VBUS_IN_EN
{8, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_5: VIN_IN_EN
{map_en_1v2.scu_port, map_en_1v2.scu_pin, scu_config_normal_drive_t{.mode = map_en_1v2.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P8_7: EN_1V2
{map_aux_power_enable.scu_port, map_aux_power_enable.scu_pin, scu_config_normal_drive_t{.mode = map_aux_power_enable.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_7: !3.3V Aux Enable
{map_aux_power_oc.scu_port, map_aux_power_oc.scu_pin, scu_config_normal_drive_t{.mode = map_aux_power_oc.gpio_mode, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // P6_11: !3.3V Aux overcurrent input
{4, 9, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P4_9: TPS62410 mode
{map_p1_ctrl0.scu_port, map_p1_ctrl0.scu_pin, scu_config_normal_drive_t{.mode = map_p1_ctrl0.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_10: P1_CTRL0
{map_p1_ctrl2.scu_port, map_p1_ctrl2.scu_pin, scu_config_normal_drive_t{.mode = map_p1_ctrl2.gpio_mode, .epd = 0, .epun = 0, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_9: P1_CTRL2 (Output VCC, PU ON,)
{map_p2_ctrl0.scu_port, map_p2_ctrl0.scu_pin, scu_config_normal_drive_t{.mode = map_p2_ctrl0.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // PE_3: P2_CTRL0
{map_p2_ctrl1.scu_port, map_p2_ctrl1.scu_pin, scu_config_normal_drive_t{.mode = map_p2_ctrl1.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // PE_4: P2_CTRL1
{map_ant_bias.scu_port, map_ant_bias.scu_pin, scu_config_normal_drive_t{.mode = map_ant_bias.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_12: !ANT BIAS
{map_ant_bias_oc.scu_port, map_ant_bias_oc.scu_pin, scu_config_normal_drive_t{.mode = map_ant_bias_oc.gpio_mode, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // P2_11: !ANT BIAS overcurrent input
#endif
/* HackRF: I2C0 */
@@ -502,28 +498,27 @@ const PALConfig pal_default_config = {
{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
{2, 5, scu_config_normal_drive_t{.mode = 4, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}},
{gpio_control::map_clkin_ctrl.scu_port, gpio_control::map_clkin_ctrl.scu_pin, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_20 CLKIN_ctrl
{map_pps_in_out.scu_port, map_pps_in_out.scu_pin, scu_config_normal_drive_t{.mode = map_pps_in_out.gpio_mode, .epd = 0, .epun = 0, .ehs = 1, .ezi = 1, .zif = 0}}, // P2_5 PPS bidirectional
{map_clkin_ctrl.scu_port, map_clkin_ctrl.scu_pin, scu_config_normal_drive_t{.mode = map_clkin_ctrl.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_20 CLKIN_ctrl
#else
{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)
{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
{map_tx_mix_bp.scu_port, map_tx_mix_bp.scu_pin, scu_config_normal_drive_t{.mode = map_tx_mix_bp.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_2 TX_MIX_BP
#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)
{2, 11, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_11 RX_AMP
{2, 12, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_12 !RX_AMP_PWR
{4, 0, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P4_0 HP
{5, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_1 LP
{5, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P5_2 TX_MIX_BP
{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
#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
@@ -535,33 +530,31 @@ const PALConfig pal_default_config = {
{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
{6, 3, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_3: MIX_ENABLE_N
{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
{6, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 1, .ezi = 0, .zif = 0}}, // P6_5: TX enable
{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
{5, 3, scu_config_normal_drive_t{.mode = 0, .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
{5, 6, scu_config_normal_drive_t{.mode = 0, .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
{1, 7, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // !MIX_BYPASS
{2, 10, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // AMP_BYPASS
{6, 8, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_8 MIX_BYPASS
{6, 9, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // !TX_AMP_PWR
{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_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
// SAFE TERMINATION (Unused pins grounded to prevent noise & save power)
#ifdef PRALINE
{1, 1, scu_config_normal_drive_t{.mode = 0, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // P1_1: Input GND
{2, 7, scu_config_normal_drive_t{.mode = 0, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // P2_7: Input GND
{1, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // P1_2: Input VCC
{4, 6, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 0}}, // P4_6: Input VCC
{6, 10, scu_config_normal_drive_t{.mode = 0, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P6_10: Output GND
{4, 8, scu_config_normal_drive_t{.mode = 4, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P4_8: Output GND
@@ -571,10 +564,6 @@ const PALConfig pal_default_config = {
{11, 2, scu_config_normal_drive_t{.mode = 4, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // PB_2: Unused IN
{11, 4, scu_config_normal_drive_t{.mode = 4, .epd = 1, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // PB_4: Unused IN
#else
{1, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_1
{1, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_2
{2, 5, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_5
{2, 7, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_7
{4, 8, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P4_8
#endif
@@ -592,6 +581,12 @@ const PALConfig pal_default_config = {
{6, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // CPLD_TCK: PortaPack CPLD.TCK(I)
{6, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, // CPLD_TDI: PortaPack CPLD.TDI(I)
{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_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
}};
#ifndef PRALINE
@@ -600,14 +595,8 @@ static const std::array<gpio_setup_t, 6> gpio_setup_og{{
{
// GPIO0
.data = (0 << 9) // P1_2: 10K PD, BOOT1
| (1 << 8) // P1_1: 10K PU, BOOT0
| (1 << 7) // P2_7: 10K PU, ISP
,
.dir = (0 << 9) // P1_2: 10K PD, BOOT1
| (0 << 8) // P1_1: 10K PU, BOOT0
| (0 << 7) // P2_7: 10K PU, ISP
},
.data = 0,
.dir = 0},
{// GPIO1
.data = 0,
.dir = 0},
@@ -630,13 +619,13 @@ static const std::array<gpio_setup_t, 6> gpio_setup_og{{
.dir = 0},
{
// GPIO5
.data = (0 << 15) // P6_7: TX
.data = boot_bit(og_tx, 0) // P6_7: TX
| boot_bit(sgpio_13, 0) // P4_8: SGPIO13, HOST_SYNC_EN
| (1 << 5) // P2_5: RX
| boot_bit(og_rx, 1) // P2_5: RX
,
.dir = (1 << 15) // P6_7: TX
.dir = boot_bit(og_tx, 1) // P6_7: TX
| boot_bit(sgpio_13, 0) // P4_8: SGPIO13, HOST_SYNC_EN
| (1 << 5) // P2_5: RX
| boot_bit(og_rx, 1) // P2_5: RX
},
}};
@@ -645,24 +634,24 @@ static const std::array<gpio_setup_t, 6> gpio_setup_og{{
static const std::array<gpio_setup_t, 6> gpio_setup_r9{{
{
// GPIO0
.data = (0 << 9) // P1_2: 10K PD, BOOT1, CLKOUT_EN
| (1 << 8) // P1_1: 10K PU, BOOT0, MCU_CLK_EN
| (1 << 7) // P2_7: 10K PU, ISP, RX
.data = boot_bit(r9_clkout_en, 0) // P1_2: 10K PD, BOOT1, CLKOUT_EN
| boot_bit(r9_mcu_clk_en, 1) // P1_1: 10K PU, BOOT0, MCU_CLK_EN
| boot_bit(r9_rx, 1) // P2_7: 10K PU, ISP, RX
,
.dir = (0 << 9) // P1_2: 10K PD, BOOT1, CLKOUT_EN
| (0 << 8) // P1_1: 10K PU, BOOT0, MCU_CLK_EN
| (0 << 7) // P2_7: 10K PU, ISP, RX
.dir = boot_bit(r9_clkout_en, 1) // P1_2: 10K PD, BOOT1, CLKOUT_EN
| boot_bit(r9_mcu_clk_en, 1) // P1_1: 10K PU, BOOT0, MCU_CLK_EN
| boot_bit(r9_rx, 1) // P2_7: 10K PU, ISP, RX
},
{// GPIO1
.data = 0,
.dir = 0},
{
// GPIO2
.data = boot_bit(r9_1v8_en, 0) // P5_0: EN1V8, 10K PD
| (1 << 4) // P4_4: !ANT_BIAS
.data = boot_bit(r9_1v8_en, 0) // P5_0: EN1V8, 10K PD
| boot_bit(ant_bias, 1) // P4_4: !ANT_BIAS
,
.dir = boot_bit(r9_1v8_en, 1) // P5_0: EN1V8, 10K PD
| (1 << 4) // P4_4: !ANT_BIAS
.dir = boot_bit(r9_1v8_en, 1) // P5_0: EN1V8, 10K PD
| boot_bit(ant_bias, 1) // P4_4: !ANT_BIAS
},
{
// GPIO3
@@ -675,54 +664,49 @@ static const std::array<gpio_setup_t, 6> gpio_setup_r9{{
.dir = 0},
{
// GPIO5
.data = (0 << 15) // P6_7: CLKIN_EN
| (0 << 12) // P4_8: CLKIN_DETECT
| (0 << 5) // P2_5: HOST_SYNC_EN
.data = boot_bit(r9_clkin_en, 0) // P6_7: CLKIN_EN
| (0 << 12) // P4_8: CLKIN_DETECT
| (0 << 5) // P2_5: HOST_SYNC_EN
,
.dir = (1 << 15) // P6_7: CLKIN_EN
| (0 << 12) // P4_8: CLKIN_DETECT
| (0 << 5) // P2_5: HOST_SYNC_EN
.dir = boot_bit(r9_clkin_en, 1) // P6_7: CLKIN_EN
| (0 << 12) // P4_8: CLKIN_DETECT
| (0 << 5) // P2_5: HOST_SYNC_EN
},
}};
/* Additional SCU configuration for HackRF OG */
static const std::array<scu_setup_t, 9> pins_setup_og{{
static const std::array<scu_setup_t, 7> pins_setup_og{{
/* Power control */
{map_og_VAA_en.scu_port, map_og_VAA_en.scu_pin, scu_config_normal_drive_t{.mode = map_og_VAA_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* !VAA_ENABLE: 10K PU, Q3.G(I), power to VAA */
{map_og_1v8_en.scu_port, map_og_1v8_en.scu_pin, scu_config_normal_drive_t{.mode = map_og_1v8_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* EN1V8/P70: 10K PD, TPS62410.EN2(I), 1V8LED.A(I) */
/* Radio section control */
{2, 5, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* RX/P43: U7.VCTL1(I), U10.VCTL1(I), U2.VCTL1(I) */
{4, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* TXENABLE/P55: MAX2837.TXENABLE(I) */
{6, 7, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* TX/P42: U7.VCTL2(I), U10.VCTL2(I), U2.VCTL2(I) */
{2, 5, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* RX/P43: U7.VCTL1(I), U10.VCTL1(I), U2.VCTL1(I) */
{4, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* TXENABLE/P55: MAX2837.TXENABLE(I) */
{map_og_tx.scu_port, map_og_tx.scu_pin, scu_config_normal_drive_t{.mode = map_og_tx.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* TX/P42: U7.VCTL2(I), U10.VCTL2(I), U2.VCTL2(I) */
{map_og_rx.scu_port, map_og_rx.scu_pin, scu_config_normal_drive_t{.mode = map_og_rx.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P2_5
/* SGPIO for sample transfer interface to HackRF CPLD. */
{map_sgpio_13.scu_port, map_sgpio_13.scu_pin, scu_config_normal_drive_t{.mode = map_sgpio_13.gpio_mode, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* SGPIO13/BANK2F3M2: CPLD.90/HOST_SYNC_EN(I) */
/* Miscellaneous */
{1, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* P1_1/P74: 10K PU, BOOT0 */
{1, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* P1_2/P73: 10K PD, BOOT1 */
{2, 7, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* ISP: 10K PU, Unused */
}};
/* Additional SCU configuration for HackRF r9 */
static const std::array<scu_setup_t, 9> pins_setup_r9{{
/* Power control */
{map_r9_VAA_en.scu_port, map_r9_VAA_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_VAA_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* !VAA_ENABLE: 10K PU, Q3.G(I), power to VAA */
{map_r9_1v8_en.scu_port, map_r9_1v8_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_1v8_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* EN1V8: 10K PD, TPS62410.EN2(I), 1V8LED.A(I) */
{map_r9_VAA_en.scu_port, map_r9_VAA_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_VAA_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // !VAA_ENABLE: 10K PU, Q3.G(I), power to VAA
{map_r9_1v8_en.scu_port, map_r9_1v8_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_1v8_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // EN1V8: 10K PD, TPS62410.EN2(I), 1V8LED.A(I)
/* Radio section control */
{2, 7, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* RX/ISP/P96: U7.VCTL(I), U10.VCTL(I), U2.VCTL(I) */
{4, 4, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* !ANT_BIAS: 10K PU, Q4.G(I) */
{map_r9_rx.scu_port, map_r9_rx.scu_pin, scu_config_normal_drive_t{.mode = map_r9_rx.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // RX/ISP/P96: U7.VCTL(I), U10.VCTL(I), U2.VCTL(I)
{map_ant_bias.scu_port, map_ant_bias.scu_pin, scu_config_normal_drive_t{.mode = map_ant_bias.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // !ANT_BIAS: 10K PU, Q4.G(I)
/* SGPIO for sample transfer interface to HackRF CPLD. */
{2, 5, scu_config_normal_drive_t{.mode = 4, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* BANK2F3M2: CPLD.90/HOST_SYNC_EN(I) */
/* Clock control */
{1, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* MCU_CLK_EN/BOOT0: 10K PU, U28.1A(I) */
{1, 2, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* CLKOUT_EN/BOOT1: 10K PD, U28.2A(I) */
{6, 7, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* CLKIN_EN: U16.SEL(I), U26.1A(I) */
{map_r9_mcu_clk_en.scu_port, map_r9_mcu_clk_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_mcu_clk_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_1: MCU_CLK_EN/BOOT0: 10K PU, U28.1A(I)
{map_r9_clkout_en.scu_port, map_r9_clkout_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_clkout_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, // P1_2: CLKOUT_EN/BOOT1: 10K PD, U28.2A(I)
{map_r9_clkin_en.scu_port, map_r9_clkin_en.scu_pin, scu_config_normal_drive_t{.mode = map_r9_clkin_en.gpio_mode, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* CLKIN_EN: U16.SEL(I), U26.1A(I) */
/* Miscellaneous */
{4, 8, scu_config_normal_drive_t{.mode = 1, .epd = 1, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}}, /* CLKIN_DETECT: U26.2Y(O) */
@@ -733,13 +717,11 @@ static const std::array<scu_setup_t, 9> pins_setup_r9{{
#ifdef PRALINE
static const std::array<scu_setup_t, 43> pins_setup_portapack{{
static const std::array<scu_setup_t, 41> pins_setup_portapack{{
{2, 0, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* U0_TXD: PortaPack P2_0/IO_STBX */
{2, 1, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* U0_RXD: PortaPack P2_1/ADDR */
{2, 3, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 1}}, /* I2C1_SDA: PortaPack P2_3/LCD_TE */
{2, 4, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 1}}, /* I2C1_SCL: PortaPack P2_4/LCD_RDX */
{2, 8, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 1}}, /* P2_8: 10K PD, BOOT2, DFU switch, PortaPack P2_8/<unused> */
{2, 9, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* P2_9: 10K PD, BOOT3, PortaPack P2_9/LCD_WRX */
{2, 13, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* P2_13: PortaPack P2_13/DIR */
{7, 0, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* GPIO3_8: PortaPack GPIO3_8(IO) */
{7, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* GPIO3_9: PortaPack GPIO3_9(IO) */
@@ -790,13 +772,11 @@ static const std::array<scu_setup_t, 43> pins_setup_portapack{{
}};
#else
static const std::array<scu_setup_t, 26> pins_setup_portapack{{
static const std::array<scu_setup_t, 24> pins_setup_portapack{{
{2, 0, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* U0_TXD: PortaPack P2_0/IO_STBX */
{2, 1, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* U0_RXD: PortaPack P2_1/ADDR */
{2, 3, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* I2C1_SDA: PortaPack P2_3/LCD_TE */
{2, 4, scu_config_normal_drive_t{.mode = 4, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* I2C1_SCL: PortaPack P2_4/LCD_RDX */
{2, 8, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 0, .ehs = 0, .ezi = 1, .zif = 1}}, /* P2_8: 10K PD, BOOT2, DFU switch, PortaPack P2_8/<unused> */
{2, 9, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* P2_9: 10K PD, BOOT3, PortaPack P2_9/LCD_WRX */
{2, 13, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* P2_13: PortaPack P2_13/DIR */
{7, 0, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* GPIO3_8: PortaPack GPIO3_8(IO) */
{7, 1, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 1, .zif = 0}}, /* GPIO3_9: PortaPack GPIO3_9(IO) */
@@ -1075,4 +1055,4 @@ extern "C" void _default_exit(void) {
| (1U << 26) // SPI_RST
| (1U << 28) // ADCHS_RST
;
}
}
@@ -52,9 +52,9 @@
*/
#ifdef PRALINE
#define SCU_ARRAY_SIZE 78
#define SCU_ARRAY_SIZE 80
#else
#define SCU_ARRAY_SIZE 56
#define SCU_ARRAY_SIZE 57
#endif
typedef struct {
+1 -1
View File
@@ -41,7 +41,7 @@ void BatteryManagement::getBatteryInfo(uint8_t& valid_mask, uint8_t& percent, ui
if (dev) {
voltage = ((i2cdev::I2cDev_ADS1110*)dev)->readVoltage();
percent = calc_percent_voltage(voltage);
valid_mask = 1;
valid_mask = BATT_VALID_VOLTAGE | BATT_VALID_PERCENT;
return;
}
+8
View File
@@ -119,4 +119,12 @@ constexpr iir_biquad_config_t apt_audio_12k_lpf_2000hz_config{
{0.15505103f, 0.31010205f, 0.15505103f},
{1.00000000f, -0.6202041f, 0.24040821f}};
// scipy.signal.butter(2, 3200 / 24000.0, 'low', analog=False)
// VOR RX audio low-pass. Removes the 9960 Hz FM subcarrier (the annoying
// high-pitch whine) while passing voice and the 1020 Hz ident tone. Cascade
// two of these sections for a 4th-order roll-off (~44 dB at 9960 Hz).
constexpr iir_biquad_config_t audio_48k_lpf_3200hz_config{
{0.03357181f, 0.06714362f, 0.03357181f},
{1.00000000f, -1.41898265f, 0.55326989f}};
#endif /*__DSP_IIR_CONFIG_H__*/
+134 -3
View File
@@ -361,8 +361,8 @@ constexpr PinMap map_p1_ctrl0{2, 10, 0, 14, 0}; // SCU: 2, 10 | GPIO: 0, 14
constexpr PinMap map_p1_ctrl1{6, 8, 5, 16, 4};
constexpr PinMap map_p1_ctrl2{6, 9, 3, 5, 0};
constexpr PinMap map_p2_ctrl0{8, 3, 7, 3, 4};
constexpr PinMap map_p2_ctrl1{8, 4, 7, 4, 4};
constexpr PinMap map_p2_ctrl0{14, 3, 7, 3, 0};
constexpr PinMap map_p2_ctrl1{14, 4, 7, 4, 0};
constexpr PinMap map_trigger_out{2, 6, 5, 6, 4};
constexpr PinMap map_trigger_in{13, 12, 6, 26, 4};
@@ -377,10 +377,25 @@ constexpr PinMap map_sgpio_4{9, 4, 5, 17, 6};
constexpr PinMap map_sgpio_8{8, 0, 4, 0, 4};
constexpr PinMap map_sgpio_9{9, 3, 4, 15, 6};
constexpr PinMap map_sgpio_10{8, 2, 4, 2, 4};
constexpr PinMap map_en_1v2{8, 7, 4, 7, 0};
constexpr PinMap map_vregmode{4, 9, 5, 13, 4};
constexpr PinMap map_en_1v2{8, 7, 4, 7, 0};
constexpr PinMap map_VAA_en{8, 1, 4, 1, 0};
constexpr PinMap map_aux_power_enable{6, 7, 5, 15, 4};
constexpr PinMap map_ant_bias{2, 12, 1, 12, 0};
constexpr PinMap map_ant_bias_oc{2, 11, 1, 11, 0};
constexpr PinMap map_aux_power_oc{6, 11, 3, 7, 0};
constexpr PinMap map_led_mcu{8, 6, 4, 6, 0};
constexpr PinMap map_mix_bypass{6, 3, 3, 2, 0};
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_unused_1{2, 7, 0, 7, 0};
#else
constexpr PinMap map_sgpio_4{6, 3, 3, 2, 2};
constexpr PinMap map_sgpio_8{9, 6, 4, 11, 6};
@@ -392,6 +407,24 @@ constexpr PinMap map_r9_1v8_en{5, 0, 2, 9, 0};
constexpr PinMap map_vregmode{6, 11, 3, 7, 0};
constexpr PinMap map_og_VAA_en{5, 0, 2, 9, 0};
constexpr PinMap map_r9_VAA_en{6, 10, 3, 6, 0};
constexpr PinMap map_rx_mix_bypass{6, 8, 5, 16, 4};
constexpr PinMap map_tx_mix_bp{5, 2, 2, 11, 0};
constexpr PinMap map_rx_mix_bp{5, 3, 2, 12, 0};
constexpr PinMap map_tx_mix_bypass{1, 7, 1, 0, 0};
constexpr PinMap map_og_rx{2, 5, 5, 5, 4};
constexpr PinMap map_og_tx{6, 7, 5, 15, 4};
constexpr PinMap map_r9_rx{2, 7, 0, 7, 0};
constexpr PinMap map_lpf{5, 1, 2, 10, 0};
constexpr PinMap map_hpf{4, 0, 2, 0, 0};
constexpr PinMap map_amp_bypass{2, 10, 0, 14, 0};
constexpr PinMap map_tx_amp{5, 6, 2, 15, 0};
constexpr PinMap map_rx_amp{2, 11, 1, 11, 0};
constexpr PinMap map_tx_amp_pwr{6, 9, 3, 5, 0};
constexpr PinMap map_rx_amp_pwr{2, 12, 1, 12, 0};
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};
#endif
constexpr PinMap map_sgpio_0{0, 0, 0, 0, 3};
@@ -406,6 +439,12 @@ constexpr PinMap map_sgpio_12{1, 18, 0, 13, 0};
constexpr PinMap map_led_usb{4, 1, 2, 1, 0};
constexpr PinMap map_led_rx{4, 2, 2, 2, 0};
constexpr PinMap map_led_tx{6, 12, 2, 8, 0};
constexpr PinMap map_dfu_boot_0{1, 1, 0, 8, 0};
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};
#ifdef PRALINE
// P1 Multiplexer control pins
@@ -461,6 +500,28 @@ constexpr GPIO led_tx{pin_led_tx, map_led_tx.gpio_port, map_led_tx.gpio_pad, map
constexpr Pin pin_led_mcu{map_led_mcu.scu_port, map_led_mcu.scu_pin}; // SCU: P8_6
constexpr GPIO led_mcu{pin_led_mcu, map_led_mcu.gpio_port, map_led_mcu.gpio_pad, map_led_mcu.gpio_mode, Polarity::ActiveLow}; // GPIO[4]6
constexpr Pin pin_mix_bypass{map_mix_bypass.scu_port, map_mix_bypass.scu_pin}; // SCU: P6_3
constexpr GPIO mix_bypass{pin_mix_bypass, map_mix_bypass.gpio_port, map_mix_bypass.gpio_pad, map_mix_bypass.gpio_mode, Polarity::ActiveLow}; // GPIO[3]2
constexpr Pin pin_tx_enable{map_tx_enable.scu_port, map_tx_enable.scu_pin}; // SCU: P6_5
constexpr GPIO tx_enable{pin_tx_enable, map_tx_enable.gpio_port, map_tx_enable.gpio_pad, map_tx_enable.gpio_mode}; // GPIO[3]4
constexpr Pin pin_rf_amp_enable{map_rf_amp_enable.scu_port, map_rf_amp_enable.scu_pin}; // SCU: PA_2
constexpr GPIO rf_amp_enable{pin_rf_amp_enable, map_rf_amp_enable.gpio_port, map_rf_amp_enable.gpio_pad, map_rf_amp_enable.gpio_mode}; // GPIO[4]9
constexpr Pin pin_aux_power_enable{map_aux_power_enable.scu_port, map_aux_power_enable.scu_pin}; // SCU: P6_7
constexpr GPIO aux_power_enable{pin_aux_power_enable, map_aux_power_enable.gpio_port, map_aux_power_enable.gpio_pad, map_aux_power_enable.gpio_mode, Polarity::ActiveLow}; // GPIO[5]15
constexpr Pin pin_aux_power_oc{map_aux_power_oc.scu_port, map_aux_power_oc.scu_pin}; // SCU: P6_11
constexpr GPIO aux_power_oc{pin_aux_power_oc, map_aux_power_oc.gpio_port, map_aux_power_oc.gpio_pad, map_aux_power_oc.gpio_mode}; // GPIO[3]7
constexpr Pin pin_ant_bias_oc{map_ant_bias_oc.scu_port, map_ant_bias_oc.scu_pin}; // SCU: P2_11
constexpr GPIO ant_bias_oc{pin_ant_bias_oc, map_ant_bias_oc.gpio_port, map_ant_bias_oc.gpio_pad, map_ant_bias_oc.gpio_mode, Polarity::ActiveLow}; // GPIO[1]11
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
#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
@@ -486,6 +547,54 @@ constexpr GPIO led_rx{pin_led_rx, map_led_rx.gpio_port, map_led_rx.gpio_pad, map
constexpr Pin pin_led_tx{map_led_tx.scu_port, map_led_tx.scu_pin}; // SCU: P6_12
constexpr GPIO led_tx{pin_led_tx, map_led_tx.gpio_port, map_led_tx.gpio_pad, map_led_tx.gpio_mode}; // GPIO[2]8
constexpr Pin pin_rx_mix_bypass{map_rx_mix_bypass.scu_port, map_rx_mix_bypass.scu_pin}; // SCU: P6_8
constexpr GPIO rx_mix_bypass{pin_rx_mix_bypass, map_rx_mix_bypass.gpio_port, map_rx_mix_bypass.gpio_pad, map_rx_mix_bypass.gpio_mode}; // GPIO[5]16
constexpr Pin pin_tx_mix_bp{map_tx_mix_bp.scu_port, map_tx_mix_bp.scu_pin}; // SCU: P5_2
constexpr GPIO tx_mix_bp{pin_tx_mix_bp, map_tx_mix_bp.gpio_port, map_tx_mix_bp.gpio_pad, map_tx_mix_bp.gpio_mode}; // GPIO[2]11
constexpr Pin pin_rx_mix_bp{map_rx_mix_bp.scu_port, map_rx_mix_bp.scu_pin}; // SCU: P5_3
constexpr GPIO rx_mix_bp{pin_rx_mix_bp, map_rx_mix_bp.gpio_port, map_rx_mix_bp.gpio_pad, map_rx_mix_bp.gpio_mode}; // GPIO[2]12
constexpr Pin pin_tx_mix_bypass{map_tx_mix_bypass.scu_port, map_tx_mix_bypass.scu_pin}; // SCU: P1_7
constexpr GPIO tx_mix_bypass{pin_tx_mix_bypass, map_tx_mix_bypass.gpio_port, map_tx_mix_bypass.gpio_pad, map_tx_mix_bypass.gpio_mode, Polarity::ActiveLow}; // GPIO[1]0
constexpr Pin pin_og_rx{map_og_rx.scu_port, map_og_rx.scu_pin}; // SCU: P2_5
constexpr GPIO og_rx{pin_og_rx, map_og_rx.gpio_port, map_og_rx.gpio_pad, map_og_rx.gpio_mode}; // GPIO[5]5
constexpr Pin pin_og_tx{map_og_tx.scu_port, map_og_tx.scu_pin}; // SCU: P6_7
constexpr GPIO og_tx{pin_og_tx, map_og_tx.gpio_port, map_og_tx.gpio_pad, map_og_tx.gpio_mode}; // GPIO[5]15
constexpr Pin pin_r9_rx{map_r9_rx.scu_port, map_r9_rx.scu_pin}; // SCU: P2_7
constexpr GPIO r9_rx{pin_r9_rx, map_r9_rx.gpio_port, map_r9_rx.gpio_pad, map_r9_rx.gpio_mode}; // GPIO[0]7
constexpr Pin pin_hpf{map_hpf.scu_port, map_hpf.scu_pin}; // SCU: P4_0
constexpr GPIO hpf{pin_hpf, map_hpf.gpio_port, map_hpf.gpio_pad, map_hpf.gpio_mode}; // GPIO[2]0
constexpr Pin pin_amp_bypass{map_amp_bypass.scu_port, map_amp_bypass.scu_pin}; // SCU: P2_10
constexpr GPIO amp_bypass{pin_amp_bypass, map_amp_bypass.gpio_port, map_amp_bypass.gpio_pad, map_amp_bypass.gpio_mode}; // GPIO[0]14
constexpr Pin pin_tx_amp{map_tx_amp.scu_port, map_tx_amp.scu_pin}; // SCU: P5_6
constexpr GPIO tx_amp{pin_tx_amp, map_tx_amp.gpio_port, map_tx_amp.gpio_pad, map_tx_amp.gpio_mode}; // GPIO[2]15
constexpr Pin pin_rx_amp{map_rx_amp.scu_port, map_rx_amp.scu_pin}; // SCU: P2_11
constexpr GPIO rx_amp{pin_rx_amp, map_rx_amp.gpio_port, map_rx_amp.gpio_pad, map_rx_amp.gpio_mode}; // GPIO[1]11
constexpr Pin pin_tx_amp_pwr{map_tx_amp_pwr.scu_port, map_tx_amp_pwr.scu_pin}; // SCU: P6_9
constexpr GPIO tx_amp_pwr{pin_tx_amp_pwr, map_tx_amp_pwr.gpio_port, map_tx_amp_pwr.gpio_pad, map_tx_amp_pwr.gpio_mode, Polarity::ActiveLow}; // GPIO[3]5
constexpr Pin pin_rx_amp_pwr{map_rx_amp_pwr.scu_port, map_rx_amp_pwr.scu_pin}; // SCU: P2_12
constexpr GPIO rx_amp_pwr{pin_rx_amp_pwr, map_rx_amp_pwr.gpio_port, map_rx_amp_pwr.gpio_pad, map_rx_amp_pwr.gpio_mode, Polarity::ActiveLow}; // GPIO[1]12
constexpr Pin pin_r9_mcu_clk_en{map_r9_mcu_clk_en.scu_port, map_r9_mcu_clk_en.scu_pin}; // SCU: P1_1
constexpr GPIO r9_mcu_clk_en{pin_r9_mcu_clk_en, map_r9_mcu_clk_en.gpio_port, map_r9_mcu_clk_en.gpio_pad, map_r9_mcu_clk_en.gpio_mode}; // GPIO[0]8
constexpr Pin pin_r9_clkout_en{map_r9_clkout_en.scu_port, map_r9_clkout_en.scu_pin}; // SCU: P1_2
constexpr GPIO r9_clkout_en{pin_r9_clkout_en, map_r9_clkout_en.gpio_port, map_r9_clkout_en.gpio_pad, map_r9_clkout_en.gpio_mode}; // GPIO[0]9
constexpr Pin pin_r9_clkin_en{map_r9_clkin_en.scu_port, map_r9_clkin_en.scu_pin}; // SCU: P6_7
constexpr GPIO r9_clkin_en{pin_r9_clkin_en, map_r9_clkin_en.gpio_port, map_r9_clkin_en.gpio_pad, map_r9_clkin_en.gpio_mode}; // GPIO[5]15
static const motocon_pwm_resources_t motocon_pwm_resources = {
.base = {.clk = &LPC_CGU->BASE_APB1_CLK, .stat = &LPC_CCU1->BASE_STAT, .stat_mask = (1 << 1)},
.branch = {.cfg = &LPC_CCU1->CLK_APB1_MOTOCON_PWM_CFG, .stat = &LPC_CCU1->CLK_APB1_MOTOCON_PWM_STAT},
@@ -540,6 +649,28 @@ constexpr GPIO sgpio_12{pin_sgpio_12, map_sgpio_12.gpio_port, map_sgpio_12.gpio_
constexpr Pin pin_vregmode{map_vregmode.scu_port, map_vregmode.scu_pin}; // SCU: P6_11, PRALINE: P4_9
constexpr GPIO vregmode{pin_vregmode, map_vregmode.gpio_port, map_vregmode.gpio_pad, map_vregmode.gpio_mode}; // GPIO[3]7, PRALINE: GPIO[5]13
constexpr Pin pin_lpf{map_lpf.scu_port, map_lpf.scu_pin}; // SCU: P5_1, PRALINE: PA_1
constexpr GPIO lpf{pin_lpf, map_lpf.gpio_port, map_lpf.gpio_pad, map_lpf.gpio_mode}; // GPIO[2]10, PRALINE: GPIO[4]8
constexpr Pin pin_ant_bias{map_ant_bias.scu_port, map_ant_bias.scu_pin}; // SCU: R9: P4_4 , PRALINE: P2_12
constexpr GPIO ant_bias{pin_ant_bias, map_ant_bias.gpio_port, map_ant_bias.gpio_pad, map_ant_bias.gpio_mode, Polarity::ActiveLow}; // R9: GPIO[2]4, PRALINE: GPIO[1]12
constexpr Pin pin_dfu_boot_0{map_dfu_boot_0.scu_port, map_dfu_boot_0.scu_pin}; // SCU: P1_1
constexpr GPIO dfu_boot_0{pin_dfu_boot_0, map_dfu_boot_0.gpio_port, map_dfu_boot_0.gpio_pad, map_dfu_boot_0.gpio_mode}; // GPIO[0]8
constexpr Pin pin_dfu_boot_1{map_dfu_boot_1.scu_port, map_dfu_boot_1.scu_pin}; // SCU: P1_2
constexpr GPIO dfu_boot_1{pin_dfu_boot_1, map_dfu_boot_1.gpio_port, map_dfu_boot_1.gpio_pad, map_dfu_boot_1.gpio_mode}; // GPIO[0]9
constexpr Pin pin_dfu_button{map_dfu_button.scu_port, map_dfu_button.scu_pin}; // SCU: P2_8
constexpr GPIO dfu_button{pin_dfu_button, map_dfu_button.gpio_port, map_dfu_button.gpio_pad, map_dfu_button.gpio_mode}; // GPIO[5]7
constexpr Pin pin_lcd_wrx{map_lcd_wrx.scu_port, map_lcd_wrx.scu_pin}; // SCU: P2_9
constexpr GPIO lcd_wrx{pin_lcd_wrx, map_lcd_wrx.gpio_port, map_lcd_wrx.gpio_pad, map_lcd_wrx.gpio_mode}; // GPIO[1]10
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
} // namespace gpio_control
namespace power_control {
-42
View File
@@ -33,28 +33,6 @@ namespace one {
/* GPIO */
constexpr GPIO gpio_rx_mix_bp = gpio[GPIO2_12];
constexpr GPIO gpio_tx_mix_bp = gpio[GPIO2_11];
#ifdef PRALINE
constexpr GPIO gpio_mix_bypass = gpio[GPIO3_2]; // P6_3: PRALINE RF path mixer bypass inverted
// constexpr GPIO gpio_mix_en_n_r1_0 = gpio[GPIO5_6]; // P2_6: R1.0 board mixer bypass
#else
constexpr GPIO gpio_mix_bypass = gpio[GPIO5_16];
#endif
constexpr GPIO gpio_not_mix_bypass = gpio[GPIO1_0];
constexpr GPIO gpio_og_rx = gpio[GPIO5_5];
constexpr GPIO gpio_og_tx = gpio[GPIO5_15];
constexpr GPIO gpio_r9_rx = gpio[GPIO0_7];
constexpr GPIO gpio_lp = gpio[GPIO2_10];
constexpr GPIO gpio_hp = gpio[GPIO2_0];
constexpr GPIO gpio_rx_amp = gpio[GPIO1_11];
constexpr GPIO gpio_tx_amp = gpio[GPIO2_15];
constexpr GPIO gpio_amp_bypass = gpio[GPIO0_14];
constexpr GPIO gpio_not_tx_amp_pwr = gpio[GPIO3_5];
#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)
@@ -62,7 +40,6 @@ constexpr GPIO gpio_rffc5072_resetx = gpio[GPIO2_14]; // P5_5: LPC43xx controls
#else
constexpr GPIO gpio_rffc5072_select = gpio[GPIO2_13];
constexpr GPIO gpio_rffc5072_resetx = gpio[GPIO2_14];
constexpr GPIO gpio_not_rx_amp_pwr = gpio[GPIO1_12];
#endif
#ifdef PRALINE
@@ -105,20 +82,6 @@ constexpr GPIO gpio_max5864_select = gpio[GPIO2_7];
constexpr GPIO gpio_q_invert = gpio[GPIO0_13];
#ifdef PRALINE
// PRALINE power control
constexpr GPIO gpio_vaa_disable = gpio[GPIO4_1]; // VAA disable (P8_1)
// PRALINE RF path control
constexpr GPIO gpio_tx_enable = gpio[GPIO3_4]; // TX enable (P6_5)
constexpr GPIO gpio_lpf_enable = gpio[GPIO4_8]; // LPF enable (PA_1)
constexpr GPIO gpio_rf_amp_enable = gpio[GPIO4_9]; // RF amp enable (PA_2)
constexpr GPIO gpio_ant_bias_disable = gpio[GPIO1_12]; // Antenna bias disable (P2_12)
// constexpr GPIO gpio_pps_out = gpio[GPIO5_5]; // PPS output (P2_5)
#endif
#ifdef PRALINE
/* PRALINE has no HackRF CPLD. These pins are used for RFFC5072 and TX_EN instead.
* Dummy assignments here allow cpld_update.cpp to compile; the functions
@@ -132,11 +95,6 @@ constexpr GPIO gpio_cpld_tms = gpio[GPIO3_4];
constexpr GPIO gpio_cpld_tck = gpio[GPIO3_0];
constexpr GPIO gpio_cpld_tdi = gpio[GPIO3_1];
constexpr GPIO gpio_r9_clkin_en = gpio[GPIO5_15];
constexpr GPIO gpio_r9_clkout_en = gpio[GPIO0_9];
constexpr GPIO gpio_r9_mcu_clk_en = gpio[GPIO0_8];
constexpr GPIO gpio_r9_not_ant_pwr = gpio[GPIO2_4];
} /* namespace one */
} /* namespace hackrf */
+20 -9
View File
@@ -192,7 +192,10 @@ void I2cDev_MAX17055::update() {
bool battChanged = false;
getBatteryInfo(validity, batteryPercentage, voltage, current, battChanged);
if (validity == 0) {
// no valid data, don't send message
return;
}
// send local message
BatteryStateMessage msg{validity, batteryPercentage, current >= 25, voltage, battChanged};
EventDispatcher::send_message(msg);
@@ -225,6 +228,7 @@ bool I2cDev_MAX17055::init(uint8_t addr_) {
bool return_status = true;
if (needsInitialization()) {
// First-time or POR initialization
was_por = true;
return_status = full_reset_and_init();
}
chThdSleepMilliseconds(300); // wait for adc to fully wake up!
@@ -252,10 +256,9 @@ bool I2cDev_MAX17055::full_reset_and_init() {
if (!load_custom_parameters()) {
return false;
}
/*if (!clear_por()) {
if (!clear_por()) {
return false;
}*/
// don't clear it, we'll need to know the state
}
return true;
}
@@ -315,8 +318,8 @@ bool I2cDev_MAX17055::needsInitialization() {
void I2cDev_MAX17055::partialInit() {
// Only update necessary volatile settings
// setHibCFG(0x0000); // If you always want hibernation disabled. this is a lower resolution mode, when the ic is on, and measuring, but on lower freq. depends un tha current (mA).
sleep_config(true); // shut down the comm
setHibCFG(0x0000); // If you always want hibernation disabled. this is a lower resolution mode, when the ic is on, and measuring, but on lower freq. depends un tha current (mA).
sleep_config(false); // shut down the comm
// Add any other volatile settings that need updating
}
@@ -390,17 +393,25 @@ void I2cDev_MAX17055::getBatteryInfo(uint8_t& valid_mask, uint8_t& batteryPercen
valid_mask = 0;
return;
}
if (status == 0xFFFF) {
valid_mask = 0;
return;
}
bool requires_reset = false;
if (((status & 0xFF) >> 1) & 0x01) { // POR FLAG
requires_reset = true;
was_por = true;
}
// Execute the reset if either flag was tripped
if (requires_reset) {
reInit(); // todo maybe don't do it automatically, just signal. but for now it is safer to do it.
// battMayChanged = true && portapack::persistent_memory::battery_replaceable(); // only signal a change if the battery is replaceable. othervise do it silently
// We don't want to signal this for now. the Voltage drop during power up messes this up a bit.
reInit(); // reinit the ic. that takes time, so we'll return
valid_mask = 0;
return;
}
#ifdef MAX17055_REPORT_POR_FLAG
battMayChanged = was_por && portapack::persistent_memory::battery_replaceable(); // only signal a change if the battery is replaceable. othervise do it silently
#endif
batteryPercentage = stateOfCharge();
current = instantCurrent();
+10 -2
View File
@@ -29,6 +29,9 @@
#include "i2cdevmanager.hpp"
// if this define is set, the UI will show the battery has changed (POR flag is set) until the settings are saved (not ic reinited, but settings applyed by user!)
#define MAX17055_REPORT_POR_FLAG 1
#define MAX17055_POR 0
#define MAX17055_IMin 1
#define MAX17055_IMax 2
@@ -292,8 +295,11 @@ class I2cDev_MAX17055 : public I2cDev {
uint16_t stateOfCharge(void);
bool reInit(); // call when battery parameters changed from ui. don't call if not needed, or the battery is not changed!!!
bool getIsBattChanged() { return statusControl(MAX17055_POR); } // true if the ic thinks we have a new battery
void resetChangedFlag() { clear_por(); } // reset the flag
void sleep_config(bool enable_sleep); // if true, the ic can sleep after 3 minutes of inactivity (over i2c). can be waken up on any i2c communication (first packet may be dropped)
void resetChangedFlag() {
clear_por();
was_por = false;
} // reset the flag
void sleep_config(bool enable_sleep); // if true, the ic can sleep after 3 minutes of inactivity (over i2c). can be waken up on any i2c communication (first packet may be dropped)
private:
const RegisterEntry* findEntry(const char* name) const;
@@ -326,6 +332,8 @@ class I2cDev_MAX17055 : public I2cDev {
bool setModelCfg(const uint8_t _Model_ID);
bool setHibCFG(const uint16_t _Config);
void config(void);
bool was_por = false;
};
} /* namespace i2cdev */
+1 -1
View File
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+90
View File
@@ -162,6 +162,12 @@ class Message {
ToneDetectConfig = 104,
FlexTosend = 105,
EPIRBRXConfig = 106,
VorRxConfigure = 107,
VorRxStatusData = 108,
VorTxConfigure = 109,
HunterConfig = 110,
HunterTrigger = 111,
HunterStop = 112,
MAX
};
@@ -1899,6 +1905,68 @@ class ToneDetectConfigureMessage : public Message {
uint32_t ctcss_freq_x10{0}; // CTCSS frequency × 10 (e.g. 1000 = 100.0 Hz); 0 = None
};
class VorRxConfigureMessage : public Message {
public:
constexpr VorRxConfigureMessage(bool enabled = true)
: Message{ID::VorRxConfigure},
enabled{enabled} {
}
bool enabled{true};
};
class VorRxStatusDataMessage : public Message {
public:
constexpr VorRxStatusDataMessage(
uint16_t phase_deg = 0,
uint16_t radial_deg = 0,
uint16_t reference_level = 0,
uint16_t variable_level = 0,
uint8_t quality = 0,
bool valid = false,
bool to_from = false)
: Message{ID::VorRxStatusData},
phase_deg{phase_deg},
radial_deg{radial_deg},
reference_level{reference_level},
variable_level{variable_level},
quality{quality},
valid{valid},
to_from{to_from} {
}
uint16_t phase_deg{0};
uint16_t radial_deg{0};
uint16_t reference_level{0};
uint16_t variable_level{0};
uint8_t quality{0};
bool valid{false};
bool to_from{false};
};
class VorTxConfigureMessage : public Message {
public:
VorTxConfigureMessage(
uint16_t radial_deg = 0,
bool ident_enabled = true,
const char* ident = "",
bool enabled = true)
: Message{ID::VorTxConfigure},
radial_deg{radial_deg},
ident_enabled{ident_enabled},
enabled{enabled} {
size_t i = 0;
for (; ident && ident[i] && i < sizeof(ident_text) - 1; ++i)
ident_text[i] = ident[i];
ident_text[i] = '\0';
}
uint16_t radial_deg{0};
bool ident_enabled{true};
bool enabled{true};
char ident_text[8]{}; // CW identifier, null-terminated (max 7 chars)
};
class FlexTosendMessage : public Message {
public:
constexpr FlexTosendMessage(
@@ -1919,4 +1987,26 @@ class FlexTosendMessage : public Message {
uint8_t msg[240] = {0};
};
class HunterConfigMessage : public Message {
public:
uint32_t energy_threshold{5000};
uint32_t hangtime_ms{500};
bool start{false};
constexpr HunterConfigMessage()
: Message{ID::HunterConfig} {}
};
class HunterTriggerMessage : public Message {
public:
uint32_t energy{0};
constexpr HunterTriggerMessage()
: Message{ID::HunterTrigger} {}
};
class HunterStopMessage : public Message {
public:
constexpr HunterStopMessage()
: Message{ID::HunterStop} {}
};
#endif /*__MESSAGE_H__*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
* Copyright (C) 2024 u-foka
* Copyright (C) 2024 Mark Thompson
*
+4
View File
@@ -129,6 +129,8 @@ constexpr image_tag_t image_tag_noaaapt_rx{'P', 'N', 'O', 'A'};
constexpr image_tag_t image_tag_sstv_rx{'P', 'S', 'R', 'X'};
constexpr image_tag_t image_tag_morse{'P', 'M', 'R', 'S'};
constexpr image_tag_t image_tag_morsetx{'P', 'M', 'R', 'T'};
constexpr image_tag_t image_tag_vor_tx{'P', 'V', 'T', 'X'};
constexpr image_tag_t image_tag_vor_rx{'P', 'V', 'R', 'X'};
constexpr image_tag_t image_tag_rttyrx{'P', 'R', 'T', 'R'};
constexpr image_tag_t image_tag_rttytx{'P', 'R', 'T', 'T'};
constexpr image_tag_t image_tag_tonedetect{'P', 'T', 'N', 'E'};
@@ -137,6 +139,8 @@ constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'};
constexpr image_tag_t image_tag_hackrf{'H', 'R', 'F', '1'};
constexpr image_tag_t image_tag_signal_hunter{'H', 'U', 'N', 'T'};
struct chunk_t {
const image_tag_t tag;
const uint32_t length;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
#
# copyleft 2024 zxkmm AKA zix aka sommermorgentraum
# copyleft 2024 zxkmm
#
# This file is part of PortaPack.
#
+1 -1
View File
@@ -4,7 +4,7 @@
# make_bitmap.py - Copyright (C) 2016 Furrtek
# Convert bitmap.hpp to icons inspired by
# bitmap_arr_reverse_decode.py - Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
# bitmap_arr_reverse_decode.py - copyleft 2024 zxkmm AKA zix aka sommermorgentraum
# bitmap_arr_reverse_decode.py - copyleft 2024 zxkmm
# Copysomething (c) 2024 LupusE with the license, needed by the PortaPack project
#
# This program is free software; you can redistribute it and/or modify
@@ -1,4 +1,4 @@
# copyleft 2024 zxkmm AKA zix aka sommermorgentraum
# copyleft 2024 zxkmm
import os
import sys
@@ -1,4 +1,4 @@
# copyleft 2024 zxkmm AKA zix aka sommermorgentraum
# copyleft 2024 zxkmm
import sys
import re
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
#
# copyleft 2025 zxkmm AKA zix aka sommermorgentraum
# copyleft 2025 zxkmm
#
# This file is part of PortaPack.
#
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
#
# copyleft 2024 zxkmm AKA zix aka sommermorgentraum
# copyleft 2024 zxkmm
#
# This file is part of PortaPack.
#
+1 -1
View File
@@ -24,4 +24,4 @@
# external app address ranges below must match those in linker file "external.ld"
maximum_application_size = 32*1024
external_apps_address_start = 0xADB00000
external_apps_address_end = 0xAE090000
external_apps_address_end = 0xAE0B0000
@@ -1,4 +1,4 @@
# copyleft 2024 zxkmm AKA zix aka sommermorgentraum
# copyleft 2024 zxkmm
#
# This file is part of PortaPack.
#
+1 -1
View File
@@ -3,7 +3,7 @@
#
# Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
# Copyright (C) 2024 Mark Thompson
# copyleft 2025 zxkmm AKA zix aka sommermorgentraum
# copyleft 2025 zxkmm
#
# This file is part of PortaPack.
#
+1 -1
View File
@@ -1,5 +1,5 @@
#
# copyleft 2025 zxkmm AKA zix aka sommermorgentraum
# copyleft 2025 zxkmm
#
# This file is part of PortaPack.
#
+1 -1
Submodule hackrf updated: ba2045a866...d371476c03
+374 -23
View File
@@ -185,6 +185,7 @@ Usage:
Download/setup ARM toolchain only
./mbt doctor Validate host setup and report gaps
./mbt shell [options] Open a shell with toolchain exported for this session
./mbt benchmark [options] Benchmark clean-build time across generators and ccache modes
Build options:
-d, --device <name> Device: hackrf-one | hackrf-pro | portarf
@@ -205,12 +206,24 @@ Build options:
-y, --non-interactive Fail instead of prompting when required input is missing
-h, --help Show this help
Benchmark options (in addition to -d/--device, -j/--jobs, --target, -B, --cmake-arg):
--bench-generators <list> Comma list of generators to test: ninja,make (default: all installed)
--bench-ccache <list> Comma list of ccache modes: off,cold,warm (default: off,cold,warm)
off = ccache disabled (baseline)
cold = ccache on, empty cache (first-build/populate cost)
warm = ccache on, pre-populated cache (the ccache speedup)
Each cell is a full clean build in a dedicated dir (default: ./build-bench),
using an isolated CCACHE_DIR so your real ccache is never touched.
Examples:
./mbt
./mbt build --device hackrf-one
./mbt build --device hackrf-pro --no-submodule-sync
./mbt build --device portarf --no-toolchain --no-deps
./mbt build --device hackrf-one --cmake-arg=-DVERSION_STRING=my-build
./mbt benchmark --device hackrf-one --no-deps --no-submodule-sync
./mbt benchmark --device hackrf-one --bench-generators ninja --bench-ccache off,cold,warm
./mbt benchmark --device hackrf-one --bench-generators ninja,make --bench-ccache off
EOF
}
@@ -308,9 +321,28 @@ mbt_detect_cached_device() {
return 0
}
# libopencm3 builds in-source (inside the submodule tree), so deleting the CMake
# build dir alone does NOT rebuild it. Clean it explicitly for a true clean build.
# Set quiet=1 to suppress make output (used by the benchmark).
mbt_clean_libopencm3() {
local quiet="${1:-0}"
local libopencm3_dir="$SCRIPT_PATH/hackrf/firmware/libopencm3"
if [[ ! -d "$libopencm3_dir" ]]; then
mbt_error "libopencm3 directory not found: $libopencm3_dir"
return 1
fi
if [[ "$quiet" -eq 1 ]]; then
make -C "$libopencm3_dir" clean >/dev/null 2>&1
else
mbt_log "cleaning libopencm3: $libopencm3_dir"
make -C "$libopencm3_dir" clean
fi
}
mbt_clean() {
local build_dir="$1"
local libopencm3_dir="$SCRIPT_PATH/hackrf/firmware/libopencm3"
mbt_log "cleaning build directory: $build_dir"
if ! rm -rf "$build_dir"; then
@@ -318,19 +350,63 @@ mbt_clean() {
return 1
fi
if [[ -d "$libopencm3_dir" ]]; then
mbt_log "cleaning libopencm3: $libopencm3_dir"
make -C "$libopencm3_dir" clean
else
mbt_error "libopencm3 directory not found: $libopencm3_dir"
return 1
fi
mbt_clean_libopencm3 0
}
mbt_report_build_error() {
printf '%s\n' 'Build error happened. Try to fix your code, or run ./mbt --clean and try again' >&2
}
# Print the device-specific CMake args, one per line (empty for hackrf-one).
mbt_device_args() {
case "$1" in
hackrf-one)
: # no extra args
;;
hackrf-pro)
printf '%s\n' "-DBOARD=PRALINE"
;;
portarf)
printf '%s\n' "-DFLASH_MB_SIZE=2" "-DFLASH_MB_LIMIT_SIZE=2"
;;
*)
return 1
;;
esac
}
# Normalize a user-supplied generator name to a canonical CMake generator name.
mbt_resolve_generator() {
local raw
raw="$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]')"
case "$raw" in
ninja)
printf "Ninja"
;;
make|makefiles|unixmakefiles|"unix makefiles")
printf "Unix Makefiles"
;;
*)
printf "%s" "$1"
;;
esac
}
# Print the command required by a canonical CMake generator (empty if unknown).
mbt_generator_command() {
case "$1" in
Ninja)
printf "ninja"
;;
"Unix Makefiles")
printf "make"
;;
*)
printf ""
;;
esac
}
mbt_build() {
local build_dir="$1"
local generator="$2"
@@ -372,20 +448,8 @@ mbt_build() {
mkdir -p "$build_dir"
device_args=()
case "$device" in
hackrf-one)
device_args+=()
;;
hackrf-pro)
device_args+=("-DBOARD=PRALINE")
;;
portarf)
device_args+=("-DFLASH_MB_SIZE=2" "-DFLASH_MB_LIMIT_SIZE=2")
;;
*)
mbt_die "unsupported device preset: $device"
;;
esac
mbt_device_args "$device" >/dev/null || mbt_die "unsupported device preset: $device"
mapfile -t device_args < <(mbt_device_args "$device")
if [[ "$generator" == "Ninja" ]] && ! mbt_command_exists ninja; then
if mbt_command_exists make; then
@@ -430,6 +494,258 @@ mbt_build() {
# have to hard code the name here cuz it's too expensive to read cmake for just a file name.
}
# Compute elapsed wall-clock seconds (with 2 decimals) between two `date +%s.%N` stamps.
mbt_elapsed() {
awk -v s="$1" -v e="$2" 'BEGIN { printf "%.2f", e - s }'
}
# Format seconds as a human-friendly "MmSS.ss" (or "SS.ss" under a minute).
mbt_format_seconds() {
awk -v t="$1" 'BEGIN {
if (t < 0) t = 0
m = int(t / 60)
s = t - m * 60
if (m > 0) printf "%dm%05.2fs", m, s
else printf "%.2fs", s
}'
}
# Run one full clean build and print elapsed seconds on stdout.
# All progress/logging goes to stderr; build output is captured to a per-cell log.
# Args: cell_dir generator use_ccache device jobs target ccache_dir log_file cmake_extra_array_name
mbt_bench_cell() {
local cell_dir="$1" generator="$2" use_ccache="$3" device="$4" jobs="$5"
local target="$6" ccache_dir="$7" log_file="$8"
shift 8
local -n cmake_extra_ref="$1"
local -a cmake_extra_args=("${cmake_extra_ref[@]}")
local -a device_args cmake_args build_args
local start end status=0
rm -rf "$cell_dir"
mkdir -p "$cell_dir"
# libopencm3 builds in-source, so it survives `rm -rf "$cell_dir"`. Without
# this, only the first cell would pay its compile cost and every later cell
# would look artificially fast, biasing the whole comparison.
mbt_clean_libopencm3 1 || true
mapfile -t device_args < <(mbt_device_args "$device")
cmake_args=(
-S "$SCRIPT_PATH"
-B "$cell_dir"
-G "$generator"
"-DUSE_CCACHE=$use_ccache"
)
cmake_args+=("${device_args[@]}")
cmake_args+=("${cmake_extra_args[@]}")
build_args=(--build "$cell_dir" --parallel "$jobs")
if [[ -n "$target" ]]; then
build_args+=(--target "$target")
fi
start="$(date +%s.%N)"
(
if [[ -n "$ccache_dir" ]]; then
export CCACHE_DIR="$ccache_dir"
fi
cmake "${cmake_args[@]}" && cmake "${build_args[@]}"
) >"$log_file" 2>&1 || status=$?
end="$(date +%s.%N)"
if [[ "$status" -ne 0 ]]; then
printf "FAILED"
return "$status"
fi
mbt_elapsed "$start" "$end"
}
# Populate a ccache directory without timing (used when a warm run is requested
# but no cold run precedes it to fill the cache).
mbt_bench_populate() {
local cell_dir="$1" generator="$2" device="$3" jobs="$4" target="$5"
local ccache_dir="$6" log_file="$7"
shift 7
mbt_bench_cell "$cell_dir" "$generator" "ON" "$device" "$jobs" "$target" \
"$ccache_dir" "$log_file" "$1" >/dev/null
}
mbt_benchmark() {
local base_dir="$1" device="$2" jobs="$3" target="$4"
local generators_csv="$5" ccache_csv="$6"
shift 6
local -n bench_cmake_extra_ref="$1"
local -a bench_extra_args=("${bench_cmake_extra_ref[@]}")
local -a want_generators=() generators=() modes=()
local raw g cmd mode
# Resolve requested generators (auto = every generator whose tool is present).
if [[ -z "$generators_csv" || "$generators_csv" == "auto" ]]; then
want_generators=("Ninja" "Unix Makefiles")
else
IFS=',' read -ra raw_gens <<< "$generators_csv"
for raw in "${raw_gens[@]}"; do
[[ -z "$raw" ]] && continue
want_generators+=("$(mbt_resolve_generator "$raw")")
done
fi
for g in "${want_generators[@]}"; do
cmd="$(mbt_generator_command "$g")"
if [[ -z "$cmd" ]]; then
mbt_error "skipping unknown generator: $g"
continue
fi
if ! mbt_command_exists "$cmd"; then
mbt_error "skipping generator '$g' ($cmd not installed)."
continue
fi
generators+=("$g")
done
if [[ "${#generators[@]}" -eq 0 ]]; then
mbt_die "no usable generators for benchmark (need ninja and/or make)."
fi
# Resolve ccache modes: off | cold | warm.
if [[ -z "$ccache_csv" ]]; then
ccache_csv="off,cold,warm"
fi
local -A mode_seen=()
IFS=',' read -ra raw_modes <<< "$ccache_csv"
for mode in "${raw_modes[@]}"; do
mode="$(printf "%s" "$mode" | tr '[:upper:]' '[:lower:]')"
[[ -z "$mode" ]] && continue
case "$mode" in
off | cold | warm) ;;
on) mode="cold" ;; # treat plain "on" as a cold measurement
*) mbt_die "unknown ccache mode: $mode (use off, cold, warm)" ;;
esac
if [[ -z "${mode_seen[$mode]:-}" ]]; then
modes+=("$mode")
mode_seen[$mode]=1
fi
done
[[ "${#modes[@]}" -eq 0 ]] && mbt_die "no ccache modes selected."
local needs_ccache=0
for mode in "${modes[@]}"; do
[[ "$mode" != "off" ]] && needs_ccache=1
done
if [[ "$needs_ccache" -eq 1 ]] && ! mbt_command_exists ccache; then
mbt_die "ccache modes requested but ccache is not installed. Install it or use --bench-ccache off."
fi
mkdir -p "$base_dir"
local log_dir="$base_dir/logs"
mkdir -p "$log_dir"
mbt_log "benchmark: device=$device jobs=$jobs generators=[${generators[*]}] ccache=[${modes[*]}]"
[[ -n "$target" ]] && mbt_log "benchmark: target=$target"
mbt_log "benchmark: results and per-cell logs under $base_dir"
mbt_log "benchmark: each cell is a full clean build; this can take a while."
# Parallel result arrays.
local -a r_gen=() r_mode=() r_secs=() r_ok=()
local cell_dir ccache_dir log_file secs cell_status label
for g in "${generators[@]}"; do
# Per-generator, isolated ccache dir so "cold" is truly cold and the
# user's real cache is never touched. Cache keys are generator-agnostic,
# so each generator needs its own fresh dir.
ccache_dir=""
if [[ "$needs_ccache" -eq 1 ]]; then
ccache_dir="$base_dir/ccache-$(printf "%s" "$g" | tr ' ' '-')"
rm -rf "$ccache_dir"
mkdir -p "$ccache_dir"
fi
local cold_done=0
for mode in "${modes[@]}"; do
label="$g / ccache=$mode"
log_file="$log_dir/$(printf "%s" "$g" | tr ' ' '-')-$mode.log"
case "$mode" in
off)
mbt_log "running: $label"
secs="$(mbt_bench_cell "$base_dir/cell" "$g" "OFF" "$device" \
"$jobs" "$target" "" "$log_file" bench_extra_args)" && cell_status=0 || cell_status=$?
;;
cold)
rm -rf "$ccache_dir" && mkdir -p "$ccache_dir"
mbt_log "running: $label (empty cache)"
secs="$(mbt_bench_cell "$base_dir/cell" "$g" "ON" "$device" \
"$jobs" "$target" "$ccache_dir" "$log_file" bench_extra_args)" && cell_status=0 || cell_status=$?
[[ "$cell_status" -eq 0 ]] && cold_done=1
;;
warm)
if [[ "$cold_done" -eq 0 ]]; then
mbt_log "priming cache for: $label (untimed)"
rm -rf "$ccache_dir" && mkdir -p "$ccache_dir"
if ! mbt_bench_populate "$base_dir/cell" "$g" "$device" \
"$jobs" "$target" "$ccache_dir" "$log_dir/$(printf "%s" "$g" | tr ' ' '-')-prime.log" bench_extra_args; then
mbt_error "cache priming failed for $label; skipping."
r_gen+=("$g"); r_mode+=("$mode"); r_secs+=("0"); r_ok+=("0")
continue
fi
fi
mbt_log "running: $label (populated cache)"
secs="$(mbt_bench_cell "$base_dir/cell" "$g" "ON" "$device" \
"$jobs" "$target" "$ccache_dir" "$log_file" bench_extra_args)" && cell_status=0 || cell_status=$?
;;
esac
if [[ "$cell_status" -ne 0 ]]; then
mbt_error "cell failed: $label (see $log_file)"
r_gen+=("$g"); r_mode+=("$mode"); r_secs+=("0"); r_ok+=("0")
else
mbt_log " -> $(mbt_format_seconds "$secs") ($secs s)"
r_gen+=("$g"); r_mode+=("$mode"); r_secs+=("$secs"); r_ok+=("1")
fi
done
done
rm -rf "$base_dir/cell"
# Summary table.
printf "\n"
mbt_log "benchmark summary (device=$device, jobs=$jobs)"
printf " %-16s %-8s %12s %10s\n" "generator" "ccache" "time" "vs off"
printf " %-16s %-8s %12s %10s\n" "---------" "------" "----" "------"
local i baseline speedup
for ((i = 0; i < ${#r_gen[@]}; i++)); do
# Find the "off" baseline for the same generator, if measured.
baseline=""
local j
for ((j = 0; j < ${#r_gen[@]}; j++)); do
if [[ "${r_gen[$j]}" == "${r_gen[$i]}" && "${r_mode[$j]}" == "off" && "${r_ok[$j]}" == "1" ]]; then
baseline="${r_secs[$j]}"
break
fi
done
if [[ "${r_ok[$i]}" != "1" ]]; then
printf " %-16s %-8s %12s %10s\n" "${r_gen[$i]}" "${r_mode[$i]}" "FAILED" "-"
continue
fi
if [[ -n "$baseline" && "${r_mode[$i]}" != "off" ]]; then
speedup="$(awk -v b="$baseline" -v t="${r_secs[$i]}" 'BEGIN { if (t > 0) printf "%.2fx", b / t; else printf "-" }')"
else
speedup="-"
fi
printf " %-16s %-8s %12s %10s\n" "${r_gen[$i]}" "${r_mode[$i]}" "$(mbt_format_seconds "${r_secs[$i]}")" "$speedup"
done
printf "\n"
mbt_log "'vs off' > 1x means faster than the same generator's ccache=off build."
mbt_log "cold = empty cache (populate cost); warm = pre-populated cache (the ccache win)."
}
main() {
local command="build"
local quickstart=0
@@ -439,11 +755,14 @@ main() {
local with_submodule_sync=1
local with_toolchain=1
local build_dir="$SCRIPT_PATH/build"
local build_dir_explicit=0
local generator="Ninja"
local jobs
local target=""
local clean_build=0
local non_interactive=0
local benchmark_generators=""
local benchmark_ccache=""
local -a cmake_extra_args=()
local -a build_extra_args=()
@@ -457,7 +776,7 @@ main() {
if [[ "$#" -gt 0 ]]; then
case "$1" in
build|deps|toolchain|doctor|shell)
build|deps|toolchain|doctor|shell|benchmark)
command="$1"
shift
;;
@@ -515,8 +834,27 @@ main() {
-B|--build-dir)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
build_dir="$2"
build_dir_explicit=1
shift 2
;;
--bench-generators)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
benchmark_generators="$2"
shift 2
;;
--bench-generators=*)
benchmark_generators="${1#*=}"
shift
;;
--bench-ccache)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
benchmark_ccache="$2"
shift 2
;;
--bench-ccache=*)
benchmark_ccache="${1#*=}"
shift
;;
-G|--generator)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
generator="$2"
@@ -598,6 +936,13 @@ main() {
;;
build)
;;
benchmark)
# Never benchmark into the user's working ./build dir; a benchmark
# wipes its build dir between cells. Use a dedicated dir by default.
if [[ "$build_dir_explicit" -eq 0 ]]; then
build_dir="$SCRIPT_PATH/build-bench"
fi
;;
*)
mbt_die "unsupported command: $command"
;;
@@ -640,6 +985,12 @@ main() {
mbt_log "quickstart mode enabled."
fi
if [[ "$command" == "benchmark" ]]; then
mbt_benchmark "$build_dir" "$device" "$jobs" "$target" \
"$benchmark_generators" "$benchmark_ccache" cmake_extra_args
exit $?
fi
mbt_build "$build_dir" "$generator" "$use_ccache" "$device" "$jobs" "$target" "$clean_build" \
cmake_extra_args[@] build_extra_args[@]
}
+7
View File
@@ -0,0 +1,7 @@
TEST
Portapack
HackRF
1
2
3
4