Merge remote-tracking branch 'upstream/master'

Conflicts:
	firmware/application/Makefile
	firmware/application/analog_audio_app.cpp
	firmware/application/analog_audio_app.hpp
	firmware/application/event.cpp
	firmware/application/irq_ipc.hpp
	firmware/application/portapack.hpp
	firmware/application/receiver_model.cpp
	firmware/application/receiver_model.hpp
	firmware/application/recent_entries.cpp
	firmware/application/string_format.hpp
	firmware/application/ui_debug.cpp
	firmware/application/ui_debug.hpp
	firmware/application/ui_menu.cpp
	firmware/application/ui_navigation.cpp
	firmware/application/ui_navigation.hpp
	firmware/application/ui_receiver.cpp
	firmware/application/ui_receiver.hpp
	firmware/application/ui_sd_card_status_view.cpp
	firmware/application/ui_sd_card_status_view.hpp
	firmware/application/ui_setup.cpp
	firmware/application/ui_setup.hpp
	firmware/application/ui_spectrum.hpp
	firmware/baseband-tx/dsp_fir_taps.cpp
	firmware/baseband-tx/dsp_fir_taps.hpp
	firmware/baseband-tx/irq_ipc_m4.cpp
	firmware/baseband-tx/irq_ipc_m4.hpp
	firmware/baseband-tx/proc_audiotx.cpp
	firmware/baseband/Makefile
	firmware/baseband/audio_output.cpp
	firmware/baseband/audio_output.hpp
	firmware/baseband/block_decimator.hpp
	firmware/baseband/dsp_decimate.cpp
	firmware/baseband/dsp_decimate.hpp
	firmware/baseband/dsp_demodulate.cpp
	firmware/baseband/dsp_demodulate.hpp
	firmware/baseband/dsp_fir_taps.cpp
	firmware/baseband/irq_ipc_m4.cpp
	firmware/baseband/irq_ipc_m4.hpp
	firmware/baseband/proc_am_audio.cpp
	firmware/baseband/proc_am_audio.hpp
	firmware/baseband/proc_nfm_audio.cpp
	firmware/baseband/proc_nfm_audio.hpp
	firmware/baseband/proc_wfm_audio.cpp
	firmware/baseband/proc_wfm_audio.hpp
	firmware/baseband/spectrum_collector.hpp
	firmware/common/dsp_fir_taps.cpp
	firmware/common/dsp_fir_taps.hpp
	firmware/common/event.hpp
	firmware/common/message.hpp
	firmware/common/ui_painter.cpp
	firmware/common/ui_painter.hpp
This commit is contained in:
furrtek 2016-02-04 11:35:55 +01:00
commit 8009a9b543
45 changed files with 1148 additions and 877 deletions

View File

