mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-12 01:29:29 +00:00
Morserx added ssb modes (#2934)
* USB receive good * LSB receive good * all mode receive good, log is dead * frequency measurement good, decode dead * total killed * szutyok, gerjed * frequenty meas only. * ui ok * Add frequency measurement handling and update Morse processing logic * fix ssb speed
This commit is contained in:
@@ -368,8 +368,8 @@ void set_subghzd_config(uint8_t modulation = 0, uint32_t sampling_rate = 0) {
|
|||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_moreserx_config() {
|
void set_moreserx_config(uint8_t mode) {
|
||||||
const MorseRXConfigureMessage message{};
|
const MorseRXConfigureMessage message{mode};
|
||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ void set_siggen_tone(const uint32_t tone);
|
|||||||
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
|
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
|
||||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||||
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
|
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
|
||||||
void set_moreserx_config();
|
void set_moreserx_config(uint8_t mode);
|
||||||
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
||||||
void set_noaaapt_config();
|
void set_noaaapt_config();
|
||||||
void set_flex_config();
|
void set_flex_config();
|
||||||
|
|||||||
+61
-53
@@ -16,6 +16,8 @@ MorseRadioView::MorseRadioView(ui::NavigationView& nav)
|
|||||||
&field_frequency,
|
&field_frequency,
|
||||||
&txt_last,
|
&txt_last,
|
||||||
&txt_speed,
|
&txt_speed,
|
||||||
|
&txt_freq,
|
||||||
|
&options_mode,
|
||||||
&btn_clear,
|
&btn_clear,
|
||||||
&console_text,
|
&console_text,
|
||||||
&labels,
|
&labels,
|
||||||
@@ -31,28 +33,43 @@ MorseRadioView::MorseRadioView(ui::NavigationView& nav)
|
|||||||
logger->init_daily_log(logs_dir);
|
logger->init_daily_log(logs_dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
audio::set_rate(audio::Rate::Hz_24000);
|
|
||||||
audio::output::start();
|
audio::output::start();
|
||||||
|
|
||||||
receiver_model.set_sampling_rate(3072000);
|
receiver_model.set_sampling_rate(3072000);
|
||||||
receiver_model.set_baseband_bandwidth(1750000);
|
receiver_model.set_baseband_bandwidth(1750000);
|
||||||
receiver_model.set_hidden_offset(0);
|
|
||||||
baseband::set_moreserx_config();
|
|
||||||
receiver_model.set_am_configuration(4);
|
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
|
|
||||||
|
options_mode.on_change = [this](size_t, int32_t value) {
|
||||||
|
current_mode = (uint8_t)value;
|
||||||
|
morse_decoder_.resetLearning();
|
||||||
|
if (current_mode == 0) {
|
||||||
|
receiver_model.set_am_configuration(4);
|
||||||
|
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||||
|
field_squelch.set_style(Theme::getInstance()->option_active);
|
||||||
|
field_squelch.set_focusable(true);
|
||||||
|
receiver_model.set_squelch_level(field_squelch.value());
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
} else {
|
||||||
|
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
||||||
|
receiver_model.set_am_configuration(current_mode + 7);
|
||||||
|
receiver_model.set_squelch_level(0);
|
||||||
|
field_squelch.set_style(Theme::getInstance()->fg_dark);
|
||||||
|
field_squelch.set_focusable(false);
|
||||||
|
audio::set_rate(audio::Rate::Hz_12000);
|
||||||
|
}
|
||||||
|
baseband::set_moreserx_config(current_mode);
|
||||||
|
};
|
||||||
|
field_squelch.set_value(receiver_model.squelch_level(), false); // will be sent later, no need to send 2x
|
||||||
|
field_squelch.on_change = [this](int32_t v) {
|
||||||
|
if (current_mode == 0)
|
||||||
|
receiver_model.set_squelch_level(v);
|
||||||
|
};
|
||||||
|
options_mode.set_selected_index(current_mode, true);
|
||||||
|
|
||||||
auto vol = field_volume.value(); // audio volume fix
|
auto vol = field_volume.value(); // audio volume fix
|
||||||
field_volume.set_value(0);
|
field_volume.set_value(0);
|
||||||
field_volume.set_value(vol);
|
field_volume.set_value(vol);
|
||||||
|
|
||||||
field_squelch.set_value(receiver_model.squelch_level());
|
|
||||||
field_squelch.on_change = [this](int32_t v) {
|
|
||||||
receiver_model.set_squelch_level(v);
|
|
||||||
};
|
|
||||||
|
|
||||||
logger = std::make_unique<MorseLogger>();
|
logger = std::make_unique<MorseLogger>();
|
||||||
|
|
||||||
chk_log.on_select = [this](Checkbox&, bool save) {
|
chk_log.on_select = [this](Checkbox&, bool save) {
|
||||||
save_log = save;
|
save_log = save;
|
||||||
if (logger && save_log)
|
if (logger && save_log)
|
||||||
@@ -86,35 +103,29 @@ void MorseLogger::init_daily_log(const std::filesystem::path& log_dir) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MorseLogger::radio_set_log() {
|
void MorseLogger::radio_set_log(uint8_t current_mode) {
|
||||||
int64_t freq = receiver_model.target_frequency();
|
int64_t freq = receiver_model.target_frequency();
|
||||||
std::string header = "Freq:" + to_string_dec_int(freq / 1000000) + "." + to_string_dec_int((freq % 1000000) / 1000, 3);
|
std::string header = "Freq:" + to_string_rounded_freq(freq, 4);
|
||||||
header += "MHz SQ:" + to_string_dec_uint(receiver_model.squelch_level());
|
header += "MHz, ";
|
||||||
header += " LNA:" + to_string_dec_uint(receiver_model.lna());
|
header += "RX MODE:" + std::string(current_mode == 0 ? "CW/FM" : (current_mode == 1 ? "USB" : "LSB"));
|
||||||
header += " VGA:" + to_string_dec_uint(receiver_model.vga());
|
header += "\r\nMessage:";
|
||||||
header += " AMP:" + std::string(receiver_model.rf_amp() ? "ON" : "OFF");
|
|
||||||
header += "\r\nMessage:\r\n";
|
|
||||||
|
|
||||||
log_file.write_raw(header);
|
log_file.write_raw(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MorseLogger::on_packet(const std::string& content, bool time) {
|
bool MorseLogger::on_packet(const std::string& content, bool time, uint8_t current_mode) {
|
||||||
if (!time) {
|
if (!time) {
|
||||||
log_file.write_raw_no_newline("\r\n\r\n");
|
log_file.write_raw_no_newline("\r\n\r\n");
|
||||||
auto timestamp = to_string_datetime(rtc_time::now(), YMDHMS);
|
auto timestamp = to_string_datetime(rtc_time::now(), YMDHMS);
|
||||||
log_file.write_raw("[" + timestamp + "] ");
|
log_file.write_raw("[" + timestamp + "] ");
|
||||||
|
radio_set_log(current_mode);
|
||||||
radio_set_log();
|
|
||||||
|
|
||||||
char_count = 0;
|
char_count = 0;
|
||||||
time = true;
|
time = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.empty()) return time;
|
if (content.empty()) return time;
|
||||||
|
std::string s;
|
||||||
for (char c : content) {
|
for (char c : content) {
|
||||||
std::string s(1, c);
|
s = c;
|
||||||
|
|
||||||
if ((char_count >= 35 && c == ' ') || (char_count >= 47)) {
|
if ((char_count >= 35 && c == ' ') || (char_count >= 47)) {
|
||||||
log_file.write_raw_no_newline("\r\n");
|
log_file.write_raw_no_newline("\r\n");
|
||||||
|
|
||||||
@@ -145,14 +156,11 @@ void MorseRadioView::focus() {
|
|||||||
|
|
||||||
void MorseRadioView::check_for_timeout() {
|
void MorseRadioView::check_for_timeout() {
|
||||||
uint64_t now = chTimeNow();
|
uint64_t now = chTimeNow();
|
||||||
|
|
||||||
if (last_activity_time == 0) {
|
if (last_activity_time == 0) {
|
||||||
last_activity_time = now;
|
last_activity_time = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t quiet_duration = now - last_activity_time;
|
uint64_t quiet_duration = now - last_activity_time;
|
||||||
|
|
||||||
if (quiet_duration >= 60000) {
|
if (quiet_duration >= 60000) {
|
||||||
morse_decoder_.resetLearning();
|
morse_decoder_.resetLearning();
|
||||||
time_stamp = false;
|
time_stamp = false;
|
||||||
@@ -170,13 +178,11 @@ int32_t MorseRadioView::ProcessSignal(int32_t sig_time_us) {
|
|||||||
long_pause_sent_ = false;
|
long_pause_sent_ = false;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sign == last_sign_) {
|
if (sign == last_sign_) {
|
||||||
accumulator_us_ += sig_time_us;
|
accumulator_us_ += sig_time_us;
|
||||||
|
|
||||||
if (sign < 0) {
|
if (sign < 0) {
|
||||||
int32_t threshold_us = morse_decoder_.getInterWordThreshold() * 1000;
|
int32_t threshold_us = morse_decoder_.getInterWordThreshold() * 1000;
|
||||||
|
|
||||||
if (!long_pause_sent_ && accumulator_us_ <= -threshold_us) {
|
if (!long_pause_sent_ && accumulator_us_ <= -threshold_us) {
|
||||||
result = accumulator_us_ / 1000;
|
result = accumulator_us_ / 1000;
|
||||||
long_pause_sent_ = true;
|
long_pause_sent_ = true;
|
||||||
@@ -189,53 +195,55 @@ int32_t MorseRadioView::ProcessSignal(int32_t sig_time_us) {
|
|||||||
} else {
|
} else {
|
||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator_us_ = sig_time_us;
|
accumulator_us_ = sig_time_us;
|
||||||
last_sign_ = sign;
|
last_sign_ = sign;
|
||||||
long_pause_sent_ = false;
|
long_pause_sent_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MorseRadioView::on_data(const MorseRXDataMessage* message) {
|
void MorseRadioView::on_data(const MorseRXDataMessage* message) {
|
||||||
uint16_t start = 0;
|
|
||||||
uint16_t stop = 0;
|
|
||||||
bool has_valid = false;
|
|
||||||
int32_t r;
|
int32_t r;
|
||||||
|
|
||||||
for (uint16_t i = 0; i <= message->maxptr; ++i) {
|
for (uint8_t i = 0; i <= message->state_cnt; ++i) {
|
||||||
if (message->state_durations[i] >= 30000 || message->state_durations[i] <= -30000) {
|
|
||||||
if (!has_valid) {
|
|
||||||
start = i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
has_valid = true;
|
|
||||||
stop = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!has_valid) return; // no valid data arrived
|
|
||||||
|
|
||||||
for (uint16_t i = start; i <= stop; i++) {
|
|
||||||
r = ProcessSignal(message->state_durations[i]);
|
r = ProcessSignal(message->state_durations[i]);
|
||||||
auto result = morse_decoder_.handleInput((r != 0) ? r : 0);
|
auto result = morse_decoder_.handleInput((r != 0) ? r : 0);
|
||||||
if (result.isValid()) {
|
if (result.isValid()) {
|
||||||
last_activity_time = chTimeNow(); // start reset timer on valid input
|
last_activity_time = chTimeNow(); // start reset timer on valid input
|
||||||
writeCharToConsole(result.text, result.confidence);
|
writeCharToConsole(result.text, result.confidence);
|
||||||
if (logger && save_log) {
|
if (logger && save_log) {
|
||||||
time_stamp = logger->on_packet(result.text, time_stamp);
|
time_stamp = logger->on_packet(result.text, time_stamp, current_mode);
|
||||||
}
|
}
|
||||||
float dah_time = morse_decoder_.getCurrentTimeUnit() * 3.0f;
|
float dah_time = morse_decoder_.getCurrentTimeUnit() * 3.0f;
|
||||||
|
|
||||||
if (dah_time > 0) {
|
if (dah_time > 0) {
|
||||||
uint16_t wpm = (uint16_t)(3600.0f / (dah_time + 18.0f) + 0.5f);
|
uint16_t wpm = (uint16_t)(3600.0f / (dah_time + 18.0f) + 0.5f);
|
||||||
|
|
||||||
txt_speed.set(to_string_dec_uint(wpm));
|
txt_speed.set(to_string_dec_uint(wpm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MorseRadioView::on_freq(const MorseRXfreqMessage* message) {
|
||||||
|
std::string ret = " ";
|
||||||
|
uint32_t freq = message->measured_frequency;
|
||||||
|
if (freq < 301) {
|
||||||
|
ret = "<";
|
||||||
|
freq = 300;
|
||||||
|
}
|
||||||
|
if (freq > 2299) {
|
||||||
|
freq = 2300;
|
||||||
|
ret = ">";
|
||||||
|
}
|
||||||
|
if (freq < 400 || freq > 1400)
|
||||||
|
txt_freq.set_style(Theme::getInstance()->fg_red);
|
||||||
|
else if (freq <= 580 || freq >= 1220)
|
||||||
|
txt_freq.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
else
|
||||||
|
txt_freq.set_style(Theme::getInstance()->fg_green);
|
||||||
|
ret += to_string_dec_uint(freq);
|
||||||
|
txt_freq.set(ret);
|
||||||
|
}
|
||||||
|
|
||||||
void MorseRadioView::writeCharToConsole(const std::string& ch, double confidence) {
|
void MorseRadioView::writeCharToConsole(const std::string& ch, double confidence) {
|
||||||
if (ch.empty()) {
|
if (ch.empty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "rtc_time.hpp"
|
#include "rtc_time.hpp"
|
||||||
#include "morsedecoder.hpp"
|
#include "morsedecoder.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace ui::external_app::morse_radio {
|
namespace ui::external_app::morse_radio {
|
||||||
@@ -53,8 +54,8 @@ class MorseLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void init_daily_log(const std::filesystem::path& log_dir);
|
void init_daily_log(const std::filesystem::path& log_dir);
|
||||||
bool on_packet(const std::string& content, bool time);
|
bool on_packet(const std::string& content, bool time, uint8_t current_mode);
|
||||||
void radio_set_log();
|
void radio_set_log(uint8_t current_mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LogFile log_file{};
|
LogFile log_file{};
|
||||||
@@ -73,6 +74,7 @@ class MorseRadioView : public ui::View {
|
|||||||
private:
|
private:
|
||||||
void writeCharToConsole(const std::string& ch, double confidence);
|
void writeCharToConsole(const std::string& ch, double confidence);
|
||||||
void on_data(const MorseRXDataMessage* message);
|
void on_data(const MorseRXDataMessage* message);
|
||||||
|
void on_freq(const MorseRXfreqMessage* message);
|
||||||
void on_message(const Message* const p);
|
void on_message(const Message* const p);
|
||||||
void check_for_timeout();
|
void check_for_timeout();
|
||||||
int32_t ProcessSignal(int32_t sig_time_us);
|
int32_t ProcessSignal(int32_t sig_time_us);
|
||||||
@@ -80,8 +82,13 @@ class MorseRadioView : public ui::View {
|
|||||||
MorseDecoder morse_decoder_{};
|
MorseDecoder morse_decoder_{};
|
||||||
RxRadioState radio_state_{};
|
RxRadioState radio_state_{};
|
||||||
std::unique_ptr<MorseLogger> logger{};
|
std::unique_ptr<MorseLogger> logger{};
|
||||||
|
|
||||||
|
uint8_t current_mode{0}; // 0=CW/FM, 1=USB, 2=LSB
|
||||||
|
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"trx_morese_radio", app_settings::Mode::RX_TX};
|
"rx_morese_radio",
|
||||||
|
app_settings::Mode::RX,
|
||||||
|
{{"cwmode"sv, ¤t_mode}}};
|
||||||
|
|
||||||
RxFrequencyField field_frequency{
|
RxFrequencyField field_frequency{
|
||||||
{UI_POS_X(0), UI_POS_Y(0)},
|
{UI_POS_X(0), UI_POS_Y(0)},
|
||||||
@@ -107,27 +114,35 @@ class MorseRadioView : public ui::View {
|
|||||||
};
|
};
|
||||||
ui::Text txt_speed{{UI_POS_X(23), UI_POS_Y(1), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)}, "??"};
|
ui::Text txt_speed{{UI_POS_X(23), UI_POS_Y(1), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)}, "??"};
|
||||||
ui::Text txt_last{{UI_POS_X(12), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(12), UI_POS_HEIGHT(1)}, ""};
|
ui::Text txt_last{{UI_POS_X(12), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(12), UI_POS_HEIGHT(1)}, ""};
|
||||||
|
ui::Text txt_freq{{UI_POS_X(21), UI_POS_Y(2), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)}, "??"};
|
||||||
ui::Console console_text{{UI_POS_X(0), UI_POS_Y(5), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(7)}};
|
ui::Console console_text{{UI_POS_X(0), UI_POS_Y(5), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(7)}};
|
||||||
ui::Labels labels{
|
ui::Labels labels{
|
||||||
{{UI_POS_X(0), UI_POS_Y(1)}, "Squelch:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(0), UI_POS_Y(1)}, "Squelch:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{UI_POS_X(15), UI_POS_Y(1)}, "Speed:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(15), UI_POS_Y(1)}, "Speed:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{UI_POS_X(26), UI_POS_Y(1)}, "wpm", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(26), UI_POS_Y(1)}, "wpm", Theme::getInstance()->fg_light->foreground},
|
||||||
{{UI_POS_X(0), UI_POS_Y(4)}, "Last seq.:", Theme::getInstance()->fg_light->foreground},
|
{{UI_POS_X(0), UI_POS_Y(4)}, "Last seq.:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
{{UI_POS_X(15), UI_POS_Y(2)}, "Tone:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(27), UI_POS_Y(2)}, "Hz", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
|
ui::OptionsField options_mode{
|
||||||
|
{UI_POS_X(9), UI_POS_Y(2)},
|
||||||
|
5,
|
||||||
|
{{"CW/FM", 0}, {"USB", 1}, {"LSB", 2}}};
|
||||||
|
|
||||||
Checkbox chk_log{{UI_POS_X(0), UI_POS_Y(2)}, 12, "Log", false};
|
Checkbox chk_log{{UI_POS_X(0), UI_POS_Y(2)}, 12, "Log", false};
|
||||||
ui::Button btn_clear{{UI_POS_X(0), UI_POS_Y_BOTTOM(2), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)}, "CLR"};
|
ui::Button btn_clear{{UI_POS_X(0), UI_POS_Y_BOTTOM(2), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)}, "CLR"};
|
||||||
|
|
||||||
uint8_t last_color_id{255};
|
|
||||||
uint8_t color_id{255};
|
|
||||||
std::string arr_color[4] = {STR_COLOR_WHITE, STR_COLOR_RED, STR_COLOR_YELLOW, STR_COLOR_GREEN};
|
std::string arr_color[4] = {STR_COLOR_WHITE, STR_COLOR_RED, STR_COLOR_YELLOW, STR_COLOR_GREEN};
|
||||||
|
|
||||||
int32_t accumulator_us_{0};
|
|
||||||
int8_t last_sign_{0};
|
|
||||||
uint8_t space_timer{0};
|
|
||||||
bool long_pause_sent_{false};
|
bool long_pause_sent_{false};
|
||||||
bool save_log{false};
|
bool save_log{false};
|
||||||
bool reset_triggered_{false};
|
bool reset_triggered_{false};
|
||||||
bool time_stamp{false};
|
bool time_stamp{false};
|
||||||
|
uint8_t last_color_id{255};
|
||||||
|
uint8_t color_id{255};
|
||||||
|
int8_t last_sign_{0};
|
||||||
|
uint8_t space_timer{0};
|
||||||
|
int32_t accumulator_us_{0};
|
||||||
uint64_t last_activity_time{0};
|
uint64_t last_activity_time{0};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
MessageHandlerRegistration message_handler_packet{
|
||||||
@@ -137,6 +152,13 @@ class MorseRadioView : public ui::View {
|
|||||||
this->on_data(message);
|
this->on_data(message);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freq{
|
||||||
|
Message::ID::MorseRXfreq,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const MorseRXfreqMessage*>(p);
|
||||||
|
this->on_freq(message);
|
||||||
|
}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_framesync{
|
MessageHandlerRegistration message_handler_framesync{
|
||||||
Message::ID::DisplayFrameSync,
|
Message::ID::DisplayFrameSync,
|
||||||
[this](const Message* const p) {
|
[this](const Message* const p) {
|
||||||
|
|||||||
+236
-133
@@ -25,186 +25,289 @@
|
|||||||
#include "event_m4.hpp"
|
#include "event_m4.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
void MorseProcessor::configure() {
|
void MorseProcessor::configure(uint8_t mode) {
|
||||||
configured = false;
|
configured = false;
|
||||||
baseband_fs = 3072000;
|
|
||||||
baseband_thread.set_sampling_rate(baseband_fs);
|
|
||||||
|
|
||||||
// 1. DSP filters
|
if (mode == 0) { // CW/FM
|
||||||
decim_0.configure(taps_11k0_decim_0.taps);
|
decim_0.configure(taps_11k0_decim_0.taps);
|
||||||
decim_1.configure(taps_11k0_decim_1.taps);
|
decim_1.configure(taps_11k0_decim_1.taps);
|
||||||
channel_filter.configure(taps_11k0_channel.taps, 2);
|
channel_filter.configure(taps_11k0_channel.taps, 2);
|
||||||
demod.configure(24000, 5000);
|
demod_cw_fm.configure(24000, 5000);
|
||||||
audio_output.configure(iir_config_passthrough, iir_config_passthrough, 0.0f);
|
} else { // USB, LSB
|
||||||
|
decim_0.configure(taps_6k0_decim_0.taps);
|
||||||
|
decim_1.configure(taps_6k0_decim_1.taps);
|
||||||
|
|
||||||
// 2. Resetting variables
|
if (mode == 1) // USB
|
||||||
dc_offset = 0;
|
channel_filter.configure(taps_2k8_usb_channel.taps, 4);
|
||||||
lpf_sample = 0;
|
else // LSB
|
||||||
prev_sample = 0;
|
channel_filter.configure(taps_2k8_lsb_channel.taps, 4);
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Algorithm reset
|
modulation = mode;
|
||||||
zc_counter = 0;
|
if (mode > 0)
|
||||||
last_zc_counter = 0;
|
audio_output.configure(audio_12k_hpf_300hz_config);
|
||||||
current_freq = 700.0f;
|
else
|
||||||
update_goertzel_coeff(700.0f);
|
audio_output.configure(iir_config_passthrough, iir_config_passthrough, (float)user_squelch_level / 100.0f);
|
||||||
|
|
||||||
goertzel_count = 0;
|
meas_samples_in_period = 0;
|
||||||
|
meas_last_period_len = 0;
|
||||||
|
meas_consistency_count = 0;
|
||||||
|
meas_signal_state_high = false;
|
||||||
|
meas_freq_accumulator = 0.0f;
|
||||||
|
meas_freq_count = 0;
|
||||||
|
ui_update_timer = 0;
|
||||||
|
|
||||||
|
// Decoding reset
|
||||||
s_prev_i = 0;
|
s_prev_i = 0;
|
||||||
s_prev2_i = 0;
|
s_prev2_i = 0;
|
||||||
|
goertzel_count = 0;
|
||||||
duration_samples = 0;
|
duration_samples = 0;
|
||||||
was_signaling = false;
|
was_signaling = false;
|
||||||
|
|
||||||
noise_floor = 5000; // learning speed
|
// Thresholds
|
||||||
|
noise_floor = 5000;
|
||||||
startup_delay = 20;
|
startup_delay = 20;
|
||||||
|
squelch_is_open = true;
|
||||||
|
squelch_hold = 0;
|
||||||
|
|
||||||
|
current_freq = 700.0f;
|
||||||
|
update_goertzel_coeff(current_freq);
|
||||||
|
|
||||||
squelch_is_open = false;
|
|
||||||
configured = true;
|
configured = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MorseProcessor::on_message(const Message* const p) {
|
inline buffer_f32_t MorseProcessor::demodulate(const buffer_c16_t& channel) {
|
||||||
if (p->id == Message::ID::MorseRXConfig) {
|
if (modulation > 0) {
|
||||||
configure();
|
// SSB always keeps squelch "technically" open for the demodulator
|
||||||
} else if (p->id == Message::ID::NBFMConfigure) {
|
squelch_is_open = true;
|
||||||
auto nbfm_msg = *reinterpret_cast<const NBFMConfigureMessage*>(p);
|
return demod_ssb.execute(channel, audio_buffer);
|
||||||
user_squelch_level = nbfm_msg.squelch_level;
|
|
||||||
audio_output.configure(iir_config_passthrough, iir_config_passthrough, (float)user_squelch_level / 100.0f);
|
|
||||||
}
|
}
|
||||||
|
return demod_cw_fm.execute(channel, audio_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MorseProcessor::update_goertzel_coeff(float freq) {
|
void MorseProcessor::update_goertzel_coeff(float freq) {
|
||||||
if (freq < 400.0f) freq = 400.0f;
|
// limit to algo capacity min/max
|
||||||
if (freq > 1500.0f) freq = 1500.0f; // limit to algo capacity min/max
|
if (freq < 300.0f) freq = 300.0f;
|
||||||
float omega = 2.0f * M_PI * freq / 24000.0f;
|
if (freq > 2300.0f) freq = 2300.0f;
|
||||||
coeff_int = (int32_t)(2.0f * cosf(omega) * 16384.0f);
|
|
||||||
|
float sample_rate = (modulation == 0) ? 24000.0f : 12000.0f;
|
||||||
|
float omega = 2.0f * M_PI * freq / sample_rate;
|
||||||
|
float omega_sq = omega * omega;
|
||||||
|
float cos_approx = 1.0f - (omega_sq * 0.5f);
|
||||||
|
// 16384 is the scaling factor for the Goertzel integer math
|
||||||
|
coeff_int = (int32_t)(2.0f * cos_approx * 16384.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorseProcessor::measure_frequency(int32_t sample) {
|
||||||
|
// Noise gate threshold
|
||||||
|
const int32_t gate_threshold = (modulation == 0) ? 4000 : 2000;
|
||||||
|
|
||||||
|
if (sample > gate_threshold || sample < -gate_threshold) {
|
||||||
|
if (sample > 0 && !meas_signal_state_high) {
|
||||||
|
// Rising Edge (End of Period)
|
||||||
|
meas_signal_state_high = true;
|
||||||
|
|
||||||
|
if (meas_samples_in_period > 0) {
|
||||||
|
bool period_is_stable = false;
|
||||||
|
if (meas_last_period_len > 0) {
|
||||||
|
int32_t diff = std::abs((int)meas_samples_in_period - (int)meas_last_period_len);
|
||||||
|
if (diff <= 1) {
|
||||||
|
meas_consistency_count++;
|
||||||
|
period_is_stable = true;
|
||||||
|
} else {
|
||||||
|
meas_consistency_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
meas_last_period_len = meas_samples_in_period;
|
||||||
|
|
||||||
|
// Wait for AT LEAST 3 STABLE CYCLES
|
||||||
|
if (period_is_stable && meas_consistency_count > 3) {
|
||||||
|
float base_rate = (modulation == 0) ? 24000.0f : 12000.0f;
|
||||||
|
float inst_freq = base_rate / (float)meas_samples_in_period;
|
||||||
|
|
||||||
|
// Check for overflows
|
||||||
|
if (inst_freq > 250 && inst_freq < 3000) {
|
||||||
|
meas_freq_accumulator += inst_freq;
|
||||||
|
meas_freq_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
meas_samples_in_period = 0;
|
||||||
|
|
||||||
|
} else if (sample < 0) {
|
||||||
|
meas_signal_state_high = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Silence detection
|
||||||
|
if (meas_samples_in_period > 200) {
|
||||||
|
meas_last_period_len = 0;
|
||||||
|
meas_consistency_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meas_samples_in_period++;
|
||||||
|
|
||||||
|
ui_update_timer++;
|
||||||
|
uint32_t update_limit = (modulation == 0) ? 4800 : 2400; // ~200ms
|
||||||
|
|
||||||
|
if (ui_update_timer > update_limit) {
|
||||||
|
if (meas_freq_count > 0) {
|
||||||
|
float avg_freq = meas_freq_accumulator / (float)meas_freq_count;
|
||||||
|
current_freq = avg_freq;
|
||||||
|
// Round to 5Hz for display
|
||||||
|
uint32_t stable_disp = (uint32_t)avg_freq;
|
||||||
|
stable_disp = (stable_disp / 5) * 5;
|
||||||
|
|
||||||
|
freq_message.measured_frequency = stable_disp;
|
||||||
|
shared_memory.application_queue.push(freq_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
meas_freq_accumulator = 0.0f;
|
||||||
|
meas_freq_count = 0;
|
||||||
|
ui_update_timer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorseProcessor::process_decoding(int32_t sample) {
|
||||||
|
update_goertzel_coeff(current_freq);
|
||||||
|
|
||||||
|
// 1. Goertzel Algorithm
|
||||||
|
int64_t s = (int64_t)sample + (((int64_t)coeff_int * s_prev_i) >> 14) - s_prev2_i;
|
||||||
|
s_prev2_i = s_prev_i;
|
||||||
|
s_prev_i = (int32_t)s;
|
||||||
|
goertzel_count++;
|
||||||
|
|
||||||
|
// 2. Evaluation every 60 samples
|
||||||
|
if (goertzel_count >= 60) {
|
||||||
|
if (startup_delay > 0) {
|
||||||
|
startup_delay--;
|
||||||
|
// Fast learning phase
|
||||||
|
int64_t pwr = (int64_t)s_prev_i * s_prev_i + (int64_t)s_prev2_i * s_prev2_i -
|
||||||
|
(((int64_t)s_prev_i * s_prev2_i * coeff_int) >> 14);
|
||||||
|
noise_floor = (noise_floor * 15 + pwr) / 16;
|
||||||
|
} else {
|
||||||
|
// Power calculation
|
||||||
|
int64_t power = (int64_t)s_prev_i * s_prev_i + (int64_t)s_prev2_i * s_prev2_i -
|
||||||
|
(((int64_t)s_prev_i * s_prev2_i * coeff_int) >> 14);
|
||||||
|
|
||||||
|
// Adaptive Noise Floor
|
||||||
|
if (!was_signaling) {
|
||||||
|
noise_floor = (noise_floor * 127 + power) / 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Threshold Calculation
|
||||||
|
int64_t sensitivity = 4 + (user_squelch_level / 10);
|
||||||
|
int64_t base_pwr_threshold = noise_floor * sensitivity;
|
||||||
|
int64_t current_pwr_threshold = was_signaling ? (base_pwr_threshold / 2) : base_pwr_threshold;
|
||||||
|
|
||||||
|
// Tone Detection
|
||||||
|
bool is_tone = squelch_is_open && (power > current_pwr_threshold) && (power > 150000);
|
||||||
|
int32_t time_base = modulation == 0 ? 125 : 250;
|
||||||
|
// State Change Logic
|
||||||
|
if (is_tone != was_signaling) {
|
||||||
|
int32_t duration_us = (int32_t)((int64_t)duration_samples * time_base / 3);
|
||||||
|
|
||||||
|
// Send message if significant
|
||||||
|
if (duration_us > 10000 || was_signaling) {
|
||||||
|
message.state_durations[0] = was_signaling ? duration_us : -duration_us;
|
||||||
|
message.state_cnt = 1; // 1 indicates valid state duration data
|
||||||
|
shared_memory.application_queue.push(message);
|
||||||
|
}
|
||||||
|
was_signaling = is_tone;
|
||||||
|
duration_samples = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout Logic
|
||||||
|
if (!was_signaling && duration_samples > 28800) {
|
||||||
|
int32_t duration_us = (int32_t)((int64_t)duration_samples * time_base / 3);
|
||||||
|
message.state_durations[0] = -duration_us;
|
||||||
|
message.state_cnt = 1;
|
||||||
|
shared_memory.application_queue.push(message);
|
||||||
|
duration_samples = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
duration_samples += 60;
|
||||||
|
s_prev_i = 0;
|
||||||
|
s_prev2_i = 0;
|
||||||
|
goertzel_count = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MorseProcessor::execute(const buffer_c8_t& buffer) {
|
void MorseProcessor::execute(const buffer_c8_t& buffer) {
|
||||||
if (!configured) return;
|
if (!configured) return;
|
||||||
|
|
||||||
buffer_c16_t dst_buffer_c16(dst_buffer.data(), dst_buffer.size());
|
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer_c16);
|
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer_c16);
|
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
|
||||||
const auto channel = channel_filter.execute(decim_1_out, dst_buffer_c16);
|
|
||||||
|
|
||||||
feed_channel_stats(channel);
|
auto audio_buf = demodulate(channel_out);
|
||||||
|
|
||||||
buffer_s16_t audio_buffer_s16(audio_buffer.data(), audio_buffer.size());
|
|
||||||
auto audio_buf = demod.execute(channel, audio_buffer_s16);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < audio_buf.count; i++) {
|
for (size_t i = 0; i < audio_buf.count; i++) {
|
||||||
int32_t raw_sample = audio_buf.p[i];
|
float raw_audio = audio_buf.p[i];
|
||||||
|
|
||||||
// 1. DC & LPF
|
// Squelch Logic
|
||||||
dc_offset += (raw_sample - dc_offset) / 32;
|
int32_t raw_int_abs = (int32_t)(raw_audio * 32768.0f);
|
||||||
int32_t sample = raw_sample - dc_offset;
|
if (raw_int_abs < 0) raw_int_abs = -raw_int_abs;
|
||||||
lpf_sample = (lpf_sample * 3 + sample) / 4;
|
|
||||||
|
|
||||||
// 2. Squelch
|
|
||||||
int32_t abs_sample = (sample < 0) ? -sample : sample;
|
|
||||||
int32_t audio_threshold = (user_squelch_level * user_squelch_level) * 3;
|
int32_t audio_threshold = (user_squelch_level * user_squelch_level) * 3;
|
||||||
int32_t current_audio_threshold = squelch_is_open ? (audio_threshold / 2) : audio_threshold;
|
|
||||||
|
|
||||||
if (abs_sample > current_audio_threshold || user_squelch_level == 0) {
|
if (raw_int_abs > audio_threshold || user_squelch_level == 0) {
|
||||||
squelch_is_open = true;
|
squelch_is_open = true;
|
||||||
squelch_hold = 2400;
|
squelch_hold = (modulation == 0) ? 2400 : 1200;
|
||||||
} else {
|
} else {
|
||||||
if (squelch_hold > 0)
|
if (squelch_hold > 0)
|
||||||
squelch_hold--;
|
squelch_hold--;
|
||||||
else
|
else if (modulation == 0)
|
||||||
squelch_is_open = false;
|
squelch_is_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Frequency measurement (ZC)
|
measure_frequency((int32_t)(raw_audio * 32768.0f));
|
||||||
if (squelch_is_open && !was_signaling) {
|
|
||||||
if ((lpf_sample >= 0 && prev_sample < 0) || (lpf_sample < 0 && prev_sample >= 0)) {
|
|
||||||
if (zc_counter >= 8 && zc_counter <= 32) {
|
|
||||||
int32_t diff = zc_counter - last_zc_counter;
|
|
||||||
if (diff >= -1 && diff <= 1) {
|
|
||||||
float n_freq = 24000.0f / (zc_counter * 2.0f);
|
|
||||||
|
|
||||||
// Hybrid tracking
|
float decode_audio = raw_audio;
|
||||||
|
if (modulation > 0) {
|
||||||
|
const float gain = 6.0f;
|
||||||
|
decode_audio *= gain;
|
||||||
|
|
||||||
if (zc_counter < 15) {
|
// Hard Limiting / Clipping
|
||||||
// This stabilizes the 1000-1400 Hz range
|
if (decode_audio > 1.0f)
|
||||||
current_freq = (current_freq * 0.6f) + (n_freq * 0.4f);
|
decode_audio = 1.0f;
|
||||||
} else {
|
else if (decode_audio < -1.0f)
|
||||||
current_freq = n_freq;
|
decode_audio = -1.0f;
|
||||||
}
|
|
||||||
|
|
||||||
update_goertzel_coeff(current_freq);
|
audio_buf.p[i] = decode_audio;
|
||||||
}
|
|
||||||
last_zc_counter = zc_counter;
|
|
||||||
} else {
|
|
||||||
last_zc_counter = 0;
|
|
||||||
}
|
|
||||||
zc_counter = 0;
|
|
||||||
} else {
|
|
||||||
if (zc_counter < 100) zc_counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prev_sample = (int16_t)lpf_sample;
|
|
||||||
|
|
||||||
// 4. Goertzel
|
|
||||||
int64_t s = (int64_t)sample + (((int64_t)coeff_int * s_prev_i) >> 14) - s_prev2_i;
|
|
||||||
s_prev2_i = s_prev_i;
|
|
||||||
s_prev_i = (int32_t)s;
|
|
||||||
goertzel_count++;
|
|
||||||
|
|
||||||
// 5. Detection
|
|
||||||
if (goertzel_count >= 60) {
|
|
||||||
if (startup_delay > 0) {
|
|
||||||
startup_delay--;
|
|
||||||
int64_t pwr = (int64_t)s_prev_i * s_prev_i + (int64_t)s_prev2_i * s_prev2_i -
|
|
||||||
(((int64_t)s_prev_i * s_prev2_i * coeff_int) >> 14);
|
|
||||||
noise_floor = (noise_floor * 15 + pwr) / 16;
|
|
||||||
} else {
|
|
||||||
int64_t power = (int64_t)s_prev_i * s_prev_i + (int64_t)s_prev2_i * s_prev2_i -
|
|
||||||
(((int64_t)s_prev_i * s_prev2_i * coeff_int) >> 14);
|
|
||||||
|
|
||||||
if (!was_signaling) {
|
|
||||||
noise_floor = (noise_floor * 127 + power) / 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t sensitivity = 4 + (user_squelch_level / 10);
|
|
||||||
int64_t base_pwr_threshold = noise_floor * sensitivity;
|
|
||||||
int64_t current_pwr_threshold = was_signaling ? (base_pwr_threshold / 2) : base_pwr_threshold;
|
|
||||||
|
|
||||||
bool is_tone = squelch_is_open && (power > current_pwr_threshold) && (power > 150000);
|
|
||||||
|
|
||||||
if (is_tone != was_signaling) {
|
|
||||||
int32_t duration_us = (int32_t)((int64_t)duration_samples * 125 / 3);
|
|
||||||
|
|
||||||
if (duration_us > 10000) {
|
|
||||||
message.state_durations[0] = was_signaling ? duration_us : -duration_us;
|
|
||||||
message.measured_frequency = (uint32_t)current_freq;
|
|
||||||
message.state_cnt = 1;
|
|
||||||
shared_memory.application_queue.push(message);
|
|
||||||
}
|
|
||||||
was_signaling = is_tone;
|
|
||||||
duration_samples = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!was_signaling && duration_samples > 28800) {
|
|
||||||
int32_t duration_us = (int32_t)((int64_t)duration_samples * 125 / 3);
|
|
||||||
message.state_durations[0] = -duration_us;
|
|
||||||
message.state_cnt = 1;
|
|
||||||
shared_memory.application_queue.push(message);
|
|
||||||
duration_samples = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
duration_samples += 60;
|
|
||||||
s_prev_i = 0;
|
|
||||||
s_prev2_i = 0;
|
|
||||||
goertzel_count = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_buf.p[i] = squelch_is_open ? (int16_t)sample : 0;
|
process_decoding((int32_t)(decode_audio * 32768.0f));
|
||||||
|
|
||||||
|
if (modulation == 0 && !squelch_is_open) {
|
||||||
|
audio_buf.p[i] = 0.0f; // mute nfm on squelch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_output.write(audio_buf);
|
audio_output.write(audio_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MorseProcessor::on_message(const Message* const p) {
|
||||||
|
switch (p->id) {
|
||||||
|
case Message::ID::MorseRXConfig: {
|
||||||
|
auto morse_rx_msg = *reinterpret_cast<const MorseRXConfigureMessage*>(p);
|
||||||
|
configure(morse_rx_msg.mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Message::ID::NBFMConfigure: {
|
||||||
|
auto nbfm_msg = *reinterpret_cast<const NBFMConfigureMessage*>(p);
|
||||||
|
user_squelch_level = nbfm_msg.squelch_level;
|
||||||
|
audio_output.configure(iir_config_passthrough, iir_config_passthrough, (float)user_squelch_level / 100.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
audio::dma::init_audio_out();
|
audio::dma::init_audio_out();
|
||||||
EventDispatcher event_dispatcher{std::make_unique<MorseProcessor>()};
|
EventDispatcher event_dispatcher{std::make_unique<MorseProcessor>()};
|
||||||
|
|||||||
@@ -37,54 +37,63 @@ class MorseProcessor : public BasebandProcessor {
|
|||||||
|
|
||||||
void execute(const buffer_c8_t& buffer) override;
|
void execute(const buffer_c8_t& buffer) override;
|
||||||
void on_message(const Message* const p) override;
|
void on_message(const Message* const p) override;
|
||||||
void update_goertzel_coeff(float freq);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool configured{false};
|
void configure(uint8_t mode);
|
||||||
|
buffer_f32_t demodulate(const buffer_c16_t& channel);
|
||||||
|
void update_goertzel_coeff(float freq);
|
||||||
|
void measure_frequency(int32_t sample);
|
||||||
|
void process_decoding(int32_t sample);
|
||||||
|
|
||||||
size_t baseband_fs = 3072000;
|
BasebandThread baseband_thread{3072000, this, baseband::Direction::Receive};
|
||||||
|
|
||||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
|
||||||
RSSIThread rssi_thread{};
|
RSSIThread rssi_thread{};
|
||||||
|
|
||||||
std::array<complex16_t, 512> dst_buffer{};
|
std::array<complex16_t, 512> dst{};
|
||||||
std::array<int16_t, 32> audio_buffer{};
|
const buffer_c16_t dst_buffer{
|
||||||
|
dst.data(),
|
||||||
|
dst.size()};
|
||||||
|
std::array<float, 32> audio{};
|
||||||
|
const buffer_f32_t audio_buffer{
|
||||||
|
audio.data(),
|
||||||
|
audio.size()};
|
||||||
|
|
||||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
||||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||||
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
||||||
dsp::demodulate::FM demod{};
|
dsp::demodulate::FM demod_cw_fm{};
|
||||||
|
dsp::demodulate::SSB demod_ssb{};
|
||||||
AudioOutput audio_output{};
|
AudioOutput audio_output{};
|
||||||
|
|
||||||
// Goertzel and signal detection
|
bool configured{false};
|
||||||
|
|
||||||
|
uint8_t modulation{0}; // 0=CW/FM, 1=USB, 2=LSB
|
||||||
|
int32_t user_squelch_level{0};
|
||||||
|
bool squelch_is_open{true};
|
||||||
|
int32_t squelch_hold{0};
|
||||||
|
|
||||||
|
// frequency measurement variables
|
||||||
|
uint32_t meas_samples_in_period{0};
|
||||||
|
bool meas_signal_state_high{false};
|
||||||
|
uint32_t meas_last_period_len{0};
|
||||||
|
uint32_t meas_consistency_count{0};
|
||||||
|
float meas_freq_accumulator{0.0f};
|
||||||
|
uint32_t meas_freq_count{0};
|
||||||
|
uint32_t ui_update_timer{0};
|
||||||
|
|
||||||
|
float current_freq{700.0f};
|
||||||
|
|
||||||
|
// --- Decoding variables (Goertzel) ---
|
||||||
int32_t coeff_int{0};
|
int32_t coeff_int{0};
|
||||||
int32_t s_prev_i{0};
|
int32_t s_prev_i{0};
|
||||||
int32_t s_prev2_i{0};
|
int32_t s_prev2_i{0};
|
||||||
uint32_t goertzel_count{0};
|
uint32_t goertzel_count{0};
|
||||||
uint32_t duration_samples{0};
|
uint32_t duration_samples{0};
|
||||||
bool was_signaling{false};
|
bool was_signaling{false};
|
||||||
|
int64_t noise_floor{5000};
|
||||||
// Variables required for measurement
|
int32_t startup_delay{20};
|
||||||
int16_t prev_sample{0};
|
|
||||||
uint32_t zc_counter{0};
|
|
||||||
float current_freq{0.0f};
|
|
||||||
|
|
||||||
int32_t lpf_sample{0};
|
|
||||||
int32_t dc_offset{0};
|
|
||||||
|
|
||||||
// Squelch and stability
|
|
||||||
bool squelch_is_open{false};
|
|
||||||
int32_t squelch_hold{0};
|
|
||||||
|
|
||||||
int32_t user_squelch_level{0};
|
|
||||||
int64_t noise_floor{0};
|
|
||||||
|
|
||||||
int32_t startup_delay{0}; // Power-on delay
|
|
||||||
int32_t last_zc_counter{0}; // Previous ZC measurement for comparison
|
|
||||||
|
|
||||||
MorseRXDataMessage message{};
|
MorseRXDataMessage message{};
|
||||||
|
MorseRXfreqMessage freq_message{};
|
||||||
void configure();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __PROC_MORSE_H__
|
#endif
|
||||||
@@ -147,8 +147,9 @@ class Message {
|
|||||||
SSTVRXCalibration = 89,
|
SSTVRXCalibration = 89,
|
||||||
SubCarData = 90,
|
SubCarData = 90,
|
||||||
MorseRXData = 91,
|
MorseRXData = 91,
|
||||||
MorseRXConfig = 92,
|
MorseRXfreq = 92,
|
||||||
TXDisabled = 93,
|
MorseRXConfig = 93,
|
||||||
|
TXDisabled = 94,
|
||||||
MAX
|
MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1706,16 +1707,24 @@ class MorseRXDataMessage : public Message {
|
|||||||
public:
|
public:
|
||||||
constexpr MorseRXDataMessage()
|
constexpr MorseRXDataMessage()
|
||||||
: Message{ID::MorseRXData} {}
|
: Message{ID::MorseRXData} {}
|
||||||
|
int32_t state_durations[5] = {0}; // positive: high, negative: low
|
||||||
|
uint8_t state_cnt = 0;
|
||||||
|
const uint8_t maxptr = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MorseRXfreqMessage : public Message {
|
||||||
|
public:
|
||||||
|
constexpr MorseRXfreqMessage()
|
||||||
|
: Message{ID::MorseRXfreq} {}
|
||||||
uint32_t measured_frequency = 0;
|
uint32_t measured_frequency = 0;
|
||||||
int32_t state_durations[2] = {0}; // positive: high, negative: low
|
|
||||||
uint16_t state_cnt = 0;
|
|
||||||
const uint16_t maxptr = 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class MorseRXConfigureMessage : public Message {
|
class MorseRXConfigureMessage : public Message {
|
||||||
public:
|
public:
|
||||||
constexpr MorseRXConfigureMessage()
|
constexpr MorseRXConfigureMessage(uint8_t mode)
|
||||||
: Message{ID::MorseRXConfig} {}
|
: Message{ID::MorseRXConfig},
|
||||||
|
mode{mode} {}
|
||||||
|
uint8_t mode = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TXDisabledMessage : public Message {
|
class TXDisabledMessage : public Message {
|
||||||
|
|||||||
Reference in New Issue
Block a user