mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-12 09:39:30 +00:00
Morse rx further development (#2959)
* filters OK, radio configurations set * all modes are working, noise still needs work, especially in FM. A baseband enum is also needed * audio and decoding fixed, clipping and the baseband enum are still missing
This commit is contained in:
@@ -17,6 +17,7 @@ MorseRadioView::MorseRadioView(ui::NavigationView& nav)
|
|||||||
&txt_last,
|
&txt_last,
|
||||||
&txt_speed,
|
&txt_speed,
|
||||||
&txt_freq,
|
&txt_freq,
|
||||||
|
&txt_clip,
|
||||||
&options_mode,
|
&options_mode,
|
||||||
&btn_clear,
|
&btn_clear,
|
||||||
&console_text,
|
&console_text,
|
||||||
@@ -39,9 +40,9 @@ MorseRadioView::MorseRadioView(ui::NavigationView& nav)
|
|||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
|
|
||||||
options_mode.on_change = [this](size_t, int32_t value) {
|
options_mode.on_change = [this](size_t, int32_t value) {
|
||||||
current_mode = (uint8_t)value;
|
current_mode = static_cast<ModulationMode>((uint8_t)value);
|
||||||
morse_decoder_.resetLearning();
|
morse_decoder_.resetLearning();
|
||||||
if (current_mode == 0) {
|
if (current_mode == ModulationMode::FM) {
|
||||||
receiver_model.set_am_configuration(4);
|
receiver_model.set_am_configuration(4);
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||||
field_squelch.set_style(Theme::getInstance()->option_active);
|
field_squelch.set_style(Theme::getInstance()->option_active);
|
||||||
@@ -49,21 +50,26 @@ MorseRadioView::MorseRadioView(ui::NavigationView& nav)
|
|||||||
receiver_model.set_squelch_level(field_squelch.value());
|
receiver_model.set_squelch_level(field_squelch.value());
|
||||||
audio::set_rate(audio::Rate::Hz_24000);
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
} else {
|
} else {
|
||||||
|
audio::set_rate(audio::Rate::Hz_12000);
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
||||||
receiver_model.set_am_configuration(current_mode + 7);
|
if (current_mode == ModulationMode::AM || current_mode == ModulationMode::DSB)
|
||||||
|
receiver_model.set_am_configuration(7);
|
||||||
|
else if (current_mode == ModulationMode::USB)
|
||||||
|
receiver_model.set_am_configuration(9);
|
||||||
|
else // LSB
|
||||||
|
receiver_model.set_am_configuration(10);
|
||||||
receiver_model.set_squelch_level(0);
|
receiver_model.set_squelch_level(0);
|
||||||
field_squelch.set_style(Theme::getInstance()->fg_dark);
|
field_squelch.set_style(Theme::getInstance()->fg_dark);
|
||||||
field_squelch.set_focusable(false);
|
field_squelch.set_focusable(false);
|
||||||
audio::set_rate(audio::Rate::Hz_12000);
|
|
||||||
}
|
}
|
||||||
baseband::set_moreserx_config(current_mode);
|
baseband::set_moreserx_config(static_cast<uint8_t>(current_mode));
|
||||||
};
|
};
|
||||||
field_squelch.set_value(receiver_model.squelch_level(), false); // will be sent later, no need to send 2x
|
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) {
|
field_squelch.on_change = [this](int32_t v) {
|
||||||
if (current_mode == 0)
|
if (current_mode == ModulationMode::FM)
|
||||||
receiver_model.set_squelch_level(v);
|
receiver_model.set_squelch_level(v);
|
||||||
};
|
};
|
||||||
options_mode.set_selected_index(current_mode, true);
|
options_mode.set_selected_index(static_cast<int32_t>(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);
|
||||||
@@ -105,9 +111,30 @@ void MorseLogger::init_daily_log(const std::filesystem::path& log_dir) {
|
|||||||
|
|
||||||
void MorseLogger::radio_set_log(uint8_t current_mode) {
|
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 mode_str;
|
||||||
|
switch (static_cast<MorseRadioView::ModulationMode>(current_mode)) {
|
||||||
|
case MorseRadioView::ModulationMode::AM:
|
||||||
|
mode_str = "AM";
|
||||||
|
break;
|
||||||
|
case MorseRadioView::ModulationMode::FM:
|
||||||
|
mode_str = "FM";
|
||||||
|
break;
|
||||||
|
case MorseRadioView::ModulationMode::DSB:
|
||||||
|
mode_str = "DSB";
|
||||||
|
break;
|
||||||
|
case MorseRadioView::ModulationMode::USB:
|
||||||
|
mode_str = "USB";
|
||||||
|
break;
|
||||||
|
case MorseRadioView::ModulationMode::LSB:
|
||||||
|
mode_str = "LSB";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode_str = "???";
|
||||||
|
break;
|
||||||
|
}
|
||||||
std::string header = "Freq:" + to_string_rounded_freq(freq, 4);
|
std::string header = "Freq:" + to_string_rounded_freq(freq, 4);
|
||||||
header += "MHz, ";
|
header += "MHz, ";
|
||||||
header += "RX MODE:" + std::string(current_mode == 0 ? "CW/FM" : (current_mode == 1 ? "USB" : "LSB"));
|
header += "RX MODE:" + mode_str;
|
||||||
header += "\r\nMessage:";
|
header += "\r\nMessage:";
|
||||||
log_file.write_raw(header);
|
log_file.write_raw(header);
|
||||||
}
|
}
|
||||||
@@ -152,6 +179,8 @@ MorseRadioView::~MorseRadioView() {
|
|||||||
|
|
||||||
void MorseRadioView::focus() {
|
void MorseRadioView::focus() {
|
||||||
field_frequency.focus();
|
field_frequency.focus();
|
||||||
|
txt_clip.set_style(Theme::getInstance()->fg_red);
|
||||||
|
txt_clip.hidden(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MorseRadioView::check_for_timeout() {
|
void MorseRadioView::check_for_timeout() {
|
||||||
@@ -205,6 +234,8 @@ int32_t MorseRadioView::ProcessSignal(int32_t sig_time_us) {
|
|||||||
void MorseRadioView::on_data(const MorseRXDataMessage* message) {
|
void MorseRadioView::on_data(const MorseRXDataMessage* message) {
|
||||||
int32_t r;
|
int32_t r;
|
||||||
|
|
||||||
|
txt_clip.hidden(!message->clipped);
|
||||||
|
|
||||||
for (uint8_t i = 0; i <= message->state_cnt; ++i) {
|
for (uint8_t i = 0; i <= message->state_cnt; ++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);
|
||||||
@@ -212,7 +243,7 @@ void MorseRadioView::on_data(const MorseRXDataMessage* message) {
|
|||||||
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, current_mode);
|
time_stamp = logger->on_packet(result.text, time_stamp, static_cast<uint8_t>(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) {
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ class MorseLogger {
|
|||||||
|
|
||||||
class MorseRadioView : public ui::View {
|
class MorseRadioView : public ui::View {
|
||||||
public:
|
public:
|
||||||
|
enum class ModulationMode : uint8_t {
|
||||||
|
AM = 0,
|
||||||
|
FM = 1,
|
||||||
|
DSB = 2,
|
||||||
|
USB = 3,
|
||||||
|
LSB = 4
|
||||||
|
};
|
||||||
|
|
||||||
MorseRadioView(ui::NavigationView& nav);
|
MorseRadioView(ui::NavigationView& nav);
|
||||||
~MorseRadioView();
|
~MorseRadioView();
|
||||||
std::string title() const override {
|
std::string title() const override {
|
||||||
@@ -83,12 +91,12 @@ class MorseRadioView : public ui::View {
|
|||||||
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
|
ModulationMode current_mode = ModulationMode::AM;
|
||||||
|
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_morese_radio",
|
"rx_morese_radio",
|
||||||
app_settings::Mode::RX,
|
app_settings::Mode::RX,
|
||||||
{{"cwmode"sv, ¤t_mode}}};
|
{{"cwmode"sv, reinterpret_cast<uint8_t*>(¤t_mode)}}};
|
||||||
|
|
||||||
RxFrequencyField field_frequency{
|
RxFrequencyField field_frequency{
|
||||||
{UI_POS_X(0), UI_POS_Y(0)},
|
{UI_POS_X(0), UI_POS_Y(0)},
|
||||||
@@ -115,6 +123,7 @@ 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::Text txt_freq{{UI_POS_X(21), UI_POS_Y(2), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)}, "??"};
|
||||||
|
ui::Text txt_clip{{UI_POS_X(15), UI_POS_Y(3), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)}, "clipping"};
|
||||||
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},
|
||||||
@@ -127,7 +136,7 @@ class MorseRadioView : public ui::View {
|
|||||||
ui::OptionsField options_mode{
|
ui::OptionsField options_mode{
|
||||||
{UI_POS_X(9), UI_POS_Y(2)},
|
{UI_POS_X(9), UI_POS_Y(2)},
|
||||||
5,
|
5,
|
||||||
{{"CW/FM", 0}, {"USB", 1}, {"LSB", 2}}};
|
{{"AM", 0}, {"FM", 1}, {"DSB", 2}, {"USB", 3}, {"LSB", 4}}};
|
||||||
|
|
||||||
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"};
|
||||||
|
|||||||
@@ -28,26 +28,32 @@
|
|||||||
void MorseProcessor::configure(uint8_t mode) {
|
void MorseProcessor::configure(uint8_t mode) {
|
||||||
configured = false;
|
configured = false;
|
||||||
|
|
||||||
if (mode == 0) { // CW/FM
|
modulation = static_cast<ModulationMode>((uint8_t)mode);
|
||||||
|
|
||||||
|
if (modulation == ModulationMode::FM) { // 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_cw_fm.configure(24000, 5000);
|
demod_cw_fm.configure(24000, 5000);
|
||||||
} else { // USB, LSB
|
} else {
|
||||||
decim_0.configure(taps_6k0_decim_0.taps);
|
decim_0.configure(taps_4k25_decim_0.taps);
|
||||||
decim_1.configure(taps_6k0_decim_1.taps);
|
decim_1.configure(taps_4k25_decim_1.taps);
|
||||||
|
if (modulation == ModulationMode::AM) { // AM
|
||||||
if (mode == 1) // USB
|
channel_filter.configure(taps_2k0_am_lpf_channel.taps, 4);
|
||||||
channel_filter.configure(taps_2k8_usb_channel.taps, 4);
|
} else { // SSB, DSB
|
||||||
else // LSB
|
if (modulation == ModulationMode::DSB) // DSB
|
||||||
channel_filter.configure(taps_2k8_lsb_channel.taps, 4);
|
channel_filter.configure(taps_1k5_dsb_lpf.taps, 4);
|
||||||
|
else if (modulation == ModulationMode::USB) // USB
|
||||||
|
channel_filter.configure(taps_1k5_USB_channel.taps, 4);
|
||||||
|
else // LSB
|
||||||
|
channel_filter.configure(taps_1k5_LSB_channel.taps, 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modulation = mode;
|
if (modulation == ModulationMode::FM)
|
||||||
if (mode > 0)
|
|
||||||
audio_output.configure(audio_12k_hpf_300hz_config);
|
|
||||||
else
|
|
||||||
audio_output.configure(iir_config_passthrough, iir_config_passthrough, (float)user_squelch_level / 100.0f);
|
audio_output.configure(iir_config_passthrough, iir_config_passthrough, (float)user_squelch_level / 100.0f);
|
||||||
|
else
|
||||||
|
audio_output.configure(audio_12k_hpf_300hz_config);
|
||||||
|
|
||||||
meas_samples_in_period = 0;
|
meas_samples_in_period = 0;
|
||||||
meas_last_period_len = 0;
|
meas_last_period_len = 0;
|
||||||
@@ -70,6 +76,8 @@ void MorseProcessor::configure(uint8_t mode) {
|
|||||||
squelch_is_open = true;
|
squelch_is_open = true;
|
||||||
squelch_hold = 0;
|
squelch_hold = 0;
|
||||||
|
|
||||||
|
dc_average = 0.0f;
|
||||||
|
|
||||||
current_freq = 700.0f;
|
current_freq = 700.0f;
|
||||||
update_goertzel_coeff(current_freq);
|
update_goertzel_coeff(current_freq);
|
||||||
|
|
||||||
@@ -77,10 +85,15 @@ void MorseProcessor::configure(uint8_t mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline buffer_f32_t MorseProcessor::demodulate(const buffer_c16_t& channel) {
|
inline buffer_f32_t MorseProcessor::demodulate(const buffer_c16_t& channel) {
|
||||||
if (modulation > 0) {
|
// AM,SSB,DSB always keeps squelch "technically" open for the demodulator
|
||||||
// SSB always keeps squelch "technically" open for the demodulator
|
if (modulation == ModulationMode::AM || modulation == ModulationMode::DSB) {
|
||||||
squelch_is_open = true;
|
squelch_is_open = true;
|
||||||
return demod_ssb.execute(channel, audio_buffer);
|
return demod_AM.execute(channel, audio_buffer);
|
||||||
|
} else {
|
||||||
|
if (modulation == ModulationMode::USB || modulation == ModulationMode::LSB) {
|
||||||
|
squelch_is_open = true;
|
||||||
|
return demod_ssb.execute(channel, audio_buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return demod_cw_fm.execute(channel, audio_buffer);
|
return demod_cw_fm.execute(channel, audio_buffer);
|
||||||
}
|
}
|
||||||
@@ -90,7 +103,7 @@ void MorseProcessor::update_goertzel_coeff(float freq) {
|
|||||||
if (freq < 300.0f) freq = 300.0f;
|
if (freq < 300.0f) freq = 300.0f;
|
||||||
if (freq > 2300.0f) freq = 2300.0f;
|
if (freq > 2300.0f) freq = 2300.0f;
|
||||||
|
|
||||||
float sample_rate = (modulation == 0) ? 24000.0f : 12000.0f;
|
float sample_rate = (modulation == ModulationMode::FM) ? 24000.0f : 12000.0f;
|
||||||
float omega = 2.0f * M_PI * freq / sample_rate;
|
float omega = 2.0f * M_PI * freq / sample_rate;
|
||||||
float omega_sq = omega * omega;
|
float omega_sq = omega * omega;
|
||||||
float cos_approx = 1.0f - (omega_sq * 0.5f);
|
float cos_approx = 1.0f - (omega_sq * 0.5f);
|
||||||
@@ -100,7 +113,7 @@ void MorseProcessor::update_goertzel_coeff(float freq) {
|
|||||||
|
|
||||||
void MorseProcessor::measure_frequency(int32_t sample) {
|
void MorseProcessor::measure_frequency(int32_t sample) {
|
||||||
// Noise gate threshold
|
// Noise gate threshold
|
||||||
const int32_t gate_threshold = (modulation == 0) ? 4000 : 2000;
|
const int32_t gate_threshold = (modulation == ModulationMode::FM) ? 4000 : 2000;
|
||||||
|
|
||||||
if (sample > gate_threshold || sample < -gate_threshold) {
|
if (sample > gate_threshold || sample < -gate_threshold) {
|
||||||
if (sample > 0 && !meas_signal_state_high) {
|
if (sample > 0 && !meas_signal_state_high) {
|
||||||
@@ -111,7 +124,7 @@ void MorseProcessor::measure_frequency(int32_t sample) {
|
|||||||
bool period_is_stable = false;
|
bool period_is_stable = false;
|
||||||
if (meas_last_period_len > 0) {
|
if (meas_last_period_len > 0) {
|
||||||
int32_t diff = std::abs((int)meas_samples_in_period - (int)meas_last_period_len);
|
int32_t diff = std::abs((int)meas_samples_in_period - (int)meas_last_period_len);
|
||||||
if (diff <= 1) {
|
if (diff <= 2) {
|
||||||
meas_consistency_count++;
|
meas_consistency_count++;
|
||||||
period_is_stable = true;
|
period_is_stable = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -121,10 +134,12 @@ void MorseProcessor::measure_frequency(int32_t sample) {
|
|||||||
meas_last_period_len = meas_samples_in_period;
|
meas_last_period_len = meas_samples_in_period;
|
||||||
|
|
||||||
// Wait for AT LEAST 3 STABLE CYCLES
|
// Wait for AT LEAST 3 STABLE CYCLES
|
||||||
if (period_is_stable && meas_consistency_count > 3) {
|
if (period_is_stable && meas_consistency_count > 5) {
|
||||||
float base_rate = (modulation == 0) ? 24000.0f : 12000.0f;
|
float base_rate = (modulation == ModulationMode::FM) ? 24000.0f : 12000.0f;
|
||||||
float inst_freq = base_rate / (float)meas_samples_in_period;
|
float inst_freq = base_rate / (float)meas_samples_in_period;
|
||||||
|
if (modulation == ModulationMode::DSB) {
|
||||||
|
inst_freq /= 2.0f;
|
||||||
|
}
|
||||||
// Check for overflows
|
// Check for overflows
|
||||||
if (inst_freq > 250 && inst_freq < 3000) {
|
if (inst_freq > 250 && inst_freq < 3000) {
|
||||||
meas_freq_accumulator += inst_freq;
|
meas_freq_accumulator += inst_freq;
|
||||||
@@ -148,7 +163,7 @@ void MorseProcessor::measure_frequency(int32_t sample) {
|
|||||||
meas_samples_in_period++;
|
meas_samples_in_period++;
|
||||||
|
|
||||||
ui_update_timer++;
|
ui_update_timer++;
|
||||||
uint32_t update_limit = (modulation == 0) ? 4800 : 2400; // ~200ms
|
uint32_t update_limit = (modulation == ModulationMode::FM) ? 4800 : 2400; // ~200ms
|
||||||
|
|
||||||
if (ui_update_timer > update_limit) {
|
if (ui_update_timer > update_limit) {
|
||||||
if (meas_freq_count > 0) {
|
if (meas_freq_count > 0) {
|
||||||
@@ -202,7 +217,7 @@ void MorseProcessor::process_decoding(int32_t sample) {
|
|||||||
|
|
||||||
// Tone Detection
|
// Tone Detection
|
||||||
bool is_tone = squelch_is_open && (power > current_pwr_threshold) && (power > 150000);
|
bool is_tone = squelch_is_open && (power > current_pwr_threshold) && (power > 150000);
|
||||||
int32_t time_base = modulation == 0 ? 125 : 250;
|
int32_t time_base = 250;
|
||||||
// State Change Logic
|
// State Change Logic
|
||||||
if (is_tone != was_signaling) {
|
if (is_tone != was_signaling) {
|
||||||
int32_t duration_us = (int32_t)((int64_t)duration_samples * time_base / 3);
|
int32_t duration_us = (int32_t)((int64_t)duration_samples * time_base / 3);
|
||||||
@@ -254,33 +269,43 @@ void MorseProcessor::execute(const buffer_c8_t& buffer) {
|
|||||||
|
|
||||||
if (raw_int_abs > 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 = (modulation == 0) ? 2400 : 1200;
|
squelch_hold = (modulation == ModulationMode::FM) ? 2400 : 1200;
|
||||||
} else {
|
} else {
|
||||||
if (squelch_hold > 0)
|
if (squelch_hold > 0)
|
||||||
squelch_hold--;
|
squelch_hold--;
|
||||||
else if (modulation == 0)
|
else if (modulation == ModulationMode::FM)
|
||||||
squelch_is_open = false;
|
squelch_is_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
measure_frequency((int32_t)(raw_audio * 32768.0f));
|
|
||||||
|
|
||||||
float decode_audio = raw_audio;
|
float decode_audio = raw_audio;
|
||||||
if (modulation > 0) {
|
if (modulation != ModulationMode::FM) {
|
||||||
const float gain = 6.0f;
|
float gain = 16.0f;
|
||||||
|
if (modulation == ModulationMode::USB || modulation == ModulationMode::LSB)
|
||||||
|
gain = 5.0f;
|
||||||
decode_audio *= gain;
|
decode_audio *= gain;
|
||||||
|
|
||||||
// Hard Limiting / Clipping
|
// Hard Limiting / Clipping
|
||||||
if (decode_audio > 1.0f)
|
message.clipped = false;
|
||||||
|
if (decode_audio > 1.0f) {
|
||||||
decode_audio = 1.0f;
|
decode_audio = 1.0f;
|
||||||
else if (decode_audio < -1.0f)
|
message.clipped = true;
|
||||||
|
} else if (decode_audio < -1.0f) {
|
||||||
decode_audio = -1.0f;
|
decode_audio = -1.0f;
|
||||||
|
}
|
||||||
audio_buf.p[i] = decode_audio;
|
audio_buf.p[i] = decode_audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DC BLOCKING
|
||||||
|
if (modulation != ModulationMode::FM) {
|
||||||
|
dc_average = (dc_average * 0.95f) + (decode_audio * 0.05f);
|
||||||
|
measure_frequency((int32_t)((decode_audio - dc_average) * 32768.0f));
|
||||||
|
} else {
|
||||||
|
measure_frequency((int32_t)(raw_audio * 32768.0f));
|
||||||
|
}
|
||||||
|
|
||||||
process_decoding((int32_t)(decode_audio * 32768.0f));
|
process_decoding((int32_t)(decode_audio * 32768.0f));
|
||||||
|
|
||||||
if (modulation == 0 && !squelch_is_open) {
|
if (modulation == ModulationMode::FM && !squelch_is_open) {
|
||||||
audio_buf.p[i] = 0.0f; // mute nfm on squelch
|
audio_buf.p[i] = 0.0f; // mute nfm on squelch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,13 +60,22 @@ class MorseProcessor : public BasebandProcessor {
|
|||||||
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::AM demod_AM{};
|
||||||
dsp::demodulate::FM demod_cw_fm{};
|
dsp::demodulate::FM demod_cw_fm{};
|
||||||
dsp::demodulate::SSB demod_ssb{};
|
dsp::demodulate::SSB demod_ssb{};
|
||||||
AudioOutput audio_output{};
|
AudioOutput audio_output{};
|
||||||
|
|
||||||
bool configured{false};
|
bool configured{false};
|
||||||
|
|
||||||
uint8_t modulation{0}; // 0=CW/FM, 1=USB, 2=LSB
|
enum class ModulationMode : uint8_t {
|
||||||
|
AM = 0,
|
||||||
|
FM = 1,
|
||||||
|
DSB = 2,
|
||||||
|
USB = 3,
|
||||||
|
LSB = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
ModulationMode modulation = ModulationMode::AM;
|
||||||
int32_t user_squelch_level{0};
|
int32_t user_squelch_level{0};
|
||||||
bool squelch_is_open{true};
|
bool squelch_is_open{true};
|
||||||
int32_t squelch_hold{0};
|
int32_t squelch_hold{0};
|
||||||
@@ -79,7 +88,6 @@ class MorseProcessor : public BasebandProcessor {
|
|||||||
float meas_freq_accumulator{0.0f};
|
float meas_freq_accumulator{0.0f};
|
||||||
uint32_t meas_freq_count{0};
|
uint32_t meas_freq_count{0};
|
||||||
uint32_t ui_update_timer{0};
|
uint32_t ui_update_timer{0};
|
||||||
|
|
||||||
float current_freq{700.0f};
|
float current_freq{700.0f};
|
||||||
|
|
||||||
// --- Decoding variables (Goertzel) ---
|
// --- Decoding variables (Goertzel) ---
|
||||||
@@ -92,6 +100,9 @@ class MorseProcessor : public BasebandProcessor {
|
|||||||
int64_t noise_floor{5000};
|
int64_t noise_floor{5000};
|
||||||
int32_t startup_delay{20};
|
int32_t startup_delay{20};
|
||||||
|
|
||||||
|
float dc_average = 0.0f; // DC level tracking
|
||||||
|
int32_t dc_average_int = 0;
|
||||||
|
|
||||||
MorseRXDataMessage message{};
|
MorseRXDataMessage message{};
|
||||||
MorseRXfreqMessage freq_message{};
|
MorseRXfreqMessage freq_message{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1681,4 +1681,362 @@ static constexpr fir_taps_real<24> taps_BTLE_Dual_PHY = {
|
|||||||
3}},
|
3}},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static constexpr fir_taps_real<63> taps_2k0_am_lpf_channel = {
|
||||||
|
.low_frequency_normalized = -2000.0f / 48000.0f,
|
||||||
|
.high_frequency_normalized = 2000.0f / 48000.0f,
|
||||||
|
.transition_normalized = 1500.0f / 48000.0f,
|
||||||
|
.taps = {{
|
||||||
|
26,
|
||||||
|
29,
|
||||||
|
31,
|
||||||
|
33,
|
||||||
|
32,
|
||||||
|
28,
|
||||||
|
18,
|
||||||
|
0,
|
||||||
|
-26,
|
||||||
|
-61,
|
||||||
|
-104,
|
||||||
|
-152,
|
||||||
|
-202,
|
||||||
|
-246,
|
||||||
|
-279,
|
||||||
|
-292,
|
||||||
|
-277,
|
||||||
|
-227,
|
||||||
|
-136,
|
||||||
|
0,
|
||||||
|
182,
|
||||||
|
409,
|
||||||
|
673,
|
||||||
|
968,
|
||||||
|
1280,
|
||||||
|
1595,
|
||||||
|
1899,
|
||||||
|
2174,
|
||||||
|
2407,
|
||||||
|
2583,
|
||||||
|
2693,
|
||||||
|
2731,
|
||||||
|
2693,
|
||||||
|
2583,
|
||||||
|
2407,
|
||||||
|
2174,
|
||||||
|
1899,
|
||||||
|
1595,
|
||||||
|
1280,
|
||||||
|
968,
|
||||||
|
673,
|
||||||
|
409,
|
||||||
|
182,
|
||||||
|
0,
|
||||||
|
-136,
|
||||||
|
-227,
|
||||||
|
-277,
|
||||||
|
-292,
|
||||||
|
-279,
|
||||||
|
-246,
|
||||||
|
-202,
|
||||||
|
-152,
|
||||||
|
-104,
|
||||||
|
-61,
|
||||||
|
-26,
|
||||||
|
0,
|
||||||
|
18,
|
||||||
|
28,
|
||||||
|
32,
|
||||||
|
33,
|
||||||
|
31,
|
||||||
|
29,
|
||||||
|
26,
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr fir_taps_real<64> taps_1K5_FM_channel = {
|
||||||
|
.low_frequency_normalized = 300.0f / 48000.0f,
|
||||||
|
.high_frequency_normalized = 1800.0f / 48000.0f,
|
||||||
|
.transition_normalized = 1000.0f / 48000.0f,
|
||||||
|
.taps = {{
|
||||||
|
24,
|
||||||
|
22,
|
||||||
|
20,
|
||||||
|
15,
|
||||||
|
9,
|
||||||
|
-2,
|
||||||
|
-18,
|
||||||
|
-39,
|
||||||
|
-66,
|
||||||
|
-98,
|
||||||
|
-132,
|
||||||
|
-167,
|
||||||
|
-198,
|
||||||
|
-220,
|
||||||
|
-228,
|
||||||
|
-217,
|
||||||
|
-181,
|
||||||
|
-117,
|
||||||
|
-19,
|
||||||
|
112,
|
||||||
|
277,
|
||||||
|
474,
|
||||||
|
697,
|
||||||
|
941,
|
||||||
|
1197,
|
||||||
|
1454,
|
||||||
|
1702,
|
||||||
|
1930,
|
||||||
|
2128,
|
||||||
|
2285,
|
||||||
|
2394,
|
||||||
|
2451,
|
||||||
|
2451,
|
||||||
|
2394,
|
||||||
|
2285,
|
||||||
|
2128,
|
||||||
|
1930,
|
||||||
|
1702,
|
||||||
|
1454,
|
||||||
|
1197,
|
||||||
|
941,
|
||||||
|
697,
|
||||||
|
474,
|
||||||
|
277,
|
||||||
|
112,
|
||||||
|
-19,
|
||||||
|
-117,
|
||||||
|
-181,
|
||||||
|
-217,
|
||||||
|
-228,
|
||||||
|
-220,
|
||||||
|
-198,
|
||||||
|
-167,
|
||||||
|
-132,
|
||||||
|
-98,
|
||||||
|
-66,
|
||||||
|
-39,
|
||||||
|
-18,
|
||||||
|
-2,
|
||||||
|
9,
|
||||||
|
15,
|
||||||
|
20,
|
||||||
|
22,
|
||||||
|
24,
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr fir_taps_real<63> taps_1k5_dsb_lpf = {
|
||||||
|
.low_frequency_normalized = -1500.0f / 48000.0f,
|
||||||
|
.high_frequency_normalized = 1500.0f / 48000.0f,
|
||||||
|
.transition_normalized = 1000.0f / 48000.0f,
|
||||||
|
.taps = {{
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
-5,
|
||||||
|
-10,
|
||||||
|
-19,
|
||||||
|
-31,
|
||||||
|
-46,
|
||||||
|
-64,
|
||||||
|
-82,
|
||||||
|
-99,
|
||||||
|
-111,
|
||||||
|
-113,
|
||||||
|
-100,
|
||||||
|
-64,
|
||||||
|
0,
|
||||||
|
99,
|
||||||
|
239,
|
||||||
|
424,
|
||||||
|
655,
|
||||||
|
932,
|
||||||
|
1251,
|
||||||
|
1605,
|
||||||
|
1983,
|
||||||
|
2372,
|
||||||
|
2757,
|
||||||
|
3120,
|
||||||
|
3447,
|
||||||
|
3719,
|
||||||
|
3925,
|
||||||
|
4053,
|
||||||
|
4096,
|
||||||
|
4053,
|
||||||
|
3925,
|
||||||
|
3719,
|
||||||
|
3447,
|
||||||
|
3120,
|
||||||
|
2757,
|
||||||
|
2372,
|
||||||
|
1983,
|
||||||
|
1605,
|
||||||
|
1251,
|
||||||
|
932,
|
||||||
|
655,
|
||||||
|
424,
|
||||||
|
239,
|
||||||
|
99,
|
||||||
|
0,
|
||||||
|
-64,
|
||||||
|
-100,
|
||||||
|
-113,
|
||||||
|
-111,
|
||||||
|
-99,
|
||||||
|
-82,
|
||||||
|
-64,
|
||||||
|
-46,
|
||||||
|
-31,
|
||||||
|
-19,
|
||||||
|
-10,
|
||||||
|
-5,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
// GAIN: 4.0x
|
||||||
|
static constexpr fir_taps_complex<63> taps_1k5_USB_channel = {
|
||||||
|
.low_frequency_normalized = 300.0f / 48000.0f,
|
||||||
|
.high_frequency_normalized = 1800.0f / 48000.0f,
|
||||||
|
.transition_normalized = 300.0f / 48000.0f,
|
||||||
|
.taps = {{
|
||||||
|
{0, 0},
|
||||||
|
{0, 0},
|
||||||
|
{2, 0},
|
||||||
|
{4, 2},
|
||||||
|
{10, 6},
|
||||||
|
{17, 14},
|
||||||
|
{27, 29},
|
||||||
|
{37, 53},
|
||||||
|
{45, 89},
|
||||||
|
{49, 140},
|
||||||
|
{41, 207},
|
||||||
|
{17, 290},
|
||||||
|
{-31, 389},
|
||||||
|
{-109, 499},
|
||||||
|
{-227, 614},
|
||||||
|
{-387, 725},
|
||||||
|
{-595, 819},
|
||||||
|
{-850, 884},
|
||||||
|
{-1147, 904},
|
||||||
|
{-1477, 866},
|
||||||
|
{-1827, 757},
|
||||||
|
{-2179, 569},
|
||||||
|
{-2512, 297},
|
||||||
|
{-2804, -55},
|
||||||
|
{-3031, -480},
|
||||||
|
{-3173, -962},
|
||||||
|
{-3213, -1481},
|
||||||
|
{-3142, -2011},
|
||||||
|
{-2955, -2524},
|
||||||
|
{-2658, -2991},
|
||||||
|
{-2262, -3386},
|
||||||
|
{-1788, -3685},
|
||||||
|
{-1258, -3873},
|
||||||
|
{-703, -3939},
|
||||||
|
{-153, -3884},
|
||||||
|
{366, -3713},
|
||||||
|
{826, -3440},
|
||||||
|
{1208, -3087},
|
||||||
|
{1499, -2677},
|
||||||
|
{1693, -2236},
|
||||||
|
{1789, -1789},
|
||||||
|
{1796, -1359},
|
||||||
|
{1726, -966},
|
||||||
|
{1594, -624},
|
||||||
|
{1420, -341},
|
||||||
|
{1220, -120},
|
||||||
|
{1012, 40},
|
||||||
|
{809, 144},
|
||||||
|
{622, 202},
|
||||||
|
{460, 223},
|
||||||
|
{324, 217},
|
||||||
|
{217, 193},
|
||||||
|
{137, 160},
|
||||||
|
{80, 125},
|
||||||
|
{42, 91},
|
||||||
|
{19, 62},
|
||||||
|
{6, 39},
|
||||||
|
{0, 22},
|
||||||
|
{-1, 11},
|
||||||
|
{-1, 5},
|
||||||
|
{-1, 1},
|
||||||
|
{0, 0},
|
||||||
|
{0, 0},
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
// GAIN: 4.0x
|
||||||
|
static constexpr fir_taps_complex<63> taps_1k5_LSB_channel = {
|
||||||
|
.low_frequency_normalized = 300.0f / 48000.0f,
|
||||||
|
.high_frequency_normalized = 1800.0f / 48000.0f,
|
||||||
|
.transition_normalized = 300.0f / 48000.0f,
|
||||||
|
.taps = {{
|
||||||
|
{0, 0},
|
||||||
|
{0, 0},
|
||||||
|
{2, 0},
|
||||||
|
{4, -2},
|
||||||
|
{10, -6},
|
||||||
|
{17, -14},
|
||||||
|
{27, -29},
|
||||||
|
{37, -53},
|
||||||
|
{45, -89},
|
||||||
|
{49, -140},
|
||||||
|
{41, -207},
|
||||||
|
{17, -290},
|
||||||
|
{-31, -389},
|
||||||
|
{-109, -499},
|
||||||
|
{-227, -614},
|
||||||
|
{-387, -725},
|
||||||
|
{-595, -819},
|
||||||
|
{-850, -884},
|
||||||
|
{-1147, -904},
|
||||||
|
{-1477, -866},
|
||||||
|
{-1827, -757},
|
||||||
|
{-2179, -569},
|
||||||
|
{-2512, -297},
|
||||||
|
{-2804, 55},
|
||||||
|
{-3031, 480},
|
||||||
|
{-3173, 962},
|
||||||
|
{-3213, 1481},
|
||||||
|
{-3142, 2011},
|
||||||
|
{-2955, 2524},
|
||||||
|
{-2658, 2991},
|
||||||
|
{-2262, 3386},
|
||||||
|
{-1788, 3685},
|
||||||
|
{-1258, 3873},
|
||||||
|
{-703, 3939},
|
||||||
|
{-153, 3884},
|
||||||
|
{366, 3713},
|
||||||
|
{826, 3440},
|
||||||
|
{1208, 3087},
|
||||||
|
{1499, 2677},
|
||||||
|
{1693, 2236},
|
||||||
|
{1789, 1789},
|
||||||
|
{1796, 1359},
|
||||||
|
{1726, 966},
|
||||||
|
{1594, 624},
|
||||||
|
{1420, 341},
|
||||||
|
{1220, 120},
|
||||||
|
{1012, -40},
|
||||||
|
{809, -144},
|
||||||
|
{622, -202},
|
||||||
|
{460, -223},
|
||||||
|
{324, -217},
|
||||||
|
{217, -193},
|
||||||
|
{137, -160},
|
||||||
|
{80, -125},
|
||||||
|
{42, -91},
|
||||||
|
{19, -62},
|
||||||
|
{6, -39},
|
||||||
|
{0, -22},
|
||||||
|
{-1, -11},
|
||||||
|
{-1, -5},
|
||||||
|
{-1, -1},
|
||||||
|
{0, 0},
|
||||||
|
{0, 0},
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
#endif /*__DSP_FIR_TAPS_H__*/
|
#endif /*__DSP_FIR_TAPS_H__*/
|
||||||
|
|||||||
@@ -1711,6 +1711,7 @@ class MorseRXDataMessage : public Message {
|
|||||||
: Message{ID::MorseRXData} {}
|
: Message{ID::MorseRXData} {}
|
||||||
int32_t state_durations[5] = {0}; // positive: high, negative: low
|
int32_t state_durations[5] = {0}; // positive: high, negative: low
|
||||||
uint8_t state_cnt = 0;
|
uint8_t state_cnt = 0;
|
||||||
|
bool clipped = false;
|
||||||
const uint8_t maxptr = 4;
|
const uint8_t maxptr = 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user