@ -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<FrequencyKeypadView>(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<ReceiverModel::Mode>(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<ReceiverModel::Mode>(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<ui::Dim>(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<Widget> 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<FrequencyOptionsView>(
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<RadioGainOptionsView>(
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<ReceiverModel::Mode>(receiver_model.modulation());
if( modulation == ReceiverModel::Mode::AMAudio ) {
options_modulation.set_style(&style_options_group);
auto widget = std::make_unique<AMOptionsView>(
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<NBFMOptionsView>(
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 */

View File

@ -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<Widget> 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<Widget> new_widget);
void update_modulation(const ReceiverModel::Mode modulation);
};
} /* namespace ui */

View File

@ -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() {

View File

@ -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();

View File

@ -61,9 +61,9 @@ ClockManager clock_manager {
i2c0, clock_generator
};
ReceiverModel receiver_model {
clock_manager
};
ReceiverModel receiver_model;
TemperatureLogger temperature_logger;
TemperatureLogger temperature_logger;

View File

@ -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;

View File

@ -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<AMConfig, 3> 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<NBFMConfig, 3> 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<WFMConfig, 1> 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<Mode>(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();
}

View File

@ -25,7 +25,6 @@
#include <cstdint>
#include <cstddef>
#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();
};

View File

@ -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"

View File

@ -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);
};

View File

@ -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<void(void)> 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 {

View File

@ -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<FrequencyKeypadView>(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<ReceiverModel::Mode>(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<ReceiverModel::Mode>(receiver_model.modulation()));
}
void ReceiverView::on_hide() {
on_modulation_changed(static_cast<ReceiverModel::Mode>(-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<AnalogAudioView>(mode);
break;
case ReceiverModel::Mode::SpectrumAnalysis:
widget_content = std::make_unique<SpectrumAnalysisView>();
break;
default:
break;
}
if( widget_content ) {
add_child(widget_content.get());
const ui::Rect rect { 0, header_height, parent_rect.width(), static_cast<ui::Dim>(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 */

View File

@ -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 <cstddef>
#include <cstdint>
@ -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> 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__*/

View File

@ -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 */

View File

@ -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);

View File

@ -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();
}

View File

@ -84,7 +84,7 @@ public:
private:
WaterfallView waterfall_view;
FrequencyScale frequency_scale;
ChannelSpectrumFIFO* fifo;
ChannelSpectrumFIFO* fifo { nullptr };
void on_channel_spectrum(const ChannelSpectrum& spectrum);
};

View File

@ -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 <cstdint>
#include <array>
#include "complex.hpp"
template<size_t N>
struct fir_taps_real {
float pass_frequency_normalized;
float stop_frequency_normalized;
std::array<int16_t, N> 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__*/

View File

@ -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();
}
}

View File

@ -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__*/

View File

@ -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);

View File

@ -27,6 +27,7 @@
#include "dsp_iir.hpp"
#include "dsp_squelch.hpp"
#include "block_decimator.hpp"
#include "audio_stats_collector.hpp"
#include <cstdint>
@ -43,6 +44,8 @@ public:
void write(const buffer_f32_t& audio);
private:
BlockDecimator<float, 32> 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);
};

View File

@ -29,7 +29,7 @@
#include "dsp_types.hpp"
#include "complex.hpp"
template<size_t N>
template<typename T, size_t N>
class BlockDecimator {
public:
constexpr BlockDecimator(
@ -65,7 +65,7 @@ public:
}
template<typename BlockCallback>
void feed(const buffer_c16_t& src, BlockCallback callback) {
void feed(const buffer_t<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<complex16_t, N> buffer;
std::array<T, N> buffer;
uint32_t input_sampling_rate_ { 0 };
size_t factor_ { 1 };
size_t src_i { 0 };

View File

@ -179,10 +179,6 @@ static inline uint32_t scale_round_and_pack(
// FIRC8xR16x24FS4Decim4 //////////////////////////////////////////////////
FIRC8xR16x24FS4Decim4::FIRC8xR16x24FS4Decim4() {
z_.fill({});
}
void FIRC8xR16x24FS4Decim4::configure(
const std::array<tap_t, taps_count>& 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<tap_t, taps_count>& 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<tap_t, taps_count>& 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<tap_t, taps_count>& 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<samples_t>(taps_count);
taps_reversed_ = std::make_unique<taps_t>(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

View File

@ -103,8 +103,6 @@ public:
Up = false
};
FIRC8xR16x24FS4Decim4();
void configure(
const std::array<tap_t, taps_count>& taps,
const int32_t scale,
@ -135,8 +133,6 @@ public:
Up = false
};
FIRC8xR16x24FS4Decim8();
void configure(
const std::array<tap_t, taps_count>& 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<tap_t, taps_count>& 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<tap_t, taps_count>& taps,
const int32_t scale
@ -243,11 +235,18 @@ private:
size_t taps_count_;
size_t decimation_factor_;
template<typename T>
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<samples_t>(taps_count);
taps_reversed_ = std::make_unique<taps_t>(taps_count);
taps_count_ = taps_count;
decimation_factor_ = decimation_factor;
std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]);
}
};
class DecimateBy2CIC4Real {

View File

@ -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<float>(t.imag()) / static_cast<float>(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;

View File

@ -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(

View File

@ -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__*/

View File

@ -21,7 +21,6 @@
#include "proc_am_audio.hpp"
#include "dsp_iir_config.hpp"
#include "audio_output.hpp"
#include <array>
@ -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;
}

View File

@ -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<complex16_t, 512> 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<float, 32> 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__*/

View File

@ -21,7 +21,6 @@
#include "proc_nfm_audio.hpp"
#include "dsp_iir_config.hpp"
#include "audio_output.hpp"
#include <cstdint>
@ -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;
}

View File

@ -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 <cstdint>
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<complex16_t, 512> 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<float, 32> 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;

View File

@ -21,7 +21,6 @@
#include "proc_wfm_audio.hpp"
#include "dsp_iir_config.hpp"
#include "audio_output.hpp"
#include <cstdint>
@ -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);

View File

@ -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<complex16_t, 512> dst;
const buffer_c16_t dst_buffer {
dst.data(),

View File

@ -50,7 +50,7 @@ public:
);
private:
BlockDecimator<256> channel_spectrum_decimator;
BlockDecimator<complex16_t, 256> channel_spectrum_decimator;
ChannelSpectrumFIFO fifo;
volatile bool channel_spectrum_request_update { false };

View File

@ -34,6 +34,13 @@ struct fir_taps_real {
std::array<int16_t, N> taps;
};
template<size_t N>
struct fir_taps_complex {
float pass_frequency_normalized;
float stop_frequency_normalized;
std::array<complex16_t, N> 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

View File

@ -19,4 +19,39 @@
* Boston, MA 02110-1301, USA.
*/
#include "dsp_fir_taps.hpp"
#include "dsp_iir.hpp"
#include <hal.h>
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<buffer_out.count; i++) {
x_[0] = x_[1];
x_[1] = x_[2];
x_[2] = buffer_in.p[i];
y_[0] = y_[1];
y_[1] = y_[2];
y_[2] = b_[0] * x_[2] + b_[1] * x_[1] + b_[2] * x_[0]
- a_[1] * y_[1] - a_[2] * y_[0];
buffer_out.p[i] = y_[2];
}
x = x_;
y = y_;
}
void IIRBiquadFilter::execute_in_place(const buffer_f32_t& buffer) {
execute(buffer, buffer);
}

View File

@ -0,0 +1,112 @@
/*
* 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_48k_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_48k_hpf_300hz_config {
{ 0.97261390f, -1.94522780f, 0.97261390f },
{ 1.00000000f, -1.94447766f, 0.94597794f }
};
// scipy.signal.butter(2, 300 / 12000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_24k_hpf_300hz_config {
{ 0.94597686f, -1.89195371f, 0.94597686f },
{ 1.00000000f, -1.88903308f, 0.89487434f }
};
// scipy.signal.butter(2, 300 / 8000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_16k_hpf_300hz_config {
{ 0.92006616f, -1.84013232f, 0.92006616f },
{ 1.00000000f, -1.83373266f, 0.84653197f }
};
// scipy.signal.butter(2, 300 / 6000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_12k_hpf_300hz_config {
{ 0.89485861f, -1.78971721f, 0.89485861f },
{ 1.00000000f, -1.77863178f, 0.80080265f }
};
// scipy.signal.butter(2, 300 / 4000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_8k_hpf_300hz_config {
{ 0.84645925f, -1.69291851f, 0.84645925f },
{ 1.00000000f, -1.66920314f, 0.71663387f }
};
// 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_48k_deemph_300_6_config {
{ 0.01925927f, 0.01925927f, 0.00000000f },
{ 1.00000000f, -0.96148145f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 12000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_24k_deemph_300_6_config {
{ 0.03780475f, 0.03780475f, 0.00000000f },
{ 1.00000000f, -0.92439049f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 8000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_16k_deemph_300_6_config {
{ 0.05568894f, 0.05568894f, 0.00000000f },
{ 1.00000000f, -0.88862213f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 6000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_12k_deemph_300_6_config {
{ 0.07295966f, 0.07295966f, 0.00000000f },
{ 1.00000000f, -0.85408069f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 4000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_8k_deemph_300_6_config {
{ 0.10583178f, 0.10583178f, 0.00000000f },
{ 1.00000000f, -0.78833643f, 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_48k_deemph_2122_6_config {
{ 0.12264116f, 0.12264116f, 0.00000000f },
{ 1.00000000f, -0.75471767f, 0.00000000f }
};
#endif/*__DSP_IIR_CONFIG_H__*/

View File

@ -394,20 +394,29 @@ void ILI9341::draw_pixels(
io.lcd_write_pixels(colors, count);
}
void ILI9341::draw_bitmap(
const ui::Point p,
const ui::Size size,
const uint8_t* const pixels,
const ui::Color foreground,
const ui::Color background
) {
lcd_start_ram_write(p, size);
const size_t count = size.w * size.h;
for(size_t i=0; i<count; i++) {
const auto pixel = pixels[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<count; i++) {
const auto pixel = pixels[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(

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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);
}

View File

@ -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);