diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index d3be5c00f..65d2eb872 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -470,6 +470,18 @@ void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start) { } static bool baseband_image_running = false; +static bool rx_fs4_supported = false; + +bool supports_rx_fs4() { + return baseband_image_running && rx_fs4_supported; +} + +void set_rx_fs4_direction(RxFs4Direction direction) { + if (supports_rx_fs4()) { + const RxFs4ConfigMessage message{direction}; + send_message(&message); + } +} bool is_image_running() { return baseband_image_running; @@ -484,6 +496,10 @@ void run_image(const spi_flash::image_tag_t image_tag, bool enforce_core_sync) { shared_memory.clear_baseband_ready(); m4_init(image_tag, memory::map::m4_code, false); + rx_fs4_supported = image_tag == spi_flash::image_tag_am_audio || + image_tag == spi_flash::image_tag_nfm_audio || + image_tag == spi_flash::image_tag_wfm_audio || + image_tag == spi_flash::image_tag_capture; baseband_image_running = true; creg::m4txevent::enable(); @@ -499,7 +515,8 @@ void run_image(const spi_flash::image_tag_t image_tag, bool enforce_core_sync) { } } -void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync) { +void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync, const spi_flash::image_tag_t prepared_image_tag) { + rx_fs4_supported = false; if (baseband_image_running) { chDbgPanic("BBRunning"); } @@ -508,6 +525,8 @@ void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync) { shared_memory.clear_baseband_ready(); m4_init_prepared(m4_code, false); + // Only explicitly identified Capture images support application FS4 control. + rx_fs4_supported = prepared_image_tag == spi_flash::image_tag_capture; baseband_image_running = true; creg::m4txevent::enable(); @@ -524,6 +543,7 @@ void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync) { } void shutdown() { + rx_fs4_supported = false; if (!baseband_image_running) { return; } diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index 4083859ae..c765c3d36 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -132,8 +132,10 @@ void request_beep_stop(); void request_audio_beep(uint32_t freq, uint32_t sample_rate, uint32_t duration_ms); bool is_image_running(); +bool supports_rx_fs4(); +void set_rx_fs4_direction(RxFs4Direction direction); void run_image(const portapack::spi_flash::image_tag_t image_tag, bool enforce_core_sync = true); -void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync = true); +void run_prepared_image(const uint32_t m4_code, bool enforce_core_sync = true, const portapack::spi_flash::image_tag_t prepared_image_tag = portapack::spi_flash::image_tag_none); void shutdown(); void spectrum_streaming_start(); diff --git a/firmware/application/external/waterfall_designer/ui_waterfall_designer.cpp b/firmware/application/external/waterfall_designer/ui_waterfall_designer.cpp index 34916ac44..284a2e4c3 100644 --- a/firmware/application/external/waterfall_designer/ui_waterfall_designer.cpp +++ b/firmware/application/external/waterfall_designer/ui_waterfall_designer.cpp @@ -71,7 +71,8 @@ bool is_color_level(const std::string& line) { WaterfallDesignerView::WaterfallDesignerView(NavigationView& nav) : nav_{nav} { - baseband::run_prepared_image(portapack::memory::map::m4_code.base()); + baseband::run_prepared_image(portapack::memory::map::m4_code.base(), true, + portapack::spi_flash::image_tag_capture); add_children({&labels, &field_frequency, diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index 638a2a3a1..93c1e6a10 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -334,6 +334,8 @@ void ReceiverModel::disable() { void ReceiverModel::initialize() { settings_ = settings_t{}; + hidden_offset = 0; + application_fs4_direction_ = RxFs4Direction::Down; am_spectrum_zoom_ = spectrum_zoom_for_am_config(settings_.am_config_index); } @@ -369,14 +371,29 @@ int32_t ReceiverModel::tuning_offset() { if ((modulation() == Mode::SpectrumAnalysis)) { return 0; } else { - return -(sampling_rate() / 4); + const int32_t quarter_rate = static_cast(sampling_rate() / 4); + const rf::Frequency effective_frequency = target_frequency() + hidden_offset; + return baseband::supports_rx_fs4() && effective_frequency - quarter_rate < 0 + ? quarter_rate + : -quarter_rate; } } void ReceiverModel::update_tuning_frequency() { - // TODO: use positive offset if freq < offset. if (enabled_) { - radio::set_tuning_frequency(target_frequency() + hidden_offset + tuning_offset()); + const auto offset = tuning_offset(); + if (!radio::set_tuning_frequency(target_frequency() + hidden_offset + offset)) + return; + + if (modulation() != Mode::SpectrumAnalysis && baseband::supports_rx_fs4()) { + application_fs4_direction_ = offset > 0 ? RxFs4Direction::Up : RxFs4Direction::Down; + // Re-send on enable too: the active processor may have restarted. + baseband::set_rx_fs4_direction(application_fs4_direction_); + } +#ifdef PRALINE + // A tune can change the FPGA shift (including the special zero entry). + update_baseband_bandwidth(); +#endif } } diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index 2d24859b1..52d740fe7 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -149,6 +149,7 @@ class ReceiverModel { AMConfigureMessage::Zoom_waterfall am_spectrum_zoom_{AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}; settings_t settings_{}; bool enabled_ = false; + RxFs4Direction application_fs4_direction_{RxFs4Direction::Down}; rf::Frequency hidden_offset = 0; // when we need to hide the offset from user, we set this. like when WeFax needs -300Hz. int32_t tuning_offset(); diff --git a/firmware/baseband/proc_am_audio.cpp b/firmware/baseband/proc_am_audio.cpp index 2e612a0f8..48d60403e 100644 --- a/firmware/baseband/proc_am_audio.cpp +++ b/firmware/baseband/proc_am_audio.cpp @@ -45,6 +45,10 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) { return; } + // Only the processing thread applies frequency-only direction changes. + if (requested_fs4_direction_.load(std::memory_order_relaxed) != applied_fs4_direction_) + configure_fs4(decim_0_taps_); + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); const auto audio_decim_0_out = audio_decim_0.execute(decim_0_out, dst_buffer); @@ -125,8 +129,26 @@ buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) { } } +void NarrowbandAMAudio::configure_fs4(const std::array& taps) { + // Keep the short first-stage update coherent with the processing thread. + chSysLock(); + decim_0_taps_ = taps; + const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed); + using Shift = dsp::decimate::FIRC8xR16x24FS4Decim4::Shift; + decim_0.configure(decim_0_taps_, 33554432, + direction == RxFs4Direction::Up ? Shift::Up : Shift::Down); + applied_fs4_direction_ = direction; + chSysUnlock(); +} + void NarrowbandAMAudio::on_message(const Message* const message) { switch (message->id) { + case Message::ID::RxFs4Config: + requested_fs4_direction_.store( + static_cast(message)->direction, + std::memory_order_relaxed); + break; + case Message::ID::UpdateSpectrum: case Message::ID::SpectrumStreamingConfig: channel_spectrum.on_message(message); @@ -162,7 +184,7 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) { constexpr size_t channel_filter_input_fs = decim_2_output_fs; // const size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor; - decim_0.configure(message.decim_0_filter.taps, 33554432); + configure_fs4(message.decim_0_filter.taps); audio_decim_0.configure(taps_audio_wide_halfband_0.taps); translating_decim_1.configure( message.decim_1_filter.taps, audio_decim_0_output_fs); diff --git a/firmware/baseband/proc_am_audio.hpp b/firmware/baseband/proc_am_audio.hpp index 16e510465..ada6ceae3 100644 --- a/firmware/baseband/proc_am_audio.hpp +++ b/firmware/baseband/proc_am_audio.hpp @@ -34,6 +34,7 @@ #include "audio_output.hpp" #include "filtered_spectrum_collector.hpp" +#include #include class NarrowbandAMAudio : public BasebandProcessor { @@ -59,6 +60,10 @@ class NarrowbandAMAudio : public BasebandProcessor { audio.size()}; dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; + std::array decim_0_taps_{}; + std::atomic requested_fs4_direction_{RxFs4Direction::Down}; + RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down}; + void configure_fs4(const std::array& taps); dsp::decimate::FIRC16xR16x16Decim2 audio_decim_0{}; dsp::FrequencyTranslatingDecimator32By8 translating_decim_1{}; dsp::decimate::FIRAndDecimateComplex decim_2{}; diff --git a/firmware/baseband/proc_capture.cpp b/firmware/baseband/proc_capture.cpp index a9f585cbd..b80f72dcd 100644 --- a/firmware/baseband/proc_capture.cpp +++ b/firmware/baseband/proc_capture.cpp @@ -34,6 +34,12 @@ CaptureProcessor::CaptureProcessor() { } void CaptureProcessor::execute(const buffer_c8_t& buffer) { + const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed); + if (direction != applied_fs4_direction_) { + decim_0.configure_fs4(decim_0_taps_, direction); + applied_fs4_direction_ = direction; + } + auto decim_0_out = decim_0.execute(buffer, dst_buffer); auto out_buffer = decim_1.execute(decim_0_out, dst_buffer); @@ -67,6 +73,12 @@ void CaptureProcessor::on_beep_message(const AudioBeepMessage& message) { void CaptureProcessor::on_message(const Message* const message) { switch (message->id) { + case Message::ID::RxFs4Config: + requested_fs4_direction_.store( + static_cast(message)->direction, + std::memory_order_relaxed); + break; + case Message::ID::UpdateSpectrum: case Message::ID::SpectrumStreamingConfig: channel_spectrum.on_message(message); @@ -125,34 +137,34 @@ void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message switch (message.oversample_rate) { case OversampleRate::x4: // M4 can't handle 2 decimation passes for sample rates needing x4. - decim_0.set().configure(taps_200k_decim_0.taps); + configure_fs4(taps_200k_decim_0.taps); decim_1.set(); break; case OversampleRate::x8: // M4 can't handle 2 decimation passes for sample rates <= 600k. if (message.sample_rate < 600'000) { - decim_0.set().configure(taps_200k_decim_0.taps); + configure_fs4(taps_200k_decim_0.taps); decim_1.set().configure(taps_200k_decim_1.taps); } else { // Using 180k taps to provide better filtering with a single pass. - decim_0.set().configure(taps_180k_wfm_decim_0.taps); + configure_fs4(taps_180k_wfm_decim_0.taps); decim_1.set(); } break; case OversampleRate::x16: - decim_0.set().configure(taps_200k_decim_0.taps); + configure_fs4(taps_200k_decim_0.taps); decim_1.set().configure(taps_200k_decim_1.taps); break; case OversampleRate::x32: - decim_0.set().configure(taps_200k_decim_0.taps); + configure_fs4(taps_200k_decim_0.taps); decim_1.set().configure(taps_16k0_decim_1.taps); break; case OversampleRate::x64: - decim_0.set().configure(taps_200k_decim_0.taps); + configure_fs4(taps_200k_decim_0.taps); decim_1.set().configure(taps_16k0_decim_1.taps); break; diff --git a/firmware/baseband/proc_capture.hpp b/firmware/baseband/proc_capture.hpp index 7df220d23..7fd607008 100644 --- a/firmware/baseband/proc_capture.hpp +++ b/firmware/baseband/proc_capture.hpp @@ -33,6 +33,8 @@ #include "message.hpp" #include +#include +#include #include #include #include @@ -65,6 +67,16 @@ class MultiDecimator { decimator_); } + // Reconfigure only the active first-stage variant; preserve its factor. + void configure_fs4(const std::array& taps, RxFs4Direction direction) { + std::visit([&](auto& decimator) { + using Shift = typename std::decay_t::Shift; + decimator.configure(taps, dsp::decimate::c8_to_c32_sat_scalar, + direction == RxFs4Direction::Up ? Shift::Up : Shift::Down); + }, + decimator_); + } + size_t decimation_factor() const { return std::visit( [](auto&& arg) -> size_t { @@ -125,6 +137,21 @@ class CaptureProcessor : public BasebandProcessor { size_t spectrum_interval_samples = 0; size_t spectrum_samples = 0; + std::array decim_0_taps_{}; + std::atomic requested_fs4_direction_{RxFs4Direction::Down}; + RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down}; + + template + void configure_fs4(const std::array& taps) { + // Variant selection, taps and direction must change together. + chSysLock(); + decim_0.set(); + decim_0_taps_ = taps; + applied_fs4_direction_ = requested_fs4_direction_.load(std::memory_order_relaxed); + decim_0.configure_fs4(decim_0_taps_, applied_fs4_direction_); + chSysUnlock(); + } + /* NB: Threads should be the last members in the class definition. */ BasebandThread baseband_thread{ baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false}; diff --git a/firmware/baseband/proc_nfm_audio.cpp b/firmware/baseband/proc_nfm_audio.cpp index a1dfc40d5..478705b30 100644 --- a/firmware/baseband/proc_nfm_audio.cpp +++ b/firmware/baseband/proc_nfm_audio.cpp @@ -50,6 +50,10 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) { return; } + // Only the processing thread applies frequency-only direction changes. + if (requested_fs4_direction_.load(std::memory_order_relaxed) != applied_fs4_direction_) + configure_fs4(decim_0_taps_); + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); const auto audio_decim_0_out = audio_decim_0.execute(decim_0_out, dst_buffer); @@ -137,8 +141,26 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) { } } +void NarrowbandFMAudio::configure_fs4(const std::array& taps) { + // Keep the short first-stage update coherent with the processing thread. + chSysLock(); + decim_0_taps_ = taps; + const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed); + using Shift = dsp::decimate::FIRC8xR16x24FS4Decim4::Shift; + decim_0.configure(decim_0_taps_, 33554432, + direction == RxFs4Direction::Up ? Shift::Up : Shift::Down); + applied_fs4_direction_ = direction; + chSysUnlock(); +} + void NarrowbandFMAudio::on_message(const Message* const message) { switch (message->id) { + case Message::ID::RxFs4Config: + requested_fs4_direction_.store( + static_cast(message)->direction, + std::memory_order_relaxed); + break; + case Message::ID::UpdateSpectrum: case Message::ID::SpectrumStreamingConfig: channel_spectrum.on_message(message); @@ -177,7 +199,7 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) { const size_t demod_input_fs = channel_filter_output_fs; - decim_0.configure(message.decim_0_filter.taps, 33554432); + configure_fs4(message.decim_0_filter.taps); audio_decim_0.configure(taps_audio_wide_halfband_0.taps); translating_decim_1.configure( message.decim_1_filter.taps, audio_decim_0_output_fs); diff --git a/firmware/baseband/proc_nfm_audio.hpp b/firmware/baseband/proc_nfm_audio.hpp index 2b44335a7..0ee69910c 100644 --- a/firmware/baseband/proc_nfm_audio.hpp +++ b/firmware/baseband/proc_nfm_audio.hpp @@ -35,6 +35,7 @@ #include "audio_output.hpp" #include "filtered_spectrum_collector.hpp" +#include #include #define Z_MIN_FILTER_COUNT 224 @@ -72,6 +73,10 @@ class NarrowbandFMAudio : public BasebandProcessor { sizeof(tone) / sizeof(int16_t)}; dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; + std::array decim_0_taps_{}; + std::atomic requested_fs4_direction_{RxFs4Direction::Down}; + RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down}; + void configure_fs4(const std::array& taps); dsp::decimate::FIRC16xR16x16Decim2 audio_decim_0{}; dsp::FrequencyTranslatingDecimator32By8 translating_decim_1{}; dsp::decimate::FIRAndDecimateComplex channel_filter{}; diff --git a/firmware/baseband/proc_wfm_audio.cpp b/firmware/baseband/proc_wfm_audio.cpp index 4f43186d2..f2e2b4c34 100644 --- a/firmware/baseband/proc_wfm_audio.cpp +++ b/firmware/baseband/proc_wfm_audio.cpp @@ -35,6 +35,10 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) { return; } + // Only the processing thread applies frequency-only direction changes. + if (requested_fs4_direction_.load(std::memory_order_relaxed) != applied_fs4_direction_) + configure_fs4(decim_0_taps_); + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); const auto channel = decim_1.execute(decim_0_out, dst_buffer); @@ -159,8 +163,26 @@ void WidebandFMAudio::post_message(const buffer_c16_t& data) { fft_step = 0; } +void WidebandFMAudio::configure_fs4(const std::array& taps) { + // Keep the short first-stage update coherent with the processing thread. + chSysLock(); + decim_0_taps_ = taps; + const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed); + using Shift = dsp::decimate::FIRC8xR16x24FS4Decim4::Shift; + decim_0.configure(decim_0_taps_, dsp::decimate::c8_to_c32_sat_scalar, + direction == RxFs4Direction::Up ? Shift::Up : Shift::Down); + applied_fs4_direction_ = direction; + chSysUnlock(); +} + void WidebandFMAudio::on_message(const Message* const message) { switch (message->id) { + case Message::ID::RxFs4Config: + requested_fs4_direction_.store( + static_cast(message)->direction, + std::memory_order_relaxed); + break; + case Message::ID::UpdateSpectrum: case Message::ID::SpectrumStreamingConfig: channel_spectrum.on_message(message); @@ -188,7 +210,7 @@ void WidebandFMAudio::configure_wfm(const WFMConfigureMessage& message) { constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor; constexpr size_t decim_1_input_fs = decim_0_output_fs; - decim_0.configure(message.decim_0_filter.taps); + configure_fs4(message.decim_0_filter.taps); // decim_1.configure(message.decim_1_filter.taps); // Original . // TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps . @@ -219,7 +241,7 @@ void WidebandFMAudio::configure_wfmam(const WFMAMConfigureMessage& message) { constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor; constexpr size_t decim_1_input_fs = decim_0_output_fs; - decim_0.configure(message.decim_0_filter.taps); + configure_fs4(message.decim_0_filter.taps); // decim_1.configure(message.decim_1_filter.taps); // Original . // TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps . diff --git a/firmware/baseband/proc_wfm_audio.hpp b/firmware/baseband/proc_wfm_audio.hpp index a9ebd87d5..b5a1c5698 100644 --- a/firmware/baseband/proc_wfm_audio.hpp +++ b/firmware/baseband/proc_wfm_audio.hpp @@ -23,6 +23,8 @@ #ifndef __PROC_WFM_AUDIO_H__ #define __PROC_WFM_AUDIO_H__ +#include + #include "baseband_processor.hpp" #include "baseband_thread.hpp" #include "rssi_thread.hpp" @@ -99,6 +101,10 @@ class WidebandFMAudio : public BasebandProcessor { complex_audio.size()}; dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; + std::array decim_0_taps_{}; + std::atomic requested_fs4_direction_{RxFs4Direction::Down}; + RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down}; + void configure_fs4(const std::array& taps); // dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; //original condition , before adding wfmam // decim_1 will handle different types of FIR filters depending on selection. diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index ea3bb9060..d521bd95a 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -171,6 +171,7 @@ class Message { TetraBsch = 113, TetraDnb = 114, AudioDDCConfig = 115, + RxFs4Config = 116, MAX }; @@ -311,6 +312,18 @@ class SpectrumStreamingConfigMessage : public Message { Mode mode{Mode::Stopped}; }; +// Application sample-rate translation, independent of PRALINE's AFE shift. +enum class RxFs4Direction : uint8_t { Down, + Up }; + +class RxFs4ConfigMessage : public Message { + public: + constexpr RxFs4ConfigMessage(RxFs4Direction direction) + : Message{ID::RxFs4Config}, direction{direction} {} + + const RxFs4Direction direction; +}; + class AudioDDCConfigMessage : public Message { public: constexpr AudioDDCConfigMessage(int32_t frequency)