diff --git a/firmware/application/analog_audio_app.cpp b/firmware/application/analog_audio_app.cpp index ec50f4d5..fcd9cda1 100644 --- a/firmware/application/analog_audio_app.cpp +++ b/firmware/application/analog_audio_app.cpp @@ -27,58 +27,273 @@ using namespace portapack; #include "utility.hpp" -AnalogAudioModel::AnalogAudioModel(ReceiverModel::Mode mode) { - receiver_model.set_baseband_configuration({ - .mode = toUType(mode), - .sampling_rate = 3072000, - .decimation_factor = 1, - }); - receiver_model.set_baseband_bandwidth(1750000); +namespace ui { - switch(mode) { - case ReceiverModel::Mode::NarrowbandFMAudio: - configure_nbfm(); - break; +/* AMOptionsView *********************************************************/ - case ReceiverModel::Mode::WidebandFMAudio: - configure_wfm(); - break; +AMOptionsView::AMOptionsView( + const Rect parent_rect, const Style* const style +) : View { parent_rect } +{ + set_style(style); - case ReceiverModel::Mode::AMAudio: - configure_am(); - break; - - default: - break; + add_children({ { + &label_config, + &options_config, + } }); + + options_config.set_selected_index(receiver_model.am_configuration()); + options_config.on_change = [this](size_t n, OptionsField::value_t) { + receiver_model.set_am_configuration(n); + }; +} + +/* NBFMOptionsView *******************************************************/ + +NBFMOptionsView::NBFMOptionsView( + const Rect parent_rect, const Style* const style +) : View { parent_rect } +{ + set_style(style); + + add_children({ { + &label_config, + &options_config, + } }); + + options_config.set_selected_index(receiver_model.nbfm_configuration()); + options_config.on_change = [this](size_t n, OptionsField::value_t) { + receiver_model.set_nbfm_configuration(n); + }; +} + +/* AnalogAudioView *******************************************************/ + +AnalogAudioView::AnalogAudioView( + NavigationView& nav +) { + add_children({ { + &rssi, + &channel, + &audio, + &field_frequency, + &field_lna, + &field_vga, + &options_modulation, + &field_volume, + &waterfall, + } }); + + field_frequency.set_value(receiver_model.tuning_frequency()); + field_frequency.set_step(receiver_model.frequency_step()); + field_frequency.on_change = [this](rf::Frequency f) { + this->on_tuning_frequency_changed(f); + }; + field_frequency.on_edit = [this, &nav]() { + // TODO: Provide separate modal method/scheme? + auto new_view = nav.push(receiver_model.tuning_frequency()); + new_view->on_changed = [this](rf::Frequency f) { + this->on_tuning_frequency_changed(f); + this->field_frequency.set_value(f); + }; + }; + + field_frequency.on_show_options = [this]() { + this->on_show_options_frequency(); + }; + + field_lna.set_value(receiver_model.lna()); + field_lna.on_change = [this](int32_t v) { + this->on_lna_changed(v); + }; + + field_lna.on_show_options = [this]() { + this->on_show_options_rf_gain(); + }; + + field_vga.set_value(receiver_model.vga()); + field_vga.on_change = [this](int32_t v_db) { + this->on_vga_changed(v_db); + }; + + const auto modulation = receiver_model.modulation(); + options_modulation.set_by_value(modulation); + options_modulation.on_change = [this](size_t, OptionsField::value_t v) { + this->on_modulation_changed(static_cast(v)); + }; + options_modulation.on_show_options = [this]() { + this->on_show_options_modulation(); + }; + + field_volume.set_value((receiver_model.headphone_volume() - wolfson::wm8731::headphone_gain_range.max).decibel() + 99); + field_volume.on_change = [this](int32_t v) { + this->on_headphone_volume_changed(v); + }; + + update_modulation(static_cast(modulation)); +} + +AnalogAudioView::~AnalogAudioView() { + // TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do + // both? + audio_codec.headphone_mute(); + + receiver_model.disable(); +} + +void AnalogAudioView::on_hide() { + // TODO: Terrible kludge because widget system doesn't notify Waterfall that + // it's being shown or hidden. + waterfall.on_hide(); + View::on_hide(); +} + +void AnalogAudioView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + + const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), static_cast(new_parent_rect.height() - header_height) }; + waterfall.set_parent_rect(waterfall_rect); +} + +void AnalogAudioView::focus() { + field_frequency.focus(); +} + +void AnalogAudioView::on_tuning_frequency_changed(rf::Frequency f) { + receiver_model.set_tuning_frequency(f); +} + +void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) { + receiver_model.set_baseband_bandwidth(bandwidth_hz); +} + +void AnalogAudioView::on_rf_amp_changed(bool v) { + receiver_model.set_rf_amp(v); +} + +void AnalogAudioView::on_lna_changed(int32_t v_db) { + receiver_model.set_lna(v_db); +} + +void AnalogAudioView::on_vga_changed(int32_t v_db) { + receiver_model.set_vga(v_db); +} + +void AnalogAudioView::on_modulation_changed(const ReceiverModel::Mode modulation) { + // TODO: Terrible kludge because widget system doesn't notify Waterfall that + // it's being shown or hidden. + waterfall.on_hide(); + update_modulation(modulation); + on_show_options_modulation(); + waterfall.on_show(); +} + +void AnalogAudioView::remove_options_widget() { + if( options_widget ) { + remove_child(options_widget.get()); + options_widget.reset(); } + field_lna.set_style(nullptr); + options_modulation.set_style(nullptr); + field_frequency.set_style(nullptr); } -void AnalogAudioModel::configure_nbfm() { - const NBFMConfigureMessage message { - taps_4k25_decim_0, - taps_4k25_decim_1, - taps_4k25_channel, - 2500, - }; - shared_memory.baseband_queue.push(message); +void AnalogAudioView::set_options_widget(std::unique_ptr new_widget) { + if( new_widget ) { + options_widget = std::move(new_widget); + add_child(options_widget.get()); + } } -void AnalogAudioModel::configure_wfm() { - const WFMConfigureMessage message { - taps_200k_wfm_decim_0, - taps_200k_wfm_decim_1, - taps_64_lp_156_198, - 75000, +void AnalogAudioView::on_show_options_frequency() { + // TODO: This approach of managing options views is error-prone and unsustainable! + remove_options_widget(); + + field_frequency.set_style(&style_options_group); + + auto widget = std::make_unique( + Rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, + &style_options_group + ); + + widget->set_step(receiver_model.frequency_step()); + widget->on_change_step = [this](rf::Frequency f) { + this->on_frequency_step_changed(f); }; - shared_memory.baseband_queue.push(message); + widget->set_reference_ppm_correction(receiver_model.reference_ppm_correction()); + widget->on_change_reference_ppm_correction = [this](int32_t v) { + this->on_reference_ppm_correction_changed(v); + }; + + set_options_widget(std::move(widget)); } -void AnalogAudioModel::configure_am() { - const AMConfigureMessage message { - taps_6k0_decim_0, - taps_6k0_decim_1, - taps_6k0_channel, +void AnalogAudioView::on_show_options_rf_gain() { + // TODO: This approach of managing options views is error-prone and unsustainable! + remove_options_widget(); + + field_lna.set_style(&style_options_group); + + auto widget = std::make_unique( + Rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, + &style_options_group + ); + + widget->set_rf_amp(receiver_model.rf_amp()); + widget->on_change_rf_amp = [this](bool enable) { + this->on_rf_amp_changed(enable); }; - shared_memory.baseband_queue.push(message); + + set_options_widget(std::move(widget)); } + +void AnalogAudioView::on_show_options_modulation() { + // TODO: This approach of managing options views is error-prone and unsustainable! + remove_options_widget(); + + const auto modulation = static_cast(receiver_model.modulation()); + if( modulation == ReceiverModel::Mode::AMAudio ) { + options_modulation.set_style(&style_options_group); + auto widget = std::make_unique( + Rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, + &style_options_group + ); + set_options_widget(std::move(widget)); + } + if( modulation == ReceiverModel::Mode::NarrowbandFMAudio ) { + options_modulation.set_style(&style_options_group); + auto widget = std::make_unique( + Rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, + &style_options_group + ); + set_options_widget(std::move(widget)); + } +} + +void AnalogAudioView::on_frequency_step_changed(rf::Frequency f) { + receiver_model.set_frequency_step(f); + field_frequency.set_step(f); +} + +void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) { + receiver_model.set_reference_ppm_correction(v); +} + +void AnalogAudioView::on_headphone_volume_changed(int32_t v) { + const auto new_volume = volume_t::decibel(v - 99) + wolfson::wm8731::headphone_gain_range.max; + receiver_model.set_headphone_volume(new_volume); +} + +void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) { + const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis); + receiver_model.set_baseband_configuration({ + .mode = toUType(modulation), + .sampling_rate = is_wideband_spectrum_mode ? 20000000U : 3072000U, + .decimation_factor = 1, + }); + receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? 12000000 : 1750000); + receiver_model.enable(); +} + +} /* namespace ui */ diff --git a/firmware/application/analog_audio_app.hpp b/firmware/application/analog_audio_app.hpp index 7c840ced..e3bd24a4 100644 --- a/firmware/application/analog_audio_app.hpp +++ b/firmware/application/analog_audio_app.hpp @@ -23,30 +23,145 @@ #define __ANALOG_AUDIO_APP_H__ #include "receiver_model.hpp" + +#include "ui_receiver.hpp" #include "ui_spectrum.hpp" -class AnalogAudioModel { -public: - AnalogAudioModel(ReceiverModel::Mode mode); - -private: - void configure_nbfm(); - void configure_wfm(); - void configure_am(); -}; +#include "ui_font_fixed_8x16.hpp" namespace ui { -class AnalogAudioView : public spectrum::WaterfallWidget { +constexpr Style style_options_group { + .font = font::fixed_8x16, + .background = Color::blue(), + .foreground = Color::white(), +}; + +class AMOptionsView : public View { public: - AnalogAudioView( - ReceiverModel::Mode mode - ) : model { mode } - { - } + AMOptionsView(const Rect parent_rect, const Style* const style); private: - AnalogAudioModel model; + Text label_config { + { 0 * 8, 0 * 16, 2 * 8, 1 * 16 }, + "BW", + }; + + OptionsField options_config { + { 3 * 8, 0 * 16 }, + 4, + { + { "DSB ", 0 }, + { "USB ", 0 }, + { "LSB ", 0 }, + } + }; +}; + +class NBFMOptionsView : public View { +public: + NBFMOptionsView(const Rect parent_rect, const Style* const style); + +private: + Text label_config { + { 0 * 8, 0 * 16, 2 * 8, 1 * 16 }, + "BW", + }; + + OptionsField options_config { + { 3 * 8, 0 * 16 }, + 4, + { + { " 8k5", 0 }, + { "11k ", 0 }, + { "16k ", 0 }, + } + }; +}; + +class AnalogAudioView : public View { +public: + AnalogAudioView(NavigationView& nav); + ~AnalogAudioView(); + + void on_hide() override; + + void set_parent_rect(const Rect new_parent_rect) override; + + void focus() override; + +private: + static constexpr ui::Dim header_height = 2 * 16; + + RSSI rssi { + { 21 * 8, 0, 6 * 8, 4 }, + }; + + Channel channel { + { 21 * 8, 5, 6 * 8, 4 }, + }; + + Audio audio { + { 21 * 8, 10, 6 * 8, 4 }, + }; + + FrequencyField field_frequency { + { 5 * 8, 0 * 16 }, + }; + + LNAGainField field_lna { + { 15 * 8, 0 * 16 } + }; + + NumberField field_vga { + { 18 * 8, 0 * 16}, + 2, + { max2837::vga::gain_db_range.minimum, max2837::vga::gain_db_range.maximum }, + max2837::vga::gain_db_step, + ' ', + }; + + OptionsField options_modulation { + { 0 * 8, 0 * 16 }, + 4, + { + { " AM ", toUType(ReceiverModel::Mode::AMAudio) }, + { "NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio) }, + { "WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio) }, + { "SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis) }, + } + }; + + NumberField field_volume { + { 28 * 8, 0 * 16 }, + 2, + { 0, 99 }, + 1, + ' ', + }; + + std::unique_ptr options_widget; + + spectrum::WaterfallWidget waterfall; + + void on_tuning_frequency_changed(rf::Frequency f); + void on_baseband_bandwidth_changed(uint32_t bandwidth_hz); + void on_rf_amp_changed(bool v); + void on_lna_changed(int32_t v_db); + void on_vga_changed(int32_t v_db); + void on_modulation_changed(const ReceiverModel::Mode modulation); + void on_show_options_frequency(); + void on_show_options_rf_gain(); + void on_show_options_modulation(); + void on_frequency_step_changed(rf::Frequency f); + void on_reference_ppm_correction_changed(int32_t v); + void on_headphone_volume_changed(int32_t v); + void on_edit_frequency(); + + void remove_options_widget(); + void set_options_widget(std::unique_ptr new_widget); + + void update_modulation(const ReceiverModel::Mode modulation); }; } /* namespace ui */ diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index 86682b74..5ec51aec 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -516,7 +516,17 @@ void ClockManager::start_audio_pll() { while( !cgu::pll0audio::is_locked() ); cgu::pll0audio::clock_enable(); - set_clock(LPC_CGU->BASE_AUDIO_CLK, cgu::CLK_SEL::PLL0AUDIO); + set_base_audio_clock_divider(1); + set_clock(LPC_CGU->BASE_AUDIO_CLK, cgu::CLK_SEL::IDIVC); +} + +void ClockManager::set_base_audio_clock_divider(const size_t divisor) { + LPC_CGU->IDIVC_CTRL = + (0 << 1) + | ((divisor - 1) << 2) + | (1 << 11) + | (toUType(cgu::CLK_SEL::PLL0AUDIO) << 24) + ; } void ClockManager::stop_audio_pll() { diff --git a/firmware/application/clock_manager.hpp b/firmware/application/clock_manager.hpp index 54a80a03..f1398529 100644 --- a/firmware/application/clock_manager.hpp +++ b/firmware/application/clock_manager.hpp @@ -51,6 +51,8 @@ public: void start_audio_pll(); void stop_audio_pll(); + void set_base_audio_clock_divider(const size_t divisor); + void enable_codec_clocks(); void disable_codec_clocks(); diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 57f2b04d..c185962c 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -61,9 +61,9 @@ ClockManager clock_manager { i2c0, clock_generator }; -ReceiverModel receiver_model { - clock_manager -}; +ReceiverModel receiver_model; + +TemperatureLogger temperature_logger; TemperatureLogger temperature_logger; diff --git a/firmware/application/portapack.hpp b/firmware/application/portapack.hpp index 221ebe47..d0ce5db8 100644 --- a/firmware/application/portapack.hpp +++ b/firmware/application/portapack.hpp @@ -30,6 +30,7 @@ #include "lcd_ili9341.hpp" #include "radio.hpp" +#include "clock_manager.hpp" #include "temperature_logger.hpp" namespace portapack { @@ -44,6 +45,7 @@ extern SPI ssp1; extern wolfson::wm8731::WM8731 audio_codec; extern si5351::Si5351 clock_generator; +extern ClockManager clock_manager; extern ReceiverModel receiver_model; extern TransmitterModel transmitter_model; diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index 4f4e4d1c..7947a652 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -26,6 +26,90 @@ #include "portapack.hpp" using namespace portapack; +#include "dsp_fir_taps.hpp" +#include "dsp_iir.hpp" +#include "dsp_iir_config.hpp" + +namespace { + +struct AMConfig { + const fir_taps_complex<64> channel; + const AMConfigureMessage::Modulation modulation; + + void apply() const; +}; + +struct NBFMConfig { + const fir_taps_real<24> decim_0; + const fir_taps_real<32> decim_1; + const fir_taps_real<32> channel; + const size_t deviation; + + void apply() const; +}; + +struct WFMConfig { + void apply() const; +}; + +void AMConfig::apply() const { + const AMConfigureMessage message { + taps_6k0_decim_0, + taps_6k0_decim_1, + taps_6k0_decim_2, + channel, + modulation, + audio_12k_hpf_300hz_config + }; + shared_memory.baseband_queue.push(message); + clock_manager.set_base_audio_clock_divider(4); +} + +void NBFMConfig::apply() const { + const NBFMConfigureMessage message { + decim_0, + decim_1, + channel, + 2, + deviation, + audio_24k_hpf_300hz_config, + audio_24k_deemph_300_6_config + }; + shared_memory.baseband_queue.push(message); + clock_manager.set_base_audio_clock_divider(2); +} + +void WFMConfig::apply() const { + const WFMConfigureMessage message { + taps_200k_wfm_decim_0, + taps_200k_wfm_decim_1, + taps_64_lp_156_198, + 75000, + audio_48k_hpf_30hz_config, + audio_48k_deemph_2122_6_config + }; + shared_memory.baseband_queue.push(message); + clock_manager.set_base_audio_clock_divider(1); +} + +static constexpr std::array am_configs { { + { taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB }, + { taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB }, + { taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB }, +} }; + +static constexpr std::array nbfm_configs { { + { taps_4k25_decim_0, taps_4k25_decim_1, taps_4k25_channel, 2500 }, + { taps_11k0_decim_0, taps_11k0_decim_1, taps_11k0_channel, 2500 }, + { taps_16k0_decim_0, taps_16k0_decim_1, taps_16k0_channel, 5000 }, +} }; + +static constexpr std::array wfm_configs { { + { }, +} }; + +} /* namespace */ + rf::Frequency ReceiverModel::tuning_frequency() const { return persistent_memory::tuned_frequency(); } @@ -129,7 +213,7 @@ void ReceiverModel::enable() { update_vga(); update_baseband_bandwidth(); update_baseband_configuration(); - + update_modulation_configuration(); update_headphone_volume(); } @@ -189,6 +273,27 @@ void ReceiverModel::set_baseband_configuration(const BasebandConfiguration confi update_baseband_configuration(); } +void ReceiverModel::set_am_configuration(const size_t n) { + if( n < am_configs.size() ) { + am_config_index = n; + update_modulation_configuration(); + } +} + +void ReceiverModel::set_nbfm_configuration(const size_t n) { + if( n < nbfm_configs.size() ) { + nbfm_config_index = n; + update_modulation_configuration(); + } +} + +void ReceiverModel::set_wfm_configuration(const size_t n) { + if( n < wfm_configs.size() ) { + wfm_config_index = n; + update_modulation_configuration(); + } +} + void ReceiverModel::update_baseband_configuration() { // TODO: Move more low-level radio control stuff to M4. It'll enable tighter // synchronization for things like wideband (sweeping) spectrum analysis, and @@ -211,3 +316,47 @@ void ReceiverModel::update_headphone_volume() { audio_codec.set_headphone_volume(headphone_volume_); } + +void ReceiverModel::update_modulation_configuration() { + switch(static_cast(modulation())) { + default: + case Mode::AMAudio: + update_am_configuration(); + break; + + case Mode::NarrowbandFMAudio: + update_nbfm_configuration(); + break; + + case Mode::WidebandFMAudio: + update_wfm_configuration(); + break; + + case Mode::SpectrumAnalysis: + break; + } +} + +size_t ReceiverModel::am_configuration() const { + return am_config_index; +} + +void ReceiverModel::update_am_configuration() { + am_configs[am_config_index].apply(); +} + +size_t ReceiverModel::nbfm_configuration() const { + return nbfm_config_index; +} + +void ReceiverModel::update_nbfm_configuration() { + nbfm_configs[nbfm_config_index].apply(); +} + +size_t ReceiverModel::wfm_configuration() const { + return wfm_config_index; +} + +void ReceiverModel::update_wfm_configuration() { + wfm_configs[wfm_config_index].apply(); +} diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index ac3ce913..4b21c87b 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -25,7 +25,6 @@ #include #include -#include "clock_manager.hpp" #include "message.hpp" #include "rf_path.hpp" #include "max2837.hpp" @@ -43,12 +42,6 @@ public: ERT = 6, }; - constexpr ReceiverModel( - ClockManager& clock_manager - ) : clock_manager(clock_manager) - { - } - rf::Frequency tuning_frequency() const; void set_tuning_frequency(rf::Frequency f); @@ -87,6 +80,15 @@ public: void set_baseband_configuration(const BasebandConfiguration config); + size_t am_configuration() const; + void set_am_configuration(const size_t n); + + size_t nbfm_configuration() const; + void set_nbfm_configuration(const size_t n); + + size_t wfm_configuration() const; + void set_wfm_configuration(const size_t n); + private: rf::Frequency frequency_step_ { 25000 }; bool enabled_ { false }; @@ -100,8 +102,10 @@ private: .sampling_rate = 3072000, .decimation_factor = 1, }; + size_t am_config_index = 0; + size_t nbfm_config_index = 0; + size_t wfm_config_index = 0; volume_t headphone_volume_ { -43.0_dB }; - ClockManager& clock_manager; int32_t tuning_offset(); @@ -114,6 +118,11 @@ private: void update_baseband_configuration(); void update_headphone_volume(); + void update_modulation_configuration(); + void update_am_configuration(); + void update_nbfm_configuration(); + void update_wfm_configuration(); + void baseband_disable(); }; diff --git a/firmware/application/ui_debug.cpp b/firmware/application/ui_debug.cpp index cdfdc54c..004c7769 100644 --- a/firmware/application/ui_debug.cpp +++ b/firmware/application/ui_debug.cpp @@ -23,11 +23,6 @@ #include "ch.h" -#include "ff.h" -#include "hackrf_gpio.hpp" -#include "portapack.hpp" -#include "portapack_shared_memory.hpp" - #include "radio.hpp" #include "string_format.hpp" diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 960e5c57..cbd3d572 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -43,6 +43,7 @@ #include "ui_sigfrx.hpp" #include "ui_numbers.hpp" +#include "analog_audio_app.hpp" #include "ais_app.hpp" #include "ert_app.hpp" #include "tpms_app.hpp" @@ -65,7 +66,6 @@ SystemStatusView::SystemStatusView() { &button_sleep, &sd_card_status_view, } }); - sd_card_status_view.set_parent_rect({ 28 * 8, 0 * 16, 2 * 8, 1 * 16 }); button_back.on_select = [this](Button&){ if( this->on_back ) { @@ -73,7 +73,7 @@ SystemStatusView::SystemStatusView() { } }; - button_sleep.on_select = [this](Button&) { + button_sleep.on_select = [this](ImageButton&) { DisplaySleepMessage message; EventDispatcher::message_map().send(&message); }; diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index 2e902f4d..12b67dea 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -37,6 +37,29 @@ namespace ui { +static constexpr uint8_t bitmap_sleep_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x04, + 0x00, 0x08, + 0x00, 0x18, + 0x00, 0x18, + 0x00, 0x38, + 0x00, 0x3c, + 0x00, 0x3c, + 0x00, 0x3e, + 0x84, 0x1f, + 0xf8, 0x1f, + 0xf0, 0x0f, + 0xc0, 0x03, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sleep { + { 16, 16 }, bitmap_sleep_data +}; + class SystemStatusView : public View { public: std::function on_back; @@ -47,7 +70,7 @@ public: void set_title(const std::string new_value); private: - static constexpr auto default_title = "PortaPack/HAVOC"; + static constexpr auto default_title = "PortaPack"; Button button_back { { 0 * 8, 0 * 16, 3 * 8, 16 }, @@ -59,12 +82,16 @@ private: default_title, }; - Button button_sleep { + ImageButton button_sleep { { 25 * 8, 0, 2 * 8, 1 * 16 }, - "ZZ", + &bitmap_sleep, + Color::white(), + Color::black() }; - SDCardStatusView sd_card_status_view; + SDCardStatusView sd_card_status_view { + { 28 * 8, 0 * 16, 2 * 8, 1 * 16 } + }; }; class NavigationView : public View { diff --git a/firmware/application/ui_receiver.cpp b/firmware/application/ui_receiver.cpp index 4263a84c..f80db742 100644 --- a/firmware/application/ui_receiver.cpp +++ b/firmware/application/ui_receiver.cpp @@ -26,11 +26,7 @@ using namespace portapack; #include "string_format.hpp" -#include "analog_audio_app.hpp" -#include "ais_app.hpp" -#include "tpms_app.hpp" -#include "ert_app.hpp" -#include "spectrum_analysis_app.hpp" +#include "max2837.hpp" namespace ui { @@ -322,184 +318,4 @@ void LNAGainField::on_focus() { } } -/* ReceiverView **********************************************************/ - -ReceiverView::ReceiverView( - NavigationView& nav -) { - add_children({ { - &rssi, - &channel, - &audio, - &field_frequency, - &field_lna, - &field_vga, - &options_modulation, - &field_volume, - &view_frequency_options, - &view_rf_gain_options, - } }); - - field_frequency.set_value(receiver_model.tuning_frequency()); - field_frequency.set_step(receiver_model.frequency_step()); - field_frequency.on_change = [this](rf::Frequency f) { - this->on_tuning_frequency_changed(f); - }; - field_frequency.on_edit = [this, &nav]() { - // TODO: Provide separate modal method/scheme? - auto new_view = nav.push(receiver_model.tuning_frequency()); - new_view->on_changed = [this](rf::Frequency f) { - this->on_tuning_frequency_changed(f); - this->field_frequency.set_value(f); - }; - }; - field_frequency.on_show_options = [this]() { - this->on_show_options_frequency(); - }; - - field_lna.set_value(receiver_model.lna()); - field_lna.on_change = [this](int32_t v) { - this->on_lna_changed(v); - }; - field_lna.on_show_options = [this]() { - this->on_show_options_rf_gain(); - }; - - field_vga.set_value(receiver_model.vga()); - field_vga.on_change = [this](int32_t v_db) { - this->on_vga_changed(v_db); - }; - - options_modulation.set_by_value(receiver_model.modulation()); - options_modulation.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; - this->on_modulation_changed(static_cast(v)); - }; - - field_volume.set_value((receiver_model.headphone_volume() - wolfson::wm8731::headphone_gain_range.max).decibel() + 99); - field_volume.on_change = [this](int32_t v) { - this->on_headphone_volume_changed(v); - }; - - view_frequency_options.hidden(true); - view_frequency_options.set_step(receiver_model.frequency_step()); - view_frequency_options.on_change_step = [this](rf::Frequency f) { - this->on_frequency_step_changed(f); - }; - view_frequency_options.set_reference_ppm_correction(receiver_model.reference_ppm_correction()); - view_frequency_options.on_change_reference_ppm_correction = [this](int32_t v) { - this->on_reference_ppm_correction_changed(v); - }; - - view_rf_gain_options.hidden(true); - view_rf_gain_options.set_rf_amp(receiver_model.rf_amp()); - view_rf_gain_options.on_change_rf_amp = [this](bool enable) { - this->on_rf_amp_changed(enable); - }; - - receiver_model.enable(); -} - -ReceiverView::~ReceiverView() { - - // TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do - // both? - audio_codec.headphone_mute(); - - receiver_model.disable(); -} - -void ReceiverView::on_show() { - View::on_show(); - - // TODO: Separate concepts of baseband "modulation" and receiver "mode". - on_modulation_changed(static_cast(receiver_model.modulation())); -} - -void ReceiverView::on_hide() { - on_modulation_changed(static_cast(-1)); - - View::on_hide(); -} - -void ReceiverView::focus() { - field_frequency.focus(); -} - -void ReceiverView::on_tuning_frequency_changed(rf::Frequency f) { - receiver_model.set_tuning_frequency(f); -} - -void ReceiverView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) { - receiver_model.set_baseband_bandwidth(bandwidth_hz); -} - -void ReceiverView::on_rf_amp_changed(bool v) { - receiver_model.set_rf_amp(v); -} - -void ReceiverView::on_lna_changed(int32_t v_db) { - receiver_model.set_lna(v_db); -} - -void ReceiverView::on_vga_changed(int32_t v_db) { - receiver_model.set_vga(v_db); -} - -void ReceiverView::on_modulation_changed(ReceiverModel::Mode mode) { - remove_child(widget_content.get()); - widget_content.reset(); - - switch(mode) { - case ReceiverModel::Mode::AMAudio: - case ReceiverModel::Mode::NarrowbandFMAudio: - case ReceiverModel::Mode::WidebandFMAudio: - widget_content = std::make_unique(mode); - break; - - case ReceiverModel::Mode::SpectrumAnalysis: - widget_content = std::make_unique(); - break; - - default: - break; - } - - if( widget_content ) { - add_child(widget_content.get()); - const ui::Rect rect { 0, header_height, parent_rect.width(), static_cast(parent_rect.height() - header_height) }; - widget_content->set_parent_rect(rect); - } -} - -void ReceiverView::on_show_options_frequency() { - view_rf_gain_options.hidden(true); - field_lna.set_style(nullptr); - - view_frequency_options.hidden(false); - field_frequency.set_style(&view_frequency_options.style()); -} - -void ReceiverView::on_show_options_rf_gain() { - view_frequency_options.hidden(true); - field_frequency.set_style(nullptr); - - view_rf_gain_options.hidden(false); - field_lna.set_style(&view_frequency_options.style()); -} - -void ReceiverView::on_frequency_step_changed(rf::Frequency f) { - receiver_model.set_frequency_step(f); - field_frequency.set_step(f); -} - -void ReceiverView::on_reference_ppm_correction_changed(int32_t v) { - receiver_model.set_reference_ppm_correction(v); -} - -void ReceiverView::on_headphone_volume_changed(int32_t v) { - const auto new_volume = volume_t::decibel(v - 99) + wolfson::wm8731::headphone_gain_range.max; - receiver_model.set_headphone_volume(new_volume); -} - } /* namespace ui */ diff --git a/firmware/application/ui_receiver.hpp b/firmware/application/ui_receiver.hpp index 6ab20236..fa74f970 100644 --- a/firmware/application/ui_receiver.hpp +++ b/firmware/application/ui_receiver.hpp @@ -23,19 +23,11 @@ #define __UI_RECEIVER_H__ #include "ui.hpp" -#include "ui_font_fixed_8x16.hpp" #include "ui_navigation.hpp" #include "ui_painter.hpp" #include "ui_widget.hpp" -#include "utility.hpp" - -#include "max2837.hpp" #include "rf_path.hpp" -#include "volume.hpp" -#include "wm8731.hpp" - -#include "receiver_model.hpp" #include #include @@ -323,98 +315,6 @@ public: void on_focus() override; }; -constexpr Style style_options_group { - .font = font::fixed_8x16, - .background = Color::blue(), - .foreground = Color::white(), -}; - -class ReceiverView : public View { -public: - ReceiverView(NavigationView& nav); - ~ReceiverView(); - - void on_show() override; - void on_hide() override; - - void focus() override; - -private: - static constexpr ui::Dim header_height = 2 * 16; - - RSSI rssi { - { 21 * 8, 0, 6 * 8, 4 }, - }; - - Channel channel { - { 21 * 8, 5, 6 * 8, 4 }, - }; - - Audio audio { - { 21 * 8, 10, 6 * 8, 4 }, - }; - - FrequencyField field_frequency { - { 5 * 8, 0 * 16 }, - }; - - LNAGainField field_lna { - { 15 * 8, 0 * 16 } - }; - - NumberField field_vga { - { 18 * 8, 0 * 16}, - 2, - { max2837::vga::gain_db_range.minimum, max2837::vga::gain_db_range.maximum }, - max2837::vga::gain_db_step, - ' ', - }; - - OptionsField options_modulation { - { 0 * 8, 0 * 16 }, - 4, - { - { " AM ", toUType(ReceiverModel::Mode::AMAudio) }, - { "NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio) }, - { "WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio) }, - { "SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis) }, - } - }; - - NumberField field_volume { - { 28 * 8, 0 * 16 }, - 2, - { 0, 99 }, - 1, - ' ', - }; - - FrequencyOptionsView view_frequency_options { - { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, - &style_options_group - }; - - RadioGainOptionsView view_rf_gain_options { - { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, - &style_options_group - }; - - std::unique_ptr widget_content; - - void on_tuning_frequency_changed(rf::Frequency f); - void on_baseband_bandwidth_changed(uint32_t bandwidth_hz); - void on_rf_amp_changed(bool v); - void on_lna_changed(int32_t v_db); - void on_vga_changed(int32_t v_db); - void on_modulation_changed(ReceiverModel::Mode mode); - void on_show_options_frequency(); - void on_show_options_rf_gain(); - void on_frequency_step_changed(rf::Frequency f); - void on_reference_ppm_correction_changed(int32_t v); - void on_headphone_volume_changed(int32_t v); - void on_edit_frequency(); -}; - } /* namespace ui */ #endif/*__UI_RECEIVER_H__*/ diff --git a/firmware/application/ui_sd_card_status_view.cpp b/firmware/application/ui_sd_card_status_view.cpp index d790a760..46a07720 100644 --- a/firmware/application/ui_sd_card_status_view.cpp +++ b/firmware/application/ui_sd_card_status_view.cpp @@ -28,12 +28,93 @@ namespace ui { /* SDCardStatusView *****************************************************/ -SDCardStatusView::SDCardStatusView() { - add_children({ { - &text_status, - } }); +namespace detail { - on_status(sd_card::status()); +static constexpr uint8_t bitmap_sd_card_ok_data[] = { + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sd_card_ok { + { 16, 16 }, bitmap_sd_card_ok_data +}; + +static constexpr uint8_t bitmap_sd_card_unknown_data[] = { + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, + 0x38, 0x1c, 0x98, 0x19, 0xf8, 0x19, 0xf8, 0x1c, + 0x78, 0x1e, 0x78, 0x1e, 0xf8, 0x1f, 0x78, 0x1e, + 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sd_card_unknown { + { 16, 16 }, bitmap_sd_card_unknown_data +}; + +static constexpr uint8_t bitmap_sd_card_error_data[] = { + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1b, 0xf8, 0x19, 0xf8, 0x1c, + 0xf8, 0x1e, 0xf8, 0x1c, 0xf8, 0x19, 0xf8, 0x1b, + 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sd_card_error { + { 16, 16 }, bitmap_sd_card_error_data +}; + +const Bitmap& bitmap_sd_card(const sd_card::Status status) { + switch(status) { + case sd_card::Status::IOError: + case sd_card::Status::MountError: + case sd_card::Status::ConnectError: + return bitmap_sd_card_error; + + case sd_card::Status::NotPresent: + return bitmap_sd_card_unknown; + + case sd_card::Status::Present: + return bitmap_sd_card_unknown; + + case sd_card::Status::Mounted: + return bitmap_sd_card_ok; + + default: + return bitmap_sd_card_unknown; + } +} + +static constexpr Color color_sd_card_error = Color::red(); +static constexpr Color color_sd_card_unknown = Color::yellow(); +static constexpr Color color_sd_card_ok = Color::green(); + +const Color color_sd_card(const sd_card::Status status) { + switch(status) { + case sd_card::Status::IOError: + case sd_card::Status::MountError: + case sd_card::Status::ConnectError: + return color_sd_card_error; + + case sd_card::Status::NotPresent: + return color_sd_card_unknown; + + case sd_card::Status::Present: + return color_sd_card_unknown; + + case sd_card::Status::Mounted: + return color_sd_card_ok; + + default: + return color_sd_card_unknown; + } +} + +} /* namespace detail */ + +SDCardStatusView::SDCardStatusView( + const Rect parent_rect +) : Image { parent_rect, &detail::bitmap_sd_card_unknown, detail::color_sd_card_unknown, Color::black() } +{ } void SDCardStatusView::on_show() { @@ -46,40 +127,17 @@ void SDCardStatusView::on_hide() { sd_card::status_signal -= sd_card_status_signal_token; } -void SDCardStatusView::on_status(const sd_card::Status status) { - std::string msg("??"); +void SDCardStatusView::paint(Painter& painter) { + const auto status = sd_card::status(); + set_bitmap(&detail::bitmap_sd_card(status)); + set_foreground(detail::color_sd_card(status)); - switch(status) { - case sd_card::Status::IOError: - msg = "IO"; - break; + Image::paint(painter); +} - case sd_card::Status::MountError: - msg = "MT"; - break; - - case sd_card::Status::ConnectError: - msg = "CN"; - break; - - case sd_card::Status::NotPresent: - msg = "XX"; - break; - - case sd_card::Status::Present: - msg = "OO"; - break; - - case sd_card::Status::Mounted: - msg = "OK"; - break; - - default: - msg = "--"; - break; - } - - text_status.set(msg); +void SDCardStatusView::on_status(const sd_card::Status) { + // Don't update image properties here, they might change. Wait until paint. + set_dirty(); } } /* namespace ui */ diff --git a/firmware/application/ui_sd_card_status_view.hpp b/firmware/application/ui_sd_card_status_view.hpp index dd542f3c..31388cfa 100644 --- a/firmware/application/ui_sd_card_status_view.hpp +++ b/firmware/application/ui_sd_card_status_view.hpp @@ -27,19 +27,16 @@ namespace ui { -class SDCardStatusView : public View { +class SDCardStatusView : public Image { public: - SDCardStatusView(); - + SDCardStatusView(const Rect parent_rect); + void on_show() override; void on_hide() override; -private: - Text text_status { - { 0 * 8, 0, 2 * 8, 1 * 16 }, - "", - }; + void paint(Painter& painter) override; +private: SignalToken sd_card_status_signal_token; void on_status(const sd_card::Status status); diff --git a/firmware/application/ui_setup.cpp b/firmware/application/ui_setup.cpp index 0a0c7062..641be01b 100644 --- a/firmware/application/ui_setup.cpp +++ b/firmware/application/ui_setup.cpp @@ -170,7 +170,19 @@ void AntennaBiasSetupView::focus() { button_done.focus(); } -void SetTouchCalibView::focus() { +AboutView::AboutView(NavigationView& nav) { + add_children({ { + &text_title, + &text_firmware, + &text_cpld_hackrf, + &text_cpld_portapack, + &button_ok, + } }); + + button_ok.on_select = [&nav](Button&){ nav.pop(); }; +} + +void AboutView::focus() { button_ok.focus(); } diff --git a/firmware/application/ui_spectrum.hpp b/firmware/application/ui_spectrum.hpp index 0c9321df..8b0381f4 100644 --- a/firmware/application/ui_spectrum.hpp +++ b/firmware/application/ui_spectrum.hpp @@ -84,7 +84,7 @@ public: private: WaterfallView waterfall_view; FrequencyScale frequency_scale; - ChannelSpectrumFIFO* fifo; + ChannelSpectrumFIFO* fifo { nullptr }; void on_channel_spectrum(const ChannelSpectrum& spectrum); }; diff --git a/firmware/baseband-tx/dsp_fir_taps.hpp b/firmware/baseband-tx/dsp_fir_taps.hpp deleted file mode 100644 index 9839a9de..00000000 --- a/firmware/baseband-tx/dsp_fir_taps.hpp +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. - * - * This file is part of PortaPack. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __DSP_FIR_TAPS_H__ -#define __DSP_FIR_TAPS_H__ - -#include -#include - -#include "complex.hpp" - -template -struct fir_taps_real { - float pass_frequency_normalized; - float stop_frequency_normalized; - std::array taps; -}; - -/* 3kHz/6.7kHz @ 96kHz. sum(abs(taps)): 89429 */ -constexpr fir_taps_real<64> taps_64_lp_031_070_tfilter { - .pass_frequency_normalized = 0.031f, - .stop_frequency_normalized = 0.070f, - .taps = { { - 56, 58, 81, 100, 113, 112, 92, 49, - -21, -120, -244, -389, -543, -692, -819, -903, - -923, -861, -698, -424, -34, 469, 1073, 1756, - 2492, 3243, 3972, 4639, 5204, 5634, 5903, 5995, - 5903, 5634, 5204, 4639, 3972, 3243, 2492, 1756, - 1073, 469, -34, -424, -698, -861, -923, -903, - -819, -692, -543, -389, -244, -120, -21, 49, - 92, 112, 113, 100, 81, 58, 56, 0, - } }, -}; - -/* 4kHz/7.5kHz @ 96kHz. sum(abs(taps)): 96783 */ -constexpr fir_taps_real<64> taps_64_lp_042_078_tfilter { - .pass_frequency_normalized = 0.042f, - .stop_frequency_normalized = 0.078f, - .taps = { { - -19, 39, 72, 126, 197, 278, 360, 432, - 478, 485, 438, 327, 152, -82, -359, -651, - -922, -1132, -1236, -1192, -968, -545, 81, 892, - 1852, 2906, 3984, 5012, 5910, 6609, 7053, 7205, - 7053, 6609, 5910, 5012, 3984, 2906, 1852, 892, - 81, -545, -968, -1192, -1236, -1132, -922, -651, - -359, -82, 152, 327, 438, 485, 478, 432, - 360, 278, 197, 126, 72, 39, -19, 0, - } }, -}; - -/* 5kHz/8.5kHz @ 96kHz. sum(abs(taps)): 101312 */ -constexpr fir_taps_real<64> taps_64_lp_052_089_tfilter { - .pass_frequency_normalized = 0.052f, - .stop_frequency_normalized = 0.089f, - .taps = { { - -65, -88, -129, -163, -178, -160, -100, 9, - 160, 340, 523, 675, 758, 738, 591, 313, - -76, -533, -987, -1355, -1544, -1472, -1077, -335, - 738, 2078, 3579, 5104, 6502, 7627, 8355, 8608, - 8355, 7627, 6502, 5104, 3579, 2078, 738, -335, - -1077, -1472, -1544, -1355, -987, -533, -76, 313, - 591, 738, 758, 675, 523, 340, 160, 9, - -100, -160, -178, -163, -129, -88, -65, 0, - } }, -}; - -/* 6kHz/9.6kHz @ 96kHz. sum(abs(taps)): 105088 */ -constexpr fir_taps_real<64> taps_64_lp_063_100_tfilter { - .pass_frequency_normalized = 0.063f, - .stop_frequency_normalized = 0.100f, - .taps = { { - 43, 21, -2, -54, -138, -245, -360, -453, - -493, -451, -309, -73, 227, 535, 776, 876, - 773, 443, -86, -730, -1357, -1801, -1898, -1515, - -585, 869, 2729, 4794, 6805, 8490, 9611, 10004, - 9611, 8490, 6805, 4794, 2729, 869, -585, -1515, - -1898, -1801, -1357, -730, -86, 443, 773, 876, - 776, 535, 227, -73, -309, -451, -493, -453, - -360, -245, -138, -54, -2, 21, 43, 0, - } }, -}; - -/* 7kHz/10.4kHz @ 96kHz: sum(abs(taps)): 110157 */ -constexpr fir_taps_real<64> taps_64_lp_073_108_tfilter { - .pass_frequency_normalized = 0.073f, - .stop_frequency_normalized = 0.108f, - .taps = { { - 79, 145, 241, 334, 396, 394, 306, 130, - -109, -360, -550, -611, -494, -197, 229, 677, - 1011, 1096, 846, 257, -570, -1436, -2078, -2225, - -1670, -327, 1726, 4245, 6861, 9146, 10704, 11257, - 10704, 9146, 6861, 4245, 1726, -327, -1670, -2225, - -2078, -1436, -570, 257, 846, 1096, 1011, 677, - 229, -197, -494, -611, -550, -360, -109, 130, - 306, 394, 396, 334, 241, 145, 79, 0, - } }, -}; - -/* 8kHz/11.5kHz @ 96kHz. sum(abs(taps)): 112092 */ -constexpr fir_taps_real<64> taps_64_lp_083_120_tfilter { - .pass_frequency_normalized = 0.083f, - .stop_frequency_normalized = 0.120f, - .taps = { { - -63, -72, -71, -21, 89, 248, 417, 537, - 548, 407, 124, -237, -563, -723, -621, -238, - 337, 919, 1274, 1201, 617, -382, -1514, -2364, - -2499, -1600, 414, 3328, 6651, 9727, 11899, 12682, - 11899, 9727, 6651, 3328, 414, -1600, -2499, -2364, - -1514, -382, 617, 1201, 1274, 919, 337, -238, - -621, -723, -563, -237, 124, 407, 548, 537, - 417, 248, 89, -21, -71, -72, -63, 0, - } }, -}; - -/* 9kHz/12.4kHz @ 96kHz. sum(abs(taps)): 116249 */ -constexpr fir_taps_real<64> taps_64_lp_094_129_tfilter { - .pass_frequency_normalized = 0.094f, - .stop_frequency_normalized = 0.129f, - .taps = { { - 5, -93, -198, -335, -449, -478, -378, -144, - 166, 444, 563, 440, 82, -395, -788, -892, - -589, 73, 859, 1421, 1431, 734, -530, -1919, - -2798, -2555, -837, 2274, 6220, 10103, 12941, 13981, - 12941, 10103, 6220, 2274, -837, -2555, -2798, -1919, - -530, 734, 1431, 1421, 859, 73, -589, -892, - -788, -395, 82, 440, 563, 444, 166, -144, - -378, -478, -449, -335, -198, -93, 5, 0, - } }, -}; - -/* 10kHz/13.4kHz @ 96kHz. sum(abs(taps)): 118511 */ -constexpr fir_taps_real<64> taps_64_lp_104_140_tfilter { - .pass_frequency_normalized = 0.104f, - .stop_frequency_normalized = 0.140f, - .taps = { { - 89, 159, 220, 208, 84, -147, -412, -597, - -588, -345, 58, 441, 595, 391, -128, -730, - -1080, -914, -198, 793, 1558, 1594, 678, -942, - -2546, -3187, -2084, 992, 5515, 10321, 13985, 15353, - 13985, 10321, 5515, 992, -2084, -3187, -2546, -942, - 678, 1594, 1558, 793, -198, -914, -1080, -730, - -128, 391, 595, 441, 58, -345, -588, -597, - -412, -147, 84, 208, 220, 159, 89, 0, - } }, -}; - -/* Wideband FM channel filter - * 103kHz/128kHz @ 768kHz - */ -constexpr fir_taps_real<64> taps_64_lp_130_169_tfilter { - .pass_frequency_normalized = 0.130f, - .stop_frequency_normalized = 0.169f, - .taps = { { - 100, 127, 62, -157, -470, -707, -678, -332, - 165, 494, 400, -85, -610, -729, -253, 535, - 1026, 734, -263, -1264, -1398, -332, 1316, 2259, - 1447, -988, -3474, -3769, -385, 6230, 13607, 18450, - 18450, 13607, 6230, -385, -3769, -3474, -988, 1447, - 2259, 1316, -332, -1398, -1264, -263, 734, 1026, - 535, -253, -729, -610, -85, 400, 494, 165, - -332, -678, -707, -470, -157, 62, 127, 100, - } }, -}; - -/* Wideband audio filter */ -/* 96kHz int16_t input - * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop - * -> 48kHz int16_t output, gain of 1.0 (I think). - * Padded to multiple of four taps for unrolled FIR code. - * sum(abs(taps)): 125270 - */ -constexpr fir_taps_real<64> taps_64_lp_156_198 { - .pass_frequency_normalized = 0.156f, - .stop_frequency_normalized = 0.196f, - .taps = { { - -27, 166, 104, -36, -174, -129, 109, 287, - 148, -232, -430, -130, 427, 597, 49, -716, - -778, 137, 1131, 957, -493, -1740, -1121, 1167, - 2733, 1252, -2633, -4899, -1336, 8210, 18660, 23254, - 18660, 8210, -1336, -4899, -2633, 1252, 2733, 1167, - -1121, -1740, -493, 957, 1131, 137, -778, -716, - 49, 597, 427, -130, -430, -232, 148, 287, - 109, -129, -174, -36, 104, 166, -27, 0, - } }, -}; - -#endif/*__DSP_FIR_TAPS_H__*/ diff --git a/firmware/baseband-tx/irq_ipc_m4.cpp b/firmware/baseband-tx/irq_ipc_m4.cpp deleted file mode 100644 index 07a75335..00000000 --- a/firmware/baseband-tx/irq_ipc_m4.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. - * - * This file is part of PortaPack. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#include "irq_ipc_m4.hpp" - -#include "ch.h" -#include "hal.h" - -#include "event_m4.hpp" - -#include "lpc43xx_cpp.hpp" -using namespace lpc43xx; - -void m0apptxevent_interrupt_enable() { - nvicEnableVector(M0CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M0APPTXEVENT_IRQ_PRIORITY)); -} - -void m0apptxevent_interrupt_disable() { - nvicDisableVector(M0CORE_IRQn); -} - -extern "C" { - -CH_IRQ_HANDLER(MAPP_IRQHandler) { - CH_IRQ_PROLOGUE(); - - chSysLockFromIsr(); - events_flag_isr(EVT_MASK_BASEBAND); - chSysUnlockFromIsr(); - - creg::m0apptxevent::clear(); - - CH_IRQ_EPILOGUE(); -} - -} diff --git a/firmware/baseband-tx/irq_ipc_m4.hpp b/firmware/baseband-tx/irq_ipc_m4.hpp deleted file mode 100644 index 61328685..00000000 --- a/firmware/baseband-tx/irq_ipc_m4.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. - * - * This file is part of PortaPack. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __IRQ_IPC_M4_H__ -#define __IRQ_IPC_M4_H__ - -void m0apptxevent_interrupt_enable(); -void m0apptxevent_interrupt_disable(); - -#endif/*__IRQ_IPC_M4_H__*/ diff --git a/firmware/baseband/audio_output.cpp b/firmware/baseband/audio_output.cpp index 6e29fa16..fa67eab2 100644 --- a/firmware/baseband/audio_output.cpp +++ b/firmware/baseband/audio_output.cpp @@ -57,6 +57,17 @@ void AudioOutput::write( void AudioOutput::write( const buffer_f32_t& audio +) { + block_buffer.feed( + audio, + [this](const buffer_f32_t& buffer) { + this->on_block(buffer); + } + ); +} + +void AudioOutput::on_block( + const buffer_f32_t& audio ) { const auto audio_present_now = squelch.execute(audio); diff --git a/firmware/baseband/audio_output.hpp b/firmware/baseband/audio_output.hpp index df490d3c..0ebaa8cd 100644 --- a/firmware/baseband/audio_output.hpp +++ b/firmware/baseband/audio_output.hpp @@ -27,6 +27,7 @@ #include "dsp_iir.hpp" #include "dsp_squelch.hpp" +#include "block_decimator.hpp" #include "audio_stats_collector.hpp" #include @@ -43,6 +44,8 @@ public: void write(const buffer_f32_t& audio); private: + BlockDecimator block_buffer { 1 }; + IIRBiquadFilter hpf; IIRBiquadFilter deemph; FMSquelch squelch; @@ -51,6 +54,7 @@ private: uint64_t audio_present_history = 0; + void on_block(const buffer_f32_t& audio); void fill_audio_buffer(const buffer_f32_t& audio); void feed_audio_stats(const buffer_f32_t& audio); }; diff --git a/firmware/baseband/block_decimator.hpp b/firmware/baseband/block_decimator.hpp index 1f7710ef..7782dc37 100644 --- a/firmware/baseband/block_decimator.hpp +++ b/firmware/baseband/block_decimator.hpp @@ -29,7 +29,7 @@ #include "dsp_types.hpp" #include "complex.hpp" -template +template class BlockDecimator { public: constexpr BlockDecimator( @@ -65,7 +65,7 @@ public: } template - void feed(const buffer_c16_t& src, BlockCallback callback) { + void feed(const buffer_t& src, BlockCallback callback) { /* NOTE: Input block size must be >= factor */ set_input_sampling_rate(src.sampling_rate); @@ -85,7 +85,7 @@ public: } private: - std::array buffer; + std::array buffer; uint32_t input_sampling_rate_ { 0 }; size_t factor_ { 1 }; size_t src_i { 0 }; diff --git a/firmware/baseband/dsp_decimate.cpp b/firmware/baseband/dsp_decimate.cpp index 5c5ba40e..abd8e7d0 100644 --- a/firmware/baseband/dsp_decimate.cpp +++ b/firmware/baseband/dsp_decimate.cpp @@ -179,10 +179,6 @@ static inline uint32_t scale_round_and_pack( // FIRC8xR16x24FS4Decim4 ////////////////////////////////////////////////// -FIRC8xR16x24FS4Decim4::FIRC8xR16x24FS4Decim4() { - z_.fill({}); -} - void FIRC8xR16x24FS4Decim4::configure( const std::array& taps, const int32_t scale, @@ -196,6 +192,7 @@ void FIRC8xR16x24FS4Decim4::configure( taps_[i+3] = taps[i+3] * negate_factor; } output_scale = scale; + z_.fill({}); } buffer_c16_t FIRC8xR16x24FS4Decim4::execute( @@ -244,10 +241,6 @@ buffer_c16_t FIRC8xR16x24FS4Decim4::execute( // FIRC8xR16x24FS4Decim8 ////////////////////////////////////////////////// -FIRC8xR16x24FS4Decim8::FIRC8xR16x24FS4Decim8() { - z_.fill({}); -} - void FIRC8xR16x24FS4Decim8::configure( const std::array& taps, const int32_t scale, @@ -261,6 +254,7 @@ void FIRC8xR16x24FS4Decim8::configure( taps_[i+3] = taps[i+3] * negate_factor; } output_scale = scale; + z_.fill({}); } buffer_c16_t FIRC8xR16x24FS4Decim8::execute( @@ -309,16 +303,13 @@ buffer_c16_t FIRC8xR16x24FS4Decim8::execute( // FIRC16xR16x16Decim2 //////////////////////////////////////////////////// -FIRC16xR16x16Decim2::FIRC16xR16x16Decim2() { - z_.fill({}); -} - void FIRC16xR16x16Decim2::configure( const std::array& taps, const int32_t scale ) { std::copy(taps.cbegin(), taps.cend(), taps_.begin()); output_scale = scale; + z_.fill({}); } buffer_c16_t FIRC16xR16x16Decim2::execute( @@ -363,16 +354,13 @@ buffer_c16_t FIRC16xR16x16Decim2::execute( // FIRC16xR16x32Decim8 //////////////////////////////////////////////////// -FIRC16xR16x32Decim8::FIRC16xR16x32Decim8() { - z_.fill({}); -} - void FIRC16xR16x32Decim8::configure( const std::array& taps, const int32_t scale ) { std::copy(taps.cbegin(), taps.cend(), taps_.begin()); output_scale = scale; + z_.fill({}); } buffer_c16_t FIRC16xR16x32Decim8::execute( @@ -657,18 +645,6 @@ buffer_s16_t FIR64AndDecimateBy2Real::execute( return { dst.p, src.count / 2, src.sampling_rate / 2 }; } -void FIRAndDecimateComplex::configure( - const int16_t* const taps, - const size_t taps_count, - const size_t decimation_factor -) { - samples_ = std::make_unique(taps_count); - taps_reversed_ = std::make_unique(taps_count); - taps_count_ = taps_count; - decimation_factor_ = decimation_factor; - std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]); -} - buffer_c16_t FIRAndDecimateComplex::execute( const buffer_c16_t& src, const buffer_c16_t& dst diff --git a/firmware/baseband/dsp_decimate.hpp b/firmware/baseband/dsp_decimate.hpp index d3f41555..1a9bf303 100644 --- a/firmware/baseband/dsp_decimate.hpp +++ b/firmware/baseband/dsp_decimate.hpp @@ -103,8 +103,6 @@ public: Up = false }; - FIRC8xR16x24FS4Decim4(); - void configure( const std::array& taps, const int32_t scale, @@ -135,8 +133,6 @@ public: Up = false }; - FIRC8xR16x24FS4Decim8(); - void configure( const std::array& taps, const int32_t scale, @@ -162,8 +158,6 @@ public: using sample_t = complex16_t; using tap_t = int16_t; - FIRC16xR16x16Decim2(); - void configure( const std::array& taps, const int32_t scale @@ -188,8 +182,6 @@ public: using sample_t = complex16_t; using tap_t = int16_t; - FIRC16xR16x32Decim8(); - void configure( const std::array& taps, const int32_t scale @@ -243,11 +235,18 @@ private: size_t taps_count_; size_t decimation_factor_; + template void configure( - const int16_t* const taps, + const T* const taps, const size_t taps_count, const size_t decimation_factor - ); + ) { + samples_ = std::make_unique(taps_count); + taps_reversed_ = std::make_unique(taps_count); + taps_count_ = taps_count; + decimation_factor_ = decimation_factor; + std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]); + } }; class DecimateBy2CIC4Real { diff --git a/firmware/baseband/dsp_demodulate.cpp b/firmware/baseband/dsp_demodulate.cpp index b6cbc45e..1c59c174 100644 --- a/firmware/baseband/dsp_demodulate.cpp +++ b/firmware/baseband/dsp_demodulate.cpp @@ -34,17 +34,10 @@ buffer_f32_t AM::execute( const buffer_c16_t& src, const buffer_f32_t& dst ) { - /* Intermediate maximum value: 46341 (when input is -32768,-32768). */ - /* Normalized to maximum 32767 for int16_t representation. */ - const auto src_p = src.p; const auto src_end = &src.p[src.count]; auto dst_p = dst.p; while(src_p < src_end) { - // const auto s = *(src_p++); - // const uint32_t r_sq = s.real() * s.real(); - // const uint32_t i_sq = s.imag() * s.imag(); - // const uint32_t mag_sq = r_sq + i_sq; const uint32_t sample0 = *__SIMD32(src_p)++; const uint32_t sample1 = *__SIMD32(src_p)++; const uint32_t mag_sq0 = __SMUAD(sample0, sample0); @@ -55,6 +48,23 @@ buffer_f32_t AM::execute( return { dst.p, src.count, src.sampling_rate }; } + +buffer_f32_t SSB::execute( + const buffer_c16_t& src, + const buffer_f32_t& dst +) { + const complex16_t* src_p = src.p; + const auto src_end = &src.p[src.count]; + auto dst_p = dst.p; + while(src_p < src_end) { + *(dst_p++) = (src_p++)->real(); + *(dst_p++) = (src_p++)->real(); + *(dst_p++) = (src_p++)->real(); + *(dst_p++) = (src_p++)->real(); + } + + return { dst.p, src.count, src.sampling_rate }; +} /* static inline float angle_approx_4deg0(const complex32_t t) { const auto x = static_cast(t.imag()) / static_cast(t.real()); @@ -89,8 +99,8 @@ buffer_f32_t FM::execute( const auto t0 = multiply_conjugate_s16_s32(s0, z); const auto t1 = multiply_conjugate_s16_s32(s1, s0); z = s1; - *(dst_p++) = angle_approx_0deg27(t0) * k; - *(dst_p++) = angle_approx_0deg27(t1) * k; + *(dst_p++) = angle_precise(t0) * k; + *(dst_p++) = angle_precise(t1) * k; } z_ = z; diff --git a/firmware/baseband/dsp_demodulate.hpp b/firmware/baseband/dsp_demodulate.hpp index c93d3b19..d7117b9d 100644 --- a/firmware/baseband/dsp_demodulate.hpp +++ b/firmware/baseband/dsp_demodulate.hpp @@ -35,6 +35,14 @@ public: ); }; +class SSB { +public: + buffer_f32_t execute( + const buffer_c16_t& src, + const buffer_f32_t& dst + ); +}; + class FM { public: buffer_f32_t execute( diff --git a/firmware/baseband/dsp_iir_config.hpp b/firmware/baseband/dsp_iir_config.hpp deleted file mode 100644 index 096acd99..00000000 --- a/firmware/baseband/dsp_iir_config.hpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. - * - * This file is part of PortaPack. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __DSP_IIR_CONFIG_H__ -#define __DSP_IIR_CONFIG_H__ - -#include "dsp_iir.hpp" - -// scipy.signal.butter(2, 30 / 24000.0, 'highpass', analog=False) -constexpr iir_biquad_config_t audio_hpf_30hz_config { - { 0.99722705f, -1.99445410f, 0.99722705f }, - { 1.00000000f, -1.99444641f, 0.99446179f } -}; - -// scipy.signal.butter(2, 300 / 24000.0, 'highpass', analog=False) -constexpr iir_biquad_config_t audio_hpf_300hz_config { - { 0.97261390f, -1.94522780f, 0.97261390f }, - { 1.00000000f, -1.94447766f, 0.94597794f } -}; - -// scipy.signal.iirdesign(wp=8000 / 24000.0, ws= 4000 / 24000.0, gpass=1, gstop=18, ftype='ellip') -constexpr iir_biquad_config_t non_audio_hpf_config { - { 0.51891061f, -0.95714180f, 0.51891061f }, - { 1.0f , -0.79878302f, 0.43960231f } -}; - -// scipy.signal.butter(1, 300 / 24000.0, 'lowpass', analog=False) -// NOTE: Technically, order-1 filter, b[2] = a[2] = 0. -constexpr iir_biquad_config_t audio_deemph_300_6_config { - { 0.01925927f, 0.01925927f, 0.00000000f }, - { 1.00000000f, -0.96148145f, 0.00000000f } -}; - -// 75us RC time constant, used in broadcast FM in Americas, South Korea -// scipy.signal.butter(1, 2122 / 24000.0, 'lowpass', analog=False) -// NOTE: Technically, order-1 filter, b[2] = a[2] = 0. -constexpr iir_biquad_config_t audio_deemph_2122_6_config { - { 0.12264116f, 0.12264116f, 0.00000000f }, - { 1.00000000f, -0.75471767f, 0.00000000f } -}; - -#endif/*__DSP_IIR_CONFIG_H__*/ diff --git a/firmware/baseband/proc_am_audio.cpp b/firmware/baseband/proc_am_audio.cpp index 06314ee0..3a073db4 100644 --- a/firmware/baseband/proc_am_audio.cpp +++ b/firmware/baseband/proc_am_audio.cpp @@ -21,7 +21,6 @@ #include "proc_am_audio.hpp" -#include "dsp_iir_config.hpp" #include "audio_output.hpp" #include @@ -33,17 +32,25 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) { const auto decim_0_out = decim_0.execute(buffer, dst_buffer); const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); - const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer); + const auto decim_2_out = decim_2.execute(decim_1_out, dst_buffer); + const auto channel_out = channel_filter.execute(decim_2_out, dst_buffer); // TODO: Feed channel_stats post-decimation data? feed_channel_stats(channel_out); channel_spectrum.feed(channel_out, channel_filter_pass_f, channel_filter_stop_f); - auto audio = demod.execute(channel_out, work_audio_buffer); - + auto audio = demodulate(channel_out); audio_output.write(audio); } +buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) { + if( modulation_ssb ) { + return demod_ssb.execute(channel, audio_buffer); + } else { + return demod_am.execute(channel, audio_buffer); + } +} + void NarrowbandAMAudio::on_message(const Message* const message) { switch(message->id) { case Message::ID::UpdateSpectrum: @@ -61,27 +68,27 @@ void NarrowbandAMAudio::on_message(const Message* const message) { } void NarrowbandAMAudio::configure(const AMConfigureMessage& message) { - constexpr size_t baseband_fs = 3072000; - constexpr size_t decim_0_input_fs = baseband_fs; - constexpr size_t decim_0_decimation_factor = 8; - constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0_decimation_factor; + 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; - constexpr size_t decim_1_decimation_factor = 8; - constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1_decimation_factor; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; - constexpr size_t channel_filter_input_fs = decim_1_output_fs; - constexpr size_t channel_filter_decimation_factor = 1; - constexpr size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor; + constexpr size_t decim_2_input_fs = decim_1_output_fs; + constexpr size_t decim_2_output_fs = decim_2_input_fs / decim_2_decimation_factor; + + 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); decim_1.configure(message.decim_1_filter.taps, 131072); + decim_2.configure(message.decim_2_filter.taps, decim_2_decimation_factor); channel_filter.configure(message.channel_filter.taps, channel_filter_decimation_factor); channel_filter_pass_f = message.channel_filter.pass_frequency_normalized * channel_filter_input_fs; channel_filter_stop_f = message.channel_filter.stop_frequency_normalized * channel_filter_input_fs; channel_spectrum.set_decimation_factor(std::floor((channel_filter_output_fs / 2) / ((channel_filter_pass_f + channel_filter_stop_f) / 2))); - audio_output.configure(audio_hpf_300hz_config); + modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB); + audio_output.configure(message.audio_hpf_config); configured = true; } diff --git a/firmware/baseband/proc_am_audio.hpp b/firmware/baseband/proc_am_audio.hpp index 5da5136d..226720e2 100644 --- a/firmware/baseband/proc_am_audio.hpp +++ b/firmware/baseband/proc_am_audio.hpp @@ -39,23 +39,31 @@ public: void on_message(const Message* const message) override; private: + static constexpr size_t baseband_fs = 3072000; + static constexpr size_t decim_2_decimation_factor = 4; + static constexpr size_t channel_filter_decimation_factor = 1; + std::array dst; const buffer_c16_t dst_buffer { dst.data(), dst.size() }; - const buffer_f32_t work_audio_buffer { - (float*)dst.data(), - sizeof(dst) / sizeof(float) + std::array audio; + const buffer_f32_t audio_buffer { + audio.data(), + audio.size() }; dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0; dsp::decimate::FIRC16xR16x32Decim8 decim_1; + dsp::decimate::FIRAndDecimateComplex decim_2; dsp::decimate::FIRAndDecimateComplex channel_filter; - uint32_t channel_filter_pass_f; - uint32_t channel_filter_stop_f; + uint32_t channel_filter_pass_f = 0; + uint32_t channel_filter_stop_f = 0; - dsp::demodulate::AM demod; + bool modulation_ssb = false; + dsp::demodulate::AM demod_am; + dsp::demodulate::SSB demod_ssb; AudioOutput audio_output; @@ -63,6 +71,8 @@ private: bool configured { false }; void configure(const AMConfigureMessage& message); + + buffer_f32_t demodulate(const buffer_c16_t& channel); }; #endif/*__PROC_AM_AUDIO_H__*/ diff --git a/firmware/baseband/proc_nfm_audio.cpp b/firmware/baseband/proc_nfm_audio.cpp index 906617cd..2a0349ac 100644 --- a/firmware/baseband/proc_nfm_audio.cpp +++ b/firmware/baseband/proc_nfm_audio.cpp @@ -21,7 +21,6 @@ #include "proc_nfm_audio.hpp" -#include "dsp_iir_config.hpp" #include "audio_output.hpp" #include @@ -39,8 +38,7 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) { feed_channel_stats(channel_out); channel_spectrum.feed(channel_out, channel_filter_pass_f, channel_filter_stop_f); - auto audio = demod.execute(channel_out, work_audio_buffer); - + auto audio = demod.execute(channel_out, audio_buffer); audio_output.write(audio); } @@ -61,30 +59,25 @@ void NarrowbandFMAudio::on_message(const Message* const message) { } void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) { - constexpr size_t baseband_fs = 3072000; - constexpr size_t decim_0_input_fs = baseband_fs; - constexpr size_t decim_0_decimation_factor = 8; - constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0_decimation_factor; + 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; - constexpr size_t decim_1_decimation_factor = 8; - constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1_decimation_factor; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; constexpr size_t channel_filter_input_fs = decim_1_output_fs; - constexpr size_t channel_filter_decimation_factor = 1; - constexpr size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor; + const size_t channel_filter_output_fs = channel_filter_input_fs / message.channel_decimation; - constexpr size_t demod_input_fs = channel_filter_output_fs; + const size_t demod_input_fs = channel_filter_output_fs; decim_0.configure(message.decim_0_filter.taps, 33554432); decim_1.configure(message.decim_1_filter.taps, 131072); - channel_filter.configure(message.channel_filter.taps, channel_filter_decimation_factor); + channel_filter.configure(message.channel_filter.taps, message.channel_decimation); demod.configure(demod_input_fs, message.deviation); channel_filter_pass_f = message.channel_filter.pass_frequency_normalized * channel_filter_input_fs; channel_filter_stop_f = message.channel_filter.stop_frequency_normalized * channel_filter_input_fs; channel_spectrum.set_decimation_factor(std::floor((channel_filter_output_fs / 2) / ((channel_filter_pass_f + channel_filter_stop_f) / 2))); - audio_output.configure(audio_hpf_300hz_config, audio_deemph_300_6_config, 6144); + audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, 12288); configured = true; } diff --git a/firmware/baseband/proc_nfm_audio.hpp b/firmware/baseband/proc_nfm_audio.hpp index 69ce6ee0..3133cc28 100644 --- a/firmware/baseband/proc_nfm_audio.hpp +++ b/firmware/baseband/proc_nfm_audio.hpp @@ -24,12 +24,15 @@ #include "baseband_processor.hpp" +#include "channel_decimator.hpp" #include "dsp_decimate.hpp" #include "dsp_demodulate.hpp" #include "audio_output.hpp" #include "spectrum_collector.hpp" +#include + class NarrowbandFMAudio : public BasebandProcessor { public: void execute(const buffer_c8_t& buffer) override; @@ -37,19 +40,21 @@ public: void on_message(const Message* const message) override; private: + static constexpr size_t baseband_fs = 3072000; + std::array dst; const buffer_c16_t dst_buffer { dst.data(), dst.size() }; - const buffer_f32_t work_audio_buffer { - (float*)dst.data(), - sizeof(dst) / sizeof(float) + std::array audio; + const buffer_f32_t audio_buffer { + audio.data(), + audio.size() }; dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0; dsp::decimate::FIRC16xR16x32Decim8 decim_1; - dsp::decimate::FIRAndDecimateComplex channel_filter; uint32_t channel_filter_pass_f = 0; uint32_t channel_filter_stop_f = 0; diff --git a/firmware/baseband/proc_wfm_audio.cpp b/firmware/baseband/proc_wfm_audio.cpp index 33526af8..4246a555 100644 --- a/firmware/baseband/proc_wfm_audio.cpp +++ b/firmware/baseband/proc_wfm_audio.cpp @@ -21,7 +21,6 @@ #include "proc_wfm_audio.hpp" -#include "dsp_iir_config.hpp" #include "audio_output.hpp" #include @@ -88,19 +87,14 @@ void WidebandFMAudio::on_message(const Message* const message) { } void WidebandFMAudio::configure(const WFMConfigureMessage& message) { - constexpr size_t baseband_fs = 3072000; - constexpr size_t decim_0_input_fs = baseband_fs; - constexpr size_t decim_0_decimation_factor = 4; - constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0_decimation_factor; + 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; - constexpr size_t decim_1_decimation_factor = 2; - constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1_decimation_factor; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; constexpr size_t demod_input_fs = decim_1_output_fs; - constexpr auto spectrum_rate_hz = 50.0f; spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz; spectrum_samples = 0; @@ -110,7 +104,7 @@ void WidebandFMAudio::configure(const WFMConfigureMessage& message) { channel_filter_stop_f = message.decim_1_filter.stop_frequency_normalized * decim_1_input_fs; demod.configure(demod_input_fs, message.deviation); audio_filter.configure(message.audio_filter.taps); - audio_output.configure(audio_hpf_30hz_config, audio_deemph_2122_6_config); + audio_output.configure(message.audio_hpf_config, message.audio_deemph_config); channel_spectrum.set_decimation_factor(1); diff --git a/firmware/baseband/proc_wfm_audio.hpp b/firmware/baseband/proc_wfm_audio.hpp index 5c30c2e9..15f5bf36 100644 --- a/firmware/baseband/proc_wfm_audio.hpp +++ b/firmware/baseband/proc_wfm_audio.hpp @@ -37,6 +37,9 @@ public: void on_message(const Message* const message) override; private: + static constexpr size_t baseband_fs = 3072000; + static constexpr auto spectrum_rate_hz = 50.0f; + std::array dst; const buffer_c16_t dst_buffer { dst.data(), diff --git a/firmware/baseband/spectrum_collector.hpp b/firmware/baseband/spectrum_collector.hpp index 4c2cd542..6c50120b 100644 --- a/firmware/baseband/spectrum_collector.hpp +++ b/firmware/baseband/spectrum_collector.hpp @@ -50,7 +50,7 @@ public: ); private: - BlockDecimator<256> channel_spectrum_decimator; + BlockDecimator channel_spectrum_decimator; ChannelSpectrumFIFO fifo; volatile bool channel_spectrum_request_update { false }; diff --git a/firmware/common/dsp_fir_taps.hpp b/firmware/common/dsp_fir_taps.hpp index c043cab9..535809b8 100644 --- a/firmware/common/dsp_fir_taps.hpp +++ b/firmware/common/dsp_fir_taps.hpp @@ -34,6 +34,13 @@ struct fir_taps_real { std::array taps; }; +template +struct fir_taps_complex { + float pass_frequency_normalized; + float stop_frequency_normalized; + std::array taps; +}; + // NBFM 16K0F3E emission type ///////////////////////////////////////////// // IFIR image-reject filter: fs=3072000, pass=8000, stop=344000, decim=8, fout=384000 @@ -170,8 +177,8 @@ constexpr fir_taps_real<32> taps_6k0_decim_1 { } }, }; -// Channel filter: fs=48000, pass=3000, stop=6700, decim=1, fout=48000 -constexpr fir_taps_real<32> taps_6k0_channel { +// IFIR prototype filter: fs=48000, pass=3000, stop=6700, decim=4, fout=12000 +constexpr fir_taps_real<32> taps_6k0_decim_2 { .pass_frequency_normalized = 3000.0f / 48000.0f, .stop_frequency_normalized = 6700.0f / 48000.0f, .taps = { { @@ -182,6 +189,85 @@ constexpr fir_taps_real<32> taps_6k0_channel { } }, }; +// Channel filter: fs=12000, pass=3000, stop=3300, decim=1, fout=12000 +/* NOTE: Slightly less than 1.0 gain (normalized to 65536) due to max(taps) being + * slightly larger than 32767 (33312). + */ +constexpr fir_taps_complex<64> taps_6k0_dsb_channel { + .pass_frequency_normalized = 3000.0f / 12000.0f, + .stop_frequency_normalized = 3300.0f / 12000.0f, + .taps = { { + { -69, 0 }, { -140, 0 }, { 119, 0 }, { 89, 0 }, + { -132, 0 }, { -134, 0 }, { 197, 0 }, { 167, 0 }, + { -273, 0 }, { -206, 0 }, { 372, 0 }, { 247, 0 }, + { -497, 0 }, { -289, 0 }, { 654, 0 }, { 331, 0 }, + { -854, 0 }, { -372, 0 }, { 1112, 0 }, { 411, 0 }, + { -1455, 0 }, { -446, 0 }, { 1933, 0 }, { 476, 0 }, + { -2654, 0 }, { -501, 0 }, { 3902, 0 }, { 520, 0 }, + { -6717, 0 }, { -531, 0 }, { 20478, 0 }, { 32767, 0 }, + { 20478, 0 }, { -531, 0 }, { -6717, 0 }, { 520, 0 }, + { 3902, 0 }, { -501, 0 }, { -2654, 0 }, { 476, 0 }, + { 1933, 0 }, { -446, 0 }, { -1455, 0 }, { 411, 0 }, + { 1112, 0 }, { -372, 0 }, { -854, 0 }, { 331, 0 }, + { 654, 0 }, { -289, 0 }, { -497, 0 }, { 247, 0 }, + { 372, 0 }, { -206, 0 }, { -273, 0 }, { 167, 0 }, + { 197, 0 }, { -134, 0 }, { -132, 0 }, { 89, 0 }, + { 119, 0 }, { -140, 0 }, { -69, 0 }, { 0, 0 }, + } }, +}; + +// USB AM 2K80J3E emission type /////////////////////////////////////////// + +// IFIR prototype filter: fs=12000, pass=3000, stop=3300, decim=1, fout=12000 +constexpr fir_taps_complex<64> taps_2k8_usb_channel { + .pass_frequency_normalized = 3000.0f / 12000.0f, + .stop_frequency_normalized = 3300.0f / 12000.0f, + .taps = { { + { -146, 0 }, { -41, -45 }, { -1, 10 }, { -95, 69 }, + { -194, -41 }, { -91, -158 }, { 14, -43 }, { -150, 67 }, + { -299, -133 }, { -100, -307 }, { 50, -86 }, { -254, 54 }, + { -453, -329 }, { -62, -587 }, { 170, -189 }, { -334, 0 }, + { -580, -645 }, { 104, -986 }, { 418, -304 }, { -412, -88 }, + { -680, -1178 }, { 527, -1623 }, { 970, -432 }, { -441, -196 }, + { -698, -2149 }, { 1617, -2800 }, { 2384, -507 }, { -429, -311 }, + { -545, -5181 }, { 6925, -7691 }, { 14340, 0 }, { 10601, 11773 }, + { -1499, 14261 }, { -8373, 6083 }, { -5095, -1083 }, { -265, -459 }, + { -753, 2318 }, { -2954, 1315 }, { -2064, -919 }, { -149, -459 }, + { -531, 920 }, { -1669, 355 }, { -1100, -800 }, { -44, -419 }, + { -346, 384 }, { -992, 0 }, { -580, -645 }, { 35, -332 }, + { -205, 149 }, { -577, -123 }, { -280, -485 }, { 80, -247 }, + { -91, 40 }, { -294, -131 }, { -101, -312 }, { 82, -142 }, + { -44, 9 }, { -147, -107 }, { -21, -197 }, { 79, -88 }, + { 10, 0 }, { -41, -45 }, { 15, -145 }, { 0, 0 }, + } }, +}; + +// LSB AM 2K80J3E emission type /////////////////////////////////////////// + +// IFIR prototype filter: fs=12000, pass=3000, stop=3300, decim=1, fout=12000 +constexpr fir_taps_complex<64> taps_2k8_lsb_channel { + .pass_frequency_normalized = 3000.0f / 12000.0f, + .stop_frequency_normalized = 3300.0f / 12000.0f, + .taps = { { + { -146, 0 }, { -41, 45 }, { -1, -10 }, { -95, -69 }, + { -194, 41 }, { -91, 158 }, { 14, 43 }, { -150, -67 }, + { -299, 133 }, { -100, 307 }, { 50, 86 }, { -254, -54 }, + { -453, 329 }, { -62, 587 }, { 170, 189 }, { -334, 0 }, + { -580, 645 }, { 104, 986 }, { 418, 304 }, { -412, 88 }, + { -680, 1178 }, { 527, 1623 }, { 970, 432 }, { -441, 196 }, + { -698, 2149 }, { 1617, 2800 }, { 2384, 507 }, { -429, 311 }, + { -545, 5181 }, { 6925, 7691 }, { 14340, 0 }, { 10601, -11773 }, + { -1499, -14261 }, { -8373, -6083 }, { -5095, 1083 }, { -265, 459 }, + { -753, -2318 }, { -2954, -1315 }, { -2064, 919 }, { -149, 459 }, + { -531, -920 }, { -1669, -355 }, { -1100, 800 }, { -44, 419 }, + { -346, -384 }, { -992, 0 }, { -580, 645 }, { 35, 332 }, + { -205, -149 }, { -577, 123 }, { -280, 485 }, { 80, 247 }, + { -91, -40 }, { -294, 131 }, { -101, 312 }, { 82, 142 }, + { -44, -9 }, { -147, 107 }, { -21, 197 }, { 79, 88 }, + { 10, 0 }, { -41, 45 }, { 15, 145 }, { 0, 0 }, + } }, +}; + // WFM 200KF8E emission type ////////////////////////////////////////////// // IFIR image-reject filter: fs=3072000, pass=100000, stop=484000, decim=4, fout=768000 diff --git a/firmware/baseband-tx/dsp_fir_taps.cpp b/firmware/common/dsp_iir.cpp similarity index 51% rename from firmware/baseband-tx/dsp_fir_taps.cpp rename to firmware/common/dsp_iir.cpp index b8bed3f2..443183ba 100644 --- a/firmware/baseband-tx/dsp_fir_taps.cpp +++ b/firmware/common/dsp_iir.cpp @@ -19,4 +19,39 @@ * Boston, MA 02110-1301, USA. */ -#include "dsp_fir_taps.hpp" +#include "dsp_iir.hpp" + +#include + +void IIRBiquadFilter::configure(const iir_biquad_config_t& new_config) { + config = new_config; +} + +void IIRBiquadFilter::execute(const buffer_f32_t& buffer_in, const buffer_f32_t& buffer_out) { + const auto a_ = config.a; + const auto b_ = config.b; + + auto x_ = x; + auto y_ = y; + + // TODO: Assert that buffer_out.count == buffer_in.count. + for(size_t i=0; i> 3] & (1U << (i & 0x7)); + io.lcd_write_pixel(pixel ? foreground : background); + } +} + void ILI9341::draw_glyph( const ui::Point p, const ui::Glyph& glyph, const ui::Color foreground, const ui::Color background ) { - lcd_start_ram_write(p, glyph.size()); - - const size_t count = glyph.w() * glyph.h(); - const auto pixels = glyph.pixels(); - for(size_t i=0; i> 3] & (1U << (i & 0x7)); - io.lcd_write_pixel(pixel ? foreground : background); - } + draw_bitmap(p, glyph.size(), glyph.pixels(), foreground, background); } void ILI9341::scroll_set_area( diff --git a/firmware/common/lcd_ili9341.hpp b/firmware/common/lcd_ili9341.hpp index 888a7699..0f735a0d 100644 --- a/firmware/common/lcd_ili9341.hpp +++ b/firmware/common/lcd_ili9341.hpp @@ -69,6 +69,14 @@ public: draw_pixels(r, colors.data(), colors.size()); } + void draw_bitmap( + const ui::Point p, + const ui::Size size, + const uint8_t* const data, + const ui::Color foreground, + const ui::Color background + ); + void draw_glyph( const ui::Point p, const ui::Glyph& glyph, diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 95657f99..d3506948 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -30,6 +30,7 @@ #include "baseband_packet.hpp" #include "ert_packet.hpp" #include "dsp_fir_taps.hpp" +#include "dsp_iir.hpp" #include "fifo.hpp" #include "utility.hpp" @@ -38,7 +39,7 @@ class Message { public: - static constexpr size_t MAX_SIZE = 276; + static constexpr size_t MAX_SIZE = 512; enum class ID : uint32_t { /* Assign consecutive IDs. IDs are used to index array. */ @@ -326,19 +327,28 @@ public: const fir_taps_real<24> decim_0_filter, const fir_taps_real<32> decim_1_filter, const fir_taps_real<32> channel_filter, - const size_t deviation + const size_t channel_decimation, + const size_t deviation, + const iir_biquad_config_t audio_hpf_config, + const iir_biquad_config_t audio_deemph_config ) : Message { ID::NBFMConfigure }, decim_0_filter(decim_0_filter), decim_1_filter(decim_1_filter), channel_filter(channel_filter), - deviation { deviation } + channel_decimation { channel_decimation }, + deviation { deviation }, + audio_hpf_config(audio_hpf_config), + audio_deemph_config(audio_deemph_config) { } const fir_taps_real<24> decim_0_filter; const fir_taps_real<32> decim_1_filter; const fir_taps_real<32> channel_filter; + const size_t channel_decimation; const size_t deviation; + const iir_biquad_config_t audio_hpf_config; + const iir_biquad_config_t audio_deemph_config; }; class WFMConfigureMessage : public Message { @@ -347,12 +357,16 @@ public: const fir_taps_real<24> decim_0_filter, const fir_taps_real<16> decim_1_filter, const fir_taps_real<64> audio_filter, - const size_t deviation + const size_t deviation, + const iir_biquad_config_t audio_hpf_config, + const iir_biquad_config_t audio_deemph_config ) : Message { ID::WFMConfigure }, decim_0_filter(decim_0_filter), decim_1_filter(decim_1_filter), audio_filter(audio_filter), - deviation { deviation } + deviation { deviation }, + audio_hpf_config(audio_hpf_config), + audio_deemph_config(audio_deemph_config) { } @@ -360,24 +374,40 @@ public: const fir_taps_real<16> decim_1_filter; const fir_taps_real<64> audio_filter; const size_t deviation; + const iir_biquad_config_t audio_hpf_config; + const iir_biquad_config_t audio_deemph_config; }; class AMConfigureMessage : public Message { public: + enum class Modulation : int32_t { + DSB = 0, + SSB = 1, + }; + constexpr AMConfigureMessage( const fir_taps_real<24> decim_0_filter, const fir_taps_real<32> decim_1_filter, - const fir_taps_real<32> channel_filter + const fir_taps_real<32> decim_2_filter, + const fir_taps_complex<64> channel_filter, + const Modulation modulation, + const iir_biquad_config_t audio_hpf_config ) : Message { ID::AMConfigure }, decim_0_filter(decim_0_filter), decim_1_filter(decim_1_filter), - channel_filter(channel_filter) + decim_2_filter(decim_2_filter), + channel_filter(channel_filter), + modulation { modulation }, + audio_hpf_config(audio_hpf_config) { } const fir_taps_real<24> decim_0_filter; const fir_taps_real<32> decim_1_filter; - const fir_taps_real<32> channel_filter; + const fir_taps_real<32> decim_2_filter; + const fir_taps_complex<64> channel_filter; + const Modulation modulation; + const iir_biquad_config_t audio_hpf_config; }; class TXDoneMessage : public Message { diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index 14749f64..035b4b8a 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -273,6 +273,11 @@ struct Rect { } }; +struct Bitmap { + const Size size; + const uint8_t* const data; +}; + enum class KeyEvent { /* Ordinals map to bit positions reported by CPLD */ Right = 0, diff --git a/firmware/common/ui_painter.cpp b/firmware/common/ui_painter.cpp index 7409b8eb..2b54e194 100644 --- a/firmware/common/ui_painter.cpp +++ b/firmware/common/ui_painter.cpp @@ -54,6 +54,10 @@ int Painter::draw_string(Point p, const Style& style, const std::string text) { return width; } +void Painter::draw_bitmap(const Point p, const Bitmap& bitmap, const Color foreground, const Color background) { + display.draw_bitmap(p, bitmap.size, bitmap.data, foreground, background); +} + void Painter::draw_hline(Point p, int width, const Color c) { display.fill_rectangle({ p, { width, 1 } }, c); } diff --git a/firmware/common/ui_painter.hpp b/firmware/common/ui_painter.hpp index 7ac7b40a..98d6d36e 100644 --- a/firmware/common/ui_painter.hpp +++ b/firmware/common/ui_painter.hpp @@ -50,6 +50,8 @@ public: int draw_string(Point p, const Style& style, const std::string text); + void draw_bitmap(const Point p, const Bitmap& bitmap, const Color background, const Color foreground); + void draw_rectangle(const Rect r, const Color c); void fill_rectangle(const Rect r, const Color c);