mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-13 18:19:33 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7c5a862da | |||
| b27c738b69 | |||
| 8c565bbf15 | |||
| 37aa9c046f | |||
| 195a6224a0 | |||
| ea238f4988 | |||
| 3514a9a608 | |||
| e2bca9aebb | |||
| 6ae164e59b | |||
| e6ad5efbb7 | |||
| 7bd370b5bc | |||
| 828eb67a52 | |||
| c4df2e66be | |||
| 47e95c0c47 | |||
| 3b5890d0aa | |||
| bee2dc17af | |||
| d6b0173e7a | |||
| 8eafe27955 | |||
| 2af95456a2 | |||
| 8ce48dbcf6 |
@@ -29,7 +29,7 @@ def print_nightly_changelog():
|
||||
|
||||
|
||||
def handle_get_request(path, offset=None):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
headers = {} if token == None else {"Authorization": f"Bearer {token}"}
|
||||
params = {"since": offset}
|
||||
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/"
|
||||
response = requests.get(url_base + path, headers=headers, params=params)
|
||||
|
||||
@@ -179,6 +179,7 @@ set(CPPSRC
|
||||
file.cpp
|
||||
freqman_db.cpp
|
||||
freqman.cpp
|
||||
io_convert.cpp
|
||||
io_file.cpp
|
||||
io_wave.cpp
|
||||
irq_controls.cpp
|
||||
|
||||
@@ -48,9 +48,9 @@ class AMOptionsView : public View {
|
||||
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
6, // number of blanking characters
|
||||
6, // Max option length
|
||||
{
|
||||
// using common messages from freqman.cpp
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
};
|
||||
|
||||
@@ -65,9 +65,9 @@ class NBFMOptionsView : public View {
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
4,
|
||||
3, // Max option length
|
||||
{
|
||||
// using common messages from freqman.cpp
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
|
||||
Text text_squelch{
|
||||
@@ -93,9 +93,9 @@ class WFMOptionsView : public View {
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
4,
|
||||
4, // Max option length
|
||||
{
|
||||
// using common messages from freqman.cpp
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace ui {
|
||||
|
||||
CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
|
||||
add_children({
|
||||
@@ -44,6 +43,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&option_bandwidth,
|
||||
&option_format,
|
||||
&record_view,
|
||||
&waterfall,
|
||||
});
|
||||
@@ -54,25 +54,30 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
this->field_frequency.set_step(v);
|
||||
};
|
||||
|
||||
option_format.set_selected_index(0); // Default to C16
|
||||
option_format.on_change = [this](size_t, uint32_t file_type) {
|
||||
record_view.set_file_type((RecordView::FileType)file_type);
|
||||
};
|
||||
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
|
||||
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
waterfall.on_hide();
|
||||
auto sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side.
|
||||
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options. */
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
|
||||
waterfall.stop();
|
||||
record_view.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
|
||||
waterfall.on_show();
|
||||
waterfall.start();
|
||||
};
|
||||
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
receiver_model.enable();
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
@@ -84,11 +89,6 @@ CaptureAppView::~CaptureAppView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void CaptureAppView::on_hide() {
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void CaptureAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ class CaptureAppView : public View {
|
||||
CaptureAppView(NavigationView& nav);
|
||||
~CaptureAppView();
|
||||
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
@@ -54,10 +53,9 @@ class CaptureAppView : public View {
|
||||
"rx_capture", app_settings::Mode::RX,
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
uint32_t sampling_rate = 0;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
|
||||
{{11 * 8, 1 * 16}, "Format:", Color::light_grey()},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
@@ -87,6 +85,12 @@ class CaptureAppView : public View {
|
||||
5,
|
||||
{}};
|
||||
|
||||
OptionsField option_format{
|
||||
{18 * 8, 1 * 16},
|
||||
3,
|
||||
{{"C16", RecordView::FileType::RawS16},
|
||||
{"C8", RecordView::FileType::RawS8}}};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
u"BBD_????.*",
|
||||
|
||||
@@ -63,17 +63,15 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
|
||||
if (metadata) {
|
||||
field_frequency.set_value(metadata->center_frequency);
|
||||
sample_rate = metadata->sample_rate;
|
||||
} else {
|
||||
sample_rate = 500000;
|
||||
transmitter_model.set_sampling_rate(metadata->sample_rate);
|
||||
}
|
||||
|
||||
// UI Fixup.
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
|
||||
progressbar.set_max(file_size / 1024);
|
||||
text_sample_rate.set(unit_auto_scale(transmitter_model.sampling_rate(), 3, 1) + "Hz");
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 2);
|
||||
auto duration = ms_duration(file_size, transmitter_model.sampling_rate(), 2);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
@@ -129,7 +127,6 @@ void GpsSimAppView::start() {
|
||||
});
|
||||
}
|
||||
|
||||
transmitter_model.set_sampling_rate(sample_rate);
|
||||
transmitter_model.enable();
|
||||
}
|
||||
|
||||
@@ -176,6 +173,11 @@ GpsSimAppView::GpsSimAppView(
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
if (!settings_.loaded()) {
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
transmitter_model.set_sampling_rate(2600000);
|
||||
}
|
||||
|
||||
field_frequency.set_step(5000);
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
|
||||
@@ -54,6 +54,8 @@ class GpsSimAppView : public View {
|
||||
std::string title() const override { return "GPS Sim TX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 1575420000;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
3000000 /* bandwidth */,
|
||||
@@ -64,7 +66,6 @@ class GpsSimAppView : public View {
|
||||
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
uint32_t sample_rate = 0;
|
||||
int32_t tx_gain{47};
|
||||
bool rf_amp{true}; // aux private var to store temporal, same as Replay App rf_amp user selection.
|
||||
static constexpr uint32_t baseband_bandwidth = 3000000; // filter bandwidth
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "portapack.hpp"
|
||||
@@ -75,7 +77,8 @@ void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 4);
|
||||
uint8_t sample_size = capture_file_sample_size(current()->path);
|
||||
auto duration = ms_duration(file_size, sample_rate, sample_size);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
@@ -110,7 +113,7 @@ void ReplayAppView::start() {
|
||||
|
||||
std::unique_ptr<stream::Reader> reader;
|
||||
|
||||
auto p = std::make_unique<FileReader>();
|
||||
auto p = std::make_unique<FileConvertReader>();
|
||||
auto open_error = p->open(file_path);
|
||||
if (open_error.is_valid()) {
|
||||
file_error();
|
||||
@@ -191,7 +194,7 @@ ReplayAppView::ReplayAppView(
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".C16");
|
||||
auto open_view = nav.push<FileLoadView>(".C*");
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
|
||||
@@ -251,7 +251,7 @@ void ControlsSwitchesWidget::on_show() {
|
||||
|
||||
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||
key_event_mask = 1 << toUType(key);
|
||||
long_press_key_event_mask = switch_long_press_occurred((size_t)key) ? key_event_mask : 0;
|
||||
long_press_key_event_mask = key_is_long_pressed(key) ? key_event_mask : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -264,9 +264,9 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
{32, 64, 16, 16}, // Down
|
||||
{32, 0, 16, 16}, // Up
|
||||
{32, 32, 16, 16}, // Select
|
||||
{96, 0, 16, 16}, // Dfu
|
||||
{16, 96, 16, 16}, // Encoder phase 0
|
||||
{48, 96, 16, 16}, // Encoder phase 1
|
||||
{96, 0, 16, 16}, // Dfu
|
||||
{96, 64, 16, 16}, // Touch
|
||||
}};
|
||||
|
||||
@@ -283,9 +283,9 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
{32 + 1, 64 + 1, 16 - 2, 16 - 2}, // Down
|
||||
{32 + 1, 0 + 1, 16 - 2, 16 - 2}, // Up
|
||||
{32 + 1, 32 + 1, 16 - 2, 16 - 2}, // Select
|
||||
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
|
||||
{16 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 0
|
||||
{48 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 1
|
||||
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
|
||||
}};
|
||||
|
||||
auto switches_raw = control::debug::switches();
|
||||
@@ -354,13 +354,13 @@ DebugControlsView::DebugControlsView(NavigationView& nav) {
|
||||
});
|
||||
|
||||
button_done.on_select = [&nav](Button&) {
|
||||
switches_long_press_enable(0);
|
||||
set_switches_long_press_config(0);
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
(void)v;
|
||||
switches_long_press_enable(options_switches_mode.selected_index_value());
|
||||
set_switches_long_press_config(options_switches_mode.selected_index_value());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -403,9 +403,10 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
|
||||
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
||||
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
||||
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -501,6 +502,56 @@ DebugFontsView::DebugFontsView(NavigationView& nav)
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
/* DebugScreenTest ****************************************************/
|
||||
|
||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
Painter painter;
|
||||
switch (key) {
|
||||
case KeyEvent::Select:
|
||||
nav_.pop();
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
|
||||
break;
|
||||
case KeyEvent::Left:
|
||||
pen_color = semirand();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_encoder(EncoderEvent delta) {
|
||||
pen_size = clip<int32_t>(pen_size + delta, 1, screen_width);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_touch(const TouchEvent event) {
|
||||
Painter painter;
|
||||
pen_pos = event.point;
|
||||
painter.fill_rectangle({pen_pos.x() - pen_size / 2, pen_pos.y() - pen_size / 2, pen_size, pen_size}, pen_color);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t DebugScreenTest::semirand() {
|
||||
static uint64_t seed{0x0102030405060708};
|
||||
seed = seed * 137;
|
||||
seed = (seed >> 1) | ((seed & 0x01) << 63);
|
||||
return (uint16_t)seed;
|
||||
}
|
||||
|
||||
void DebugScreenTest::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
|
||||
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
|
||||
pen_color = semirand();
|
||||
}
|
||||
|
||||
/* DebugLCRView *******************************************************/
|
||||
|
||||
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
|
||||
|
||||
@@ -321,6 +321,22 @@ class DebugFontsView : public View {
|
||||
NavigationView& nav_;
|
||||
};
|
||||
|
||||
class DebugScreenTest : public View {
|
||||
public:
|
||||
DebugScreenTest(NavigationView& nav);
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
uint16_t semirand();
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Point pen_pos{};
|
||||
Color pen_color{0};
|
||||
int16_t pen_size{10};
|
||||
};
|
||||
|
||||
/*class DebugLCRView : public View {
|
||||
public:
|
||||
DebugLCRView(NavigationView& nav, std::string lcrstring);
|
||||
|
||||
@@ -39,7 +39,9 @@ namespace fs = std::filesystem;
|
||||
namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path cxx_ext{u".C*"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
static const fs::path bmp_ext{u".BMP"};
|
||||
} // namespace ui
|
||||
@@ -78,14 +80,15 @@ fs::path get_partner_file(fs::path path) {
|
||||
return {};
|
||||
auto ext = path.extension();
|
||||
|
||||
if (path_iequal(ext, txt_ext))
|
||||
ext = c16_ext;
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
ext = txt_ext;
|
||||
else
|
||||
if (is_cxx_capture_file(path))
|
||||
path.replace_extension(txt_ext);
|
||||
else if (path_iequal(ext, txt_ext)) {
|
||||
path.replace_extension(c8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(c16_ext);
|
||||
} else
|
||||
return {};
|
||||
|
||||
path.replace_extension(ext);
|
||||
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{};
|
||||
}
|
||||
|
||||
@@ -141,6 +144,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
current_path = dir_path;
|
||||
entry_list.clear();
|
||||
auto filtering = !extension_filter.empty();
|
||||
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
@@ -150,7 +154,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
continue;
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path(), 0, true});
|
||||
@@ -497,7 +501,7 @@ bool FileManagerView::handle_file_open() {
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
return true;
|
||||
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
|
||||
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
nav_.push<PlaylistView>(path);
|
||||
return true;
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "utility.hpp"
|
||||
@@ -48,7 +50,6 @@ namespace fs = std::filesystem;
|
||||
namespace ui {
|
||||
|
||||
// TODO: consolidate extesions into a shared header?
|
||||
static const fs::path c16_ext = u".C16";
|
||||
static const fs::path ppl_ext = u".PPL";
|
||||
|
||||
void PlaylistView::load_file(const fs::path& playlist_path) {
|
||||
@@ -258,7 +259,7 @@ void PlaylistView::send_current_track() {
|
||||
chThdSleepMilliseconds(current()->ms_delay);
|
||||
|
||||
// Open the sample file to send.
|
||||
auto reader = std::make_unique<FileReader>();
|
||||
auto reader = std::make_unique<FileConvertReader>();
|
||||
auto error = reader->open(current()->path);
|
||||
if (error) {
|
||||
show_file_error(current()->path, "Can't open file to send.");
|
||||
@@ -323,9 +324,10 @@ void PlaylistView::update_ui() {
|
||||
chDbgAssert(!at_end(), "update_ui #1", "current_index_ invalid");
|
||||
|
||||
text_filename.set(current()->path.filename().string());
|
||||
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, 0) + "Hz");
|
||||
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, (current()->metadata.sample_rate > 1000000) ? 2 : 0) + "Hz");
|
||||
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
|
||||
uint8_t sample_size = capture_file_sample_size(current()->path);
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, sample_size);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
field_frequency.set_value(current()->metadata.center_frequency);
|
||||
|
||||
@@ -336,7 +338,7 @@ void PlaylistView::update_ui() {
|
||||
|
||||
progressbar_track.set_max(playlist_db_.size() - 1);
|
||||
progressbar_track.set_value(current_index_);
|
||||
progressbar_transmit.set_max(current()->file_size);
|
||||
progressbar_transmit.set_max(current()->file_size * sizeof(complex16_t) / sample_size);
|
||||
}
|
||||
|
||||
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
|
||||
@@ -406,7 +408,7 @@ PlaylistView::PlaylistView(
|
||||
button_add.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
auto open_view = nav_.push<FileLoadView>(".C16");
|
||||
auto open_view = nav_.push<FileLoadView>(".C*");
|
||||
open_view->push_dir(u"CAPTURES");
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
add_entry(std::move(path));
|
||||
@@ -459,7 +461,7 @@ PlaylistView::PlaylistView(
|
||||
auto ext = path.extension();
|
||||
if (path_iequal(ext, ppl_ext))
|
||||
on_file_changed(path);
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
else if (is_cxx_capture_file(path))
|
||||
add_entry(fs::path{path});
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ bool ReconView::recon_load_config_from_sd() {
|
||||
parse_int(line, recon_match_mode);
|
||||
break;
|
||||
case 6:
|
||||
parse_int(line, recon_match_mode);
|
||||
parse_int(line, wait);
|
||||
complete = true; // NB: Last entry.
|
||||
break;
|
||||
}
|
||||
@@ -890,7 +890,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (recon) {
|
||||
if (!timer) {
|
||||
status = 0;
|
||||
continuous_lock = false;
|
||||
freq_lock = 0;
|
||||
timer = local_recon_lock_duration;
|
||||
big_display.set_style(&Styles::white);
|
||||
@@ -907,22 +906,19 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
}
|
||||
if (db > squelch) // MATCHING LEVEL
|
||||
{
|
||||
continuous_lock = true;
|
||||
freq_lock++;
|
||||
timer += time_interval; // give some more time for next lock
|
||||
} else {
|
||||
// continuous, direct cut it if not consecutive match after 1 first match
|
||||
if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
|
||||
if (freq_lock > 0) {
|
||||
timer = 0;
|
||||
continuous_lock = false;
|
||||
}
|
||||
freq_lock = 0;
|
||||
timer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (freq_lock >= recon_lock_nb_match) // LOCKED
|
||||
{
|
||||
if (status != 2) {
|
||||
continuous_lock = false;
|
||||
status = 2;
|
||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
|
||||
@@ -963,10 +959,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
last_timer = timer;
|
||||
text_timer.set("TIMER: " + to_string_dec_int(timer));
|
||||
}
|
||||
if (timer) {
|
||||
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE) {
|
||||
timer -= time_interval;
|
||||
}
|
||||
|
||||
if (timer != 0) {
|
||||
timer -= time_interval;
|
||||
if (timer < 0) {
|
||||
timer = 0;
|
||||
}
|
||||
@@ -1126,7 +1121,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
void ReconView::recon_pause() {
|
||||
timer = 0;
|
||||
freq_lock = 0;
|
||||
continuous_lock = false;
|
||||
recon = false;
|
||||
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
@@ -1139,7 +1133,6 @@ void ReconView::recon_pause() {
|
||||
void ReconView::recon_resume() {
|
||||
timer = 0;
|
||||
freq_lock = 0;
|
||||
continuous_lock = false;
|
||||
recon = true;
|
||||
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
|
||||
@@ -564,7 +564,7 @@ ScannerView::ScannerView(
|
||||
field_lock_wait.set_value(2);
|
||||
|
||||
field_squelch.on_change = [this](int32_t v) { squelch = v; };
|
||||
field_squelch.set_value(-10);
|
||||
field_squelch.set_value(-30);
|
||||
|
||||
// LEARN FREQUENCIES
|
||||
std::string scanner_txt = "SCANNER";
|
||||
|
||||
@@ -480,12 +480,10 @@ class SetEncoderDialView : public View {
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 3 * 16},
|
||||
7,
|
||||
{{"LOWEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOWEST},
|
||||
{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH},
|
||||
{"HIGHEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGHEST}}};
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
|
||||
@@ -72,6 +72,7 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_state_.first_line = first_line;
|
||||
paint_state_.first_col = first_col;
|
||||
paint_state_.redraw_text = true;
|
||||
paint_state_.line = UINT32_MAX; // forget old cursor position when overwritten
|
||||
}
|
||||
|
||||
if (paint_state_.redraw_text) {
|
||||
@@ -221,6 +222,10 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
r.top() + (int)i * char_height,
|
||||
clear_width * char_width, char_height},
|
||||
style().background);
|
||||
|
||||
// if cursor line got overwritten, disable XOR of old cursor when displaying new cursor
|
||||
if (i == paint_state_.line)
|
||||
paint_state_.line = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,21 +233,32 @@ void TextViewer::paint_cursor(Painter& painter) {
|
||||
if (!has_focus())
|
||||
return;
|
||||
|
||||
auto draw_cursor = [this, &painter](uint32_t line, uint16_t col, Color c) {
|
||||
auto r = screen_rect();
|
||||
line = line - paint_state_.first_line;
|
||||
col = col - paint_state_.first_col;
|
||||
auto xor_cursor = [this, &painter](int32_t line, uint16_t col) {
|
||||
int cursor_width = char_width + 1;
|
||||
int x = (col - paint_state_.first_col) * char_width - 1;
|
||||
if (x < 0) { // cursor is one pixel narrower when in left column
|
||||
cursor_width--;
|
||||
x = 0;
|
||||
}
|
||||
int y = screen_rect().top() + (line - paint_state_.first_line) * char_height;
|
||||
|
||||
painter.draw_rectangle(
|
||||
{(int)col * char_width - 1,
|
||||
r.top() + (int)line * char_height,
|
||||
char_width + 1, char_height},
|
||||
c);
|
||||
// Converting one row at a time to reduce buffer size
|
||||
auto pbuf8 = cursor_.pixel_buffer8;
|
||||
auto pbuf = cursor_.pixel_buffer;
|
||||
for (auto col = 0; col < char_height; col++) {
|
||||
// Annoyingly, read_pixels uses a 24-bit pixel format vs draw_pixels which uses 16-bit
|
||||
portapack::display.read_pixels({x, y + col, cursor_width, 1}, pbuf8, cursor_width);
|
||||
for (auto i = 0; i < cursor_width; i++)
|
||||
pbuf[i] = Color(pbuf8[i].r, pbuf8[i].g, pbuf8[i].b).v ^ 0xFFFF;
|
||||
portapack::display.draw_pixels({x, y + col, cursor_width, 1}, pbuf, cursor_width);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear old cursor. CONSIDER: XOR cursor?
|
||||
draw_cursor(paint_state_.line, paint_state_.col, style().background);
|
||||
draw_cursor(cursor_.line, cursor_.col, style().foreground);
|
||||
if (paint_state_.line != UINT32_MAX) // only XOR old cursor if it still appears on the screen
|
||||
xor_cursor(paint_state_.line, paint_state_.col);
|
||||
|
||||
xor_cursor(cursor_.line, cursor_.col);
|
||||
|
||||
paint_state_.line = cursor_.line;
|
||||
paint_state_.col = cursor_.col;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,10 @@ class TextViewer : public Widget {
|
||||
uint32_t line{};
|
||||
uint16_t col{};
|
||||
ScrollDirection dir{ScrollDirection::Vertical};
|
||||
|
||||
// Pixel buffer used for cursor XOR'ing - Max cursor width = Max char width + 1
|
||||
ColorRGB888 pixel_buffer8[ui::char_width + 1]{};
|
||||
Color pixel_buffer[ui::char_width + 1]{};
|
||||
} cursor_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,16 @@
|
||||
|
||||
#include "core_control.hpp"
|
||||
|
||||
/* Set true to enable additional checks to ensure
|
||||
* M4 and M0 are synchronized before passing messages. */
|
||||
static constexpr bool enforce_core_sync = true;
|
||||
|
||||
/* Set true to enable check for baseband messages getting stuck.
|
||||
* This implies the baseband thread is not dequeuing and has probably stalled.
|
||||
* NB: This check adds a small amout of overhead to the message sending code
|
||||
* and may impact application perf if it is sending a lot of messages. */
|
||||
static constexpr bool check_for_message_hang = true;
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace baseband {
|
||||
@@ -40,8 +50,18 @@ static void send_message(const Message* const message) {
|
||||
// another message is present before setting new message.
|
||||
shared_memory.baseband_message = message;
|
||||
creg::m0apptxevent::assert_event();
|
||||
while (shared_memory.baseband_message)
|
||||
;
|
||||
|
||||
if constexpr (check_for_message_hang) {
|
||||
auto count = UINT32_MAX;
|
||||
while (shared_memory.baseband_message && --count)
|
||||
/* spin */;
|
||||
|
||||
if (count == 0)
|
||||
chDbgPanic("Baseband Send Fail");
|
||||
} else {
|
||||
while (shared_memory.baseband_message)
|
||||
/* spin */;
|
||||
}
|
||||
}
|
||||
|
||||
void AMConfig::apply() const {
|
||||
@@ -276,17 +296,28 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
|
||||
|
||||
static bool baseband_image_running = false;
|
||||
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag) {
|
||||
void run_image(const spi_flash::image_tag_t image_tag) {
|
||||
if (baseband_image_running) {
|
||||
chDbgPanic("BBRunning");
|
||||
}
|
||||
|
||||
creg::m4txevent::clear();
|
||||
shared_memory.clear_baseband_ready();
|
||||
|
||||
m4_init(image_tag, portapack::memory::map::m4_code, false);
|
||||
m4_init(image_tag, memory::map::m4_code, false);
|
||||
baseband_image_running = true;
|
||||
|
||||
creg::m4txevent::enable();
|
||||
|
||||
if constexpr (enforce_core_sync) {
|
||||
// Wait up to 3 seconds for baseband to start handling events.
|
||||
auto count = 3'000u;
|
||||
while (!shared_memory.baseband_ready && --count)
|
||||
chThdSleepMilliseconds(1);
|
||||
|
||||
if (count == 0)
|
||||
chDbgPanic("Baseband Sync Fail");
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
|
||||
@@ -20,21 +20,20 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "core_control.hpp"
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "message.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "core_control.hpp"
|
||||
#include "hal.h"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "lz4.h"
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to, const bool full_reset) {
|
||||
const portapack::spi_flash::chunk_t* chunk = reinterpret_cast<const portapack::spi_flash::chunk_t*>(portapack::spi_flash::images.base());
|
||||
using namespace lpc43xx;
|
||||
using namespace portapack;
|
||||
|
||||
void m4_init(const spi_flash::image_tag_t image_tag, const memory::region_t to, const bool full_reset) {
|
||||
const spi_flash::chunk_t* chunk = reinterpret_cast<const spi_flash::chunk_t*>(spi_flash::images.base());
|
||||
while (chunk->tag) {
|
||||
if (chunk->tag == image_tag) {
|
||||
const void* src = &chunk->data[0];
|
||||
@@ -49,9 +48,8 @@ void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack:
|
||||
LPC_CREG->M4MEMMAP = to.base();
|
||||
|
||||
/* Reset M4 core and optionally all peripherals */
|
||||
LPC_RGU->RESET_CTRL[0] = (full_reset) ? (1 << 1) // PERIPH_RST
|
||||
: (1 << 13) // M4_RST
|
||||
;
|
||||
LPC_RGU->RESET_CTRL[0] = (full_reset) ? (1 << 1) // PERIPH_RST
|
||||
: (1 << 13); // M4_RST
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,17 @@
|
||||
*/
|
||||
|
||||
#include "file.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
#include <locale>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
|
||||
Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BYTE mode) {
|
||||
auto result = f_open(&f, reinterpret_cast<const TCHAR*>(filename.c_str()), mode);
|
||||
if (result == FR_OK) {
|
||||
@@ -507,6 +512,19 @@ bool path_iequal(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_cxx_capture_file(const path& filename) {
|
||||
auto ext = filename.extension();
|
||||
return path_iequal(c8_ext, ext) || path_iequal(c16_ext, ext);
|
||||
}
|
||||
|
||||
uint8_t capture_file_sample_size(const path& filename) {
|
||||
if (path_iequal(filename.extension(), c8_ext))
|
||||
return sizeof(complex8_t);
|
||||
if (path_iequal(filename.extension(), c16_ext))
|
||||
return sizeof(complex16_t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
const std::filesystem::path& path,
|
||||
const std::filesystem::path& wild)
|
||||
|
||||
@@ -168,6 +168,8 @@ path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
/* Case insensitive path equality on underlying "native" string. */
|
||||
bool path_iequal(const path& lhs, const path& rhs);
|
||||
bool is_cxx_capture_file(const path& filename);
|
||||
uint8_t capture_file_sample_size(const path& filename);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
|
||||
@@ -23,6 +23,31 @@
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
uint8_t Debounce::state() {
|
||||
bool v = !pulse_upon_release_ && (state_ || simulated_pulse_);
|
||||
if (simulated_pulse_)
|
||||
simulated_pulse_ = false;
|
||||
return v;
|
||||
}
|
||||
|
||||
void Debounce::enable_repeat() {
|
||||
repeat_enabled_ = true;
|
||||
}
|
||||
|
||||
bool Debounce::get_long_press_enabled() const {
|
||||
return long_press_enabled_;
|
||||
}
|
||||
|
||||
void Debounce::set_long_press_enabled(bool v) {
|
||||
long_press_enabled_ = v;
|
||||
}
|
||||
|
||||
bool Debounce::long_press_occurred() {
|
||||
bool v = long_press_occurred_;
|
||||
long_press_occurred_ = false;
|
||||
return v;
|
||||
}
|
||||
|
||||
// Returns TRUE if button state changed (after debouncing)
|
||||
bool Debounce::feed(const uint8_t bit) {
|
||||
history_ = (history_ << 1) | (bit & 1);
|
||||
@@ -63,11 +88,12 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
// Has button been released for DEBOUNCE_COUNT ticks?
|
||||
if ((history_ & DEBOUNCE_MASK) == 0) {
|
||||
// Button has been released when long_press_enabled_ and before LONG_PRESS_DELAY was reached;
|
||||
// allow state() function to finally return a single press indication
|
||||
// (in long press mode, apps won't see button press until the button is released)
|
||||
// allow state() function to finally return a single press indication (simulated pulse).
|
||||
// Note: In long press mode, apps won't see button press until the button is released.
|
||||
if (pulse_upon_release_) {
|
||||
// leaving state_==1 for one cycle
|
||||
pulse_upon_release_ = 0;
|
||||
// force state() function (called by EventDispatcher) to return simulated press for one cycle
|
||||
simulated_pulse_ = true;
|
||||
pulse_upon_release_ = false;
|
||||
} else {
|
||||
state_ = 0;
|
||||
}
|
||||
@@ -84,10 +110,11 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
// Button is being held down and long_press support is enabled for this key:
|
||||
// if LONG_PRESS_DELAY is reached then finally report that switch is pressed and set flag
|
||||
// indicating it was a LONG press
|
||||
// (note that repease_support and long_press support are mutually exclusive)
|
||||
if (held_time_ == LONG_PRESS_DELAY) {
|
||||
// (note that repeat_support and long_press support are mutually exclusive)
|
||||
if (held_time_ >= LONG_PRESS_DELAY) {
|
||||
long_press_occurred_ = true;
|
||||
pulse_upon_release_ = 0;
|
||||
simulated_pulse_ = true;
|
||||
pulse_upon_release_ = false;
|
||||
held_time_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -31,39 +31,27 @@
|
||||
// # of timer0 ticks before a held button starts being counted as repeated presses
|
||||
#define REPEAT_INITIAL_DELAY 250
|
||||
#define REPEAT_SUBSEQUENT_DELAY 92
|
||||
#define LONG_PRESS_DELAY 1000
|
||||
#define LONG_PRESS_DELAY 800
|
||||
|
||||
class Debounce {
|
||||
public:
|
||||
bool feed(const uint8_t bit);
|
||||
|
||||
uint8_t state() const {
|
||||
return (pulse_upon_release_) ? 0 : state_;
|
||||
}
|
||||
|
||||
void enable_repeat() {
|
||||
repeat_enabled_ = true;
|
||||
}
|
||||
|
||||
void set_long_press_support(bool v) {
|
||||
long_press_enabled_ = v;
|
||||
}
|
||||
|
||||
bool long_press_occurred() {
|
||||
bool v = long_press_occurred_;
|
||||
long_press_occurred_ = false;
|
||||
return v;
|
||||
}
|
||||
uint8_t state();
|
||||
void enable_repeat();
|
||||
bool get_long_press_enabled() const;
|
||||
void set_long_press_enabled(bool v);
|
||||
bool long_press_occurred();
|
||||
|
||||
private:
|
||||
uint8_t history_{0};
|
||||
uint8_t state_{0};
|
||||
bool repeat_enabled_{0};
|
||||
bool repeat_enabled_{false};
|
||||
uint16_t repeat_ctr_{0};
|
||||
uint16_t held_time_{0};
|
||||
bool pulse_upon_release_{0};
|
||||
bool long_press_enabled_{0};
|
||||
bool long_press_occurred_{0};
|
||||
bool pulse_upon_release_{false};
|
||||
bool simulated_pulse_{false};
|
||||
bool long_press_enabled_{false};
|
||||
bool long_press_occurred_{false};
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -32,24 +32,66 @@
|
||||
// 12 degrees of rotation.
|
||||
//
|
||||
// For each encoder "pulse" there are 4 state transitions, and we can choose
|
||||
// how many transitions are needed before movement is registered
|
||||
static const int8_t transition_map[16] = {
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
// between looking at all of them (high sensitivity), half of them (medium/default),
|
||||
// or one quarter of them (low sensitivity).
|
||||
static const int8_t transition_map[][16] = {
|
||||
// Normal (Medium) Sensitivity -- default
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Low Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
0, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
0, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// High Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
};
|
||||
|
||||
int_fast8_t Encoder::update(
|
||||
@@ -60,13 +102,6 @@ int_fast8_t Encoder::update(
|
||||
state <<= 1;
|
||||
state |= phase_1;
|
||||
|
||||
int_fast8_t retval = transition_map[state & 0xf];
|
||||
|
||||
transition_count += retval;
|
||||
if (abs(transition_count) > portapack::persistent_memory::config_encoder_dial_sensitivity())
|
||||
transition_count = 0;
|
||||
else
|
||||
retval = 0;
|
||||
|
||||
return retval;
|
||||
// dial sensitivity setting is stored in pmem
|
||||
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ class Encoder {
|
||||
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
int_fast8_t transition_count{0};
|
||||
};
|
||||
|
||||
#endif /*__ENCODER_H__*/
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "io_convert.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext = u".C8";
|
||||
|
||||
namespace file_convert {
|
||||
|
||||
// Convert buffer contents from c16 to c8.
|
||||
// Same buffer used for input & output; input size is bytes; output size is bytes/2.
|
||||
void c16_to_c8(const void* buffer, File::Size bytes) {
|
||||
complex16_t* src = (complex16_t*)buffer;
|
||||
complex8_t* dest = (complex8_t*)buffer;
|
||||
|
||||
// Shift isn't used here because it would amplify noise at center freq since it's a signed number
|
||||
// i.e. ((-1 >> 8) << 8) = -256, whereas (-1 / 256) * 256 = 0
|
||||
for (File::Size i = 0; i < bytes / sizeof(complex16_t); i++) {
|
||||
auto re_out = src[i].real() / 256;
|
||||
auto im_out = src[i].imag() / 256;
|
||||
dest[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert c8 buffer to c16 buffer.
|
||||
// Same buffer used for input & output; input size is bytes; output size is 2*bytes.
|
||||
void c8_to_c16(const void* buffer, File::Size bytes) {
|
||||
complex8_t* src = (complex8_t*)buffer;
|
||||
complex16_t* dest = (complex16_t*)buffer;
|
||||
uint32_t i = bytes / sizeof(complex8_t);
|
||||
|
||||
if (i != 0) {
|
||||
do {
|
||||
i--;
|
||||
auto re_out = (int16_t)src[i].real() * 256; // C8 to C16 conversion;
|
||||
auto im_out = (int16_t)src[i].imag() * 256; // Can't shift signed numbers left, so using multiply
|
||||
dest[i] = {(int16_t)re_out, (int16_t)im_out};
|
||||
} while (i != 0);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace file_convert */
|
||||
|
||||
// Automatically enables C8/C16 conversion based on file extension
|
||||
Optional<File::Error> FileConvertReader::open(const std::filesystem::path& filename) {
|
||||
convert_c8_to_c16 = path_iequal(filename.extension(), c8_ext);
|
||||
return file_.open(filename);
|
||||
}
|
||||
|
||||
// If C8 conversion enabled, half the number of bytes are read from the file & expanded to fill the whole buffer.
|
||||
File::Result<File::Size> FileConvertReader::read(void* const buffer, const File::Size bytes) {
|
||||
auto read_result = file_.read(buffer, convert_c8_to_c16 ? bytes / 2 : bytes);
|
||||
if (read_result.is_ok()) {
|
||||
if (convert_c8_to_c16) {
|
||||
file_convert::c8_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() * 2;
|
||||
}
|
||||
bytes_read_ += read_result.value();
|
||||
}
|
||||
return read_result;
|
||||
}
|
||||
|
||||
// Automatically enables C8/C16 conversion based on file extension
|
||||
Optional<File::Error> FileConvertWriter::create(const std::filesystem::path& filename) {
|
||||
convert_c16_to_c8 = path_iequal(filename.extension(), c8_ext);
|
||||
return file_.create(filename);
|
||||
}
|
||||
|
||||
// If C8 conversion is enabled, half the number of bytes are written to the file.
|
||||
File::Result<File::Size> FileConvertWriter::write(const void* const buffer, const File::Size bytes) {
|
||||
if (convert_c16_to_c8) {
|
||||
file_convert::c16_to_c8(buffer, bytes);
|
||||
}
|
||||
auto write_result = file_.write(buffer, convert_c16_to_c8 ? bytes / 2 : bytes);
|
||||
if (write_result.is_ok()) {
|
||||
if (convert_c16_to_c8) {
|
||||
write_result = write_result.value() * 2;
|
||||
}
|
||||
bytes_written_ += write_result.value();
|
||||
}
|
||||
return write_result;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "io_file.hpp"
|
||||
|
||||
#include "io.hpp"
|
||||
#include "file.hpp"
|
||||
#include "optional.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace file_convert {
|
||||
|
||||
void c8_to_c16(const void* buffer, File::Size bytes);
|
||||
void c16_to_c8(const void* buffer, File::Size bytes);
|
||||
|
||||
} /* namespace file_convert */
|
||||
|
||||
class FileConvertReader : public stream::Reader {
|
||||
public:
|
||||
FileConvertReader() = default;
|
||||
|
||||
FileConvertReader(const FileConvertReader&) = delete;
|
||||
FileConvertReader& operator=(const FileConvertReader&) = delete;
|
||||
FileConvertReader(FileConvertReader&& file) = delete;
|
||||
FileConvertReader& operator=(FileConvertReader&&) = delete;
|
||||
|
||||
Optional<File::Error> open(const std::filesystem::path& filename);
|
||||
|
||||
File::Result<File::Size> read(void* const buffer, const File::Size bytes) override;
|
||||
const File& file() const& { return file_; }
|
||||
|
||||
bool convert_c8_to_c16{};
|
||||
|
||||
protected:
|
||||
File file_{};
|
||||
uint64_t bytes_read_{0};
|
||||
};
|
||||
|
||||
class FileConvertWriter : public stream::Writer {
|
||||
public:
|
||||
FileConvertWriter() = default;
|
||||
|
||||
FileConvertWriter(const FileConvertWriter&) = delete;
|
||||
FileConvertWriter& operator=(const FileConvertWriter&) = delete;
|
||||
FileConvertWriter(FileConvertWriter&& file) = delete;
|
||||
FileConvertWriter& operator=(FileConvertWriter&&) = delete;
|
||||
|
||||
Optional<File::Error> create(const std::filesystem::path& filename);
|
||||
|
||||
File::Result<File::Size> write(const void* const buffer, const File::Size bytes) override;
|
||||
const File& file() const& { return file_; }
|
||||
|
||||
bool convert_c16_to_c8{};
|
||||
|
||||
protected:
|
||||
File file_{};
|
||||
uint64_t bytes_written_{0};
|
||||
};
|
||||
@@ -22,32 +22,33 @@
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include "debounce.hpp"
|
||||
#include "encoder.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "hal.h"
|
||||
#include "touch.hpp"
|
||||
#include "touch_adc.hpp"
|
||||
#include "encoder.hpp"
|
||||
#include "debounce.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
#include "portapack_io.hpp"
|
||||
|
||||
#include "hackrf_hal.hpp"
|
||||
|
||||
using namespace hackrf::one;
|
||||
using namespace portapack;
|
||||
|
||||
static Thread* thread_controls_event = NULL;
|
||||
|
||||
static std::array<Debounce, 8> switch_debounce;
|
||||
// Index with the Switch enum.
|
||||
static std::array<Debounce, 6> switch_debounce;
|
||||
static std::array<Debounce, 2> encoder_debounce;
|
||||
|
||||
static_assert(std::size(switch_debounce) == toUType(Switch::Dfu) + 1);
|
||||
|
||||
static Encoder encoder;
|
||||
|
||||
static volatile uint32_t encoder_position{0};
|
||||
|
||||
static volatile uint32_t touch_phase{0};
|
||||
|
||||
/* TODO: Change how touch scanning works. It produces a decent amount of noise
|
||||
@@ -60,11 +61,11 @@ static volatile uint32_t touch_phase{0};
|
||||
* Noise will only occur when the panel is being touched. Not ideal, but
|
||||
* an acceptable improvement.
|
||||
*/
|
||||
static std::array<portapack::IO::TouchPinsConfig, 3> touch_pins_configs{
|
||||
static std::array<IO::TouchPinsConfig, 3> touch_pins_configs{
|
||||
/* State machine will pause here until touch is detected. */
|
||||
portapack::IO::TouchPinsConfig::SensePressure,
|
||||
portapack::IO::TouchPinsConfig::SenseX,
|
||||
portapack::IO::TouchPinsConfig::SenseY,
|
||||
IO::TouchPinsConfig::SensePressure,
|
||||
IO::TouchPinsConfig::SenseX,
|
||||
IO::TouchPinsConfig::SenseY,
|
||||
};
|
||||
|
||||
static touch::Frame temp_frame;
|
||||
@@ -80,7 +81,7 @@ static bool touch_update() {
|
||||
const auto current_phase = touch_pins_configs[touch_phase];
|
||||
|
||||
switch (current_phase) {
|
||||
case portapack::IO::TouchPinsConfig::SensePressure: {
|
||||
case IO::TouchPinsConfig::SensePressure: {
|
||||
const auto z1 = samples.xp - samples.xn;
|
||||
const auto z2 = samples.yp - samples.yn;
|
||||
const auto touch_raw = (z1 > touch::touch_threshold) || (z2 > touch::touch_threshold);
|
||||
@@ -94,11 +95,11 @@ static bool touch_update() {
|
||||
}
|
||||
} break;
|
||||
|
||||
case portapack::IO::TouchPinsConfig::SenseX:
|
||||
case IO::TouchPinsConfig::SenseX:
|
||||
temp_frame.x += samples;
|
||||
break;
|
||||
|
||||
case portapack::IO::TouchPinsConfig::SenseY:
|
||||
case IO::TouchPinsConfig::SenseY:
|
||||
temp_frame.y += samples;
|
||||
break;
|
||||
|
||||
@@ -122,25 +123,57 @@ static bool touch_update() {
|
||||
|
||||
static uint8_t switches_raw = 0;
|
||||
|
||||
/* The raw data is not packed in a way that makes looping over it easy.
|
||||
* One option would be an accessor helper (RawSwitch). Another option
|
||||
* is to swizzle the bits into a friendlier order. */
|
||||
// /* Type to access the bits in the raw switch data. */
|
||||
// struct RawSwitch {
|
||||
// const uint8_t raw_{0};
|
||||
|
||||
// uint8_t right() const { return (raw_ >> 0) & 1; }
|
||||
// uint8_t left() const { return (raw_ >> 1) & 1; }
|
||||
// uint8_t down() const { return (raw_ >> 2) & 1; }
|
||||
// uint8_t up() const { return (raw_ >> 3) & 1; }
|
||||
// uint8_t select() const { return (raw_ >> 4) & 1; }
|
||||
// uint8_t rot_a() const { return (raw_ >> 5) & 1; }
|
||||
// uint8_t rot_b() const { return (raw_ >> 6) & 1; }
|
||||
// uint8_t dfu() const { return (raw_ >> 7) & 1; }};
|
||||
|
||||
static uint8_t swizzle_raw(uint8_t raw) {
|
||||
return (raw & 0x1F) | // Keep the bottom 5 bits the same.
|
||||
((raw >> 2) & 0x20) | // Shift the DFU bit down to bit 6.
|
||||
((raw << 1) & 0xC0); // Shift the encoder bits up to be 7 & 8.
|
||||
}
|
||||
|
||||
static bool switches_update(const uint8_t raw) {
|
||||
// TODO: Only fire event on press, not release?
|
||||
bool switch_changed = false;
|
||||
for (size_t i = 0; i < switch_debounce.size(); i++) {
|
||||
switch_changed |= switch_debounce[i].feed((raw >> i) & 1);
|
||||
|
||||
for (size_t i = 0; i < switch_debounce.size(); ++i) {
|
||||
uint8_t bit = (raw >> i) & 0x01;
|
||||
switch_changed |= switch_debounce[i].feed(bit);
|
||||
}
|
||||
|
||||
return switch_changed;
|
||||
}
|
||||
|
||||
static bool encoder_update(const uint8_t raw) {
|
||||
bool encoder_changed = false;
|
||||
|
||||
encoder_changed |= encoder_debounce[0].feed((raw >> 6) & 0x01);
|
||||
encoder_changed |= encoder_debounce[1].feed((raw >> 7) & 0x01);
|
||||
|
||||
return encoder_changed;
|
||||
}
|
||||
|
||||
static bool encoder_read() {
|
||||
const auto delta = encoder.update(
|
||||
switch_debounce[5].state(),
|
||||
switch_debounce[6].state());
|
||||
encoder_debounce[0].state(),
|
||||
encoder_debounce[1].state());
|
||||
|
||||
if (delta != 0) {
|
||||
encoder_position += delta;
|
||||
return true;
|
||||
;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -149,11 +182,13 @@ static bool encoder_read() {
|
||||
void timer0_callback(GPTDriver* const) {
|
||||
eventmask_t event_mask = 0;
|
||||
if (touch_update()) event_mask |= EVT_MASK_TOUCH;
|
||||
switches_raw = portapack::io.io_update(touch_pins_configs[touch_phase]);
|
||||
if (switches_update(switches_raw)) {
|
||||
|
||||
switches_raw = swizzle_raw(io.io_update(touch_pins_configs[touch_phase]));
|
||||
if (switches_update(switches_raw))
|
||||
event_mask |= EVT_MASK_SWITCHES;
|
||||
if (encoder_read()) event_mask |= EVT_MASK_ENCODER;
|
||||
}
|
||||
|
||||
if (encoder_update(switches_raw) && encoder_read())
|
||||
event_mask |= EVT_MASK_ENCODER;
|
||||
|
||||
/* Signal event loop */
|
||||
if (event_mask) {
|
||||
@@ -189,37 +224,40 @@ void controls_init() {
|
||||
gptStartContinuous(&GPTD1, timer0_match_count);
|
||||
|
||||
// Enable repeat for directional switches only
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
switch_debounce[i].enable_repeat();
|
||||
for (auto i = Switch::Right; i <= Switch::Up; incr(i))
|
||||
switch_debounce[toUType(i)].enable_repeat();
|
||||
}
|
||||
|
||||
// Note: Called by event handler or apps, not in ISR, so some presses might be missed during high CPU utilization
|
||||
SwitchesState get_switches_state() {
|
||||
SwitchesState result;
|
||||
|
||||
// Right, Left, Down, Up, & Select switches
|
||||
for (size_t i = 0; i < result.size() - 1; i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
result[i] = switch_debounce[i].state();
|
||||
}
|
||||
|
||||
// Grab Dfu switch from bit 7 and return in bit 5 so that all switches are grouped together in this 6-bit result
|
||||
result[(size_t)Switch::Dfu] = switch_debounce[7].state();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Configure which switches support long press (note those switches will not support Repeat function)
|
||||
void switches_long_press_enable(SwitchesState switches_long_press_enabled) {
|
||||
// Right, Left, Down, Up, & Select switches
|
||||
for (size_t i = 0; i < switches_long_press_enabled.size() - 1; i++) {
|
||||
switch_debounce[i].set_long_press_support(switches_long_press_enabled[i]);
|
||||
}
|
||||
/* Gets the long press enabled state for all the switches. */
|
||||
SwitchesState get_switches_long_press_config() {
|
||||
SwitchesState result;
|
||||
|
||||
// Dfu switch
|
||||
switch_debounce[7].set_long_press_support(switches_long_press_enabled[(size_t)Switch::Dfu]);
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
result[i] = switch_debounce[i].get_long_press_enabled();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool switch_long_press_occurred(size_t v) {
|
||||
return (v == (size_t)Switch::Dfu) ? switch_debounce[7].long_press_occurred() : switch_debounce[v].long_press_occurred();
|
||||
/* Configures which switches support long press.
|
||||
* NB: those switches will not support Repeat function. */
|
||||
void set_switches_long_press_config(SwitchesState switch_config) {
|
||||
for (size_t i = 0; i < switch_config.size(); i++)
|
||||
switch_debounce[i].set_long_press_enabled(switch_config[i]);
|
||||
}
|
||||
|
||||
bool switch_is_long_pressed(Switch s) {
|
||||
return switch_debounce[toUType(s)].long_press_occurred();
|
||||
}
|
||||
|
||||
EncoderPosition get_encoder_position() {
|
||||
|
||||
@@ -28,25 +28,28 @@
|
||||
#include "touch.hpp"
|
||||
|
||||
// Must be same values as in ui::KeyEvent
|
||||
enum class Switch {
|
||||
// TODO: Why not just reuse one or the other?
|
||||
enum class Switch : uint8_t {
|
||||
Right = 0,
|
||||
Left = 1,
|
||||
Down = 2,
|
||||
Up = 3,
|
||||
Sel = 4,
|
||||
Dfu = 5,
|
||||
Dfu = 5
|
||||
};
|
||||
|
||||
// Index with the Switch enum.
|
||||
using SwitchesState = std::bitset<6>;
|
||||
|
||||
using EncoderPosition = uint32_t;
|
||||
|
||||
void controls_init();
|
||||
SwitchesState get_switches_state();
|
||||
EncoderPosition get_encoder_position();
|
||||
touch::Frame get_touch_frame();
|
||||
void switches_long_press_enable(SwitchesState v);
|
||||
bool switch_long_press_occurred(size_t v);
|
||||
|
||||
SwitchesState get_switches_long_press_config();
|
||||
void set_switches_long_press_config(SwitchesState switch_config);
|
||||
bool switch_is_long_pressed(Switch s);
|
||||
|
||||
namespace control {
|
||||
namespace debug {
|
||||
|
||||
@@ -238,6 +238,7 @@ void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum) {
|
||||
|
||||
void set_baseband_rate(const uint32_t rate) {
|
||||
portapack::clock_manager.set_sampling_frequency(rate);
|
||||
// TODO: actually set baseband too?
|
||||
}
|
||||
|
||||
void set_antenna_bias(const bool on) {
|
||||
|
||||
@@ -32,6 +32,7 @@ using namespace portapack;
|
||||
#include "string_format.hpp"
|
||||
#include "complex.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_font_fixed_5x8.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -243,6 +244,7 @@ void GeoMap::paint(Painter& painter) {
|
||||
|
||||
// Draw the other markers
|
||||
draw_markers(painter);
|
||||
draw_scale(painter);
|
||||
markerListUpdated = false;
|
||||
set_clean();
|
||||
}
|
||||
@@ -283,6 +285,10 @@ void GeoMap::move(const float lon, const float lat) {
|
||||
x_pos = map_width - map_rect.width();
|
||||
if (y_pos > (map_height + map_rect.height()))
|
||||
y_pos = map_height - map_rect.height();
|
||||
|
||||
// Scale calculation
|
||||
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
|
||||
pixels_per_km = (map_rect.width() / 2) / km_per_deg_lon;
|
||||
}
|
||||
|
||||
bool GeoMap::init() {
|
||||
@@ -318,6 +324,24 @@ bool GeoMap::manual_panning() {
|
||||
return manual_panning_;
|
||||
}
|
||||
|
||||
void GeoMap::draw_scale(Painter& painter) {
|
||||
uint16_t km = 100;
|
||||
uint16_t scale_width = km * pixels_per_km * map_zoom;
|
||||
|
||||
while (scale_width > screen_width / 2) {
|
||||
scale_width /= 2;
|
||||
km /= 2;
|
||||
}
|
||||
|
||||
std::string km_string = to_string_dec_uint(km) + " km";
|
||||
|
||||
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 4}, {scale_width, 2}}, Color::black());
|
||||
display.fill_rectangle({{screen_width - 5, screen_height - 8}, {2, 6}}, Color::black());
|
||||
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 8}, {2, 6}}, Color::black());
|
||||
|
||||
painter.draw_string({(uint16_t)(screen_width - 25 - scale_width - km_string.length() * 5 / 2), screen_height - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
|
||||
}
|
||||
|
||||
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
|
||||
Point arrow_a, arrow_b, arrow_c;
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ class GeoMap : public Widget {
|
||||
MapMarkerStored store_marker(GeoMarker& marker);
|
||||
|
||||
private:
|
||||
void draw_scale(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_markers(Painter& painter);
|
||||
@@ -199,6 +200,7 @@ class GeoMap : public Widget {
|
||||
int32_t prev_x_pos{0xFFFF}, prev_y_pos{0xFFFF};
|
||||
float lat_{};
|
||||
float lon_{};
|
||||
float pixels_per_km{};
|
||||
uint16_t angle_{};
|
||||
std::string tag_{};
|
||||
|
||||
|
||||
@@ -20,16 +20,15 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "irq_controls.hpp"
|
||||
#include "max283x.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "max283x.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* FrequencyField ********************************************************/
|
||||
@@ -38,15 +37,26 @@ FrequencyField::FrequencyField(
|
||||
const Point parent_pos)
|
||||
: Widget{{parent_pos, {8 * 10, 16}}},
|
||||
length_{11},
|
||||
range(rf::tuning_range) {
|
||||
range_{rf::tuning_range} {
|
||||
initial_switch_config_ = get_switches_long_press_config();
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
FrequencyField::~FrequencyField() {
|
||||
reset_switch_config();
|
||||
}
|
||||
|
||||
rf::Frequency FrequencyField::value() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
void FrequencyField::set_value(rf::Frequency new_value) {
|
||||
// While in digit mode, don't update if the value is
|
||||
// out of range. That way going out of range doesn't
|
||||
// cause all the other digits to reset.
|
||||
if (digit_mode_ && !range_.contains_inc(new_value))
|
||||
return;
|
||||
|
||||
new_value = clamp_value(new_value);
|
||||
|
||||
if (new_value != value_) {
|
||||
@@ -59,48 +69,98 @@ void FrequencyField::set_value(rf::Frequency new_value) {
|
||||
}
|
||||
|
||||
void FrequencyField::set_step(rf::Frequency new_value) {
|
||||
step = new_value;
|
||||
// TODO: Quantize current frequency to a step of the new size?
|
||||
step_ = new_value;
|
||||
}
|
||||
|
||||
void FrequencyField::set_allow_digit_mode(bool allowed) {
|
||||
allow_digit_mode_ = allowed;
|
||||
|
||||
if (!allowed && digit_mode_) {
|
||||
digit_mode_ = false;
|
||||
reset_switch_config();
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencyField::paint(Painter& painter) {
|
||||
const std::string str_value = to_string_short_freq(value_);
|
||||
|
||||
const auto str_value = to_string_short_freq(value_);
|
||||
const auto paint_style = has_focus() ? style().invert() : style();
|
||||
|
||||
painter.draw_string(
|
||||
screen_pos(),
|
||||
paint_style,
|
||||
str_value);
|
||||
|
||||
// Highlight current digit in digit_mode.
|
||||
if (digit_mode_) {
|
||||
auto p = screen_pos();
|
||||
p += {digit_ * char_width, 0};
|
||||
painter.draw_char(p, Styles::bg_blue, str_value[digit_]);
|
||||
}
|
||||
}
|
||||
|
||||
bool FrequencyField::on_key(const ui::KeyEvent event) {
|
||||
if (event == ui::KeyEvent::Select) {
|
||||
bool FrequencyField::on_key(KeyEvent event) {
|
||||
if (event == KeyEvent::Select) {
|
||||
// Toggle 'digit' mode with long-press.
|
||||
if (digit_mode_ || key_is_long_pressed(event)) {
|
||||
digit_mode_ = !digit_mode_;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (on_edit) {
|
||||
on_edit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (digit_mode_) {
|
||||
constexpr uint8_t decimal_pos = 4;
|
||||
int8_t delta = 0;
|
||||
|
||||
switch (event) {
|
||||
case KeyEvent::Up:
|
||||
set_value(value_ + digit_step());
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
set_value(value_ - digit_step());
|
||||
break;
|
||||
case KeyEvent::Left:
|
||||
delta = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta = 1;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta != 0) {
|
||||
digit_ += delta;
|
||||
|
||||
// If on decimal, skip it.
|
||||
if (digit_ == decimal_pos)
|
||||
digit_ += delta;
|
||||
// Otherwise ensure in bounds.
|
||||
else
|
||||
digit_ = clip<int8_t>(digit_, 0, 8);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FrequencyField::on_encoder(const EncoderEvent delta) {
|
||||
if (step == 0) { // 'Auto' mode.'
|
||||
auto ms = RTT2MS(halGetCounterValue());
|
||||
auto delta_ms = last_ms_ <= ms ? ms - last_ms_ : ms;
|
||||
last_ms_ = ms;
|
||||
if (digit_mode_)
|
||||
set_value(value_ + (delta * digit_step()));
|
||||
else
|
||||
set_value(value_ + (delta * step_));
|
||||
|
||||
// The goal is to map 'scale' to a range of about 10 to 10M.
|
||||
// The faster the encoder is rotated, the larger the step.
|
||||
// Linear doesn't feel right. Hyperbolic felt better.
|
||||
// To get these magic numbers, I graphed the function until the
|
||||
// curve shape seemed about right then tested on device.
|
||||
delta_ms = std::min(145ull, delta_ms) + 5; // Prevent DIV/0
|
||||
int64_t scale = 200'000'000 / (0.001'55 * std::pow(delta_ms, 5.45)) + 8;
|
||||
set_value(value() + (delta * scale));
|
||||
} else {
|
||||
set_value(value() + (delta * step));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -112,13 +172,49 @@ bool FrequencyField::on_touch(const TouchEvent event) {
|
||||
}
|
||||
|
||||
void FrequencyField::on_focus() {
|
||||
if (on_show_options) {
|
||||
if (on_show_options)
|
||||
on_show_options();
|
||||
}
|
||||
|
||||
enable_switch_config();
|
||||
}
|
||||
|
||||
void FrequencyField::on_blur() {
|
||||
reset_switch_config();
|
||||
}
|
||||
|
||||
rf::Frequency FrequencyField::digit_step() const {
|
||||
constexpr rf::Frequency steps[] = {
|
||||
1'000'000'000,
|
||||
100'000'000,
|
||||
10'000'000,
|
||||
1'000'000,
|
||||
0, // Decimal point position.
|
||||
100'000,
|
||||
10'000,
|
||||
1'000,
|
||||
100,
|
||||
};
|
||||
|
||||
return digit_ < std::size(steps) ? steps[digit_] : 0;
|
||||
}
|
||||
|
||||
rf::Frequency FrequencyField::clamp_value(rf::Frequency value) {
|
||||
return range.clip(value);
|
||||
return range_.clip(value);
|
||||
}
|
||||
|
||||
void FrequencyField::enable_switch_config() {
|
||||
// Don't enable long press testing when not allowed.
|
||||
if (!allow_digit_mode_)
|
||||
return;
|
||||
|
||||
// Enable long press on "Select".
|
||||
SwitchesState config;
|
||||
config[toUType(Switch::Sel)] = true;
|
||||
set_switches_long_press_config(config);
|
||||
}
|
||||
|
||||
void FrequencyField::reset_switch_config() {
|
||||
set_switches_long_press_config(initial_switch_config_);
|
||||
}
|
||||
|
||||
/* FrequencyKeypadView ***************************************************/
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "irq_controls.hpp"
|
||||
#include "rf_path.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
@@ -45,27 +46,41 @@ class FrequencyField : public Widget {
|
||||
using range_t = rf::FrequencyRange;
|
||||
|
||||
FrequencyField(Point parent_pos);
|
||||
~FrequencyField();
|
||||
|
||||
rf::Frequency value() const;
|
||||
|
||||
void set_value(rf::Frequency new_value);
|
||||
void set_step(rf::Frequency new_value);
|
||||
void set_allow_digit_mode(bool allowed);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_key(ui::KeyEvent event) override;
|
||||
bool on_key(KeyEvent event) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
void on_focus() override;
|
||||
void on_blur() override;
|
||||
|
||||
private:
|
||||
const size_t length_;
|
||||
const range_t range;
|
||||
const range_t range_;
|
||||
|
||||
rf::Frequency value_{0};
|
||||
rf::Frequency step{25000};
|
||||
rf::Frequency step_{25000};
|
||||
uint64_t last_ms_{0};
|
||||
|
||||
uint8_t digit_{3};
|
||||
bool digit_mode_{false};
|
||||
bool allow_digit_mode_{true};
|
||||
SwitchesState initial_switch_config_{};
|
||||
|
||||
/* Gets the step value for the given digit when in digit_mode. */
|
||||
rf::Frequency digit_step() const;
|
||||
rf::Frequency clamp_value(rf::Frequency value);
|
||||
|
||||
void enable_switch_config();
|
||||
void reset_switch_config();
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
@@ -242,7 +257,6 @@ class FrequencyStepView : public OptionsField {
|
||||
parent_pos,
|
||||
5,
|
||||
{
|
||||
{" Auto", 0}, /* Faster == larger step. */
|
||||
{" 10", 10}, /* Fine tuning SSB voice pitch,in HF and QO-100 sat #669 */
|
||||
{" 50", 50}, /* added 50Hz/10Hz,but we do not increase GUI digit decimal */
|
||||
{" 100", 100},
|
||||
|
||||
@@ -308,13 +308,25 @@ WaterfallView::WaterfallView(const bool cursor) {
|
||||
}
|
||||
|
||||
void WaterfallView::on_show() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_start();
|
||||
start();
|
||||
}
|
||||
|
||||
void WaterfallView::on_hide() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_stop();
|
||||
stop();
|
||||
}
|
||||
|
||||
void WaterfallView::start() {
|
||||
if (!running_) {
|
||||
baseband::spectrum_streaming_start();
|
||||
running_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallView::stop() {
|
||||
if (running_) {
|
||||
baseband::spectrum_streaming_stop();
|
||||
running_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallView::show_audio_spectrum_view(const bool show) {
|
||||
|
||||
@@ -131,11 +131,14 @@ class WaterfallView : public View {
|
||||
WaterfallView& operator=(const WaterfallView&) = delete;
|
||||
WaterfallView& operator=(WaterfallView&&) = delete;
|
||||
|
||||
// TODO: remove these, use start/stop directly instead.
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void show_audio_spectrum_view(const bool show);
|
||||
|
||||
private:
|
||||
@@ -147,6 +150,7 @@ class WaterfallView : public View {
|
||||
|
||||
WaterfallWidget waterfall_widget{};
|
||||
FrequencyScale frequency_scale{};
|
||||
bool running_{false};
|
||||
|
||||
ChannelSpectrumFIFO* channel_fifo{nullptr};
|
||||
AudioSpectrum* audio_spectrum_data{nullptr};
|
||||
|
||||
@@ -497,13 +497,8 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{
|
||||
"ADS-B",
|
||||
Color::green(),
|
||||
&bitmap_icon_adsb,
|
||||
[&nav]() { nav.push<ADSBRxView>(); },
|
||||
},
|
||||
//{ "ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }, },
|
||||
{"ADS-B", Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBRxView>(); }},
|
||||
//{ "ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }},
|
||||
{"AIS Boats", Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
|
||||
{"AFSK", Color::yellow(), &bitmap_icon_modem, [&nav]() { nav.push<AFSKRxView>(); }},
|
||||
{"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push<BTLERxView>(); }},
|
||||
|
||||
@@ -26,6 +26,7 @@ using namespace portapack;
|
||||
|
||||
#include "io_file.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
@@ -194,6 +195,7 @@ void RecordView::start() {
|
||||
}
|
||||
} break;
|
||||
|
||||
case FileType::RawS8:
|
||||
case FileType::RawS16: {
|
||||
const auto metadata_file_error =
|
||||
write_metadata_file(get_metadata_path(base_path),
|
||||
@@ -204,8 +206,8 @@ void RecordView::start() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = std::make_unique<RawFileWriter>();
|
||||
auto create_error = p->create(base_path.replace_extension(u".C16"));
|
||||
auto p = std::make_unique<FileConvertWriter>();
|
||||
auto create_error = p->create(base_path.replace_extension((file_type == FileType::RawS8) ? u".C8" : u".C16"));
|
||||
if (create_error.is_valid()) {
|
||||
handle_error(create_error.value());
|
||||
} else {
|
||||
|
||||
@@ -40,8 +40,10 @@ class RecordView : public View {
|
||||
std::function<void(std::string)> on_error{};
|
||||
|
||||
enum FileType {
|
||||
RawS8 = 1,
|
||||
RawS16 = 2,
|
||||
WAV = 3,
|
||||
RawS32 = 3,
|
||||
WAV = 4,
|
||||
};
|
||||
|
||||
RecordView(
|
||||
@@ -57,6 +59,8 @@ class RecordView : public View {
|
||||
|
||||
void set_sampling_rate(const size_t new_sampling_rate);
|
||||
|
||||
void set_file_type(const FileType v) { file_type = v; }
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void on_hide() override;
|
||||
@@ -83,7 +87,7 @@ class RecordView : public View {
|
||||
|
||||
const std::filesystem::path filename_stem_pattern;
|
||||
const std::filesystem::path folder;
|
||||
const FileType file_type;
|
||||
FileType file_type;
|
||||
const size_t write_size;
|
||||
const size_t buffer_count;
|
||||
size_t sampling_rate{0};
|
||||
|
||||
@@ -46,24 +46,34 @@ Thread* BasebandThread::thread = nullptr;
|
||||
BasebandThread::BasebandThread(
|
||||
uint32_t sampling_rate,
|
||||
BasebandProcessor* const baseband_processor,
|
||||
const tprio_t priority,
|
||||
baseband::Direction direction)
|
||||
: baseband_processor{baseband_processor},
|
||||
_direction{direction},
|
||||
sampling_rate{sampling_rate} {
|
||||
thread = chThdCreateStatic(baseband_thread_wa, sizeof(baseband_thread_wa),
|
||||
priority, ThreadBase::fn,
|
||||
this);
|
||||
baseband::Direction direction,
|
||||
bool auto_start,
|
||||
tprio_t priority)
|
||||
: baseband_processor_{baseband_processor},
|
||||
direction_{direction},
|
||||
sampling_rate_{sampling_rate},
|
||||
priority_{priority} {
|
||||
if (auto_start) start();
|
||||
}
|
||||
|
||||
BasebandThread::~BasebandThread() {
|
||||
chThdTerminate(thread);
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
if (thread) {
|
||||
chThdTerminate(thread);
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandThread::start() {
|
||||
if (!thread) {
|
||||
thread = chThdCreateStatic(
|
||||
baseband_thread_wa, sizeof(baseband_thread_wa),
|
||||
priority_, ThreadBase::fn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandThread::set_sampling_rate(uint32_t new_sampling_rate) {
|
||||
sampling_rate = new_sampling_rate;
|
||||
sampling_rate_ = new_sampling_rate;
|
||||
}
|
||||
|
||||
void BasebandThread::run() {
|
||||
@@ -71,9 +81,7 @@ void BasebandThread::run() {
|
||||
baseband::dma::init();
|
||||
|
||||
const auto baseband_buffer = std::make_unique<std::array<baseband::sample_t, 8192>>();
|
||||
baseband::dma::configure(
|
||||
baseband_buffer->data(),
|
||||
direction());
|
||||
baseband::dma::configure(baseband_buffer->data(), direction());
|
||||
// baseband::dma::allocate(4, 2048);
|
||||
|
||||
baseband_sgpio.configure(direction());
|
||||
@@ -85,10 +93,10 @@ void BasebandThread::run() {
|
||||
const auto buffer_tmp = baseband::dma::wait_for_buffer();
|
||||
if (buffer_tmp) {
|
||||
buffer_c8_t buffer{
|
||||
buffer_tmp.p, buffer_tmp.count, sampling_rate};
|
||||
buffer_tmp.p, buffer_tmp.count, sampling_rate_};
|
||||
|
||||
if (baseband_processor) {
|
||||
baseband_processor->execute(buffer);
|
||||
if (baseband_processor_) {
|
||||
baseband_processor_->execute(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,20 @@
|
||||
|
||||
#include <ch.h>
|
||||
|
||||
/* NB: Because ThreadBase threads start when then are initialized (by default),
|
||||
* they should be the last members in a Processor class to ensure the rest of the
|
||||
* members are fully initialized before data handling starts. If the Procressor
|
||||
* needs to do additional initialization (in its ctor), set 'auto_start' to false
|
||||
* and manually call 'start()' on the thread. */
|
||||
|
||||
class BasebandThread : public ThreadBase {
|
||||
public:
|
||||
BasebandThread(
|
||||
uint32_t sampling_rate,
|
||||
BasebandProcessor* const baseband_processor,
|
||||
const tprio_t priority,
|
||||
const baseband::Direction direction = baseband::Direction::Receive);
|
||||
baseband::Direction direction,
|
||||
bool auto_start = true,
|
||||
tprio_t priority = (NORMALPRIO + 20));
|
||||
~BasebandThread();
|
||||
|
||||
BasebandThread(const BasebandThread&) = delete;
|
||||
@@ -42,10 +49,12 @@ class BasebandThread : public ThreadBase {
|
||||
BasebandThread& operator=(const BasebandThread&) = delete;
|
||||
BasebandThread& operator=(BasebandThread&&) = delete;
|
||||
|
||||
void start() override;
|
||||
|
||||
// This getter should die, it's just here to leak information to code that
|
||||
// isn't in the right place to begin with.
|
||||
baseband::Direction direction() const {
|
||||
return _direction;
|
||||
return direction_;
|
||||
}
|
||||
|
||||
void set_sampling_rate(uint32_t new_sampling_rate);
|
||||
@@ -53,9 +62,10 @@ class BasebandThread : public ThreadBase {
|
||||
private:
|
||||
static Thread* thread;
|
||||
|
||||
BasebandProcessor* baseband_processor{nullptr};
|
||||
baseband::Direction _direction{baseband::Direction::Receive};
|
||||
uint32_t sampling_rate{0};
|
||||
BasebandProcessor* baseband_processor_;
|
||||
baseband::Direction direction_;
|
||||
uint32_t sampling_rate_;
|
||||
const tprio_t priority_;
|
||||
|
||||
void run() override;
|
||||
};
|
||||
|
||||
@@ -19,21 +19,18 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "event_m4.hpp"
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "message_queue.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include "debug.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "message_queue.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
using namespace lpc43xx;
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(MAPP_IRQHandler) {
|
||||
@@ -61,6 +58,10 @@ void EventDispatcher::run() {
|
||||
|
||||
lpc43xx::creg::m0apptxevent::enable();
|
||||
|
||||
// Indicate to the M0 thread that
|
||||
// M4 is ready to receive message events.
|
||||
shared_memory.set_baseband_ready();
|
||||
|
||||
while (is_running) {
|
||||
const auto events = wait();
|
||||
dispatch(events);
|
||||
|
||||
@@ -32,6 +32,7 @@ ACARSProcessor::ACARSProcessor() {
|
||||
decim_0.configure(taps_11k0_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_11k0_decim_1.taps, 131072);
|
||||
packet.clear();
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void ACARSProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
@@ -110,9 +110,6 @@ class ACARSProcessor : public BasebandProcessor {
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2457600;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -138,6 +135,11 @@ class ACARSProcessor : public BasebandProcessor {
|
||||
};*/
|
||||
baseband::Packet packet{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
};
|
||||
|
||||
@@ -36,15 +36,11 @@ using namespace adsb;
|
||||
class ADSBRXProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2000000;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
ADSBFrame frame{};
|
||||
bool configured{false};
|
||||
uint32_t prev_mag{0};
|
||||
@@ -58,6 +54,10 @@ class ADSBRXProcessor : public BasebandProcessor {
|
||||
uint32_t sample{0};
|
||||
int32_t re{}, im{};
|
||||
int32_t amp{0};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,14 +29,11 @@
|
||||
class ADSBTXProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const p) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
|
||||
BasebandThread baseband_thread{4000000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
const complex8_t am_lut[4] = {
|
||||
{127, 0},
|
||||
{0, 127},
|
||||
@@ -48,6 +45,9 @@ class ADSBTXProcessor : public BasebandProcessor {
|
||||
uint32_t phase{0};
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{4000000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,14 +32,11 @@
|
||||
class AFSKProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const msg) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
|
||||
BasebandThread baseband_thread{AFSK_SAMPLERATE, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
uint32_t afsk_samples_per_bit{0};
|
||||
uint32_t afsk_phase_inc_mark{0};
|
||||
uint32_t afsk_phase_inc_space{0};
|
||||
@@ -59,6 +56,9 @@ class AFSKProcessor : public BasebandProcessor {
|
||||
int8_t re{0}, im{0};
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{AFSK_SAMPLERATE, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
class AFSKRxProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
@@ -53,9 +52,6 @@ class AFSKRxProcessor : public BasebandProcessor {
|
||||
RECEIVE
|
||||
};
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -93,9 +89,13 @@ class AFSKRxProcessor : public BasebandProcessor {
|
||||
bool trigger_word{};
|
||||
bool triggered{};
|
||||
|
||||
void configure(const AFSKRxConfigureMessage& message);
|
||||
|
||||
AFSKDataMessage data_message{false, 0};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const AFSKRxConfigureMessage& message);
|
||||
};
|
||||
|
||||
#endif /*__PROC_TPMS_H__*/
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
AISProcessor::AISProcessor() {
|
||||
decim_0.configure(taps_11k0_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_11k0_decim_1.taps, 131072);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void AISProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
@@ -51,9 +51,6 @@ class AISProcessor : public BasebandProcessor {
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2457600;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -77,6 +74,11 @@ class AISProcessor : public BasebandProcessor {
|
||||
this->payload_handler(packet);
|
||||
}};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
};
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
class NarrowbandAMAudio : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
@@ -46,9 +45,6 @@ class NarrowbandAMAudio : public BasebandProcessor {
|
||||
static constexpr size_t decim_2_decimation_factor = 4;
|
||||
static constexpr size_t channel_filter_decimation_factor = 1;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -65,6 +61,7 @@ class NarrowbandAMAudio : public BasebandProcessor {
|
||||
int32_t channel_filter_low_f = 0;
|
||||
int32_t channel_filter_high_f = 0;
|
||||
int32_t channel_filter_transition = 0;
|
||||
bool configured{false};
|
||||
|
||||
bool modulation_ssb = false;
|
||||
dsp::demodulate::AM demod_am{};
|
||||
@@ -74,7 +71,10 @@ class NarrowbandAMAudio : public BasebandProcessor {
|
||||
|
||||
SpectrumCollector channel_spectrum{};
|
||||
|
||||
bool configured{false};
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const AMConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
|
||||
|
||||
@@ -38,15 +38,11 @@
|
||||
class WidebandFMAudio : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2000000;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -55,8 +51,12 @@ class WidebandFMAudio : public BasebandProcessor {
|
||||
AudioSpectrum audio_spectrum{};
|
||||
TvCollector channel_spectrum{};
|
||||
std::array<complex16_t, 256> spectrum{};
|
||||
|
||||
bool configured{false};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const WFMConfigureMessage& message);
|
||||
};
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ static uint16_t crc_ccitt_tab[256] = {
|
||||
class APRSRxProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
@@ -90,9 +89,6 @@ class APRSRxProcessor : public BasebandProcessor {
|
||||
IN_FRAME
|
||||
};
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -135,6 +131,10 @@ class APRSRxProcessor : public BasebandProcessor {
|
||||
|
||||
aprs::APRSPacket aprs_packet{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const APRSRxConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
void parse_packet();
|
||||
|
||||
@@ -37,8 +37,6 @@ class AudioTXProcessor : public BasebandProcessor {
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 1536000;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
std::unique_ptr<StreamOutput> stream{};
|
||||
|
||||
ToneGen tone_gen{};
|
||||
@@ -61,6 +59,9 @@ class AudioTXProcessor : public BasebandProcessor {
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
RequestSignalMessage sig_message{RequestSignalMessage::Signal::FillRequest};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,16 +39,12 @@
|
||||
class BTLERxProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 4000000;
|
||||
static constexpr size_t audio_fs = baseband_fs / 8 / 8 / 2;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -81,10 +77,13 @@ class BTLERxProcessor : public BasebandProcessor {
|
||||
int RB_SIZE{1000};
|
||||
|
||||
bool configured{false};
|
||||
AFSKDataMessage data_message{false, 0};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const BTLERxConfigureMessage& message);
|
||||
|
||||
AFSKDataMessage data_message{false, 0};
|
||||
};
|
||||
|
||||
#endif /*__PROC_BTLERX_H__*/
|
||||
|
||||
@@ -23,9 +23,7 @@
|
||||
#include "proc_capture.hpp"
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
CaptureProcessor::CaptureProcessor() {
|
||||
@@ -33,6 +31,7 @@ CaptureProcessor::CaptureProcessor() {
|
||||
decim_1.configure(taps_200k_decim_1.taps, 131072);
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include "stream_input.hpp"
|
||||
|
||||
#include <array>
|
||||
@@ -41,17 +39,12 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
CaptureProcessor();
|
||||
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
// TODO: Repeated value needs to be transmitted from application side.
|
||||
size_t baseband_fs = 0;
|
||||
size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -69,6 +62,11 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
size_t spectrum_interval_samples = 0;
|
||||
size_t spectrum_samples = 0;
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void samplerate_config(const SamplerateConfigMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
};
|
||||
|
||||
@@ -67,9 +67,6 @@ class ERTProcessor : public BasebandProcessor {
|
||||
const size_t samples_per_symbol = channel_sampling_rate / symbol_rate;
|
||||
const float clock_recovery_rate = symbol_rate * 2;
|
||||
|
||||
BasebandThread baseband_thread{baseband_sampling_rate, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
clock_recovery::ClockRecovery<clock_recovery::FixedErrorFilter> clock_recovery{
|
||||
clock_recovery_rate,
|
||||
symbol_rate,
|
||||
@@ -116,6 +113,10 @@ class ERTProcessor : public BasebandProcessor {
|
||||
float offset_i{0.0f};
|
||||
float offset_q{0.0f};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_sampling_rate, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
float abs(const complex8_t& v);
|
||||
};
|
||||
|
||||
|
||||
@@ -29,14 +29,11 @@
|
||||
class FSKProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const p) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
|
||||
BasebandThread baseband_thread{2280000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
uint32_t samples_per_bit{0};
|
||||
uint32_t length{0};
|
||||
|
||||
@@ -48,6 +45,9 @@ class FSKProcessor : public BasebandProcessor {
|
||||
uint32_t phase{0}, sphase{0};
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{2280000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
ReplayProcessor::ReplayProcessor() {
|
||||
GPSReplayProcessor::GPSReplayProcessor() {
|
||||
channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * 1000000;
|
||||
channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * 1000000;
|
||||
channel_filter_transition = taps_200k_decim_1.transition_normalized * 1000000;
|
||||
@@ -39,44 +39,44 @@ ReplayProcessor::ReplayProcessor() {
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
|
||||
configured = false;
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 4MHz, 2048 samples */
|
||||
void GPSReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.6MHz, 2048 samples */
|
||||
|
||||
if (!configured) return;
|
||||
if (!configured || !stream) return;
|
||||
|
||||
// File data is in C16 format, we need C8
|
||||
// File samplerate is 500kHz, we're at 4MHz
|
||||
// iq_buffer can only be 512 C16 samples (RAM limitation)
|
||||
// To fill up the 2048-sample C8 buffer, we need:
|
||||
// 2048 samples * 2 bytes per sample = 4096 bytes
|
||||
// Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each
|
||||
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
|
||||
if (stream) { // sizeof(*buffer.p) = sizeof(C8) = 2*int8 = 2 bytes //buffer.count = 2048
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
bytes_read += stream->read(iq_buffer.p, bytes_to_read);
|
||||
}
|
||||
// File data is in C8 format, which is what we need
|
||||
// File samplerate is 2.6MHz, which is what we need
|
||||
// To fill up the 2048-sample C8 buffer @ 2 bytes per sample = 4096 bytes
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count);
|
||||
size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read);
|
||||
size_t samples_read_this_iteration = bytes_read_this_iteration / sizeof(*buffer.p);
|
||||
|
||||
// Fill and "stretch"
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
auto re_out = iq_buffer.p[i].real();
|
||||
auto im_out = iq_buffer.p[i].imag();
|
||||
buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
}
|
||||
bytes_read += bytes_read_this_iteration;
|
||||
|
||||
spectrum_samples += buffer.count;
|
||||
// NB: Couldn't we have just read the data into buffer.p to start with, or some DMA/cache coherency concern?
|
||||
//
|
||||
// for (size_t i = 0; i < buffer.count; i++) {
|
||||
// auto re_out = iq_buffer.p[i].real();
|
||||
// auto im_out = iq_buffer.p[i].imag();
|
||||
// buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
// }
|
||||
memcpy(buffer.p, iq_buffer.p, bytes_read_this_iteration); // memcpy should be more efficient than 1 byte at a time
|
||||
|
||||
spectrum_samples += samples_read_this_iteration;
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
|
||||
txprogress_message.progress = bytes_read / 1024; // Inform UI about progress
|
||||
txprogress_message.progress = bytes_read; // Inform UI about progress
|
||||
|
||||
txprogress_message.done = false;
|
||||
shared_memory.application_queue.push(txprogress_message);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayProcessor::on_message(const Message* const message) {
|
||||
void GPSReplayProcessor::on_message(const Message* const message) {
|
||||
switch (message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
@@ -103,13 +103,13 @@ void ReplayProcessor::on_message(const Message* const message) {
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayProcessor::samplerate_config(const SamplerateConfigMessage& message) {
|
||||
void GPSReplayProcessor::samplerate_config(const SamplerateConfigMessage& message) {
|
||||
baseband_fs = message.sample_rate;
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
|
||||
}
|
||||
|
||||
void ReplayProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||
void GPSReplayProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||
if (message.config) {
|
||||
stream = std::make_unique<StreamOutput>(message.config);
|
||||
|
||||
@@ -121,7 +121,7 @@ void ReplayProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<ReplayProcessor>()};
|
||||
EventDispatcher event_dispatcher{std::make_unique<GPSReplayProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -34,20 +34,17 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class ReplayProcessor : public BasebandProcessor {
|
||||
class GPSReplayProcessor : public BasebandProcessor {
|
||||
public:
|
||||
ReplayProcessor();
|
||||
GPSReplayProcessor();
|
||||
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
size_t baseband_fs = 0;
|
||||
size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
std::array<complex8_t, 2048> iq{};
|
||||
const buffer_c8_t iq_buffer{
|
||||
iq.data(),
|
||||
@@ -72,6 +69,10 @@ class ReplayProcessor : public BasebandProcessor {
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
RequestSignalMessage sig_message{RequestSignalMessage::Signal::FillRequest};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Transmit, /*auto_start*/ false};
|
||||
};
|
||||
|
||||
#endif /*__PROC_GPS_SIM_HPP__*/
|
||||
|
||||
@@ -33,14 +33,11 @@ using namespace jammer;
|
||||
class JammerProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const msg) override;
|
||||
|
||||
private:
|
||||
bool configured{false};
|
||||
|
||||
BasebandThread baseband_thread{3072000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
JammerChannel* jammer_channels{};
|
||||
|
||||
JammerType noise_type{};
|
||||
@@ -54,6 +51,9 @@ class JammerProcessor : public BasebandProcessor {
|
||||
int8_t sample{0};
|
||||
int8_t re{0}, im{0};
|
||||
RetuneMessage message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{3072000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
class MicTXProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const msg) override;
|
||||
|
||||
private:
|
||||
@@ -40,8 +39,6 @@ class MicTXProcessor : public BasebandProcessor {
|
||||
|
||||
bool configured{false};
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
int16_t audio_data[64];
|
||||
buffer_s16_t audio_buffer{
|
||||
audio_data,
|
||||
@@ -74,6 +71,9 @@ class MicTXProcessor : public BasebandProcessor {
|
||||
|
||||
AudioLevelReportMessage level_message{};
|
||||
TXProgressMessage txprogress_message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -42,15 +42,11 @@
|
||||
class NarrowbandFMAudio : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -97,12 +93,16 @@ class NarrowbandFMAudio : public BasebandProcessor {
|
||||
static constexpr float ki = 1.0f / k;
|
||||
|
||||
bool configured{false};
|
||||
// RequestSignalMessage sig_message { RequestSignalMessage::Signal::Squelched };
|
||||
CodedSquelchMessage ctcss_message{0};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void pitch_rssi_config(const PitchRSSIConfigureMessage& message);
|
||||
void configure(const NBFMConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
|
||||
// RequestSignalMessage sig_message { RequestSignalMessage::Signal::Squelched };
|
||||
CodedSquelchMessage ctcss_message{0};
|
||||
};
|
||||
|
||||
#endif /*__PROC_NFM_AUDIO_H__*/
|
||||
|
||||
@@ -31,7 +31,8 @@ class NOOPProcessor : public BasebandProcessor {
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
private:
|
||||
BasebandThread baseband_thread{1536000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{1536000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,16 +39,12 @@
|
||||
class NRFRxProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 4000000;
|
||||
static constexpr size_t audio_fs = baseband_fs / 8 / 8 / 2;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -82,10 +78,13 @@ class NRFRxProcessor : public BasebandProcessor {
|
||||
int RB_SIZE{1000};
|
||||
|
||||
bool configured{false};
|
||||
AFSKDataMessage data_message{false, 0};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const NRFRxConfigureMessage& message);
|
||||
|
||||
AFSKDataMessage data_message{false, 0};
|
||||
};
|
||||
|
||||
#endif /*__PROC_NRFRX_H__*/
|
||||
|
||||
@@ -29,14 +29,11 @@
|
||||
class OOKProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const p) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
|
||||
BasebandThread baseband_thread{2280000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
uint32_t samples_per_bit{0};
|
||||
uint8_t repeat{0};
|
||||
uint32_t length{0};
|
||||
@@ -67,6 +64,9 @@ class OOKProcessor : public BasebandProcessor {
|
||||
size_t scan_progress{0};
|
||||
uint8_t scan_done{true};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{2280000, this, baseband::Direction::Transmit};
|
||||
|
||||
size_t duval_algo_step();
|
||||
void scan_process(const buffer_c8_t& buffer);
|
||||
bool scan_init(unsigned int order);
|
||||
|
||||
@@ -129,7 +129,6 @@ class SmoothVals {
|
||||
class POCSAGProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
int OnDataFrame(int len, int baud);
|
||||
@@ -138,9 +137,6 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -166,7 +162,6 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
// ----------------------------------------
|
||||
// Frame extractraction methods and members
|
||||
// ----------------------------------------
|
||||
private:
|
||||
void initFrameExtraction();
|
||||
struct FIFOStruct {
|
||||
unsigned long codeword;
|
||||
@@ -219,6 +214,10 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
bool m_gotSync{false};
|
||||
int m_numCode{0};
|
||||
bool m_inverted{false};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_POCSAG_H__*/
|
||||
|
||||
@@ -33,14 +33,11 @@
|
||||
class RDSProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const msg) override;
|
||||
|
||||
private:
|
||||
uint32_t* rdsdata{};
|
||||
|
||||
BasebandThread baseband_thread{2280000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
uint16_t message_length{0};
|
||||
int8_t re{0}, im{0};
|
||||
uint8_t mphase{0}, s{0};
|
||||
@@ -132,6 +129,9 @@ class RDSProcessor : public BasebandProcessor {
|
||||
0, -14, -27, -41, -53, -66, -77, -88,
|
||||
-99, -109, -118, -126, -134, -141, -147, -152,
|
||||
-157, -160, -163, -166, -167, -168, -168, -167};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{2280000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,12 +38,13 @@ ReplayProcessor::ReplayProcessor() {
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
|
||||
configured = false;
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 4MHz, 2048 samples */
|
||||
|
||||
if (!configured) return;
|
||||
if (!configured || !stream) return;
|
||||
|
||||
// File data is in C16 format, we need C8
|
||||
// File samplerate is 500kHz, we're at 4MHz
|
||||
@@ -52,14 +53,15 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// 2048 samples * 2 bytes per sample = 4096 bytes
|
||||
// Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each
|
||||
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
|
||||
if (stream) {
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
bytes_read += stream->read(iq_buffer.p, bytes_to_read);
|
||||
}
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read);
|
||||
size_t oversamples_this_iteration = bytes_read_this_iteration * 8 / (sizeof(*buffer.p) * 2);
|
||||
|
||||
bytes_read += bytes_read_this_iteration;
|
||||
|
||||
// Fill and "stretch"
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
if (i & 3) {
|
||||
for (size_t i = 0; i < oversamples_this_iteration; i++) {
|
||||
if (i & 7) {
|
||||
buffer.p[i] = buffer.p[i - 1];
|
||||
} else {
|
||||
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
|
||||
@@ -68,7 +70,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
spectrum_samples += buffer.count;
|
||||
spectrum_samples += oversamples_this_iteration;
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.feed(iq_buffer, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
|
||||
|
||||
@@ -38,15 +38,12 @@ class ReplayProcessor : public BasebandProcessor {
|
||||
ReplayProcessor();
|
||||
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
size_t baseband_fs = 0;
|
||||
size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
std::array<complex16_t, 256> iq{};
|
||||
const buffer_c16_t iq_buffer{
|
||||
iq.data(),
|
||||
@@ -71,6 +68,10 @@ class ReplayProcessor : public BasebandProcessor {
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
RequestSignalMessage sig_message{RequestSignalMessage::Signal::FillRequest};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Transmit, /*auto_start*/ false};
|
||||
};
|
||||
|
||||
#endif /*__PROC_REPLAY_HPP__*/
|
||||
|
||||
@@ -30,14 +30,11 @@
|
||||
class SigGenProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const msg) override;
|
||||
|
||||
private:
|
||||
bool configured{false};
|
||||
|
||||
BasebandThread baseband_thread{1536000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
uint32_t tone_delta{0}, fm_delta{}, tone_phase{0};
|
||||
uint8_t tone_shape{};
|
||||
uint32_t sample_count{0};
|
||||
@@ -51,6 +48,9 @@ class SigGenProcessor : public BasebandProcessor {
|
||||
// uint8_t lfsr { }, bit { }; // Finally not used lfsr of 8 bits , bit must be 8-bit to allow bit<<7 later in the code */
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{1536000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -35,6 +35,7 @@ SondeProcessor::SondeProcessor() {
|
||||
audio_output.configure(false);
|
||||
|
||||
tone_gen.configure(BEEP_BASE_FREQ, 1.0, ToneGen::tone_type::sine, AUDIO_SAMPLE_RATE);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void SondeProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
@@ -131,9 +131,6 @@ class SondeProcessor : public BasebandProcessor {
|
||||
|
||||
ToneGen tone_gen{};
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -179,6 +176,11 @@ class SondeProcessor : public BasebandProcessor {
|
||||
shared_memory.application_queue.push(message);
|
||||
}};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void play_beep();
|
||||
void stop_beep();
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ class SpectrumPainterProcessor : public BasebandProcessor {
|
||||
|
||||
private:
|
||||
bool configured{false};
|
||||
BasebandThread baseband_thread{3072000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{3072000, this, baseband::Direction::Transmit};
|
||||
Thread* thread{nullptr};
|
||||
|
||||
protected:
|
||||
|
||||
@@ -33,7 +33,6 @@ using namespace sstv;
|
||||
class SSTVTXProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const p) override;
|
||||
|
||||
private:
|
||||
@@ -56,8 +55,6 @@ class SSTVTXProcessor : public BasebandProcessor {
|
||||
|
||||
bool configured{false};
|
||||
|
||||
BasebandThread baseband_thread{3072000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
uint32_t vis_code_sequence[10]{};
|
||||
sstv_scanline scanline_buffer[2]{};
|
||||
uint8_t buffer_flip{0}, substep{0};
|
||||
@@ -76,6 +73,9 @@ class SSTVTXProcessor : public BasebandProcessor {
|
||||
int8_t re{}, im{};
|
||||
|
||||
RequestSignalMessage sig_message{RequestSignalMessage::Signal::FillRequest};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{3072000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
TestProcessor::TestProcessor() {
|
||||
decim_0.configure(taps_11k0_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_11k0_decim_1.taps, 131072);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void TestProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
@@ -52,9 +52,6 @@ class TestProcessor : public BasebandProcessor {
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2457600 * 2;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -80,6 +77,11 @@ class TestProcessor : public BasebandProcessor {
|
||||
const TestAppPacketMessage message{packet};
|
||||
shared_memory.application_queue.push(message);
|
||||
}};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_TEST_H__*/
|
||||
|
||||
@@ -31,14 +31,11 @@
|
||||
class TonesProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const p) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
|
||||
BasebandThread baseband_thread{1536000, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
std::array<int16_t, 32> audio{}; // 2048/64
|
||||
const buffer_s16_t audio_buffer{
|
||||
(int16_t*)audio.data(),
|
||||
@@ -63,6 +60,9 @@ class TonesProcessor : public BasebandProcessor {
|
||||
|
||||
TXProgressMessage txprogress_message{};
|
||||
AudioOutput audio_output{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{1536000, this, baseband::Direction::Transmit};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
TPMSProcessor::TPMSProcessor() {
|
||||
decim_0.configure(taps_200k_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_200k_decim_1.taps, 131072);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void TPMSProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
@@ -74,9 +74,6 @@ class TPMSProcessor : public BasebandProcessor {
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2457600;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -141,6 +138,11 @@ class TPMSProcessor : public BasebandProcessor {
|
||||
const TPMSPacketMessage message{tpms::SignalType::OOK_8k4_Schrader, packet};
|
||||
shared_memory.application_queue.push(message);
|
||||
}};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_TPMS_H__*/
|
||||
|
||||
@@ -38,16 +38,12 @@
|
||||
class WidebandFMAudio : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
@@ -93,6 +89,11 @@ class WidebandFMAudio : public BasebandProcessor {
|
||||
size_t spectrum_samples = 0;
|
||||
|
||||
bool configured{false};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void configure(const WFMConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
void post_message(const buffer_c16_t& data);
|
||||
|
||||
@@ -37,22 +37,20 @@
|
||||
class WidebandSpectrum : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
|
||||
size_t baseband_fs = 20000000;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
SpectrumCollector channel_spectrum{};
|
||||
|
||||
std::array<complex16_t, 256> spectrum{};
|
||||
|
||||
size_t phase = 0, trigger = 127;
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_WIDEBAND_SPECTRUM_H__*/
|
||||
|
||||
@@ -32,16 +32,25 @@ WORKING_AREA(rssi_thread_wa, 128);
|
||||
|
||||
Thread* RSSIThread::thread = nullptr;
|
||||
|
||||
RSSIThread::RSSIThread(const tprio_t priority) {
|
||||
thread = chThdCreateStatic(rssi_thread_wa, sizeof(rssi_thread_wa),
|
||||
priority, ThreadBase::fn,
|
||||
this);
|
||||
RSSIThread::RSSIThread(bool auto_start, tprio_t priority)
|
||||
: priority_{priority} {
|
||||
if (auto_start) start();
|
||||
}
|
||||
|
||||
RSSIThread::~RSSIThread() {
|
||||
chThdTerminate(thread);
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
if (thread) {
|
||||
chThdTerminate(thread);
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RSSIThread::start() {
|
||||
if (!thread) {
|
||||
thread = chThdCreateStatic(
|
||||
rssi_thread_wa, sizeof(rssi_thread_wa),
|
||||
priority_, ThreadBase::fn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void RSSIThread::run() {
|
||||
|
||||
@@ -25,20 +25,32 @@
|
||||
#include "thread_base.hpp"
|
||||
|
||||
#include <ch.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/* NB: Because ThreadBase threads start when then are initialized (by default),
|
||||
* they should be the last members in a Processor class to ensure the rest of the
|
||||
* members are fully initialized before data handling starts. If the Procressor
|
||||
* needs to do additional initialization (in its ctor), set 'auto_start' to false
|
||||
* and manually call 'start()' on the thread.
|
||||
* This isn't as relevant for RSSIThread which is entirely self-contained, but
|
||||
* it's good practice to keep all the thread-init together. */
|
||||
|
||||
class RSSIThread : public ThreadBase {
|
||||
public:
|
||||
RSSIThread(const tprio_t priority);
|
||||
RSSIThread(
|
||||
bool auto_start = true,
|
||||
tprio_t priority = (NORMALPRIO + 10));
|
||||
~RSSIThread();
|
||||
|
||||
void start() override;
|
||||
|
||||
private:
|
||||
void run() override;
|
||||
|
||||
static Thread* thread;
|
||||
const tprio_t priority_;
|
||||
|
||||
const uint32_t sampling_rate{400000};
|
||||
static Thread* thread;
|
||||
static constexpr uint32_t sampling_rate{400000};
|
||||
};
|
||||
|
||||
#endif /*__RSSI_THREAD_H__*/
|
||||
|
||||
@@ -100,6 +100,9 @@ class ILI9341 {
|
||||
constexpr ui::Dim height() const { return 320; }
|
||||
constexpr ui::Rect screen_rect() const { return {0, 0, width(), height()}; }
|
||||
|
||||
void draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count);
|
||||
void read_pixels(const ui::Rect r, ui::ColorRGB888* const colors, const size_t count);
|
||||
|
||||
private:
|
||||
struct scroll_t {
|
||||
ui::Coord top_area;
|
||||
@@ -109,9 +112,6 @@ class ILI9341 {
|
||||
};
|
||||
|
||||
scroll_t scroll_state;
|
||||
|
||||
void draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count);
|
||||
void read_pixels(const ui::Rect r, ui::ColorRGB888* const colors, const size_t count);
|
||||
};
|
||||
|
||||
} /* namespace lcd */
|
||||
|
||||
@@ -80,7 +80,7 @@ constexpr clkout_freq_range_t clkout_freq_range{10, 60000};
|
||||
constexpr uint16_t clkout_freq_reset_value{10000};
|
||||
|
||||
enum data_structure_version_enum : uint32_t {
|
||||
VERSION_CURRENT = 0x10000004,
|
||||
VERSION_CURRENT = 0x10000005,
|
||||
};
|
||||
|
||||
static const uint32_t TOUCH_CALIBRATION_MAGIC = 0x074af82f;
|
||||
|
||||
@@ -107,11 +107,9 @@ struct backlight_config_t {
|
||||
};
|
||||
|
||||
enum encoder_dial_sensitivity {
|
||||
DIAL_SENSITIVITY_HIGHEST = 0,
|
||||
DIAL_SENSITIVITY_HIGH = 1,
|
||||
DIAL_SENSITIVITY_NORMAL = 2,
|
||||
DIAL_SENSITIVITY_LOW = 3,
|
||||
DIAL_SENSITIVITY_LOWEST = 4,
|
||||
DIAL_SENSITIVITY_NORMAL = 0,
|
||||
DIAL_SENSITIVITY_LOW = 1,
|
||||
DIAL_SENSITIVITY_HIGH = 2,
|
||||
NUM_DIAL_SENSITIVITY
|
||||
};
|
||||
|
||||
|
||||
@@ -65,6 +65,11 @@ struct SharedMemory {
|
||||
uint8_t data[512];
|
||||
} bb_data{{{{0, 0}}, 0, {0}}};
|
||||
|
||||
// Set by the M4 to indicate that the baseband app is ready.
|
||||
bool volatile baseband_ready{false};
|
||||
void clear_baseband_ready() { baseband_ready = false; }
|
||||
void set_baseband_ready() { baseband_ready = true; }
|
||||
|
||||
uint8_t volatile request_m4_performance_counter{0};
|
||||
uint8_t volatile m4_cpu_usage{0};
|
||||
uint16_t volatile m4_stack_usage{0};
|
||||
|
||||
@@ -28,6 +28,8 @@ class ThreadBase {
|
||||
public:
|
||||
virtual ~ThreadBase() = default;
|
||||
|
||||
virtual void start() = 0;
|
||||
|
||||
protected:
|
||||
static msg_t fn(void* arg) {
|
||||
auto obj = static_cast<ThreadBase*>(arg);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "sine_table.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
@@ -102,4 +103,14 @@ Point fast_polar_to_point(int32_t angle, uint32_t distance) {
|
||||
(int16_sin_s4(((1 << 16) * (-angle - 90)) / 360) * distance) / (1 << 16));
|
||||
}
|
||||
|
||||
bool key_is_long_pressed(KeyEvent key) {
|
||||
if (key < KeyEvent::Back) {
|
||||
// TODO: this would make more sense as a flag on KeyEvent
|
||||
// passed up to the UI via event dispatch.
|
||||
return switch_is_long_pressed(static_cast<Switch>(key));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
+10
-1
@@ -34,6 +34,10 @@ using Dim = int16_t;
|
||||
constexpr uint16_t screen_width = 240;
|
||||
constexpr uint16_t screen_height = 320;
|
||||
|
||||
/* Dimensions for the default font character. */
|
||||
constexpr uint16_t char_width = 8;
|
||||
constexpr uint16_t char_height = 16;
|
||||
|
||||
struct Color {
|
||||
uint16_t v; // rrrrrGGGGGGbbbbb
|
||||
|
||||
@@ -328,7 +332,7 @@ struct Bitmap {
|
||||
const uint8_t* const data;
|
||||
};
|
||||
|
||||
enum class KeyEvent {
|
||||
enum class KeyEvent : uint8_t {
|
||||
/* Ordinals map to bit positions reported by CPLD */
|
||||
Right = 0,
|
||||
Left = 1,
|
||||
@@ -356,6 +360,11 @@ Point polar_to_point(float angle, uint32_t distance);
|
||||
|
||||
Point fast_polar_to_point(int32_t angle, uint32_t distance);
|
||||
|
||||
/* Default font glyph size. */
|
||||
constexpr Size char_size{char_width, char_height};
|
||||
|
||||
bool key_is_long_pressed(KeyEvent key);
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_H__*/
|
||||
|
||||
@@ -573,7 +573,7 @@ void ProgressBar::paint(Painter& painter) {
|
||||
const auto sr = screen_rect();
|
||||
const auto s = style();
|
||||
|
||||
v_scaled = (sr.size().width() * _value) / _max;
|
||||
v_scaled = (sr.size().width() * (uint64_t)_value) / _max;
|
||||
|
||||
painter.fill_rectangle({sr.location(), {v_scaled, sr.size().height()}}, style().foreground);
|
||||
painter.fill_rectangle({{sr.location().x() + v_scaled, sr.location().y()}, {sr.size().width() - v_scaled, sr.size().height()}}, s.background);
|
||||
|
||||
@@ -53,6 +53,12 @@ constexpr typename std::underlying_type<E>::type toUType(E enumerator) noexcept
|
||||
return static_cast<typename std::underlying_type<E>::type>(enumerator);
|
||||
}
|
||||
|
||||
/* Increments an enum value. Enumerator values are assumed to be serial. */
|
||||
template <typename E>
|
||||
void incr(E& e) {
|
||||
e = static_cast<E>(toUType(e) + 1);
|
||||
}
|
||||
|
||||
inline uint32_t flp2(uint32_t v) {
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
@@ -180,13 +186,18 @@ struct range_t {
|
||||
return value < minimum;
|
||||
}
|
||||
|
||||
/* Exclusive of maximum. */
|
||||
constexpr bool contains(const T& value) const {
|
||||
// TODO: Subtle gotcha here! Range test doesn't include maximum!
|
||||
return (value >= minimum) && (value < maximum);
|
||||
}
|
||||
|
||||
/* Inclusive of maximum. */
|
||||
constexpr bool contains_inc(const T& value) const {
|
||||
return (value >= minimum) && (value <= maximum);
|
||||
}
|
||||
|
||||
/* Exclusive of maximum. */
|
||||
constexpr bool out_of_range(const T& value) const {
|
||||
// TODO: Subtle gotcha here! Range test in contains() doesn't include maximum!
|
||||
return !contains(value);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user