Compare commits

...

20 Commits

Author SHA1 Message Date
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
Pezsma c16bac24c7 Add RSSI input configuration for VGA output measurement (#3231) 2026-06-21 11:17:09 +02:00
Payalo64 4e6a2cc934 Add 3.3GHz Analog FPV Channels to FPV_ANALOG.TXT (#3230) 2026-06-20 18:28:02 +02:00
Pezsma cafe62564e Gpio modify (#3229)
* Refactor GPIO configuration for PRALINE and non-PRALINE setups

- Updated PinMap structure to include gpio_mode for better flexibility.
- Added new GPIO mappings for SGPIO pins with appropriate configurations.
- Commented out unused GPIO definitions in hackrf_gpio.hpp to improve code clarity.
- Adjusted GPIO initialization for control pins to utilize the new PinMap structure.
- Ensured compatibility for both PRALINE and non-PRALINE configurations by using preprocessor directives.

* Refactor GPIO handling and remove LED abstraction

- Updated GPIO class to support logical polarity, enabling/disabling features based on their configured state.
- Replaced direct GPIO manipulation in power control functions with new GPIO methods for better readability and maintainability.
- Removed the LED class and its associated functionality, as it was deemed unnecessary for the current implementation.
- Adjusted GPIO initialization for various components, ensuring correct polarity settings for VAA and power enable pins.
- Cleaned up unused includes and commented-out code in hackrf_gpio.hpp.

* Refactor GPIO LED control methods to use setActive() and setInactive() for improved clarity

* Refactor GPIO control methods to use setActive() and setInactive() for improved clarity and consistency

* copilot

* Update GPIO control logic and pin definitions for clarity and consistency

* Refactor GPIO methods for improved naming consistency and clarity
2026-06-19 07:48:55 +02:00
gullradriel 960e5b8329 Upgrade subm plus warning fix (#3227)
* fix compilation warning
* update submodule
2026-06-17 11:34:00 +02:00
Totoo f5a5e8e5e8 Map improv. (#3226) 2026-06-16 17:54:32 +02:00
Synray d8a1e2530a Add Security+ 2.0 TX App (#3217)
* Add U64 BoundSetting
* Add secplus-tx
* Add secplus license
* Update icon
* UI update
* Add filesystem::path BoundSetting
* Add file UI
* Rename file
* Add save button
* Fix warnings, add integer conversions to UI macros
* UI update
* Copilot suggestions, name edit UI
* Fix buffer length
* Style update
* Avoid recursion in Widget::style()
* Apply suggestions from code review
* Move secplustx.cpp to external
* Shorten title
* Space optimizations, remove FilePath setting
* Remove assertions
* Change namespace
* Change title to match
2026-06-15 19:28:47 +02:00
Totoo b84a424875 MBT update 2026-06-13 09:50:25 +08:00
gullradriel 62d0e598d4 Update submodule (#3222)
* update submodule to latest GSG modifications
* build: fix delay function
* update submodule to include praline fix
* fix dep to rebuild/reinclude hackrf_usb_ram.bin
2026-06-12 22:15:44 +02:00
Synray a93f2ff583 Remove sine table lookup from proc_ook (#3201) 2026-06-12 16:26:13 +02:00
Totoo e2586ceb22 proper fix for flasher (#3223) 2026-06-12 16:25:43 +02:00
未来方舟 5a2b0cdc2a build tool and agent.md (#2993)
* init

* note

* detect arch

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update mbt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* protect rm -rf

* remove wrong ai code

* Update mbt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add agent file

* remove empty file

* agent.md rewrite

* agent.md rewrite

* fix on ubuntu

* err check

* add make

* add python3-yaml

* add binutils

* fix the build order to avoid some compile bugs

* mg

* remove trash

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
Co-authored-by: HTotoo <ttotoo@gmail.com>
2026-06-12 16:52:13 +08:00
65 changed files with 3089 additions and 1131 deletions
+65
View File
@@ -0,0 +1,65 @@
# PortaPack Mayhem Agent Guidelines
Welcome to the PortaPack Mayhem repository. This document sets mandatory rules and best practices for human developers and AI coding assistants. It exists to prevent unverified, untested code from overwhelming the maintainers, while still enabling AI to accelerate development.
## 🚫 STRICTLY PROHIBITED
1. **No Fully Automated Pull Requests**
Autonomous agents (bots, crawlers, automated workflows) must **never** open a Pull Request without direct human supervision, review, and explicit approval.
2. **No “Blind” AI-Generated Submissions**
If you cannot explain, debug, or maintain the C++/DSP code an AI produced for you, do **not** submit it. PRs that appear to be untested, AI-only “compile-and-pray” attempts will be closed immediately.
3. **No Unverified Hardware Interactions**
AI cannot test on real hardware. Do **not** submit code that has not been compiled and physically tested on a PortaPack device (or a highly accurate emulator). This includes UI rendering, RF transmission quality, and baseband stability.
## ✅ ENCOURAGED AI USE CASES
We strongly encourage using AI assistants (Copilot, ChatGPT, Claude, local LLMs) as pair-programmers. Excellent use cases include:
- **Bug Triage & Root-Causing:** Analyze crash logs, stack traces, and complex memory issues to identify the root cause of an open issue.
- **Code Comprehension:** Map Mayhems architecture, understand M0M4 IPC, or break down complex DSP algorithms.
- **Development Assistance:** Generate boilerplate UI code, write unit tests, optimize math functions, or draft documentation provided a human reviews and refines the output.
- **Targeted Refactoring:** Suggest cleaner, modern C++ for small legacy code segments. **Do not attempt to rewrite the entire codebase.**
## 📋 MANDATORY RULES FOR AIASSISTED CONTRIBUTIONS
When submitting AIassisted work, you must:
1. **Take Full Ownership**
You are strictly responsible for every line of code you submit, regardless of origin. Be prepared to answer detailed technical questions during review.
2. **Hardware Verification Required**
In your PR, explicitly state: *“I have compiled this code and tested it on physical PortaPack hardware.”* PRs without this statement will be rejected.
3. **Transparency (Recommended)**
If AI significantly helped architect a feature or fix a complex bug, mention it briefly in the PR description (e.g., *“Used an LLM to help optimize the DSP filter loop”*).
4. **Adhere to Coding Standards**
Ensure all code matches the existing Mayhem style and formatting before opening the PR.
## ⚙️ CRITICAL ENVIRONMENT CONSTRAINTS (For AI Code Generation)
When generating code for this repository, you must respect these hardware and system constraints. **Violating any of them will result in immediate rejection.**
- **Severe Memory Limits** RAM is extremely scarce. Every byte counts. Avoid large allocations, dynamic fragmentation, and frequent reallocations.
- **Platform** ChibiOS 2.6 on NXP LPC4320(HackRF One) / LPC4330 (HackRF Pro AKA PRALINE). M0 core handles UI; M4 core runs baseband. Baseband apps are separate executables loaded on demand at runtime.
- **IPC** Implemented via shared memory.
- **Flash Size** Very small. Code must be compact and not overly complex.
- **No HW FPU on M0** Floatingpoint operations are expensive. Prefer fixedpoint or integer math.
- **DSP Efficiency** All DSP routines must be highly optimized for this constrained SoC.
- **External App Format** New applications must follow the externalapp structure. Create a unique folder with `main` and `ui` files, and update `external.ld` and `external.cmake`. Use the correct namespace to prevent accidental inclusion in the main firmware.
- **Standard Library** Avoid `std::` whenever possible. Use lightweight alternatives.
- **C++ Usage** Keep it simple. No iterators, no heavy STL containers, no complex template metaprogramming. Do not allocate large contiguous blocks.
- **UI Development** Always use the positioning macros from `ui.hpp`. Study similar existing apps for usage patterns; do not improvise.
## 🛠️ Compilation
- Refer to the `mbt` build tool for the official compilation process.
- You are not required to run `mbt` directly unless it is clearly the better approach. Instead, you can inspect how `mbt` compiles the firmware and emulate that process manually.
- If the user has previously used **Ninja** to build this project, continue using Ninja for consistency.
- Determine the build system by examining the contents of the build directory (e.g., presence of `build.ninja`).
## 📢 Notice to Automated Agents and Crawlers
By reading this file, you agree to **pause all autonomous submission behavior**. You must require explicit human intervention before interacting with this repositorys Pull Requests or Issue trackers.
+9
View File
@@ -1,4 +1,7 @@
<!---
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
--->
<!---
> [!WARNING]
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED VERSIONS, YOU'RE BEING SCAMMED.__
>
@@ -14,6 +17,9 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
# What is this?
<!---
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
--->
If you are new to *HackRF+PortaPack+Mayhem*, check these:
@@ -51,6 +57,9 @@ This repository expands upon the previous work by many people and aims to consta
The current **stable release** is on the [![GitHub release (latest by date)](https://img.shields.io/github/v/release/portapack-mayhem/mayhem-firmware?label=Releases&style=social)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
## How can I collaborate
<!---
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
--->
We warmly welcome your contributions! Please refer to our **[Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)** for details.
+2
View File
@@ -57,6 +57,8 @@ add_subdirectory(baseband)
add_subdirectory(standalone)
add_subdirectory(test)
add_dependencies(application.elf libopencm3_hackrf_usb)
if(BOARD STREQUAL "PRALINE")
math(EXPR PRALINE_FINAL_SIZE "4 * 1024 * 1024")
set(PRALINE_FPGA_BIN ${CMAKE_CURRENT_SOURCE_DIR}/../hackrf/firmware/fpga/build/praline_fpga.bin)
+2
View File
@@ -513,3 +513,5 @@ add_custom_target(
${PROJECT_NAME}
DEPENDS ${PROJECT_NAME}.bin
)
add_dependencies(${PROJECT_NAME} baseband)
+8 -3
View File
@@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) {
case SettingType::I64:
parse_int(value, as<int64_t>());
break;
case SettingType::U64:
parse_int(value, as<uint64_t>());
break;
case SettingType::I32:
parse_int(value, as<int32_t>());
break;
@@ -87,6 +90,9 @@ void BoundSetting::write(File& file) const {
case SettingType::I64:
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
break;
case SettingType::U64:
file.write(to_string_dec_uint(as<uint64_t>(), buffer, length), length);
break;
case SettingType::I32:
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
break;
@@ -97,9 +103,8 @@ void BoundSetting::write(File& file) const {
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
break;
case SettingType::String: {
const auto& str = as<std::string>();
file.write(str.data(), str.length());
break;
file.write_line(as<std::string>());
return;
}
case SettingType::Bool:
file.write(as<bool>() ? "1" : "0", 1);
+4
View File
@@ -48,6 +48,7 @@ class BoundSetting {
/* The type of bound setting. */
enum class SettingType : uint8_t {
I64,
U64,
I32,
U32,
U8,
@@ -60,6 +61,9 @@ class BoundSetting {
BoundSetting(std::string_view name, int64_t* target)
: name_{name}, target_{target}, type_{SettingType::I64} {}
BoundSetting(std::string_view name, uint64_t* target)
: name_{name}, target_{target}, type_{SettingType::U64} {}
BoundSetting(std::string_view name, int32_t* target)
: name_{name}, target_{target}, type_{SettingType::I32} {}
+2 -2
View File
@@ -3176,9 +3176,9 @@ void RFFC5072StatusView::refresh_status() {
// Blink LED
/*for (int i = 0; i < 3; i++) {
hackrf::one::led_rx.on();
hackrf::one::led_rx.setActive();
chThdSleepMilliseconds(100);
hackrf::one::led_rx.off();
hackrf::one::led_rx.setInactive();
chThdSleepMilliseconds(100);
}*/
} else {
@@ -134,12 +134,6 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
}
}
void FlashUtilityView::wait_till_loaded() {
while (!isLoaded) {
chThdSleepMilliseconds(50);
}
}
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
//
painter.fill_rectangle(
@@ -157,6 +151,7 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
ui::Painter painter;
menu_view.hidden(true);
if (endsWith(path, u".tar")) {
// extract, then update
path = extract_tar(u'/' + path, painter).native();
@@ -166,13 +161,14 @@ bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, Theme::getInstance()->bg_darkest->background);
painter.draw_string({0, 60}, *Theme::getInstance()->fg_red, "BAD FIRMWARE FILE OR W/R ERR");
chThdSleepMilliseconds(5000);
menu_view.hidden(false);
return false;
}
painter.fill_rectangle(
{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.");
@@ -49,7 +49,6 @@ class FlashUtilityView : public View {
std::string title() const override { return "Flash Utility"; };
bool flash_firmware(std::filesystem::path::string_type path);
void wait_till_loaded();
private:
NavigationView& nav_;
@@ -68,14 +67,6 @@ class FlashUtilityView : public View {
void firmware_selected(std::filesystem::path::string_type path);
bool endsWith(const std::u16string& str, const std::u16string& suffix);
bool isLoaded = false;
uint8_t refreshcnt = 0;
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
refreshcnt++;
if (refreshcnt > 5) isLoaded = true;
}};
};
} /* namespace ui */
+5 -1
View File
@@ -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();
+47 -44
View File
@@ -25,6 +25,8 @@
#include "portapack_io.hpp"
#include "portapack.hpp"
#include "lpc43xx.inc"
#include "hackrf_hal.hpp"
using namespace hackrf::one;
@@ -417,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. */
@@ -426,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);
}
@@ -434,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();
@@ -676,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--);
}
@@ -691,7 +695,7 @@ ClockManager::Reference ClockManager::choose_reference() {
}
if (hackrf_r9) {
gpio_r9_clkin_en.write(0);
gpio_control::r9_clkin_en.setInactive();
}
#endif
@@ -1178,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.
@@ -1211,47 +1214,47 @@ void ClockManager::enable_clock_output(bool enable) {
#ifdef PRALINE
void ClockManager::set_p1_control(P1_Function func) {
// Truth table based on P1_Control.csv (L=clear, H=set)
// Truth table based on P1_Control.csv (L=setInactive, H=setActive)
switch (func) {
case P1_Function::TriggerIn:
gpio_control::p1_ctrl2.clear();
gpio_control::p1_ctrl1.clear();
gpio_control::p1_ctrl0.clear();
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::AuxClk1:
gpio_control::p1_ctrl2.clear();
gpio_control::p1_ctrl1.clear();
gpio_control::p1_ctrl0.set();
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setActive();
break;
case P1_Function::ClkIn:
gpio_control::p1_ctrl2.clear();
gpio_control::p1_ctrl1.set();
gpio_control::p1_ctrl0.clear();
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::TriggerOut:
gpio_control::p1_ctrl2.clear();
gpio_control::p1_ctrl1.set();
gpio_control::p1_ctrl0.set();
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setActive();
break;
case P1_Function::P22_ClkIn:
gpio_control::p1_ctrl2.set();
gpio_control::p1_ctrl1.clear();
gpio_control::p1_ctrl0.clear();
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::P2_5:
gpio_control::p1_ctrl2.set();
gpio_control::p1_ctrl1.clear();
gpio_control::p1_ctrl0.set();
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setActive();
break;
case P1_Function::NotConnected:
gpio_control::p1_ctrl2.set();
gpio_control::p1_ctrl1.set();
gpio_control::p1_ctrl0.clear();
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::AuxClk2:
gpio_control::p1_ctrl2.set();
gpio_control::p1_ctrl1.set();
gpio_control::p1_ctrl0.set();
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setActive();
break;
}
}
@@ -1259,20 +1262,20 @@ void ClockManager::set_p1_control(P1_Function func) {
void ClockManager::set_p2_control(P2_Function func) {
// Ensure all P2 control pins are configured as outputs
// Truth table based on P2_Control.csv (L=clear, H=set)
// Truth table based on P2_Control.csv (L=setInactive, H=setActive)
switch (func) {
case P2_Function::Clk3:
// CTRL0 is 'X' (don't care) according to CSV, we default it to Low (clear)
gpio_control::p2_ctrl1.clear();
gpio_control::p2_ctrl0.clear();
// CTRL0 is 'X' (don't care) according to CSV, we default it to Low (setInactive)
gpio_control::p2_ctrl1.setInactive();
gpio_control::p2_ctrl0.setInactive();
break;
case P2_Function::TriggerIn:
gpio_control::p2_ctrl1.set();
gpio_control::p2_ctrl0.clear();
gpio_control::p2_ctrl1.setActive();
gpio_control::p2_ctrl0.setInactive();
break;
case P2_Function::TriggerOut:
gpio_control::p2_ctrl1.set();
gpio_control::p2_ctrl0.set();
gpio_control::p2_ctrl1.setActive();
gpio_control::p2_ctrl0.setActive();
break;
}
}
+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;
+15 -10
View File
@@ -24,6 +24,11 @@
#include "hackrf_gpio.hpp"
#include "portapack_hal.hpp"
#include "board.h"
#include "gpio.hpp"
using namespace gpio_control;
void config_mode_blink_until_dfu();
void config_mode_set() {
@@ -103,16 +108,16 @@ void config_mode_run() {
auto tx_value = ((tx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
if (tx_value) {
hackrf::one::led_tx.on();
led_tx.setActive();
} else {
hackrf::one::led_tx.off();
led_tx.setInactive();
}
auto rx_value = ((rx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
if (rx_value) {
hackrf::one::led_rx.on();
led_rx.setActive();
} else {
hackrf::one::led_rx.off();
led_rx.setInactive();
}
chThdSleepMilliseconds(100);
@@ -122,14 +127,14 @@ void config_mode_run() {
void config_mode_blink_until_dfu() {
while (true) {
hackrf::one::led_tx.on();
hackrf::one::led_rx.on();
hackrf::one::led_usb.on();
led_tx.setActive();
led_rx.setActive();
led_usb.setActive();
chThdSleepMilliseconds(10);
hackrf::one::led_tx.off();
hackrf::one::led_rx.off();
hackrf::one::led_usb.off();
led_tx.setInactive();
led_rx.setInactive();
led_usb.setInactive();
chThdSleepMilliseconds(115);
auto dfu_btn = portapack::gpio_dfu.read();
+5 -2
View File
@@ -33,6 +33,9 @@
#include "irq_controls.hpp"
#include "file_path.hpp"
#include "gpio.hpp"
using namespace gpio_control;
using namespace ui;
#define DEBUG_LOG_FILE "debug_log.txt"
@@ -134,9 +137,9 @@ void draw_line(int32_t y_offset, const char* label, regarm_t value) {
}
void runtime_error(uint8_t source) {
LED led = (source == CORTEX_M0) ? hackrf::one::led_rx : hackrf::one::led_tx;
const auto& led = (source == CORTEX_M0) ? led_rx : led_tx;
led.off();
led.setInactive();
// wait for DFU button release if pressed, so we don't immediately jump into stack dump
while (swizzled_switches() & (1 << (int)Switch::Dfu));
+12 -14
View File
@@ -37,8 +37,8 @@
#include "ch.h"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;
#include "gpio.hpp"
using namespace gpio_control;
#include "irq_rtc.hpp"
@@ -56,8 +56,6 @@ using namespace hackrf::one;
#include "ui_navigation.hpp"
#include "gpio.hpp"
static int delayed_error = 0;
extern "C" {
@@ -229,8 +227,8 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
LPC_ADC0->CR &= ~(1 << 21);
LPC_ADC1->CR &= ~(1 << 21);
led_rx.off();
led_usb.off();
led_rx.setInactive();
led_usb.setInactive();
rtc_wakeup_init();
NVIC_EnableIRQ(I2C0_OR_I2C1_IRQn);
@@ -247,21 +245,21 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
if (is_full) {
// Case 1: Battery full (All LEDs off)
led_rx.off();
led_tx.off();
led_rx.setInactive();
led_tx.setInactive();
} else if ((voltage < 4150 && current < 10) || valid_mask == 0) {
// Case 2: Not full but low current draw (<10mA) -> Charging error
led_tx.on(); // LED indicates error/idle
led_rx.off();
led_tx.setActive(); // LED indicates error/idle
led_rx.setInactive();
} else {
// Case 3: Actively charging
led_rx.on(); // LED indicates charging
led_tx.off();
led_rx.setActive(); // LED indicates charging
led_tx.setInactive();
}
} else {
// Case 4: Battery IC not detected -> Error or H2 or older, so don't show that as an error.
led_tx.on();
led_rx.on();
led_tx.setActive();
led_rx.setActive();
}
// Shut down I2C and power down the APB bus for sleep
+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);
+7 -1
View File
@@ -327,7 +327,7 @@ set(EXTCPPSRC
external/rtty_tx/ui_rtty_tx.cpp
external/rtty_tx/baudot.cpp
#pocsag_tx
#pocsag_tx
external/pocsag_tx/main.cpp
external/pocsag_tx/ui_pocsag_tx.cpp
@@ -362,6 +362,11 @@ set(EXTCPPSRC
#hard_reset
external/hard_reset/main.cpp
external/hard_reset/ui_hard_reset.cpp
#secplustx
external/secplustx/main.cpp
external/secplustx/ui_secplustx.cpp
external/secplustx/secplustx.cpp
)
set(EXTAPPLIST
@@ -451,6 +456,7 @@ set(EXTAPPLIST
two_tone_pager
two_tone_rx
hard_reset
secplustx
)
# sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds
+7
View File
@@ -110,6 +110,7 @@ MEMORY
ram_external_app_two_tone_rx (rwx) : org = 0xAE050000, len = 32k
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
}
SECTIONS
@@ -635,4 +636,10 @@ SECTIONS
KEEP(*(.external_app.app_hard_reset.application_information));
*(*ui*external_app*hard_reset*);
} > ram_external_app_hard_reset
.external_app_secplustx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_secplustx.application_information));
*(*ui*external_app*secplustx*);
} > ram_external_app_secplustx
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2026 Synray
*
* 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_navigation.hpp"
#include "ui_secplustx.hpp"
#include "external_app.hpp"
namespace ui::external_app::ui_secplustx {
void initialize_app(ui::NavigationView& nav) {
nav.push<SecplusTXView>();
}
} // namespace ui::external_app::ui_secplustx
extern "C" {
__attribute__((section(".external_app.app_secplustx.application_information"), used)) application_information_t _application_information_secplustx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::ui_secplustx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Security+",
/*.bitmap_data = */ {
0b00100000,
0b00000000,
0b00100000,
0b00100000,
0b00100000,
0b01110000,
0b00100000,
0b00100000,
0b11100000,
0b00000111,
0b11110000,
0b00001111,
0b00110000,
0b00001100,
0b00110000,
0b00001100,
0b11110000,
0b00001111,
0b11110000,
0b00001111,
0b01110000,
0b00001101,
0b10110000,
0b00001110,
0b01110000,
0b00001101,
0b10110000,
0b00001110,
0b11110000,
0b00001111,
0b11100000,
0b00000111,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+150
View File
@@ -0,0 +1,150 @@
/*
* Copyright 2022 Clayton Smith (argilo@gmail.com)
*
* This file is part of secplus.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "secplustx.hpp"
namespace ui::external_app::ui_secplustx {
static void v2_calc_parity(const uint64_t fixed, uint32_t* data) {
uint32_t parity = (fixed >> 32) & 0xf;
int8_t offset;
*data &= 0xffff0fff;
for (offset = 0; offset < 32; offset += 4) {
parity ^= ((*data >> offset) & 0xf);
}
*data |= (parity << 12);
}
static void encode_v2_rolling(const uint32_t rolling,
uint32_t* rolling_halves) {
uint32_t rolling_reversed = 0;
int8_t i, half;
for (i = 0; i < 28; i++) {
rolling_reversed |= ((rolling >> i) & 1) << (28 - i - 1);
}
rolling_halves[0] = 0;
rolling_halves[1] = 0;
for (half = 0; half < 2; half++) {
for (i = 0; i < 8; i += 2) {
rolling_halves[half] |= rolling_reversed % 3 << i;
rolling_reversed /= 3;
}
}
for (half = 0; half < 2; half++) {
for (i = 10; i < 18; i += 2) {
rolling_halves[half] |= rolling_reversed % 3 << i;
rolling_reversed /= 3;
}
}
rolling_halves[0] |= (rolling_reversed % 3) << 8;
rolling_reversed /= 3;
rolling_halves[1] |= (rolling_reversed % 3) << 8;
}
static const int8_t ORDER[16] = {9, 33, 6, -1, 24, 18, 36, -1,
24, 36, 6, -1, -1, -1, -1, -1};
static const int8_t INVERT[16] = {6, 2, 1, -1, 7, 5, 3, -1,
4, 0, 5, -1, -1, -1, -1, -1};
static void v2_scramble(const uint32_t* parts, const uint8_t frame_type, uint8_t* packet_half) {
const int8_t order = ORDER[packet_half[0] >> 4];
const int8_t invert = INVERT[packet_half[0] & 0xf];
int8_t i;
uint8_t out_offset = 10;
int8_t end;
uint32_t parts_permuted[3];
end = (frame_type == 0 ? 5 : 8);
for (i = 1; i < end; i++) {
packet_half[i] = 0;
}
parts_permuted[0] =
(invert & 4) ? ~parts[(order >> 4) & 3] : parts[(order >> 4) & 3];
parts_permuted[1] =
(invert & 2) ? ~parts[(order >> 2) & 3] : parts[(order >> 2) & 3];
parts_permuted[2] = (invert & 1) ? ~parts[order & 3] : parts[order & 3];
end = (frame_type == 0 ? 8 : 0);
for (i = 18 - 1; i >= end; i--) {
packet_half[out_offset >> 3] |= ((parts_permuted[0] >> i) & 1)
<< (7 - (out_offset % 8));
out_offset++;
packet_half[out_offset >> 3] |= ((parts_permuted[1] >> i) & 1)
<< (7 - (out_offset % 8));
out_offset++;
packet_half[out_offset >> 3] |= ((parts_permuted[2] >> i) & 1)
<< (7 - (out_offset % 8));
out_offset++;
}
}
static void encode_v2_half_parts(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) {
uint32_t parts[3];
parts[0] = ((fixed >> 10) << 8) | (data >> 8);
parts[1] = ((fixed & 0x3ff) << 8) | (data & 0xff);
parts[2] = rolling;
packet_half[0] = (uint8_t)rolling;
v2_scramble(parts, frame_type, packet_half);
}
static int8_t v2_check_limits(const uint32_t rolling, const uint64_t fixed) {
if ((rolling >> 28) != 0) {
return -1;
}
if ((fixed >> 40) != 0) {
return -1;
}
return 0;
}
static void encode_v2_half(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) {
encode_v2_half_parts(rolling, fixed, data, frame_type, packet_half);
/* shift indicator two bits to the right */
packet_half[1] |= (packet_half[0] & 0x3) << 6;
packet_half[0] >>= 2;
/* set frame type */
packet_half[0] |= (frame_type << 6);
}
int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2) {
int8_t err = 0;
uint32_t rolling_halves[2];
err = v2_check_limits(rolling, fixed);
if (err < 0) {
return err;
}
encode_v2_rolling(rolling, rolling_halves);
v2_calc_parity(fixed, &data);
encode_v2_half(rolling_halves[0], fixed >> 20, data >> 16, frame_type,
packet1);
encode_v2_half(rolling_halves[1], fixed & 0xfffff, data & 0xffff, frame_type,
packet2);
return 0;
}
}; // namespace ui::external_app::ui_secplustx
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2022 Clayton Smith (argilo@gmail.com)
*
* This file is part of secplus.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#ifndef __SECPLUSTX__
#define __SECPLUSTX__
#include <cstdint>
namespace ui::external_app::ui_secplustx {
int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2);
};
#endif
+227
View File
@@ -0,0 +1,227 @@
#include "ui_secplustx.hpp"
#include <string_view>
#include <string>
#include <utility>
#include <vector>
#include "secplustx.hpp"
#include "ui_fileman.hpp"
#include "file_reader.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "optional.hpp"
using namespace portapack;
using namespace ui;
namespace fs = std::filesystem;
namespace ui::external_app::ui_secplustx {
static constexpr uint8_t secplus_repeat_count = 3;
static constexpr float secplus_sample_rate = OOK_SAMPLERATE / 4000.0f;
Optional<SecplusData> SecplusTXView::read_secplus_file(const fs::path& file_path) {
File file{};
if (auto error = file.open(file_path)) return {};
std::string raw = *FileLineReader{file}.begin();
std::vector chunks = split_string(raw, ';');
if (chunks.empty()) return {};
SecplusData data{};
data.version = static_cast<SecplusVersion>(std::strtoul(chunks[0].data(), NULL, 10));
switch (data.version) {
case SecplusVersion::V1:
return {}; // unimplemented
case SecplusVersion::V2: {
if (chunks.size() != 6) return {};
data.name = std::string{chunks[1]};
if (data.name.empty()) data.name = "Remote";
data.has_data = std::strtoul(chunks[2].data(), NULL, 10);
data.fixed_code = std::strtoull(chunks[3].data(), NULL, 16);
data.rolling_code = std::strtoul(chunks[4].data(), NULL, 16);
data.data = std::strtoul(chunks[5].data(), NULL, 16);
return data;
}
default:
return {};
}
}
bool SecplusTXView::write_secplus_file(const fs::path& file_path, const SecplusData& data) {
ensure_directory(secplus_dir);
File file{};
if (auto error = file.create(file_path)) {
return false;
}
switch (data.version) {
case SecplusVersion::V1:
return false; // unimplemented
case SecplusVersion::V2: {
std::string formatted = to_string_dec_uint(static_cast<uint8_t>(data.version));
formatted += ';';
formatted += data.name;
formatted += ';';
formatted += to_string_dec_uint(data.has_data);
formatted += ';';
formatted += to_string_hex(data.fixed_code);
formatted += ';';
formatted += to_string_hex(data.rolling_code);
formatted += ';';
formatted += to_string_hex(data.data);
file.write_line(formatted);
file.close();
return true;
}
default:
return false;
}
}
SecplusTXView::SecplusTXView(NavigationView& nav)
: nav_{nav} {
if (!fs::file_exists(file_path)) write_secplus_file(file_path, data);
baseband::run_image(portapack::spi_flash::image_tag_ook);
add_children({&button_open,
&button_save,
&field_name,
&labels,
&field_fixed,
&field_rolling,
&has_data,
&field_data,
&learn_mode,
&autosave,
&progressbar,
&tx_view});
button_open.on_select = [this](const Button&) {
ensure_directory(secplus_dir);
auto open_view = nav_.push<FileLoadView>(".SECPLUS");
open_view->push_dir(secplus_dir);
open_view->on_changed = [this](fs::path path) { file_path = path.string(); };
nav_.set_on_pop([this]() { reload_data(); });
};
button_save.on_select = [this](const Button&) { write_secplus_file(file_path, data); };
field_name.on_select = [this, &nav](TextField&) {
buffer = data.name;
text_prompt(nav_, buffer, field_name.parent_rect().width() / UI_POS_DEFAULT_WIDTH, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
data.name = new_name;
field_name.set_text(new_name);
save_data();
});
};
field_fixed.on_change = [this](SymField& field) { data.fixed_code = field.to_integer(); };
field_rolling.on_change = [this](SymField& field) { data.rolling_code = field.to_integer(); };
field_data.on_change = [this](SymField& field) { data.data = field.to_integer(); };
has_data.on_select = [this](Checkbox& checkbox, bool value) {
data.has_data = value;
// fade if inactive, otherwise use default style
const Style* new_style = value ? &style() : Theme::getInstance()->fg_medium;
checkbox.set_style(new_style);
field_data.set_style(new_style);
field_data.set_focusable(value);
};
tx_view.on_edit_frequency = [this]() {
auto new_view = nav_.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [](rf::Frequency f) {
transmitter_model.set_target_frequency(f);
};
};
tx_view.on_start = [this]() { start_tx(); };
tx_view.on_stop = [this]() {
baseband::kill_ook();
stop_tx();
};
autosave.set_value(true);
reload_data();
}
void SecplusTXView::save_data() {
if (autosave.value()) write_secplus_file(file_path, data);
}
void SecplusTXView::reload_data() {
if (auto result = read_secplus_file(file_path)) data = std::move(*result);
// always update data, use default values if read failed
field_name.set_text(data.name);
has_data.set_value(data.has_data);
field_rolling.set_value(data.rolling_code);
field_fixed.set_value(data.fixed_code);
field_data.set_value(data.data);
}
void SecplusTXView::start_tx() {
uint8_t packet1[8]{};
uint8_t packet2[8]{};
size_t bitstream_length = 0;
constexpr uint8_t packet1_indicator = 0b00;
constexpr uint8_t packet2_indicator = 0b01;
auto encode_packet = [&bitstream_length](uint8_t indicator, auto& packet, bool has_data) {
constexpr uint32_t preamble = 0b0000000000000000'1111;
constexpr uint32_t blank_size = 24;
auto manchester_encode = [&bitstream_length](auto& x, uint32_t size) {
for (uint32_t i = 0; i < size; ++i) {
bool bit = (x >> (size - 1 - i)) & 1;
encoders::bitstream_append(bitstream_length, 2, bit ? 0b01 : 0b10);
}
};
manchester_encode(preamble, 20);
manchester_encode(indicator, 2);
for (uint8_t byte = 0; byte < (has_data ? 8 : 5); ++byte) manchester_encode(packet[byte], 8);
encoders::bitstream_append(bitstream_length, blank_size, 0);
};
if (encode_v2(data.rolling_code, data.fixed_code, data.data, data.has_data, packet1, packet2) < 0) {
nav_.display_modal("Error", "Invalid rolling/fixed code");
return;
}
encode_packet(packet1_indicator, packet1, has_data.value());
encode_packet(packet2_indicator, packet2, has_data.value());
if (!learn_mode.value()) {
field_rolling.set_value(++data.rolling_code);
field_rolling.set_dirty();
}
progressbar.set_max(secplus_repeat_count);
progressbar.set_value(1);
tx_view.set_transmitting(true);
transmitter_model.enable();
baseband::set_ook_data(
bitstream_length,
secplus_sample_rate,
secplus_repeat_count,
0);
}
void SecplusTXView::stop_tx() {
transmitter_model.disable();
progressbar.set_value(0);
tx_view.set_transmitting(false);
}
void SecplusTXView::on_tx_progress(uint32_t progress, bool done) {
progressbar.set_value(progress + 1);
if (done) {
stop_tx();
save_data();
}
}
SecplusTXView::~SecplusTXView() {
transmitter_model.disable();
baseband::shutdown();
save_data();
}
} // namespace ui::external_app::ui_secplustx
@@ -0,0 +1,96 @@
#ifndef __UI_SECPLUSTX__
#define __UI_SECPLUSTX__
#include <cstdint>
#include <string>
#include "ui.hpp"
#include "file.hpp"
#include "ui_transmitter.hpp"
#include "transmitter_model.hpp"
#include "file_path.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "encoders.hpp"
#include "string_format.hpp"
namespace ui::external_app::ui_secplustx {
enum class SecplusVersion : uint8_t {
V1,
V2,
};
struct SecplusData {
SecplusVersion version;
std::string name;
bool has_data;
uint64_t fixed_code;
uint32_t rolling_code;
uint32_t data;
};
class SecplusTXView : public View {
public:
void focus() override { button_open.focus(); }
SecplusTXView(NavigationView& nav);
~SecplusTXView();
std::string title() const override {
return "Security+";
}
private:
std::string file_path = (secplus_dir / "DEFAULT.SECPLUS").string();
SecplusData data{SecplusVersion::V2, "Remote", false, 0, 0, 0};
std::string buffer{};
NavigationView& nav_;
TxRadioState radio_state_{
315000000,
1750000,
OOK_SAMPLERATE};
app_settings::SettingsManager settings_{"tx_secplus", app_settings::Mode::TX, {{"file_path"sv, &file_path}}};
Button button_open{{UI_POS_X(1), UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Open"};
Button button_save{{screen_width / 2, UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Save"};
// remote data
TextField field_name{{UI_POS_X(1), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(2), UI_POS_HEIGHT(1)}, "Remote"};
Labels labels{
{{UI_POS_X(1), UI_POS_Y(4)}, "Fixed:", Theme::getInstance()->fg_medium->foreground},
{{UI_POS_X(1), UI_POS_Y(5)}, "Rolling:", Theme::getInstance()->fg_medium->foreground}};
SymField field_fixed{{UI_POS_X(10), UI_POS_Y(4)}, 10, SymField::Type::Hex, true};
SymField field_rolling{{UI_POS_X(10), UI_POS_Y(5)}, 7, SymField::Type::Hex, true};
Checkbox has_data{{UI_POS_X(1), UI_POS_Y(6)}, 5, "Data:", true};
SymField field_data{{UI_POS_X(10), UI_POS_Y(6)}, 8, SymField::Type::Hex, true};
// options
Checkbox learn_mode{{UI_POS_X(1), UI_POS_Y(7)}, 5, "Learn", true};
Checkbox autosave{{UI_POS_X(1), UI_POS_Y(8)}, 8, "Autosave", true};
ProgressBar progressbar{{UI_POS_X(2), UI_POS_Y_BOTTOM(5.75), UI_POS_WIDTH_REMAINING(4), UI_POS_HEIGHT(1)}};
TransmitterView tx_view{UI_POS_Y_BOTTOM(4), 1000000, 0};
Optional<SecplusData> read_secplus_file(const std::filesystem::path& file_path);
bool write_secplus_file(const std::filesystem::path& file_path, const SecplusData& data);
void save_data();
void reload_data();
void start_tx();
void stop_tx();
void on_tx_progress(uint32_t progress, bool done);
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.progress, message.done);
}};
};
} // namespace ui::external_app::ui_secplustx
#endif
+1
View File
@@ -57,4 +57,5 @@ const std::filesystem::path macaddress_dir = u"MACADDRESS";
const std::filesystem::path splash_dot_bmp = u"/splash.bmp";
const std::filesystem::path keeloq_keys_dir = u"KEELOQKEYS";
const std::filesystem::path keeloq_remotes_dir = u"KEELOQREMOTES";
const std::filesystem::path secplus_dir = u"SECPLUS";
const std::filesystem::path epirb_dir = u"EPIRB";
+1
View File
@@ -58,6 +58,7 @@ extern const std::filesystem::path waterfalls_dir;
extern const std::filesystem::path macaddress_dir;
extern const std::filesystem::path keeloq_keys_dir;
extern const std::filesystem::path keeloq_remotes_dir;
extern const std::filesystem::path secplus_dir;
extern const std::filesystem::path epirb_dir;
extern const std::filesystem::path splash_dot_bmp;
+10
View File
@@ -1,5 +1,6 @@
#include "gpio_lpc.h"
#include "gpio.h"
#include "delay.h"
typedef enum {
LED1 = 0,
@@ -34,6 +35,15 @@ void delay_us_at_mhz(uint32_t us, uint32_t mhz) {
delay((uint32_t)cycles64);
}
/* The application M0 core runs from BASE_M4_CLK at 200MHz. */
void delay_us(uint32_t us) {
delay_us_at_mhz(us, 200);
}
void delay_ms(uint32_t ms) {
delay_us_at_mhz(ms * 1000, 200);
}
void led_on(const led_t led) {
gpio_set(&gpio_led[led]);
}
+2 -2
View File
@@ -347,9 +347,9 @@ void MAX2831::set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) {
* external Anti-Aliasing (AA) filter on pin P1_14 to prevent aliasing.
*/
if (actual_bw <= 1750000) {
gpio_control::aa_en.set(); // Enable external narrow AA filter
gpio_control::aa_en.setActive(); // Enable external narrow AA filter
} else {
gpio_control::aa_en.clear(); // Disable external AA filter for wideband operations
gpio_control::aa_en.setInactive(); // Disable external AA filter for wideband operations
}
_desired_lpf_bw = actual_bw;
+3 -2
View File
@@ -131,6 +131,8 @@ void MAX2839::init() {
_map.r.rssi_vga.RSSI_MODE = 1; /* RSSI independent of RXHP */
_map.r.rssi_vga.RSSI_INPUT = 0; /* Measure RSSI at VGA Output (stronger signal) */
/*
* There are two LNA band settings, but we only use one of them.
* Switching to the other one doesn't make the overall spectrum any
@@ -305,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
@@ -176,7 +176,7 @@ struct SynthConfig {
void RFFC507x::init() {
#ifdef PRALINE
gpio_control::rf5072_mix_en.set(); // RF5072_MIX_EN
gpio_control::rf5072_mix_en.setActive(); // RF5072_MIX_EN
#endif
gpio_rffc5072_resetx.set();
-1
View File
@@ -132,7 +132,6 @@ Continuous (Fox-oring)
#include "spi_image.hpp"
#include "debug.hpp"
#include "led.hpp"
#include "gcc.hpp"
+8 -9
View File
@@ -53,6 +53,9 @@ using namespace hackrf::one;
#include "baseband_api.hpp"
#include "hal.h" // For LPC_SGPIO
#include "gpio.hpp"
using namespace gpio_control;
#include <array>
/* Direct access to the radio. Setting values incorrectly can damage
@@ -148,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;
@@ -280,9 +279,9 @@ void set_direction(const rf::Direction new_direction) {
baseband_codec.set_mode((direction == rf::Direction::Transmit) ? max5864::Mode::Transmit : max5864::Mode::Receive);
if (direction == rf::Direction::Receive)
led_rx.on();
led_rx.setActive();
else
led_tx.on();
led_tx.setActive();
}
bool set_tuning_frequency(const rf::Frequency frequency) {
@@ -395,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);
}
@@ -442,8 +441,8 @@ void disable() {
first_if.disable();
set_rf_amp(false);
led_rx.off();
led_tx.off();
led_rx.setInactive();
led_tx.setInactive();
}
#ifdef PRALINE
+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
+3 -1
View File
@@ -26,7 +26,9 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "event_m0.hpp"
#include "hackrf_gpio.hpp"
#include "gpio.hpp"
using namespace gpio_control;
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio.hpp"
+67 -25
View File
@@ -237,26 +237,69 @@ void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
if (map_zoom == 1) {
map_file.read(buffer, pixels << 1);
} else if (map_zoom > 1) {
map_file.read(buffer, (pixels / map_zoom) << 1);
// Zoom in: Expand each pixel to "map_zoom" number of pixels.
// Future TODO: Add dithering to smooth out the pixelation.
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
// For 240 width, than means no check is needed for map_zoom values up to 6.
// (Rectangle height must also divide evenly into map_zoom or we get black lines at end of screen)
// Note that zooming in results in a map offset of (1/map_zoom) pixels to the right & downward directions (see zoom_pixel_offset).
for (int i = (width / map_zoom) - 1; i >= 0; i--) {
for (int j = 0; j < map_zoom; j++) {
buffer[(i * map_zoom) + j] = buffer[i];
// Calculate how many source pixels we actually need from the file
uint16_t src_pixels_needed = (pixels + map_zoom - 1) / map_zoom;
if (src_pixels_needed == 0) src_pixels_needed = 1;
// Position the source data at the very END of the buffer.
// This allows us to overwrite the buffer from left-to-right safely.
uint16_t src_offset = pixels - src_pixels_needed;
// Read directly into the tail of the target buffer. Zero extra RAM needed.
map_file.read(&buffer[src_offset], src_pixels_needed << 1);
// Process forwards. Because `dst` grows by 1 and the source index grows by 1/map_zoom,
// `dst` will never catch up to overwrite a source pixel before we are done with it.
for (uint16_t dst = 0; dst < pixels; ++dst) {
uint16_t i0 = dst / map_zoom;
uint16_t i1 = i0 + 1;
// Clamp the right-hand pixel to avoid reading out of bounds on the last iteration
if (i1 >= src_pixels_needed) {
i1 = src_pixels_needed - 1;
}
// 'frac' represents the integer distance from the left pixel (0 to map_zoom - 1)
uint16_t frac = dst % map_zoom;
// Fetch colors from the tail of our buffer
ui::Color c0 = buffer[src_offset + i0];
ui::Color c1 = buffer[src_offset + i1];
if (frac == 0) {
// Exact pixel match, bypass the math entirely for a speed boost
buffer[dst] = c0;
} else {
uint32_t inv_frac = map_zoom - frac;
uint8_t r = ((uint32_t)c0.r() * inv_frac + (uint32_t)c1.r() * frac) / map_zoom;
uint8_t g = ((uint32_t)c0.g() * inv_frac + (uint32_t)c1.g() * frac) / map_zoom;
uint8_t b = ((uint32_t)c0.b() * inv_frac + (uint32_t)c1.b() * frac) / map_zoom;
buffer[dst] = ui::Color(r, g, b);
}
}
} else {
ui::Color zoom_out_buffer[(pixels * (-map_zoom))];
map_file.read(zoom_out_buffer, (pixels * (-map_zoom)) << 1);
// Zoom out: Collapse each group of "-map_zoom" pixels into one pixel.
// Future TODO: Average each group of pixels (in both X & Y directions if possible).
for (int i = 0; i < width; i++) {
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
const int skip = -map_zoom;
const int MAX_BUFFER_ELEMENTS = 256;
// Fixed-size local array avoids VLA crashes.
ui::Color zoom_out_buffer[MAX_BUFFER_ELEMENTS];
const int total_elements_needed = pixels * skip;
// Use the default size, but strictly cap it at 256 to protect the stack
const int chunk_size = total_elements_needed < MAX_BUFFER_ELEMENTS ? total_elements_needed : MAX_BUFFER_ELEMENTS;
int target_i = 0;
int current_file_offset = 0;
// Sequentially read through the required portion of the file in chunks
while (target_i < width && current_file_offset < total_elements_needed) {
// Calculate how many pixels we can read in this specific pass
int read_size = chunk_size < (total_elements_needed - current_file_offset) ? chunk_size : (total_elements_needed - current_file_offset);
// Read the chunk (<< 1 converts pixel count to byte count)
map_file.read(zoom_out_buffer, read_size << 1);
// Determine where the first valid "zoomed" pixel is located inside this specific chunk
int first_valid_index = 0;
int offset_modulo = current_file_offset % skip;
if (offset_modulo != 0) {
first_valid_index = skip - offset_modulo;
}
// Extract only the pixels we need, skipping the rest
for (int j = first_valid_index; j < read_size; j += skip) {
if (target_i < width) {
buffer[target_i] = zoom_out_buffer[j];
target_i++;
}
}
current_file_offset += read_size;
}
}
}
@@ -415,8 +458,7 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
return true;
}
BMPFile bmp{};
bmp.open("/OSM/" + to_string_dec_int(zoom) + "/" + to_string_dec_int(tile_x) + "/" + to_string_dec_int(tile_y) + ".bmp", true);
BMPFile* bmp = bmp_cache.get(zoom, tile_x, tile_y);
// 1. Define the source and destination areas, starting with the full tile.
int src_x = 0;
int src_y = 0;
@@ -449,7 +491,7 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
return true;
}
if (!bmp.is_loaded()) {
if (!bmp || !bmp->is_loaded()) {
// Draw an error rectangle using the calculated clipped dimensions
ui::Rect error_rect{{dest_x + r.left(), dest_y + r.top()}, {clip_w, clip_h}};
display.fill_rectangle(error_rect, Theme::getInstance()->bg_darkest->background);
@@ -458,20 +500,20 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
map_line_buffer.resize(clip_w);
if (bmp.is_bottomup()) {
if (bmp->is_bottomup()) {
for (int y = clip_h - 1; y >= 0; --y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(map_line_buffer.data(), clip_w, false);
bmp->seek(src_x, source_row);
bmp->read_next_px_cnt(map_line_buffer.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, map_line_buffer);
}
} else {
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(map_line_buffer.data(), clip_w, false);
bmp->seek(src_x, source_row);
bmp->read_next_px_cnt(map_line_buffer.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, map_line_buffer);
}
}
+101 -2
View File
@@ -30,13 +30,16 @@
#include "bmpfile.hpp"
#include "mathdef.hpp"
#include <array>
#include <cstdio>
#include <inttypes.h>
#include "portapack.hpp"
namespace ui {
#define MAX_MAP_ZOOM_IN 4000
#define MAX_MAP_ZOOM_OUT 10
#define MAP_ZOOM_RESOLUTION_LIMIT 5 // Max zoom-in to show map; rect height & width must divide into this evenly
#define MAX_MAP_ZOOM_OUT 15
#define MAP_ZOOM_RESOLUTION_LIMIT 10 // Max zoom-in to show map;
#define INVALID_LAT_LON 200
#define INVALID_ANGLE 400
@@ -74,6 +77,101 @@ struct GeoMarker {
}
};
class BMPFileCache {
public:
static constexpr uint8_t SlotsCount = 9;
BMPFileCache() {
}
~BMPFileCache() {
clear();
}
BMPFile* get(const int32_t z, const int32_t x, const int32_t y) {
// Cache hit.
for (auto& slot : slots_) {
if (slot.used && slot.x == x && slot.y == y && slot.z == z) {
slot.last_used = next_stamp();
return &slot.bmp;
}
}
// Select free slot first, otherwise LRU slot.
Slot* target = nullptr;
uint16_t oldest = 0xFFFFu;
for (auto& slot : slots_) {
if (!slot.used) {
target = &slot;
break;
}
if (slot.last_used < oldest) {
oldest = slot.last_used;
target = &slot;
}
}
if (!target) {
return nullptr;
}
if (target->used) {
target->bmp.close();
target->used = false;
}
// OSM tile path convention: <base>/<z>/<x>/<y>.bmp
char path_buffer[64];
snprintf(path_buffer, sizeof(path_buffer), "/OSM/%" PRId32 "/%" PRId32 "/%" PRId32 ".bmp", z, x, y);
if (!target->bmp.open(path_buffer, true)) {
target->bmp.close();
return nullptr;
}
target->x = x;
target->y = y;
target->z = z;
target->last_used = next_stamp();
target->used = true;
return &target->bmp;
}
void clear() {
for (auto& slot : slots_) {
if (slot.used) {
slot.bmp.close();
slot.used = false;
}
}
}
private:
struct Slot {
BMPFile bmp{};
int32_t x{};
int32_t y{};
int32_t z{};
uint16_t last_used{};
bool used{false};
};
uint16_t next_stamp() {
if (++stamp_ == 0) {
uint16_t v = 1;
for (auto& slot : slots_) {
if (slot.used) {
slot.last_used = v++;
}
}
stamp_ = v;
}
return stamp_;
}
std::array<Slot, SlotsCount> slots_{};
uint16_t stamp_{0};
};
class GeoPos : public View {
public:
enum alt_unit {
@@ -276,6 +374,7 @@ class GeoMap : public Widget {
bool hide_center_marker_{false};
GeoMapMode mode_{};
File map_file{};
BMPFileCache bmp_cache{};
bool map_opened{};
bool map_visible{};
uint16_t map_width{}, map_height{};
+150
View File
@@ -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) {
+54 -1
View File
@@ -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,
Color::yellow(),
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 -2
View File
@@ -157,8 +157,7 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
// call nav with flash
auto open_view = nav->push<ui::FlashUtilityView>();
chprintf(chp, "Flashing started\r\n");
chThdSleepMilliseconds(150); // to give display some time to paint the screen
open_view->wait_till_loaded(); // also wait for first frame sync
chThdSleepMilliseconds(150); // to give display some time to paint the screen
if (!open_view->flash_firmware(path.native())) {
chprintf(chp, "error\r\n");
}
+20 -5
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")
@@ -776,6 +782,7 @@ set(MODE_CPPSRC
${HACKRF_PATH}/firmware/common/usb_request.c
${HACKRF_PATH}/firmware/common/usb_standard_request.c
${HACKRF_PATH}/firmware/common/platform_detect.c
${HACKRF_PATH}/firmware/common/delay.c
${HACKRF_PATH}/firmware/common/platform_gpio.c
${HACKRF_PATH}/firmware/common/platform_scu.c
${HACKRF_PATH}/firmware/common/gpio_lpc.c
@@ -806,11 +813,19 @@ endif()
### HackRF "factory" firmware
# The RAM image (${HACKRF_FIRMWARE_BIN_IMAGE}) is produced by the hackrf
# "hackrf_usb_ram.bin" custom target (objcopy of the .elf). The previous
# DEPENDS used the bare filename ${HACKRF_FIRMWARE_BIN_FILENAME}, which CMake
# silently dropped, so the embed (hackrf.lz4/hackrf.img) was never regenerated
# when the hackrf firmware changed -> stale RAM image baked into the build.
# Depend on the full path (marked GENERATED so Make doesn't look for a file
# rule) AND on the producing custom target (for build ordering).
set_source_files_properties(${HACKRF_FIRMWARE_BIN_IMAGE} PROPERTIES GENERATED TRUE)
add_custom_command(
OUTPUT hackrf.img
COMMAND ${LZ4} -f -9 ${HACKRF_FIRMWARE_BIN_IMAGE} hackrf.lz4
COMMAND ${MAKE_IMAGE_CHUNK} hackrf.lz4 HRF1 hackrf.img
DEPENDS ${HACKRF_FIRMWARE_BIN_FILENAME} ${MAKE_IMAGE_CHUNK}
DEPENDS ${HACKRF_FIRMWARE_BIN_IMAGE} hackrf_usb_ram.bin ${MAKE_IMAGE_CHUNK}
VERBATIM
)
+4 -17
View File
@@ -22,25 +22,19 @@
#include "proc_ook.hpp"
#include "portapack_shared_memory.hpp"
#include "sine_table_int8.hpp"
#include "event_m4.hpp"
#include <cstdint>
inline void OOKProcessor::write_sample(const buffer_c8_t& buffer, uint8_t bit_value, size_t i) {
int8_t re, im;
if (bit_value) {
phase = (phase + 200); // What ?
sphase = phase + (64 << 18);
re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]);
im = (sine_table_i8[(phase & 0x03FC0000) >> 18]);
re = INT8_MAX;
im = 0;
} else {
re = 0;
im = 0;
}
buffer.p[i] = {re, im};
}
@@ -125,11 +119,8 @@ inline size_t OOKProcessor::duval_algo_step() {
void OOKProcessor::scan_process(const buffer_c8_t& buffer) {
size_t buf_ptr = 0;
// transmit any leftover bits from previous step
if (!scan_encode(buffer, buf_ptr))
return;
while (1) {
// Encode the sequence into required format. First call transmits any leftover bits from previous step
while (scan_encode(buffer, buf_ptr)) {
// calculate next chunk of deBruijn sequence
duval_length = duval_algo_step();
@@ -151,10 +142,6 @@ void OOKProcessor::scan_process(const buffer_c8_t& buffer) {
duval_bit = 0;
duval_sample_bit = 0;
duval_symbol = 0;
// encode the sequence into required format
if (!scan_encode(buffer, buf_ptr))
break;
}
}
-2
View File
@@ -46,8 +46,6 @@ class OOKProcessor : public BasebandProcessor {
uint16_t bit_pos{0};
uint8_t cur_bit{0};
uint32_t sample_count{0};
uint32_t tone_phase{0}, phase{0}, sphase{0};
int32_t tone_sample{0}, sig{0}, frq{0};
TXProgressMessage txprogress_message{};
+16 -2
View File
@@ -39,6 +39,7 @@
#include "platform_scu.h"
#include "fixed_point.h"
#include "clkin.h"
#include "cpu_clock.h"
#include "usb_type.h"
#include <libopencm3/lpc43xx/cgu.h>
#include <libopencm3/lpc43xx/ccu.h>
@@ -51,6 +52,10 @@
#include "gpio_lpc.h"
/* Referenced by the hackrf common delay.c. The Mayhem firmware already runs
* the CPU at 204MHz when this baseband image is started. */
unsigned int cpu_clock_mhz = 204;
/* GPIO Output PinMux */
static struct gpio gpio_led[] = {
GPIO(2, 1),
@@ -492,6 +497,9 @@ static void cpu_clock_pll1_max_speed(void) {
reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_IRC) | CGU_BASE_M4_CLK_AUTOBLOCK(1);
CGU_BASE_M4_CLK = reg_val;
/* CPU is now at 12MHz */
cpu_clock_mhz = 12;
/* 2. Enable the crystal oscillator. */
CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_ENABLE_MASK;
@@ -537,11 +545,17 @@ static void cpu_clock_pll1_max_speed(void) {
reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_PLL1);
CGU_BASE_M4_CLK = reg_val;
/* CPU is now at 102MHz */
cpu_clock_mhz = 102;
/* 9. Wait 50us. */
delay_us_at_mhz(50, 102);
/* 10. Set the PLL1 P-divider to direct output mode (DIRECT=1). */
CGU_PLL1_CTRL |= CGU_PLL1_CTRL_DIRECT_MASK;
/* CPU is now at 204MHz */
cpu_clock_mhz = 204;
}
/* clock startup for LPC4320 configure PLL1 to max speed (204MHz).
@@ -758,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
@@ -771,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)) {
+2 -1
View File
@@ -26,11 +26,12 @@
#include <libopencm3/lpc43xx/scu.h>
#include <libopencm3/lpc43xx/rgu.h>
#include <libopencm3/lpc43xx/wwdt.h>
#include "delay.h"
#include "string.h"
volatile bool usb_bulk_block_done = false;
void delay(uint32_t duration);
void usb_bulk_block_cb(void* user_data, unsigned int bytes_transferred) {
usb_bulk_block_done = true;
File diff suppressed because it is too large Load Diff
@@ -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;
}
+4
View File
@@ -105,6 +105,7 @@ bool BMPFile::open(const std::filesystem::path& file, bool readonly) {
if (!((bmp_header.signature == 0x4D42) &&
(bmp_header.planes == 1) &&
(bmp_header.compression == 0 || bmp_header.compression == 3))) {
bmpimage.close();
return false;
}
@@ -114,6 +115,7 @@ bool BMPFile::open(const std::filesystem::path& file, bool readonly) {
byte_per_px = 1;
// bmpimage.seek(sizeof(bmp_header_t));
// bmpimage.read(color_palette, 1024);
bmpimage.close();
return false; // niy
break;
@@ -121,6 +123,7 @@ bool BMPFile::open(const std::filesystem::path& file, bool readonly) {
byte_per_px = 2;
type = 5;
if (bmp_header.compression == 3) {
bmpimage.close();
return false;
} // niy
@@ -135,6 +138,7 @@ bool BMPFile::open(const std::filesystem::path& file, bool readonly) {
break;
default:
// not supported
bmpimage.close();
return false;
}
byte_per_row = (bmp_header.width * byte_per_px + 3) & ~3;
+16 -22
View File
@@ -23,6 +23,8 @@
#include "platform.hpp"
using namespace gpio_control;
namespace power_control {
/* VAA powers:
@@ -53,7 +55,7 @@ void vaa_power_on(void) {
}
/* Latch VAA to ON state (Active LOW) */
LPC_GPIO->CLR[4] = (1 << 1);
VAA_en.setActive();
#else
if (hackrf_r9) {
@@ -104,8 +106,8 @@ void vaa_power_on(void) {
}
/* Hold !VAA_ENABLE active using a GPIO, so we can reclaim and shut down the MOTOCONPWM peripheral. */
LPC_GPIO->CLR[2] = (1 << 9); // !VAA_ENABLE
LPC_GPIO->DIR[2] |= (1 << 9);
og_VAA_en.output();
og_VAA_en.setActive();
setup_pin(pin_setup_vaa_enablex_gpio_og); // P5_0 /GPIO2[ 9]/MCOB2: !VAA_ENABLE, 10K PU
peripheral_reset(&motocon_pwm_resources.reset);
@@ -122,16 +124,13 @@ void vaa_power_off(void) {
*/
#ifdef PRALINE
/* Safe state: OFF (VAA RF is active LOW, so Set = OFF) */
LPC_GPIO->SET[4] = (1 << 1);
/* Turn OFF LED3 (TX) */
LPC_GPIO->SET[2] = (1 << 8);
VAA_en.setInactive();
#else
if (hackrf_r9) {
LPC_GPIO->W3[6] = 1; // Turn OFF VAA for r9 P6_10
r9_VAA_en.setInactive(); // Turn OFF VAA for r9 P6_10
} else {
LPC_GPIO->W2[9] = 1; // Turn OFF VAA for OG P5_0
og_VAA_en.setInactive(); // Turn OFF VAA for OG P5_0
}
#endif
}
@@ -150,34 +149,29 @@ void aux_power_off(void) {
#endif /* PRALINE */
void core_power_on(void) { // Core power enable
void core_power_on(void) {
#ifdef PRALINE
// 1.2V FPGA - P8_7 = GPIO4[7], Active HIGH (Set = ON)
LPC_GPIO->SET[4] = (1 << 7);
en_1v2.setActive();
#else
if (hackrf_r9) {
// P5_0 (GPIO2[9]) is the EN1V8 pin
LPC_GPIO->SET[2] = (1 << 9);
r9_1v8_en.setActive();
} else {
// On older OG HackRF boards, P6_10 (GPIO3[6]) is the EN1V8 pin
LPC_GPIO->SET[3] = (1 << 6);
og_1v8_en.setActive();
}
#endif
}
void core_power_off(void) { // Core power disable
void core_power_off(void) {
#ifdef PRALINE
// 1.2V FPGA - P8_7 = GPIO4[7], Active HIGH (Clear = OFF) */
LPC_GPIO->CLR[4] = (1 << 7);
en_1v2.setInactive();
#else
if (hackrf_r9) {
// P5_0 (GPIO2[9]) is the EN1V8 pin
LPC_GPIO->CLR[2] = (1 << 9);
r9_1v8_en.setInactive();
} else {
// On older OG HackRF boards, P6_10 (GPIO3[6]) is the EN1V8 pin
LPC_GPIO->CLR[3] = (1 << 6);
og_1v8_en.setInactive();
}
#endif
}
+352 -52
View File
@@ -28,7 +28,7 @@
#include "ch.h"
#include "hal.h"
#include "pal.h"
#include "pal_lld.h"
struct PinConfig {
const uint32_t mode;
@@ -200,6 +200,12 @@ struct Pin {
uint8_t _pin_pad;
};
// Defines the logical polarity of the GPIO pin
enum class Polarity {
ActiveHigh, // High physical state means the function is ON (default)
ActiveLow // Low physical state means the function is ON (inverted)
};
struct GPIO {
// GPIO() = delete;
// GPIO(const GPIO& gpio) = delete;
@@ -209,22 +215,15 @@ struct GPIO {
const Pin& pin,
const ioportid_t gpio_port,
const iopadid_t gpio_pad,
const uint16_t gpio_mode)
const uint16_t gpio_mode,
const Polarity polarity = Polarity::ActiveHigh) // Default parameter ensures backward compatibility
: _pin{pin},
_gpio_port{gpio_port},
_gpio_pad{gpio_pad},
_gpio_mode{gpio_mode} {
_gpio_mode{gpio_mode},
_polarity{polarity} {
}
/*
constexpr GPIO(
const GPIO& gpio
) : _pin { gpio._pin },
_gpio_port { gpio._gpio_port },
_gpio_pad { gpio._gpio_pad },
_gpio_mode { gpio._gpio_mode }
{
}
*/
constexpr ioportid_t port() const {
return _gpio_port;
}
@@ -237,6 +236,11 @@ struct GPIO {
return _pin;
}
// Returns the configured polarity of the pin
constexpr Polarity polarity() const {
return _polarity;
}
void configure() const {
_pin.mode(_gpio_mode);
}
@@ -245,6 +249,8 @@ struct GPIO {
return _gpio_mode;
}
// Physical Level Operations
void set() const {
palSetPad(_gpio_port, _gpio_pad);
}
@@ -273,6 +279,39 @@ struct GPIO {
return palReadPad(_gpio_port, _gpio_pad);
}
// Turns the feature ON based on its polarity
void setActive() const {
if (_polarity == Polarity::ActiveHigh) {
set();
} else {
clear();
}
}
// Turns the feature OFF based on its polarity
void setInactive() const {
if (_polarity == Polarity::ActiveHigh) {
clear();
} else {
set();
}
}
// Sets the logical state of the feature
void setState(const bool active) const {
if (active) {
setActive();
} else {
setInactive();
}
}
// Returns true if the feature is logically active/enabled
bool isEnabled() const {
const bool physical_state = read();
return (_polarity == Polarity::ActiveHigh) ? physical_state : !physical_state;
}
bool operator!=(const GPIO& other) const {
return (port() != other.port()) || (pad() != other.pad());
}
@@ -281,6 +320,7 @@ struct GPIO {
const ioportid_t _gpio_port;
const iopadid_t _gpio_pad;
const uint16_t _gpio_mode;
const Polarity _polarity;
};
constexpr uint32_t boot_bit(const GPIO& pin, bool state) {
@@ -306,7 +346,6 @@ inline void setup_pins(const std::array<scu_setup_t, N>& pins_setup) {
}
}
#ifdef PRALINE
namespace gpio_control {
struct PinMap {
@@ -314,54 +353,325 @@ struct PinMap {
uint8_t scu_pin;
uint8_t gpio_port;
uint8_t gpio_pad;
uint8_t gpio_mode;
};
constexpr PinMap map_p1_ctrl0{2, 10, 0, 14}; // SCU: 2, 10 | GPIO: 0, 14
constexpr PinMap map_p1_ctrl1{6, 8, 5, 16};
constexpr PinMap map_p1_ctrl2{6, 9, 3, 5};
constexpr PinMap map_p2_ctrl0{8, 3, 7, 3};
constexpr PinMap map_p2_ctrl1{8, 4, 7, 4};
constexpr PinMap map_trigger_out{2, 6, 5, 6};
constexpr PinMap map_trigger_in{13, 12, 6, 26};
#ifdef PRALINE
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_rf5072_mix_en{9, 0, 4, 12};
constexpr PinMap map_p2_ctrl0{14, 3, 7, 3, 0};
constexpr PinMap map_p2_ctrl1{14, 4, 7, 4, 0};
constexpr PinMap map_aa_en{1, 14, 1, 7}; // Anti-Aliasing filter
constexpr PinMap map_trigger_out{2, 6, 5, 6, 4};
constexpr PinMap map_trigger_in{13, 12, 6, 26, 4};
constexpr PinMap map_clkin_ctrl{1, 20, 0, 15, 0};
constexpr PinMap map_rf5072_mix_en{9, 0, 4, 12, 0};
constexpr PinMap map_aa_en{1, 14, 1, 7, 0}; // Anti-Aliasing filter
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_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};
constexpr PinMap map_sgpio_9{4, 3, 2, 3, 7};
constexpr PinMap map_sgpio_10{1, 14, 1, 7, 6};
constexpr PinMap map_sgpio_13{4, 8, 5, 12, 4};
constexpr PinMap map_og_1v8_en{6, 10, 3, 6, 0};
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};
constexpr PinMap map_sgpio_1{0, 1, 0, 1, 3};
constexpr PinMap map_sgpio_2{1, 15, 0, 2, 2};
constexpr PinMap map_sgpio_3{1, 16, 0, 3, 2};
constexpr PinMap map_sgpio_5{6, 6, 0, 5, 2};
constexpr PinMap map_sgpio_6{2, 2, 5, 2, 0};
constexpr PinMap map_sgpio_7{1, 0, 0, 4, 6};
constexpr PinMap map_sgpio_11{1, 17, 0, 12, 6};
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
constexpr Pin pin_p1_ctrl0{map_p1_ctrl0.scu_port, map_p1_ctrl0.scu_pin}; // SCU: P2_10
constexpr GPIO p1_ctrl0{pin_p1_ctrl0, map_p1_ctrl0.gpio_port, map_p1_ctrl0.gpio_pad, PAL_MODE_OUTPUT_PUSHPULL}; // GPIO[0]14
constexpr Pin pin_p1_ctrl0{map_p1_ctrl0.scu_port, map_p1_ctrl0.scu_pin}; // SCU: P2_10
constexpr GPIO p1_ctrl0{pin_p1_ctrl0, map_p1_ctrl0.gpio_port, map_p1_ctrl0.gpio_pad, map_p1_ctrl0.gpio_mode}; // GPIO[0]14
constexpr Pin pin_p1_ctrl1{map_p1_ctrl1.scu_port, map_p1_ctrl1.scu_pin}; // SCU: P6_8
constexpr GPIO p1_ctrl1{pin_p1_ctrl1, map_p1_ctrl1.gpio_port, map_p1_ctrl1.gpio_pad, PAL_MODE_OUTPUT_PUSHPULL}; // GPIO[5]16
constexpr Pin pin_p1_ctrl1{map_p1_ctrl1.scu_port, map_p1_ctrl1.scu_pin}; // SCU: P6_8
constexpr GPIO p1_ctrl1{pin_p1_ctrl1, map_p1_ctrl1.gpio_port, map_p1_ctrl1.gpio_pad, map_p1_ctrl1.gpio_mode}; // GPIO[5]16
constexpr Pin pin_p1_ctrl2{map_p1_ctrl2.scu_port, map_p1_ctrl2.scu_pin}; // SCU: P6_9
constexpr GPIO p1_ctrl2{pin_p1_ctrl2, map_p1_ctrl2.gpio_port, map_p1_ctrl2.gpio_pad, PAL_MODE_OUTPUT_PUSHPULL}; // GPIO[3]5
constexpr Pin pin_p1_ctrl2{map_p1_ctrl2.scu_port, map_p1_ctrl2.scu_pin}; // SCU: P6_9
constexpr GPIO p1_ctrl2{pin_p1_ctrl2, map_p1_ctrl2.gpio_port, map_p1_ctrl2.gpio_pad, map_p1_ctrl2.gpio_mode}; // GPIO[3]5
// P2 Multiplexer control pins
constexpr Pin pin_p2_ctrl0{map_p2_ctrl0.scu_port, map_p2_ctrl0.scu_pin}; // SCU: PE_3
constexpr GPIO p2_ctrl0{pin_p2_ctrl0, map_p2_ctrl0.gpio_port, map_p2_ctrl0.gpio_pad, PAL_MODE_OUTPUT_PUSHPULL}; // GPIO[7]3
constexpr Pin pin_p2_ctrl0{map_p2_ctrl0.scu_port, map_p2_ctrl0.scu_pin}; // SCU: PE_3
constexpr GPIO p2_ctrl0{pin_p2_ctrl0, map_p2_ctrl0.gpio_port, map_p2_ctrl0.gpio_pad, map_p2_ctrl0.gpio_mode}; // GPIO[7]3
constexpr Pin pin_p2_ctrl1{map_p2_ctrl1.scu_port, map_p2_ctrl1.scu_pin}; // SCU: PE_4
constexpr GPIO p2_ctrl1{pin_p2_ctrl1, map_p2_ctrl1.gpio_port, map_p2_ctrl1.gpio_pad, PAL_MODE_OUTPUT_PUSHPULL}; // GPIO[7]4
constexpr Pin pin_p2_ctrl1{map_p2_ctrl1.scu_port, map_p2_ctrl1.scu_pin}; // SCU: PE_4
constexpr GPIO p2_ctrl1{pin_p2_ctrl1, map_p2_ctrl1.gpio_port, map_p2_ctrl1.gpio_pad, map_p2_ctrl1.gpio_mode}; // GPIO[7]4
// Trigger pins
constexpr Pin trigger_out_pin{map_trigger_out.scu_port, map_trigger_out.scu_pin}; // SCU: P2_6
constexpr GPIO trigger_out{trigger_out_pin, map_trigger_out.gpio_port, map_trigger_out.gpio_pad, PAL_MODE_OUTPUT_PUSHPULL}; // GPIO[5]6
constexpr Pin trigger_out_pin{map_trigger_out.scu_port, map_trigger_out.scu_pin}; // SCU: P2_6
constexpr GPIO trigger_out{trigger_out_pin, map_trigger_out.gpio_port, map_trigger_out.gpio_pad, map_trigger_out.gpio_mode}; // GPIO[5]6
constexpr Pin trigger_in_pin{map_trigger_in.scu_port, map_trigger_in.scu_pin}; // SCU: PD_12
constexpr GPIO trigger_in{trigger_in_pin, map_trigger_in.gpio_port, map_trigger_in.gpio_pad, PAL_MODE_INPUT}; // GPIO[6]26
constexpr Pin trigger_in_pin{map_trigger_in.scu_port, map_trigger_in.scu_pin}; // SCU: PD_12
constexpr GPIO trigger_in{trigger_in_pin, map_trigger_in.gpio_port, map_trigger_in.gpio_pad, map_trigger_in.gpio_mode}; // GPIO[6]26
// Clock input control
constexpr Pin pin_clkin_ctrl{map_clkin_ctrl.scu_port, map_clkin_ctrl.scu_pin}; // SCU: P1_20
constexpr GPIO clkin_ctrl{pin_clkin_ctrl, map_clkin_ctrl.gpio_port, map_clkin_ctrl.gpio_pad, map_clkin_ctrl.gpio_mode}; // GPIO[0]15
// RF507x
constexpr Pin pin_rf5072_mix_en{map_rf5072_mix_en.scu_port, map_rf5072_mix_en.scu_pin};
constexpr GPIO rf5072_mix_en{pin_rf5072_mix_en, map_rf5072_mix_en.gpio_port, map_rf5072_mix_en.gpio_pad, 0};
constexpr Pin pin_rf5072_mix_en{map_rf5072_mix_en.scu_port, map_rf5072_mix_en.scu_pin}; // SCU: P9_0
constexpr GPIO rf5072_mix_en{pin_rf5072_mix_en, map_rf5072_mix_en.gpio_port, map_rf5072_mix_en.gpio_pad, map_rf5072_mix_en.gpio_mode}; // GPIO[4]12
// Anti-Aliasing filter
constexpr Pin pin_aa_en{map_aa_en.scu_port, map_aa_en.scu_pin};
constexpr GPIO aa_en{pin_aa_en, map_aa_en.gpio_port, map_aa_en.gpio_pad, 0};
constexpr Pin pin_aa_en{map_aa_en.scu_port, map_aa_en.scu_pin}; // SCU: P1_14
constexpr GPIO aa_en{pin_aa_en, map_aa_en.gpio_port, map_aa_en.gpio_pad, map_aa_en.gpio_mode}; // GPIO[1]7
constexpr Pin pin_en_1v2{map_en_1v2.scu_port, map_en_1v2.scu_pin}; // SCU: P8_7
constexpr GPIO en_1v2{pin_en_1v2, map_en_1v2.gpio_port, map_en_1v2.gpio_pad, map_en_1v2.gpio_mode}; // GPIO[4]7
constexpr Pin pin_VAA_en{map_VAA_en.scu_port, map_VAA_en.scu_pin}; // SCU: P8_1
constexpr GPIO VAA_en{pin_VAA_en, map_VAA_en.gpio_port, map_VAA_en.gpio_pad, map_VAA_en.gpio_mode, Polarity::ActiveLow}; // GPIO[4]1
constexpr Pin pin_led_usb{map_led_usb.scu_port, map_led_usb.scu_pin}; // SCU: P4_1
constexpr GPIO led_usb{pin_led_usb, map_led_usb.gpio_port, map_led_usb.gpio_pad, map_led_usb.gpio_mode, Polarity::ActiveLow}; // GPIO[2]1
constexpr Pin pin_led_rx{map_led_rx.scu_port, map_led_rx.scu_pin}; // SCU: P4_2
constexpr GPIO led_rx{pin_led_rx, map_led_rx.gpio_port, map_led_rx.gpio_pad, map_led_rx.gpio_mode, Polarity::ActiveLow}; // GPIO[2]2
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, Polarity::ActiveLow}; // GPIO[2]8
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
constexpr Pin pin_og_1v8_en{map_og_1v8_en.scu_port, map_og_1v8_en.scu_pin}; // SCU: P6_10
constexpr GPIO og_1v8_en{pin_og_1v8_en, map_og_1v8_en.gpio_port, map_og_1v8_en.gpio_pad, map_og_1v8_en.gpio_mode}; // GPIO[3]6
constexpr Pin pin_r9_1v8_en{map_r9_1v8_en.scu_port, map_r9_1v8_en.scu_pin}; // SCU: P5_0
constexpr GPIO r9_1v8_en{pin_r9_1v8_en, map_r9_1v8_en.gpio_port, map_r9_1v8_en.gpio_pad, map_r9_1v8_en.gpio_mode}; // GPIO[2]9
constexpr Pin pin_og_VAA_en{map_og_VAA_en.scu_port, map_og_VAA_en.scu_pin}; // SCU: P5_0
constexpr GPIO og_VAA_en{pin_og_VAA_en, map_og_VAA_en.gpio_port, map_og_VAA_en.gpio_pad, map_og_VAA_en.gpio_mode, Polarity::ActiveLow}; // GPIO[2]9
constexpr Pin pin_r9_VAA_en{map_r9_VAA_en.scu_port, map_r9_VAA_en.scu_pin}; // SCU: P6_10
constexpr GPIO r9_VAA_en{pin_r9_VAA_en, map_r9_VAA_en.gpio_port, map_r9_VAA_en.gpio_pad, map_r9_VAA_en.gpio_mode, Polarity::ActiveLow}; // GPIO[3]6
constexpr Pin pin_led_usb{map_led_usb.scu_port, map_led_usb.scu_pin}; // SCU: P4_1
constexpr GPIO led_usb{pin_led_usb, map_led_usb.gpio_port, map_led_usb.gpio_pad, map_led_usb.gpio_mode}; // GPIO[2]1
constexpr Pin pin_led_rx{map_led_rx.scu_port, map_led_rx.scu_pin}; // SCU: P4_2
constexpr GPIO led_rx{pin_led_rx, map_led_rx.gpio_port, map_led_rx.gpio_pad, map_led_rx.gpio_mode}; // GPIO[2]2
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},
.reset = {.output_index = 38},
};
static const scu_setup_t pin_setup_vaa_enablex_pwm = {5, 0, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}};
static const scu_setup_t pin_setup_vaa_enablex_gpio_og = {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}};
static const scu_setup_t pin_setup_vaa_enablex_gpio_r9 = {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}};
#endif
// SGPIO
constexpr Pin pin_sgpio_0{map_sgpio_0.scu_port, map_sgpio_0.scu_pin}; // SCU: P0_0
constexpr GPIO sgpio_0{pin_sgpio_0, map_sgpio_0.gpio_port, map_sgpio_0.gpio_pad, map_sgpio_0.gpio_mode}; // GPIO[0]0
constexpr Pin pin_sgpio_1{map_sgpio_1.scu_port, map_sgpio_1.scu_pin}; // SCU: P0_1
constexpr GPIO sgpio_1{pin_sgpio_1, map_sgpio_1.gpio_port, map_sgpio_1.gpio_pad, map_sgpio_1.gpio_mode}; // GPIO[0]1
constexpr Pin pin_sgpio_2{map_sgpio_2.scu_port, map_sgpio_2.scu_pin}; // SCU: P1_15
constexpr GPIO sgpio_2{pin_sgpio_2, map_sgpio_2.gpio_port, map_sgpio_2.gpio_pad, map_sgpio_2.gpio_mode}; // GPIO[0]2
constexpr Pin pin_sgpio_3{map_sgpio_3.scu_port, map_sgpio_3.scu_pin}; // SCU: P1_16
constexpr GPIO sgpio_3{pin_sgpio_3, map_sgpio_3.gpio_port, map_sgpio_3.gpio_pad, map_sgpio_3.gpio_mode}; // GPIO[0]3
constexpr Pin pin_sgpio_4{map_sgpio_4.scu_port, map_sgpio_4.scu_pin}; // SCU: P6_3, PRALINE: P9_4
constexpr GPIO sgpio_4{pin_sgpio_4, map_sgpio_4.gpio_port, map_sgpio_4.gpio_pad, map_sgpio_4.gpio_mode}; // GPIO[6]3 PRALINE: GPIO[5]17
constexpr Pin pin_sgpio_5{map_sgpio_5.scu_port, map_sgpio_5.scu_pin}; // SCU: P6_6
constexpr GPIO sgpio_5{pin_sgpio_5, map_sgpio_5.gpio_port, map_sgpio_5.gpio_pad, map_sgpio_5.gpio_mode}; // GPIO[0]5
constexpr Pin pin_sgpio_6{map_sgpio_6.scu_port, map_sgpio_6.scu_pin}; // SCU: P2_2
constexpr GPIO sgpio_6{pin_sgpio_6, map_sgpio_6.gpio_port, map_sgpio_6.gpio_pad, map_sgpio_6.gpio_mode}; // GPIO[5]2
constexpr Pin pin_sgpio_7{map_sgpio_7.scu_port, map_sgpio_7.scu_pin}; // SCU: P1_0
constexpr GPIO sgpio_7{pin_sgpio_7, map_sgpio_7.gpio_port, map_sgpio_7.gpio_pad, map_sgpio_7.gpio_mode}; // GPIO[0]4
constexpr Pin pin_sgpio_8{map_sgpio_8.scu_port, map_sgpio_8.scu_pin}; // SCU: P9_6, PRALINE: P8_0
constexpr GPIO sgpio_8{pin_sgpio_8, map_sgpio_8.gpio_port, map_sgpio_8.gpio_pad, map_sgpio_8.gpio_mode}; // GPIO[4]11, PRALINE: GPIO[4]0
constexpr Pin pin_sgpio_9{map_sgpio_9.scu_port, map_sgpio_9.scu_pin}; // SCU: P4_3, PRALINE: P9_3
constexpr GPIO sgpio_9{pin_sgpio_9, map_sgpio_9.gpio_port, map_sgpio_9.gpio_pad, map_sgpio_9.gpio_mode}; // GPIO[2]3, PRALINE: GPIO[4]12
constexpr Pin pin_sgpio_10{map_sgpio_10.scu_port, map_sgpio_10.scu_pin}; // SCU: P1_14, PRALINE: P8_2
constexpr GPIO sgpio_10{pin_sgpio_10, map_sgpio_10.gpio_port, map_sgpio_10.gpio_pad, map_sgpio_10.gpio_mode}; // GPIO[1]7, PRALINE: GPIO[4]2
constexpr Pin pin_sgpio_11{map_sgpio_11.scu_port, map_sgpio_11.scu_pin}; // SCU: P1_17
constexpr GPIO sgpio_11{pin_sgpio_11, map_sgpio_11.gpio_port, map_sgpio_11.gpio_pad, map_sgpio_11.gpio_mode}; // GPIO[0]17
constexpr Pin pin_sgpio_12{map_sgpio_12.scu_port, map_sgpio_12.scu_pin}; // SCU: P1_18
constexpr GPIO sgpio_12{pin_sgpio_12, map_sgpio_12.gpio_port, map_sgpio_12.gpio_pad, map_sgpio_12.gpio_mode}; // GPIO[0]13
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
#endif
namespace power_control {
@@ -376,16 +686,6 @@ void aux_power_off(void);
void core_power_on(void);
void core_power_off(void);
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},
.reset = {.output_index = 38},
};
static const scu_setup_t pin_setup_vaa_enablex_pwm = {5, 0, scu_config_normal_drive_t{.mode = 1, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}};
static const scu_setup_t pin_setup_vaa_enablex_gpio_og = {5, 0, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}};
static const scu_setup_t pin_setup_vaa_enablex_gpio_r9 = {6, 10, scu_config_normal_drive_t{.mode = 0, .epd = 0, .epun = 1, .ehs = 0, .ezi = 0, .zif = 0}};
} // namespace power_control
#endif /*__GPIO_H__*/
+4 -85
View File
@@ -23,7 +23,6 @@
#define __HACKRF_GPIO_H__
#include "pins.hpp"
#include "led.hpp"
#include <array>
@@ -34,36 +33,6 @@ namespace one {
/* GPIO */
constexpr GPIO gpio_led_usb = gpio[GPIO2_1];
constexpr GPIO gpio_led_rx = gpio[GPIO2_2];
constexpr GPIO gpio_led_tx = gpio[GPIO2_8];
constexpr GPIO gpio_og_1v8_enable = gpio[GPIO3_6];
constexpr GPIO gpio_r9_1v8_enable = gpio[GPIO2_9];
constexpr GPIO gpio_vregmode = gpio[GPIO3_7];
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)
@@ -71,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
@@ -90,11 +58,11 @@ constexpr GPIO gpio_max283x_select = gpio[GPIO0_15];
#ifdef PRALINE
// PRALINE uses MAX2831 transceiver with different control pins
constexpr GPIO gpio_max283x_enable = gpio[GPIO7_1]; // MAX2831 ENABLE (PE_1)
constexpr GPIO gpio_max2831_enable = gpio[GPIO7_1]; // Alias
constexpr GPIO gpio_max283x_enable = gpio[GPIO7_1]; // MAX2831 ENABLE (PE_1)
// constexpr GPIO gpio_max2831_enable = gpio[GPIO7_1]; // Alias
constexpr GPIO gpio_max2831_rx_enable = gpio[GPIO7_2]; // MAX2831 RX_ENABLE (PE_2)
constexpr GPIO gpio_max2831_rxhp = gpio[GPIO6_29]; // MAX2831 RXHP (PD_15)
constexpr GPIO gpio_max2831_ld = gpio[GPIO4_11]; // MAX2831 Lock Detect (P9_6)
// constexpr GPIO gpio_max2831_rxhp = gpio[GPIO6_29]; // MAX2831 RXHP (PD_15)
constexpr GPIO gpio_max2831_ld = gpio[GPIO4_11]; // MAX2831 Lock Detect (P9_6)
// Legacy aliases for code compatibility
constexpr GPIO gpio_max2837_rxenable = gpio[GPIO7_2];
constexpr GPIO gpio_max2837_txenable = gpio[GPIO7_2]; // MAX2831 uses single RX/TX control
@@ -108,50 +76,12 @@ constexpr GPIO gpio_max2839_rxtx = gpio[GPIO2_5];
#ifdef PRALINE
constexpr GPIO gpio_max5864_select = gpio[GPIO6_30]; // PD_16: PRALINE MAX5864 CS
constexpr GPIO gpio_fpga_select = gpio[GPIO2_10]; // FPGA SPI CS (P5_1)
#else
constexpr GPIO gpio_max5864_select = gpio[GPIO2_7];
#endif
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)
constexpr GPIO gpio_1v2_enable = gpio[GPIO4_7]; // 1V2 enable (P8_7)
constexpr GPIO gpio_3v3aux_disable = gpio[GPIO5_15]; // 3V3 aux disable (P6_7)
constexpr GPIO gpio_3v3aux_oc = gpio[GPIO1_11]; // 3.3V aux overcurrent input (P2_11)
constexpr GPIO gpio_vbus_enable = gpio[GPIO4_4]; // VBUS_IN_EN P8_4 ->LOW
constexpr GPIO gpio_vin_enable = gpio[GPIO4_5]; // VIN_IN_EN P8_5 ->HIGH
// PRALINE RF path control
constexpr GPIO gpio_tx_enable = gpio[GPIO3_4]; // TX enable (P6_5)
// constexpr GPIO gpio_mix_enable_n = gpio[GPIO3_2]; // Mixer enable inverted (P6_3)
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_bias_oc = gpio[GPIO3_7]; // Bias tee overcurrent input (P6_11)
// PRALINE mixer lock detect (gpio_max2831_ld defined above at line 93)
constexpr GPIO gpio_rffc5072_ld = gpio[GPIO6_25]; // Mixer lock detect (PD_11)
// PRALINE LED4
constexpr GPIO gpio_led4 = gpio[GPIO4_6]; // LED4 (P8_6)
// PRALINE dual port control
constexpr GPIO gpio_p1_ctrl0 = gpio[GPIO0_14]; // P1 control 0 (P2_10)
constexpr GPIO gpio_p1_ctrl1 = gpio[GPIO5_16]; // P1 control 1 (P6_8)
constexpr GPIO gpio_p1_ctrl2 = gpio[GPIO3_5]; // P1 control 2 (P6_9)
constexpr GPIO gpio_p2_ctrl0 = gpio[GPIO7_3]; // P2 control 0 (PE_3)
constexpr GPIO gpio_p2_ctrl1 = gpio[GPIO7_4]; // P2 control 1 (PE_4)
constexpr GPIO gpio_clkin_ctrl = gpio[GPIO0_15]; // CLKIN control (P1_20)
// PRALINE trigger/sync I/O
constexpr GPIO gpio_trigger_in = gpio[GPIO6_26]; // Trigger input (PD_12)
constexpr GPIO gpio_trigger_out = gpio[GPIO5_6]; // Trigger output (P2_6)
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
@@ -165,17 +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];
/* LEDs */
constexpr LED led_usb{gpio_led_usb};
constexpr LED led_rx{gpio_led_rx};
constexpr LED led_tx{gpio_led_tx};
} /* 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 */
-75
View File
@@ -1,75 +0,0 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __LED_H__
#define __LED_H__
#include "gpio.hpp"
struct LED {
constexpr LED(const GPIO gpio)
: _gpio{gpio} {
}
void setup() const {
#ifdef PRALINE
// No-op: GPIO direction and initial OFF state are already
// enforced globally at boot by pal_default_config in board.cpp.
#else
_gpio.clear();
_gpio.output();
_gpio.configure();
#endif
}
void on() const {
#ifdef PRALINE
_gpio.clear(); /* LOW = ON for PRALINE */
#else
_gpio.set();
#endif
}
void off() const {
#ifdef PRALINE
_gpio.set(); /* HIGH = OFF for PRALINE */
#else
_gpio.clear();
#endif
}
void toggle() const {
_gpio.toggle();
}
void write(const bool value) const {
#ifdef PRALINE
_gpio.write(!value); /* Invert for PRALINE */
#else
_gpio.write(value);
#endif
}
private:
const GPIO _gpio;
};
#endif /*__LED_H__*/
+2 -2
View File
@@ -23,9 +23,9 @@
#ifndef __SINE_TABLE_I8_H__
#define __SINE_TABLE_I8_H__
#include <cmath>
#include <cstdint>
static const int8_t sine_table_i8[256] = {
static constexpr int8_t sine_table_i8[256] = {
0, 2, 5, 8, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45,
48, 51, 54, 57, 59, 62, 65, 67, 70, 73, 75, 78, 80, 83, 85, 87,
90, 92, 94, 96, 98, 100, 102, 104, 105, 107, 109, 110, 112, 113, 115, 116,
+16 -16
View File
@@ -26,6 +26,9 @@
namespace ui {
using Coord = int16_t;
using Dim = int16_t;
// Positioning helpers PER CHARACTERS (8*16)
// EACH parameters must be used az CHAR position, not PX coordinates. So If you wanna use the 8,16 coordinates (that is the second character in X and second character in Y you must use UI_POS_X(1) UI_POS_Y(1) (since we count from 0)
@@ -36,31 +39,31 @@ namespace ui {
// default font width
#define UI_POS_DEFAULT_WIDTH 8
// px position of the linenum-th character (Y)
#define UI_POS_Y(linenum) ((int)((linenum) * UI_POS_DEFAULT_HEIGHT))
#define UI_POS_Y(linenum) ((Coord)((linenum) * UI_POS_DEFAULT_HEIGHT))
// px position of the linenum-th character from the bottom of the screen (Y) (please calculate the +1 line top-bar to it too if that is visible!)
#define UI_POS_Y_BOTTOM(linenum) ((int)(screen_height - (linenum) * UI_POS_DEFAULT_HEIGHT))
#define UI_POS_Y_BOTTOM(linenum) ((Coord)(screen_height - (int)((linenum) * UI_POS_DEFAULT_HEIGHT)))
// px position of the linenum-th character from the left of the screen (X)
#define UI_POS_X(charnum) ((int)((charnum) * UI_POS_DEFAULT_WIDTH))
#define UI_POS_X(charnum) ((Coord)((charnum) * UI_POS_DEFAULT_WIDTH))
// px position of the linenum-th character from the right of the screen (X)
#define UI_POS_X_RIGHT(charnum) ((int)(screen_width - ((charnum) * UI_POS_DEFAULT_WIDTH)))
#define UI_POS_X_RIGHT(charnum) ((Coord)(screen_width - (int)((charnum) * UI_POS_DEFAULT_WIDTH)))
// px position of the left character from the center of the screen (X) (for N character wide string)
#define UI_POS_X_CENTER(charnum) ((int)((screen_width / 2) - ((charnum) * UI_POS_DEFAULT_WIDTH / 2)))
// px position of the currcol in a table with colnum number of columns, where one coloumn is charnum characters wide maximum
#define UI_POS_X_TABLE(colnum, currcol) ((currcol) * (screen_width / (colnum)))
#define UI_POS_X_CENTER(charnum) ((Coord)((screen_width / 2) - (int)((charnum) * UI_POS_DEFAULT_WIDTH / 2)))
// px position of the currcol in a table with colnum number of columns, where one column is charnum characters wide maximum
#define UI_POS_X_TABLE(colnum, currcol) ((Coord)((currcol) * (screen_width / (colnum))))
// px width of N characters
#define UI_POS_WIDTH(charnum) ((int)((charnum) * UI_POS_DEFAULT_WIDTH))
#define UI_POS_WIDTH(charnum) ((Dim)((charnum) * UI_POS_DEFAULT_WIDTH))
// px width of the screen
#define UI_POS_MAXWIDTH (screen_width)
// px height of N line
#define UI_POS_HEIGHT(linecount) ((int)((linecount) * UI_POS_DEFAULT_HEIGHT))
#define UI_POS_HEIGHT(linecount) ((Dim)((linecount) * UI_POS_DEFAULT_HEIGHT))
// px height of the screen's percent
#define UI_POS_HEIGHT_PERCENT(percent) ((int)(screen_height * (percent) / 100))
#define UI_POS_HEIGHT_PERCENT(percent) ((Dim)(screen_height * (percent) / 100))
// remaining px from the linenum-th line to the bottom of the screen. (please calculate the +1 line top-bar to it too if that is visible!)
#define UI_POS_HEIGHT_REMAINING(linenum) ((int)(screen_height - ((linenum) * UI_POS_DEFAULT_HEIGHT)))
#define UI_POS_HEIGHT_REMAINING(linenum) ((Dim)(screen_height - (int)((linenum) * UI_POS_DEFAULT_HEIGHT)))
// remaining px from the charnum-th character to the right of the screen
#define UI_POS_WIDTH_REMAINING(charnum) ((int)(screen_width - ((charnum) * UI_POS_DEFAULT_WIDTH)))
#define UI_POS_WIDTH_REMAINING(charnum) ((Dim)(screen_width - (int)((charnum) * UI_POS_DEFAULT_WIDTH)))
// px width of the screen's percent
#define UI_POS_WIDTH_PERCENT(percent) ((int)(screen_width * (percent) / 100))
#define UI_POS_WIDTH_PERCENT(percent) ((Dim)(screen_width * (percent) / 100))
// px width of the screen
#define UI_POS_MAXHEIGHT (screen_height)
@@ -85,9 +88,6 @@ namespace ui {
#define DEG_TO_RAD(d) (d * (2 * pi) / 360.0)
using Coord = int16_t;
using Dim = int16_t;
extern uint16_t screen_width;
extern uint16_t screen_height;
+5 -1
View File
@@ -206,7 +206,11 @@ void Widget::set_style(const Style* new_style) {
}
const Style& Widget::style() const {
return style_ ? *style_ : parent()->style();
for (const Widget* curr = this; curr; curr = curr->parent()) {
if (curr->style_) return *curr->style_;
}
// default style should always be set by SystemView
return *style_;
}
void Widget::drawn(bool v) {
+4 -8
View File
@@ -201,15 +201,11 @@ void Widget::set_style(const Style* new_style) {
}
const Style& Widget::style() const {
if (style_ != nullptr)
return *style_;
else {
auto p = parent();
if (p == nullptr)
// TODO: debug
while (true);
return p->style();
for (const Widget* curr = this; curr; curr = curr->parent()) {
if (curr->style_) return *curr->style_;
}
// default style should always be set by SystemView
return *style_;
}
void Widget::drawn(bool v) {
+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 = 0xAE060000
external_apps_address_end = 0xAE090000
+1 -1
Submodule hackrf updated: 216949a844...d371476c03
Executable
+647
View File
@@ -0,0 +1,647 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
DEFAULT_TOOLCHAIN_URL="https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/RC2.1/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=6e63531f-8cb1-40b9-bbfc-8a57cdfc01b4&la=en&hash=F761343D43A0587E8AC0925B723C04DBFB848339"
mbt_log() {
printf "mbt: %s\n" "$*"
}
mbt_error() {
printf "mbt: error: %s\n" "$*" >&2
}
mbt_die() {
mbt_error "$*"
exit 1
}
mbt_command_exists() {
command -v "$1" >/dev/null 2>&1
}
mbt_default_jobs() {
local jobs
jobs="$(getconf _NPROCESSORS_ONLN 2>/dev/null || true)"
if [[ -z "$jobs" ]] && mbt_command_exists nproc; then
jobs="$(nproc)"
fi
if [[ -z "$jobs" ]]; then
jobs=1
fi
printf "%s" "$jobs"
}
mbt_detect_distro() {
if mbt_command_exists apt-get; then
printf "debian"
return 0
fi
if mbt_command_exists pacman; then
printf "arch"
return 0
fi
printf "unknown"
}
mbt_run_privileged() {
if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then
"$@"
return
fi
if ! mbt_command_exists sudo; then
mbt_die "sudo is required for dependency installation. Re-run as root or install sudo."
fi
sudo "$@"
}
mbt_install_deps() {
local distro
distro="$(mbt_detect_distro)"
case "$distro" in
debian)
mbt_log "detected Debian-like environment; installing required packages."
mbt_run_privileged apt-get update
mbt_run_privileged apt-get install -y --no-install-recommends \
git tar wget dfu-util cmake make python3 bzip2 lz4 curl hackrf \
python3-setuptools python3-yaml binutils python3-setuptools ccache ninja-build
;;
arch)
mbt_log "detected Arch-like environment; installing required packages."
mbt_run_privileged pacman -Sy --needed --noconfirm \
git tar wget dfu-util cmake make python3 bzip2 lz4 curl hackrf \
python-distutils-extra python-setuptools python-pip python-yaml \
ninja ccache binutils
;;
*)
mbt_die "unsupported distribution. Install dependencies manually (see wiki on github), or you can send PR to support for your distro. Detected distro: $distro"
;;
esac
}
mbt_sync_submodules() {
local jobs
jobs="$(mbt_default_jobs)"
mbt_log "syncing submodules."
git -C "$SCRIPT_PATH" submodule update --init --recursive --jobs "$((jobs * 2))"
}
mbt_enable_toolchain_env() {
export MBT_ROOT="$SCRIPT_PATH"
export MBT_TOOLCHAIN_DIR="${MBT_TOOLCHAIN_DIR:-$SCRIPT_PATH/armbin}"
export MBT_TOOLCHAIN_URL="${MBT_TOOLCHAIN_URL:-$DEFAULT_TOOLCHAIN_URL}"
export MBT_EXPECTED_GCC_VERSION="${MBT_EXPECTED_GCC_VERSION:-9.2.1}"
if [[ -z "${MBT_FORCE_TOOLCHAIN+x}" ]]; then
MBT_FORCE_TOOLCHAIN="${FORCE_TOOLCHAIN:-0}"
fi
export MBT_FORCE_TOOLCHAIN
if [[ -z "${MBT_NO_TOOLCHAIN_DOWNLOAD+x}" ]]; then
MBT_NO_TOOLCHAIN_DOWNLOAD="${NO_TOOLCHAIN_DOWNLOAD:-0}"
fi
export MBT_NO_TOOLCHAIN_DOWNLOAD
export MBT_VERBOSE="${MBT_VERBOSE:-}"
# shellcheck source=/dev/null
. "$SCRIPT_PATH/scripts/toolchain/mbtenv.sh"
}
mbt_use_existing_toolchain_env() {
local repo_toolchain_bin="$SCRIPT_PATH/armbin/bin"
if [[ ! -d "$repo_toolchain_bin" ]]; then
return 0
fi
case ":$PATH:" in
*":$repo_toolchain_bin:"*)
return 0
;;
esac
export PATH="$repo_toolchain_bin:$PATH"
}
mbt_normalize_device() {
local raw
raw="$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]')"
case "$raw" in
1|one|hackrf|hackrf-one)
printf "hackrf-one"
;;
2|pro|hackrf-pro|praline)
printf "hackrf-pro"
;;
3|portarf|porta-rf|porta_rf)
printf "portarf"
;;
*)
return 1
;;
esac
}
mbt_prompt_device() {
local choice normalized
cat >&2 <<'EOF'
Select device type:
1) HackRF One
2) HackRF Pro (PRALINE)
3) PortaRF
EOF
while true; do
printf "Device [1-3]: " >&2
if ! IFS= read -r choice; then
return 1
fi
normalized="$(mbt_normalize_device "$choice" 2>/dev/null || true)"
if [[ -n "$normalized" ]]; then
printf "%s" "$normalized"
return 0
fi
mbt_error "invalid device selection: $choice"
done
}
mbt_print_help() {
cat <<'EOF'
Usage:
./mbt One-step build flow (installs deps, syncs submodules, downloads toolchain, asks for device, builds)
./mbt build [options] Developer build flow
./mbt deps Install distro dependencies only
./mbt toolchain [--force-toolchain]
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
Build options:
-d, --device <name> Device: hackrf-one | hackrf-pro | portarf
--ccache / --no-ccache Toggle USE_CCACHE (default: on)
--with-deps / --no-deps Install Linux dependencies before build
--with-submodule-sync Update git submodules before build (default)
--no-submodule-sync Skip submodule sync
--with-toolchain Ensure toolchain exists and matches expected version (default)
--no-toolchain Skip toolchain checks/downloads and use existing PATH (prepends ./armbin/bin if present)
--force-toolchain Re-download and reinstall toolchain
-B, --build-dir <path> Build directory (default: ./build)
-G, --generator <name> CMake generator (default: Ninja)
-j, --jobs <n> Build parallelism (default: host CPU count)
--target <name> Build a specific target
--clean Remove build directory, run libopencm3 clean, and exit
--cmake-arg <arg> Extra CMake configure arg (repeatable)
--build-arg <arg> Extra cmake --build arg (repeatable)
-y, --non-interactive Fail instead of prompting when required input is missing
-h, --help Show this help
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
EOF
}
mbt_doctor() {
local distro missing=0 toolchain_cc toolchain_version cmd
local -a required_cmds
distro="$(mbt_detect_distro)"
mbt_log "doctor report for $SCRIPT_PATH"
mbt_log "distro family: $distro"
required_cmds=(git tar wget dfu-util cmake python3 bzip2 lz4 curl ccache ninja)
if [[ "$distro" == "arch" ]]; then
required_cmds+=(make)
fi
for cmd in "${required_cmds[@]}"; do
if mbt_command_exists "$cmd"; then
printf " [ok] %s\n" "$cmd"
else
printf " [missing] %s\n" "$cmd"
missing=1
fi
done
toolchain_cc="$SCRIPT_PATH/armbin/bin/arm-none-eabi-gcc"
if [[ -x "$toolchain_cc" ]]; then
toolchain_version="$("$toolchain_cc" -dumpfullversion 2>/dev/null || "$toolchain_cc" -dumpversion 2>/dev/null || true)"
printf " [ok] arm-none-eabi-gcc %s (%s)\n" "$toolchain_version" "$toolchain_cc"
if [[ "$toolchain_version" != "9.2.1" ]]; then
printf " [warn] expected arm-none-eabi-gcc 9.2.1\n"
fi
else
if mbt_command_exists arm-none-eabi-gcc; then
toolchain_cc="arm-none-eabi-gcc"
toolchain_version="$("$toolchain_cc" -dumpfullversion 2>/dev/null || "$toolchain_cc" -dumpversion 2>/dev/null || true)"
printf " [ok] arm-none-eabi-gcc %s (%s)\n" "$toolchain_version" "$toolchain_cc"
if [[ "$toolchain_version" != "9.2.1" ]]; then
printf " [warn] expected arm-none-eabi-gcc 9.2.1\n"
fi
else
printf " [missing] %s\n" "$toolchain_cc"
missing=1
fi
fi
if git -C "$SCRIPT_PATH" submodule status --recursive | grep -q '^-'; then
printf " [warn] some submodules are not initialized (run ./mbt build --with-submodule-sync --device hackrf-one)\n"
else
printf " [ok] submodules initialized\n"
fi
if [[ "$missing" -ne 0 ]]; then
mbt_error "doctor found missing prerequisites."
return 1
fi
mbt_log "doctor passed."
return 0
}
mbt_validate_jobs() {
[[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -gt 0 ]]
}
mbt_detect_cached_device() {
local build_dir="$1"
local cache_file="$build_dir/CMakeCache.txt"
local board flash_limit
if [[ ! -f "$cache_file" ]]; then
return 1
fi
board="$(grep -E '^BOARD:' "$cache_file" | head -n 1 | cut -d= -f2- || true)"
flash_limit="$(grep -E '^FLASH_MB_LIMIT_SIZE:' "$cache_file" | head -n 1 | cut -d= -f2- || true)"
if [[ "$board" == "PRALINE" ]]; then
printf "hackrf-pro"
return 0
fi
case "$flash_limit" in
2|2.0)
printf "portarf"
return 0
;;
1|1.0)
printf "hackrf-one"
return 0
;;
esac
printf "unknown"
return 0
}
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
mbt_error "failed to delete build directory. Try running ./mbt --clean as root."
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_report_build_error() {
printf '%s\n' 'Build error happened. Try to fix your code, or run ./mbt --clean and try again' >&2
}
mbt_build() {
local build_dir="$1"
local generator="$2"
local use_ccache="$3"
local device="$4"
local jobs="$5"
local target="$6"
local clean_build="$7"
shift 7
if [[ "$#" -lt 2 ]]; then
mbt_die "internal error: mbt_build expected extra arg array names"
fi
local -n cmake_extra_args_ref="$1"
local -a cmake_extra_args=("${cmake_extra_args_ref[@]}")
shift
local -n build_extra_args_ref="$1"
local -a build_extra_args=("${build_extra_args_ref[@]}")
local -a cmake_args build_args device_args
local marker_file="$build_dir/.mbt_device"
local previous_device=""
if [[ "$clean_build" -eq 1 ]]; then
mbt_clean "$build_dir"
return 0
else
if [[ -f "$marker_file" ]]; then
previous_device="$(<"$marker_file")"
elif [[ -f "$build_dir/CMakeCache.txt" ]]; then
previous_device="$(mbt_detect_cached_device "$build_dir" 2>/dev/null || true)"
fi
if [[ -n "$previous_device" ]] && [[ "$previous_device" != "$device" ]]; then
mbt_log "device changed ($previous_device -> $device), cleaning build directory: $build_dir"
rm -rf "$build_dir"
fi
fi
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
if [[ "$generator" == "Ninja" ]] && ! mbt_command_exists ninja; then
if mbt_command_exists make; then
mbt_log "ninja not found; falling back to Unix Makefiles."
generator="Unix Makefiles"
else
mbt_die "ninja not found and make is unavailable. Install dependencies or set a working generator."
fi
fi
cmake_args=(
-S "$SCRIPT_PATH"
-B "$build_dir"
-G "$generator"
"-DUSE_CCACHE=$use_ccache"
)
cmake_args+=("${device_args[@]}")
cmake_args+=("${cmake_extra_args[@]}")
mbt_log "configuring build for $device in $build_dir"
if ! cmake "${cmake_args[@]}"; then
mbt_report_build_error
return 1
fi
printf "%s\n" "$device" > "$marker_file"
build_args=(--build "$build_dir" --parallel "$jobs")
if [[ -n "$target" ]]; then
build_args+=(--target "$target")
fi
build_args+=("${build_extra_args[@]}")
mbt_log "building..."
if ! cmake "${build_args[@]}"; then
mbt_report_build_error
return 1
fi
mbt_log "build complete. Outputs are in $build_dir."
mbt_log "The bare binary (for the hackrf_spiflash host tool), excluding external apps, is located at $build_dir/firmware/portapack-mayhem-firmware.bin"
mbt_log "The full tar.gz package (for MayhemHub and the Flash Utility OTA app), including external apps and the firmware file, is located at $build_dir/firmware/portapack-mayhem_OCI.ppfw.tar"
mbt_log "If you have the dev's SD card switcher hardware, the fast flash script is at $build_dir/flash.py"
# have to hard code the name here cuz it's too expensive to read cmake for just a file name.
}
main() {
local command="build"
local quickstart=0
local device=""
local use_ccache="ON"
local with_deps=0
local with_submodule_sync=1
local with_toolchain=1
local build_dir="$SCRIPT_PATH/build"
local generator="Ninja"
local jobs
local target=""
local clean_build=0
local non_interactive=0
local -a cmake_extra_args=()
local -a build_extra_args=()
FORCE_TOOLCHAIN=0
jobs="$(mbt_default_jobs)"
if [[ "$#" -eq 0 ]]; then
quickstart=1
with_deps=1
fi
if [[ "$#" -gt 0 ]]; then
case "$1" in
build|deps|toolchain|doctor|shell)
command="$1"
shift
;;
-h|--help|help)
mbt_print_help
exit 0
;;
esac
fi
while [[ "$#" -gt 0 ]]; do
case "$1" in
-d|--device)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
device="$2"
shift 2
;;
--ccache)
use_ccache="ON"
shift
;;
--no-ccache)
use_ccache="OFF"
shift
;;
--with-deps)
with_deps=1
shift
;;
--no-deps)
with_deps=0
shift
;;
--with-submodule-sync)
with_submodule_sync=1
shift
;;
--no-submodule-sync)
with_submodule_sync=0
shift
;;
--with-toolchain)
with_toolchain=1
shift
;;
--no-toolchain)
with_toolchain=0
shift
;;
--force-toolchain)
FORCE_TOOLCHAIN=1
with_toolchain=1
shift
;;
-B|--build-dir)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
build_dir="$2"
shift 2
;;
-G|--generator)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
generator="$2"
shift 2
;;
-j|--jobs)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
jobs="$2"
shift 2
;;
--target)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
target="$2"
shift 2
;;
--clean)
clean_build=1
shift
;;
--cmake-arg)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
cmake_extra_args+=("$2")
shift 2
;;
--cmake-arg=*)
cmake_extra_args+=("${1#*=}")
shift
;;
--build-arg)
[[ "$#" -ge 2 ]] || mbt_die "$1 requires a value"
build_extra_args+=("$2")
shift 2
;;
--build-arg=*)
build_extra_args+=("${1#*=}")
shift
;;
-y|--non-interactive)
non_interactive=1
shift
;;
-h|--help)
mbt_print_help
exit 0
;;
*)
mbt_die "unknown option: $1"
;;
esac
done
if ! mbt_validate_jobs "$jobs"; then
mbt_die "invalid jobs value: $jobs"
fi
case "$command" in
deps)
mbt_install_deps
exit 0
;;
toolchain)
NO_TOOLCHAIN_DOWNLOAD=0
mbt_enable_toolchain_env
mbt_log "toolchain ready: $SCRIPT_PATH/armbin"
exit 0
;;
doctor)
mbt_doctor
exit $?
;;
shell)
if [[ "$with_toolchain" -eq 1 ]]; then
mbt_enable_toolchain_env
else
mbt_use_existing_toolchain_env
fi
mbt_log "spawning shell with toolchain exported in this session. Use 'exit' to leave."
exec "${SHELL:-/bin/bash}" -i
;;
build)
;;
*)
mbt_die "unsupported command: $command"
;;
esac
if [[ "$clean_build" -eq 1 ]]; then
mbt_clean "$build_dir"
exit 0
fi
if [[ "$with_deps" -eq 1 ]]; then
mbt_install_deps
fi
if [[ "$with_submodule_sync" -eq 1 ]]; then
mbt_sync_submodules
fi
if [[ "$with_toolchain" -eq 1 ]]; then
mbt_enable_toolchain_env
else
mbt_use_existing_toolchain_env
fi
if [[ -n "$device" ]]; then
device="$(mbt_normalize_device "$device" 2>/dev/null || true)"
fi
if [[ -z "$device" ]]; then
if [[ "$non_interactive" -eq 1 ]]; then
mbt_die "--device is required when --non-interactive is set"
fi
if [[ ! -t 0 ]]; then
mbt_die "missing --device and no interactive terminal available"
fi
device="$(mbt_prompt_device)" || mbt_die "failed to read device selection"
fi
if [[ "$quickstart" -eq 1 ]]; then
mbt_log "quickstart mode enabled."
fi
mbt_build "$build_dir" "$generator" "$use_ccache" "$device" "$jobs" "$target" "$clean_build" \
cmake_extra_args[@] build_extra_args[@]
}
main "$@"
+209
View File
@@ -0,0 +1,209 @@
#!/bin/sh
# shellcheck disable=SC2039
MBT_ROOT="${MBT_ROOT:-$(pwd -P)}"
MBT_VERBOSE="${MBT_VERBOSE:-}"
MBT_TOOLCHAIN_DIR="${MBT_TOOLCHAIN_DIR:-$MBT_ROOT/armbin}"
MBT_TOOLCHAIN_URL="${MBT_TOOLCHAIN_URL:-https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/RC2.1/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=6e63531f-8cb1-40b9-bbfc-8a57cdfc01b4&la=en&hash=F761343D43A0587E8AC0925B723C04DBFB848339}"
MBT_EXPECTED_GCC_VERSION="${MBT_EXPECTED_GCC_VERSION:-9.2.1}"
MBT_FORCE_TOOLCHAIN="${MBT_FORCE_TOOLCHAIN:-0}"
MBT_NO_TOOLCHAIN_DOWNLOAD="${MBT_NO_TOOLCHAIN_DOWNLOAD:-0}"
MBT_TOOLCHAIN_BIN="$MBT_TOOLCHAIN_DIR/bin"
MBT_TOOLCHAIN_CC="$MBT_TOOLCHAIN_BIN/arm-none-eabi-gcc"
MBT_TMP_ARCHIVE=""
MBT_DOWNLOADER=""
mbtenv_log() {
printf "mbt: %s\n" "$*"
}
mbtenv_verbose() {
if [ -n "$MBT_VERBOSE" ]; then
mbtenv_log "$*"
fi
}
mbtenv_command_exists() {
command -v "$1" >/dev/null 2>&1
}
mbtenv_cleanup_tmp() {
if [ -n "$MBT_TMP_ARCHIVE" ] && [ -f "$MBT_TMP_ARCHIVE" ]; then
rm -f "$MBT_TMP_ARCHIVE"
fi
}
mbtenv_check_linux() {
if [ "$(uname -s)" != "Linux" ]; then
mbtenv_log "error: mbt currently supports Linux only."
return 1
fi
arch="$(uname -m)"
case "$arch" in
x86_64|amd64)
# Supported architecture for the default x86_64 Linux toolchain archive.
;;
*)
mbtenv_log "error: unsupported architecture '$arch'. mbt currently supports x86_64 Linux only for the bundled toolchain."
return 1
;;
esac
return 0
}
mbtenv_choose_downloader() {
if mbtenv_command_exists curl; then
MBT_DOWNLOADER="curl"
return 0
fi
if mbtenv_command_exists wget; then
MBT_DOWNLOADER="wget"
return 0
fi
mbtenv_log "error: neither curl nor wget found in PATH."
return 1
}
mbtenv_download_archive() {
if [ "$MBT_DOWNLOADER" = "curl" ]; then
curl --fail --location --progress-bar --output "$1" "$2"
return $?
fi
wget --show-progress --progress=bar:force -O "$1" "$2"
}
mbtenv_get_installed_version() {
if [ ! -x "$MBT_TOOLCHAIN_CC" ]; then
return 1
fi
"$MBT_TOOLCHAIN_CC" -dumpfullversion 2>/dev/null || "$MBT_TOOLCHAIN_CC" -dumpversion 2>/dev/null
}
mbtenv_toolchain_is_expected() {
INSTALLED_VERSION="$(mbtenv_get_installed_version 2>/dev/null || true)"
if [ "$INSTALLED_VERSION" = "$MBT_EXPECTED_GCC_VERSION" ]; then
return 0
fi
return 1
}
mbtenv_download_and_unpack_toolchain() {
mbtenv_choose_downloader || return 1
MBT_TMP_ARCHIVE="$(mktemp /tmp/mbt-toolchain-XXXXXX.tar.bz2)"
trap mbtenv_cleanup_tmp EXIT INT TERM
mbtenv_log "downloading ARM GNU toolchain..."
mbtenv_download_archive "$MBT_TMP_ARCHIVE" "$MBT_TOOLCHAIN_URL" || {
mbtenv_log "error: failed to download toolchain from ARM."
return 1
}
mbtenv_log "putting toolchain into $MBT_TOOLCHAIN_DIR"
if [ -d "$MBT_TOOLCHAIN_DIR" ] && [ ! -f "$MBT_TOOLCHAIN_DIR/bin/arm-none-eabi-gcc-9.2.1" ]; then
mbtenv_log "Sorry, i can't verify if the toolchain dir is legit to delete. it either could be becasue you failed to download the full toolchain last time, or the enviroment var were pointed to something else, which is not safe for me to remove. please check if it's safe to remove $MBT_TOOLCHAIN_DIR and manually remove $MBT_TOOLCHAIN_DIR"
return 1
fi
rm -rf "$MBT_TOOLCHAIN_DIR"
mkdir -p "$MBT_TOOLCHAIN_DIR"
tar --strip-components=1 -xjf "$MBT_TMP_ARCHIVE" -C "$MBT_TOOLCHAIN_DIR" || {
mbtenv_log "error: failed to extract toolchain archive."
return 1
}
mbtenv_cleanup_tmp
trap - EXIT INT TERM
MBT_TMP_ARCHIVE=""
return 0
}
mbtenv_ensure_toolchain() {
if mbtenv_toolchain_is_expected && [ "$MBT_FORCE_TOOLCHAIN" != "1" ]; then
mbtenv_verbose "toolchain already installed at $MBT_TOOLCHAIN_DIR"
return 0
fi
if [ "$MBT_NO_TOOLCHAIN_DOWNLOAD" = "1" ]; then
if [ ! -x "$MBT_TOOLCHAIN_CC" ]; then
mbtenv_log "error: arm-none-eabi-gcc not found in $MBT_TOOLCHAIN_BIN"
mbtenv_log "hint: run ./mbt toolchain once, or remove --no-toolchain."
return 1
fi
INSTALLED_VERSION="$(mbtenv_get_installed_version 2>/dev/null || true)"
mbtenv_log "error: found arm-none-eabi-gcc $INSTALLED_VERSION, expected $MBT_EXPECTED_GCC_VERSION"
mbtenv_log "hint: run ./mbt toolchain once, or remove --no-toolchain."
return 1
fi
if [ -x "$MBT_TOOLCHAIN_CC" ]; then
INSTALLED_VERSION="$(mbtenv_get_installed_version 2>/dev/null || true)"
mbtenv_log "toolchain version mismatch (found $INSTALLED_VERSION, expected $MBT_EXPECTED_GCC_VERSION)"
mbtenv_log "reinstalling toolchain..."
fi
mbtenv_download_and_unpack_toolchain || return 1
if ! mbtenv_toolchain_is_expected; then
INSTALLED_VERSION="$(mbtenv_get_installed_version 2>/dev/null || true)"
mbtenv_log "error: installed toolchain version check failed (found $INSTALLED_VERSION)."
return 1
fi
return 0
}
mbtenv_activate_path() {
if [ ! -d "$MBT_TOOLCHAIN_BIN" ]; then
mbtenv_log "error: missing toolchain bin directory: $MBT_TOOLCHAIN_BIN"
return 1
fi
case ":$PATH:" in
*":$MBT_TOOLCHAIN_BIN:"*)
return 0
;;
esac
if [ -z "${MBT_SAVED_PATH+x}" ]; then
MBT_SAVED_PATH="$PATH"
export MBT_SAVED_PATH
fi
PATH="$MBT_TOOLCHAIN_BIN:$PATH"
export PATH
return 0
}
mbtenv_restore() {
if [ -n "${MBT_SAVED_PATH+x}" ]; then
PATH="$MBT_SAVED_PATH"
export PATH
unset MBT_SAVED_PATH
fi
return 0
}
mbtenv_main() {
mbtenv_check_linux || return 1
if [ "${1:-}" = "--restore" ]; then
mbtenv_restore
return 0
fi
if [ "${MBT_NOENV:-0}" = "1" ]; then
return 0
fi
mbtenv_ensure_toolchain || return 1
mbtenv_activate_path || return 1
return 0
}
mbtenv_main "${1:-}"
+7
View File
@@ -0,0 +1,7 @@
TEST
Portapack
HackRF
1
2
3
4
+18
View File
@@ -58,3 +58,21 @@ f=1258000000,d=FPV Ch 6
f=1280000000,d=FPV Ch 7
f=1320000000,d=FPV Ch 8
f=1360000000,d=FPV Ch 9
# 3.1GHz & 3.3GHz Analog FPV Channels
f=3330000000,d=FPV Ch A-1
f=3350000000,d=FPV Ch A-2
f=3370000000,d=FPV Ch A-3
f=3390000000,d=FPV Ch A-4
f=3410000000,d=FPV Ch A-5
f=3430000000,d=FPV Ch A-6
f=3450000000,d=FPV Ch A-7
f=3470000000,d=FPV Ch A-8
f=3170000000,d=FPV Ch B-1
f=3190000000,d=FPV Ch B-2
f=3210000000,d=FPV Ch B-3
f=3230000000,d=FPV Ch B-4
f=3250000000,d=FPV Ch B-5
f=3270000000,d=FPV Ch B-6
f=3290000000,d=FPV Ch B-7
f=3310000000,d=FPV Ch B-8