Using new CPLD data (fixes spectrum mirroring)

Scanner bugfix for wide ranges
Added squelch parameter for NFM receiver
Adjustment to Vumeter widget rendering
This commit is contained in:
furrtek 2017-06-11 09:50:29 +01:00
parent 042d271a9f
commit e2f0a03460
13 changed files with 105 additions and 54 deletions

View File

@ -28,7 +28,7 @@ set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack)
set(HACKRF_FIRMWARE_FILENAME hackrf_one_usb.dfu) set(HACKRF_FIRMWARE_FILENAME hackrf_one_usb.dfu)
set(HACKRF_FIRMWARE_IMAGE ${PROJECT_SOURCE_DIR}/${HACKRF_FIRMWARE_FILENAME}) 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(HACKRF_CPLD_SVF_PATH ${PROJECT_SOURCE_DIR}/${HACKRF_CPLD_SVF_FILENAME})
set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py) set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py)

View File

@ -66,12 +66,19 @@ NBFMOptionsView::NBFMOptionsView(
add_children({ add_children({
&label_config, &label_config,
&options_config, &options_config,
&text_squelch,
&field_squelch
}); });
options_config.set_selected_index(receiver_model.nbfm_configuration()); options_config.set_selected_index(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) { options_config.on_change = [this](size_t n, OptionsField::value_t) {
receiver_model.set_nbfm_configuration(n); 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 *******************************************************/ /* AnalogAudioView *******************************************************/

View File

@ -78,6 +78,19 @@ private:
{ "16k ", 0 }, { "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 { class AnalogAudioView : public View {

View File

@ -52,7 +52,7 @@ void AMConfig::apply() const {
audio::set_rate(audio::Rate::Hz_12000); audio::set_rate(audio::Rate::Hz_12000);
} }
void NBFMConfig::apply() const { void NBFMConfig::apply(const uint8_t squelch_level) const {
const NBFMConfigureMessage message { const NBFMConfigureMessage message {
decim_0, decim_0,
decim_1, decim_1,
@ -60,7 +60,8 @@ void NBFMConfig::apply() const {
2, 2,
deviation, deviation,
audio_24k_hpf_300hz_config, audio_24k_hpf_300hz_config,
audio_24k_deemph_300_6_config audio_24k_deemph_300_6_config,
squelch_level
}; };
send_message(&message); send_message(&message);
audio::set_rate(audio::Rate::Hz_24000); audio::set_rate(audio::Rate::Hz_24000);

View File

@ -48,7 +48,7 @@ struct NBFMConfig {
const fir_taps_real<32> channel; const fir_taps_real<32> channel;
const size_t deviation; const size_t deviation;
void apply() const; void apply(const uint8_t squelch_level) const;
}; };
struct WFMConfig { struct WFMConfig {

View File

@ -153,6 +153,15 @@ void ReceiverModel::set_headphone_volume(volume_t v) {
update_headphone_volume(); 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() { void ReceiverModel::enable() {
enabled_ = true; enabled_ = true;
radio::set_direction(rf::Direction::Receive); radio::set_direction(rf::Direction::Receive);
@ -286,7 +295,7 @@ size_t ReceiverModel::nbfm_configuration() const {
} }
void ReceiverModel::update_nbfm_configuration() { 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 { size_t ReceiverModel::wfm_configuration() const {

View File

@ -72,6 +72,9 @@ public:
volume_t headphone_volume() const; volume_t headphone_volume() const;
void set_headphone_volume(volume_t v); void set_headphone_volume(volume_t v);
uint8_t squelch_level() const;
void set_squelch_level(uint8_t v);
void enable(); void enable();
void disable(); void disable();
@ -99,6 +102,7 @@ private:
size_t nbfm_config_index = 0; size_t nbfm_config_index = 0;
size_t wfm_config_index = 0; size_t wfm_config_index = 0;
volume_t headphone_volume_ { -43.0_dB }; volume_t headphone_volume_ { -43.0_dB };
uint8_t squelch_level_ { 80 };
int32_t tuning_offset(); int32_t tuning_offset();

View File

@ -66,6 +66,14 @@ void ScannerView::do_detection() {
rtc::RTC datetime; rtc::RTC datetime;
std::string str_approx, str_timestamp; 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_power = mean_acc / (SCAN_BIN_NB_NO_DC * slices_nb);
mean_acc = 0; mean_acc = 0;
@ -79,8 +87,8 @@ void ScannerView::do_detection() {
if ((power >= mean_power + power_threshold) && (power > power_max)) { if ((power >= mean_power + power_threshold) && (power > power_max)) {
power_max = power; power_max = power;
bin_max_pixel = slices[slice].max_index; bin_max = slices[slice].max_index + (slice * SCAN_BIN_NB);
bin_max = bin_max_pixel + (slice * SCAN_BIN_NB); bin_max_pixel = bin_max / slices_nb;
} }
} }
@ -157,63 +165,65 @@ void ScannerView::do_detection() {
scan_counter++; scan_counter++;
// Refresh red tick // 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 > -1) {
if (bin_max_pixel < 120) //if (bin_max_pixel < 120)
bin_max_pixel += 2; // bin_max_pixel += 2;
//else //else
// bin_max_pixel -= 0; // bin_max_pixel -= 0;
last_pos = (ui::Coord)bin_max_pixel; last_tick_pos = (Coord)bin_max_pixel;
portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::red()); 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) { void ScannerView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
uint8_t power_max = 0; uint8_t max_power = 0;
int16_t bin_max = 0; int16_t max_bin = 0;
uint8_t power; uint8_t power;
size_t bin; size_t bin;
std::array<Color, 240> pixel_row;
baseband::spectrum_streaming_stop(); 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++) { for (bin = 0; bin < 120; bin++) {
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[134 + bin]]; // 134~253 in 0~119 add_spectrum_pixel(spectrum_rgb3_lut[spectrum.db[134 + bin]]); // 134~253 goes 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++) {
power = spectrum.db[134 + bin]; power = spectrum.db[134 + bin];
mean_acc += power; mean_acc += power;
if (power > power_max) { if (power > max_power) {
power_max = power; max_power = power;
bin_max = bin - 2; max_bin = bin - 2; // To check
} }
} }
for (bin = 120; bin < 240; bin++) { 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]; power = spectrum.db[bin - 118];
mean_acc += power; mean_acc += power;
if (power > power_max) { if (power > max_power) {
power_max = power; max_power = power;
bin_max = bin + 2; max_bin = bin + 2; // To check
} }
} }
slices[slice_counter].max_power = power_max; slices[slice_counter].max_power = max_power;
slices[slice_counter].max_index = bin_max; slices[slice_counter].max_index = max_bin;
// Slice update
if (slices_nb > 1) { if (slices_nb > 1) {
// Slice sequence
slice_counter++; slice_counter++;
if (slice_counter >= slices_nb) { if (slice_counter >= slices_nb) {
do_detection(); do_detection();
@ -221,6 +231,7 @@ void ScannerView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
} }
receiver_model.set_tuning_frequency(slices[slice_counter].center_frequency); receiver_model.set_tuning_frequency(slices[slice_counter].center_frequency);
} else { } else {
// Unique slice
do_detection(); do_detection();
} }
@ -273,6 +284,8 @@ void ScannerView::on_range_changed() {
text_slices.set(" 1"); text_slices.set(" 1");
} }
bin_skip_frac = 0x10000 / slices_nb;
slice_counter = 0; slice_counter = 0;
} }
@ -353,7 +366,7 @@ ScannerView::ScannerView(
&recent_entries_view &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.set_parent_rect({ 0, 28 * 8, 240, 12 * 8 });
recent_entries_view.on_select = [this, &nav](const ScannerRecentEntry& entry) { recent_entries_view.on_select = [this, &nav](const ScannerRecentEntry& entry) {

View File

@ -112,6 +112,9 @@ private:
int16_t index; int16_t index;
} slices[32]; } slices[32];
uint32_t bin_skip_acc { 0 }, bin_skip_frac { };
uint32_t pixel_index { 0 };
std::array<Color, 240> spectrum_row { 0 };
ChannelSpectrumFIFO* fifo { nullptr }; ChannelSpectrumFIFO* fifo { nullptr };
rf::Frequency f_min { 0 }, f_max { 0 }; rf::Frequency f_min { 0 }, f_max { 0 };
uint8_t detect_timer { 0 }, release_timer { 0 }, timing_div { 0 }; uint8_t detect_timer { 0 }, release_timer { 0 }, timing_div { 0 };
@ -123,7 +126,7 @@ private:
uint8_t slices_nb { 0 }; uint8_t slices_nb { 0 };
uint8_t slice_counter { 0 }; uint8_t slice_counter { 0 };
int16_t last_bin { 0 }; int16_t last_bin { 0 };
Coord last_pos { 0 }; Coord last_tick_pos { 0 };
rf::Frequency scan_span { 0 }, resolved_frequency { 0 }; rf::Frequency scan_span { 0 }, resolved_frequency { 0 };
uint16_t locked_bin { 0 }; uint16_t locked_bin { 0 };
uint8_t scan_counter { 0 }; uint8_t scan_counter { 0 };
@ -135,6 +138,7 @@ private:
void on_lna_changed(int32_t v_db); void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db); void on_vga_changed(int32_t v_db);
void do_timers(); void do_timers();
void add_spectrum_pixel(Color color);
const RecentEntriesColumns columns { { const RecentEntriesColumns columns { {
{ "Frequency", 9 }, { "Frequency", 9 },
@ -149,7 +153,7 @@ private:
{ { 1 * 8, 4 * 8 }, "Trig: /255 Mean: /255", Color::light_grey() }, { { 1 * 8, 4 * 8 }, "Trig: /255 Mean: /255", Color::light_grey() },
{ { 1 * 8, 6 * 8 }, "Slices: /32 Rate: Hz", Color::light_grey() }, { { 1 * 8, 6 * 8 }, "Slices: /32 Rate: Hz", Color::light_grey() },
{ { 6 * 8, 10 * 8 }, "Timer Status", 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() } { { 26 * 8, 25 * 8 }, "MHz", Color::light_grey() }
}; };
@ -188,12 +192,12 @@ private:
VuMeter vu_max { VuMeter vu_max {
{ 1 * 8, 11 * 8 - 4, 3 * 8, 48 }, { 1 * 8, 11 * 8 - 4, 3 * 8, 48 },
16, 18,
false false
}; };
ProgressBar progress_timers { ProgressBar progress_timers {
{ 6 * 8, 12 * 8, 5 * 8, 16 } { 6 * 8, 12 * 8, 6 * 8, 16 }
}; };
Text text_infos { Text text_infos {
{ 13 * 8, 12 * 8, 15 * 8, 16 }, { 13 * 8, 12 * 8, 15 * 8, 16 },
@ -203,11 +207,11 @@ private:
Checkbox check_snap { Checkbox check_snap {
{ 6 * 8, 15 * 8 }, { 6 * 8, 15 * 8 },
7, 7,
"Adjust:", "Snap to:",
true true
}; };
OptionsField options_snap { OptionsField options_snap {
{ 15 * 8, 15 * 8 }, { 17 * 8, 15 * 8 },
7, 7,
{ {
{ "25kHz ", 25000 }, { "25kHz ", 25000 },

View File

@ -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_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_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))); 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, (float)message.squelch_level / 100.0);
audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, 0.8f);
synth_acc = 0; synth_acc = 0;

View File

@ -23,8 +23,6 @@
#include "event_m4.hpp" #include "event_m4.hpp"
#include "dsp_fft.hpp"
#include <cstdint> #include <cstdint>
#include <cstddef> #include <cstddef>

View File

@ -356,7 +356,8 @@ public:
const size_t channel_decimation, const size_t channel_decimation,
const size_t deviation, const size_t deviation,
const iir_biquad_config_t audio_hpf_config, 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 }, ) : Message { ID::NBFMConfigure },
decim_0_filter(decim_0_filter), decim_0_filter(decim_0_filter),
decim_1_filter(decim_1_filter), decim_1_filter(decim_1_filter),
@ -364,7 +365,8 @@ public:
channel_decimation { channel_decimation }, channel_decimation { channel_decimation },
deviation { deviation }, deviation { deviation },
audio_hpf_config(audio_hpf_config), 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 size_t deviation;
const iir_biquad_config_t audio_hpf_config; 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;
}; };
class WFMConfigureMessage : public Message { class WFMConfigureMessage : public Message {

View File

@ -1461,7 +1461,7 @@ VuMeter::VuMeter(
show_max_ { show_max } show_max_ { show_max }
{ {
//set_focusable(false); //set_focusable(false);
LED_height = parent_rect.size().height() / LEDs; LED_height = std::max(1UL, parent_rect.size().height() / LEDs);
split = 256 / LEDs; split = 256 / LEDs;
} }
@ -1507,7 +1507,7 @@ void VuMeter::paint(Painter& painter) {
else else
color = lit ? Color::green() : Color::dark_green(); 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_; prev_value = value_;
} }