mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-07-26 10:38:52 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b363b79aa | |||
| 0d51787099 | |||
| 8f5b25b10d | |||
| 4e56d0f420 | |||
| 2bb3b415d1 | |||
| 6e0394fb6b | |||
| da21ea7866 | |||
| 2c434946cd |
@@ -17,12 +17,12 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||
|
||||
[<img alt="PortaPack H4M Crash Course" src="https://img.youtube.com/vi/CjI-nUzdpas/maxresdefault.jpg" width="701">](https://share.hackrf.app/SPFOZO)
|
||||
[<img alt="HackRF Pro Quick Look" src="https://img.youtube.com/vi/IoURGzIMhzo/maxresdefault.jpg" width="701">](https://share.hackrf.app/VNBTP6)
|
||||
|
||||
[<img alt="PortaPack H4M Crash Course" src="https://img.youtube.com/vi/CjI-nUzdpas/maxresdefault.jpg" width="172">](https://share.hackrf.app/SPFOZO)
|
||||
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="172">](https://share.hackrf.app/6HKX9A)
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF)
|
||||
[<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO)
|
||||
[<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://share.hackrf.app/0JUHZ6)
|
||||
|
||||
|
||||
# Frequently Asked Questions
|
||||
@@ -32,11 +32,11 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/DF2BZS) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/O4OC90) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/QT5JMB), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/O3GI0O), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/24T3TO), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/H7OR74), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
|
||||
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
||||
|
||||
|
||||
@@ -332,6 +332,7 @@ set(CPPSRC
|
||||
${CPLD_H4M_DATA_CPP}
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
ui_external_items_menu_loader.cpp
|
||||
view_factory_base.cpp
|
||||
)
|
||||
|
||||
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <variant>
|
||||
|
||||
#include "file.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "max283x.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
@@ -134,7 +135,7 @@ struct AppSettings {
|
||||
uint8_t vga = 32;
|
||||
uint8_t rx_amp = 0;
|
||||
uint8_t tx_amp = 0;
|
||||
uint8_t tx_gain = 35;
|
||||
uint8_t tx_gain = hackrf::one::default_tx_gain_db;
|
||||
uint32_t channel_bandwidth = 1;
|
||||
uint32_t rx_frequency;
|
||||
uint32_t tx_frequency;
|
||||
|
||||
@@ -238,6 +238,7 @@ APRSTXView::APRSTXView(NavigationView& nav) {
|
||||
process_coordinates(lat, lon);
|
||||
});
|
||||
};
|
||||
|
||||
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,8 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10,
|
||||
&text_info_line_11});
|
||||
&text_info_line_11,
|
||||
&text_info_line_12});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
@@ -118,9 +119,12 @@ void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
||||
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
||||
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
||||
text_info_line_12.set(std::string("M") + (radio::get_mixer_invert() ? "Y" : "N") +
|
||||
" B" + (radio::get_baseband_invert() ? "Y" : "N") +
|
||||
" IQ" + ((radio::get_mixer_invert() ^ radio::get_baseband_invert()) ? "Y" : "N"));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 11 + 2;
|
||||
constexpr auto lines = 12 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -96,6 +96,7 @@ class DfuMenu2 : public View {
|
||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
{{5 * CHARACTER_WIDTH, 16 * LINE_HEIGHT}, "IQ Inv:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -109,6 +110,7 @@ class DfuMenu2 : public View {
|
||||
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_12{{(14 + 1) * CHARACTER_WIDTH, 16 * LINE_HEIGHT, (10 - 1) * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -390,6 +390,7 @@ std::string get_stem(std::string t) {
|
||||
return t.substr(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_filename(std::string _s) {
|
||||
const auto index = _s.find_last_of("/");
|
||||
if (index == _s.npos) {
|
||||
@@ -398,6 +399,7 @@ std::string get_filename(std::string _s) {
|
||||
return _s.substr(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
@@ -408,8 +410,6 @@ void FileManBaseView::refresh_list() {
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
auto entry_name = std::string{entry.path.length() <= max_filename_length ? entry.path : entry.path.substr(0, max_filename_length)};
|
||||
|
||||
if (entry.is_directory) {
|
||||
std::string size_str{};
|
||||
if (entry.path == str_next || entry.path == str_back) {
|
||||
@@ -418,8 +418,10 @@ void FileManBaseView::refresh_list() {
|
||||
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
|
||||
}
|
||||
|
||||
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
|
||||
// Do not use spaces or padding here. The menu will handle the alignment.
|
||||
menu_view.add_item(
|
||||
{entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str,
|
||||
{entry.path + "\t" + size_str,
|
||||
Theme::getInstance()->fg_yellow->foreground,
|
||||
&bitmap_icon_dir,
|
||||
[this](KeyEvent key) {
|
||||
@@ -431,8 +433,9 @@ void FileManBaseView::refresh_list() {
|
||||
const auto& assoc = get_assoc(get_extension(entry.path));
|
||||
auto size_str = to_string_file_size(entry.size);
|
||||
|
||||
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
|
||||
menu_view.add_item(
|
||||
{entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str,
|
||||
{entry.path + "\t" + size_str,
|
||||
assoc.color,
|
||||
assoc.icon,
|
||||
[this](KeyEvent key) {
|
||||
|
||||
@@ -32,7 +32,7 @@ static const char* hackrf_magic = "HACKRFFW";
|
||||
#define FIRST_CHECKSUM_NIGHTLY 240125
|
||||
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
static constexpr size_t max_filename_length = 60; // max length of filename
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||
File firmware_file;
|
||||
|
||||
@@ -164,11 +164,13 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
|
||||
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, gradient.lut[spectrum_data[xpos]]);
|
||||
}
|
||||
// indicate RSSI min and max power
|
||||
display.fill_rectangle({{0, screen_height - map(raw_min, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_min]);
|
||||
display.fill_rectangle({{0, screen_height - map(raw_max, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_max]);
|
||||
if (show_rssi_guides) {
|
||||
display.fill_rectangle({{0, screen_height - map(raw_min, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_min]);
|
||||
display.fill_rectangle({{0, screen_height - map(raw_max, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_max]);
|
||||
}
|
||||
if (last_max_freq != max_freq_hold) {
|
||||
last_max_freq = max_freq_hold;
|
||||
freq_stats.set("MAX HOLD: " + to_string_short_freq(max_freq_hold));
|
||||
freq_stats.set("MAX: " + to_string_short_freq(max_freq_hold));
|
||||
}
|
||||
plot_marker(marker_pixel_index);
|
||||
} else {
|
||||
@@ -394,6 +396,7 @@ GlassView::GlassView(
|
||||
&button_marker_plus,
|
||||
&button_jump,
|
||||
&button_rst,
|
||||
&button_rssi,
|
||||
&freq_stats});
|
||||
|
||||
load_presets(); // Load available presets from TXT files (or default).
|
||||
@@ -448,6 +451,7 @@ GlassView::GlassView(
|
||||
freq_stats.hidden(true);
|
||||
button_jump.hidden(true);
|
||||
button_rst.hidden(true);
|
||||
button_rssi.hidden(true);
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
|
||||
break;
|
||||
|
||||
@@ -458,6 +462,7 @@ GlassView::GlassView(
|
||||
freq_stats.hidden(false);
|
||||
button_jump.hidden(false);
|
||||
button_rst.hidden(false);
|
||||
button_rssi.hidden(false);
|
||||
break;
|
||||
|
||||
case 2: // PEAK
|
||||
@@ -468,6 +473,7 @@ GlassView::GlassView(
|
||||
freq_stats.hidden(false);
|
||||
button_jump.hidden(false);
|
||||
button_rst.hidden(false);
|
||||
button_rssi.hidden(false);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -537,6 +543,16 @@ GlassView::GlassView(
|
||||
reset_live_view();
|
||||
};
|
||||
|
||||
button_rssi.on_select = [this](Button& button) {
|
||||
show_rssi_guides = !show_rssi_guides;
|
||||
if (show_rssi_guides) {
|
||||
button.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
button.set_style(Theme::getInstance()->bg_darkest);
|
||||
}
|
||||
reset_live_view();
|
||||
};
|
||||
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
||||
|
||||
// trigger:
|
||||
|
||||
@@ -164,6 +164,7 @@ class GlassView : public View {
|
||||
uint8_t bin_length = screen_width;
|
||||
uint8_t offset = 0;
|
||||
uint8_t ignore_dc = 0;
|
||||
bool show_rssi_guides = false;
|
||||
|
||||
// start of spectrum area
|
||||
static constexpr int spectrum_y = (108 + 16);
|
||||
@@ -288,15 +289,19 @@ class GlassView : public View {
|
||||
}};
|
||||
|
||||
Button button_jump{
|
||||
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
|
||||
{UI_POS_X_RIGHT(4), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
|
||||
"JMP"};
|
||||
|
||||
Button button_rst{
|
||||
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
|
||||
"RST"};
|
||||
|
||||
Button button_rssi{
|
||||
{UI_POS_X_RIGHT(15), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
|
||||
"RSSI"};
|
||||
|
||||
Text freq_stats{
|
||||
{UI_POS_X(0), 5 * 16, screen_width - 10 * 8, 8},
|
||||
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config{
|
||||
|
||||
@@ -186,26 +186,26 @@ void set_dirty() {
|
||||
}
|
||||
|
||||
standalone_application_api_t api = {
|
||||
/* .malloc = */ &alloc,
|
||||
/* .calloc = */ &calloc,
|
||||
/* .realloc = */ &realloc,
|
||||
/* .free = */ &chHeapFree,
|
||||
/* .create_thread = */ &create_thread,
|
||||
/* .fill_rectangle = */ &fill_rectangle,
|
||||
/* .swizzled_switches = */ &swizzled_switches,
|
||||
/* .get_switches_state = */ &get_switches_state_ulong,
|
||||
/* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(),
|
||||
/* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
|
||||
/* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
|
||||
/* .draw_bitmap = */ &draw_bitmap,
|
||||
/* .scroll_area_y = */ &scroll_area_y,
|
||||
/* .scroll_set_area = */ &scroll_set_area,
|
||||
/* .scroll_disable = */ &scroll_disable,
|
||||
/* .scroll_set_position = */ &scroll_set_position,
|
||||
/* .scroll = */ &scroll,
|
||||
/* .i2c_read = */ &i2c_read,
|
||||
/* .panic = */ &chDbgPanic,
|
||||
/* .set_dirty = */ &set_dirty,
|
||||
.malloc = &alloc,
|
||||
.calloc = &calloc,
|
||||
.realloc = &realloc,
|
||||
.free = &chHeapFree,
|
||||
.create_thread = &create_thread,
|
||||
.fill_rectangle = &fill_rectangle,
|
||||
.swizzled_switches = &swizzled_switches,
|
||||
.get_switches_state = &get_switches_state_ulong,
|
||||
.fixed_5x8_glyph_data = ui::font::fixed_5x8.get_data(),
|
||||
.fixed_8x16_glyph_data = ui::font::fixed_8x16.get_data(),
|
||||
.fill_rectangle_unrolled8 = &fill_rectangle_unrolled8,
|
||||
.draw_bitmap = &draw_bitmap,
|
||||
.scroll_area_y = &scroll_area_y,
|
||||
.scroll_set_area = &scroll_set_area,
|
||||
.scroll_disable = &scroll_disable,
|
||||
.scroll_set_position = &scroll_set_position,
|
||||
.scroll = &scroll,
|
||||
.i2c_read = &i2c_read,
|
||||
.panic = &chDbgPanic,
|
||||
.set_dirty = &set_dirty,
|
||||
// Version 3
|
||||
.f_open = &ext_f_open,
|
||||
.f_close = &ext_f_close,
|
||||
|
||||
@@ -325,6 +325,12 @@ static constexpr ClockControl::MultiSynthSource get_si5351a_reference_clock_gene
|
||||
? ClockControl::MultiSynthSource::PLLA
|
||||
: ClockControl::MultiSynthSource::PLLB;
|
||||
}
|
||||
|
||||
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
||||
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
||||
? ClockControl::MultiSynthSource::PLLA
|
||||
: ClockControl::MultiSynthSource::PLLB;
|
||||
}
|
||||
#else
|
||||
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
|
||||
return (reference_source == ClockManager::ReferenceSource::Xtal)
|
||||
@@ -424,32 +430,22 @@ using namespace hackrf::one;
|
||||
|
||||
void ClockManager::init_clock_generator() {
|
||||
#ifdef PRALINE
|
||||
// thoese PIN can review platform_scu file
|
||||
// have many conflict and modify for clock init
|
||||
// P2_10 -> GPIO0[14] P1_CTRL0
|
||||
/* Route the PRALINE P22 10MHz reference to the Si5351 CLKIN pin. */
|
||||
LPC_SCU->SFSP[2][10] = 0xF0;
|
||||
// P6_8 -> GPIO5[16] P1_CTRL1
|
||||
LPC_SCU->SFSP[6][8] = 0xF4;
|
||||
// P6_9 -> GPIO3[5] P1_CTRL2
|
||||
LPC_SCU->SFSP[6][9] = 0xF0;
|
||||
// P1_20 -> GPIO0[15] CLKIN_CTRL
|
||||
LPC_SCU->SFSP[1][20] = 0xF0;
|
||||
|
||||
constexpr GPIO gpio_p1_ctrl0 = gpio[GPIO0_14];
|
||||
constexpr GPIO gpio_p1_ctrl1 = gpio[GPIO5_16];
|
||||
constexpr GPIO gpio_p1_ctrl2 = gpio[GPIO3_5];
|
||||
constexpr GPIO gpio_clkin_ctrl = gpio[GPIO0_15];
|
||||
|
||||
gpio_p1_ctrl0.output();
|
||||
gpio_p1_ctrl1.output();
|
||||
gpio_p1_ctrl2.output();
|
||||
gpio_clkin_ctrl.output();
|
||||
|
||||
// P1 control = 100 and choose P22 clock external clock on portapack
|
||||
gpio_p1_ctrl0.write(0);
|
||||
gpio_p1_ctrl1.write(0);
|
||||
gpio_p1_ctrl2.write(1);
|
||||
gpio_clkin_ctrl.write(1);
|
||||
|
||||
chThdSleepMilliseconds(20);
|
||||
|
||||
#else
|
||||
@@ -465,35 +461,45 @@ void ClockManager::init_clock_generator() {
|
||||
clock_generator.enable_fanout();
|
||||
|
||||
#ifdef PRALINE
|
||||
/* PRALINE has Si5351A (NOT Si5351C like HackRF One OG).
|
||||
* Must use Si5351A configuration: PLLA only, no CLKIN support.
|
||||
*
|
||||
* IMPORTANT: Follow HackRF reference sequence:
|
||||
* 1. Set PLL input sources
|
||||
* 2. Configure PLL and multisynths
|
||||
* 3. Set clock control registers (AFTER multisynths!)
|
||||
* 4. Reset PLLs
|
||||
* 5. Enable outputs
|
||||
*/
|
||||
// change there detect method same as hackrf one
|
||||
// PLLA -> XTAL,PLLB -> CLKIN
|
||||
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
|
||||
// although the name a and the branch define CLK2 MCU CLOCK
|
||||
// only use CLK2
|
||||
auto si5351_clock_control_common = si5351a_clock_control_common;
|
||||
constexpr size_t clock_generator_output_pro_mcu_clkin = 2;
|
||||
// clk_src define CLKIN
|
||||
auto si5351_clock_control_common = si5351a_clock_control_common;
|
||||
|
||||
/*
|
||||
* Match the HackRF One init flow:
|
||||
* 1. PLLA stays on the local 25MHz crystal.
|
||||
* 2. PLLB is configured from the Si5351 CLKIN pin.
|
||||
* 3. CLK2 temporarily drives GP_CLKIN directly from CLKIN so we can
|
||||
* detect whether P22 is carrying a valid 10MHz reference.
|
||||
* 4. Once the source is chosen, all runtime clocks are switched to the
|
||||
* selected PLL and the working multisynths are programmed.
|
||||
*/
|
||||
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
|
||||
|
||||
clock_generator.set_clock_control(
|
||||
clock_generator_output_pro_mcu_clkin,
|
||||
si5351_clock_control_common[clock_generator_output_pro_mcu_clkin]
|
||||
.clk_src(ClockControl::ClockSource::CLKIN)
|
||||
.clk_pdn(ClockControl::ClockPowerDown::Power_On));
|
||||
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
||||
chThdSleepMilliseconds(20);
|
||||
// should be extern icon and 10MHz
|
||||
reference = choose_reference();
|
||||
|
||||
/* Clock control will be set AFTER multisynth configuration - see below */
|
||||
chThdSleepMilliseconds(20);
|
||||
reference = choose_reference();
|
||||
clock_generator.disable_output(clock_generator_output_pro_mcu_clkin);
|
||||
|
||||
const auto ref_pll =
|
||||
get_si5351c_reference_clock_generator_pll(reference.source);
|
||||
|
||||
const ClockControls si5351_clock_control = ClockControls{{
|
||||
si5351_clock_control_common[0].ms_src(ref_pll),
|
||||
si5351_clock_control_common[1].ms_src(ref_pll),
|
||||
si5351_clock_control_common[2].ms_src(ref_pll),
|
||||
si5351_clock_control_common[3].ms_src(ref_pll),
|
||||
si5351_clock_control_common[4].ms_src(ref_pll),
|
||||
si5351_clock_control_common[5].ms_src(ref_pll),
|
||||
si5351_clock_control_common[6].ms_src(ref_pll),
|
||||
si5351_clock_control_common[7].ms_src(ref_pll),
|
||||
}};
|
||||
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||
#else
|
||||
clock_generator.set_pll_input_sources(hackrf_r9
|
||||
? si5351a_pll_input_sources
|
||||
@@ -538,86 +544,50 @@ void ClockManager::init_clock_generator() {
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
clock_generator.write_pll_single_byte(0, si5351_pll_a_afe_800m);
|
||||
|
||||
/* * Praline HackRF Pro Clock Assignments (800 MHz VCO Configuration)
|
||||
* VCO Frequency: 800,000,000 Hz (Master Reference)
|
||||
* * CLK0: AFE_CLK (MAX5864 Codec & FPGA ADC Interface)
|
||||
* - Note: Defines hardware sample rate. Essential for WFM purity.
|
||||
* * CLK1: SCT_CLK (iCE40 FPGA System/Timing Clock)
|
||||
* - Note: Timing for SGPIO data bus; scales to 2x SR in wideband modes.
|
||||
* * CLK2: MCU_CLKIN (LPC43xx MCU External Clock Input)
|
||||
* - Note: Synchronizes MCU processing to the RF clock tree.
|
||||
* * CLK3: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 1
|
||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
||||
* * CLK4: P_CLK (MAX2831 Peripheral/Expansion Clock)
|
||||
* - Note: Routed to expansion headers for external hardware sync.
|
||||
* * CLK5: AUX_CLK (RFFC5371 Auxiliary reference for secondary logic)
|
||||
* - Note: Provides additional timing flexibility for the iCE40 FPGA.
|
||||
* * CLK6: SG_CLK (Switching Regulator/Internal Logic Sync) SMA Port 2
|
||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
||||
* * CLK7: Unused / Power-Down
|
||||
* - State: Disabled (si5351a_ms6_7_off_reg)
|
||||
* - Note: Kept OFF to reduce EMI/RFI near the RF front-end.
|
||||
* * CLKOUT: Optional external clock output on the header.
|
||||
*/
|
||||
|
||||
/* Write PLL A (800 MHz based on 25 MHz xtal for RF) and
|
||||
* PLL B (800 MHZ based on 25 MHz xtalfor Digital)
|
||||
* Use single-byte writes to debug I2C issues
|
||||
*/
|
||||
{
|
||||
// Write PLLA (Registers 26-33)
|
||||
/* Write PLL A configuration (Base 26) */
|
||||
const auto& pll_a = si5351_pll_a_800_reg;
|
||||
for (size_t i = 1; i < pll_a.size(); i++) {
|
||||
clock_generator.write_register(pll_a[0] + i - 1, pll_a[i]);
|
||||
}
|
||||
|
||||
// Write PLLB (Registers 34-41)
|
||||
/* Write PLL B configuration (Base 34) */
|
||||
const auto& pll_b = si5351_pll_b_800_reg;
|
||||
const auto& pll_b = si5351c_pll_b_clkin_reg;
|
||||
for (size_t i = 1; i < pll_b.size(); i++) {
|
||||
clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]);
|
||||
}
|
||||
}
|
||||
// CLK0: DAFE_CLK initial 4 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
0,
|
||||
si5351_ms_afe_4m);
|
||||
|
||||
/* Write multisynth configurations using single-byte writes */
|
||||
// These cover all active channels on the Praline board
|
||||
clock_generator.write_ms_single_byte(0, si5351_ms_afe_4m); // CLK0: PLL A AFE Codec (4 MHz)
|
||||
clock_generator.write_ms_single_byte(1, si5351_ms_10m); // CLK1: PLL B SGPIO/FPGA Timing (10 MHz)
|
||||
clock_generator.write_ms_single_byte(2, si5351_ms_afe_40m); // CLK2: PLL A Audio and MCU Input (40 MHz)
|
||||
clock_generator.write_ms_single_byte(3, si5351_ms_0_4m); // CLK3: PLL B SMA Port 1 Logic Sync (4 MHz or 10 MHz)
|
||||
clock_generator.write_ms_single_byte(4, si5351_ms_afe_40m); // CLK4: PLL A MAX2831 Second IF (40 MHz)
|
||||
clock_generator.write_ms_single_byte(5, si5351_ms_afe_40m); // CLK5: PLL A RFFC5071First IF (40 MHz)
|
||||
clock_generator.write_ms_single_byte(6, si5351_ms_0_4m); // CLK6: PLL B SMA Port 2 Logic Sync (4 MHz or 10 MHz)
|
||||
clock_generator.write_ms_single_byte(7, si5351_ms_0_4m); // CLK7: PLL B Unused (4 MHz)
|
||||
// CLK1: DSCT_CLK initial 10 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
1,
|
||||
si5351_ms_10m);
|
||||
|
||||
/* NOW set clock control registers (AFTER multisynths per HackRF reference) */
|
||||
const auto ref_pll_a = ClockControl::MultiSynthSource::PLLA;
|
||||
const auto ref_pll_b = ClockControl::MultiSynthSource::PLLB;
|
||||
const ClockControls si5351_clock_control = ClockControls{{
|
||||
si5351a_clock_control_common[0].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[1].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[2].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[3].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[4].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[5].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[6].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[7].ms_src(ref_pll_b),
|
||||
}};
|
||||
// clock_generator.set_clock_control(si5351_clock_control);
|
||||
// Use single-byte writes instead of multi-byte
|
||||
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||
// CLK2: MCU_CLK 40 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
2,
|
||||
si5351_ms_afe_40m);
|
||||
|
||||
// Don't write CLKS 3, 6, and 7 multisynth
|
||||
// Ensure CLK3 clock control has Power_Off
|
||||
// Verify output is disabled
|
||||
clock_generator.disable_output(3);
|
||||
clock_generator.disable_clock(3);
|
||||
clock_generator.disable_output(6);
|
||||
clock_generator.disable_clock(6);
|
||||
clock_generator.disable_output(7);
|
||||
clock_generator.disable_clock(7);
|
||||
// CLK3: P2 clock mux, disabled below
|
||||
clock_generator.write_ms_single_byte(
|
||||
3,
|
||||
si5351_ms_10m);
|
||||
|
||||
// CLK4: DXCVR_CLK 40 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
4,
|
||||
si5351_ms_afe_40m);
|
||||
|
||||
// CLK5: DMIX_CLK 40 MHz
|
||||
clock_generator.write_ms_single_byte(
|
||||
5,
|
||||
si5351_ms_afe_40m);
|
||||
|
||||
{
|
||||
const auto& ms6_7 = si5351a_ms6_7_off_reg;
|
||||
for (size_t i = 1; i < ms6_7.size(); i++) {
|
||||
clock_generator.write_register(ms6_7[0] + i - 1, ms6_7[i]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
||||
@@ -645,15 +615,14 @@ void ClockManager::init_clock_generator() {
|
||||
|
||||
// Wait for PLL(s) to lock.
|
||||
#ifdef PRALINE
|
||||
// PRALINE: Wait for 0x60 (0x20 | 0x40), PLLA and PLLB to lock (0x20 = LOL_A bit, 0x40 = LOL_B bit)
|
||||
uint8_t device_status_mask = 0x60;
|
||||
uint8_t device_status_mask =
|
||||
(ref_pll == ClockControl::MultiSynthSource::PLLB) ? 0x40 : 0x20;
|
||||
uint32_t pll_timeout = 100000;
|
||||
while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) {
|
||||
pll_timeout--;
|
||||
}
|
||||
// Store PLL lock status for debugging
|
||||
static volatile uint32_t pll_lock_timeout = pll_timeout;
|
||||
(void)pll_lock_timeout;
|
||||
|
||||
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
|
||||
|
||||
#else
|
||||
// Wait for PLL(s) to lock - with timeout to prevent hang
|
||||
@@ -753,13 +722,13 @@ void ClockManager::shutdown() {
|
||||
|
||||
void ClockManager::enable_codec_clocks() {
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: CLK0 (AFE_CLK) for codec/FPGA, CLK1 (SCT_CLK) for FPGA timing.
|
||||
* Reference hackrf_core.c shows PRALINE needs both CLK0 and CLK1. */
|
||||
/* PRALINE: only gate the clocks owned by the RF datapath here.
|
||||
* CLK2 is MCU_CLKIN and must stay independent of runtime RX/TX clock
|
||||
* management or the SGPIO/GPIO subsystem can glitch mid-stream. */
|
||||
clock_generator.enable_clock(clock_generator_output_og_codec); /* CLK0 MAX5864*/
|
||||
clock_generator.enable_clock(clock_generator_output_og_cpld); /* CLK1 iCE40 FPGA*/
|
||||
clock_generator.enable_clock(clock_generator_output_og_sgpio); /* CLK2 LPC43xx*/
|
||||
clock_generator.enable_output_mask(
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
clock_generator.enable_clock(clock_generator_output_r9_sgpio);
|
||||
@@ -787,12 +756,11 @@ void ClockManager::disable_codec_clocks() {
|
||||
* CLKx_DISABLE_STATE.
|
||||
*/
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: CLK0 (AFE_CLK), CLK1 (SCT_CLK), and CLK2 MCU used for codec/FPGA */
|
||||
/* PRALINE: leave CLK2/MCU_CLKIN alone; only disable datapath-owned clocks. */
|
||||
clock_generator.disable_output_mask(
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
|
||||
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
|
||||
clock_generator.disable_clock(clock_generator_output_og_codec);
|
||||
clock_generator.disable_clock(clock_generator_output_og_cpld);
|
||||
clock_generator.disable_clock(clock_generator_output_og_sgpio);
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
clock_generator.disable_output_mask(1U << clock_generator_output_r9_sgpio);
|
||||
@@ -851,7 +819,7 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||
/*
|
||||
* PRALINE sample rate strategy:
|
||||
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
|
||||
* 2. Use FPGA decimation to achieve desired output rate
|
||||
* 2. Use FPGA decimation/interpolation to achieve desired output rate
|
||||
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
|
||||
*/
|
||||
|
||||
@@ -872,50 +840,96 @@ void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||
n++;
|
||||
}
|
||||
|
||||
_resampling_n = n;
|
||||
|
||||
// === Stop FPGA processing and flush filters ===
|
||||
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
|
||||
|
||||
// Verify we're in RX mode before writing RX registers
|
||||
if (fpga_get_mode() != FPGA_MODE_RX) {
|
||||
// Either set mode or return error
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
/* The TX gateware only implements interpolation factors x1..x16
|
||||
* (tx_intrp 0..4). The rate search above can select n == 5 (x32) for very
|
||||
* low TX sample rates. If we kept afe_rate/CLK0/CLK1 at the x32 rate while
|
||||
* the FPGA only interpolated by x16, the TX datapath would be clocked at
|
||||
* twice the gateware's output rate, doubling the waveform speed. Cap the
|
||||
* AFE rate and _resampling_n to the x16 limit in TX so the clocks and the
|
||||
* interpolation setting stay aligned. */
|
||||
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
|
||||
constexpr uint8_t MAX_TX_N = 4; // x16, matches max tx_intrp
|
||||
if (n > MAX_TX_N) {
|
||||
n = MAX_TX_N;
|
||||
afe_rate = frequency << n;
|
||||
}
|
||||
}
|
||||
|
||||
// Set FPGA RX decimation register
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
||||
|
||||
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
|
||||
* We shift up by (3 * n) to compensate for CIC bit-growth.
|
||||
* Relationship: ds = (Stages * n) - Offset
|
||||
* For a 3-stage filter, every increment of n grows the signal by 3 bits.
|
||||
* We subtract a baseline offset to keep the signal within 8-bit bounds.
|
||||
* Add a baseline shift to ensure the signal isn't too quiet
|
||||
*/
|
||||
uint8_t ds = (3 * n);
|
||||
ds += 2;
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
||||
_resampling_n = n;
|
||||
|
||||
radio::invalidate_spi_config();
|
||||
|
||||
// Configure Si5351 clocks
|
||||
// CLK0: AFE_CLK (with r_div=1 for ÷2)
|
||||
// CLK1: SCT_CLK (with r_div=0 for ÷1, runs at 2× AFE for FPGA timing)
|
||||
// Configure Si5351 clocks using the correct AFE VCO
|
||||
// CLK0 always drives the AFE/MAX5864 clock.
|
||||
clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1);
|
||||
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
||||
|
||||
// === Reset PLL A for phase alignment ===
|
||||
clock_generator.write_register(si5351::Register::PLLReset, 0x20);
|
||||
/* Do not reset PLLA here. On PRALINE, CLK2/MCU_CLKIN is sourced from PLLA,
|
||||
* so a runtime PLLA reset can glitch the LPC43xx peripheral clock tree and
|
||||
* break SGPIO-driven RX apps such as ADSB/APRS. Updating the multisynths is
|
||||
* sufficient for sample-rate changes. */
|
||||
|
||||
// Brief delay for PLL lock and clock stability ===
|
||||
// ~1ms at 96MHz = ~96000 cycles, use 10ms for safety
|
||||
volatile uint32_t delay = 240000; // ~2.5ms
|
||||
while (delay--);
|
||||
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
|
||||
/*
|
||||
* TX path:
|
||||
* Baseband generators such as AFSK still synthesize samples at their
|
||||
* legacy logical sample rates (for APRS this is 1.536MHz). On PRALINE,
|
||||
* the FPGA must interpolate those samples up to the active AFE rate.
|
||||
*
|
||||
* The TX datapath is clocked from CLK1/fpgaclk. Therefore CLK1 must
|
||||
* equal the desired complex sample rate at the DAC side. Driving CLK1
|
||||
* at 2x the intended TX sample rate causes the entire APRS waveform to
|
||||
* run twice as fast, which matches the "energy present, undecodable"
|
||||
* symptom seen on HackRF One receivers.
|
||||
*
|
||||
* Gateware mapping from standard.py:
|
||||
* tx_intrp = 0 -> x1
|
||||
* tx_intrp = 1 -> x2
|
||||
* tx_intrp = 2 -> x4
|
||||
* tx_intrp = 3 -> x8
|
||||
* tx_intrp = 4 -> x16
|
||||
*/
|
||||
if (fpga_get_mode() != FPGA_MODE_TX) {
|
||||
fpga_set_mode(FPGA_MODE_TX);
|
||||
}
|
||||
|
||||
// Re-enable FPGA processing with clean state ===
|
||||
fpga_debug_register_write(1, 0x01);
|
||||
// TX gateware consumes complex samples at the rate presented on CLK1.
|
||||
clock_generator.set_ms_frequency(1, afe_rate, si5351_vco_afe_f, 0);
|
||||
|
||||
uint8_t tx_interp = 0;
|
||||
uint32_t tx_rate = frequency;
|
||||
while ((tx_rate < afe_rate) && (tx_interp < 4)) {
|
||||
tx_rate <<= 1;
|
||||
tx_interp++;
|
||||
}
|
||||
fpga_tx_set_interpolation(tx_interp);
|
||||
fpga_tx_set_nco_enable(false);
|
||||
fpga_tx_set_phase_step(0);
|
||||
}
|
||||
// for RX mode
|
||||
else {
|
||||
// RX path keeps CLK1 at 2x AFE for receive timing / SGPIO alignment.
|
||||
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
||||
|
||||
// === Stop FPGA processing and flush filters ===
|
||||
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
|
||||
|
||||
if (fpga_get_mode() != FPGA_MODE_RX) {
|
||||
fpga_set_mode(FPGA_MODE_RX);
|
||||
}
|
||||
|
||||
// Set FPGA RX decimation register
|
||||
fpga_debug_register_write(FPGA_REG_DECIM, n);
|
||||
|
||||
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
|
||||
* We shift up by (3 * n) to compensate for CIC bit-growth.
|
||||
*/
|
||||
uint8_t ds = (3 * n);
|
||||
ds += 2;
|
||||
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
|
||||
|
||||
// Re-enable FPGA processing with clean state ===
|
||||
fpga_debug_register_write(1, 0x01);
|
||||
}
|
||||
|
||||
#else
|
||||
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
|
||||
|
||||
@@ -360,19 +360,12 @@ static void set_cpu_clock_speed() {
|
||||
|
||||
/* Step into the 90-110MHz M4 clock range */
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: Enable and use 12MHz XTAL directly (no GP_CLKIN from Si5351) */
|
||||
|
||||
/* Step 1: Enable the crystal oscillator */
|
||||
LPC_CGU->XTAL_OSC_CTRL.ENABLE = 0; // 0 = enable (active low)
|
||||
LPC_CGU->XTAL_OSC_CTRL.HF = 0; // 0 = low frequency mode (1-20MHz)
|
||||
|
||||
/* Step 2: Wait for oscillator to stabilize (~250us at IRC speed) */
|
||||
volatile uint32_t delay = 3000; // ~250us at 12MHz IRC
|
||||
while (delay--);
|
||||
|
||||
/* Step 3: Configure PLL1 from XTAL
|
||||
* Fclkin = 12M, /N=1 = 12M, Fcco = 12M * 17 = 204M
|
||||
* Fclk = Fcco / (2*(P=1)) = 102M
|
||||
/* PRALINE: source PLL1 from GP_CLKIN, which is driven by the Si5351
|
||||
* CLK2/MCU_CLK output at 40MHz (see ClockManager Si5351 setup). The
|
||||
* on-chip 12MHz XTAL bootstrap path is intentionally not used here.
|
||||
*
|
||||
* Fclkin = 40M (GP_CLKIN), /N=2 = 20M, Fcco = 20M * 10 = 200M
|
||||
* Fclk = Fcco / (2*(P=1)) = 100M
|
||||
*/
|
||||
cgu::pll1::ctrl({
|
||||
.pd = 1,
|
||||
@@ -381,10 +374,11 @@ static void set_cpu_clock_speed() {
|
||||
.direct = 0,
|
||||
.psel = 0,
|
||||
.autoblock = 1,
|
||||
.nsel = 0, // N = 1
|
||||
.msel = 16, // M = 17, so 12MHz * 17 = 204MHz
|
||||
.clk_sel = cgu::CLK_SEL::XTAL,
|
||||
.nsel = 1UL, // N = 2
|
||||
.msel = 9UL, // M = 10
|
||||
.clk_sel = cgu::CLK_SEL::GP_CLKIN,
|
||||
});
|
||||
|
||||
#else
|
||||
/* OG:
|
||||
* Fclkin = 40M, /N=2 = 20M, Fcco = 20M * 10 = 200M
|
||||
@@ -613,6 +607,7 @@ init_status_t init() {
|
||||
cgu::pll1::disable();
|
||||
|
||||
set_cpu_clock_speed();
|
||||
|
||||
/* sample max: 1023 sample_t AKA uint16_t
|
||||
* touch_sensitivity: range: 1 to 128
|
||||
* threshold range: 1023/1 to 1023/128 = 1023 to 8
|
||||
|
||||
@@ -390,11 +390,13 @@ void set_baseband_rate(const uint32_t rate) {
|
||||
}
|
||||
|
||||
void set_antenna_bias(const bool on) {
|
||||
/* Pull MOSFET gate low to turn on antenna bias. */
|
||||
#ifdef PRALINE
|
||||
// Praline: P2_12 = GPIO1[12], ANT_BIAS_EN_N (active LOW)
|
||||
LPC_GPIO->CLR[1] = on ? (1 << 12) : 0;
|
||||
LPC_GPIO->SET[1] = on ? 0 : (1 << 12);
|
||||
/* Keep bias control inside the PRALINE RF path state machine so later
|
||||
* band/direction/amp updates don't silently force ANT_BIAS_EN_N off.
|
||||
* Reassert output mode here because this pin is safety-critical and
|
||||
* direct GPIO writes on an input-configured pad have no effect. */
|
||||
gpio_ant_bias_disable.output();
|
||||
rf_path.set_antenna_bias(on);
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
gpio_r9_not_ant_pwr.write(on ? 0 : 1);
|
||||
@@ -404,6 +406,14 @@ void set_antenna_bias(const bool on) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool get_mixer_invert() {
|
||||
return mixer_invert;
|
||||
}
|
||||
|
||||
bool get_baseband_invert() {
|
||||
return baseband_invert;
|
||||
}
|
||||
|
||||
void set_tx_max283x_iq_phase_calibration(const size_t v) {
|
||||
second_if->set_tx_LO_iq_phase_calibration(v);
|
||||
}
|
||||
@@ -619,4 +629,4 @@ uint32_t register_read(const size_t register_number) {
|
||||
|
||||
} /* namespace debug */
|
||||
|
||||
} /* namespace radio */
|
||||
} /* namespace radio */
|
||||
|
||||
@@ -54,6 +54,8 @@ void set_baseband_filter_bandwidth_rx(const uint32_t bandwidth_minimum);
|
||||
void set_baseband_filter_bandwidth_tx(const uint32_t bandwidth_minimum);
|
||||
void set_baseband_rate(const uint32_t rate);
|
||||
void set_antenna_bias(const bool on);
|
||||
bool get_mixer_invert();
|
||||
bool get_baseband_invert();
|
||||
void set_tx_max283x_iq_phase_calibration(const size_t v);
|
||||
void set_rx_max283x_iq_phase_calibration(const size_t v);
|
||||
|
||||
|
||||
@@ -263,6 +263,11 @@ void Path::set_rf_amp(const bool new_rf_amp) {
|
||||
update();
|
||||
}
|
||||
|
||||
void Path::set_antenna_bias(const bool new_antenna_bias) {
|
||||
antenna_bias = new_antenna_bias;
|
||||
update();
|
||||
}
|
||||
|
||||
void Path::update() {
|
||||
/* 0 ^ 0 => 0 & 0 = 0 ^ 0 = 0 (no change)
|
||||
* 0 ^ 1 => 1 & 0 = 0 ^ 0 = 0 (ignore change to 1)
|
||||
@@ -301,8 +306,8 @@ void Path::update() {
|
||||
/* RF amp when amplification requested */
|
||||
config.rf_amp_en = rf_amp;
|
||||
|
||||
/* Antenna bias off by default */
|
||||
config.ant_bias_en_n = true;
|
||||
/* Preserve requested antenna bias state across band/direction/amp updates. */
|
||||
config.ant_bias_en_n = !antenna_bias;
|
||||
|
||||
config.apply();
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class Path {
|
||||
void set_direction(const Direction direction);
|
||||
void set_band(const Band band);
|
||||
void set_rf_amp(const bool rf_amp);
|
||||
void set_antenna_bias(const bool antenna_bias);
|
||||
|
||||
Band get_band() const { return _band; } //_band is used solely for debugging purposes.
|
||||
|
||||
@@ -72,6 +73,7 @@ class Path {
|
||||
Direction direction{Direction::Receive};
|
||||
Band band{Band::Mid};
|
||||
bool rf_amp{false};
|
||||
bool antenna_bias{false};
|
||||
|
||||
void update();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include <ctime>
|
||||
|
||||
namespace rtc_time {
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <cstddef>
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "max2837.hpp"
|
||||
#include "message.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
@@ -40,8 +41,9 @@ class TransmitterModel {
|
||||
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
|
||||
uint32_t sampling_rate = 3'072'000;
|
||||
uint32_t channel_bandwidth = 1;
|
||||
/* 35 should give approx 1m transmission range. */
|
||||
uint8_t tx_gain_db = 35;
|
||||
/* Platform default chosen to avoid PRALINE MAX2831 overdrive while
|
||||
* preserving legacy HackRF One behavior. */
|
||||
uint8_t tx_gain_db = hackrf::one::default_tx_gain_db;
|
||||
bool rf_amp = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "rtc_time.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include <algorithm>
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -61,7 +60,6 @@ BtnGridView::BtnGridView(
|
||||
}
|
||||
|
||||
BtnGridView::~BtnGridView() {
|
||||
ExternalItemsMenuLoader::unload_external_items();
|
||||
}
|
||||
|
||||
void BtnGridView::set_max_rows(int rows) {
|
||||
@@ -89,7 +87,6 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
|
||||
remove_child(item.get());
|
||||
|
||||
menu_item_views.clear();
|
||||
menu_item_views.shrink_to_fit();
|
||||
}
|
||||
|
||||
button_w = screen_width / rows_;
|
||||
@@ -138,17 +135,15 @@ void BtnGridView::set_arrow_down_enabled(bool enabled) {
|
||||
};
|
||||
|
||||
void BtnGridView::clear() {
|
||||
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
|
||||
// clear vector, not using swap since it's causing capture to glitch/fault
|
||||
menu_items.clear();
|
||||
menu_items.shrink_to_fit();
|
||||
|
||||
// TODO(u-foka): Clean up my mess, move this somewhere to clear memory when the view is not visible, but not to be confused with clearing the menu items...
|
||||
for (auto& item : menu_item_views)
|
||||
remove_child(item.get());
|
||||
|
||||
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
|
||||
// clear vector, not using swap since it's causing capture to glitch/fault
|
||||
menu_item_views.clear();
|
||||
menu_item_views.shrink_to_fit();
|
||||
}
|
||||
|
||||
void BtnGridView::add_items(std::initializer_list<GridItem> new_items, bool inhibit_update) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "ui_menu.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include <string_view>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -29,57 +30,112 @@ namespace ui {
|
||||
|
||||
void MenuItemView::set_item(MenuItem* item_) {
|
||||
item = item_;
|
||||
scroll_offset = 0;
|
||||
can_scroll = false;
|
||||
}
|
||||
|
||||
void MenuItemView::highlight() {
|
||||
set_highlighted(true);
|
||||
scroll_offset = 0;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void MenuItemView::unhighlight() {
|
||||
set_highlighted(false);
|
||||
scroll_offset = 0;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void MenuItemView::paint(Painter& painter) {
|
||||
Coord offset_x{};
|
||||
void MenuItemView::set_scroll_offset(size_t offset) {
|
||||
if (can_scroll && scroll_offset != offset) {
|
||||
scroll_offset = offset;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuItemView::paint(Painter& painter) {
|
||||
if (!item) return;
|
||||
|
||||
const auto r = screen_rect();
|
||||
|
||||
const auto rect = screen_rect();
|
||||
const auto paint_style = (highlighted() && (parent()->has_focus() || keep_highlight)) ? style().invert() : style();
|
||||
|
||||
const auto font_height = paint_style.font.line_height();
|
||||
const int char_width = paint_style.font.char_width();
|
||||
const int line_height = paint_style.font.line_height();
|
||||
|
||||
if (char_width == 0 || line_height == 0) return;
|
||||
|
||||
const int margin_x = char_width / 2;
|
||||
|
||||
ui::Color final_item_color = (highlighted() && (parent()->has_focus() || keep_highlight)) ? paint_style.foreground : item->color;
|
||||
ui::Color final_bg_color = (highlighted() && (parent()->has_focus() || keep_highlight)) ? item->color : paint_style.background;
|
||||
|
||||
if (final_item_color.v == final_bg_color.v) final_item_color = paint_style.foreground;
|
||||
|
||||
painter.fill_rectangle(
|
||||
r,
|
||||
final_bg_color);
|
||||
painter.fill_rectangle(rect, final_bg_color);
|
||||
|
||||
Coord offset_x = 0;
|
||||
|
||||
if (item->bitmap) {
|
||||
painter.draw_bitmap(
|
||||
{r.location().x() + 4, r.location().y() + 4},
|
||||
{rect.location().x() + 4, rect.location().y() + 4},
|
||||
*item->bitmap,
|
||||
final_item_color,
|
||||
final_bg_color);
|
||||
offset_x = 26;
|
||||
} else
|
||||
} else {
|
||||
offset_x = 0;
|
||||
}
|
||||
|
||||
Style text_style{
|
||||
.font = paint_style.font,
|
||||
.background = final_bg_color,
|
||||
.foreground = final_item_color};
|
||||
Style text_style{.font = paint_style.font, .background = final_bg_color, .foreground = final_item_color};
|
||||
|
||||
painter.draw_string(
|
||||
{r.location().x() + offset_x, r.location().y() + (r.size().height() - font_height) / 2},
|
||||
text_style,
|
||||
item->text);
|
||||
std::string_view full_text = item->text;
|
||||
std::string_view file_name = full_text;
|
||||
std::string_view file_size_text = "";
|
||||
|
||||
auto tab_pos = full_text.find('\t');
|
||||
if (tab_pos != std::string_view::npos) {
|
||||
file_size_text = full_text.substr(tab_pos + 1);
|
||||
file_name = full_text.substr(0, tab_pos);
|
||||
}
|
||||
|
||||
int available_width_px = rect.width() - offset_x;
|
||||
if (available_width_px <= 0) return;
|
||||
|
||||
size_t max_name_chars = available_width_px / char_width;
|
||||
if (max_name_chars == 0) return;
|
||||
|
||||
if (!file_size_text.empty() && max_name_chars > file_size_text.length()) {
|
||||
max_name_chars = max_name_chars - file_size_text.length() - 1;
|
||||
}
|
||||
|
||||
if (max_name_chars == 0) return;
|
||||
|
||||
can_scroll = (file_name.length() > max_name_chars);
|
||||
|
||||
std::string_view display_name = file_name;
|
||||
if (file_name.length() > max_name_chars) {
|
||||
if (highlighted()) {
|
||||
size_t max_scroll = file_name.length() - max_name_chars;
|
||||
size_t actual_offset = scroll_offset % (max_scroll + 1);
|
||||
display_name = file_name.substr(actual_offset, max_name_chars);
|
||||
} else {
|
||||
display_name = file_name.substr(0, max_name_chars);
|
||||
}
|
||||
}
|
||||
|
||||
Coord text_y = rect.location().y() + (rect.height() - line_height) / 2;
|
||||
|
||||
painter.draw_string({rect.location().x() + offset_x, text_y}, text_style, display_name);
|
||||
|
||||
if (!file_size_text.empty()) {
|
||||
int file_size_width = static_cast<int>(file_size_text.length()) * char_width;
|
||||
int file_size_x = rect.width() - file_size_width - margin_x;
|
||||
|
||||
// Csak akkor rajzoljuk ki, ha pozitív koordinátára esik és nem takarja el az ikont/nevet
|
||||
if (file_size_x > offset_x) {
|
||||
painter.draw_string({rect.location().x() + static_cast<Coord>(file_size_x), text_y}, text_style, file_size_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* MenuView **************************************************************/
|
||||
@@ -134,6 +190,20 @@ void MenuView::set_parent_rect(const Rect new_parent_rect) {
|
||||
update_items();
|
||||
}
|
||||
|
||||
void MenuView::increment_scroll() {
|
||||
if (menu_items.empty()) return;
|
||||
const size_t view_index = highlighted_item - offset;
|
||||
if (view_index >= menu_item_views.size()) {
|
||||
return;
|
||||
}
|
||||
// The MenuView checks if the currently highlighted item needs scrolling
|
||||
scroll_offset++;
|
||||
auto* view = item_view(view_index);
|
||||
if (view) {
|
||||
view->set_scroll_offset(scroll_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuView::on_tick_second() {
|
||||
if (more && blink)
|
||||
arrow_more.set_foreground(Theme::getInstance()->bg_darkest->foreground);
|
||||
@@ -141,8 +211,9 @@ void MenuView::on_tick_second() {
|
||||
arrow_more.set_foreground(Theme::getInstance()->bg_darkest->background);
|
||||
|
||||
blink = !blink;
|
||||
|
||||
arrow_more.set_dirty();
|
||||
|
||||
increment_scroll();
|
||||
}
|
||||
|
||||
void MenuView::clear() {
|
||||
@@ -153,6 +224,7 @@ void MenuView::clear() {
|
||||
menu_items.shrink_to_fit();
|
||||
highlighted_item = 0;
|
||||
offset = 0;
|
||||
reset_scroll();
|
||||
}
|
||||
|
||||
size_t MenuView::item_count() const {
|
||||
@@ -225,6 +297,8 @@ bool MenuView::set_highlighted(int32_t new_value) {
|
||||
item_view(highlighted_item - offset)->highlight();
|
||||
}
|
||||
|
||||
reset_scroll();
|
||||
|
||||
if (on_highlight)
|
||||
on_highlight();
|
||||
|
||||
|
||||
@@ -67,9 +67,13 @@ class MenuItemView : public Widget {
|
||||
void highlight();
|
||||
void unhighlight();
|
||||
|
||||
void set_scroll_offset(size_t offset);
|
||||
|
||||
private:
|
||||
MenuItem* item{nullptr};
|
||||
bool keep_highlight = false;
|
||||
size_t scroll_offset{0};
|
||||
bool can_scroll{false};
|
||||
};
|
||||
|
||||
class MenuView : public View {
|
||||
@@ -108,6 +112,10 @@ class MenuView : public View {
|
||||
void update_items();
|
||||
void on_tick_second();
|
||||
|
||||
// SCROLLING
|
||||
void increment_scroll();
|
||||
inline void reset_scroll() { scroll_offset = 0; }
|
||||
|
||||
bool keep_highlight{false};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
@@ -126,6 +134,8 @@ class MenuView : public View {
|
||||
size_t displayed_max{0};
|
||||
size_t highlighted_item{0};
|
||||
size_t offset{0};
|
||||
|
||||
size_t scroll_offset{0};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* static */ std::vector<std::unique_ptr<DynamicBitmap<16, 16>>> ExternalItemsMenuLoader::bitmaps;
|
||||
|
||||
// to save ram when entering an app
|
||||
void ExternalItemsMenuLoader::unload_external_items() {
|
||||
bitmaps.clear();
|
||||
bitmaps.shrink_to_fit();
|
||||
}
|
||||
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
|
||||
|
||||
// iterates over all possible ext apps-s, and if it is runnable on the current system, it'll call the callback, and pass minimal info. used to print to console, and for autostart setting's app list. where the minimal info is enough
|
||||
// please keep in sync with load_external_items
|
||||
@@ -103,9 +97,8 @@ void ExternalItemsMenuLoader::unload_external_items() {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
/* static */ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
bitmaps.clear();
|
||||
bitmaps.shrink_to_fit();
|
||||
|
||||
std::vector<GridItemEx> external_apps;
|
||||
|
||||
@@ -133,8 +126,8 @@ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_e
|
||||
|
||||
gridItem.color = Color((uint16_t)appInfo->icon_color);
|
||||
|
||||
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(appInfo->bitmap_data);
|
||||
gridItem.bitmap = dyn_bmp->bitmap();
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{appInfo->bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, appInfo, i]() {
|
||||
@@ -211,8 +204,8 @@ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_e
|
||||
if (versionMatches) {
|
||||
gridItem.color = Color((uint16_t)application_information.icon_color);
|
||||
|
||||
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(application_information.bitmap_data);
|
||||
gridItem.bitmap = dyn_bmp->bitmap();
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, app_location, filePath]() {
|
||||
@@ -262,8 +255,8 @@ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_e
|
||||
|
||||
gridItem.color = Color((uint16_t)application_information.icon_color);
|
||||
|
||||
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(application_information.bitmap_data);
|
||||
gridItem.bitmap = dyn_bmp->bitmap();
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, app_location, filePath]() {
|
||||
@@ -357,8 +350,6 @@ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_e
|
||||
if (checksum != EXT_APP_EXPECTED_CHECKSUM)
|
||||
return false;
|
||||
|
||||
nav.pop();
|
||||
nav.set_last_menu_went_deeper(true);
|
||||
application_information.externalAppEntry(nav);
|
||||
return true;
|
||||
}
|
||||
@@ -395,8 +386,7 @@ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_e
|
||||
}
|
||||
}
|
||||
|
||||
nav.set_last_menu_went_deeper(true);
|
||||
nav.replace<StandaloneView>(app_image);
|
||||
nav.push<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -410,8 +400,7 @@ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_e
|
||||
}
|
||||
}
|
||||
|
||||
nav.set_last_menu_went_deeper(true);
|
||||
nav.replace<StandaloneView>(app_image);
|
||||
nav.push<StandaloneView>(app_image);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,38 +33,24 @@
|
||||
#define EXT_APP_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <size_t Width, size_t Height>
|
||||
class DynamicBitmap {
|
||||
public:
|
||||
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8);
|
||||
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8); // one bit per pixel
|
||||
|
||||
// Main constructor
|
||||
DynamicBitmap(const uint8_t data[buffer_size])
|
||||
: _bitmap{{Width, Height}, _buffer.data()} {
|
||||
std::memcpy(_buffer.data(), data, buffer_size);
|
||||
: _buffer(buffer_size, 0),
|
||||
_bitmap{new Bitmap{{Width, Height}, &_buffer[0]}} {
|
||||
memcpy(&_buffer[0], data, buffer_size);
|
||||
}
|
||||
|
||||
DynamicBitmap(const DynamicBitmap& other)
|
||||
: _buffer(other._buffer),
|
||||
_bitmap{{Width, Height}, _buffer.data()} {}
|
||||
|
||||
DynamicBitmap(DynamicBitmap&& other) noexcept
|
||||
: _buffer(std::move(other._buffer)),
|
||||
_bitmap{{Width, Height}, _buffer.data()} {}
|
||||
|
||||
DynamicBitmap& operator=(const DynamicBitmap& other) = delete;
|
||||
DynamicBitmap& operator=(DynamicBitmap&& other) noexcept = delete;
|
||||
|
||||
// Destructor (Default is fine, no heap memory to free manually)
|
||||
~DynamicBitmap() = default;
|
||||
|
||||
const Bitmap* bitmap() const { return &_bitmap; }
|
||||
const Bitmap* bitmap() { return _bitmap.get(); }
|
||||
|
||||
private:
|
||||
// Order matters: _buffer must be declared before _bitmap
|
||||
// so it is initialized first and its .data() pointer is valid.
|
||||
std::array<uint8_t, buffer_size> _buffer{};
|
||||
Bitmap _bitmap{};
|
||||
// Allocating both members so the class is movable without invalidation.
|
||||
std::vector<uint8_t> _buffer;
|
||||
std::unique_ptr<Bitmap> _bitmap;
|
||||
};
|
||||
|
||||
class ExternalItemsMenuLoader {
|
||||
@@ -79,10 +65,9 @@ class ExternalItemsMenuLoader {
|
||||
static bool run_standalone_app(ui::NavigationView&, std::filesystem::path);
|
||||
static bool run_module_app(ui::NavigationView&, uint8_t*, size_t);
|
||||
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback, bool module_included = false);
|
||||
static void unload_external_items();
|
||||
|
||||
private:
|
||||
static std::vector<std::unique_ptr<DynamicBitmap<16, 16>>> bitmaps;
|
||||
static std::vector<DynamicBitmap<16, 16>> bitmaps;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -85,47 +85,44 @@ namespace ui {
|
||||
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
|
||||
const NavigationView::AppList NavigationView::appList = {
|
||||
/* HOME ******************************************************************/
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ReceiversMenuView>(nav); }},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TransmittersMenuView>(nav); }},
|
||||
{nullptr, "Transceiver", HOME, Color::cyan(), &bitmap_icon_transceivers, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TransceiversMenuView>(nav); }},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ReconView>(nav); }},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<CaptureAppView>(nav); }},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<PlaylistView>(nav); }},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<GlassView>(nav); }},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<UtilitiesMenuView>(nav); }},
|
||||
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<GamesMenuView>(nav); }},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SettingsMenuView>(nav); }},
|
||||
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{nullptr, "Transceiver", HOME, Color::cyan(), &bitmap_icon_transceivers, new ViewFactory<TransceiversMenuView>()},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
|
||||
/* RX ********************************************************************/
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ADSBRxView>(nav); }},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<AISAppView>(nav); }},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<APRSRXView>(nav); }},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<AnalogAudioView>(nav); }},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<BLERxView>(nav); }},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<POCSAGAppView>(nav); }},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SondeView>(nav); }},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SearchView>(nav); }},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SubGhzDView>(nav); }},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<WeatherView>(nav); }},
|
||||
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
/* TX ********************************************************************/
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<APRSTXView>(nav); }},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<BLETxView>(nav); }},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<EncodersView>(nav); }},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<RDSView>(nav); }},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TouchTunesView>(nav); }},
|
||||
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* TRX ********************************************************************/
|
||||
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<MicTXView>(nav); }},
|
||||
|
||||
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FileManagerView>(nav); }},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FrequencyManagerView>(nav); }},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<IQTrimView>(nav); }},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TextEditorView>(nav); }},
|
||||
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<DebugMenuView>(nav); }},
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FlashUtilityView>(nav); }},
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
|
||||
};
|
||||
|
||||
bool NavigationView::StartAppByName(const char* name) {
|
||||
@@ -133,7 +130,7 @@ bool NavigationView::StartAppByName(const char* name) {
|
||||
|
||||
for (const auto& app : appList) {
|
||||
if (app.id != nullptr && strcmp(app.id, name) == 0) {
|
||||
push_view(app.producer(*this));
|
||||
push_view(app.viewFactory->produce(*this));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -633,7 +630,6 @@ void NavigationView::pop(bool trigger_update) {
|
||||
|
||||
free_view();
|
||||
view_stack.pop_back();
|
||||
view_stack.shrink_to_fit();
|
||||
|
||||
// NB: These are executed _after_ the view has been
|
||||
// destroyed. The old view MUST NOT be referenced in
|
||||
@@ -757,22 +753,10 @@ void NavigationView::handle_autostart() {
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.menuLocation == loc) {
|
||||
const AppInfo* p_app = &app;
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
[&nav, p_app]() {
|
||||
NavigationView& local_nav = nav;
|
||||
if (p_app->menuLocation == HOME) {
|
||||
local_nav.store_last_menu_name(p_app->displayName);
|
||||
local_nav.set_last_menu_went_deeper(false);
|
||||
auto new_view = p_app->producer(local_nav);
|
||||
local_nav.push_view(std::move(new_view));
|
||||
} else {
|
||||
auto new_view = p_app->producer(local_nav);
|
||||
local_nav.pop();
|
||||
local_nav.set_last_menu_went_deeper(true);
|
||||
local_nav.push_view(std::move(new_view));
|
||||
}
|
||||
}},
|
||||
[&nav, &app]() {
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(0); //if i navigate away from any menu, turn off autoscan
|
||||
nav.push_view(app.viewFactory->produce(nav)); }},
|
||||
true);
|
||||
}
|
||||
};
|
||||
@@ -799,7 +783,7 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
return a.desired_position < b.desired_position;
|
||||
});
|
||||
|
||||
for (auto & gridItem : externalItems) {
|
||||
for (auto const& gridItem : externalItems) {
|
||||
if (gridItem.desired_position < 0) {
|
||||
grid.add_item(std::move(gridItem), true);
|
||||
} else {
|
||||
@@ -931,27 +915,6 @@ SystemView::SystemView(
|
||||
{{0, 0},
|
||||
{parent_rect.width(), status_view_height}});
|
||||
status_view.on_back = [this]() {
|
||||
if (this->navigation_view.view_stack_size() == 2) {
|
||||
const AppInfo* lastmenu = nullptr;
|
||||
const auto last_menu_name = this->navigation_view.get_last_menu_name();
|
||||
this->navigation_view.store_last_menu_name("");
|
||||
if (!last_menu_name.empty()) {
|
||||
for (const auto& app : NavigationView::appList) {
|
||||
if (app.displayName == last_menu_name) {
|
||||
lastmenu = &app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastmenu && this->navigation_view.get_last_menu_went_deeper()) {
|
||||
this->navigation_view.pop();
|
||||
this->navigation_view.push_view(lastmenu->producer(this->navigation_view));
|
||||
this->navigation_view.store_last_menu_name(lastmenu->displayName); // since we opened it again, store it again
|
||||
this->navigation_view.set_last_menu_went_deeper(false); // not yet
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->navigation_view.pop();
|
||||
};
|
||||
|
||||
@@ -968,7 +931,6 @@ SystemView::SystemView(
|
||||
navigation_view.on_view_changed = [this](const View& new_view) {
|
||||
if (!this->navigation_view.is_top()) {
|
||||
remove_child(&info_view);
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(0);
|
||||
} else {
|
||||
add_child(&info_view);
|
||||
info_view.refresh();
|
||||
@@ -1029,15 +991,16 @@ void SystemView::toggle_overlay() {
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = last_perf_counter_status;
|
||||
overlay_active = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SystemView::paint_overlay() {
|
||||
static bool last_paint_state = false;
|
||||
if (overlay_active) {
|
||||
// paint background only every other second
|
||||
if ((((chTimeNow() >> 10) & 0x01) == 0x01) == last_paint_state)
|
||||
return;
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
if (overlay_active == 1 && overlay)
|
||||
overlay->set_dirty();
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "lfsr_random.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include "external_app.hpp"
|
||||
#include "view_factory.hpp"
|
||||
#include "battery.hpp"
|
||||
|
||||
// for incrementing fake date when RTC battery is dead
|
||||
@@ -59,9 +60,6 @@ using namespace sd_card;
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NavigationView;
|
||||
using ViewProducer = std::unique_ptr<View> (*)(NavigationView&);
|
||||
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc);
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos, bool show_error_tile = true);
|
||||
|
||||
@@ -71,7 +69,7 @@ enum modal_t {
|
||||
ABORT
|
||||
};
|
||||
|
||||
// Should only be used as part of the appList in NavigationView.
|
||||
// Should only be used as part of the appList in NavigationView, the viewFactory will never be destroyed.
|
||||
class AppInfo {
|
||||
public:
|
||||
const char* id; // MUST be unique! Used by serial command to start the app so it also has to make sense
|
||||
@@ -79,7 +77,7 @@ class AppInfo {
|
||||
app_location_t menuLocation;
|
||||
Color iconColor;
|
||||
const Bitmap* icon;
|
||||
ViewProducer producer;
|
||||
ViewFactoryBase* viewFactory; // Never destroyed, and I believe it's ok ;) Having a unique_ptr here breaks the initializer list of appList
|
||||
};
|
||||
|
||||
struct AppInfoConsole {
|
||||
@@ -140,13 +138,6 @@ 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();
|
||||
|
||||
void store_last_menu_name(const std::string& name) { last_menu_name_ = name; }
|
||||
std::string get_last_menu_name() const { return last_menu_name_; }
|
||||
size_t view_stack_size() const { return view_stack.size(); }
|
||||
|
||||
bool get_last_menu_went_deeper() { return last_menu_went_deeper; }
|
||||
void set_last_menu_went_deeper(bool went_deeper) { last_menu_went_deeper = went_deeper; }
|
||||
|
||||
private:
|
||||
struct ViewState {
|
||||
std::unique_ptr<View> view;
|
||||
@@ -159,8 +150,6 @@ class NavigationView : public View {
|
||||
|
||||
void free_view();
|
||||
void update_view();
|
||||
std::string last_menu_name_{}; // this stores the last menu name, when we replace the menu with the app, we'll know, where to navigate back
|
||||
bool last_menu_went_deeper = false;
|
||||
};
|
||||
|
||||
/* Holds widgets and grows dynamically toward the left.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* 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 __VIEW_FACTORY_HPP__
|
||||
#define __VIEW_FACTORY_HPP__
|
||||
|
||||
#include "view_factory_base.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <typename T>
|
||||
class ViewFactory : public ViewFactoryBase {
|
||||
public:
|
||||
virtual std::unique_ptr<View> produce(NavigationView& nav) const override {
|
||||
return std::unique_ptr<View>(new T(nav));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif //__VIEW_FACTORY_HPP__
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* 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 "view_factory_base.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
ViewFactoryBase::~ViewFactoryBase() {}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* 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 __VIEW_FACTORY_BASE_HPP__
|
||||
#define __VIEW_FACTORY_BASE_HPP__
|
||||
|
||||
#include <memory>
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NavigationView;
|
||||
|
||||
class ViewFactoryBase {
|
||||
public:
|
||||
virtual ~ViewFactoryBase();
|
||||
virtual std::unique_ptr<View> produce(NavigationView& nav) const = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif //__VIEW_FACTORY_BASE_HPP__
|
||||
@@ -140,7 +140,7 @@
|
||||
// ============================================================================
|
||||
static fpga_mode_t current_mode = FPGA_MODE_OFF;
|
||||
// Cached register values for debug reads (since reads may require mode switch)
|
||||
static uint8_t fpga_reg_cache[6] = {0, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
static uint8_t fpga_reg_cache[7] = {0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// Context structure for SPIFI-based reading
|
||||
struct spifi_fpga_read_ctx {
|
||||
@@ -315,7 +315,7 @@ static void fpga_spi_write(uint8_t reg, uint8_t value) {
|
||||
// Public function to read FPGA register (callable from C++ application code)
|
||||
// Switches SPI mode, reads register, switches back
|
||||
uint8_t fpga_debug_register_read(uint8_t reg) {
|
||||
if (reg == 0 || reg > 5) return 0xFF;
|
||||
if (reg == 0 || reg > 6) return 0xFF;
|
||||
|
||||
uint8_t value;
|
||||
ssp1_set_mode_ice40();
|
||||
@@ -328,7 +328,7 @@ uint8_t fpga_debug_register_read(uint8_t reg) {
|
||||
|
||||
// Public function to write FPGA register (callable from C++ application code)
|
||||
void fpga_debug_register_write(uint8_t reg, uint8_t value) {
|
||||
if (reg == 0 || reg > 5) return;
|
||||
if (reg == 0 || reg > 6) return;
|
||||
|
||||
ssp1_set_mode_ice40();
|
||||
fpga_spi_write(reg, value);
|
||||
@@ -342,7 +342,7 @@ void fpga_debug_register_write(uint8_t reg, uint8_t value) {
|
||||
// ============================================================================
|
||||
|
||||
uint8_t fpga_register_read(uint8_t reg) {
|
||||
if (reg == 0 || reg > 5) return 0xFF;
|
||||
if (reg == 0 || reg > 6) return 0xFF;
|
||||
|
||||
ssp1_set_mode_ice40();
|
||||
uint8_t val = fpga_spi_read(reg);
|
||||
@@ -353,7 +353,7 @@ uint8_t fpga_register_read(uint8_t reg) {
|
||||
}
|
||||
|
||||
void fpga_register_write(uint8_t reg, uint8_t value) {
|
||||
if (reg == 0 || reg > 5) return;
|
||||
if (reg == 0 || reg > 6) return;
|
||||
|
||||
ssp1_set_mode_ice40();
|
||||
fpga_spi_write(reg, value);
|
||||
@@ -422,22 +422,22 @@ void fpga_rx_set_dc_adapt_rate(uint8_t rate) {
|
||||
/* TX Functions with mode assertion */
|
||||
void fpga_tx_set_nco_enable(bool enable) {
|
||||
if (current_mode != FPGA_MODE_TX) return;
|
||||
uint8_t val = fpga_register_read(FPGA_REG_SHARED_3);
|
||||
uint8_t val = fpga_register_read(FPGA_REG3_TX_NCO_CTRL);
|
||||
if (enable)
|
||||
val |= FPGA_TX_NCO_EN;
|
||||
else
|
||||
val &= ~FPGA_TX_NCO_EN;
|
||||
fpga_register_write(FPGA_REG_SHARED_3, val);
|
||||
fpga_register_write(FPGA_REG3_TX_NCO_CTRL, val);
|
||||
}
|
||||
|
||||
void fpga_tx_set_interpolation(uint8_t ratio) {
|
||||
if (current_mode != FPGA_MODE_TX) return;
|
||||
fpga_register_write(FPGA_REG_SHARED_4, ratio & FPGA_TX_INTERP_MASK);
|
||||
fpga_register_write(FPGA_REG_TX_INTERP, ratio & FPGA_TX_INTERP_MASK);
|
||||
}
|
||||
|
||||
void fpga_tx_set_phase_step(uint8_t step) {
|
||||
if (current_mode != FPGA_MODE_TX) return;
|
||||
fpga_register_write(FPGA_REG_SHARED_5, step);
|
||||
fpga_register_write(FPGA_REG_TX_PHASE_STEP, step);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -16,17 +16,21 @@ extern "C" {
|
||||
|
||||
#ifdef PRALINE
|
||||
|
||||
/* FPGA Register Map Address 0x03 (Dual Purpose) */
|
||||
/* RX path (legacy PRALINE software map kept for compatibility) */
|
||||
#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */
|
||||
#define FPGA_REG_TX_CONTROL 0x03 /* NCO_EN and TX flags (TX Mode) */
|
||||
|
||||
/* FPGA Register Map Address 0x04 (Shared) */
|
||||
#define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */
|
||||
#define FPGA_REG_TX_INTERP 0x04 /* Interpolation ratio (TX Mode) */
|
||||
|
||||
/* FPGA Register Map Address 0x05 (Shared) */
|
||||
#define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */
|
||||
#define FPGA_REG_TX_PHASE_STEP 0x05 /* NCO frequency step (TX Mode) */
|
||||
|
||||
/*
|
||||
* TX path must match the currently built PRALINE standard gateware in
|
||||
* hackrf/firmware/fpga/top/standard.py:
|
||||
* 0x04 tx_ctrl
|
||||
* 0x05 tx_intrp
|
||||
* 0x06 tx_pstep
|
||||
*/
|
||||
#define FPGA_REG_TX_CONTROL 0x04
|
||||
#define FPGA_REG_TX_INTERP 0x05
|
||||
#define FPGA_REG_TX_PHASE_STEP 0x06
|
||||
|
||||
/*
|
||||
* FPGA Operating Mode
|
||||
@@ -39,13 +43,21 @@ typedef enum {
|
||||
|
||||
/*
|
||||
* FPGA Register Addresses
|
||||
* Note: Registers 3-5 are dual-purpose (meaning depends on RX/TX mode)
|
||||
* NOTE:
|
||||
* The currently loaded PRALINE standard gateware uses:
|
||||
* 0x01 CTRL
|
||||
* 0x02 RX_DECIM
|
||||
* 0x03 RX_DIGITAL_GAIN (RX) / TX_NCO_CTRL (TX)
|
||||
* 0x04 TX_CTRL
|
||||
* 0x05 TX_INTRP
|
||||
* 0x06 TX_PSTEP
|
||||
*/
|
||||
#define FPGA_REG_CTRL 0x01 /* Control register */
|
||||
#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */
|
||||
#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose register */
|
||||
#define FPGA_REG_DECIM 0x02 /* RX decimation */
|
||||
#define FPGA_REG_SHARED_3 0x03 /* Legacy shared register */
|
||||
#define FPGA_REG_SHARED_4 0x04 /* Legacy shared register */
|
||||
#define FPGA_REG_SHARED_5 0x05 /* Legacy shared register */
|
||||
#define FPGA_REG_SHARED_6 0x06 /* TX phase step */
|
||||
|
||||
/*
|
||||
* Register 1 (CTRL) Bit Definitions
|
||||
@@ -77,7 +89,7 @@ typedef enum {
|
||||
#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/* TX Mode: Interpolation ratio */
|
||||
#define FPGA_REG4_TX_INTERP 0x04
|
||||
#define FPGA_REG4_TX_INTERP 0x05
|
||||
#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/*
|
||||
@@ -88,7 +100,7 @@ typedef enum {
|
||||
#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* TX Mode: NCO phase step (frequency) */
|
||||
#define FPGA_REG5_TX_PHASE_STEP 0x05
|
||||
#define FPGA_REG5_TX_PHASE_STEP 0x06
|
||||
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* Export default values so other methods can use them */
|
||||
|
||||
@@ -54,6 +54,12 @@ constexpr ClockFrequency max283x_reference_f = 40000000U;
|
||||
constexpr ClockFrequency mcu_clkin_og_f = 40000000U;
|
||||
constexpr ClockFrequency mcu_clkin_r9_f = 10000000U;
|
||||
|
||||
#ifdef PRALINE
|
||||
constexpr int8_t default_tx_gain_db = 16;
|
||||
#else
|
||||
constexpr int8_t default_tx_gain_db = 35;
|
||||
#endif
|
||||
|
||||
constexpr uint8_t si5351_i2c_address = 0x60;
|
||||
|
||||
/* Clock Generator */
|
||||
|
||||
@@ -1214,7 +1214,7 @@ class SSTVRXConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr SSTVRXConfigureMessage(
|
||||
const uint8_t code)
|
||||
: Message{id : ID::SSTVRXConfigure},
|
||||
: Message{ID::SSTVRXConfigure},
|
||||
code(code) {
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule hackrf updated: f526ddf4eb...216949a844
Reference in New Issue
Block a user