diff --git a/firmware/application/apps/ui_level.cpp b/firmware/application/apps/ui_level.cpp index 278e5762..82d18a3f 100644 --- a/firmware/application/apps/ui_level.cpp +++ b/firmware/application/apps/ui_level.cpp @@ -35,6 +35,25 @@ using portapack::memory::map::backup_ram; namespace ui { +void LevelView::m4_manage_stat_update() { + if (audio_mode) { + if (radio_mode == WFM_MODULATION || radio_mode == SPEC_MODULATION) { + shared_memory.request_m4_performance_counter = 0; + } else { + shared_memory.request_m4_performance_counter = 2; + } + if (radio_mode == SPEC_MODULATION) { + beep = true; + } + } else { + shared_memory.request_m4_performance_counter = 2; + if (radio_mode == SPEC_MODULATION) { + beep = false; + baseband::request_beep_stop(); + } + } +} + void LevelView::focus() { button_frequency.focus(); } @@ -62,18 +81,15 @@ LevelView::LevelView(NavigationView& nav) &freq_stats_rssi, &freq_stats_db, &freq_stats_rx, - &audio_mode, + &text_beep_squelch, + &field_beep_squelch, + &field_audio_mode, &peak_mode, &rssi, &rssi_graph}); // activate vertical bar mode rssi.set_vertical_rssi(true); - // activate counters for RxSat - shared_memory.request_m4_performance_counter = 2; - - change_mode(NFM_MODULATION); // Start on AM - field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector freq_ = receiver_model.target_frequency(); button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>"); @@ -87,6 +103,11 @@ LevelView::LevelView(NavigationView& nav) }; }; + field_beep_squelch.set_value(beep_squelch); + field_beep_squelch.on_change = [this](int32_t v) { + beep_squelch = v; + }; + button_frequency.on_change = [this]() { int64_t def_step = freqman_entry_get_step_value(step_mode.selected_index()); freq_ = freq_ + (button_frequency.get_encoder_delta() * def_step); @@ -102,17 +123,14 @@ LevelView::LevelView(NavigationView& nav) button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>"); }; + freqman_set_modulation_option(field_mode); field_mode.on_change = [this](size_t, OptionsField::value_t v) { if (v != -1) { - receiver_model.disable(); - baseband::shutdown(); change_mode(v); - if (audio_mode.selected_index() != 0) { - audio::output::start(); - } - receiver_model.enable(); } }; + field_mode.set_by_value(radio_mode); // Reflect the mode into the manual selector + field_bw.set_selected_index(radio_bw); rssi_resolution.on_change = [this](size_t, OptionsField::value_t v) { if (v != -1) { @@ -120,15 +138,18 @@ LevelView::LevelView(NavigationView& nav) } }; - audio_mode.on_change = [this](size_t, OptionsField::value_t v) { + field_audio_mode.on_change = [this](size_t, OptionsField::value_t v) { + audio_mode = v; if (v == 0) { audio::output::stop(); } else if (v == 1) { + audio::set_rate(audio_sampling_rate); audio::output::start(); receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack. - } else { } + m4_manage_stat_update(); // rx_sat hack }; + field_audio_mode.set_selected_index(audio_mode); peak_mode.on_change = [this](size_t, OptionsField::value_t v) { if (v == 0) { @@ -142,7 +163,6 @@ LevelView::LevelView(NavigationView& nav) peak_mode.set_selected_index(2); rssi_resolution.set_selected_index(1); // FILL STEP OPTIONS - freqman_set_modulation_option(field_mode); freqman_set_step_option_short(step_mode); freq_stats_rssi.set_style(&Styles::white); freq_stats_db.set_style(&Styles::white); @@ -168,13 +188,28 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) { last_min_rssi = rssi_graph.get_graph_min(); last_avg_rssi = rssi_graph.get_graph_avg(); last_max_rssi = rssi_graph.get_graph_max(); - freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi) + ", dt: " + to_string_dec_uint(rssi_graph.get_graph_delta())); + freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi)); } + + if (beep && statistics.max_db > beep_squelch) { + baseband::request_audio_beep(((132 + statistics.max_db) * 2000) / 120, 24000, 250); + } + // refresh sat + if (radio_mode == SPEC_MODULATION || (radio_mode == WFM_MODULATION && audio_mode == 1)) { + Style style_freq_stats_rx{ + .font = font::fixed_8x16, + .background = {55, 55, 55}, + .foreground = {155, 155, 155}, + }; + freq_stats_rx.set_style(&style_freq_stats_rx); + freq_stats_rx.set("RxSat off"); + return; + } uint8_t rx_sat = ((uint32_t)shared_memory.m4_performance_counter) * 100 / 127; if (last_rx_sat != rx_sat) { last_rx_sat = rx_sat; - freq_stats_rx.set("RxSat: " + to_string_dec_uint(rx_sat) + "%"); + uint8_t br = 0; uint8_t bg = 0; uint8_t bb = 0; @@ -191,6 +226,7 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) { .foreground = {255, 255, 255}, }; freq_stats_rx.set_style(&style_freq_stats_rx); + freq_stats_rx.set("RxSat: " + to_string_dec_uint(rx_sat) + "%"); } } /* on_statistic_updates */ @@ -198,50 +234,59 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) { size_t LevelView::change_mode(freqman_index_t new_mod) { field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; }; + radio_mode = new_mod; + + audio::output::stop(); + receiver_model.disable(); + baseband::shutdown(); + switch (new_mod) { case AM_MODULATION: + audio_sampling_rate = audio::Rate::Hz_12000; freqman_set_bandwidth_option(new_mod, field_bw); baseband::run_image(portapack::spi_flash::image_tag_am_audio); receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); - receiver_model.set_am_configuration(field_bw.selected_index_value()); - field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); }; // bw DSB (0) default field_bw.set_by_value(0); - text_ctcss.set(" "); + receiver_model.set_am_configuration(0); + field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_am_configuration(n); }; break; case NFM_MODULATION: + audio_sampling_rate = audio::Rate::Hz_24000; freqman_set_bandwidth_option(new_mod, field_bw); baseband::run_image(portapack::spi_flash::image_tag_nfm_audio); receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); receiver_model.set_nbfm_configuration(field_bw.selected_index_value()); - field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); }; // bw 16k (2) default field_bw.set_by_value(2); + field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_nbfm_configuration(n); }; break; case WFM_MODULATION: + audio_sampling_rate = audio::Rate::Hz_48000; freqman_set_bandwidth_option(new_mod, field_bw); baseband::run_image(portapack::spi_flash::image_tag_wfm_audio); receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio); receiver_model.set_wfm_configuration(field_bw.selected_index_value()); - field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); }; - // bw 200k (0) only/default + // bw 200k (0) default field_bw.set_by_value(0); - text_ctcss.set(" "); + field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_wfm_configuration(n); }; break; case SPEC_MODULATION: + audio_sampling_rate = audio::Rate::Hz_24000; freqman_set_bandwidth_option(new_mod, field_bw); baseband::run_image(portapack::spi_flash::image_tag_capture); receiver_model.set_modulation(ReceiverModel::Mode::Capture); - field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) { + // 12k5 (0) default + field_bw.set_by_value(0); + field_bw.on_change = [this](size_t index, OptionsField::value_t sampling_rate) { + radio_bw = index; // Baseband needs to know the desired sampling and oversampling rates. baseband::set_sample_rate(sampling_rate, get_oversample_rate(sampling_rate)); - // The radio needs to know the effective sampling rate. auto actual_sampling_rate = get_actual_sample_rate(sampling_rate); receiver_model.set_sampling_rate(actual_sampling_rate); receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate)); }; - field_bw.set_by_value(0); default: break; } @@ -250,6 +295,18 @@ size_t LevelView::change_mode(freqman_index_t new_mod) { receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(1750000); } + if (new_mod != NFM_MODULATION) { + text_ctcss.set(" "); + } + + m4_manage_stat_update(); // rx_sat hack + + if (audio_mode) { + audio::set_rate(audio_sampling_rate); + audio::output::start(); + receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack. + } + receiver_model.enable(); return step_mode.selected_index(); } diff --git a/firmware/application/apps/ui_level.hpp b/firmware/application/apps/ui_level.hpp index 0f9681e8..86862a6c 100644 --- a/firmware/application/apps/ui_level.hpp +++ b/firmware/application/apps/ui_level.hpp @@ -55,14 +55,29 @@ class LevelView : public View { NavigationView& nav_; RxRadioState radio_state_{}; - app_settings::SettingsManager settings_{ - "rx_level", app_settings::Mode::RX}; size_t change_mode(freqman_index_t mod_type); void on_statistics_update(const ChannelStatistics& statistics); void set_display_freq(int64_t freq); + void m4_manage_stat_update(); // to finely adjust the RxSaturation usage rf::Frequency freq_ = {0}; + bool beep = false; + uint8_t radio_mode = 0; + uint8_t radio_bw = 0; + uint8_t audio_mode = 0; + int32_t beep_squelch = 0; + audio::Rate audio_sampling_rate = audio::Rate::Hz_48000; + + app_settings::SettingsManager settings_{ + "rx_level", + app_settings::Mode::RX, + { + {"beep_squelch"sv, &beep_squelch}, + {"audio_mode"sv, &audio_mode}, + {"radio_mode"sv, &radio_mode}, + {"radio_bw"sv, &radio_bw}, + }}; Labels labels{ {{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()}, @@ -100,23 +115,27 @@ class LevelView : public View { {0 * 8, 2 * 16 + 8, 15 * 8, 1 * 8}, ""}; - OptionsField audio_mode{ + OptionsField field_audio_mode{ {21 * 8, 1 * 16}, 9, - { - {"audio off", 0}, - {"audio on", 1} - //{"tone on", 2}, - //{"tone off", 3}, - }}; + {{"audio off", 0}, + {"audio on", 1}}}; - Text text_ctcss{ - {22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8}, - ""}; + Text text_beep_squelch{ + {21 * 8, 3 * 16 + 4, 4 * 8, 1 * 8}, + "Bip>"}; - // RSSI: XX/XX/XXX,dt: XX + NumberField field_beep_squelch{ + {25 * 8, 3 * 16 + 4}, + 3, + {-120, 12}, + 1, + ' ', + }; + + // RSSI: XX/XX/XXX Text freq_stats_rssi{ - {0 * 8, 3 * 16 + 4, 22 * 8, 1 * 16}, + {0 * 8, 3 * 16 + 4, 15 * 8, 1 * 16}, }; // Power: -XXX db @@ -153,14 +172,18 @@ class LevelView : public View { {0 * 8, 5 * 16 + 4, 10 * 8, 1 * 16}, }; + Text text_ctcss{ + {12 * 8, 5 * 16 + 4, 8 * 8, 1 * 8}, + ""}; + RSSIGraph rssi_graph{ // 240x320 => - {0, 6 * 16 + 4, 240 - 5 * 8, 320 - (6 * 16 + 4)}, + {0, 6 * 16 + 8, 240 - 5 * 8, 320 - (6 * 16)}, }; RSSI rssi{ // 240x320 => - {240 - 5 * 8, 6 * 16 + 4, 5 * 8, 320 - (6 * 16 + 4)}, + {240 - 5 * 8, 6 * 16 + 8, 5 * 8, 320 - (6 * 16)}, }; void handle_coded_squelch(const uint32_t value); diff --git a/firmware/baseband/proc_capture.cpp b/firmware/baseband/proc_capture.cpp index c29255f1..a9f585cb 100644 --- a/firmware/baseband/proc_capture.cpp +++ b/firmware/baseband/proc_capture.cpp @@ -21,7 +21,7 @@ */ #include "proc_capture.hpp" - +#include "audio_dma.hpp" #include "dsp_fir_taps.hpp" #include "event_m4.hpp" #include "utility.hpp" @@ -55,6 +55,16 @@ void CaptureProcessor::execute(const buffer_c8_t& buffer) { } } +void CaptureProcessor::on_signal_message(const RequestSignalMessage& message) { + if (message.signal == RequestSignalMessage::Signal::BeepStopRequest) { + audio::dma::beep_stop(); + } +} + +void CaptureProcessor::on_beep_message(const AudioBeepMessage& message) { + audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms); +} + void CaptureProcessor::on_message(const Message* const message) { switch (message->id) { case Message::ID::UpdateSpectrum: @@ -70,6 +80,14 @@ void CaptureProcessor::on_message(const Message* const message) { capture_config(*reinterpret_cast(message)); break; + case Message::ID::RequestSignal: + on_signal_message(*reinterpret_cast(message)); + break; + + case Message::ID::AudioBeep: + on_beep_message(*reinterpret_cast(message)); + break; + default: break; } @@ -152,7 +170,8 @@ void CaptureProcessor::capture_config(const CaptureConfigMessage& message) { } int main() { + audio::dma::init_audio_out(); EventDispatcher event_dispatcher{std::make_unique()}; event_dispatcher.run(); return 0; -} \ No newline at end of file +} diff --git a/firmware/baseband/proc_capture.hpp b/firmware/baseband/proc_capture.hpp index de51e23c..7df220d2 100644 --- a/firmware/baseband/proc_capture.hpp +++ b/firmware/baseband/proc_capture.hpp @@ -30,6 +30,7 @@ #include "dsp_decimate.hpp" #include "spectrum_collector.hpp" #include "stream_input.hpp" +#include "message.hpp" #include #include @@ -92,6 +93,9 @@ class CaptureProcessor : public BasebandProcessor { void on_message(const Message* const message) override; private: + void on_signal_message(const RequestSignalMessage& message); + void on_beep_message(const AudioBeepMessage& message); + size_t baseband_fs = 3072000; // aka: sample_rate static constexpr auto spectrum_rate_hz = 50.0f; diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index c6f2718a..e116aa3f 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -134,7 +134,7 @@ class Message { }; struct RSSIStatistics { - uint16_t accumulator{0}; + uint32_t accumulator{0}; uint8_t min{0}; uint8_t max{0}; uint16_t count{0};