diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index bacffca3e..4e5e1d735 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -28,7 +28,7 @@ set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack) set(HACKRF_FIRMWARE_FILENAME hackrf_one_usb.dfu) set(HACKRF_FIRMWARE_IMAGE ${PROJECT_SOURCE_DIR}/${HACKRF_FIRMWARE_FILENAME}) -set(HACKRF_CPLD_SVF_FILENAME hackrf_cpld_default.svf) +set(HACKRF_CPLD_SVF_FILENAME hackrf_cpld_portapack.svf) set(HACKRF_CPLD_SVF_PATH ${PROJECT_SOURCE_DIR}/${HACKRF_CPLD_SVF_FILENAME}) set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py) diff --git a/firmware/application/analog_audio_app.cpp b/firmware/application/analog_audio_app.cpp index 57e7183b9..91a682f5f 100644 --- a/firmware/application/analog_audio_app.cpp +++ b/firmware/application/analog_audio_app.cpp @@ -66,12 +66,19 @@ NBFMOptionsView::NBFMOptionsView( add_children({ &label_config, &options_config, + &text_squelch, + &field_squelch }); 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); }; + + field_squelch.set_value(receiver_model.squelch_level()); + field_squelch.on_change = [this](int32_t v) { + receiver_model.set_squelch_level(v); + }; } /* AnalogAudioView *******************************************************/ diff --git a/firmware/application/analog_audio_app.hpp b/firmware/application/analog_audio_app.hpp index a82925163..61294e01f 100644 --- a/firmware/application/analog_audio_app.hpp +++ b/firmware/application/analog_audio_app.hpp @@ -78,6 +78,19 @@ private: { "16k ", 0 }, } }; + + Text text_squelch { + { 9 * 8, 0 * 16, 7 * 8, 1 * 16 }, + "SQ /100" + }; + + NumberField field_squelch { + { 12 * 8, 0 * 16 }, + 3, + { 0, 100 }, + 1, + ' ', + }; }; class AnalogAudioView : public View { diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index 593068993..f35255f83 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -52,7 +52,7 @@ void AMConfig::apply() const { audio::set_rate(audio::Rate::Hz_12000); } -void NBFMConfig::apply() const { +void NBFMConfig::apply(const uint8_t squelch_level) const { const NBFMConfigureMessage message { decim_0, decim_1, @@ -60,7 +60,8 @@ void NBFMConfig::apply() const { 2, deviation, audio_24k_hpf_300hz_config, - audio_24k_deemph_300_6_config + audio_24k_deemph_300_6_config, + squelch_level }; send_message(&message); audio::set_rate(audio::Rate::Hz_24000); diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index e166c56a1..39fb3ee53 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -48,7 +48,7 @@ struct NBFMConfig { const fir_taps_real<32> channel; const size_t deviation; - void apply() const; + void apply(const uint8_t squelch_level) const; }; struct WFMConfig { diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index 3392068b9..9b68bbe6f 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -153,6 +153,15 @@ void ReceiverModel::set_headphone_volume(volume_t v) { update_headphone_volume(); } +uint8_t ReceiverModel::squelch_level() const { + return squelch_level_; +} + +void ReceiverModel::set_squelch_level(uint8_t v) { + squelch_level_ = v; + update_modulation(); +} + void ReceiverModel::enable() { enabled_ = true; radio::set_direction(rf::Direction::Receive); @@ -286,7 +295,7 @@ size_t ReceiverModel::nbfm_configuration() const { } void ReceiverModel::update_nbfm_configuration() { - nbfm_configs[nbfm_config_index].apply(); + nbfm_configs[nbfm_config_index].apply(squelch_level_); } size_t ReceiverModel::wfm_configuration() const { diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index 1d6d4783c..c3d993d14 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -71,6 +71,9 @@ public: volume_t headphone_volume() const; void set_headphone_volume(volume_t v); + + uint8_t squelch_level() const; + void set_squelch_level(uint8_t v); void enable(); void disable(); @@ -99,6 +102,7 @@ private: size_t nbfm_config_index = 0; size_t wfm_config_index = 0; volume_t headphone_volume_ { -43.0_dB }; + uint8_t squelch_level_ { 80 }; int32_t tuning_offset(); diff --git a/firmware/application/ui_scanner.cpp b/firmware/application/ui_scanner.cpp index 1a75908eb..2a2a93052 100644 --- a/firmware/application/ui_scanner.cpp +++ b/firmware/application/ui_scanner.cpp @@ -66,6 +66,14 @@ void ScannerView::do_detection() { rtc::RTC datetime; std::string str_approx, str_timestamp; + // Display spectrum + bin_skip_acc = 0; + pixel_index = 0; + display.draw_pixels( + { { 0, 88 }, { (Dim)spectrum_row.size(), 1 } }, + spectrum_row + ); + mean_power = mean_acc / (SCAN_BIN_NB_NO_DC * slices_nb); mean_acc = 0; @@ -79,8 +87,8 @@ void ScannerView::do_detection() { if ((power >= mean_power + power_threshold) && (power > power_max)) { power_max = power; - bin_max_pixel = slices[slice].max_index; - bin_max = bin_max_pixel + (slice * SCAN_BIN_NB); + bin_max = slices[slice].max_index + (slice * SCAN_BIN_NB); + bin_max_pixel = bin_max / slices_nb; } } @@ -157,63 +165,65 @@ void ScannerView::do_detection() { scan_counter++; // Refresh red tick - portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::black()); + portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Color::black()); if (bin_max > -1) { - if (bin_max_pixel < 120) - bin_max_pixel += 2; + //if (bin_max_pixel < 120) + // bin_max_pixel += 2; //else // bin_max_pixel -= 0; - last_pos = (ui::Coord)bin_max_pixel; - portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::red()); + last_tick_pos = (Coord)bin_max_pixel; + portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Color::red()); } } +void ScannerView::add_spectrum_pixel(Color color) { + // Is avoiding floats really needed ? + bin_skip_acc += bin_skip_frac; + if (bin_skip_acc < 0x10000) + return; + + bin_skip_acc -= 0x10000; + + if (pixel_index < 240) + spectrum_row[pixel_index++] = color; +} + void ScannerView::on_channel_spectrum(const ChannelSpectrum& spectrum) { - uint8_t power_max = 0; - int16_t bin_max = 0; + uint8_t max_power = 0; + int16_t max_bin = 0; uint8_t power; size_t bin; - std::array pixel_row; baseband::spectrum_streaming_stop(); - // Draw spectrum line + // Add pixels to spectrum row, and find max power for this slice + // Leftmost and rightmost 2 bins are ignored + // Center 12 bins are ignored + // 256-2-2-12 = 240 bins used for (bin = 0; bin < 120; bin++) { - const auto pixel_color = spectrum_rgb3_lut[spectrum.db[134 + bin]]; // 134~253 in 0~119 - pixel_row[bin] = pixel_color; - } - for (bin = 120; bin < 240; bin++) { - const auto pixel_color = spectrum_rgb3_lut[spectrum.db[bin - 118]]; // 2~121 in 120~239 - pixel_row[bin] = pixel_color; - } - display.draw_pixels( - { { 0, 88 + slice_counter * 2 }, { pixel_row.size(), 1 } }, - pixel_row - ); - - // Find max power for this slice - for (bin = 0 ; bin < 120; bin++) { + add_spectrum_pixel(spectrum_rgb3_lut[spectrum.db[134 + bin]]); // 134~253 goes in 0~119 power = spectrum.db[134 + bin]; mean_acc += power; - if (power > power_max) { - power_max = power; - bin_max = bin - 2; + if (power > max_power) { + max_power = power; + max_bin = bin - 2; // To check } } for (bin = 120; bin < 240; bin++) { + add_spectrum_pixel(spectrum_rgb3_lut[spectrum.db[bin - 118]]); // 2~121 goes in 120~239 power = spectrum.db[bin - 118]; mean_acc += power; - if (power > power_max) { - power_max = power; - bin_max = bin + 2; + if (power > max_power) { + max_power = power; + max_bin = bin + 2; // To check } } - slices[slice_counter].max_power = power_max; - slices[slice_counter].max_index = bin_max; + slices[slice_counter].max_power = max_power; + slices[slice_counter].max_index = max_bin; - // Slice update if (slices_nb > 1) { + // Slice sequence slice_counter++; if (slice_counter >= slices_nb) { do_detection(); @@ -221,6 +231,7 @@ void ScannerView::on_channel_spectrum(const ChannelSpectrum& spectrum) { } receiver_model.set_tuning_frequency(slices[slice_counter].center_frequency); } else { + // Unique slice do_detection(); } @@ -272,6 +283,8 @@ void ScannerView::on_range_changed() { slices_nb = 1; text_slices.set(" 1"); } + + bin_skip_frac = 0x10000 / slices_nb; slice_counter = 0; } @@ -353,7 +366,7 @@ ScannerView::ScannerView( &recent_entries_view }); - baseband::set_spectrum(SCAN_SLICE_WIDTH, 32); + baseband::set_spectrum(SCAN_SLICE_WIDTH, 31); recent_entries_view.set_parent_rect({ 0, 28 * 8, 240, 12 * 8 }); recent_entries_view.on_select = [this, &nav](const ScannerRecentEntry& entry) { diff --git a/firmware/application/ui_scanner.hpp b/firmware/application/ui_scanner.hpp index 3e84f470a..4cf967027 100644 --- a/firmware/application/ui_scanner.hpp +++ b/firmware/application/ui_scanner.hpp @@ -112,6 +112,9 @@ private: int16_t index; } slices[32]; + uint32_t bin_skip_acc { 0 }, bin_skip_frac { }; + uint32_t pixel_index { 0 }; + std::array spectrum_row { 0 }; ChannelSpectrumFIFO* fifo { nullptr }; rf::Frequency f_min { 0 }, f_max { 0 }; uint8_t detect_timer { 0 }, release_timer { 0 }, timing_div { 0 }; @@ -123,7 +126,7 @@ private: uint8_t slices_nb { 0 }; uint8_t slice_counter { 0 }; int16_t last_bin { 0 }; - Coord last_pos { 0 }; + Coord last_tick_pos { 0 }; rf::Frequency scan_span { 0 }, resolved_frequency { 0 }; uint16_t locked_bin { 0 }; uint8_t scan_counter { 0 }; @@ -135,6 +138,7 @@ private: void on_lna_changed(int32_t v_db); void on_vga_changed(int32_t v_db); void do_timers(); + void add_spectrum_pixel(Color color); const RecentEntriesColumns columns { { { "Frequency", 9 }, @@ -149,7 +153,7 @@ private: { { 1 * 8, 4 * 8 }, "Trig: /255 Mean: /255", Color::light_grey() }, { { 1 * 8, 6 * 8 }, "Slices: /32 Rate: Hz", Color::light_grey() }, { { 6 * 8, 10 * 8 }, "Timer Status", Color::light_grey() }, - { { 1 * 8, 25 * 8 }, "Accuracy: +/-4.9kHz", Color::light_grey() }, + { { 1 * 8, 25 * 8 }, "Accuracy +/-4.9kHz", Color::light_grey() }, { { 26 * 8, 25 * 8 }, "MHz", Color::light_grey() } }; @@ -188,12 +192,12 @@ private: VuMeter vu_max { { 1 * 8, 11 * 8 - 4, 3 * 8, 48 }, - 16, + 18, false }; ProgressBar progress_timers { - { 6 * 8, 12 * 8, 5 * 8, 16 } + { 6 * 8, 12 * 8, 6 * 8, 16 } }; Text text_infos { { 13 * 8, 12 * 8, 15 * 8, 16 }, @@ -203,11 +207,11 @@ private: Checkbox check_snap { { 6 * 8, 15 * 8 }, 7, - "Adjust:", + "Snap to:", true }; OptionsField options_snap { - { 15 * 8, 15 * 8 }, + { 17 * 8, 15 * 8 }, 7, { { "25kHz ", 25000 }, diff --git a/firmware/baseband/proc_nfm_audio.cpp b/firmware/baseband/proc_nfm_audio.cpp index b25b13a8d..ecd609961 100644 --- a/firmware/baseband/proc_nfm_audio.cpp +++ b/firmware/baseband/proc_nfm_audio.cpp @@ -112,8 +112,7 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) { 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 / (channel_filter_pass_f + channel_filter_stop_f))); - // TODO: Configurable squelch threshold - audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, 0.8f); + audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, (float)message.squelch_level / 100.0); synth_acc = 0; diff --git a/firmware/baseband/proc_wideband_spectrum.cpp b/firmware/baseband/proc_wideband_spectrum.cpp index f17ec245c..2251572e1 100644 --- a/firmware/baseband/proc_wideband_spectrum.cpp +++ b/firmware/baseband/proc_wideband_spectrum.cpp @@ -23,8 +23,6 @@ #include "event_m4.hpp" -#include "dsp_fft.hpp" - #include #include diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index c1988d5ac..ca28fbf7a 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -356,7 +356,8 @@ public: 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 + const iir_biquad_config_t audio_deemph_config, + const uint8_t squelch_level ) : Message { ID::NBFMConfigure }, decim_0_filter(decim_0_filter), decim_1_filter(decim_1_filter), @@ -364,7 +365,8 @@ public: channel_decimation { channel_decimation }, deviation { deviation }, audio_hpf_config(audio_hpf_config), - audio_deemph_config(audio_deemph_config) + audio_deemph_config(audio_deemph_config), + squelch_level(squelch_level) { } @@ -375,6 +377,7 @@ public: const size_t deviation; const iir_biquad_config_t audio_hpf_config; const iir_biquad_config_t audio_deemph_config; + const uint8_t squelch_level; }; class WFMConfigureMessage : public Message { diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 1fe80a50f..4941c7102 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -1461,7 +1461,7 @@ VuMeter::VuMeter( show_max_ { show_max } { //set_focusable(false); - LED_height = parent_rect.size().height() / LEDs; + LED_height = std::max(1UL, parent_rect.size().height() / LEDs); split = 256 / LEDs; } @@ -1507,7 +1507,7 @@ void VuMeter::paint(Painter& painter) { else color = lit ? Color::green() : Color::dark_green(); - painter.fill_rectangle({ pos.x(), pos.y() + (Coord)(bar * LED_height), width, (Coord)LED_height - 2 }, color); + painter.fill_rectangle({ pos.x(), pos.y() + (Coord)(bar * (LED_height + 1)), width, (Coord)LED_height }, color); } prev_value = value_; }