mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-20 14:39:01 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fba15c48e5 | |||
| 888634ce35 | |||
| 03131c8a0c | |||
| 605c9efa0c | |||
| aee54e5b2d | |||
| e33e697cca | |||
| d7d27d99e7 | |||
| 78d3480b68 | |||
| 70b42f4a56 |
+17
-1
@@ -1 +1,17 @@
|
||||
Please check [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/)
|
||||
# Security Policy
|
||||
|
||||
## Scope and Threat Model
|
||||
This repository hosts the codebase for an embedded system designed for security research. Because the system operates primarily offline, traditional network-based vulnerabilities are generally not applicable. However, we still welcome and review security reports concerning the code within this repository.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
If you discover a vulnerability within this tool itself, please submit a report. Please be aware that the maintainers do not promise assistance with obtaining CVE IDs or managing formal security advisories.
|
||||
|
||||
## Out of Scope
|
||||
* **Vulnerabilities found *using* this tool:** Please do not submit reports here for vulnerabilities you have discovered in other systems or targets using this tool. This channel is strictly for reporting vulnerabilities found *in* the tool itself.
|
||||
* **Other Repositories:** For security issues concerning related tools, such as MayhemHub, please submit your reports directly to their respective repositories.
|
||||
|
||||
## Research Attribution
|
||||
If you utilize this tool to successfully discover vulnerabilities in external systems, we would greatly appreciate it if you credit or mention our project in your published research or write-ups.
|
||||
|
||||
## Legal and Security Disclaimer
|
||||
For comprehensive guidelines regarding the legal and secure usage of this tool, as well as our full liability disclaimer, please refer to: [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/). Users are expected to comply with all applicable laws and regulations when using this software and/or hardware.
|
||||
|
||||
@@ -419,10 +419,10 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
|
||||
baseband::spectrum_streaming_stop();
|
||||
waterfall.stop();
|
||||
update_modulation(modulation);
|
||||
on_show_options_modulation();
|
||||
baseband::spectrum_streaming_start();
|
||||
waterfall.start();
|
||||
}
|
||||
|
||||
void AnalogAudioView::remove_options_widget() {
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "portapack.hpp"
|
||||
#include "battery.hpp"
|
||||
#include <cstring>
|
||||
#include "ui_settings.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -138,10 +140,8 @@ void BattinfoView::update_result() {
|
||||
}
|
||||
if ((valid_mask & battery::BatteryManagement::BATT_VALID_PERCENT) == battery::BatteryManagement::BATT_VALID_PERCENT) {
|
||||
text_method.set("IC");
|
||||
button_mode.set_text("Volt");
|
||||
} else {
|
||||
text_method.set("Voltage");
|
||||
button_mode.set_text("IC");
|
||||
}
|
||||
if (uichg) set_dirty();
|
||||
// to update status bar too, send message in behalf of batt manager
|
||||
@@ -158,8 +158,9 @@ BattinfoView::BattinfoView(NavigationView& nav)
|
||||
&text_current,
|
||||
&text_charge,
|
||||
&text_method,
|
||||
&button_mode,
|
||||
&button_settings,
|
||||
&button_exit,
|
||||
&text_capacity,
|
||||
// &text_cycles,
|
||||
// &text_warn,
|
||||
&text_ttef});
|
||||
@@ -167,17 +168,11 @@ BattinfoView::BattinfoView(NavigationView& nav)
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
button_mode.on_select = [this, &nav](Button&) {
|
||||
if (button_mode.text() == "IC") {
|
||||
battery::BatteryManagement::set_calc_override(false);
|
||||
persistent_memory::set_ui_override_batt_calc(false);
|
||||
button_mode.set_text("Volt");
|
||||
} else {
|
||||
battery::BatteryManagement::set_calc_override(true);
|
||||
persistent_memory::set_ui_override_batt_calc(true);
|
||||
button_mode.set_text("IC");
|
||||
}
|
||||
button_settings.on_select = [this, &nav](Button&) {
|
||||
nav.replace<SetBatteryView>();
|
||||
};
|
||||
text_capacity.set(to_string_dec_uint(persistent_memory::battery_cap_mah()) + " mAh");
|
||||
if (!persistent_memory::battery_cap_valid()) text_capacity.set_style(Theme::getInstance()->fg_red);
|
||||
update_result();
|
||||
if (thread == nullptr) thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, BattinfoView::static_fn, this);
|
||||
}
|
||||
|
||||
@@ -54,48 +54,53 @@ class BattinfoView : public View {
|
||||
int32_t current = 0;
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 1 * 16}, "Percent:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 2 * 16}, "Voltage:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 3 * 16}, "Method:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(1)}, "Percent:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(2)}, "Voltage:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(3)}, "Method:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(4)}, "Capacity:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Labels labels_opt{
|
||||
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
||||
// {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(5)}, "Current:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(6)}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(7)}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
||||
// {{UI_POS_X(2), UI_POS_Y(8)}, "Cycles:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(10)}, "Change settings:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Text text_percent{
|
||||
{13 * 8, 1 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(1), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_voltage{
|
||||
{13 * 8, 2 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(2), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_method{
|
||||
{13 * 8, 3 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(3), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_capacity{
|
||||
{UI_POS_X(13), UI_POS_Y(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_current{
|
||||
{13 * 8, 4 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(5), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_charge{
|
||||
{13 * 8, 5 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(6), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_ttef{
|
||||
{13 * 8, 6 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(7), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
|
||||
/* Text text_cycles{
|
||||
{13 * 8, 7 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
|
||||
Text text_warn{
|
||||
{1 * 8, 8 * 16, screen_width, 2 * 16},
|
||||
{UI_POS_X(1), UI_POS_Y(9), screen_width, UI_POS_HEIGHT(2)},
|
||||
""}; */
|
||||
|
||||
Button button_mode{
|
||||
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
|
||||
"Volt"};
|
||||
Button button_settings{
|
||||
{UI_POS_X(2), UI_POS_Y(11) + 5, UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
|
||||
"Settings"};
|
||||
|
||||
Button button_exit{
|
||||
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
|
||||
@@ -1160,6 +1160,8 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&field_battcap,
|
||||
&button_help_cap,
|
||||
&checkbox_overridebatt,
|
||||
&checkbox_battery_charge_hint});
|
||||
|
||||
@@ -1169,21 +1171,44 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
||||
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
|
||||
pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
|
||||
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
|
||||
if (((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) {
|
||||
pmem::set_battery_cap_mah(field_battcap.value());
|
||||
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||
if (dev && !dev->reInit()) {
|
||||
nav.display_modal("Error", "Battery gauge re-init failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_reset.on_select = [&nav, this](Button&) {
|
||||
auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||
if (dev->reset_learned())
|
||||
if (dev && dev->reset_learned())
|
||||
nav.display_modal("Reset", "Battery parameters reset");
|
||||
else
|
||||
nav.display_modal("Error", "Error parameter reset");
|
||||
};
|
||||
|
||||
button_help_cap.on_select = [&nav, this](Button&) {
|
||||
nav.display_modal("Battery Capacity",
|
||||
"Only change default, if you\n"
|
||||
" changed the battery!\n"
|
||||
"Defaults:\n"
|
||||
"H4 + Hackrf One: 2500\n"
|
||||
"H4 + Hackrf Pro: 2000\n"
|
||||
"H4Pro + Hackrf Pro: custom\n"
|
||||
"PortaRf: 3000\n"
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
|
||||
checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
|
||||
|
||||
field_battcap.set_value(pmem::battery_cap_mah());
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -1045,21 +1045,36 @@ class SetBatteryView : public View {
|
||||
private:
|
||||
int32_t selected = 0;
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(26), UI_POS_Y(0)}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(24), UI_POS_Y(1)}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
|
||||
/**/
|
||||
{{1 * 8, 6 * 16}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 7 * 16}, "when you charge", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X_CENTER(29), UI_POS_Y(4)}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(16), UI_POS_Y(5)}, "when you charge", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(17), UI_POS_Y(8)}, "Battery capacity", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(7), UI_POS_Y(9)}, "mAh", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Labels labels2{{{1 * 8, 11 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||
Labels labels2{{{UI_POS_X(1), UI_POS_Y(11)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_battcap{
|
||||
{UI_POS_X(1), UI_POS_Y(9)},
|
||||
5,
|
||||
{BATT_18650_MIN_MAH, BATT_18650_MAX_MAH},
|
||||
100,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_help_cap{
|
||||
{UI_POS_X(12), UI_POS_Y(9), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
|
||||
"Help",
|
||||
};
|
||||
|
||||
Checkbox checkbox_overridebatt{
|
||||
{2 * 8, 4 * 16},
|
||||
{UI_POS_X(2), UI_POS_Y(2)},
|
||||
23,
|
||||
"Override"};
|
||||
|
||||
Checkbox checkbox_battery_charge_hint{
|
||||
{2 * 8, 9 * 16},
|
||||
{UI_POS_X(2), UI_POS_Y(6)},
|
||||
23,
|
||||
"Charge hint"};
|
||||
|
||||
|
||||
+177
-46
@@ -26,6 +26,7 @@
|
||||
#include "ui_freqman.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "oversample.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
@@ -35,36 +36,112 @@ using portapack::memory::map::backup_ram;
|
||||
|
||||
namespace ui::external_app::detector_rx {
|
||||
|
||||
// Function to map the value from one range to another
|
||||
int32_t DetectorRxView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
|
||||
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
|
||||
}
|
||||
|
||||
void DetectorRxView::focus() {
|
||||
field_lna.focus();
|
||||
button_index.focus();
|
||||
}
|
||||
|
||||
DetectorRxView::~DetectorRxView() {
|
||||
// reset performance counters request to default
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
receiver_model.disable();
|
||||
audio::output::stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void DetectorRxView::on_timer() {
|
||||
freq_index++;
|
||||
if (freq_mode == 0 && freq_index >= tetra_uplink_monitoring_frequencies_hz.size()) freq_index = 0; // TETRA UP
|
||||
if (freq_mode == 1 && freq_index >= lora_monitoring_frequencies_hz.size()) freq_index = 0; // Lora
|
||||
if (freq_mode == 2 && freq_index >= remotes_monitoring_frequencies_hz.size()) freq_index = 0; // Remotes
|
||||
std::string DetectorRxView::format_freq_mhz(int64_t freq_hz) {
|
||||
int64_t mhz = freq_hz / 1000000;
|
||||
int64_t khz_frac = (freq_hz % 1000000) / 1000;
|
||||
return "< " + to_string_dec_uint(mhz) + "." + to_string_dec_uint(khz_frac, 3, '0') + " MHz >";
|
||||
}
|
||||
|
||||
if (freq_mode == 2)
|
||||
freq_ = remotes_monitoring_frequencies_hz[freq_index];
|
||||
else if (freq_mode == 1)
|
||||
freq_ = lora_monitoring_frequencies_hz[freq_index];
|
||||
else
|
||||
freq_ = tetra_uplink_monitoring_frequencies_hz[freq_index];
|
||||
receiver_model.set_target_frequency(freq_);
|
||||
void DetectorRxView::load_freqman() {
|
||||
freqman_load_options options{};
|
||||
options.load_freqs = true;
|
||||
options.load_ranges = true;
|
||||
options.load_hamradios = false;
|
||||
options.load_repeaters = false;
|
||||
|
||||
if (!load_freqman_file(freq_file_stem, frequency_list, options) || frequency_list.empty()) {
|
||||
button_file.set_text("No file!");
|
||||
button_index.set_text("");
|
||||
text_entry_desc.set("");
|
||||
button_freq.set_text("");
|
||||
frequency_list.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
current_index = 0;
|
||||
init_current_entry();
|
||||
button_file.set_text(freq_file_stem);
|
||||
}
|
||||
|
||||
void DetectorRxView::init_current_entry() {
|
||||
if (frequency_list.empty()) return;
|
||||
|
||||
auto& entry = *frequency_list[current_index];
|
||||
if (entry.type == freqman_type::Range) {
|
||||
minfreq = entry.frequency_a;
|
||||
maxfreq = entry.frequency_b;
|
||||
current_freq = minfreq;
|
||||
current_step = is_valid(entry.step) ? freqman_entry_get_step_value(entry.step) : DETECTOR_BW;
|
||||
} else {
|
||||
current_freq = entry.frequency_a;
|
||||
minfreq = current_freq;
|
||||
maxfreq = current_freq;
|
||||
current_step = DETECTOR_BW;
|
||||
}
|
||||
|
||||
update_entry_display();
|
||||
update_freq_display();
|
||||
receiver_model.set_target_frequency(current_freq);
|
||||
}
|
||||
|
||||
void DetectorRxView::update_entry_display() {
|
||||
if (frequency_list.empty()) return;
|
||||
|
||||
auto& entry = *frequency_list[current_index];
|
||||
button_index.set_text(
|
||||
to_string_dec_uint(current_index + 1) + "/" +
|
||||
to_string_dec_uint(frequency_list.size()));
|
||||
text_entry_desc.set(entry.description);
|
||||
}
|
||||
|
||||
void DetectorRxView::update_freq_display() {
|
||||
if (auto_scan && (minfreq != maxfreq)) {
|
||||
if (last_update_was_auto_range_type != 1) {
|
||||
button_freq.set_text("RANGE SCAN...");
|
||||
last_update_was_auto_range_type = 1;
|
||||
}
|
||||
} else {
|
||||
button_freq.set_text(format_freq_mhz(current_freq));
|
||||
last_update_was_auto_range_type = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DetectorRxView::on_timer() {
|
||||
if (frequency_list.empty() || !auto_scan) return;
|
||||
|
||||
auto& entry = *frequency_list[current_index];
|
||||
if (entry.type == freqman_type::Range) {
|
||||
current_freq += current_step;
|
||||
if (current_freq > maxfreq) {
|
||||
if (auto_advance) {
|
||||
current_index = (current_index + 1) % frequency_list.size();
|
||||
init_current_entry();
|
||||
return;
|
||||
}
|
||||
current_freq = minfreq;
|
||||
}
|
||||
receiver_model.set_target_frequency(current_freq);
|
||||
update_freq_display();
|
||||
} else if (auto_advance) {
|
||||
// Single frequency: advance to next entry
|
||||
current_index = (current_index + 1) % frequency_list.size();
|
||||
init_current_entry();
|
||||
}
|
||||
}
|
||||
|
||||
DetectorRxView::DetectorRxView(NavigationView& nav)
|
||||
@@ -75,21 +152,22 @@ DetectorRxView::DetectorRxView(NavigationView& nav)
|
||||
&field_vga,
|
||||
&field_rf_amp,
|
||||
&field_volume,
|
||||
&text_frequency,
|
||||
&freq_stats_rssi,
|
||||
&freq_stats_db,
|
||||
&button_file,
|
||||
&button_index,
|
||||
&text_entry_desc,
|
||||
&text_beep_squelch,
|
||||
&field_beep_squelch,
|
||||
&freq_stats_db,
|
||||
&freq_stats_rssi,
|
||||
&button_freq,
|
||||
&button_auto_scan,
|
||||
&button_auto_advance,
|
||||
&rssi,
|
||||
&rssi_graph,
|
||||
&field_mode,
|
||||
});
|
||||
|
||||
// activate vertical bar mode
|
||||
rssi.set_vertical_rssi(true);
|
||||
|
||||
freq_ = receiver_model.target_frequency();
|
||||
|
||||
field_beep_squelch.set_value(beep_squelch);
|
||||
field_beep_squelch.on_change = [this](int32_t v) {
|
||||
beep_squelch = v;
|
||||
@@ -97,29 +175,85 @@ DetectorRxView::DetectorRxView(NavigationView& nav)
|
||||
|
||||
rssi_graph.set_nb_columns(256);
|
||||
|
||||
// FILE button opens file picker
|
||||
button_file.on_select = [this](Button&) {
|
||||
auto open_view = nav_.push<FileLoadView>(".TXT");
|
||||
open_view->push_dir(freqman_dir);
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
if (new_file_path.native().find((u"/" / freqman_dir).native()) != 0) {
|
||||
button_file.set_text("Invalid file");
|
||||
return;
|
||||
}
|
||||
freq_file_stem = new_file_path.stem().string();
|
||||
load_freqman();
|
||||
};
|
||||
};
|
||||
|
||||
// Index encoder: rotate to change entry, click does nothing
|
||||
button_index.on_change = [this]() {
|
||||
if (frequency_list.empty()) return;
|
||||
int32_t delta = button_index.get_encoder_delta();
|
||||
button_index.set_encoder_delta(0);
|
||||
if (delta == 0) return;
|
||||
|
||||
// Use signed arithmetic for index wraparound to avoid unsigned promotion issues.
|
||||
const int32_t list_size = static_cast<int32_t>(frequency_list.size());
|
||||
int32_t idx = static_cast<int32_t>(current_index);
|
||||
if (delta > 0) {
|
||||
idx = (idx + 1) % list_size;
|
||||
} else {
|
||||
idx = (idx - 1 + list_size) % list_size;
|
||||
}
|
||||
current_index = static_cast<size_t>(idx);
|
||||
init_current_entry();
|
||||
};
|
||||
|
||||
// Frequency encoder: rotate to step within range
|
||||
button_freq.on_change = [this]() {
|
||||
if (frequency_list.empty()) return;
|
||||
auto& entry = *frequency_list[current_index];
|
||||
if (entry.type != freqman_type::Range) {
|
||||
button_freq.set_encoder_delta(0);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t delta = button_freq.get_encoder_delta();
|
||||
button_freq.set_encoder_delta(0);
|
||||
if (delta == 0) return;
|
||||
|
||||
if (delta > 0) {
|
||||
current_freq += current_step;
|
||||
if (current_freq > maxfreq) current_freq = minfreq;
|
||||
} else {
|
||||
current_freq -= current_step;
|
||||
if (current_freq < minfreq) current_freq = maxfreq;
|
||||
}
|
||||
receiver_model.set_target_frequency(current_freq);
|
||||
update_freq_display();
|
||||
};
|
||||
|
||||
// Auto-scan toggle
|
||||
button_auto_scan.on_select = [this](Button&) {
|
||||
auto_scan = !auto_scan;
|
||||
button_auto_scan.set_text(auto_scan ? "AUTOSCAN" : "NO SCAN");
|
||||
update_freq_display();
|
||||
};
|
||||
button_auto_scan.set_text(auto_scan ? "AUTOSCAN" : "NO SCAN");
|
||||
|
||||
// Auto-advance toggle
|
||||
button_auto_advance.on_select = [this](Button&) {
|
||||
auto_advance = !auto_advance;
|
||||
button_auto_advance.set_text(auto_advance ? "AUTOADV" : "NO ADV");
|
||||
};
|
||||
button_auto_advance.set_text(auto_advance ? "AUTOADV" : "NO ADV");
|
||||
|
||||
change_mode();
|
||||
rssi.set_peak(true, 3000);
|
||||
|
||||
// FILL STEP OPTIONS
|
||||
freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
|
||||
freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
|
||||
|
||||
field_mode.on_change = [this](size_t, int32_t value) {
|
||||
freq_mode = value;
|
||||
freq_index = 0;
|
||||
switch (value) {
|
||||
case 1: // Lora
|
||||
text_frequency.set(" 433, 868, 915 Mhz");
|
||||
break;
|
||||
case 2: // Remotes
|
||||
text_frequency.set(" 433, 315 Mhz");
|
||||
break;
|
||||
default:
|
||||
case 0: // TETRA UP
|
||||
text_frequency.set(" 380-390 Mhz");
|
||||
break;
|
||||
}
|
||||
};
|
||||
load_freqman();
|
||||
}
|
||||
|
||||
void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
@@ -130,25 +264,23 @@ void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
|
||||
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
|
||||
|
||||
// refresh db
|
||||
if (last_max_db != statistics.max_db) {
|
||||
last_max_db = statistics.max_db;
|
||||
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
|
||||
rssi.set_db(statistics.max_db);
|
||||
}
|
||||
// refresh rssi
|
||||
|
||||
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
|
||||
last_min_rssi = rssi_graph.get_graph_min();
|
||||
last_avg_rssi = rssi_graph.get_graph_avg();
|
||||
last_max_rssi = rssi_graph.get_graph_max();
|
||||
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
|
||||
freq_stats_rssi.set("RSSI:" + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
|
||||
}
|
||||
|
||||
if (statistics.max_db > beep_squelch) {
|
||||
baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
|
||||
}
|
||||
|
||||
} /* on_statistic_updates */
|
||||
}
|
||||
|
||||
size_t DetectorRxView::change_mode() {
|
||||
audio::output::stop();
|
||||
@@ -160,14 +292,13 @@ size_t DetectorRxView::change_mode() {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
|
||||
baseband::set_sample_rate(DETECTOR_BW, get_oversample_rate(DETECTOR_BW));
|
||||
// The radio needs to know the effective sampling rate.
|
||||
auto actual_sampling_rate = get_actual_sample_rate(DETECTOR_BW);
|
||||
receiver_model.set_sampling_rate(actual_sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
|
||||
|
||||
audio::set_rate(audio_sampling_rate);
|
||||
audio::output::start();
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume());
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
|
||||
+53
-72
@@ -29,6 +29,7 @@
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "radio_state.hpp"
|
||||
@@ -60,20 +61,35 @@ class DetectorRxView : public View {
|
||||
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
|
||||
size_t change_mode();
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void set_display_freq(int64_t freq);
|
||||
void on_timer();
|
||||
void load_freqman();
|
||||
void init_current_entry();
|
||||
void update_entry_display();
|
||||
void update_freq_display();
|
||||
std::string format_freq_mhz(int64_t freq_hz);
|
||||
|
||||
freqman_db frequency_list{};
|
||||
size_t current_index{0};
|
||||
int64_t current_freq{0};
|
||||
int64_t minfreq{0};
|
||||
int64_t maxfreq{0};
|
||||
int32_t current_step{DETECTOR_BW};
|
||||
std::string freq_file_stem{"DETECTOR"};
|
||||
bool auto_scan{true};
|
||||
bool auto_advance{false};
|
||||
uint8_t last_update_was_auto_range_type = -1;
|
||||
|
||||
uint8_t freq_index = 0;
|
||||
rf::Frequency freq_ = {433920000};
|
||||
int32_t beep_squelch = 0;
|
||||
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
|
||||
uint8_t freq_mode = 0;
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_detector",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"beep_squelch"sv, &beep_squelch},
|
||||
{"freq_file"sv, &freq_file_stem},
|
||||
{"auto_scan"sv, &auto_scan},
|
||||
{"auto_advance"sv, &auto_advance},
|
||||
}};
|
||||
|
||||
Labels labels{
|
||||
@@ -93,41 +109,55 @@ class DetectorRxView : public View {
|
||||
AudioVolumeField field_volume{
|
||||
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
|
||||
|
||||
OptionsField field_mode{
|
||||
{UI_POS_X(0), UI_POS_Y(1)},
|
||||
9,
|
||||
{
|
||||
{"TETRA UP", 0},
|
||||
{"Lora", 1},
|
||||
{"Remotes", 2},
|
||||
}};
|
||||
// Row 1: filename + auto advance
|
||||
Button button_file{
|
||||
{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
|
||||
""};
|
||||
|
||||
Text text_frequency{
|
||||
{UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
|
||||
Button button_auto_advance{
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(1), UI_POS_WIDTH(9), UI_POS_DEFAULT_HEIGHT},
|
||||
"NO ADV"};
|
||||
|
||||
// Row 2: index encoder + description + auto scan
|
||||
ButtonWithEncoder button_index{
|
||||
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
|
||||
""};
|
||||
|
||||
Text text_entry_desc{
|
||||
{UI_POS_X(4), UI_POS_Y(2), UI_POS_WIDTH(17), UI_POS_DEFAULT_HEIGHT},
|
||||
""};
|
||||
|
||||
Button button_auto_scan{
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(9), UI_POS_DEFAULT_HEIGHT},
|
||||
"AUTO SCAN"};
|
||||
|
||||
// Row 3: frequency encoder + bip level
|
||||
ButtonWithEncoder button_freq{
|
||||
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
|
||||
""};
|
||||
|
||||
Text text_beep_squelch{
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(3), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
|
||||
"Bip>"};
|
||||
|
||||
NumberField field_beep_squelch{
|
||||
{UI_POS_X_RIGHT(5), UI_POS_Y(2)},
|
||||
{UI_POS_X_RIGHT(5), UI_POS_Y(3)},
|
||||
4,
|
||||
{-100, 20},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
// RSSI: XX/XX/XXX
|
||||
Text freq_stats_rssi{
|
||||
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
|
||||
};
|
||||
|
||||
// Power: -XXX db
|
||||
// Row 4: Power + RSSI on same line
|
||||
Text freq_stats_db{
|
||||
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
|
||||
{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
|
||||
};
|
||||
|
||||
Text freq_stats_rssi{
|
||||
{UI_POS_X(15), UI_POS_Y(4), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
|
||||
};
|
||||
|
||||
// Row 5+: RSSI graph + vertical bar
|
||||
RSSIGraph rssi_graph{
|
||||
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)},
|
||||
};
|
||||
@@ -147,55 +177,6 @@ class DetectorRxView : public View {
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
|
||||
const std::vector<uint32_t> remotes_monitoring_frequencies_hz = {
|
||||
// Around 315 MHz (common for older remotes, key fobs in some regions)
|
||||
// Window centered on 315 MHz, covers 314.625 - 315.375 MHz
|
||||
315000000,
|
||||
|
||||
// Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
|
||||
// Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
|
||||
433920000,
|
||||
};
|
||||
const std::vector<uint32_t> lora_monitoring_frequencies_hz = {
|
||||
// EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
|
||||
// Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
|
||||
433375000, // Covers 433.000 - 433.750 MHz
|
||||
434125000, // Covers 433.750 - 434.500 MHz (includes 433.92 MHz)
|
||||
434875000, // Covers 434.500 - 435.250 MHz (covers up to 434.79 MHz)
|
||||
|
||||
// EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
|
||||
// Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
|
||||
867375000, // Covers 867.000 - 867.750 MHz
|
||||
868125000, // Covers 867.750 - 868.500 MHz
|
||||
868875000, // Covers 868.500 - 869.250 MHz (covers up to 868.6 MHz)
|
||||
|
||||
// US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
|
||||
// Providing a few sample windows around the 915 MHz area with 750kHz steps.
|
||||
// This band is wide; a full scan would require many more frequencies.
|
||||
914250000, // Covers 913.875 - 914.625 MHz
|
||||
915000000, // Covers 914.625 - 915.375 MHz (Centered on 915 MHz)
|
||||
915750000, // Covers 915.375 - 916.125 MHz
|
||||
};
|
||||
const std::vector<uint32_t> tetra_uplink_monitoring_frequencies_hz = {
|
||||
// Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
|
||||
// First center: 380,000,000 + 375,000 = 380,375,000 Hz
|
||||
// Last center: 380,375,000 + 13 * 750,000 = 390,125,000 Hz (14 frequencies total for this band)
|
||||
380375000, // Covers 380.000 - 380.750 MHz
|
||||
381125000, // Covers 380.750 - 381.500 MHz
|
||||
381875000, // Covers 381.500 - 382.250 MHz
|
||||
382625000, // Covers 382.250 - 383.000 MHz
|
||||
383375000, // Covers 383.000 - 383.750 MHz
|
||||
384125000, // Covers 383.750 - 384.500 MHz
|
||||
384875000, // Covers 384.500 - 385.250 MHz
|
||||
385625000, // Covers 385.250 - 386.000 MHz
|
||||
386375000, // Covers 386.000 - 386.750 MHz
|
||||
387125000, // Covers 386.750 - 387.500 MHz
|
||||
387875000, // Covers 387.500 - 388.250 MHz
|
||||
388625000, // Covers 388.250 - 389.000 MHz
|
||||
389375000, // Covers 389.000 - 389.750 MHz
|
||||
390125000, // Covers 389.750 - 390.500 MHz
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::detector_rx
|
||||
|
||||
+175
-22
@@ -98,6 +98,39 @@ void EPIRBTXAppView::on_timer() {
|
||||
}
|
||||
}
|
||||
|
||||
void EPIRBTXAppView::update_bpsk_frequency() {
|
||||
bool was_transmitting = false;
|
||||
if (transmitting && (am_enabled || transmitting_bpsk)) {
|
||||
// We need to stop transmission before changing frequency
|
||||
transmitter_model.disable();
|
||||
was_transmitting = true;
|
||||
}
|
||||
transmitter_model.set_target_frequency(bpsk_frequency);
|
||||
// Update displayed frequency
|
||||
tx_view.on_show();
|
||||
if (was_transmitting) {
|
||||
// Start over
|
||||
start_tx();
|
||||
}
|
||||
}
|
||||
|
||||
void EPIRBTXAppView::update_am_transmission() {
|
||||
if (am_enabled && transmitting && !transmitting_bpsk) {
|
||||
// Start am transmission
|
||||
// Restore am frequency
|
||||
epirb_tx_message.mode_bpsk = false;
|
||||
transmitter_model.set_target_frequency(am_frequency);
|
||||
// Send config to baseband
|
||||
baseband::set_epirb_tx_config(epirb_tx_message);
|
||||
// Start transmitting
|
||||
transmitter_model.enable();
|
||||
} else if (transmitting && !transmitting_bpsk) {
|
||||
// Stop am transmission
|
||||
transmitter_model.disable();
|
||||
tx_view.set_transmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
void EPIRBTXAppView::update_frame(bool updateConfig) {
|
||||
if (mode_file) {
|
||||
// In file mode, currently selected beacon has changed => load the new one
|
||||
@@ -136,17 +169,17 @@ void EPIRBTXAppView::update_frame(bool updateConfig) {
|
||||
}
|
||||
|
||||
void EPIRBTXAppView::update_config() {
|
||||
if (epirb_tx_message.mode_bpsk) {
|
||||
// Already in BPSK mode => backup bpsk frequency
|
||||
bpsk_frequency = transmitter_model.target_frequency();
|
||||
} else {
|
||||
if (!epirb_tx_message.mode_bpsk) {
|
||||
// Previously in AM mode => restore bpsk frequency
|
||||
transmitter_model.set_target_frequency(bpsk_frequency);
|
||||
// Update displayed frequency
|
||||
tx_view.on_show();
|
||||
}
|
||||
// Set mode to bpsk
|
||||
epirb_tx_message.mode_bpsk = true;
|
||||
transmitting_bpsk = true;
|
||||
// Set pre/post count
|
||||
epirb_tx_message.pre_count = (150 * TONES_SAMPLERATE) / 1000; // 150 ms
|
||||
epirb_tx_message.pre_count = (160 * TONES_SAMPLERATE) / 1000; // 160 ms carrier (COSPAS spec.)
|
||||
epirb_tx_message.post_count = (100 * TONES_SAMPLERATE) / 1000; // 100 ms
|
||||
// Send config to baseband
|
||||
baseband::set_epirb_tx_config(epirb_tx_message);
|
||||
@@ -178,15 +211,12 @@ void EPIRBTXAppView::stop_tx() {
|
||||
void EPIRBTXAppView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
(void)progress;
|
||||
if (done) {
|
||||
transmitting_bpsk = false;
|
||||
if (am_enabled) {
|
||||
// BPSK frame sent, switch back to 121.5 AM signal
|
||||
epirb_tx_message.mode_bpsk = false;
|
||||
// Backup bpsk frequency for next run
|
||||
bpsk_frequency = transmitter_model.target_frequency();
|
||||
// Restore am frequency
|
||||
transmitter_model.set_target_frequency(am_frequency);
|
||||
// Send config to baseband
|
||||
baseband::set_epirb_tx_config(epirb_tx_message);
|
||||
// Start am transmission
|
||||
update_am_transmission();
|
||||
} else {
|
||||
// End of BPSK frame
|
||||
transmitter_model.disable();
|
||||
@@ -262,6 +292,8 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
&checkbox_am,
|
||||
&field_am_frequency,
|
||||
&checkbox_send_on_change,
|
||||
&options_am_channel,
|
||||
&options_bpsk_channel,
|
||||
&tx_view});
|
||||
|
||||
text_beacon.set_style(Theme::getInstance()->fg_light);
|
||||
@@ -279,6 +311,11 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
options_mode.set_by_value(!mode_file);
|
||||
transmitter_model.set_target_frequency(bpsk_frequency);
|
||||
field_am_frequency.set_value(am_frequency);
|
||||
options_am_channel.set_by_value(am_channel);
|
||||
options_am_channel.set_style((am_channel == (uint8_t)AmChannel::REAL) ? Theme::getInstance()->fg_red : Theme::getInstance()->bg_darkest);
|
||||
manual_am_frequency = am_frequency;
|
||||
options_bpsk_channel.set_by_value(bpsk_channel);
|
||||
manual_bpsk_frequency = bpsk_frequency;
|
||||
field_delay.set_value(delay);
|
||||
options_beacon_type.set_by_value(beacon_type);
|
||||
options_beacon_protocol.set_by_value(beacon_protocol);
|
||||
@@ -292,23 +329,23 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
update_mode();
|
||||
update_location();
|
||||
|
||||
options_mode.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
mode_file = (index == 0);
|
||||
options_mode.on_change = [this](size_t, OptionsField::value_t value) {
|
||||
mode_file = (((BeaconMode)value) == BeaconMode::FILE);
|
||||
update_mode();
|
||||
update_frame();
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
options_beacon_type.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
beacon_params.type = (BeaconType)index;
|
||||
beacon_type = index;
|
||||
options_beacon_type.on_change = [this](size_t, OptionsField::value_t value) {
|
||||
beacon_params.type = (BeaconType)value;
|
||||
beacon_type = value;
|
||||
update_frame();
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
options_beacon_protocol.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
beacon_params.protocol = (BeaconProtocol)index;
|
||||
beacon_protocol = index;
|
||||
options_beacon_protocol.on_change = [this](size_t, OptionsField::value_t value) {
|
||||
beacon_params.protocol = (BeaconProtocol)value;
|
||||
beacon_protocol = value;
|
||||
update_frame();
|
||||
set_dirty();
|
||||
};
|
||||
@@ -320,6 +357,71 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
options_am_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
bool is_real = false;
|
||||
switch ((AmChannel)v) {
|
||||
case AmChannel::REAL:
|
||||
is_real = true;
|
||||
am_frequency = AM_REAL_FREQUENCY;
|
||||
break;
|
||||
case AmChannel::MANUAL:
|
||||
am_frequency = manual_am_frequency;
|
||||
break;
|
||||
default:
|
||||
v = (uint8_t)AmChannel::TEST;
|
||||
// fallthrough
|
||||
case AmChannel::TEST:
|
||||
am_frequency = AM_TEST_FREQUENCY;
|
||||
break;
|
||||
}
|
||||
// Actual frequency change will be done by field_am_frequency.on_change()
|
||||
field_am_frequency.set_value(am_frequency);
|
||||
am_channel = v;
|
||||
options_am_channel.set_style(is_real ? Theme::getInstance()->fg_red : Theme::getInstance()->bg_darkest);
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
options_bpsk_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
switch ((BpskChannel)v) {
|
||||
case BpskChannel::MANUAL:
|
||||
bpsk_frequency = manual_bpsk_frequency;
|
||||
break;
|
||||
case BpskChannel::B:
|
||||
bpsk_frequency = BPSK_FREQUENCY_B;
|
||||
break;
|
||||
case BpskChannel::C:
|
||||
bpsk_frequency = BPSK_FREQUENCY_C;
|
||||
break;
|
||||
case BpskChannel::F:
|
||||
bpsk_frequency = BPSK_FREQUENCY_F;
|
||||
break;
|
||||
case BpskChannel::G:
|
||||
bpsk_frequency = BPSK_FREQUENCY_G;
|
||||
break;
|
||||
case BpskChannel::J:
|
||||
bpsk_frequency = BPSK_FREQUENCY_J;
|
||||
break;
|
||||
case BpskChannel::K:
|
||||
bpsk_frequency = BPSK_FREQUENCY_K;
|
||||
break;
|
||||
case BpskChannel::N:
|
||||
bpsk_frequency = BPSK_FREQUENCY_N;
|
||||
break;
|
||||
case BpskChannel::O:
|
||||
bpsk_frequency = BPSK_FREQUENCY_O;
|
||||
break;
|
||||
default:
|
||||
v = (uint8_t)BpskChannel::HAM;
|
||||
// fallthrough
|
||||
case BpskChannel::HAM:
|
||||
bpsk_frequency = BPSK_FREQUENCY_HAM;
|
||||
break;
|
||||
}
|
||||
bpsk_channel = v;
|
||||
update_bpsk_frequency();
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
checkbox_beacon_internal.on_select = [this](Checkbox&, bool v) {
|
||||
beacon_internal = v;
|
||||
beacon_params.is_internal = v;
|
||||
@@ -358,7 +460,7 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
|
||||
field_am_frequency.on_change = [this](rf::Frequency freq) {
|
||||
am_frequency = freq;
|
||||
if (transmitting && !epirb_tx_message.mode_bpsk && am_enabled)
|
||||
if (transmitting && !transmitting_bpsk && am_enabled)
|
||||
// Update transmitter frequency
|
||||
transmitter_model.set_target_frequency(am_frequency);
|
||||
};
|
||||
@@ -397,6 +499,8 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
checkbox_am.on_select = [this](Checkbox&, bool v) {
|
||||
beacon_params.has_121_5 = v;
|
||||
am_enabled = v;
|
||||
update_am_transmission();
|
||||
// We update the additional location device data in the frame based on this
|
||||
if (!mode_file) update_frame(false);
|
||||
};
|
||||
|
||||
@@ -404,7 +508,21 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
field_am_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(field_am_frequency.value());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_am_frequency.set_value(f);
|
||||
switch (f) {
|
||||
case AM_REAL_FREQUENCY:
|
||||
am_channel = (uint8_t)AmChannel::REAL;
|
||||
break;
|
||||
case AM_TEST_FREQUENCY:
|
||||
am_channel = (uint8_t)AmChannel::TEST;
|
||||
break;
|
||||
default:
|
||||
manual_am_frequency = f;
|
||||
am_channel = (uint8_t)AmChannel::MANUAL;
|
||||
break;
|
||||
}
|
||||
// Actual frequency change will be done by options_am_channel.on_change()
|
||||
options_am_channel.set_by_value(am_channel);
|
||||
set_dirty();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -415,8 +533,43 @@ EPIRBTXAppView::EPIRBTXAppView(
|
||||
tx_view.on_edit_frequency = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
bpsk_frequency = f;
|
||||
switch (f) {
|
||||
case BPSK_FREQUENCY_HAM:
|
||||
bpsk_channel = (uint8_t)BpskChannel::HAM;
|
||||
break;
|
||||
case BPSK_FREQUENCY_B:
|
||||
bpsk_channel = (uint8_t)BpskChannel::B;
|
||||
break;
|
||||
case BPSK_FREQUENCY_C:
|
||||
bpsk_channel = (uint8_t)BpskChannel::C;
|
||||
break;
|
||||
case BPSK_FREQUENCY_F:
|
||||
bpsk_channel = (uint8_t)BpskChannel::F;
|
||||
break;
|
||||
case BPSK_FREQUENCY_G:
|
||||
bpsk_channel = (uint8_t)BpskChannel::G;
|
||||
break;
|
||||
case BPSK_FREQUENCY_J:
|
||||
bpsk_channel = (uint8_t)BpskChannel::J;
|
||||
break;
|
||||
case BPSK_FREQUENCY_K:
|
||||
bpsk_channel = (uint8_t)BpskChannel::K;
|
||||
break;
|
||||
case BPSK_FREQUENCY_N:
|
||||
bpsk_channel = (uint8_t)BpskChannel::N;
|
||||
break;
|
||||
case BPSK_FREQUENCY_O:
|
||||
bpsk_channel = (uint8_t)BpskChannel::O;
|
||||
break;
|
||||
default:
|
||||
bpsk_channel = (uint8_t)BpskChannel::MANUAL;
|
||||
manual_bpsk_frequency = bpsk_frequency;
|
||||
break;
|
||||
}
|
||||
// Actual frequency change will be done by options_bpsk_channel.on_change()
|
||||
options_bpsk_channel.set_by_value(bpsk_channel);
|
||||
set_dirty();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+87
-15
@@ -37,8 +37,26 @@
|
||||
#define BEACON_HEXA_HALF_SIZE 18
|
||||
#define BEACON_SIZE 18
|
||||
|
||||
#define AM_TEST_FREQUENCY 121375000
|
||||
#define AM_REAL_FREQUENCY 121500000
|
||||
|
||||
#define BPSK_FREQUENCY_HAM 433025000
|
||||
#define BPSK_FREQUENCY_B 406025000
|
||||
#define BPSK_FREQUENCY_C 406028000
|
||||
#define BPSK_FREQUENCY_F 406037000
|
||||
#define BPSK_FREQUENCY_G 406040000
|
||||
#define BPSK_FREQUENCY_J 406049000
|
||||
#define BPSK_FREQUENCY_K 406052000
|
||||
#define BPSK_FREQUENCY_N 406061000
|
||||
#define BPSK_FREQUENCY_O 406064000
|
||||
|
||||
namespace ui::external_app::epirb_tx {
|
||||
|
||||
enum class BeaconMode {
|
||||
FILE = 0,
|
||||
MODE_MANUAL = 1
|
||||
};
|
||||
|
||||
enum class BeaconType {
|
||||
EPIRB = 0,
|
||||
ELT = 1,
|
||||
@@ -51,6 +69,25 @@ enum class BeaconProtocol {
|
||||
NATIONAL = 2
|
||||
};
|
||||
|
||||
enum class AmChannel {
|
||||
TEST = 0,
|
||||
REAL = 1,
|
||||
MANUAL = 2
|
||||
};
|
||||
|
||||
enum class BpskChannel {
|
||||
HAM = 0,
|
||||
B = 1,
|
||||
C = 2,
|
||||
F = 3,
|
||||
G = 4,
|
||||
J = 5,
|
||||
K = 6,
|
||||
N = 7,
|
||||
O = 8,
|
||||
MANUAL = 10
|
||||
};
|
||||
|
||||
struct Location {
|
||||
std::string locator;
|
||||
bool south;
|
||||
@@ -95,6 +132,8 @@ class EPIRBTXAppView : public View {
|
||||
std::string frame_to_hex_string(bool start);
|
||||
void generate_frame(BeaconParams params);
|
||||
void update_frame(bool updateConfig = true);
|
||||
void update_bpsk_frequency();
|
||||
void update_am_transmission();
|
||||
void update_mode();
|
||||
void update_location(bool updateLocatorField = true);
|
||||
|
||||
@@ -114,25 +153,33 @@ class EPIRBTXAppView : public View {
|
||||
// Frequency of the transmitter before starting the app (used to restore frequency when leaving)
|
||||
rf::Frequency original_frequency{0};
|
||||
// Frequency of the AM emergency signal
|
||||
rf::Frequency am_frequency{121500000};
|
||||
rf::Frequency am_frequency{AM_TEST_FREQUENCY};
|
||||
// Frequency of the 406 MHz BPSK signal
|
||||
rf::Frequency bpsk_frequency{406025000};
|
||||
rf::Frequency bpsk_frequency{BPSK_FREQUENCY_HAM};
|
||||
// Selected am channel
|
||||
uint8_t am_channel{(uint8_t)AmChannel::TEST};
|
||||
// Selected bpsk channel
|
||||
uint8_t bpsk_channel{(uint8_t)BpskChannel::HAM};
|
||||
// Manual AM frequency value
|
||||
rf::Frequency manual_am_frequency{AM_TEST_FREQUENCY};
|
||||
// Manual BPSK frequency value
|
||||
rf::Frequency manual_bpsk_frequency{BPSK_FREQUENCY_HAM};
|
||||
|
||||
// True when using a beacon from the BEACONS.TXT file
|
||||
bool mode_file{true};
|
||||
bool mode_file{false};
|
||||
// True when looping on sending beacons is enabled
|
||||
bool loop_enabled{true};
|
||||
// True if AM emergency signal transmission is enabled
|
||||
bool am_enabled{true};
|
||||
// True if we want to send a new frame each time the user changes the current beacon
|
||||
bool send_on_change{false};
|
||||
bool send_on_change{true};
|
||||
// The current locator string
|
||||
std::string locator{"JN03RO"};
|
||||
// The delay between each frame when on loop mode
|
||||
uint32_t delay{50};
|
||||
uint8_t beacon_type{0};
|
||||
uint8_t beacon_type{(uint8_t)BeaconType::EPIRB};
|
||||
// Currently selected beacon protocol
|
||||
uint8_t beacon_protocol{0};
|
||||
uint8_t beacon_protocol{(uint8_t)BeaconProtocol::USER};
|
||||
// Currently selected beacon country
|
||||
uint32_t beacon_country{227};
|
||||
// Current beacon's internal state (true for internal location system)
|
||||
@@ -150,6 +197,8 @@ class EPIRBTXAppView : public View {
|
||||
{"sbeacon"sv, &selected_beacon},
|
||||
{"amfreq"sv, &am_frequency},
|
||||
{"bpskfreq"sv, &bpsk_frequency},
|
||||
{"amchan"sv, &am_channel},
|
||||
{"bpskchan"sv, &bpsk_channel},
|
||||
{"loop"sv, &loop_enabled},
|
||||
{"delay"sv, &delay},
|
||||
{"file"sv, &mode_file},
|
||||
@@ -166,6 +215,8 @@ class EPIRBTXAppView : public View {
|
||||
uint32_t last_frame_time{0};
|
||||
// True when transmission is enabled
|
||||
bool transmitting{false};
|
||||
// True when transmitting a BPSK frame
|
||||
bool transmitting_bpsk{false};
|
||||
// True when currently looping on sending beacons
|
||||
bool loop{false};
|
||||
|
||||
@@ -179,7 +230,9 @@ class EPIRBTXAppView : public View {
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Source:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(6)}, "Frame:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(10)}, "Next frame in s.", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(12)}, "AM frequency MHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(12)}, "AM frequency: MHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(14)}, "AM chan.:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(15)}, "BPSK chan.:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(17), UI_POS_Y(9)}, "s.", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
// For file mode
|
||||
@@ -231,15 +284,15 @@ class EPIRBTXAppView : public View {
|
||||
OptionsField options_beacon_type{
|
||||
{UI_POS_X(9), UI_POS_Y(1)},
|
||||
7,
|
||||
{{"EPIRB", 0},
|
||||
{"ELT", 1},
|
||||
{"PLB", 2}}};
|
||||
{{"EPIRB", (uint8_t)BeaconType::EPIRB},
|
||||
{"ELT", (uint8_t)BeaconType::ELT},
|
||||
{"PLB", (uint8_t)BeaconType::PLB}}};
|
||||
OptionsField options_beacon_protocol{
|
||||
{UI_POS_X(9 + 7), UI_POS_Y(1)},
|
||||
30,
|
||||
{{"User", 0},
|
||||
{"Standard", 1},
|
||||
{"National", 2}}};
|
||||
{{"User", (uint8_t)BeaconProtocol::USER},
|
||||
{"Standard", (uint8_t)BeaconProtocol::STANDARD},
|
||||
{"National", (uint8_t)BeaconProtocol::NATIONAL}}};
|
||||
OptionsField options_beacon_country{
|
||||
{UI_POS_X(9), UI_POS_Y(2)},
|
||||
7,
|
||||
@@ -261,8 +314,8 @@ class EPIRBTXAppView : public View {
|
||||
OptionsField options_mode{
|
||||
{UI_POS_X(7), UI_POS_Y(0)},
|
||||
30,
|
||||
{{"File (BEACONS.TXT)", 0},
|
||||
{"Manual (Editor)", 1}}};
|
||||
{{"File (BEACONS.TXT)", (uint8_t)BeaconMode::FILE},
|
||||
{"Manual (Editor)", (uint8_t)BeaconMode::MODE_MANUAL}}};
|
||||
|
||||
// Frame content
|
||||
Text text_frame{
|
||||
@@ -305,6 +358,25 @@ class EPIRBTXAppView : public View {
|
||||
"START"};
|
||||
const Style& style_tx_start = *Theme::getInstance()->fg_green;
|
||||
const Style& style_tx_stop = *Theme::getInstance()->fg_red;
|
||||
OptionsField options_am_channel{
|
||||
{UI_POS_X(11), UI_POS_Y(14)},
|
||||
20,
|
||||
{{"121.375 MHz (Test)", 0},
|
||||
{"121.500 MHz /!\\Real", 1},
|
||||
{"Manual", 2}}};
|
||||
OptionsField options_bpsk_channel{
|
||||
{UI_POS_X(11), UI_POS_Y(15)},
|
||||
20,
|
||||
{{"433.025 MHz (Ham)", (uint8_t)BpskChannel::HAM},
|
||||
{"406.025 MHz (B)", (uint8_t)BpskChannel::B},
|
||||
{"406.028 MHz (C)", (uint8_t)BpskChannel::C},
|
||||
{"406.037 MHz (F)", (uint8_t)BpskChannel::F},
|
||||
{"406.040 MHz (G)", (uint8_t)BpskChannel::G},
|
||||
{"406.049 MHz (J)", (uint8_t)BpskChannel::J},
|
||||
{"406.052 MHz (K)", (uint8_t)BpskChannel::K},
|
||||
{"406.061 MHz (N)", (uint8_t)BpskChannel::N},
|
||||
{"406.064 MHz (O)", (uint8_t)BpskChannel::O},
|
||||
{"Manual", (uint8_t)BpskChannel::MANUAL}}};
|
||||
|
||||
// Transmitter view
|
||||
TransmitterView tx_view{
|
||||
|
||||
+12
-14
@@ -3,13 +3,11 @@
|
||||
/*
|
||||
* FPV RX — how frequency search and lock work
|
||||
*
|
||||
* 1. Power metric: We use channelized power (baseband IQ magnitude² in the
|
||||
* capture bandwidth), not raw RF RSSI. Stats come from ChannelStatsCollector
|
||||
* over filtered IQ, so we see power in the tuned channel, not wideband.
|
||||
* 1. Power metric: We use channelized power that portapack RSSI algo included.
|
||||
*
|
||||
* 2. Scanning: We step through FPV bands/channels (A/B/E/F/R, 8 ch each). When
|
||||
* power on the current channel exceeds the detect threshold, we enter
|
||||
* "Candidate" and run verification — we do not lock on a single peak.
|
||||
* "Candidate" and run verification - with some edge detecting algo.
|
||||
*
|
||||
* 3. Verification (making sure the drone is on that freq):
|
||||
* - Multiple samples: verify_hits / verify_misses over several updates.
|
||||
@@ -177,7 +175,7 @@ void FpvDetectView::reset_detector(bool retune_current) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FpvDetectView::is_possible_analog_carrier(const ChannelStatistics& statistics) const {
|
||||
bool FpvDetectView::is_possible_freq_spike(const ChannelStatistics& statistics) const {
|
||||
return statistics.max_db >= detect_threshold_db();
|
||||
}
|
||||
|
||||
@@ -392,17 +390,17 @@ void FpvDetectView::update_state_badge() {
|
||||
text_state.set("SCANNING");
|
||||
break;
|
||||
case DetectState::Candidate:
|
||||
text_state.set("VERIFY FPV");
|
||||
text_state.set("CHECKING");
|
||||
break;
|
||||
case DetectState::Locked:
|
||||
text_state.set("DRONE FOUND");
|
||||
text_state.set("FREQ FOUND");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FpvDetectView::update_confidence_text() {
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "Conf %u%%", static_cast<unsigned>(candidate_confidence_));
|
||||
std::snprintf(buf, sizeof(buf), "Posi %u%%", static_cast<unsigned>(candidate_confidence_));
|
||||
text_confidence.set(buf);
|
||||
}
|
||||
|
||||
@@ -412,21 +410,21 @@ void FpvDetectView::update_status_text() {
|
||||
switch (detect_state_) {
|
||||
case DetectState::Scanning:
|
||||
if (band_mode < FPV_NUM_BANDS) {
|
||||
std::snprintf(buf, sizeof(buf), "Scanning band %c for analog FPV", band_labels[band_mode]);
|
||||
std::snprintf(buf, sizeof(buf), "Scanning band %c", band_labels[band_mode]);
|
||||
} else {
|
||||
std::snprintf(buf, sizeof(buf), "Scanning all FPV bands");
|
||||
std::snprintf(buf, sizeof(buf), "Scanning all from list");
|
||||
}
|
||||
break;
|
||||
|
||||
case DetectState::Candidate:
|
||||
std::snprintf(buf, sizeof(buf), "VERIFYING %c%d %ld MHz",
|
||||
std::snprintf(buf, sizeof(buf), "CHKING %c%d %ld MHz",
|
||||
band_labels[candidate_band_],
|
||||
static_cast<int>(candidate_ch_ + 1),
|
||||
static_cast<long>(fpv_frequencies[candidate_band_][candidate_ch_] / 1000000LL));
|
||||
break;
|
||||
|
||||
case DetectState::Locked:
|
||||
std::snprintf(buf, sizeof(buf), "!!! DRONE FOUND !!! %c%d %ld",
|
||||
std::snprintf(buf, sizeof(buf), "FREQ FOUND %c%d %ld",
|
||||
band_labels[candidate_band_],
|
||||
static_cast<int>(candidate_ch_ + 1),
|
||||
static_cast<long>(fpv_frequencies[candidate_band_][candidate_ch_] / 1000000LL));
|
||||
@@ -453,7 +451,7 @@ void FpvDetectView::update_detail_text() {
|
||||
break;
|
||||
|
||||
case DetectState::Locked:
|
||||
std::snprintf(buf, sizeof(buf), "LOCKED %c%d conf %u%% hold %u",
|
||||
std::snprintf(buf, sizeof(buf), "LOCKED %c%d posi %u%% hold %u",
|
||||
band_labels[candidate_band_],
|
||||
static_cast<int>(candidate_ch_ + 1),
|
||||
static_cast<unsigned>(candidate_confidence_),
|
||||
@@ -605,7 +603,7 @@ void FpvDetectView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
|
||||
switch (detect_state_) {
|
||||
case DetectState::Scanning:
|
||||
if (is_possible_analog_carrier(statistics)) {
|
||||
if (is_possible_freq_spike(statistics)) {
|
||||
enter_candidate(statistics);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -72,7 +72,7 @@ class FpvDetectView : public View {
|
||||
void step_scan();
|
||||
void reset_detector(bool retune_current);
|
||||
|
||||
bool is_possible_analog_carrier(const ChannelStatistics& statistics) const;
|
||||
bool is_possible_freq_spike(const ChannelStatistics& statistics) const;
|
||||
void enter_candidate(const ChannelStatistics& statistics);
|
||||
void evaluate_candidate_sample(const ChannelStatistics& statistics);
|
||||
void enter_lock();
|
||||
@@ -162,7 +162,7 @@ class FpvDetectView : public View {
|
||||
Text text_freq{{UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT}, ""};
|
||||
|
||||
Text text_state{{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(12), UI_POS_DEFAULT_HEIGHT}, "SCANNING"};
|
||||
Text text_confidence{{UI_POS_X(12), UI_POS_Y(2), UI_POS_WIDTH(8), UI_POS_DEFAULT_HEIGHT}, "Conf 0%"};
|
||||
Text text_confidence{{UI_POS_X(12), UI_POS_Y(2), UI_POS_WIDTH(8), UI_POS_DEFAULT_HEIGHT}, "Posi 0%"};
|
||||
Text text_detect_label{{UI_POS_X_RIGHT(10), UI_POS_Y(2), UI_POS_WIDTH(5), UI_POS_DEFAULT_HEIGHT}, "Thr>"};
|
||||
|
||||
NumberField field_detect_threshold{
|
||||
@@ -176,8 +176,8 @@ class FpvDetectView : public View {
|
||||
Text freq_stats_rssi{{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT}, "RSSI 0/0/0"};
|
||||
Text freq_stats_db{{UI_POS_X_RIGHT(14), UI_POS_Y(3), UI_POS_WIDTH(14), UI_POS_DEFAULT_HEIGHT}, "PWR -120 dB"};
|
||||
|
||||
Text text_status{{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(30), UI_POS_DEFAULT_HEIGHT}, "SCANNING FOR ANALOG FPV"};
|
||||
Text text_detail{{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH(30), UI_POS_DEFAULT_HEIGHT}, "Waiting for FPV-like carrier"};
|
||||
Text text_status{{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(30), UI_POS_DEFAULT_HEIGHT}, "SCANNING FOR FREQS"};
|
||||
Text text_detail{{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH(30), UI_POS_DEFAULT_HEIGHT}, "Waiting for FPV FREQ"};
|
||||
|
||||
RSSIGraph rssi_graph{{UI_POS_X(0), UI_POS_Y(6), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(7)}};
|
||||
RSSI rssi{{UI_POS_X_RIGHT(5), UI_POS_Y(6), UI_POS_WIDTH(5), UI_POS_HEIGHT_REMAINING(7)}};
|
||||
|
||||
+75
-42
@@ -27,6 +27,9 @@ MorseRadiotxView::MorseRadiotxView(ui::NavigationView& nav)
|
||||
&chk_trans,
|
||||
&bandwidth,
|
||||
&chk_callsgn,
|
||||
&chk_loop,
|
||||
&wait_time,
|
||||
&progressbar,
|
||||
&txt_last,
|
||||
&console_text,
|
||||
&btn_clear,
|
||||
@@ -69,6 +72,7 @@ MorseRadiotxView::MorseRadiotxView(ui::NavigationView& nav)
|
||||
options_mode.set_selected_index(current_mode, true);
|
||||
tone_.set_value(tone, true);
|
||||
wpm_.set_value(wpm, true);
|
||||
wait_time.set_value(1, true);
|
||||
bandwidth.set_value(band, true);
|
||||
|
||||
btn_ptt.on_select = [this](Button&) {
|
||||
@@ -156,6 +160,14 @@ MorseRadiotxView::MorseRadiotxView(ui::NavigationView& nav)
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
};
|
||||
|
||||
chk_trans.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) chk_loop.set_value(false);
|
||||
};
|
||||
|
||||
chk_loop.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) chk_trans.set_value(false);
|
||||
};
|
||||
}
|
||||
|
||||
MorseRadiotxView::~MorseRadiotxView() {
|
||||
@@ -197,19 +209,15 @@ MorseRadiotxView::MorseTimings MorseRadiotxView::calculate_morse_timings(uint32_
|
||||
|
||||
void MorseRadiotxView::transmit_morse_message() {
|
||||
std::string full_message = "";
|
||||
// cal sign
|
||||
if (chk_callsgn.value() && !call_sign.empty()) {
|
||||
full_message = call_sign;
|
||||
if (!chk_trans.value() && !msg_buffer.empty()) {
|
||||
full_message += " " + msg_buffer;
|
||||
}
|
||||
} else {
|
||||
if (!chk_trans.value()) full_message = msg_buffer;
|
||||
}
|
||||
|
||||
if (full_message.empty() && !chk_trans.value()) {
|
||||
if (chk_trans.value()) {
|
||||
ptt_button_visibility(false);
|
||||
return;
|
||||
} else {
|
||||
full_message = msg_buffer;
|
||||
if (chk_callsgn.value() && !call_sign.empty()) { // call sign
|
||||
full_message += " " + call_sign;
|
||||
}
|
||||
}
|
||||
|
||||
// enable transmit
|
||||
@@ -219,46 +227,63 @@ void MorseRadiotxView::transmit_morse_message() {
|
||||
ui_toggle();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < full_message.length(); i++) {
|
||||
if (chThdShouldTerminate()) break;
|
||||
uint8_t loop_seconds = static_cast<uint8_t>(wait_time.value());
|
||||
progressbar.set_max(loop_seconds * 2);
|
||||
|
||||
char c = full_message[i];
|
||||
do {
|
||||
for (size_t i = 0; i < full_message.length(); i++) {
|
||||
if (chThdShouldTerminate()) break;
|
||||
|
||||
std::string s_char(1, c);
|
||||
// space
|
||||
if (c == ' ') {
|
||||
console_text.write(" ");
|
||||
chThdSleepMilliseconds(current_timings.word_gap);
|
||||
continue;
|
||||
}
|
||||
char c = full_message[i];
|
||||
|
||||
const char* pattern = morse_decoder_.get_morse_pattern(c);
|
||||
std::string s_char(1, c);
|
||||
// space
|
||||
if (c == ' ') {
|
||||
console_text.write(" ");
|
||||
chThdSleepMilliseconds(current_timings.word_gap);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pattern != nullptr) {
|
||||
txt_last.set(pattern);
|
||||
const char* pattern = morse_decoder_.get_morse_pattern(c);
|
||||
|
||||
// write blue char to console
|
||||
console_text.write(STR_COLOR_BLUE + s_char);
|
||||
if (pattern != nullptr) {
|
||||
txt_last.set(pattern);
|
||||
|
||||
// Morze (dih/dah) send
|
||||
for (int j = 0; pattern[j] != '\0'; j++) {
|
||||
baseband::set_morsetx_key(true);
|
||||
if (pattern[j] == '.') {
|
||||
chThdSleepMilliseconds(current_timings.dot_ms);
|
||||
} else if (pattern[j] == '-') {
|
||||
chThdSleepMilliseconds(current_timings.dash_ms);
|
||||
// write blue char to console
|
||||
console_text.write(STR_COLOR_BLUE + s_char);
|
||||
|
||||
// Morze (dih/dah) send
|
||||
for (int j = 0; pattern[j] != '\0'; j++) {
|
||||
baseband::set_morsetx_key(true);
|
||||
if (pattern[j] == '.') {
|
||||
chThdSleepMilliseconds(current_timings.dot_ms);
|
||||
} else if (pattern[j] == '-') {
|
||||
chThdSleepMilliseconds(current_timings.dash_ms);
|
||||
}
|
||||
if (chThdShouldTerminate()) break;
|
||||
// pause between signs
|
||||
baseband::set_morsetx_key(false);
|
||||
chThdSleepMilliseconds(current_timings.symbol_gap);
|
||||
}
|
||||
if (chThdShouldTerminate()) break;
|
||||
// pause between signs
|
||||
baseband::set_morsetx_key(false);
|
||||
chThdSleepMilliseconds(current_timings.symbol_gap);
|
||||
}
|
||||
if (chThdShouldTerminate()) break;
|
||||
if (current_timings.char_gap > current_timings.symbol_gap) {
|
||||
chThdSleepMilliseconds(current_timings.char_gap - current_timings.symbol_gap);
|
||||
if (current_timings.char_gap > current_timings.symbol_gap) {
|
||||
chThdSleepMilliseconds(current_timings.char_gap - current_timings.symbol_gap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chThdShouldTerminate()) break;
|
||||
if (chk_loop.value()) {
|
||||
console_text.write(" ");
|
||||
for (uint8_t s = 0; s < (loop_seconds * 2); s++) {
|
||||
if (chThdShouldTerminate()) break;
|
||||
progressbar.set_value(s + 1);
|
||||
if (!chk_loop.value()) break;
|
||||
chThdSleepMilliseconds(500);
|
||||
}
|
||||
progressbar.set_value(0);
|
||||
}
|
||||
if (!chk_loop.value()) break;
|
||||
} while (chk_loop.value() && !chThdShouldTerminate());
|
||||
|
||||
if (chk_trans.value()) ptt_button_visibility(false);
|
||||
baseband::set_morsetx_key(false);
|
||||
@@ -356,13 +381,17 @@ void MorseRadiotxView::ui_toggle() {
|
||||
wpm_.set_style(Theme::getInstance()->fg_dark);
|
||||
wpm_.set_focusable(false);
|
||||
btn_message.set_style(Theme::getInstance()->fg_dark);
|
||||
btn_message.set_focusable(false);
|
||||
btn_calls.set_style(Theme::getInstance()->fg_dark);
|
||||
btn_calls.set_focusable(false);
|
||||
chk_trans.set_style(Theme::getInstance()->fg_dark);
|
||||
chk_trans.set_focusable(false);
|
||||
bandwidth.set_style(Theme::getInstance()->fg_dark);
|
||||
bandwidth.set_focusable(false);
|
||||
chk_callsgn.set_style(Theme::getInstance()->fg_dark);
|
||||
chk_callsgn.set_focusable(false);
|
||||
wait_time.set_style(Theme::getInstance()->fg_dark);
|
||||
wait_time.set_focusable(false);
|
||||
bandwidth.set_style(Theme::getInstance()->fg_dark);
|
||||
bandwidth.set_focusable(false);
|
||||
|
||||
} else {
|
||||
options_mode.set_style(Theme::getInstance()->bg_darker);
|
||||
@@ -370,11 +399,15 @@ void MorseRadiotxView::ui_toggle() {
|
||||
wpm_.set_style(Theme::getInstance()->bg_darker);
|
||||
wpm_.set_focusable(true);
|
||||
btn_message.set_style(Theme::getInstance()->bg_darker);
|
||||
btn_message.set_focusable(true);
|
||||
btn_calls.set_style(Theme::getInstance()->bg_darker);
|
||||
btn_calls.set_focusable(true);
|
||||
chk_trans.set_style(Theme::getInstance()->bg_darker);
|
||||
chk_trans.set_focusable(true);
|
||||
chk_callsgn.set_style(Theme::getInstance()->bg_darker);
|
||||
chk_callsgn.set_focusable(true);
|
||||
wait_time.set_style(Theme::getInstance()->bg_darker);
|
||||
wait_time.set_focusable(true);
|
||||
ptt_button_visibility(true);
|
||||
tone_.set_style(Theme::getInstance()->bg_darker);
|
||||
tone_.set_focusable(true);
|
||||
|
||||
@@ -118,15 +118,20 @@ class MorseRadiotxView : public ui::View {
|
||||
{{"AM", 0}, {"FM", 1}, {"DSB", 2}, {"USB", 3}, {"LSB", 4}}};
|
||||
NumberField tone_{{UI_POS_X(14), UI_POS_Y(0)}, 4, {400, 1400}, 10, ' ', true};
|
||||
NumberField wpm_{{UI_POS_X(25), UI_POS_Y(0)}, 2, {10, 45}, 1, ' ', true};
|
||||
FloatField bandwidth{{UI_POS_X(20), UI_POS_Y(3)}, 4, {1.0, 16.0}, 0.1, ' ', true, 1};
|
||||
NumberField wait_time{{UI_POS_X(10), UI_POS_Y(4)}, 3, {1, 99}, 1, ' ', true}; // wait between tx-es in loop mode (sec)
|
||||
FloatField bandwidth{{UI_POS_X(6), UI_POS_Y(5)}, 4, {1.0, 16.0}, 0.1, ' ', true, 1};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{UI_POS_X(0), UI_POS_Y(6), screen_width, 16}};
|
||||
|
||||
ui::Text txt_msg{{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "[" + msg_buffer + "] "};
|
||||
ui::Button btn_message{{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, "Message"};
|
||||
ui::Button btn_calls{{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, (call_sign.empty()) ? "call sign?" : call_sign};
|
||||
ui::Button btn_calls{{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, (call_sign.empty()) ? "call sign?" : call_sign};
|
||||
Checkbox chk_trans{{UI_POS_X(14), UI_POS_Y(2)}, 13, "Manual trans.", true};
|
||||
Checkbox chk_callsgn{{UI_POS_X(14), UI_POS_Y(4)}, 13, "Call sign", true};
|
||||
ui::Text txt_last{{UI_POS_X(10), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)}, ""};
|
||||
ui::Console console_text{{UI_POS_X(0), UI_POS_Y(7), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(14)}};
|
||||
Checkbox chk_callsgn{{UI_POS_X(14), UI_POS_Y(3)}, 9, "Call sign", true};
|
||||
Checkbox chk_loop{{UI_POS_X(14), UI_POS_Y(4)}, 4, "loop", true};
|
||||
ui::Text txt_last{{UI_POS_X(10), UI_POS_Y(7), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)}, ""};
|
||||
ui::Console console_text{{UI_POS_X(0), UI_POS_Y(9), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(14)}};
|
||||
ui::Button btn_clear{{UI_POS_X(0), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)}, "CLR"};
|
||||
ui::Button btn_ptt{{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(7), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)}, "PTT"};
|
||||
|
||||
@@ -135,10 +140,11 @@ class MorseRadiotxView : public ui::View {
|
||||
{{UI_POS_X(9), UI_POS_Y(0)}, "Tone:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(18), UI_POS_Y(0)}, "Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(21), UI_POS_Y(0)}, "WPM:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(14), UI_POS_Y(3)}, "BandW:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(24), UI_POS_Y(3)}, "kHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(5)}, "Last seq:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(6)}, "Sent Message:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(5)}, "BandW:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(10), UI_POS_Y(5)}, "kHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(4)}, "Wait time: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(7)}, "Last seq:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(8)}, "Sent Message:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_RIGHT(7), UI_POS_Y_BOTTOM(5)}, "Vol.:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ options_t freqman_steps = {
|
||||
{"100kHz (FM2)", 100000},
|
||||
{"250kHz (N2)", 250000},
|
||||
{"500kHz (WFM)", 500000},
|
||||
{"750kHz ", 750000},
|
||||
{"1MHz ", 1000000},
|
||||
};
|
||||
|
||||
@@ -156,6 +157,7 @@ options_t freqman_steps_short = {
|
||||
{"100kHz", 100000},
|
||||
{"250kHz", 250000},
|
||||
{"500kHz", 500000},
|
||||
{"750kHz", 750000},
|
||||
{"1MHz", 1000000},
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ enum class freqman_type : uint8_t {
|
||||
_100kHz,
|
||||
_250kHz,
|
||||
_500kHz,
|
||||
_750kHz,
|
||||
_1MHz,
|
||||
Unknown,
|
||||
* };
|
||||
@@ -130,6 +131,7 @@ enum class freqman_type : uint8_t {
|
||||
freqman_step_info{ freqman_step::_100kHz, "100kHz", "100kHz (FM2)", 100'000 },
|
||||
freqman_step_info{ freqman_step::_250kHz, "250kHz", "250kHz (N2)", 250'000 },
|
||||
freqman_step_info{ freqman_step::_500kHz, "500kHz", "500kHz (WFM)", 500'000 },
|
||||
freqman_step_info{ freqman_step::_750kHz, "750kHz", "750kHz ", 750'000 },
|
||||
freqman_step_info{ freqman_step::_1MHz, "1MHz", "1MHz ", 1'000'000 },
|
||||
freqman_step_info{ freqman_step::Unknown, "Unknown", "Unknown ", 0 },
|
||||
* };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "gpio_lpc.h"
|
||||
#include "gpio.h"
|
||||
|
||||
typedef enum {
|
||||
LED1 = 0,
|
||||
@@ -8,7 +9,7 @@ typedef enum {
|
||||
} led_t;
|
||||
|
||||
/* GPIO Output PinMux */
|
||||
static struct gpio_t gpio_led[] = {
|
||||
static struct gpio gpio_led[] = {
|
||||
GPIO(2, 1),
|
||||
GPIO(2, 2),
|
||||
GPIO(2, 8),
|
||||
|
||||
@@ -636,7 +636,7 @@ init_status_t init() {
|
||||
// This function returns LD_SUCCESS (0) if the FPGA confirms the bitstream
|
||||
// Call fpga_bridge_init and continue boot regardless of result
|
||||
// (Watchdog was resetting device when we halted with while(1))
|
||||
int load_result = fpga_bridge_init();
|
||||
int load_result = fpga_bridge_init(&shared_memory.bb_data.data[0]);
|
||||
(void)load_result; // Ignore result for now, just let boot continue
|
||||
|
||||
/* RELEASE FPGA RESET */
|
||||
|
||||
@@ -361,6 +361,8 @@ void WaterfallView::stop() {
|
||||
baseband::spectrum_streaming_stop();
|
||||
running_ = false;
|
||||
}
|
||||
this->channel_fifo = nullptr;
|
||||
this->audio_spectrum_data = nullptr;
|
||||
}
|
||||
|
||||
void WaterfallView::show_audio_spectrum_view(const bool show) {
|
||||
|
||||
@@ -173,12 +173,16 @@ class WaterfallView : public View {
|
||||
MessageHandlerRegistration message_handler_channel_spectrum_config{
|
||||
Message::ID::ChannelSpectrumConfig,
|
||||
[this](const Message* const p) {
|
||||
if (!running_)
|
||||
return;
|
||||
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
||||
this->channel_fifo = message.fifo;
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_audio_spectrum{
|
||||
Message::ID::AudioSpectrum,
|
||||
[this](const Message* const p) {
|
||||
if (!running_)
|
||||
return;
|
||||
const auto message = *reinterpret_cast<const AudioSpectrumMessage*>(p);
|
||||
this->audio_spectrum_data = message.data;
|
||||
this->audio_spectrum_update = true;
|
||||
@@ -186,6 +190,8 @@ class WaterfallView : public View {
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
if (!running_)
|
||||
return;
|
||||
if (this->channel_fifo) {
|
||||
ChannelSpectrum channel_spectrum;
|
||||
while (channel_fifo->out(channel_spectrum)) {
|
||||
|
||||
@@ -746,6 +746,8 @@ 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/platform_gpio.c
|
||||
${HACKRF_PATH}/firmware/common/platform_scu.c
|
||||
${HACKRF_PATH}/firmware/common/gpio_lpc.c
|
||||
${HACKRF_PATH}/firmware/common/firmware_info.c
|
||||
${HACKRF_PATH}/firmware/common/si5351c.c
|
||||
|
||||
@@ -46,15 +46,15 @@ void EPIRBTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (mode_bpsk) {
|
||||
// BPSK Manchester beacon signal
|
||||
if (bpsk_pre_count < config_pre_count) {
|
||||
// Pre-count state: send a negative phase carrier during pre-count
|
||||
// Pre-count state: send carrier only during pre-count
|
||||
bpsk_pre_count++;
|
||||
re = i_neg;
|
||||
im = q_neg;
|
||||
re = i_carrier;
|
||||
im = q_carrier;
|
||||
} else if (bpsk_post_count > 0) {
|
||||
// Post-count: send a negative phase carrier during post-count
|
||||
// Post-count: send carrier only during post-count
|
||||
bpsk_post_count++;
|
||||
re = i_neg;
|
||||
im = q_neg;
|
||||
re = i_carrier;
|
||||
im = q_carrier;
|
||||
if (bpsk_post_count >= config_post_count) {
|
||||
// End transmission here
|
||||
byte_index = 0;
|
||||
|
||||
@@ -60,14 +60,17 @@ class EPIRBTXProcessor : public BasebandProcessor {
|
||||
// Size of the frame to send in BPSK mode
|
||||
uint8_t frame_data_len = 0;
|
||||
|
||||
// BPSK parameters: Target phase +/-63° as per COSPAS/SARSAT specifications
|
||||
static constexpr float phase_rad = 63.0f * M_PI / 180.0f;
|
||||
// BPSK parameters: Target phase +/-1.1 RAD as per COSPAS/SARSAT specifications
|
||||
static constexpr float phase_rad = 1.1f;
|
||||
|
||||
// I/Q values for BPSK (positive phase and negative phase)
|
||||
int8_t i_pos = (int8_t)(cos(phase_rad) * 127);
|
||||
int8_t q_pos = (int8_t)(sin(phase_rad) * 127);
|
||||
int8_t i_neg = i_pos;
|
||||
int8_t q_neg = -q_pos;
|
||||
// I/Q values for carrier only
|
||||
static constexpr int8_t i_carrier = 127;
|
||||
static constexpr int8_t q_carrier = 0;
|
||||
|
||||
// COSPAS/SARSAT signal is manchester (2 states per bit) encoded 400 bit/sec
|
||||
static const uint32_t samples_per_halfbit = TONES_SAMPLERATE / 400 / 2;
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#include "i2c_lpc.h"
|
||||
#include "cpld_jtag.h"
|
||||
#include "platform_detect.h"
|
||||
#include "platform_gpio.h"
|
||||
#include "platform_scu.h"
|
||||
#include "fixed_point.h"
|
||||
#include "clkin.h"
|
||||
#include <libopencm3/lpc43xx/cgu.h>
|
||||
#include <libopencm3/lpc43xx/ccu.h>
|
||||
@@ -48,7 +51,7 @@
|
||||
#include "gpio_lpc.h"
|
||||
|
||||
/* GPIO Output PinMux */
|
||||
static struct gpio_t gpio_led[] = {
|
||||
static struct gpio gpio_led[] = {
|
||||
GPIO(2, 1),
|
||||
GPIO(2, 2),
|
||||
GPIO(2, 8),
|
||||
@@ -58,90 +61,89 @@ static struct gpio_t gpio_led[] = {
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
static struct gpio_t gpio_1v8_enable = GPIO(3, 6);
|
||||
static struct gpio gpio_1v8_enable = GPIO(3, 6);
|
||||
|
||||
/* MAX283x GPIO (XCVR_CTL) PinMux */
|
||||
static struct gpio_t gpio_max283x_select = GPIO(0, 15);
|
||||
static struct gpio gpio_max283x_select = GPIO(0, 15);
|
||||
|
||||
/* MAX5864 SPI chip select (AD_CS) GPIO PinMux */
|
||||
static struct gpio_t gpio_max5864_select = GPIO(2, 7);
|
||||
static struct gpio gpio_max5864_select = GPIO(2, 7);
|
||||
|
||||
/* RFFC5071 GPIO serial interface PinMux */
|
||||
// #ifdef RAD1O
|
||||
// static struct gpio_t gpio_rffc5072_select = GPIO(2, 13);
|
||||
// static struct gpio_t gpio_rffc5072_clock = GPIO(5, 6);
|
||||
// static struct gpio_t gpio_rffc5072_data = GPIO(3, 3);
|
||||
// static struct gpio_t gpio_rffc5072_reset = GPIO(2, 14);
|
||||
// static struct gpio gpio_rffc5072_select = GPIO(2, 13);
|
||||
// static struct gpio gpio_rffc5072_clock = GPIO(5, 6);
|
||||
// static struct gpio gpio_rffc5072_data = GPIO(3, 3);
|
||||
// static struct gpio gpio_rffc5072_reset = GPIO(2, 14);
|
||||
// #endif
|
||||
|
||||
/* RF supply (VAA) control */
|
||||
#ifdef HACKRF_ONE
|
||||
static struct gpio_t gpio_vaa_disable = GPIO(2, 9);
|
||||
static struct gpio gpio_vaa_disable = GPIO(2, 9);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
static struct gpio_t gpio_vaa_enable = GPIO(2, 9);
|
||||
static struct gpio gpio_vaa_enable = GPIO(2, 9);
|
||||
#endif
|
||||
|
||||
static struct gpio_t gpio_w25q80bv_hold = GPIO(1, 14);
|
||||
static struct gpio_t gpio_w25q80bv_wp = GPIO(1, 15);
|
||||
static struct gpio_t gpio_w25q80bv_select = GPIO(5, 11);
|
||||
static struct gpio gpio_w25q80bv_hold = GPIO(1, 14);
|
||||
static struct gpio gpio_w25q80bv_wp = GPIO(1, 15);
|
||||
|
||||
/* RF switch control */
|
||||
#ifdef HACKRF_ONE
|
||||
static struct gpio_t gpio_hp = GPIO(2, 0);
|
||||
static struct gpio_t gpio_lp = GPIO(2, 10);
|
||||
static struct gpio_t gpio_tx_mix_bp = GPIO(2, 11);
|
||||
static struct gpio_t gpio_no_mix_bypass = GPIO(1, 0);
|
||||
static struct gpio_t gpio_rx_mix_bp = GPIO(2, 12);
|
||||
static struct gpio_t gpio_tx_amp = GPIO(2, 15);
|
||||
static struct gpio_t gpio_tx = GPIO(5, 15);
|
||||
static struct gpio_t gpio_mix_bypass = GPIO(5, 16);
|
||||
static struct gpio_t gpio_rx = GPIO(5, 5);
|
||||
static struct gpio_t gpio_no_tx_amp_pwr = GPIO(3, 5);
|
||||
static struct gpio_t gpio_amp_bypass = GPIO(0, 14);
|
||||
static struct gpio_t gpio_rx_amp = GPIO(1, 11);
|
||||
static struct gpio_t gpio_no_rx_amp_pwr = GPIO(1, 12);
|
||||
static struct gpio gpio_hp = GPIO(2, 0);
|
||||
static struct gpio gpio_lp = GPIO(2, 10);
|
||||
static struct gpio gpio_tx_mix_bp = GPIO(2, 11);
|
||||
static struct gpio gpio_no_mix_bypass = GPIO(1, 0);
|
||||
static struct gpio gpio_rx_mix_bp = GPIO(2, 12);
|
||||
static struct gpio gpio_tx_amp = GPIO(2, 15);
|
||||
static struct gpio gpio_tx = GPIO(5, 15);
|
||||
static struct gpio gpio_mix_bypass = GPIO(5, 16);
|
||||
static struct gpio gpio_rx = GPIO(5, 5);
|
||||
static struct gpio gpio_no_tx_amp_pwr = GPIO(3, 5);
|
||||
static struct gpio gpio_amp_bypass = GPIO(0, 14);
|
||||
static struct gpio gpio_rx_amp = GPIO(1, 11);
|
||||
static struct gpio gpio_no_rx_amp_pwr = GPIO(1, 12);
|
||||
#endif
|
||||
#ifdef RAD1O
|
||||
static struct gpio_t gpio_tx_rx_n = GPIO(1, 11);
|
||||
static struct gpio_t gpio_tx_rx = GPIO(0, 14);
|
||||
static struct gpio_t gpio_by_mix = GPIO(1, 12);
|
||||
static struct gpio_t gpio_by_mix_n = GPIO(2, 10);
|
||||
static struct gpio_t gpio_by_amp = GPIO(1, 0);
|
||||
static struct gpio_t gpio_by_amp_n = GPIO(5, 5);
|
||||
static struct gpio_t gpio_mixer_en = GPIO(5, 16);
|
||||
static struct gpio_t gpio_low_high_filt = GPIO(2, 11);
|
||||
static struct gpio_t gpio_low_high_filt_n = GPIO(2, 12);
|
||||
static struct gpio_t gpio_tx_amp = GPIO(2, 15);
|
||||
static struct gpio_t gpio_rx_lna = GPIO(5, 15);
|
||||
static struct gpio gpio_tx_rx_n = GPIO(1, 11);
|
||||
static struct gpio gpio_tx_rx = GPIO(0, 14);
|
||||
static struct gpio gpio_by_mix = GPIO(1, 12);
|
||||
static struct gpio gpio_by_mix_n = GPIO(2, 10);
|
||||
static struct gpio gpio_by_amp = GPIO(1, 0);
|
||||
static struct gpio gpio_by_amp_n = GPIO(5, 5);
|
||||
static struct gpio gpio_mixer_en = GPIO(5, 16);
|
||||
static struct gpio gpio_low_high_filt = GPIO(2, 11);
|
||||
static struct gpio gpio_low_high_filt_n = GPIO(2, 12);
|
||||
static struct gpio gpio_tx_amp = GPIO(2, 15);
|
||||
static struct gpio gpio_rx_lna = GPIO(5, 15);
|
||||
#endif
|
||||
|
||||
/* CPLD JTAG interface GPIO pins */
|
||||
static struct gpio_t gpio_cpld_tdo = GPIO(5, 18);
|
||||
static struct gpio_t gpio_cpld_tck = GPIO(3, 0);
|
||||
static struct gpio gpio_cpld_tdo = GPIO(5, 18);
|
||||
static struct gpio gpio_cpld_tck = GPIO(3, 0);
|
||||
#if (defined HACKRF_ONE || defined RAD1O)
|
||||
static struct gpio_t gpio_cpld_tms = GPIO(3, 4);
|
||||
static struct gpio_t gpio_cpld_tdi = GPIO(3, 1);
|
||||
static struct gpio gpio_cpld_tms = GPIO(3, 4);
|
||||
static struct gpio gpio_cpld_tdi = GPIO(3, 1);
|
||||
#else
|
||||
static struct gpio_t gpio_cpld_tms = GPIO(3, 1);
|
||||
static struct gpio_t gpio_cpld_tdi = GPIO(3, 4);
|
||||
static struct gpio gpio_cpld_tms = GPIO(3, 1);
|
||||
static struct gpio gpio_cpld_tdi = GPIO(3, 4);
|
||||
#endif
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
static struct gpio_t gpio_cpld_pp_tms = GPIO(1, 1);
|
||||
static struct gpio_t gpio_cpld_pp_tdo = GPIO(1, 8);
|
||||
static struct gpio gpio_cpld_pp_tms = GPIO(1, 1);
|
||||
static struct gpio gpio_cpld_pp_tdo = GPIO(1, 8);
|
||||
#endif
|
||||
|
||||
/* other CPLD interface GPIO pins */
|
||||
static struct gpio_t gpio_hw_sync_enable = GPIO(5, 12);
|
||||
static struct gpio_t gpio_q_invert = GPIO(0, 13);
|
||||
static struct gpio gpio_hw_sync_enable = GPIO(5, 12);
|
||||
static struct gpio gpio_q_invert = GPIO(0, 13);
|
||||
|
||||
/* HackRF One r9 */
|
||||
#ifdef HACKRF_ONE
|
||||
static struct gpio_t gpio_h1r9_rx = GPIO(0, 7);
|
||||
static struct gpio_t gpio_h1r9_1v8_enable = GPIO(2, 9);
|
||||
static struct gpio_t gpio_h1r9_vaa_disable = GPIO(3, 6);
|
||||
static struct gpio_t gpio_h1r9_hw_sync_enable = GPIO(5, 5);
|
||||
static struct gpio gpio_h1r9_rx = GPIO(0, 7);
|
||||
static struct gpio gpio_h1r9_1v8_enable = GPIO(2, 9);
|
||||
static struct gpio gpio_h1r9_vaa_disable = GPIO(3, 6);
|
||||
static struct gpio gpio_h1r9_hw_sync_enable = GPIO(5, 5);
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
@@ -216,11 +218,10 @@ max5864_driver_t max5864 = {
|
||||
.target_init = max5864_target_init,
|
||||
};
|
||||
|
||||
const ssp_config_t ssp_config_w25q80bv = {
|
||||
ssp_config_t ssp_config_w25q80bv = {
|
||||
.data_bits = SSP_DATA_8BITS,
|
||||
.serial_clock_rate = 2,
|
||||
.clock_prescale_rate = 2,
|
||||
.gpio_select = &gpio_w25q80bv_select,
|
||||
};
|
||||
|
||||
spi_bus_t spi_bus_ssp0 = {
|
||||
@@ -431,96 +432,105 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sample_rate_set(const uint32_t sample_rate_hz) {
|
||||
uint32_t p1 = 4608;
|
||||
uint32_t p2 = 0;
|
||||
uint32_t p3 = 0;
|
||||
/*
|
||||
* Configure clock generator to produce sample clock in units of 1/(2**24) Hz.
|
||||
* Can be called with program=false for a dry run that returns the resultant
|
||||
* frequency without actually configuring the clock generator.
|
||||
*/
|
||||
fp_40_24_t sample_rate_set(const fp_40_24_t sample_rate, const bool program) {
|
||||
const fp_40_24_t vco = 800 * FP_ONE_MHZ;
|
||||
uint64_t p1, p2, p3;
|
||||
uint64_t n, d, q;
|
||||
fp_40_24_t remainder, resultant_rate;
|
||||
|
||||
switch (sample_rate_hz) {
|
||||
case 8000000:
|
||||
p1 = SI_INTDIV(50); // 800MHz / 50 = 16 MHz (SGPIO), 8 MHz (codec)
|
||||
break;
|
||||
fp_40_24_t rate = sample_rate * 2;
|
||||
|
||||
case 9216000:
|
||||
// 43.40277777777778: a = 43; b = 29; c = 72
|
||||
p1 = 5043;
|
||||
p2 = 40;
|
||||
p3 = 72;
|
||||
break;
|
||||
p1 = ((128 * vco) / rate) - 512;
|
||||
if (vco % rate) {
|
||||
n = (128 * vco) - (rate * (p1 + 512));
|
||||
d = rate / FP_ONE_HZ;
|
||||
n += (d / 2);
|
||||
p2 = n / d;
|
||||
p3 = 1 << 24;
|
||||
|
||||
case 10000000:
|
||||
p1 = SI_INTDIV(40); // 800MHz / 40 = 20 MHz (SGPIO), 10 MHz (codec)
|
||||
break;
|
||||
unsigned int shift = p2 ? __builtin_ctzll(p2) : 24;
|
||||
p2 >>= shift;
|
||||
p3 >>= shift;
|
||||
|
||||
case 12288000:
|
||||
// 32.552083333333336: a = 32; b = 159; c = 288
|
||||
p1 = 3654;
|
||||
p2 = 192;
|
||||
p3 = 288;
|
||||
break;
|
||||
const uint64_t p3_max = 0xfffff;
|
||||
if (p3 > p3_max) {
|
||||
p2 *= p3_max;
|
||||
p2 += (p3 / 2);
|
||||
p2 /= p3;
|
||||
p3 = p3_max;
|
||||
}
|
||||
|
||||
case 12500000:
|
||||
p1 = SI_INTDIV(32); // 800MHz / 32 = 25 MHz (SGPIO), 12.5 MHz (codec)
|
||||
break;
|
||||
if (p2 >= p3) {
|
||||
p1++;
|
||||
p2 = 0;
|
||||
}
|
||||
} else {
|
||||
p2 = 0;
|
||||
}
|
||||
|
||||
case 16000000:
|
||||
p1 = SI_INTDIV(25); // 800MHz / 25 = 32 MHz (SGPIO), 16 MHz (codec)
|
||||
break;
|
||||
if (p1 > 0x3fe00) {
|
||||
p1 = 0x3fe00;
|
||||
p2 = 0;
|
||||
}
|
||||
|
||||
case 18432000:
|
||||
// 21.70138888889: a = 21; b = 101; c = 144
|
||||
p1 = 2265;
|
||||
p2 = 112;
|
||||
p3 = 144;
|
||||
break;
|
||||
if (p2 == 0) {
|
||||
p3 = 1;
|
||||
n = (vco * 128);
|
||||
d = (p1 + 512);
|
||||
n += (d / 2);
|
||||
resultant_rate = n / d;
|
||||
} else {
|
||||
const uint64_t vco_hz = vco / FP_ONE_HZ;
|
||||
n = p3 * vco_hz * 128;
|
||||
d = p3 * (p1 + 512) + p2;
|
||||
const uint64_t rate_hz = n / d;
|
||||
remainder = (n - (d * rate_hz)) * FP_ONE_HZ;
|
||||
remainder += (d / 2);
|
||||
q = remainder / d;
|
||||
resultant_rate = (rate_hz * FP_ONE_HZ) + q;
|
||||
}
|
||||
|
||||
case 20000000:
|
||||
p1 = SI_INTDIV(20); // 800MHz / 20 = 40 MHz (SGPIO), 20 MHz (codec)
|
||||
break;
|
||||
resultant_rate = (resultant_rate + 1) / 2;
|
||||
|
||||
default:
|
||||
return false;
|
||||
if (!program) {
|
||||
return resultant_rate;
|
||||
}
|
||||
|
||||
bool streaming = sgpio_cpld_stream_is_enabled(&sgpio_config);
|
||||
|
||||
if (streaming) {
|
||||
sgpio_cpld_stream_disable(&sgpio_config);
|
||||
}
|
||||
|
||||
if (p1 & 0x1 || p2) {
|
||||
si5351c_set_int_mode(&clock_gen, 0, 0);
|
||||
} else {
|
||||
si5351c_set_int_mode(&clock_gen, 0, 1);
|
||||
}
|
||||
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
/*
|
||||
* On HackRF One r9 all sample clocks are externally derived
|
||||
* from MS1/CLK1 operating at twice the sample rate.
|
||||
*/
|
||||
si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0);
|
||||
} else {
|
||||
/*
|
||||
* On other platforms the clock generator produces three
|
||||
* different sample clocks, all derived from multisynth 0.
|
||||
*/
|
||||
/* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */
|
||||
si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1);
|
||||
|
||||
/* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
1,
|
||||
p1,
|
||||
0,
|
||||
1,
|
||||
0); // p1 doesn't matter
|
||||
|
||||
/* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */
|
||||
si5351c_configure_multisynth(
|
||||
&clock_gen,
|
||||
2,
|
||||
p1,
|
||||
0,
|
||||
1,
|
||||
0); // p1 doesn't matter
|
||||
si5351c_configure_multisynth(&clock_gen, 1, 0, 0, 0, 0);
|
||||
si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (streaming) {
|
||||
sgpio_cpld_stream_enable(&sgpio_config);
|
||||
}
|
||||
|
||||
return resultant_rate;
|
||||
}
|
||||
|
||||
bool baseband_filter_bandwidth_set(const uint32_t bandwidth_hz) {
|
||||
uint32_t bandwidth_hz_real;
|
||||
bandwidth_hz_real = max283x_set_lpf_bandwidth(&max283x, bandwidth_hz);
|
||||
bandwidth_hz_real = max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_RX, bandwidth_hz);
|
||||
|
||||
if (bandwidth_hz_real) {
|
||||
hackrf_ui()->set_filter_bw(bandwidth_hz_real);
|
||||
@@ -615,8 +625,8 @@ void cpu_clock_init(void) {
|
||||
si5351c_power_down_all_clocks(&clock_gen);
|
||||
si5351c_set_crystal_configuration(&clock_gen);
|
||||
si5351c_enable_xo_and_ms_fanout(&clock_gen);
|
||||
si5351c_configure_pll_sources(&clock_gen);
|
||||
si5351c_configure_pll_multisynth(&clock_gen);
|
||||
si5351c_configure_pll_sources(&clock_gen, PLL_SOURCE_XTAL);
|
||||
si5351c_configure_pll_multisynth(&clock_gen, PLL_SOURCE_XTAL);
|
||||
|
||||
/*
|
||||
* Clocks on HackRF One r9:
|
||||
@@ -667,7 +677,7 @@ void cpu_clock_init(void) {
|
||||
/* MS7/CLK7 is unused. */
|
||||
|
||||
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
|
||||
sample_rate_set(10000000);
|
||||
sample_rate_set(10000000 * (fp_40_24_t)FP_ONE_HZ, true);
|
||||
|
||||
si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL);
|
||||
// soft reset
|
||||
@@ -854,6 +864,8 @@ void ssp1_set_mode_max5864(void) {
|
||||
}
|
||||
|
||||
void pin_setup(void) {
|
||||
const platform_scu_t* scu = platform_scu();
|
||||
|
||||
/* Configure all GPIO as Input (safe state) */
|
||||
// gpio_init();
|
||||
|
||||
@@ -872,26 +884,26 @@ void pin_setup(void) {
|
||||
* LPC43xx pull-up and pull-down resistors are approximately 53K.
|
||||
*/
|
||||
#ifdef HACKRF_ONE
|
||||
scu_pinmux(SCU_PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(SCU_PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
|
||||
/* Configure SCU Pin Mux as GPIO */
|
||||
scu_pinmux(SCU_PINMUX_LED1, SCU_GPIO_NOPULL);
|
||||
scu_pinmux(SCU_PINMUX_LED2, SCU_GPIO_NOPULL);
|
||||
scu_pinmux(SCU_PINMUX_LED3, SCU_GPIO_NOPULL);
|
||||
scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL);
|
||||
scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL);
|
||||
scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL);
|
||||
#ifdef RAD1O
|
||||
scu_pinmux(SCU_PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4);
|
||||
scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4);
|
||||
#endif
|
||||
|
||||
/* Configure USB indicators */
|
||||
#ifdef JAWBREAKER
|
||||
scu_pinmux(SCU_PINMUX_USB_LED0, SCU_CONF_FUNCTION3);
|
||||
scu_pinmux(SCU_PINMUX_USB_LED1, SCU_CONF_FUNCTION3);
|
||||
scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3);
|
||||
scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3);
|
||||
#endif
|
||||
|
||||
gpio_output(&gpio_led[0]);
|
||||
@@ -905,11 +917,11 @@ void pin_setup(void) {
|
||||
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
|
||||
#ifdef HACKRF_ONE
|
||||
gpio_output(&gpio_h1r9_1v8_enable);
|
||||
scu_pinmux(SCU_H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
#endif
|
||||
} else {
|
||||
gpio_output(&gpio_1v8_enable);
|
||||
scu_pinmux(SCU_PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||||
}
|
||||
|
||||
#ifdef HACKRF_ONE
|
||||
@@ -935,8 +947,8 @@ void pin_setup(void) {
|
||||
scu_pinmux(CLK0, SCU_CLK_IN | SCU_CONF_FUNCTION7);
|
||||
scu_pinmux(CLK2, SCU_CLK_IN | SCU_CONF_FUNCTION7);
|
||||
|
||||
scu_pinmux(SCU_PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(SCU_PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -956,7 +968,7 @@ void pin_setup(void) {
|
||||
rf_path_pin_setup(&rf_path);
|
||||
|
||||
/* Configure external clock in */
|
||||
scu_pinmux(SCU_PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1);
|
||||
scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1);
|
||||
|
||||
sgpio_configure_pin_functions(&sgpio_config);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "scsi.h"
|
||||
#include "diskio.h"
|
||||
#include "gpio_lpc.h"
|
||||
#include <libopencm3/lpc43xx/scu.h>
|
||||
#include <libopencm3/lpc43xx/rgu.h>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
@@ -286,7 +287,7 @@ void scsi_command(msd_cbw_t* msd_cbw_data) {
|
||||
|
||||
case SCSI_CMD_START_STOP_UNIT:
|
||||
SCU_SFSP2_8 = (SCU_SFSP2_8 & ~(7)) | 4;
|
||||
struct gpio_t dfu = GPIO(5, 7);
|
||||
struct gpio dfu = GPIO(5, 7);
|
||||
gpio_output(&dfu);
|
||||
gpio_clear(&dfu);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,16 +17,16 @@ extern "C" {
|
||||
#ifdef PRALINE
|
||||
|
||||
/* FPGA Register Map Address 0x03 (Dual Purpose) */
|
||||
#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */
|
||||
#define FPGA_REG_TX_CONTROL 0x03 /* NCO_EN and TX flags (TX Mode) */
|
||||
#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) */
|
||||
#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) */
|
||||
#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) */
|
||||
|
||||
/*
|
||||
* FPGA Operating Mode
|
||||
@@ -41,60 +41,60 @@ typedef enum {
|
||||
* FPGA Register Addresses
|
||||
* Note: Registers 3-5 are dual-purpose (meaning depends on RX/TX mode)
|
||||
*/
|
||||
#define FPGA_REG_CTRL 0x01 /* Control register */
|
||||
#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */
|
||||
#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose register */
|
||||
#define FPGA_REG_CTRL 0x01 /* Control register */
|
||||
#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */
|
||||
#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */
|
||||
#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose register */
|
||||
|
||||
/*
|
||||
* Register 1 (CTRL) Bit Definitions
|
||||
*/
|
||||
#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */
|
||||
#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */
|
||||
#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */
|
||||
#define FPGA_CTRL_TX_MODE (1 << 5) /* TX mode indicator (if applicable) */
|
||||
#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */
|
||||
#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */
|
||||
#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */
|
||||
#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */
|
||||
#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */
|
||||
#define FPGA_CTRL_TX_MODE (1 << 5) /* TX mode indicator (if applicable) */
|
||||
#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */
|
||||
#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */
|
||||
|
||||
/*
|
||||
* Register 3 Dual-Purpose Definitions
|
||||
*/
|
||||
/* RX Mode: Digital gain/shift */
|
||||
#define FPGA_REG3_RX_DIGITAL_GAIN 0x03
|
||||
#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */
|
||||
#define FPGA_REG3_RX_DIGITAL_GAIN 0x03
|
||||
#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */
|
||||
|
||||
/* TX Mode: NCO control */
|
||||
#define FPGA_REG3_TX_NCO_CTRL 0x03
|
||||
#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */
|
||||
#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */
|
||||
#define FPGA_REG3_TX_NCO_CTRL 0x03
|
||||
#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */
|
||||
#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */
|
||||
|
||||
/*
|
||||
* Register 4 Dual-Purpose Definitions
|
||||
*/
|
||||
/* RX Mode: DC block notch width */
|
||||
#define FPGA_REG4_RX_DC_WIDTH 0x04
|
||||
#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */
|
||||
#define FPGA_REG4_RX_DC_WIDTH 0x04
|
||||
#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/* TX Mode: Interpolation ratio */
|
||||
#define FPGA_REG4_TX_INTERP 0x04
|
||||
#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */
|
||||
#define FPGA_REG4_TX_INTERP 0x04
|
||||
#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */
|
||||
|
||||
/*
|
||||
* Register 5 Dual-Purpose Definitions
|
||||
*/
|
||||
/* RX Mode: DC block adaptation rate */
|
||||
#define FPGA_REG5_RX_DC_RATE 0x05
|
||||
#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */
|
||||
#define FPGA_REG5_RX_DC_RATE 0x05
|
||||
#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* TX Mode: NCO phase step (frequency) */
|
||||
#define FPGA_REG5_TX_PHASE_STEP 0x05
|
||||
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */
|
||||
#define FPGA_REG5_TX_PHASE_STEP 0x05
|
||||
#define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */
|
||||
|
||||
/* Export default values so other methods can use them */
|
||||
#define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00
|
||||
#define FPGA_RX_DEFAULT_DC_WIDTH 0x04
|
||||
#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08
|
||||
#define FPGA_RX_DEFAULT_DC_WIDTH 0x04
|
||||
#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08
|
||||
|
||||
/*
|
||||
* Core Functions
|
||||
@@ -102,7 +102,7 @@ typedef enum {
|
||||
|
||||
/* Initialize the FPGA - loads bitstream from SPIFI flash
|
||||
* Returns: 0 on success, non-zero on failure */
|
||||
int fpga_bridge_init(void);
|
||||
int fpga_bridge_init(uint8_t* mem_base);
|
||||
|
||||
/* Set operating mode - MUST be called before using mode-specific functions
|
||||
* This ensures registers 3-5 are interpreted correctly */
|
||||
|
||||
@@ -214,6 +214,14 @@ bool I2cDev_MAX17055::init(uint8_t addr_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2cDev_MAX17055::reInit() {
|
||||
if (!full_reset_and_init()) {
|
||||
return false;
|
||||
}
|
||||
partialInit();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2cDev_MAX17055::full_reset_and_init() {
|
||||
if (!soft_reset()) {
|
||||
return false;
|
||||
@@ -235,12 +243,12 @@ bool I2cDev_MAX17055::soft_reset() {
|
||||
}
|
||||
|
||||
bool I2cDev_MAX17055::initialize_custom_parameters() {
|
||||
if (!write_register(0xD0, 0x03E8)) return false; // Unknown register, possibly related to battery profile
|
||||
if (!write_register(0xDB, 0x0000)) return false; // ModelCfg
|
||||
if (!write_register(0x05, 0x0000)) return false; // RepCap
|
||||
uint32_t designcap = portapack::device_type == portapack::DEV_PORTARF ? __MAX17055_Design_Capacity_PRF__ * 2 : __MAX17055_Design_Capacity__ * 2; // the original design has a 2x multiplier here, so i keep it
|
||||
if (!write_register(0x18, designcap)) return false; // DesignCap
|
||||
if (!write_register(0x45, designcap / 32)) return false; // dQAcc = DesignCap / 32
|
||||
if (!write_register(0xD0, 0x03E8)) return false; // Unknown register, possibly related to battery profile
|
||||
if (!write_register(0xDB, 0x0000)) return false; // ModelCfg
|
||||
if (!write_register(0x05, 0x0000)) return false; // RepCap
|
||||
uint32_t designcap = portapack::persistent_memory::battery_cap_mah() * 2; // the original design has a 2x multiplier here, so i keep it
|
||||
if (!write_register(0x18, designcap)) return false; // DesignCap
|
||||
if (!write_register(0x45, designcap / 32)) return false; // dQAcc = DesignCap / 32
|
||||
|
||||
if (!write_register(0x1E, 0x03C0)) return false; // IChgTerm
|
||||
if (!write_register(0x3A, 0x9661)) return false; // VEmpty
|
||||
|
||||
@@ -290,7 +290,7 @@ class I2cDev_MAX17055 : public I2cDev {
|
||||
uint16_t averageMVoltage(void);
|
||||
int32_t instantCurrent(void);
|
||||
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!!!
|
||||
private:
|
||||
const RegisterEntry* findEntry(const char* name) const;
|
||||
|
||||
|
||||
@@ -156,8 +156,7 @@ struct misc_config_t {
|
||||
bool tx_amp_disabled : 1;
|
||||
|
||||
uint8_t tx_gain_max_db;
|
||||
uint8_t PLACEHOLDER_1;
|
||||
uint8_t PLACEHOLDER_2;
|
||||
uint16_t batt_cap_mah;
|
||||
};
|
||||
static_assert(sizeof(misc_config_t) == sizeof(uint32_t));
|
||||
|
||||
@@ -445,6 +444,8 @@ void defaults() {
|
||||
set_config_tx_disabled(false);
|
||||
set_config_tx_amp_disabled(false);
|
||||
set_config_tx_gain_max_db(47);
|
||||
|
||||
set_battery_cap_mah(0);
|
||||
}
|
||||
|
||||
void init() {
|
||||
@@ -1243,6 +1244,32 @@ int load_persistent_settings_from_file() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool battery_cap_valid() {
|
||||
return (data->misc_config.batt_cap_mah >= BATT_18650_MIN_MAH && data->misc_config.batt_cap_mah <= BATT_18650_MAX_MAH);
|
||||
}
|
||||
|
||||
void set_battery_cap_mah(uint16_t mah) {
|
||||
if ((mah < BATT_18650_MIN_MAH || mah > BATT_18650_MAX_MAH) && mah != 0) {
|
||||
// Invalid value, ignore it.
|
||||
return;
|
||||
}
|
||||
if (data->misc_config.batt_cap_mah != mah) {
|
||||
data->misc_config.batt_cap_mah = mah;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t battery_cap_mah() {
|
||||
if (battery_cap_valid()) {
|
||||
return data->misc_config.batt_cap_mah;
|
||||
}
|
||||
// we don't know, need to assume.
|
||||
if (portapack::device_type == portapack::DEV_PORTARF) return 3000;
|
||||
#ifdef PRALINE
|
||||
return 2000; // with h4 + pro and h4pro + pro it is ~safe
|
||||
#endif
|
||||
return 2500; // h4 + one
|
||||
}
|
||||
|
||||
// Pmem size helper
|
||||
|
||||
size_t data_size() {
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
#define PMEM_SIZE_BYTES 256 // total amount of pmem space in bytes, including checksum
|
||||
#define PMEM_SIZE_WORDS (PMEM_SIZE_BYTES / 4)
|
||||
|
||||
// to check battery mins and maxes in pmem, and validity
|
||||
#define BATT_18650_MIN_MAH 1000
|
||||
#define BATT_18650_MAX_MAH 5000
|
||||
|
||||
using namespace modems;
|
||||
using namespace serializer;
|
||||
using namespace ui;
|
||||
@@ -387,6 +391,11 @@ uint32_t pmem_data_word(uint32_t index);
|
||||
uint32_t pmem_stored_checksum(void);
|
||||
uint32_t pmem_calculated_checksum(void);
|
||||
|
||||
// battery capacity settings
|
||||
void set_battery_cap_mah(uint16_t mah); // 0 is not known; use assumed default based on device type/build config
|
||||
uint32_t battery_cap_mah();
|
||||
bool battery_cap_valid();
|
||||
|
||||
size_t data_size();
|
||||
|
||||
} /* namespace persistent_memory */
|
||||
|
||||
+1
-1
Submodule hackrf updated: 38e082b939...442d95e23d
@@ -0,0 +1,21 @@
|
||||
# Around 315 MHz (common for older remotes, key fobs in some regions)
|
||||
# Window centered on 315 MHz, covers 314.625 - 315.375 MHz
|
||||
f=315000000,d=Remotes 315M
|
||||
|
||||
# Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
|
||||
# Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
|
||||
f=433920000,d=Remotes 433.92M
|
||||
|
||||
# EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
|
||||
# Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
|
||||
a=433375000,b=434875000,s=750kHz,d=LoRa EU433
|
||||
|
||||
# EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
|
||||
# Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
|
||||
a=867375000,b=868875000,s=750kHz,d=LoRa EU868
|
||||
|
||||
# US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
|
||||
a=902000000,b=928250000,s=750kHz,d=LoRa US915
|
||||
|
||||
# TETRA Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
|
||||
a=380375000,b=390125000,s=750kHz,d=TETRA UP
|
||||
Reference in New Issue
Block a user