mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-12-02 22:11:48 +00:00
SYNC
This commit is contained in:
@@ -124,22 +124,26 @@ CSRC = $(PORTSRC) \
|
||||
# setting.
|
||||
CPPSRC = main.cpp \
|
||||
message_queue.cpp \
|
||||
event.cpp \
|
||||
event_m4.cpp \
|
||||
irq_ipc_m4.cpp \
|
||||
gpdma.cpp \
|
||||
baseband_dma.cpp \
|
||||
baseband_sgpio.cpp \
|
||||
portapack_shared_memory.cpp \
|
||||
baseband_thread.cpp \
|
||||
baseband_processor.cpp \
|
||||
channel_decimator.cpp \
|
||||
baseband_stats_collector.cpp \
|
||||
dsp_decimate.cpp \
|
||||
dsp_demodulate.cpp \
|
||||
matched_filter.cpp \
|
||||
proc_am_audio.cpp \
|
||||
proc_nfm_audio.cpp \
|
||||
spectrum_collector.cpp \
|
||||
proc_wfm_audio.cpp \
|
||||
proc_ais.cpp \
|
||||
proc_wideband_spectrum.cpp \
|
||||
proc_tpms.cpp \
|
||||
proc_ert.cpp \
|
||||
proc_afskrx.cpp \
|
||||
proc_sigfrx.cpp \
|
||||
dsp_squelch.cpp \
|
||||
@@ -147,11 +151,15 @@ CPPSRC = main.cpp \
|
||||
packet_builder.cpp \
|
||||
dsp_fft.cpp \
|
||||
dsp_fir_taps.cpp \
|
||||
dsp_iir.cpp \
|
||||
fxpt_atan2.cpp \
|
||||
rssi.cpp \
|
||||
rssi_dma.cpp \
|
||||
rssi_thread.cpp \
|
||||
audio.cpp \
|
||||
audio_output.cpp \
|
||||
audio_dma.cpp \
|
||||
audio_stats_collector.cpp \
|
||||
touch_dma.cpp \
|
||||
../common/utility.cpp \
|
||||
../common/chibios_cpp.cpp \
|
||||
|
||||
@@ -208,8 +208,8 @@ void enable() {
|
||||
}
|
||||
|
||||
void disable() {
|
||||
gpdma_channel_i2s0_tx.disable_force();
|
||||
gpdma_channel_i2s0_rx.disable_force();
|
||||
gpdma_channel_i2s0_tx.disable();
|
||||
gpdma_channel_i2s0_rx.disable();
|
||||
}
|
||||
|
||||
buffer_t tx_empty_buffer() {
|
||||
|
||||
100
firmware/baseband/audio_output.cpp
Normal file
100
firmware/baseband/audio_output.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 "audio_output.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
void AudioOutput::configure(
|
||||
const iir_biquad_config_t& hpf_config,
|
||||
const iir_biquad_config_t& deemph_config,
|
||||
const float squelch_threshold
|
||||
) {
|
||||
hpf.configure(hpf_config);
|
||||
deemph.configure(deemph_config);
|
||||
squelch.set_threshold(squelch_threshold);
|
||||
}
|
||||
|
||||
void AudioOutput::write(
|
||||
const buffer_s16_t& audio
|
||||
) {
|
||||
std::array<float, 32> audio_f;
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio_f[i] = audio.p[i];
|
||||
}
|
||||
write(buffer_f32_t {
|
||||
audio_f.data(),
|
||||
audio.count,
|
||||
audio.sampling_rate
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutput::write(
|
||||
const buffer_f32_t& audio
|
||||
) {
|
||||
const auto audio_present_now = squelch.execute(audio);
|
||||
|
||||
hpf.execute_in_place(audio);
|
||||
deemph.execute_in_place(audio);
|
||||
|
||||
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);
|
||||
const bool audio_present = (audio_present_history != 0);
|
||||
|
||||
if( audio_present ) {
|
||||
i2s::i2s0::tx_unmute();
|
||||
} else {
|
||||
i2s::i2s0::tx_mute();
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio.p[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fill_audio_buffer(audio);
|
||||
}
|
||||
|
||||
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio) {
|
||||
auto audio_buffer = audio::dma::tx_empty_buffer();
|
||||
for(size_t i=0; i<audio_buffer.count; i++) {
|
||||
const int32_t sample_int = audio.p[i];
|
||||
const int32_t sample_saturated = __SSAT(sample_int, 16);
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = sample_saturated;
|
||||
}
|
||||
|
||||
feed_audio_stats(audio);
|
||||
}
|
||||
|
||||
void AudioOutput::feed_audio_stats(const buffer_f32_t& audio) {
|
||||
audio_stats.feed(
|
||||
audio,
|
||||
[](const AudioStatistics& statistics) {
|
||||
const AudioStatisticsMessage audio_stats_message { statistics };
|
||||
shared_memory.application_queue.push(audio_stats_message);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -19,36 +19,40 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "irq_ipc_m4.hpp"
|
||||
#ifndef __AUDIO_OUTPUT_H__
|
||||
#define __AUDIO_OUTPUT_H__
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_squelch.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "audio_stats_collector.hpp"
|
||||
|
||||
void m0apptxevent_interrupt_enable() {
|
||||
nvicEnableVector(M0CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M0APPTXEVENT_IRQ_PRIORITY));
|
||||
}
|
||||
#include <cstdint>
|
||||
|
||||
void m0apptxevent_interrupt_disable() {
|
||||
nvicDisableVector(M0CORE_IRQn);
|
||||
}
|
||||
class AudioOutput {
|
||||
public:
|
||||
void configure(
|
||||
const iir_biquad_config_t& hpf_config,
|
||||
const iir_biquad_config_t& deemph_config = iir_config_passthrough,
|
||||
const float squelch_threshold = 0.0f
|
||||
);
|
||||
|
||||
extern "C" {
|
||||
void write(const buffer_s16_t& audio);
|
||||
void write(const buffer_f32_t& audio);
|
||||
|
||||
CH_IRQ_HANDLER(MAPP_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
private:
|
||||
IIRBiquadFilter hpf;
|
||||
IIRBiquadFilter deemph;
|
||||
FMSquelch squelch;
|
||||
|
||||
chSysLockFromIsr();
|
||||
events_flag_isr(EVT_MASK_BASEBAND);
|
||||
chSysUnlockFromIsr();
|
||||
AudioStatsCollector audio_stats;
|
||||
|
||||
creg::m0apptxevent::clear();
|
||||
uint64_t audio_present_history = 0;
|
||||
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
void fill_audio_buffer(const buffer_f32_t& audio);
|
||||
void feed_audio_stats(const buffer_f32_t& audio);
|
||||
};
|
||||
|
||||
}
|
||||
#endif/*__AUDIO_OUTPUT_H__*/
|
||||
67
firmware/baseband/audio_stats_collector.cpp
Normal file
67
firmware/baseband/audio_stats_collector.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 "audio_stats_collector.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
void AudioStatsCollector::consume_audio_buffer(const buffer_f32_t& src) {
|
||||
auto src_p = src.p;
|
||||
const auto src_end = &src.p[src.count];
|
||||
while(src_p < src_end) {
|
||||
const auto sample = *(src_p++);
|
||||
const auto sample_squared = sample * sample;
|
||||
squared_sum += sample_squared;
|
||||
if( sample_squared > max_squared ) {
|
||||
max_squared = sample_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioStatsCollector::update_stats(const size_t sample_count, const size_t sampling_rate) {
|
||||
count += sample_count;
|
||||
|
||||
const size_t samples_per_update = sampling_rate * update_interval;
|
||||
|
||||
if( count >= samples_per_update ) {
|
||||
statistics.rms_db = complex16_mag_squared_to_dbv_norm(squared_sum / count);
|
||||
statistics.max_db = complex16_mag_squared_to_dbv_norm(max_squared);
|
||||
statistics.count = count;
|
||||
|
||||
squared_sum = 0;
|
||||
max_squared = 0;
|
||||
count = 0;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioStatsCollector::feed(const buffer_f32_t& src) {
|
||||
consume_audio_buffer(src);
|
||||
|
||||
return update_stats(src.count, src.sampling_rate);
|
||||
}
|
||||
|
||||
bool AudioStatsCollector::mute(const size_t sample_count, const size_t sampling_rate) {
|
||||
return update_stats(sample_count, sampling_rate);
|
||||
}
|
||||
@@ -22,9 +22,8 @@
|
||||
#ifndef __AUDIO_STATS_COLLECTOR_H__
|
||||
#define __AUDIO_STATS_COLLECTOR_H__
|
||||
|
||||
#include "buffer.hpp"
|
||||
#include "dsp_types.hpp"
|
||||
#include "message.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -32,64 +31,33 @@
|
||||
class AudioStatsCollector {
|
||||
public:
|
||||
template<typename Callback>
|
||||
void feed(buffer_s16_t src, Callback callback) {
|
||||
consume_audio_buffer(src);
|
||||
|
||||
if( update_stats(src.count, src.sampling_rate) ) {
|
||||
void feed(const buffer_f32_t& src, Callback callback) {
|
||||
if( feed(src) ) {
|
||||
callback(statistics);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void mute(const size_t sample_count, const size_t sampling_rate, Callback callback) {
|
||||
if( update_stats(sample_count, sampling_rate) ) {
|
||||
if( mute(sample_count, sampling_rate) ) {
|
||||
callback(statistics);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr float update_interval { 0.1f };
|
||||
uint64_t squared_sum { 0 };
|
||||
uint32_t max_squared { 0 };
|
||||
float squared_sum { 0 };
|
||||
float max_squared { 0 };
|
||||
size_t count { 0 };
|
||||
|
||||
AudioStatistics statistics;
|
||||
|
||||
void consume_audio_buffer(buffer_s16_t src) {
|
||||
auto src_p = src.p;
|
||||
const auto src_end = &src.p[src.count];
|
||||
while(src_p < src_end) {
|
||||
const auto sample = *(src_p++);
|
||||
const uint64_t sample_squared = sample * sample;
|
||||
squared_sum += sample_squared;
|
||||
if( sample_squared > max_squared ) {
|
||||
max_squared = sample_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
void consume_audio_buffer(const buffer_f32_t& src);
|
||||
|
||||
bool update_stats(const size_t sample_count, const size_t sampling_rate) {
|
||||
count += sample_count;
|
||||
bool update_stats(const size_t sample_count, const size_t sampling_rate);
|
||||
|
||||
const size_t samples_per_update = sampling_rate * update_interval;
|
||||
|
||||
if( count >= samples_per_update ) {
|
||||
const float squared_sum_f = squared_sum;
|
||||
const float max_squared_f = max_squared;
|
||||
const float squared_avg_f = squared_sum_f / count;
|
||||
statistics.rms_db = complex16_mag_squared_to_dbv_norm(squared_avg_f);
|
||||
statistics.max_db = complex16_mag_squared_to_dbv_norm(max_squared_f);
|
||||
statistics.count = count;
|
||||
|
||||
squared_sum = 0;
|
||||
max_squared = 0;
|
||||
count = 0;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool feed(const buffer_f32_t& src);
|
||||
bool mute(const size_t sample_count, const size_t sampling_rate);
|
||||
};
|
||||
|
||||
#endif/*__AUDIO_STATS_COLLECTOR_H__*/
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
#include "baseband_dma.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -35,8 +34,6 @@ using namespace lpc43xx;
|
||||
|
||||
namespace baseband {
|
||||
namespace dma {
|
||||
|
||||
int quitt = 0;
|
||||
|
||||
constexpr uint32_t gpdma_ahb_master_sgpio = 0;
|
||||
constexpr uint32_t gpdma_ahb_master_memory = 1;
|
||||
@@ -102,17 +99,12 @@ constexpr size_t msg_count = transfers_per_buffer - 1;
|
||||
static std::array<gpdma::channel::LLI, transfers_per_buffer> lli_loop;
|
||||
static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gpdma_channel_number];
|
||||
|
||||
//static Mailbox mailbox;
|
||||
//static std::array<msg_t, msg_count> messages;
|
||||
static Semaphore semaphore;
|
||||
|
||||
static volatile const gpdma::channel::LLI* next_lli = nullptr;
|
||||
|
||||
void transfer_complete() {
|
||||
static void transfer_complete() {
|
||||
next_lli = gpdma_channel_sgpio.next_lli();
|
||||
quitt = 0;
|
||||
/* TODO: Is Mailbox the proper synchronization mechanism for this? */
|
||||
//chMBPostI(&mailbox, 0);
|
||||
chSemSignalI(&semaphore);
|
||||
}
|
||||
|
||||
@@ -121,7 +113,6 @@ static void dma_error() {
|
||||
}
|
||||
|
||||
void init() {
|
||||
//chMBInit(&mailbox, messages.data(), messages.size());
|
||||
chSemInit(&semaphore, 0);
|
||||
gpdma_channel_sgpio.set_handlers(transfer_complete, dma_error);
|
||||
|
||||
@@ -148,7 +139,6 @@ void enable(const baseband::Direction direction) {
|
||||
const auto gpdma_config = config(direction);
|
||||
gpdma_channel_sgpio.configure(lli_loop[0], gpdma_config);
|
||||
|
||||
//chMBReset(&mailbox);
|
||||
chSemReset(&semaphore, 0);
|
||||
|
||||
gpdma_channel_sgpio.enable();
|
||||
@@ -159,14 +149,11 @@ bool is_enabled() {
|
||||
}
|
||||
|
||||
void disable() {
|
||||
gpdma_channel_sgpio.disable_force();
|
||||
gpdma_channel_sgpio.disable();
|
||||
}
|
||||
|
||||
baseband::buffer_t wait_for_rx_buffer() {
|
||||
//msg_t msg;
|
||||
//const auto status = chMBFetch(&mailbox, &msg, TIME_INFINITE);
|
||||
const auto status = chSemWait(&semaphore);
|
||||
if (quitt) return { nullptr, 0 };
|
||||
if( status == RDY_OK ) {
|
||||
const auto next = next_lli;
|
||||
if( next ) {
|
||||
@@ -174,28 +161,10 @@ baseband::buffer_t wait_for_rx_buffer() {
|
||||
const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask;
|
||||
return { reinterpret_cast<sample_t*>(lli_loop[free_index].destaddr), transfer_samples };
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
return { };
|
||||
}
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
baseband::buffer_t wait_for_tx_buffer() {
|
||||
//msg_t msg;
|
||||
//const auto status = chMBFetch(&mailbox, &msg, TIME_INFINITE);
|
||||
const auto status = chSemWait(&semaphore);
|
||||
if( status == RDY_OK ) {
|
||||
const auto next = next_lli;
|
||||
if( next ) {
|
||||
const size_t next_index = next - &lli_loop[0];
|
||||
const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask;
|
||||
return { reinterpret_cast<sample_t*>(lli_loop[free_index].srcaddr), transfer_samples };
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,100 +23,14 @@
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "dsp_fft.hpp"
|
||||
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
|
||||
void BasebandProcessor::update_spectrum() {
|
||||
// Called from idle thread (after EVT_MASK_SPECTRUM is flagged)
|
||||
if( channel_spectrum_request_update ) {
|
||||
/* Decimated buffer is full. Compute spectrum. */
|
||||
channel_spectrum_request_update = false;
|
||||
fft_c_preswapped(channel_spectrum);
|
||||
|
||||
ChannelSpectrumMessage spectrum_message;
|
||||
for(size_t i=0; i<spectrum_message.spectrum.db.size(); i++) {
|
||||
const auto mag2 = magnitude_squared(channel_spectrum[i]);
|
||||
const float db = complex16_mag_squared_to_dbv_norm(mag2);
|
||||
constexpr float mag_scale = 5.0f;
|
||||
const unsigned int v = (db * mag_scale) + 255.0f;
|
||||
spectrum_message.spectrum.db[i] = std::max(0U, std::min(255U, v));
|
||||
}
|
||||
|
||||
/* TODO: Rename .db -> .magnitude, or something more (less!) accurate. */
|
||||
spectrum_message.spectrum.db_count = spectrum_message.spectrum.db.size();
|
||||
spectrum_message.spectrum.sampling_rate = channel_spectrum_sampling_rate;
|
||||
spectrum_message.spectrum.channel_filter_pass_frequency = channel_filter_pass_frequency;
|
||||
spectrum_message.spectrum.channel_filter_stop_frequency = channel_filter_stop_frequency;
|
||||
shared_memory.application_queue.push(spectrum_message);
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandProcessor::feed_channel_stats(const buffer_c16_t channel) {
|
||||
void BasebandProcessor::feed_channel_stats(const buffer_c16_t& channel) {
|
||||
channel_stats.feed(
|
||||
channel,
|
||||
[this](const ChannelStatistics statistics) {
|
||||
this->post_channel_stats_message(statistics);
|
||||
[](const ChannelStatistics& statistics) {
|
||||
const ChannelStatisticsMessage channel_stats_message { statistics };
|
||||
shared_memory.application_queue.push(channel_stats_message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void BasebandProcessor::feed_channel_spectrum(
|
||||
const buffer_c16_t channel,
|
||||
const uint32_t filter_pass_frequency,
|
||||
const uint32_t filter_stop_frequency
|
||||
) {
|
||||
channel_filter_pass_frequency = filter_pass_frequency;
|
||||
channel_filter_stop_frequency = filter_stop_frequency;
|
||||
channel_spectrum_decimator.feed(
|
||||
channel,
|
||||
[this](const buffer_c16_t data) {
|
||||
this->post_channel_spectrum_message(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void BasebandProcessor::fill_audio_buffer(const buffer_s16_t audio) {
|
||||
auto audio_buffer = audio::dma::tx_empty_buffer();;
|
||||
for(size_t i=0; i<audio_buffer.count; i++) {
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = audio.p[i];
|
||||
}
|
||||
i2s::i2s0::tx_unmute();
|
||||
|
||||
feed_audio_stats(audio);
|
||||
}
|
||||
|
||||
void BasebandProcessor::post_channel_stats_message(const ChannelStatistics statistics) {
|
||||
channel_stats_message.statistics = statistics;
|
||||
shared_memory.application_queue.push(channel_stats_message);
|
||||
}
|
||||
|
||||
void BasebandProcessor::post_channel_spectrum_message(const buffer_c16_t data) {
|
||||
if( !channel_spectrum_request_update ) {
|
||||
fft_swap(data, channel_spectrum);
|
||||
channel_spectrum_sampling_rate = data.sampling_rate;
|
||||
channel_spectrum_request_update = true;
|
||||
events_flag(EVT_MASK_SPECTRUM);
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandProcessor::feed_audio_stats(const buffer_s16_t audio) {
|
||||
audio_stats.feed(
|
||||
audio,
|
||||
[this](const AudioStatistics statistics) {
|
||||
this->post_audio_stats_message(statistics);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void BasebandProcessor::post_audio_stats_message(const AudioStatistics statistics) {
|
||||
audio_stats_message.statistics = statistics;
|
||||
shared_memory.application_queue.push(audio_stats_message);
|
||||
}
|
||||
|
||||
@@ -23,54 +23,24 @@
|
||||
#define __BASEBAND_PROCESSOR_H__
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
#include "block_decimator.hpp"
|
||||
#include "channel_stats_collector.hpp"
|
||||
#include "audio_stats_collector.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <complex>
|
||||
#include "message.hpp"
|
||||
|
||||
class BasebandProcessor {
|
||||
public:
|
||||
virtual ~BasebandProcessor() = default;
|
||||
|
||||
virtual void execute(buffer_c8_t buffer) = 0;
|
||||
virtual void execute(const buffer_c8_t& buffer) = 0;
|
||||
|
||||
void update_spectrum();
|
||||
virtual void on_message(const Message* const) { };
|
||||
|
||||
protected:
|
||||
void feed_channel_stats(const buffer_c16_t channel);
|
||||
|
||||
void feed_channel_spectrum(
|
||||
const buffer_c16_t channel,
|
||||
const uint32_t filter_pass_frequency,
|
||||
const uint32_t filter_stop_frequency
|
||||
);
|
||||
|
||||
void fill_audio_buffer(const buffer_s16_t audio);
|
||||
|
||||
volatile bool channel_spectrum_request_update { false };
|
||||
std::array<std::complex<float>, 256> channel_spectrum;
|
||||
uint32_t channel_spectrum_sampling_rate { 0 };
|
||||
uint32_t channel_filter_pass_frequency { 0 };
|
||||
uint32_t channel_filter_stop_frequency { 0 };
|
||||
void feed_channel_stats(const buffer_c16_t& channel);
|
||||
|
||||
private:
|
||||
BlockDecimator<256> channel_spectrum_decimator { 4 };
|
||||
|
||||
ChannelStatsCollector channel_stats;
|
||||
ChannelStatisticsMessage channel_stats_message;
|
||||
|
||||
AudioStatsCollector audio_stats;
|
||||
AudioStatisticsMessage audio_stats_message;
|
||||
|
||||
void post_channel_stats_message(const ChannelStatistics statistics);
|
||||
void post_channel_spectrum_message(const buffer_c16_t data);
|
||||
void feed_audio_stats(const buffer_s16_t audio);
|
||||
void post_audio_stats_message(const AudioStatistics statistics);
|
||||
};
|
||||
|
||||
#endif/*__BASEBAND_PROCESSOR_H__*/
|
||||
|
||||
59
firmware/baseband/baseband_stats_collector.cpp
Normal file
59
firmware/baseband/baseband_stats_collector.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 "baseband_stats_collector.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
|
||||
bool BasebandStatsCollector::process(const buffer_c8_t& buffer) {
|
||||
samples += buffer.count;
|
||||
|
||||
const size_t report_samples = buffer.sampling_rate * report_interval;
|
||||
const auto report_delta = samples - samples_last_report;
|
||||
return report_delta >= report_samples;
|
||||
}
|
||||
|
||||
BasebandStatistics BasebandStatsCollector::capture_statistics() {
|
||||
BasebandStatistics statistics;
|
||||
|
||||
const auto idle_ticks = thread_idle->total_ticks;
|
||||
statistics.idle_ticks = (idle_ticks - last_idle_ticks);
|
||||
last_idle_ticks = idle_ticks;
|
||||
|
||||
const auto main_ticks = thread_main->total_ticks;
|
||||
statistics.main_ticks = (main_ticks - last_main_ticks);
|
||||
last_main_ticks = main_ticks;
|
||||
|
||||
const auto rssi_ticks = thread_rssi->total_ticks;
|
||||
statistics.rssi_ticks = (rssi_ticks - last_rssi_ticks);
|
||||
last_rssi_ticks = rssi_ticks;
|
||||
|
||||
const auto baseband_ticks = thread_baseband->total_ticks;
|
||||
statistics.baseband_ticks = (baseband_ticks - last_baseband_ticks);
|
||||
last_baseband_ticks = baseband_ticks;
|
||||
|
||||
statistics.saturation = lpc43xx::m4::flag_saturation();
|
||||
lpc43xx::m4::clear_flag_saturation();
|
||||
|
||||
samples_last_report = samples;
|
||||
|
||||
return statistics;
|
||||
}
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
#include "message.hpp"
|
||||
#include "utility_m4.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -46,36 +45,9 @@ public:
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void process(buffer_c8_t buffer, Callback callback) {
|
||||
samples += buffer.count;
|
||||
|
||||
const size_t report_samples = buffer.sampling_rate * report_interval;
|
||||
const auto report_delta = samples - samples_last_report;
|
||||
if( report_delta >= report_samples ) {
|
||||
BasebandStatistics statistics;
|
||||
|
||||
const auto idle_ticks = thread_idle->total_ticks;
|
||||
statistics.idle_ticks = (idle_ticks - last_idle_ticks);
|
||||
last_idle_ticks = idle_ticks;
|
||||
|
||||
const auto main_ticks = thread_main->total_ticks;
|
||||
statistics.main_ticks = (main_ticks - last_main_ticks);
|
||||
last_main_ticks = main_ticks;
|
||||
|
||||
const auto rssi_ticks = thread_rssi->total_ticks;
|
||||
statistics.rssi_ticks = (rssi_ticks - last_rssi_ticks);
|
||||
last_rssi_ticks = rssi_ticks;
|
||||
|
||||
const auto baseband_ticks = thread_baseband->total_ticks;
|
||||
statistics.baseband_ticks = (baseband_ticks - last_baseband_ticks);
|
||||
last_baseband_ticks = baseband_ticks;
|
||||
|
||||
statistics.saturation = m4_flag_saturation();
|
||||
clear_m4_flag_saturation();
|
||||
|
||||
callback(statistics);
|
||||
|
||||
samples_last_report = samples;
|
||||
void process(const buffer_c8_t& buffer, Callback callback) {
|
||||
if( process(buffer) ) {
|
||||
callback(capture_statistics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +63,9 @@ private:
|
||||
uint32_t last_rssi_ticks { 0 };
|
||||
const Thread* const thread_baseband;
|
||||
uint32_t last_baseband_ticks { 0 };
|
||||
|
||||
bool process(const buffer_c8_t& buffer);
|
||||
BasebandStatistics capture_statistics();
|
||||
};
|
||||
|
||||
#endif/*__BASEBAND_STATS_COLLECTOR_H__*/
|
||||
|
||||
157
firmware/baseband/baseband_thread.cpp
Normal file
157
firmware/baseband/baseband_thread.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 "baseband_thread.hpp"
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
#include "baseband.hpp"
|
||||
#include "baseband_stats_collector.hpp"
|
||||
#include "baseband_sgpio.hpp"
|
||||
#include "baseband_dma.hpp"
|
||||
|
||||
#include "rssi.hpp"
|
||||
#include "i2s.hpp"
|
||||
|
||||
#include "proc_am_audio.hpp"
|
||||
#include "proc_nfm_audio.hpp"
|
||||
#include "proc_wfm_audio.hpp"
|
||||
#include "proc_ais.hpp"
|
||||
#include "proc_wideband_spectrum.hpp"
|
||||
#include "proc_tpms.hpp"
|
||||
#include "proc_ert.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
static baseband::SGPIO baseband_sgpio;
|
||||
|
||||
WORKING_AREA(baseband_thread_wa, 4096);
|
||||
|
||||
Thread* BasebandThread::start(const tprio_t priority) {
|
||||
return chThdCreateStatic(baseband_thread_wa, sizeof(baseband_thread_wa),
|
||||
priority, ThreadBase::fn,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
void BasebandThread::set_configuration(const BasebandConfiguration& new_configuration) {
|
||||
if( new_configuration.mode != baseband_configuration.mode ) {
|
||||
disable();
|
||||
|
||||
// TODO: Timing problem around disabling DMA and nulling and deleting old processor
|
||||
auto old_p = baseband_processor;
|
||||
baseband_processor = nullptr;
|
||||
delete old_p;
|
||||
|
||||
baseband_processor = create_processor(new_configuration.mode);
|
||||
|
||||
enable();
|
||||
}
|
||||
|
||||
baseband_configuration = new_configuration;
|
||||
}
|
||||
|
||||
void BasebandThread::on_message(const Message* const message) {
|
||||
if( message->id == Message::ID::BasebandConfiguration ) {
|
||||
set_configuration(reinterpret_cast<const BasebandConfigurationMessage*>(message)->configuration);
|
||||
} else {
|
||||
if( baseband_processor ) {
|
||||
baseband_processor->on_message(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandThread::run() {
|
||||
baseband_sgpio.init();
|
||||
baseband::dma::init();
|
||||
|
||||
const auto baseband_buffer = new std::array<baseband::sample_t, 8192>();
|
||||
baseband::dma::configure(
|
||||
baseband_buffer->data(),
|
||||
direction()
|
||||
);
|
||||
//baseband::dma::allocate(4, 2048);
|
||||
|
||||
BasebandStatsCollector stats {
|
||||
chSysGetIdleThread(),
|
||||
thread_main,
|
||||
thread_rssi,
|
||||
chThdSelf()
|
||||
};
|
||||
|
||||
while(true) {
|
||||
// TODO: Place correct sampling rate into buffer returned here:
|
||||
const auto buffer_tmp = baseband::dma::wait_for_rx_buffer();
|
||||
if( buffer_tmp ) {
|
||||
buffer_c8_t buffer {
|
||||
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
|
||||
};
|
||||
|
||||
if( baseband_processor ) {
|
||||
baseband_processor->execute(buffer);
|
||||
}
|
||||
|
||||
stats.process(buffer,
|
||||
[](const BasebandStatistics& statistics) {
|
||||
const BasebandStatisticsMessage message { statistics };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
delete baseband_buffer;
|
||||
}
|
||||
|
||||
BasebandProcessor* BasebandThread::create_processor(const int32_t mode) {
|
||||
switch(mode) {
|
||||
case 0: return new NarrowbandAMAudio();
|
||||
case 1: return new NarrowbandFMAudio();
|
||||
case 2: return new WidebandFMAudio();
|
||||
case 3: return new AISProcessor();
|
||||
case 4: return new WidebandSpectrum();
|
||||
case 5: return new TPMSProcessor();
|
||||
case 6: return new ERTProcessor();
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandThread::disable() {
|
||||
if( baseband_processor ) {
|
||||
i2s::i2s0::tx_mute();
|
||||
baseband::dma::disable();
|
||||
baseband_sgpio.streaming_disable();
|
||||
rf::rssi::stop();
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandThread::enable() {
|
||||
if( baseband_processor ) {
|
||||
if( direction() == baseband::Direction::Receive ) {
|
||||
rf::rssi::start();
|
||||
}
|
||||
baseband_sgpio.configure(direction());
|
||||
baseband::dma::enable(direction());
|
||||
baseband_sgpio.streaming_enable();
|
||||
}
|
||||
}
|
||||
65
firmware/baseband/baseband_thread.hpp
Normal file
65
firmware/baseband/baseband_thread.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 __BASEBAND_THREAD_H__
|
||||
#define __BASEBAND_THREAD_H__
|
||||
|
||||
#include "thread_base.hpp"
|
||||
#include "message.hpp"
|
||||
#include "baseband_processor.hpp"
|
||||
|
||||
#include <ch.h>
|
||||
|
||||
class BasebandThread : public ThreadBase {
|
||||
public:
|
||||
BasebandThread(
|
||||
) : ThreadBase { "baseband" }
|
||||
{
|
||||
}
|
||||
|
||||
Thread* start(const tprio_t priority);
|
||||
|
||||
void on_message(const Message* const message);
|
||||
|
||||
// This getter should die, it's just here to leak information to code that
|
||||
// isn't in the right place to begin with.
|
||||
baseband::Direction direction() const {
|
||||
return baseband::Direction::Receive;
|
||||
}
|
||||
|
||||
Thread* thread_main { nullptr };
|
||||
Thread* thread_rssi { nullptr };
|
||||
BasebandProcessor* baseband_processor { nullptr };
|
||||
|
||||
private:
|
||||
BasebandConfiguration baseband_configuration;
|
||||
|
||||
void run() override;
|
||||
|
||||
BasebandProcessor* create_processor(const int32_t mode);
|
||||
|
||||
void disable();
|
||||
void enable();
|
||||
|
||||
void set_configuration(const BasebandConfiguration& new_configuration);
|
||||
};
|
||||
|
||||
#endif/*__BASEBAND_THREAD_H__*/
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename BlockCallback>
|
||||
void feed(const buffer_c16_t src, BlockCallback callback) {
|
||||
void feed(const buffer_c16_t& src, BlockCallback callback) {
|
||||
/* NOTE: Input block size must be >= factor */
|
||||
|
||||
set_input_sampling_rate(src.sampling_rate);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "channel_decimator.hpp"
|
||||
|
||||
buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) {
|
||||
buffer_c16_t ChannelDecimator::execute_decimation(const buffer_c8_t& buffer) {
|
||||
const buffer_c16_t work_baseband_buffer {
|
||||
work_baseband.data(),
|
||||
work_baseband.size()
|
||||
@@ -39,19 +39,15 @@ buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) {
|
||||
* -> gain of 256
|
||||
* -> decimation by 2
|
||||
* -> 1.544MHz complex<int16_t>[1024], [-32768, 32512] */
|
||||
const auto stage_0_out = translate.execute(buffer, work_baseband_buffer);
|
||||
|
||||
//if( fs_over_4_downconvert ) {
|
||||
// // TODO:
|
||||
//} else {
|
||||
// Won't work until cic_0 will accept input type of buffer_c8_t.
|
||||
// stage_0_out = cic_0.execute(buffer, work_baseband_buffer);
|
||||
//}
|
||||
auto stage_0_out = execute_stage_0(buffer, work_baseband_buffer);
|
||||
if( decimation_factor == DecimationFactor::By2 ) {
|
||||
return stage_0_out;
|
||||
}
|
||||
|
||||
/* 1.536MHz complex<int16_t>[1024], [-32768, 32512]
|
||||
* -> 3rd order CIC: -0.1dB @ 0.028fs, -1dB @ 0.088fs, -60dB @ 0.468fs
|
||||
* -0.1dB @ 43kHz, -1dB @ 136kHz, -60dB @ 723kHz
|
||||
* -> gain of 8
|
||||
* -> gain of 1
|
||||
* -> decimation by 2
|
||||
* -> 768kHz complex<int16_t>[512], [-8192, 8128] */
|
||||
auto cic_1_out = cic_1.execute(stage_0_out, work_baseband_buffer);
|
||||
@@ -82,3 +78,14 @@ buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) {
|
||||
|
||||
return cic_4_out;
|
||||
}
|
||||
|
||||
buffer_c16_t ChannelDecimator::execute_stage_0(
|
||||
const buffer_c8_t& buffer,
|
||||
const buffer_c16_t& work_baseband_buffer
|
||||
) {
|
||||
if( fs_over_4_downconvert ) {
|
||||
return translate.execute(buffer, work_baseband_buffer);
|
||||
} else {
|
||||
return cic_0.execute(buffer, work_baseband_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
class ChannelDecimator {
|
||||
public:
|
||||
enum class DecimationFactor {
|
||||
By2,
|
||||
By4,
|
||||
By8,
|
||||
By16,
|
||||
@@ -39,13 +40,16 @@ public:
|
||||
};
|
||||
|
||||
constexpr ChannelDecimator(
|
||||
) : decimation_factor { DecimationFactor::By32 }
|
||||
) : decimation_factor { DecimationFactor::By32 },
|
||||
fs_over_4_downconvert { true }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr ChannelDecimator(
|
||||
const DecimationFactor decimation_factor
|
||||
) : decimation_factor { decimation_factor }
|
||||
const DecimationFactor decimation_factor,
|
||||
const bool fs_over_4_downconvert = true
|
||||
) : decimation_factor { decimation_factor },
|
||||
fs_over_4_downconvert { fs_over_4_downconvert }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -53,7 +57,7 @@ public:
|
||||
decimation_factor = f;
|
||||
}
|
||||
|
||||
buffer_c16_t execute(buffer_c8_t buffer) {
|
||||
buffer_c16_t execute(const buffer_c8_t& buffer) {
|
||||
auto decimated = execute_decimation(buffer);
|
||||
|
||||
return decimated;
|
||||
@@ -62,18 +66,22 @@ public:
|
||||
private:
|
||||
std::array<complex16_t, 1024> work_baseband;
|
||||
|
||||
//const bool fs_over_4_downconvert = true;
|
||||
|
||||
dsp::decimate::TranslateByFSOver4AndDecimateBy2CIC3 translate;
|
||||
//dsp::decimate::DecimateBy2CIC3 cic_0;
|
||||
dsp::decimate::Complex8DecimateBy2CIC3 cic_0;
|
||||
dsp::decimate::DecimateBy2CIC3 cic_1;
|
||||
dsp::decimate::DecimateBy2CIC3 cic_2;
|
||||
dsp::decimate::DecimateBy2CIC3 cic_3;
|
||||
dsp::decimate::DecimateBy2CIC3 cic_4;
|
||||
|
||||
DecimationFactor decimation_factor;
|
||||
const bool fs_over_4_downconvert;
|
||||
|
||||
buffer_c16_t execute_decimation(buffer_c8_t buffer);
|
||||
buffer_c16_t execute_decimation(const buffer_c8_t& buffer);
|
||||
|
||||
buffer_c16_t execute_stage_0(
|
||||
const buffer_c8_t& buffer,
|
||||
const buffer_c16_t& work_baseband_buffer
|
||||
);
|
||||
};
|
||||
|
||||
#endif/*__CHANNEL_DECIMATOR_H__*/
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
class ChannelStatsCollector {
|
||||
public:
|
||||
template<typename Callback>
|
||||
void feed(buffer_c16_t src, Callback callback) {
|
||||
void feed(const buffer_c16_t& src, Callback callback) {
|
||||
auto src_p = src.p;
|
||||
while(src_p < &src.p[src.count]) {
|
||||
const uint32_t sample = *__SIMD32(src_p)++;
|
||||
|
||||
@@ -26,7 +26,461 @@
|
||||
namespace dsp {
|
||||
namespace decimate {
|
||||
|
||||
buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(buffer_c8_t src, buffer_c16_t dst) {
|
||||
static inline complex32_t mac_fs4_shift(
|
||||
const vec2_s16* const z,
|
||||
const vec2_s16* const t,
|
||||
const size_t index,
|
||||
const complex32_t accum
|
||||
) {
|
||||
/* Accumulate sample * tap results for samples already in z buffer.
|
||||
* Multiply using swap/negation to achieve Fs/4 shift.
|
||||
* For iterations where samples are shifting out of z buffer (being discarded).
|
||||
* Expect negated tap t[2] to accomodate instruction set limitations.
|
||||
*/
|
||||
const bool negated_t2 = index & 1;
|
||||
const auto q1_i0 = z[index*2 + 0];
|
||||
const auto i1_q0 = z[index*2 + 1];
|
||||
const auto t1_t0 = t[index];
|
||||
const auto real = negated_t2 ? smlsd(q1_i0, t1_t0, accum.real()) : smlad(q1_i0, t1_t0, accum.real());
|
||||
const auto imag = negated_t2 ? smlad(i1_q0, t1_t0, accum.imag()) : smlsd(i1_q0, t1_t0, accum.imag());
|
||||
return { real, imag };
|
||||
}
|
||||
|
||||
static inline complex32_t mac_shift(
|
||||
const vec2_s16* const z,
|
||||
const vec2_s16* const t,
|
||||
const size_t index,
|
||||
const complex32_t accum
|
||||
) {
|
||||
/* Accumulate sample * tap results for samples already in z buffer.
|
||||
* For iterations where samples are shifting out of z buffer (being discarded).
|
||||
* real += i1 * t1 + i0 * t0
|
||||
* imag += q1 * t1 + q0 * t0
|
||||
*/
|
||||
const auto i1_i0 = z[index*2 + 0];
|
||||
const auto q1_q0 = z[index*2 + 1];
|
||||
const auto t1_t0 = t[index];
|
||||
const auto real = smlad(i1_i0, t1_t0, accum.real());
|
||||
const auto imag = smlad(q1_q0, t1_t0, accum.imag());
|
||||
return { real, imag };
|
||||
}
|
||||
|
||||
static inline complex32_t mac_fs4_shift_and_store(
|
||||
vec2_s16* const z,
|
||||
const vec2_s16* const t,
|
||||
const size_t decimation_factor,
|
||||
const size_t index,
|
||||
const complex32_t accum
|
||||
) {
|
||||
/* Accumulate sample * tap results for samples already in z buffer.
|
||||
* Place new samples into z buffer.
|
||||
* Expect negated tap t[2] to accomodate instruction set limitations.
|
||||
*/
|
||||
const bool negated_t2 = index & 1;
|
||||
const auto q1_i0 = z[decimation_factor + index*2 + 0];
|
||||
const auto i1_q0 = z[decimation_factor + index*2 + 1];
|
||||
const auto t1_t0 = t[decimation_factor / 2 + index];
|
||||
z[index*2 + 0] = q1_i0;
|
||||
const auto real = negated_t2 ? smlsd(q1_i0, t1_t0, accum.real()) : smlad(q1_i0, t1_t0, accum.real());
|
||||
z[index*2 + 1] = i1_q0;
|
||||
const auto imag = negated_t2 ? smlad(i1_q0, t1_t0, accum.imag()) : smlsd(i1_q0, t1_t0, accum.imag());
|
||||
return { real, imag };
|
||||
}
|
||||
|
||||
static inline complex32_t mac_shift_and_store(
|
||||
vec2_s16* const z,
|
||||
const vec2_s16* const t,
|
||||
const size_t decimation_factor,
|
||||
const size_t index,
|
||||
const complex32_t accum
|
||||
) {
|
||||
/* Accumulate sample * tap results for samples already in z buffer.
|
||||
* Place new samples into z buffer.
|
||||
* Expect negated tap t[2] to accomodate instruction set limitations.
|
||||
*/
|
||||
const auto i1_i0 = z[decimation_factor + index*2 + 0];
|
||||
const auto q1_q0 = z[decimation_factor + index*2 + 1];
|
||||
const auto t1_t0 = t[decimation_factor / 2 + index];
|
||||
z[index*2 + 0] = i1_i0;
|
||||
const auto real = smlad(i1_i0, t1_t0, accum.real());
|
||||
z[index*2 + 1] = q1_q0;
|
||||
const auto imag = smlad(q1_q0, t1_t0, accum.imag());
|
||||
return { real, imag };
|
||||
}
|
||||
|
||||
static inline complex32_t mac_fs4_shift_and_store_new_c8_samples(
|
||||
vec2_s16* const z,
|
||||
const vec2_s16* const t,
|
||||
const vec4_s8* const in,
|
||||
const size_t decimation_factor,
|
||||
const size_t index,
|
||||
const size_t length,
|
||||
const complex32_t accum
|
||||
) {
|
||||
/* Accumulate sample * tap results for new samples.
|
||||
* Place new samples into z buffer.
|
||||
* Expect negated tap t[2] to accomodate instruction set limitations.
|
||||
*/
|
||||
const bool negated_t2 = index & 1;
|
||||
const auto q1_i1_q0_i0 = in[index];
|
||||
const auto t1_t0 = t[(length - decimation_factor) / 2 + index];
|
||||
const auto i1_q1_i0_q0 = rev16(q1_i1_q0_i0);
|
||||
const auto i1_q1_q0_i0 = pkhbt(q1_i1_q0_i0, i1_q1_i0_q0);
|
||||
const auto q1_i0 = sxtb16(i1_q1_q0_i0);
|
||||
const auto i1_q0 = sxtb16(i1_q1_q0_i0, 8);
|
||||
z[length - decimation_factor * 2 + index*2 + 0] = q1_i0;
|
||||
const auto real = negated_t2 ? smlsd(q1_i0, t1_t0, accum.real()) : smlad(q1_i0, t1_t0, accum.real());
|
||||
z[length - decimation_factor * 2 + index*2 + 1] = i1_q0;
|
||||
const auto imag = negated_t2 ? smlad(i1_q0, t1_t0, accum.imag()) : smlsd(i1_q0, t1_t0, accum.imag());
|
||||
return { real, imag };
|
||||
}
|
||||
|
||||
static inline complex32_t mac_shift_and_store_new_c16_samples(
|
||||
vec2_s16* const z,
|
||||
const vec2_s16* const t,
|
||||
const vec2_s16* const in,
|
||||
const size_t decimation_factor,
|
||||
const size_t index,
|
||||
const size_t length,
|
||||
const complex32_t accum
|
||||
) {
|
||||
/* Accumulate sample * tap results for new samples.
|
||||
* Place new samples into z buffer.
|
||||
* Expect negated tap t[2] to accomodate instruction set limitations.
|
||||
*/
|
||||
const auto q0_i0 = in[index*2+0];
|
||||
const auto q1_i1 = in[index*2+1];
|
||||
const auto i1_i0 = pkhbt(q0_i0, q1_i1, 16);
|
||||
const auto q1_q0 = pkhtb(q1_i1, q0_i0, 16);
|
||||
const auto t1_t0 = t[(length - decimation_factor) / 2 + index];
|
||||
z[length - decimation_factor * 2 + index*2 + 0] = i1_i0;
|
||||
const auto real = smlad(i1_i0, t1_t0, accum.real());
|
||||
z[length - decimation_factor * 2 + index*2 + 1] = q1_q0;
|
||||
const auto imag = smlad(q1_q0, t1_t0, accum.imag());
|
||||
return { real, imag };
|
||||
}
|
||||
|
||||
static inline uint32_t scale_round_and_pack(
|
||||
const complex32_t value,
|
||||
const int32_t scale_factor
|
||||
) {
|
||||
/* Multiply 32-bit components of the complex<int32_t> by a scale factor,
|
||||
* into int64_ts, then round to nearest LSB (1 << 32), saturate to 16 bits,
|
||||
* and pack into a complex<int16_t>.
|
||||
*/
|
||||
const auto scaled_real = __SMMULR(value.real(), scale_factor);
|
||||
const auto saturated_real = __SSAT(scaled_real, 16);
|
||||
|
||||
const auto scaled_imag = __SMMULR(value.imag(), scale_factor);
|
||||
const auto saturated_imag = __SSAT(scaled_imag, 16);
|
||||
|
||||
return __PKHBT(saturated_real, saturated_imag, 16);
|
||||
}
|
||||
|
||||
// FIRC8xR16x24FS4Decim4 //////////////////////////////////////////////////
|
||||
|
||||
FIRC8xR16x24FS4Decim4::FIRC8xR16x24FS4Decim4() {
|
||||
z_.fill({});
|
||||
}
|
||||
|
||||
void FIRC8xR16x24FS4Decim4::configure(
|
||||
const std::array<tap_t, taps_count>& taps,
|
||||
const int32_t scale,
|
||||
const Shift shift
|
||||
) {
|
||||
const int negate_factor = (shift == Shift::Up) ? -1 : 1;
|
||||
for(size_t i=0; i<taps.size(); i+=4) {
|
||||
taps_[i+0] = taps[i+0];
|
||||
taps_[i+1] = taps[i+1] * negate_factor;
|
||||
taps_[i+2] = -taps[i+2];
|
||||
taps_[i+3] = taps[i+3] * negate_factor;
|
||||
}
|
||||
output_scale = scale;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC8xR16x24FS4Decim4::execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4));
|
||||
|
||||
const auto k = output_scale;
|
||||
|
||||
const size_t count = src.count / decimation_factor;
|
||||
for(size_t i=0; i<count; i++) {
|
||||
const vec4_s8* const in = static_cast<const vec4_s8*>(__builtin_assume_aligned(&src.p[i * decimation_factor], 4));
|
||||
|
||||
complex32_t accum;
|
||||
|
||||
// Oldest samples are discarded.
|
||||
accum = mac_fs4_shift(z, t, 0, accum);
|
||||
accum = mac_fs4_shift(z, t, 1, accum);
|
||||
|
||||
// Middle samples are shifted earlier in the "z" delay buffer.
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 0, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 1, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 2, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 3, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 4, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 5, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 6, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 7, accum);
|
||||
|
||||
// Newest samples come from "in" buffer, are copied to "z" delay buffer.
|
||||
accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 0, taps_count, accum);
|
||||
accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 1, taps_count, accum);
|
||||
|
||||
d[i] = scale_round_and_pack(accum, k);
|
||||
}
|
||||
|
||||
return {
|
||||
dst.p,
|
||||
count,
|
||||
src.sampling_rate / decimation_factor
|
||||
};
|
||||
}
|
||||
|
||||
// FIRC8xR16x24FS4Decim8 //////////////////////////////////////////////////
|
||||
|
||||
FIRC8xR16x24FS4Decim8::FIRC8xR16x24FS4Decim8() {
|
||||
z_.fill({});
|
||||
}
|
||||
|
||||
void FIRC8xR16x24FS4Decim8::configure(
|
||||
const std::array<tap_t, taps_count>& taps,
|
||||
const int32_t scale,
|
||||
const Shift shift
|
||||
) {
|
||||
const int negate_factor = (shift == Shift::Up) ? -1 : 1;
|
||||
for(size_t i=0; i<taps.size(); i+=4) {
|
||||
taps_[i+0] = taps[i+0];
|
||||
taps_[i+1] = taps[i+1] * negate_factor;
|
||||
taps_[i+2] = -taps[i+2];
|
||||
taps_[i+3] = taps[i+3] * negate_factor;
|
||||
}
|
||||
output_scale = scale;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC8xR16x24FS4Decim8::execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4));
|
||||
|
||||
const auto k = output_scale;
|
||||
|
||||
const size_t count = src.count / decimation_factor;
|
||||
for(size_t i=0; i<count; i++) {
|
||||
const vec4_s8* const in = static_cast<const vec4_s8*>(__builtin_assume_aligned(&src.p[i * decimation_factor], 4));
|
||||
|
||||
complex32_t accum;
|
||||
|
||||
// Oldest samples are discarded.
|
||||
accum = mac_fs4_shift(z, t, 0, accum);
|
||||
accum = mac_fs4_shift(z, t, 1, accum);
|
||||
accum = mac_fs4_shift(z, t, 2, accum);
|
||||
accum = mac_fs4_shift(z, t, 3, accum);
|
||||
|
||||
// Middle samples are shifted earlier in the "z" delay buffer.
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 0, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 1, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 2, accum);
|
||||
accum = mac_fs4_shift_and_store(z, t, decimation_factor, 3, accum);
|
||||
|
||||
// Newest samples come from "in" buffer, are copied to "z" delay buffer.
|
||||
accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 0, taps_count, accum);
|
||||
accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 1, taps_count, accum);
|
||||
accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 2, taps_count, accum);
|
||||
accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 3, taps_count, accum);
|
||||
|
||||
d[i] = scale_round_and_pack(accum, k);
|
||||
}
|
||||
|
||||
return {
|
||||
dst.p,
|
||||
count,
|
||||
src.sampling_rate / decimation_factor
|
||||
};
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC16xR16x16Decim2::execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4));
|
||||
|
||||
const auto k = output_scale;
|
||||
|
||||
const size_t count = src.count / decimation_factor;
|
||||
for(size_t i=0; i<count; i++) {
|
||||
const vec2_s16* const in = static_cast<const vec2_s16*>(__builtin_assume_aligned(&src.p[i * decimation_factor], 4));
|
||||
|
||||
complex32_t accum;
|
||||
|
||||
// Oldest samples are discarded.
|
||||
accum = mac_shift(z, t, 0, accum);
|
||||
|
||||
// Middle samples are shifted earlier in the "z" delay buffer.
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 0, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 1, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 2, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 3, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 4, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 5, accum);
|
||||
|
||||
// Newest samples come from "in" buffer, are copied to "z" delay buffer.
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 0, taps_count, accum);
|
||||
|
||||
d[i] = scale_round_and_pack(accum, k);
|
||||
}
|
||||
|
||||
return {
|
||||
dst.p,
|
||||
count,
|
||||
src.sampling_rate / decimation_factor
|
||||
};
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC16xR16x32Decim8::execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4));
|
||||
|
||||
const auto k = output_scale;
|
||||
|
||||
const size_t count = src.count / decimation_factor;
|
||||
for(size_t i=0; i<count; i++) {
|
||||
const vec2_s16* const in = static_cast<const vec2_s16*>(__builtin_assume_aligned(&src.p[i * decimation_factor], 4));
|
||||
|
||||
complex32_t accum;
|
||||
|
||||
// Oldest samples are discarded.
|
||||
accum = mac_shift(z, t, 0, accum);
|
||||
accum = mac_shift(z, t, 1, accum);
|
||||
accum = mac_shift(z, t, 2, accum);
|
||||
accum = mac_shift(z, t, 3, accum);
|
||||
|
||||
// Middle samples are shifted earlier in the "z" delay buffer.
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 0, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 1, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 2, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 3, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 4, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 5, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 6, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 7, accum);
|
||||
|
||||
// Newest samples come from "in" buffer, are copied to "z" delay buffer.
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 0, taps_count, accum);
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 1, taps_count, accum);
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 2, taps_count, accum);
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 3, taps_count, accum);
|
||||
|
||||
d[i] = scale_round_and_pack(accum, k);
|
||||
}
|
||||
|
||||
return {
|
||||
dst.p,
|
||||
count,
|
||||
src.sampling_rate / decimation_factor
|
||||
};
|
||||
}
|
||||
|
||||
buffer_c16_t Complex8DecimateBy2CIC3::execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
|
||||
/* Decimates by two using a non-recursive third-order CIC filter.
|
||||
*/
|
||||
|
||||
/* CIC filter (decimating by two):
|
||||
* D_I0 = i3 * 1 + i2 * 3 + i1 * 3 + i0 * 1
|
||||
* D_Q0 = q3 * 1 + q2 * 3 + q1 * 3 + q0 * 1
|
||||
*
|
||||
* D_I1 = i5 * 1 + i4 * 3 + i3 * 3 + i2 * 1
|
||||
* D_Q1 = q5 * 1 + q4 * 3 + q3 * 3 + q2 * 1
|
||||
*/
|
||||
|
||||
uint32_t i1_i0 = _i1_i0;
|
||||
uint32_t q1_q0 = _q1_q0;
|
||||
|
||||
/* 3:1 Scaled by 32 to normalize output to +/-32768-ish. */
|
||||
constexpr uint32_t scale_factor = 32;
|
||||
constexpr uint32_t k_3_1 = 0x00030001 * scale_factor;
|
||||
uint32_t* src_p = reinterpret_cast<uint32_t*>(&src.p[0]);
|
||||
uint32_t* const src_end = reinterpret_cast<uint32_t*>(&src.p[src.count]);
|
||||
uint32_t* dst_p = reinterpret_cast<uint32_t*>(&dst.p[0]);
|
||||
while(src_p < src_end) {
|
||||
const uint32_t q3_i3_q2_i2 = *(src_p++); // 3
|
||||
const uint32_t q5_i5_q4_i4 = *(src_p++);
|
||||
|
||||
const uint32_t d_i0_partial = __SMUAD(k_3_1, i1_i0); // 1: = 3 * i1 + 1 * i0
|
||||
const uint32_t i3_i2 = __SXTB16(q3_i3_q2_i2, 0); // 1: (q3_i3_q2_i2 ror 0)[23:16]:(q3_i3_q2_i2 ror 0)[7:0]
|
||||
const uint32_t d_i0 = __SMLADX(k_3_1, i3_i2, d_i0_partial); // 1: + 3 * i2 + 1 * i3
|
||||
|
||||
const uint32_t d_q0_partial = __SMUAD(k_3_1, q1_q0); // 1: = 3 * q1 * 1 * q0
|
||||
const uint32_t q3_q2 = __SXTB16(q3_i3_q2_i2, 8); // 1: (q3_i3_q2_i2 ror 8)[23:16]:(q3_i3_q2_i2 ror 8)[7:0]
|
||||
const uint32_t d_q0 = __SMLADX(k_3_1, q3_q2, d_q0_partial); // 1: + 3 * q2 + 1 * q3
|
||||
|
||||
const uint32_t d_q0_i0 = __PKHBT(d_i0, d_q0, 16); // 1: (Rm<<16)[31:16]:Rn[15:0]
|
||||
|
||||
const uint32_t d_i1_partial = __SMUAD(k_3_1, i3_i2); // 1: = 3 * i3 + 1 * i2
|
||||
const uint32_t i5_i4 = __SXTB16(q5_i5_q4_i4, 0); // 1: (q5_i5_q4_i4 ror 0)[23:16]:(q5_i5_q4_i4 ror 0)[7:0]
|
||||
const uint32_t d_i1 = __SMLADX(k_3_1, i5_i4, d_i1_partial); // 1: + 1 * i5 + 3 * i4
|
||||
|
||||
const uint32_t d_q1_partial = __SMUAD(k_3_1, q3_q2); // 1: = 3 * q3 * 1 * q2
|
||||
const uint32_t q5_q4 = __SXTB16(q5_i5_q4_i4, 8); // 1: (q5_i5_q4_i4 ror 8)[23:16]:(q5_i5_q4_i4 ror 8)[7:0]
|
||||
const uint32_t d_q1 = __SMLADX(k_3_1, q5_q4, d_q1_partial); // 1: + 1 * q5 + 3 * q4
|
||||
|
||||
const uint32_t d_q1_i1 = __PKHBT(d_i1, d_q1, 16); // 1: (Rm<<16)[31:16]:Rn[15:0]
|
||||
|
||||
*(dst_p++) = d_q0_i0; // 3
|
||||
*(dst_p++) = d_q1_i1;
|
||||
|
||||
i1_i0 = i5_i4;
|
||||
q1_q0 = q5_q4;
|
||||
}
|
||||
_i1_i0 = i1_i0;
|
||||
_q1_q0 = q1_q0;
|
||||
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
|
||||
buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
|
||||
/* Translates incoming complex<int8_t> samples by -fs/4,
|
||||
* decimates by two using a non-recursive third-order CIC filter.
|
||||
*/
|
||||
@@ -111,8 +565,8 @@ buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(buffer_c8_t src, buff
|
||||
}
|
||||
|
||||
buffer_c16_t DecimateBy2CIC3::execute(
|
||||
buffer_c16_t src,
|
||||
buffer_c16_t dst
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
/* Complex non-recursive 3rd-order CIC filter (taps 1,3,3,1).
|
||||
* Gain of 8.
|
||||
@@ -164,9 +618,15 @@ buffer_c16_t DecimateBy2CIC3::execute(
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
|
||||
void FIR64AndDecimateBy2Real::configure(
|
||||
const std::array<int16_t, taps_count>& new_taps
|
||||
) {
|
||||
std::copy(new_taps.cbegin(), new_taps.cend(), taps.begin());
|
||||
}
|
||||
|
||||
buffer_s16_t FIR64AndDecimateBy2Real::execute(
|
||||
buffer_s16_t src,
|
||||
buffer_s16_t dst
|
||||
const buffer_s16_t& src,
|
||||
const buffer_s16_t& dst
|
||||
) {
|
||||
/* int16_t input (sample count "n" must be multiple of 4)
|
||||
* -> int16_t output, decimated by 2.
|
||||
@@ -197,9 +657,21 @@ 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(
|
||||
buffer_c16_t src,
|
||||
buffer_c16_t dst
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
/* int16_t input (sample count "n" must be multiple of decimation_factor)
|
||||
* -> int16_t output, decimated by decimation_factor.
|
||||
@@ -308,8 +780,8 @@ buffer_c16_t FIRAndDecimateComplex::execute(
|
||||
}
|
||||
|
||||
buffer_s16_t DecimateBy2CIC4Real::execute(
|
||||
buffer_s16_t src,
|
||||
buffer_s16_t dst
|
||||
const buffer_s16_t& src,
|
||||
const buffer_s16_t& dst
|
||||
) {
|
||||
auto src_p = src.p;
|
||||
auto dst_p = dst.p;
|
||||
@@ -328,76 +800,6 @@ buffer_s16_t DecimateBy2CIC4Real::execute(
|
||||
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
#if 0
|
||||
buffer_c16_t DecimateBy2HBF5Complex::execute(
|
||||
buffer_c16_t const src,
|
||||
buffer_c16_t const dst
|
||||
) {
|
||||
auto src_p = src.p;
|
||||
auto dst_p = dst.p;
|
||||
int32_t n = src.count;
|
||||
for(; n>0; n-=2) {
|
||||
/* TODO: Probably a lot of room to optimize... */
|
||||
z[0] = z[2];
|
||||
//z[1] = z[3];
|
||||
z[2] = z[4];
|
||||
//z[3] = z[5];
|
||||
z[4] = z[6];
|
||||
z[5] = z[7];
|
||||
z[6] = z[8];
|
||||
z[7] = z[9];
|
||||
z[8] = z[10];
|
||||
z[9] = *(src_p++);
|
||||
z[10] = *(src_p++);
|
||||
|
||||
int32_t t_real { z[5].real * 256 };
|
||||
int32_t t_imag { z[5].imag * 256 };
|
||||
t_real += (z[ 0].real + z[10].real) * 3;
|
||||
t_imag += (z[ 0].imag + z[10].imag) * 3;
|
||||
t_real -= (z[ 2].real + z[ 8].real) * 25;
|
||||
t_imag -= (z[ 2].imag + z[ 8].imag) * 25;
|
||||
t_real += (z[ 4].real + z[ 6].real) * 150;
|
||||
t_imag += (z[ 4].imag + z[ 6].imag) * 150;
|
||||
*(dst_p++) = { t_real / 256, t_imag / 256 };
|
||||
}
|
||||
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
|
||||
buffer_c16_t DecimateBy2HBF7Complex::execute(
|
||||
buffer_c16_t const src,
|
||||
buffer_c16_t const dst
|
||||
) {
|
||||
auto src_p = src.p;
|
||||
auto dst_p = dst.p;
|
||||
int32_t n = src.count;
|
||||
for(; n>0; n-=2) {
|
||||
/* TODO: Probably a lot of room to optimize... */
|
||||
z[0] = z[2];
|
||||
//z[1] = z[3];
|
||||
z[2] = z[4];
|
||||
//z[3] = z[5];
|
||||
z[4] = z[6];
|
||||
z[5] = z[7];
|
||||
z[6] = z[8];
|
||||
z[7] = z[9];
|
||||
z[8] = z[10];
|
||||
z[9] = *(src_p++);
|
||||
z[10] = *(src_p++);
|
||||
|
||||
int32_t t_real { z[5].real * 512 };
|
||||
int32_t t_imag { z[5].imag * 512 };
|
||||
t_real += (z[ 0].real + z[10].real) * 7;
|
||||
t_imag += (z[ 0].imag + z[10].imag) * 7;
|
||||
t_real -= (z[ 2].real + z[ 8].real) * 53;
|
||||
t_imag -= (z[ 2].imag + z[ 8].imag) * 53;
|
||||
t_real += (z[ 4].real + z[ 6].real) * 302;
|
||||
t_imag += (z[ 4].imag + z[ 6].imag) * 302;
|
||||
*(dst_p++) = { t_real / 512, t_imag / 512 };
|
||||
}
|
||||
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
#endif
|
||||
} /* namespace decimate */
|
||||
} /* namespace dsp */
|
||||
|
||||
@@ -31,14 +31,28 @@
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
#include "simd.hpp"
|
||||
|
||||
namespace dsp {
|
||||
namespace decimate {
|
||||
|
||||
class Complex8DecimateBy2CIC3 {
|
||||
public:
|
||||
buffer_c16_t execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
uint32_t _i1_i0 { 0 };
|
||||
uint32_t _q1_q0 { 0 };
|
||||
};
|
||||
|
||||
class TranslateByFSOver4AndDecimateBy2CIC3 {
|
||||
public:
|
||||
buffer_c16_t execute(
|
||||
buffer_c8_t src,
|
||||
buffer_c16_t dst
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
@@ -49,8 +63,8 @@ private:
|
||||
class DecimateBy2CIC3 {
|
||||
public:
|
||||
buffer_c16_t execute(
|
||||
buffer_c16_t src,
|
||||
buffer_c16_t dst
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
@@ -62,20 +76,134 @@ class FIR64AndDecimateBy2Real {
|
||||
public:
|
||||
static constexpr size_t taps_count = 64;
|
||||
|
||||
FIR64AndDecimateBy2Real(
|
||||
void configure(
|
||||
const std::array<int16_t, taps_count>& taps
|
||||
) : taps(taps)
|
||||
{
|
||||
}
|
||||
);
|
||||
|
||||
buffer_s16_t execute(
|
||||
buffer_s16_t src,
|
||||
buffer_s16_t dst
|
||||
const buffer_s16_t& src,
|
||||
const buffer_s16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<int16_t, taps_count + 2> z;
|
||||
const std::array<int16_t, taps_count>& taps;
|
||||
std::array<int16_t, taps_count> taps;
|
||||
};
|
||||
|
||||
class FIRC8xR16x24FS4Decim4 {
|
||||
public:
|
||||
static constexpr size_t taps_count = 24;
|
||||
static constexpr size_t decimation_factor = 4;
|
||||
|
||||
using sample_t = complex8_t;
|
||||
using tap_t = int16_t;
|
||||
|
||||
enum class Shift : bool {
|
||||
Down = true,
|
||||
Up = false
|
||||
};
|
||||
|
||||
FIRC8xR16x24FS4Decim4();
|
||||
|
||||
void configure(
|
||||
const std::array<tap_t, taps_count>& taps,
|
||||
const int32_t scale,
|
||||
const Shift shift = Shift::Down
|
||||
);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> taps_;
|
||||
int32_t output_scale = 0;
|
||||
};
|
||||
|
||||
class FIRC8xR16x24FS4Decim8 {
|
||||
public:
|
||||
static constexpr size_t taps_count = 24;
|
||||
static constexpr size_t decimation_factor = 8;
|
||||
|
||||
using sample_t = complex8_t;
|
||||
using tap_t = int16_t;
|
||||
|
||||
enum class Shift : bool {
|
||||
Down = true,
|
||||
Up = false
|
||||
};
|
||||
|
||||
FIRC8xR16x24FS4Decim8();
|
||||
|
||||
void configure(
|
||||
const std::array<tap_t, taps_count>& taps,
|
||||
const int32_t scale,
|
||||
const Shift shift = Shift::Down
|
||||
);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> taps_;
|
||||
int32_t output_scale = 0;
|
||||
};
|
||||
|
||||
class FIRC16xR16x16Decim2 {
|
||||
public:
|
||||
static constexpr size_t taps_count = 16;
|
||||
static constexpr size_t decimation_factor = 2;
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> taps_;
|
||||
int32_t output_scale = 0;
|
||||
};
|
||||
|
||||
class FIRC16xR16x32Decim8 {
|
||||
public:
|
||||
static constexpr size_t taps_count = 32;
|
||||
static constexpr size_t decimation_factor = 8;
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> taps_;
|
||||
int32_t output_scale = 0;
|
||||
};
|
||||
|
||||
class FIRAndDecimateComplex {
|
||||
@@ -99,16 +227,12 @@ public:
|
||||
const T& taps,
|
||||
const size_t decimation_factor
|
||||
) {
|
||||
samples_ = std::make_unique<samples_t>(taps.size());
|
||||
taps_reversed_ = std::make_unique<taps_t>(taps.size());
|
||||
taps_count_ = taps.size();
|
||||
decimation_factor_ = decimation_factor;
|
||||
std::reverse_copy(taps.cbegin(), taps.cend(), &taps_reversed_[0]);
|
||||
configure(taps.data(), taps.size(), decimation_factor);
|
||||
}
|
||||
|
||||
buffer_c16_t execute(
|
||||
buffer_c16_t src,
|
||||
buffer_c16_t dst
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
@@ -118,124 +242,25 @@ private:
|
||||
std::unique_ptr<taps_t> taps_reversed_;
|
||||
size_t taps_count_;
|
||||
size_t decimation_factor_;
|
||||
|
||||
void configure(
|
||||
const int16_t* const taps,
|
||||
const size_t taps_count,
|
||||
const size_t decimation_factor
|
||||
);
|
||||
};
|
||||
|
||||
class DecimateBy2CIC4Real {
|
||||
public:
|
||||
buffer_s16_t execute(
|
||||
buffer_s16_t src,
|
||||
buffer_s16_t dst
|
||||
const buffer_s16_t& src,
|
||||
const buffer_s16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
int16_t z[5];
|
||||
};
|
||||
#if 0
|
||||
class DecimateBy2HBF5Complex {
|
||||
public:
|
||||
buffer_c16_t execute(
|
||||
buffer_c16_t const src,
|
||||
buffer_c16_t const dst
|
||||
);
|
||||
|
||||
private:
|
||||
complex16_t z[11];
|
||||
};
|
||||
|
||||
class DecimateBy2HBF7Complex {
|
||||
public:
|
||||
buffer_c16_t execute(
|
||||
buffer_c16_t const src,
|
||||
buffer_c16_t const dst
|
||||
);
|
||||
|
||||
private:
|
||||
complex16_t z[11];
|
||||
};
|
||||
#endif
|
||||
/* From http://www.dspguru.com/book/export/html/3
|
||||
|
||||
Here are several basic techniques to fake circular buffers:
|
||||
|
||||
Split the calculation: You can split any FIR calculation into its "pre-wrap"
|
||||
and "post-wrap" parts. By splitting the calculation into these two parts, you
|
||||
essentially can do the circular logic only once, rather than once per tap.
|
||||
(See fir_double_z in FirAlgs.c above.)
|
||||
|
||||
Duplicate the delay line: For a FIR with N taps, use a delay line of size 2N.
|
||||
Copy each sample to its proper location, as well as at location-plus-N.
|
||||
Therefore, the FIR calculation's MAC loop can be done on a flat buffer of N
|
||||
points, starting anywhere within the first set of N points. The second set of
|
||||
N delayed samples provides the "wrap around" comparable to a true circular
|
||||
buffer. (See fir_double_z in FirAlgs.c above.)
|
||||
|
||||
Duplicate the coefficients: This is similar to the above, except that the
|
||||
duplication occurs in terms of the coefficients, not the delay line.
|
||||
Compared to the previous method, this has a calculation advantage of not
|
||||
having to store each incoming sample twice, and it also has a memory
|
||||
advantage when the same coefficient set will be used on multiple delay lines.
|
||||
(See fir_double_h in FirAlgs.c above.)
|
||||
|
||||
Use block processing: In block processing, you use a delay line which is a
|
||||
multiple of the number of taps. You therefore only have to move the data
|
||||
once per block to implement the delay-line mechanism. When the block size
|
||||
becomes "large", the overhead of a moving the delay line once per block
|
||||
becomes negligible.
|
||||
*/
|
||||
|
||||
#if 0
|
||||
template<size_t N>
|
||||
class FIRAndDecimateBy2Complex {
|
||||
public:
|
||||
FIR64AndDecimateBy2Complex(
|
||||
const std::array<int16_t, N>& taps
|
||||
) : taps { taps }
|
||||
{
|
||||
}
|
||||
|
||||
buffer_c16_t execute(
|
||||
buffer_c16_t const src,
|
||||
buffer_c16_t const dst
|
||||
) {
|
||||
/* int16_t input (sample count "n" must be multiple of 4)
|
||||
* -> int16_t output, decimated by 2.
|
||||
* taps are normalized to 1 << 16 == 1.0.
|
||||
*/
|
||||
|
||||
return { dst.p, src.count / 2 };
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<complex16_t, N> z;
|
||||
const std::array<int16_t, N>& taps;
|
||||
|
||||
complex<int16_t> process_one(const size_t start_offset) {
|
||||
const auto split = &z[start_offset];
|
||||
const auto end = &z[z.size()];
|
||||
auto tap = &taps[0];
|
||||
|
||||
complex<int32_t> t { 0, 0 };
|
||||
|
||||
auto p = split;
|
||||
while(p < end) {
|
||||
const auto t = *(tap++);
|
||||
const auto c = *(p++);
|
||||
t.real += c.real * t;
|
||||
t.imag += c.imag * t;
|
||||
}
|
||||
|
||||
p = &z[0];
|
||||
while(p < split) {
|
||||
const auto t = *(tap++);
|
||||
const auto c = *(p++);
|
||||
t.real += c.real * t;
|
||||
t.imag += c.imag * t;
|
||||
}
|
||||
|
||||
return { t.real / 65536, t.imag / 65536 };
|
||||
}
|
||||
};
|
||||
#endif
|
||||
} /* namespace decimate */
|
||||
} /* namespace dsp */
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
namespace dsp {
|
||||
namespace demodulate {
|
||||
|
||||
buffer_s16_t AM::execute(
|
||||
buffer_c16_t src,
|
||||
buffer_s16_t dst
|
||||
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. */
|
||||
@@ -49,15 +49,8 @@ buffer_s16_t AM::execute(
|
||||
const uint32_t sample1 = *__SIMD32(src_p)++;
|
||||
const uint32_t mag_sq0 = __SMUAD(sample0, sample0);
|
||||
const uint32_t mag_sq1 = __SMUAD(sample1, sample1);
|
||||
const int32_t mag0_int = __builtin_sqrtf(mag_sq0);
|
||||
const int32_t mag0_sat = __SSAT(mag0_int, 16);
|
||||
const int32_t mag1_int = __builtin_sqrtf(mag_sq1);
|
||||
const int32_t mag1_sat = __SSAT(mag1_int, 16);
|
||||
*__SIMD32(dst_p)++ = __PKHBT(
|
||||
mag0_sat,
|
||||
mag1_sat,
|
||||
16
|
||||
);
|
||||
*(dst_p++) = __builtin_sqrtf(mag_sq0);
|
||||
*(dst_p++) = __builtin_sqrtf(mag_sq1);
|
||||
}
|
||||
|
||||
return { dst.p, src.count, src.sampling_rate };
|
||||
@@ -69,17 +62,44 @@ static inline float angle_approx_4deg0(const complex32_t t) {
|
||||
}
|
||||
*/
|
||||
static inline float angle_approx_0deg27(const complex32_t t) {
|
||||
const auto x = static_cast<float>(t.imag()) / static_cast<float>(t.real());
|
||||
return x / (1.0f + 0.28086f * x * x);
|
||||
if( t.real() ) {
|
||||
const auto x = static_cast<float>(t.imag()) / static_cast<float>(t.real());
|
||||
return x / (1.0f + 0.28086f * x * x);
|
||||
} else {
|
||||
return (t.imag() < 0) ? -1.5707963268f : 1.5707963268f;
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
static inline float angle_precise(const complex32_t t) {
|
||||
return atan2f(t.imag(), t.real());
|
||||
}
|
||||
*/
|
||||
|
||||
buffer_f32_t FM::execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_f32_t& dst
|
||||
) {
|
||||
auto z = z_;
|
||||
|
||||
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 s0 = *__SIMD32(src_p)++;
|
||||
const auto s1 = *__SIMD32(src_p)++;
|
||||
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;
|
||||
}
|
||||
z_ = z;
|
||||
|
||||
return { dst.p, src.count, src.sampling_rate };
|
||||
}
|
||||
|
||||
buffer_s16_t FM::execute(
|
||||
buffer_c16_t src,
|
||||
buffer_s16_t dst
|
||||
const buffer_c16_t& src,
|
||||
const buffer_s16_t& dst
|
||||
) {
|
||||
auto z = z_;
|
||||
|
||||
|
||||
@@ -29,39 +29,36 @@ namespace demodulate {
|
||||
|
||||
class AM {
|
||||
public:
|
||||
buffer_s16_t execute(
|
||||
buffer_c16_t src,
|
||||
buffer_s16_t dst
|
||||
buffer_f32_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_f32_t& dst
|
||||
);
|
||||
};
|
||||
|
||||
class FM {
|
||||
public:
|
||||
/*
|
||||
* angle: -pi to pi. output range: -32768 to 32767.
|
||||
* Maximum delta-theta (output of atan2) at maximum deviation frequency:
|
||||
* delta_theta_max = 2 * pi * deviation / sampling_rate
|
||||
*/
|
||||
constexpr FM(
|
||||
const float sampling_rate,
|
||||
const float deviation_hz
|
||||
) : z_ { 0 },
|
||||
k { static_cast<float>(32767.0f / (2.0 * pi * deviation_hz / sampling_rate)) }
|
||||
{
|
||||
}
|
||||
buffer_f32_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_f32_t& dst
|
||||
);
|
||||
|
||||
buffer_s16_t execute(
|
||||
buffer_c16_t src,
|
||||
buffer_s16_t dst
|
||||
const buffer_c16_t& src,
|
||||
const buffer_s16_t& dst
|
||||
);
|
||||
|
||||
void configure(const float sampling_rate, const float deviation_hz) {
|
||||
/*
|
||||
* angle: -pi to pi. output range: -32768 to 32767.
|
||||
* Maximum delta-theta (output of atan2) at maximum deviation frequency:
|
||||
* delta_theta_max = 2 * pi * deviation / sampling_rate
|
||||
*/
|
||||
k = static_cast<float>(32767.0f / (2.0 * pi * deviation_hz / sampling_rate));
|
||||
}
|
||||
|
||||
private:
|
||||
complex16_t::rep_type z_;
|
||||
float k;
|
||||
complex16_t::rep_type z_ { 0 };
|
||||
float k { 0 };
|
||||
};
|
||||
|
||||
} /* namespace demodulate */
|
||||
|
||||
@@ -1,278 +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,
|
||||
} },
|
||||
};
|
||||
|
||||
// 41kHz/70kHz @ 192kHz
|
||||
// http://t-filter.appspot.com
|
||||
constexpr fir_taps_real<64> taps_64_lp_410_700_tfilter {
|
||||
.pass_frequency_normalized = 0.213f,
|
||||
.stop_frequency_normalized = 0.364f,
|
||||
.taps = { {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
3,
|
||||
-3,
|
||||
-7,
|
||||
12,
|
||||
10,
|
||||
-35,
|
||||
-3,
|
||||
79,
|
||||
-37,
|
||||
-138,
|
||||
146,
|
||||
180,
|
||||
-361,
|
||||
-126,
|
||||
688,
|
||||
-149,
|
||||
-1062,
|
||||
800,
|
||||
1308,
|
||||
-1991,
|
||||
-1092,
|
||||
3963,
|
||||
-286,
|
||||
-7710,
|
||||
6211,
|
||||
32368,
|
||||
32368,
|
||||
6211,
|
||||
-7710,
|
||||
-286,
|
||||
3963,
|
||||
-1092,
|
||||
-1991,
|
||||
1308,
|
||||
800,
|
||||
-1062,
|
||||
-149,
|
||||
688,
|
||||
-126,
|
||||
-361,
|
||||
180,
|
||||
146,
|
||||
-138,
|
||||
-37,
|
||||
79,
|
||||
-3,
|
||||
-35,
|
||||
10,
|
||||
12,
|
||||
-7,
|
||||
-3,
|
||||
3,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
} },
|
||||
};
|
||||
|
||||
/* 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__*/
|
||||
57
firmware/baseband/dsp_iir.cpp
Normal file
57
firmware/baseband/dsp_iir.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -27,13 +27,27 @@
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
struct iir_biquad_config_t {
|
||||
const std::array<float, 3> b;
|
||||
const std::array<float, 3> a;
|
||||
std::array<float, 3> b;
|
||||
std::array<float, 3> a;
|
||||
};
|
||||
|
||||
constexpr iir_biquad_config_t iir_config_passthrough {
|
||||
{ { 1.0f, 0.0f, 0.0f } },
|
||||
{ { 0.0f, 0.0f, 0.0f } },
|
||||
};
|
||||
|
||||
constexpr iir_biquad_config_t iir_config_no_pass {
|
||||
{ { 0.0f, 0.0f, 0.0f } },
|
||||
{ { 0.0f, 0.0f, 0.0f } },
|
||||
};
|
||||
|
||||
class IIRBiquadFilter {
|
||||
public:
|
||||
// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
||||
constexpr IIRBiquadFilter(
|
||||
) : IIRBiquadFilter(iir_config_no_pass)
|
||||
{
|
||||
}
|
||||
|
||||
// Assume all coefficients are normalized so that a0=1.0
|
||||
constexpr IIRBiquadFilter(
|
||||
@@ -42,34 +56,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void execute(buffer_s16_t buffer_in, buffer_s16_t buffer_out) {
|
||||
// TODO: Assert that buffer_out.count == buffer_in.count.
|
||||
for(size_t i=0; i<buffer_out.count; i++) {
|
||||
buffer_out.p[i] = execute_sample(buffer_in.p[i]);
|
||||
}
|
||||
}
|
||||
void configure(const iir_biquad_config_t& new_config);
|
||||
|
||||
void execute_in_place(buffer_s16_t buffer) {
|
||||
execute(buffer, buffer);
|
||||
}
|
||||
void execute(const buffer_f32_t& buffer_in, const buffer_f32_t& buffer_out);
|
||||
void execute_in_place(const buffer_f32_t& buffer);
|
||||
|
||||
private:
|
||||
const iir_biquad_config_t config;
|
||||
iir_biquad_config_t config;
|
||||
std::array<float, 3> x { { 0.0f, 0.0f, 0.0f } };
|
||||
std::array<float, 3> y { { 0.0f, 0.0f, 0.0f } };
|
||||
|
||||
float execute_sample(const float in) {
|
||||
x[0] = x[1];
|
||||
x[1] = x[2];
|
||||
x[2] = in;
|
||||
|
||||
y[0] = y[1];
|
||||
y[1] = y[2];
|
||||
y[2] = config.b[0] * x[2] + config.b[1] * x[1] + config.b[2] * x[0]
|
||||
- config.a[1] * y[1] - config.a[2] * y[0];
|
||||
|
||||
return y[2];
|
||||
}
|
||||
};
|
||||
|
||||
#endif/*__DSP_IIR_H__*/
|
||||
|
||||
@@ -24,14 +24,37 @@
|
||||
|
||||
#include "dsp_iir.hpp"
|
||||
|
||||
constexpr iir_biquad_config_t audio_hpf_config {
|
||||
{ 0.93346032f, -1.86687724f, 0.93346032f },
|
||||
{ 1.0f , -1.97730264f, 0.97773668f }
|
||||
// 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__*/
|
||||
|
||||
@@ -24,22 +24,30 @@
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
bool FMSquelch::execute(buffer_s16_t audio) {
|
||||
bool FMSquelch::execute(const buffer_f32_t& audio) {
|
||||
if( threshold_squared == 0.0f ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: No hard-coded array size.
|
||||
std::array<int16_t, N> squelch_energy_buffer;
|
||||
const buffer_s16_t squelch_energy {
|
||||
std::array<float, N> squelch_energy_buffer;
|
||||
const buffer_f32_t squelch_energy {
|
||||
squelch_energy_buffer.data(),
|
||||
squelch_energy_buffer.size()
|
||||
};
|
||||
non_audio_hpf.execute(audio, squelch_energy);
|
||||
|
||||
uint64_t max_squared = 0;
|
||||
float non_audio_max_squared = 0;
|
||||
for(const auto sample : squelch_energy_buffer) {
|
||||
const uint64_t sample_squared = sample * sample;
|
||||
if( sample_squared > max_squared ) {
|
||||
max_squared = sample_squared;
|
||||
const float sample_squared = sample * sample;
|
||||
if( sample_squared > non_audio_max_squared ) {
|
||||
non_audio_max_squared = sample_squared;
|
||||
}
|
||||
}
|
||||
|
||||
return (max_squared < (threshold * threshold));
|
||||
return (non_audio_max_squared < threshold_squared);
|
||||
}
|
||||
|
||||
void FMSquelch::set_threshold(const float new_value) {
|
||||
threshold_squared = new_value * new_value;
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@
|
||||
|
||||
class FMSquelch {
|
||||
public:
|
||||
bool execute(buffer_s16_t audio);
|
||||
bool execute(const buffer_f32_t& audio);
|
||||
|
||||
void set_threshold(const float new_value);
|
||||
|
||||
private:
|
||||
static constexpr size_t N = 32;
|
||||
static constexpr int16_t threshold = 3072;
|
||||
float threshold_squared { 0.0f };
|
||||
|
||||
// nyquist = 48000 / 2.0
|
||||
// scipy.signal.iirdesign(wp=8000 / nyquist, ws= 4000 / nyquist, gpass=1, gstop=18, ftype='ellip')
|
||||
IIRBiquadFilter non_audio_hpf { non_audio_hpf_config };
|
||||
};
|
||||
|
||||
|
||||
@@ -21,10 +21,99 @@
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "message_queue.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
Thread* thread_event_loop = nullptr;
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
void events_initialize(Thread* const event_loop_thread) {
|
||||
thread_event_loop = event_loop_thread;
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(MAPP_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
chSysLockFromIsr();
|
||||
EventDispatcher::events_flag_isr(EVT_MASK_BASEBAND);
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
creg::m0apptxevent::clear();
|
||||
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Thread* EventDispatcher::thread_event_loop = nullptr;
|
||||
|
||||
void EventDispatcher::run() {
|
||||
thread_event_loop = chThdSelf();
|
||||
lpc43xx::creg::m0apptxevent::enable();
|
||||
|
||||
baseband_thread.thread_main = chThdSelf();
|
||||
baseband_thread.thread_rssi = rssi_thread.start(NORMALPRIO + 10);
|
||||
baseband_thread.start(NORMALPRIO + 20);
|
||||
|
||||
while(is_running) {
|
||||
const auto events = wait();
|
||||
dispatch(events);
|
||||
}
|
||||
|
||||
lpc43xx::creg::m0apptxevent::disable();
|
||||
}
|
||||
|
||||
void EventDispatcher::request_stop() {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
eventmask_t EventDispatcher::wait() {
|
||||
return chEvtWaitAny(ALL_EVENTS);
|
||||
}
|
||||
|
||||
void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
if( events & EVT_MASK_BASEBAND ) {
|
||||
handle_baseband_queue();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_SPECTRUM ) {
|
||||
handle_spectrum();
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_baseband_queue() {
|
||||
std::array<uint8_t, Message::MAX_SIZE> message_buffer;
|
||||
while(Message* const message = shared_memory.baseband_queue.peek(message_buffer)) {
|
||||
on_message(message);
|
||||
shared_memory.baseband_queue.skip();
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::on_message(const Message* const message) {
|
||||
switch(message->id) {
|
||||
case Message::ID::Shutdown:
|
||||
on_message_shutdown(*reinterpret_cast<const ShutdownMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
on_message_default(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::on_message_shutdown(const ShutdownMessage&) {
|
||||
request_stop();
|
||||
}
|
||||
|
||||
void EventDispatcher::on_message_default(const Message* const message) {
|
||||
baseband_thread.on_message(message);
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_spectrum() {
|
||||
const UpdateSpectrumMessage message;
|
||||
baseband_thread.on_message(&message);
|
||||
}
|
||||
|
||||
@@ -22,25 +22,54 @@
|
||||
#ifndef __EVENT_M4_H__
|
||||
#define __EVENT_M4_H__
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
constexpr auto EVT_MASK_BASEBAND = EVENT_MASK(0);
|
||||
constexpr auto EVT_MASK_SPECTRUM = EVENT_MASK(1);
|
||||
|
||||
void events_initialize(Thread* const event_loop_thread);
|
||||
class EventDispatcher {
|
||||
public:
|
||||
void run();
|
||||
void request_stop();
|
||||
|
||||
extern Thread* thread_event_loop;
|
||||
|
||||
inline void events_flag(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignal(thread_event_loop, events);
|
||||
static inline void events_flag(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignal(thread_event_loop, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void events_flag_isr(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignalI(thread_event_loop, events);
|
||||
static inline void events_flag_isr(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignalI(thread_event_loop, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static Thread* thread_event_loop;
|
||||
|
||||
BasebandThread baseband_thread;
|
||||
RSSIThread rssi_thread;
|
||||
|
||||
bool is_running = true;
|
||||
|
||||
eventmask_t wait();
|
||||
|
||||
void dispatch(const eventmask_t events);
|
||||
|
||||
void handle_baseband_queue();
|
||||
|
||||
void on_message(const Message* const message);
|
||||
void on_message_shutdown(const ShutdownMessage&);
|
||||
void on_message_default(const Message* const message);
|
||||
|
||||
void handle_spectrum();
|
||||
};
|
||||
|
||||
#endif/*__EVENT_M4_H__*/
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
#include "ch.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
|
||||
@@ -29,44 +28,13 @@
|
||||
|
||||
#include "gpdma.hpp"
|
||||
|
||||
#include "baseband.hpp"
|
||||
#include "baseband_dma.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "irq_ipc_m4.hpp"
|
||||
|
||||
#include "rssi.hpp"
|
||||
#include "rssi_dma.hpp"
|
||||
|
||||
#include "touch_dma.hpp"
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "dsp_fft.hpp"
|
||||
#include "dsp_fir_taps.hpp"
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "dsp_squelch.hpp"
|
||||
|
||||
#include "baseband_stats_collector.hpp"
|
||||
#include "rssi_stats_collector.hpp"
|
||||
|
||||
#include "channel_decimator.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
#include "baseband_processor.hpp"
|
||||
#include "proc_am_audio.hpp"
|
||||
#include "proc_nfm_audio.hpp"
|
||||
#include "proc_wfm_audio.hpp"
|
||||
#include "proc_ais.hpp"
|
||||
#include "proc_wideband_spectrum.hpp"
|
||||
#include "proc_tpms.hpp"
|
||||
#include "proc_afskrx.hpp"
|
||||
#include "proc_sigfrx.hpp"
|
||||
|
||||
#include "clock_recovery.hpp"
|
||||
#include "packet_builder.hpp"
|
||||
|
||||
#include "message_queue.hpp"
|
||||
|
||||
@@ -84,9 +52,6 @@
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <bitset>
|
||||
#include <math.h>
|
||||
|
||||
static baseband::Direction direction = baseband::Direction::Receive;
|
||||
|
||||
class ThreadBase {
|
||||
public:
|
||||
@@ -141,41 +106,22 @@ private:
|
||||
};
|
||||
|
||||
while(true) {
|
||||
if (direction == baseband::Direction::Transmit) {
|
||||
const auto buffer_tmp = baseband::dma::wait_for_tx_buffer();
|
||||
|
||||
const buffer_c8_t buffer {
|
||||
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
|
||||
};
|
||||
// TODO: Place correct sampling rate into buffer returned here:
|
||||
const auto buffer_tmp = baseband::dma::wait_for_rx_buffer();
|
||||
const buffer_c8_t buffer {
|
||||
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
|
||||
};
|
||||
|
||||
if( baseband_processor ) {
|
||||
baseband_processor->execute(buffer);
|
||||
}
|
||||
|
||||
stats.process(buffer,
|
||||
[](const BasebandStatistics statistics) {
|
||||
const BasebandStatisticsMessage message { statistics };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const auto buffer_tmp = baseband::dma::wait_for_rx_buffer();
|
||||
|
||||
const buffer_c8_t buffer {
|
||||
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
|
||||
};
|
||||
|
||||
if( baseband_processor ) {
|
||||
baseband_processor->execute(buffer);
|
||||
}
|
||||
|
||||
stats.process(buffer,
|
||||
[](const BasebandStatistics statistics) {
|
||||
const BasebandStatisticsMessage message { statistics };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
);
|
||||
if( baseband_processor ) {
|
||||
baseband_processor->execute(buffer);
|
||||
}
|
||||
|
||||
stats.process(buffer,
|
||||
[](const BasebandStatistics statistics) {
|
||||
const BasebandStatisticsMessage message { statistics };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -346,25 +292,8 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
const auto baseband_buffer =
|
||||
new std::array<baseband::sample_t, 8192>();
|
||||
static constexpr auto direction = baseband::Direction::Receive;
|
||||
|
||||
char ram_loop[32];
|
||||
typedef int (*fn_ptr)(void);
|
||||
fn_ptr loop_ptr;
|
||||
|
||||
void ram_loop_fn(void) {
|
||||
while(1) {}
|
||||
}
|
||||
|
||||
void wait_for_switch(void) {
|
||||
memcpy(&ram_loop[0], reinterpret_cast<char*>(&ram_loop_fn), 32);
|
||||
loop_ptr = reinterpret_cast<fn_ptr>(&ram_loop[0]);
|
||||
ReadyForSwitchMessage message;
|
||||
shared_memory.application_queue.push(message);
|
||||
(*loop_ptr)();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init();
|
||||
|
||||
@@ -373,18 +302,6 @@ int main(void) {
|
||||
|
||||
EventDispatcher event_dispatcher;
|
||||
auto& message_handlers = event_dispatcher.message_handlers();
|
||||
|
||||
message_handlers.register_handler(Message::ID::ModuleID,
|
||||
[&message_handlers](Message* p) {
|
||||
ModuleIDMessage reply;
|
||||
auto message = static_cast<ModuleIDMessage*>(p);
|
||||
if (message->query == true) { // Shouldn't be needed
|
||||
memcpy(reply.md5_signature, (const void *)(0x10087FF0), 16);
|
||||
reply.query = false;
|
||||
shared_memory.application_queue.push(reply);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
message_handlers.register_handler(Message::ID::BasebandConfiguration,
|
||||
[&message_handlers](const Message* const p) {
|
||||
@@ -403,47 +320,29 @@ int main(void) {
|
||||
delete old_p;
|
||||
|
||||
switch(message->configuration.mode) {
|
||||
case RX_NBAM_AUDIO:
|
||||
direction = baseband::Direction::Receive;
|
||||
case 0:
|
||||
baseband_thread.baseband_processor = new NarrowbandAMAudio();
|
||||
break;
|
||||
|
||||
case RX_NBFM_AUDIO:
|
||||
direction = baseband::Direction::Receive;
|
||||
case 1:
|
||||
baseband_thread.baseband_processor = new NarrowbandFMAudio();
|
||||
break;
|
||||
|
||||
case RX_WBFM_AUDIO:
|
||||
case 2:
|
||||
baseband_thread.baseband_processor = new WidebandFMAudio();
|
||||
break;
|
||||
|
||||
case RX_AIS:
|
||||
direction = baseband::Direction::Receive;
|
||||
case 3:
|
||||
baseband_thread.baseband_processor = new AISProcessor();
|
||||
break;
|
||||
|
||||
case RX_WBSPECTRUM:
|
||||
direction = baseband::Direction::Receive;
|
||||
case 4:
|
||||
baseband_thread.baseband_processor = new WidebandSpectrum();
|
||||
break;
|
||||
|
||||
case RX_TPMS:
|
||||
direction = baseband::Direction::Receive;
|
||||
case 5:
|
||||
baseband_thread.baseband_processor = new TPMSProcessor();
|
||||
break;
|
||||
|
||||
case RX_AFSK:
|
||||
direction = baseband::Direction::Receive;
|
||||
baseband_thread.baseband_processor = new AFSKRXProcessor();
|
||||
break;
|
||||
|
||||
case RX_SIGFOX:
|
||||
direction = baseband::Direction::Receive;
|
||||
baseband_thread.baseband_processor = new SIGFRXProcessor();
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
wait_for_switch();
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -454,14 +353,8 @@ int main(void) {
|
||||
rf::rssi::start();
|
||||
}
|
||||
baseband::dma::enable(direction);
|
||||
rf::rssi::stop();
|
||||
}
|
||||
}
|
||||
|
||||
baseband::dma::configure(
|
||||
baseband_buffer->data(),
|
||||
direction
|
||||
);
|
||||
|
||||
baseband_thread.baseband_configuration = message->configuration;
|
||||
}
|
||||
@@ -475,26 +368,23 @@ int main(void) {
|
||||
|
||||
/* TODO: Ensure DMAs are configured to point at first LLI in chain. */
|
||||
|
||||
rf::rssi::dma::allocate(4, 400);
|
||||
if( direction == baseband::Direction::Receive ) {
|
||||
rf::rssi::dma::allocate(4, 400);
|
||||
}
|
||||
|
||||
touch::dma::allocate();
|
||||
touch::dma::enable();
|
||||
|
||||
|
||||
const auto baseband_buffer =
|
||||
new std::array<baseband::sample_t, 8192>();
|
||||
baseband::dma::configure(
|
||||
baseband_buffer->data(),
|
||||
direction
|
||||
);
|
||||
|
||||
//baseband::dma::allocate(4, 2048);
|
||||
|
||||
event_dispatcher.run();
|
||||
|
||||
shutdown();
|
||||
|
||||
ShutdownMessage shutdown_message;
|
||||
shared_memory.application_queue.push(shutdown_message);
|
||||
|
||||
halt();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,26 @@
|
||||
|
||||
#include "matched_filter.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace dsp {
|
||||
namespace matched_filter {
|
||||
|
||||
void MatchedFilter::configure(
|
||||
const tap_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]);
|
||||
}
|
||||
|
||||
bool MatchedFilter::execute_once(
|
||||
const sample_t input
|
||||
) {
|
||||
|
||||
@@ -22,17 +22,10 @@
|
||||
#ifndef __MATCHED_FILTER_H__
|
||||
#define __MATCHED_FILTER_H__
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <complex>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace dsp {
|
||||
namespace matched_filter {
|
||||
|
||||
@@ -61,11 +54,7 @@ public:
|
||||
const T& taps,
|
||||
size_t decimation_factor
|
||||
) {
|
||||
samples_ = std::make_unique<samples_t>(taps.size());
|
||||
taps_reversed_ = std::make_unique<taps_t>(taps.size());
|
||||
taps_count_ = taps.size();
|
||||
decimation_factor_ = decimation_factor;
|
||||
std::reverse_copy(taps.cbegin(), taps.cend(), &taps_reversed_[0]);
|
||||
configure(taps.data(), taps.size(), decimation_factor);
|
||||
}
|
||||
|
||||
bool execute_once(const sample_t input);
|
||||
@@ -93,6 +82,12 @@ private:
|
||||
bool is_new_decimation_cycle() const {
|
||||
return (decimation_phase == 0);
|
||||
}
|
||||
|
||||
void configure(
|
||||
const tap_t* const taps,
|
||||
const size_t taps_count,
|
||||
const size_t decimation_factor
|
||||
);
|
||||
};
|
||||
|
||||
} /* namespace matched_filter */
|
||||
|
||||
@@ -28,12 +28,26 @@
|
||||
#include <functional>
|
||||
|
||||
#include "bit_pattern.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
|
||||
struct NeverMatch {
|
||||
bool operator()(const BitHistory&, const size_t) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct FixedLength {
|
||||
bool operator()(const BitHistory&, const size_t symbols_received) const {
|
||||
return symbols_received >= length;
|
||||
}
|
||||
|
||||
const size_t length;
|
||||
};
|
||||
|
||||
template<typename PreambleMatcher, typename UnstuffMatcher, typename EndMatcher>
|
||||
class PacketBuilder {
|
||||
public:
|
||||
using PayloadType = std::bitset<1024>;
|
||||
using PayloadHandlerFunc = std::function<void(const PayloadType& payload, const size_t bits_received)>;
|
||||
using PayloadHandlerFunc = std::function<void(const baseband::Packet& packet)>;
|
||||
|
||||
PacketBuilder(
|
||||
const PreambleMatcher preamble_matcher,
|
||||
@@ -64,18 +78,19 @@ public:
|
||||
|
||||
switch(state) {
|
||||
case State::Preamble:
|
||||
if( preamble(bit_history, bits_received) ) {
|
||||
if( preamble(bit_history, packet.size()) ) {
|
||||
state = State::Payload;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Payload:
|
||||
if( !unstuff(bit_history, bits_received) ) {
|
||||
payload[bits_received++] = symbol;
|
||||
if( !unstuff(bit_history, packet.size()) ) {
|
||||
packet.add(symbol);
|
||||
}
|
||||
|
||||
if( end(bit_history, bits_received) ) {
|
||||
payload_handler(payload, bits_received);
|
||||
if( end(bit_history, packet.size()) ) {
|
||||
packet.set_timestamp(Timestamp::now());
|
||||
payload_handler(packet);
|
||||
reset_state();
|
||||
} else {
|
||||
if( packet_truncated() ) {
|
||||
@@ -97,7 +112,7 @@ private:
|
||||
};
|
||||
|
||||
bool packet_truncated() const {
|
||||
return bits_received >= payload.size();
|
||||
return packet.size() >= packet.capacity();
|
||||
}
|
||||
|
||||
const PayloadHandlerFunc payload_handler;
|
||||
@@ -107,12 +122,11 @@ private:
|
||||
UnstuffMatcher unstuff;
|
||||
EndMatcher end;
|
||||
|
||||
size_t bits_received { 0 };
|
||||
State state { State::Preamble };
|
||||
PayloadType payload;
|
||||
baseband::Packet packet;
|
||||
|
||||
void reset_state() {
|
||||
bits_received = 0;
|
||||
packet.clear();
|
||||
state = State::Preamble;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,36 +23,28 @@
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "i2s.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
void AISProcessor::execute(buffer_c8_t buffer) {
|
||||
AISProcessor::AISProcessor() {
|
||||
decim_0.configure(taps_11k0_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_11k0_decim_1.taps, 131072);
|
||||
}
|
||||
|
||||
void AISProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
|
||||
auto decimator_out = decimator.execute(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 decimator_out = decim_1_out;
|
||||
|
||||
/* 76.8kHz, 64 samples */
|
||||
/* 38.4kHz, 32 samples */
|
||||
feed_channel_stats(decimator_out);
|
||||
/* No spectrum display while AIS decoding.
|
||||
feed_channel_spectrum(
|
||||
channel,
|
||||
decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized,
|
||||
decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized
|
||||
);
|
||||
*/
|
||||
|
||||
for(size_t i=0; i<decimator_out.count; i++) {
|
||||
// TODO: No idea why implicit cast int16_t->float is not allowed.
|
||||
const std::complex<float> sample {
|
||||
static_cast<float>(decimator_out.p[i].real()),
|
||||
static_cast<float>(decimator_out.p[i].imag())
|
||||
};
|
||||
if( mf.execute_once(sample) ) {
|
||||
if( mf.execute_once(decimator_out.p[i]) ) {
|
||||
clock_recovery(mf.get_output());
|
||||
}
|
||||
}
|
||||
|
||||
i2s::i2s0::tx_mute();
|
||||
}
|
||||
|
||||
void AISProcessor::consume_symbol(
|
||||
@@ -65,11 +57,8 @@ void AISProcessor::consume_symbol(
|
||||
}
|
||||
|
||||
void AISProcessor::payload_handler(
|
||||
const std::bitset<1024>& payload,
|
||||
const size_t bits_received
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
AISPacketMessage message;
|
||||
message.packet.payload = payload;
|
||||
message.packet.bits_received = bits_received;
|
||||
const AISPacketMessage message { packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "clock_recovery.hpp"
|
||||
#include "symbol_coding.hpp"
|
||||
#include "packet_builder.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
@@ -41,13 +42,20 @@
|
||||
|
||||
class AISProcessor : public BasebandProcessor {
|
||||
public:
|
||||
using payload_t = std::bitset<1024>;
|
||||
AISProcessor();
|
||||
|
||||
void execute(buffer_c8_t buffer) override;
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
private:
|
||||
ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By32 };
|
||||
dsp::matched_filter::MatchedFilter mf { baseband::ais::rrc_taps_76k8_4t_p, 4 };
|
||||
std::array<complex16_t, 512> dst;
|
||||
const buffer_c16_t dst_buffer {
|
||||
dst.data(),
|
||||
dst.size()
|
||||
};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0;
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1;
|
||||
dsp::matched_filter::MatchedFilter mf { baseband::ais::rrc_taps_38k4_4t_p, 2 };
|
||||
|
||||
clock_recovery::ClockRecovery<clock_recovery::FixedErrorFilter> clock_recovery {
|
||||
19200, 9600, { 0.0555f },
|
||||
@@ -58,13 +66,13 @@ private:
|
||||
{ 0b0101010101111110, 16, 1 },
|
||||
{ 0b111110, 6 },
|
||||
{ 0b01111110, 8 },
|
||||
[this](const payload_t& payload, const size_t bits_received) {
|
||||
this->payload_handler(payload, bits_received);
|
||||
[this](const baseband::Packet& packet) {
|
||||
this->payload_handler(packet);
|
||||
}
|
||||
};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const payload_t& payload, const size_t bits_received);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
};
|
||||
|
||||
#endif/*__PROC_AIS_H__*/
|
||||
|
||||
@@ -21,39 +21,67 @@
|
||||
|
||||
#include "proc_am_audio.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "audio_output.hpp"
|
||||
|
||||
void NarrowbandAMAudio::execute(buffer_c8_t buffer) {
|
||||
auto decimator_out = decimator.execute(buffer);
|
||||
#include <array>
|
||||
|
||||
const buffer_c16_t work_baseband_buffer {
|
||||
(complex16_t*)decimator_out.p,
|
||||
sizeof(*decimator_out.p) * decimator_out.count
|
||||
};
|
||||
void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
|
||||
if( !configured ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 96kHz complex<int16_t>[64]
|
||||
* -> FIR filter, <?kHz (0.???fs) pass, gain 1.0
|
||||
* -> 48kHz int16_t[32] */
|
||||
auto channel = channel_filter.execute(decimator_out, work_baseband_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);
|
||||
|
||||
// TODO: Feed channel_stats post-decimation data?
|
||||
feed_channel_stats(channel);
|
||||
feed_channel_spectrum(
|
||||
channel,
|
||||
decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized,
|
||||
decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized
|
||||
);
|
||||
feed_channel_stats(channel_out);
|
||||
channel_spectrum.feed(channel_out, channel_filter_pass_f, channel_filter_stop_f);
|
||||
|
||||
const buffer_s16_t work_audio_buffer {
|
||||
(int16_t*)decimator_out.p,
|
||||
sizeof(*decimator_out.p) * decimator_out.count
|
||||
};
|
||||
auto audio = demod.execute(channel_out, work_audio_buffer);
|
||||
|
||||
/* 48kHz complex<int16_t>[32]
|
||||
* -> AM demodulation
|
||||
* -> 48kHz int16_t[32] */
|
||||
auto audio = demod.execute(channel, work_audio_buffer);
|
||||
|
||||
audio_hpf.execute_in_place(audio);
|
||||
fill_audio_buffer(audio);
|
||||
audio_output.write(audio);
|
||||
}
|
||||
|
||||
void NarrowbandAMAudio::on_message(const Message* const message) {
|
||||
switch(message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
channel_spectrum.on_message(message);
|
||||
break;
|
||||
|
||||
case Message::ID::AMConfigure:
|
||||
configure(*reinterpret_cast<const AMConfigureMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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_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 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;
|
||||
|
||||
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_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);
|
||||
|
||||
configured = true;
|
||||
}
|
||||
|
||||
@@ -24,28 +24,45 @@
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
|
||||
#include "channel_decimator.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "dsp_fir_taps.hpp"
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_iir_config.hpp"
|
||||
|
||||
#include "audio_output.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class NarrowbandAMAudio : public BasebandProcessor {
|
||||
public:
|
||||
NarrowbandAMAudio() {
|
||||
decimator.set_decimation_factor(ChannelDecimator::DecimationFactor::By32);
|
||||
channel_filter.configure(channel_filter_taps.taps, 2);
|
||||
}
|
||||
|
||||
void execute(buffer_c8_t buffer) override;
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
ChannelDecimator decimator;
|
||||
const fir_taps_real<64>& channel_filter_taps = taps_64_lp_031_070_tfilter;
|
||||
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)
|
||||
};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0;
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1;
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter;
|
||||
uint32_t channel_filter_pass_f;
|
||||
uint32_t channel_filter_stop_f;
|
||||
|
||||
dsp::demodulate::AM demod;
|
||||
IIRBiquadFilter audio_hpf { audio_hpf_config };
|
||||
|
||||
AudioOutput audio_output;
|
||||
|
||||
SpectrumCollector channel_spectrum;
|
||||
|
||||
bool configured { false };
|
||||
void configure(const AMConfigureMessage& message);
|
||||
};
|
||||
|
||||
#endif/*__PROC_AM_AUDIO_H__*/
|
||||
|
||||
103
firmware/baseband/proc_ert.cpp
Normal file
103
firmware/baseband/proc_ert.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "proc_ert.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
float ERTProcessor::abs(const complex8_t& v) {
|
||||
// const int16_t r = v.real() - offset_i;
|
||||
// const int16_t i = v.imag() - offset_q;
|
||||
// const uint32_t r2 = r * r;
|
||||
// const uint32_t i2 = i * i;
|
||||
// const uint32_t r2_i2 = r2 + i2;
|
||||
// return std::sqrt(static_cast<float>(r2_i2));
|
||||
const float r = static_cast<float>(v.real()) - offset_i;
|
||||
const float i = static_cast<float>(v.imag()) - offset_q;
|
||||
const float r2 = r * r;
|
||||
const float i2 = i * i;
|
||||
const float r2_i2 = r2 + i2;
|
||||
return std::sqrt(r2_i2);
|
||||
}
|
||||
|
||||
void ERTProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 4.194304MHz, 2048 samples */
|
||||
|
||||
const complex8_t* src = &buffer.p[0];
|
||||
const complex8_t* const src_end = &buffer.p[buffer.count];
|
||||
|
||||
average_i += src->real();
|
||||
average_q += src->imag();
|
||||
average_count++;
|
||||
if( average_count == average_window ) {
|
||||
offset_i = static_cast<float>(average_i) / average_window;
|
||||
offset_q = static_cast<float>(average_q) / average_window;
|
||||
average_i = 0;
|
||||
average_q = 0;
|
||||
average_count = 0;
|
||||
}
|
||||
|
||||
const float gain = 128 * samples_per_symbol;
|
||||
const float k = 1.0f / gain;
|
||||
|
||||
while(src < src_end) {
|
||||
float sum = 0.0f;
|
||||
for(size_t i=0; i<(samples_per_symbol / 2); i++) {
|
||||
sum += abs(*(src++));
|
||||
}
|
||||
sum_half_period[1] = sum_half_period[0];
|
||||
sum_half_period[0] = sum;
|
||||
|
||||
sum_period[2] = sum_period[1];
|
||||
sum_period[1] = sum_period[0];
|
||||
sum_period[0] = (sum_half_period[0] + sum_half_period[1]) * k;
|
||||
|
||||
manchester[2] = manchester[1];
|
||||
manchester[1] = manchester[0];
|
||||
manchester[0] = sum_period[2] - sum_period[0];
|
||||
|
||||
const auto data = manchester[0] - manchester[2];
|
||||
|
||||
clock_recovery(data);
|
||||
}
|
||||
}
|
||||
|
||||
void ERTProcessor::consume_symbol(
|
||||
const float raw_symbol
|
||||
) {
|
||||
const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0;
|
||||
scm_builder.execute(sliced_symbol);
|
||||
idm_builder.execute(sliced_symbol);
|
||||
}
|
||||
|
||||
void ERTProcessor::scm_handler(
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
const ERTPacketMessage message { ert::Packet::Type::SCM, packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void ERTProcessor::idm_handler(
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
const ERTPacketMessage message { ert::Packet::Type::IDM, packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
105
firmware/baseband/proc_ert.hpp
Normal file
105
firmware/baseband/proc_ert.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 __PROC_ERT_H__
|
||||
#define __PROC_ERT_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
|
||||
#include "channel_decimator.hpp"
|
||||
|
||||
#include "clock_recovery.hpp"
|
||||
#include "symbol_coding.hpp"
|
||||
#include "packet_builder.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <bitset>
|
||||
|
||||
// ''.join(['%d%d' % (c, 1-c) for c in map(int, bin(0x1f2a60)[2:].zfill(21))])
|
||||
constexpr uint64_t scm_preamble_and_sync_manchester { 0b101010101001011001100110010110100101010101 };
|
||||
constexpr size_t scm_preamble_and_sync_length { 42 - 10 };
|
||||
constexpr size_t scm_payload_length_max { 150 };
|
||||
|
||||
// ''.join(['%d%d' % (c, 1-c) for c in map(int, bin(0x555516a3)[2:].zfill(32))])
|
||||
constexpr uint64_t idm_preamble_and_sync_manchester { 0b0110011001100110011001100110011001010110011010011001100101011010 };
|
||||
constexpr size_t idm_preamble_and_sync_length { 64 - 16 };
|
||||
|
||||
constexpr size_t idm_payload_length_max { 1408 };
|
||||
|
||||
class ERTProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
private:
|
||||
const uint32_t baseband_sampling_rate = 4194304;
|
||||
const size_t decimation = 1;
|
||||
const float symbol_rate = 32768;
|
||||
|
||||
const uint32_t channel_sampling_rate = baseband_sampling_rate / decimation;
|
||||
const size_t samples_per_symbol = channel_sampling_rate / symbol_rate;
|
||||
const float clock_recovery_rate = symbol_rate * 2;
|
||||
|
||||
clock_recovery::ClockRecovery<clock_recovery::FixedErrorFilter> clock_recovery {
|
||||
clock_recovery_rate, symbol_rate, { 1.0f / 18.0f },
|
||||
[this](const float symbol) { this->consume_symbol(symbol); }
|
||||
};
|
||||
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> scm_builder {
|
||||
{ scm_preamble_and_sync_manchester, scm_preamble_and_sync_length, 1 },
|
||||
{ },
|
||||
{ scm_payload_length_max },
|
||||
[this](const baseband::Packet& packet) {
|
||||
this->scm_handler(packet);
|
||||
}
|
||||
};
|
||||
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> idm_builder {
|
||||
{ idm_preamble_and_sync_manchester, idm_preamble_and_sync_length, 1 },
|
||||
{ },
|
||||
{ idm_payload_length_max },
|
||||
[this](const baseband::Packet& packet) {
|
||||
this->idm_handler(packet);
|
||||
}
|
||||
};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void scm_handler(const baseband::Packet& packet);
|
||||
void idm_handler(const baseband::Packet& packet);
|
||||
|
||||
float sum_half_period[2];
|
||||
float sum_period[3];
|
||||
float manchester[3];
|
||||
|
||||
const size_t average_window { 2048 };
|
||||
int32_t average_i { 0 };
|
||||
int32_t average_q { 0 };
|
||||
size_t average_count { 0 };
|
||||
float offset_i { 0.0f };
|
||||
float offset_q { 0.0f };
|
||||
|
||||
float abs(const complex8_t& v);
|
||||
};
|
||||
|
||||
#endif/*__PROC_ERT_H__*/
|
||||
@@ -21,54 +21,70 @@
|
||||
|
||||
#include "proc_nfm_audio.hpp"
|
||||
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "audio_output.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
void NarrowbandFMAudio::execute(buffer_c8_t buffer) {
|
||||
/* Called every 2048/3072000 second -- 1500Hz. */
|
||||
|
||||
auto decimator_out = decimator.execute(buffer);
|
||||
|
||||
const buffer_c16_t work_baseband_buffer {
|
||||
(complex16_t*)decimator_out.p,
|
||||
sizeof(*decimator_out.p) * decimator_out.count
|
||||
};
|
||||
|
||||
/* 96kHz complex<int16_t>[64]
|
||||
* -> FIR filter, <6kHz (0.063fs) pass, gain 1.0
|
||||
* -> 48kHz int16_t[32] */
|
||||
auto channel = channel_filter.execute(decimator_out, work_baseband_buffer);
|
||||
|
||||
// TODO: Feed channel_stats post-decimation data?
|
||||
feed_channel_stats(channel);
|
||||
feed_channel_spectrum(
|
||||
channel,
|
||||
decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized,
|
||||
decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized
|
||||
);
|
||||
|
||||
const buffer_s16_t work_audio_buffer {
|
||||
(int16_t*)decimator_out.p,
|
||||
sizeof(*decimator_out.p) * decimator_out.count
|
||||
};
|
||||
|
||||
/* 48kHz complex<int16_t>[32]
|
||||
* -> FM demodulation
|
||||
* -> 48kHz int16_t[32] */
|
||||
auto audio = demod.execute(channel, work_audio_buffer);
|
||||
|
||||
static uint64_t audio_present_history = 0;
|
||||
const auto audio_present_now = squelch.execute(audio);
|
||||
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);
|
||||
const bool audio_present = (audio_present_history != 0);
|
||||
|
||||
if( !audio_present ) {
|
||||
// Zero audio buffer.
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio.p[i] = 0;
|
||||
}
|
||||
void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
|
||||
if( !configured ) {
|
||||
return;
|
||||
}
|
||||
|
||||
audio_hpf.execute_in_place(audio);
|
||||
fill_audio_buffer(audio);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
audio_output.write(audio);
|
||||
}
|
||||
|
||||
void NarrowbandFMAudio::on_message(const Message* const message) {
|
||||
switch(message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
channel_spectrum.on_message(message);
|
||||
break;
|
||||
|
||||
case Message::ID::NBFMConfigure:
|
||||
configure(*reinterpret_cast<const NBFMConfigureMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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_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 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 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);
|
||||
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);
|
||||
|
||||
configured = true;
|
||||
}
|
||||
|
||||
@@ -24,31 +24,44 @@
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
|
||||
#include "channel_decimator.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "dsp_fir_taps.hpp"
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "dsp_squelch.hpp"
|
||||
|
||||
#include "audio_output.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
class NarrowbandFMAudio : public BasebandProcessor {
|
||||
public:
|
||||
NarrowbandFMAudio() {
|
||||
decimator.set_decimation_factor(ChannelDecimator::DecimationFactor::By32);
|
||||
channel_filter.configure(channel_filter_taps.taps, 2);
|
||||
}
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void execute(buffer_c8_t buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
ChannelDecimator decimator;
|
||||
const fir_taps_real<64>& channel_filter_taps = taps_64_lp_042_078_tfilter;
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter;
|
||||
dsp::demodulate::FM demod { 48000, 7500 };
|
||||
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)
|
||||
};
|
||||
|
||||
IIRBiquadFilter audio_hpf { audio_hpf_config };
|
||||
FMSquelch squelch;
|
||||
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;
|
||||
|
||||
dsp::demodulate::FM demod;
|
||||
|
||||
AudioOutput audio_output;
|
||||
|
||||
SpectrumCollector channel_spectrum;
|
||||
|
||||
bool configured { false };
|
||||
void configure(const NBFMConfigureMessage& message);
|
||||
};
|
||||
|
||||
#endif/*__PROC_NFM_AUDIO_H__*/
|
||||
|
||||
@@ -23,36 +23,49 @@
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "i2s.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
void TPMSProcessor::execute(buffer_c8_t buffer) {
|
||||
// IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400
|
||||
static constexpr fir_taps_real<24> taps_200k_decim_0 = {
|
||||
.pass_frequency_normalized = 100000.0f / 2457600.0f,
|
||||
.stop_frequency_normalized = 407200.0f / 2457600.0f,
|
||||
.taps = { {
|
||||
90, 94, 4, -240, -570, -776, -563, 309,
|
||||
1861, 3808, 5618, 6710, 6710, 5618, 3808, 1861,
|
||||
309, -563, -776, -570, -240, 4, 94, 90,
|
||||
} },
|
||||
};
|
||||
|
||||
// IFIR prototype filter: fs=614400, pass=100000, stop=207200, decim=2, fout=307200
|
||||
static constexpr fir_taps_real<16> taps_200k_decim_1 = {
|
||||
.pass_frequency_normalized = 100000.0f / 614400.0f,
|
||||
.stop_frequency_normalized = 207200.0f / 614400.0f,
|
||||
.taps = { {
|
||||
-132, -256, 545, 834, -1507, -2401, 4666, 14583,
|
||||
14583, 4666, -2401, -1507, 834, 545, -256, -132,
|
||||
} },
|
||||
};
|
||||
|
||||
TPMSProcessor::TPMSProcessor() {
|
||||
decim_0.configure(taps_200k_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_200k_decim_1.taps, 131072);
|
||||
}
|
||||
|
||||
void TPMSProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
|
||||
auto decimator_out = decimator.execute(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 decimator_out = decim_1_out;
|
||||
|
||||
/* 76.8kHz, 64 samples */
|
||||
/* 307.2kHz, 256 samples */
|
||||
feed_channel_stats(decimator_out);
|
||||
/* No spectrum display while FSK decoding.
|
||||
feed_channel_spectrum(
|
||||
channel,
|
||||
decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized,
|
||||
decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized
|
||||
);
|
||||
*/
|
||||
|
||||
for(size_t i=0; i<decimator_out.count; i++) {
|
||||
// TODO: No idea why implicit cast int16_t->float is not allowed.
|
||||
const std::complex<float> sample {
|
||||
static_cast<float>(decimator_out.p[i].real()),
|
||||
static_cast<float>(decimator_out.p[i].imag())
|
||||
};
|
||||
if( mf.execute_once(sample) ) {
|
||||
if( mf.execute_once(decimator_out.p[i]) ) {
|
||||
clock_recovery(mf.get_output());
|
||||
}
|
||||
}
|
||||
|
||||
i2s::i2s0::tx_mute();
|
||||
}
|
||||
|
||||
void TPMSProcessor::consume_symbol(
|
||||
@@ -63,11 +76,8 @@ void TPMSProcessor::consume_symbol(
|
||||
}
|
||||
|
||||
void TPMSProcessor::payload_handler(
|
||||
const std::bitset<1024>& payload,
|
||||
const size_t bits_received
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
TPMSPacketMessage message;
|
||||
message.packet.payload = payload;
|
||||
message.packet.bits_received = bits_received;
|
||||
const TPMSPacketMessage message { packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "clock_recovery.hpp"
|
||||
#include "symbol_coding.hpp"
|
||||
#include "packet_builder.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
@@ -37,39 +38,37 @@
|
||||
#include <cstddef>
|
||||
#include <bitset>
|
||||
|
||||
struct NeverMatch {
|
||||
bool operator()(const BitHistory&, const size_t) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct FixedLength {
|
||||
bool operator()(const BitHistory&, const size_t symbols_received) const {
|
||||
return symbols_received >= length;
|
||||
}
|
||||
|
||||
const size_t length;
|
||||
};
|
||||
|
||||
// Translate+rectangular filter
|
||||
// sample=153.6k, deviation=38400, symbol=19200
|
||||
// Length: 8 taps, 1 symbols, 2 cycles of sinusoid
|
||||
constexpr std::array<std::complex<float>, 8> rect_taps_153k6_1t_p { {
|
||||
{ 1.2500000000e-01f, 0.0000000000e+00f }, { 7.6540424947e-18f, 1.2500000000e-01f },
|
||||
{ -1.2500000000e-01f, 1.5308084989e-17f }, { -2.2962127484e-17f, -1.2500000000e-01f },
|
||||
{ 1.2500000000e-01f, -3.0616169979e-17f }, { 3.8270212473e-17f, 1.2500000000e-01f },
|
||||
{ -1.2500000000e-01f, 4.5924254968e-17f }, { -5.3578297463e-17f, -1.2500000000e-01f },
|
||||
// sample=307.2k, deviation=38400, symbol=19200
|
||||
// Length: 16 taps, 1 symbols, 2 cycles of sinusoid
|
||||
constexpr std::array<std::complex<float>, 16> rect_taps_307k2_1t_p { {
|
||||
{ 6.2500000000e-02f, 0.0000000000e+00f }, { 4.4194173824e-02f, 4.4194173824e-02f },
|
||||
{ 0.0000000000e+00f, 6.2500000000e-02f }, { -4.4194173824e-02f, 4.4194173824e-02f },
|
||||
{ -6.2500000000e-02f, 0.0000000000e+00f }, { -4.4194173824e-02f, -4.4194173824e-02f },
|
||||
{ 0.0000000000e+00f, -6.2500000000e-02f }, { 4.4194173824e-02f, -4.4194173824e-02f },
|
||||
{ 6.2500000000e-02f, 0.0000000000e+00f }, { 4.4194173824e-02f, 4.4194173824e-02f },
|
||||
{ 0.0000000000e+00f, 6.2500000000e-02f }, { -4.4194173824e-02f, 4.4194173824e-02f },
|
||||
{ -6.2500000000e-02f, 0.0000000000e+00f }, { -4.4194173824e-02f, -4.4194173824e-02f },
|
||||
{ 0.0000000000e+00f, -6.2500000000e-02f }, { 4.4194173824e-02f, -4.4194173824e-02f },
|
||||
} };
|
||||
|
||||
class TPMSProcessor : public BasebandProcessor {
|
||||
public:
|
||||
using payload_t = std::bitset<1024>;
|
||||
TPMSProcessor();
|
||||
|
||||
void execute(buffer_c8_t buffer) override;
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
private:
|
||||
ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By16 };
|
||||
dsp::matched_filter::MatchedFilter mf { rect_taps_153k6_1t_p, 4 };
|
||||
std::array<complex16_t, 512> dst;
|
||||
const buffer_c16_t dst_buffer {
|
||||
dst.data(),
|
||||
dst.size()
|
||||
};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0;
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1;
|
||||
|
||||
dsp::matched_filter::MatchedFilter mf { rect_taps_307k2_1t_p, 8 };
|
||||
|
||||
clock_recovery::ClockRecovery<clock_recovery::FixedErrorFilter> clock_recovery {
|
||||
38400, 19200, { 0.0555f },
|
||||
@@ -79,13 +78,13 @@ private:
|
||||
{ 0b010101010101010101010101010110, 30, 1 },
|
||||
{ },
|
||||
{ 256 },
|
||||
[this](const payload_t& payload, const size_t bits_received) {
|
||||
this->payload_handler(payload, bits_received);
|
||||
[this](const baseband::Packet& packet) {
|
||||
this->payload_handler(packet);
|
||||
}
|
||||
};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const payload_t& payload, const size_t bits_received);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
};
|
||||
|
||||
#endif/*__PROC_TPMS_H__*/
|
||||
|
||||
@@ -21,45 +21,46 @@
|
||||
|
||||
#include "proc_wfm_audio.hpp"
|
||||
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "audio_output.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
void WidebandFMAudio::execute(buffer_c8_t buffer) {
|
||||
auto decimator_out = decimator.execute(buffer);
|
||||
void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
|
||||
if( !configured ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer_s16_t work_audio_buffer {
|
||||
(int16_t*)decimator_out.p,
|
||||
sizeof(*decimator_out.p) * decimator_out.count
|
||||
};
|
||||
|
||||
auto channel = decimator_out;
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
const auto channel = decim_1.execute(decim_0_out, dst_buffer);
|
||||
|
||||
// TODO: Feed channel_stats post-decimation data?
|
||||
feed_channel_stats(channel);
|
||||
//feed_channel_spectrum(channel);
|
||||
|
||||
/* 768kHz complex<int16_t>[512]
|
||||
spectrum_samples += channel.count;
|
||||
if( spectrum_samples >= spectrum_interval_samples ) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.feed(channel, channel_filter_pass_f, channel_filter_stop_f);
|
||||
}
|
||||
|
||||
/* 384kHz complex<int16_t>[256]
|
||||
* -> FM demodulation
|
||||
* -> 768kHz int16_t[512] */
|
||||
* -> 384kHz int16_t[256] */
|
||||
/* TODO: To improve adjacent channel rejection, implement complex channel filter:
|
||||
* pass < +/- 100kHz, stop > +/- 200kHz
|
||||
*/
|
||||
|
||||
auto audio_oversampled = demod.execute(decimator_out, work_audio_buffer);
|
||||
|
||||
/* 768kHz int16_t[512]
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 384kHz int16_t[256] */
|
||||
auto audio_8fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer);
|
||||
auto audio_oversampled = demod.execute(channel, work_audio_buffer);
|
||||
|
||||
/* 384kHz int16_t[256]
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 192kHz int16_t[128] */
|
||||
auto audio_4fs = audio_dec_2.execute(audio_8fs, work_audio_buffer);
|
||||
auto audio_4fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer);
|
||||
|
||||
/* 192kHz int16_t[128]
|
||||
* -> 4th order CIC decimation by 2, gain of 1
|
||||
* -> 96kHz int16_t[64] */
|
||||
auto audio_2fs = audio_dec_3.execute(audio_4fs, work_audio_buffer);
|
||||
auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer);
|
||||
|
||||
/* 96kHz int16_t[64]
|
||||
* -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1
|
||||
@@ -67,6 +68,51 @@ void WidebandFMAudio::execute(buffer_c8_t buffer) {
|
||||
auto audio = audio_filter.execute(audio_2fs, work_audio_buffer);
|
||||
|
||||
/* -> 48kHz int16_t[32] */
|
||||
audio_hpf.execute_in_place(audio);
|
||||
fill_audio_buffer(audio);
|
||||
audio_output.write(audio);
|
||||
}
|
||||
|
||||
void WidebandFMAudio::on_message(const Message* const message) {
|
||||
switch(message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
channel_spectrum.on_message(message);
|
||||
break;
|
||||
|
||||
case Message::ID::WFMConfigure:
|
||||
configure(*reinterpret_cast<const WFMConfigureMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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_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 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;
|
||||
|
||||
decim_0.configure(message.decim_0_filter.taps, 33554432);
|
||||
decim_1.configure(message.decim_1_filter.taps, 131072);
|
||||
channel_filter_pass_f = message.decim_1_filter.pass_frequency_normalized * decim_1_input_fs;
|
||||
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);
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
|
||||
configured = true;
|
||||
}
|
||||
|
||||
@@ -24,32 +24,47 @@
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
|
||||
#include "channel_decimator.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "dsp_fir_taps.hpp"
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_iir_config.hpp"
|
||||
|
||||
#include "audio_output.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
class WidebandFMAudio : public BasebandProcessor {
|
||||
public:
|
||||
WidebandFMAudio() {
|
||||
decimator.set_decimation_factor(ChannelDecimator::DecimationFactor::By4);
|
||||
}
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void execute(buffer_c8_t buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
ChannelDecimator decimator;
|
||||
std::array<complex16_t, 512> dst;
|
||||
const buffer_c16_t dst_buffer {
|
||||
dst.data(),
|
||||
dst.size()
|
||||
};
|
||||
const buffer_s16_t work_audio_buffer {
|
||||
(int16_t*)dst.data(),
|
||||
sizeof(dst) / sizeof(int16_t)
|
||||
};
|
||||
|
||||
dsp::demodulate::FM demod { 768000, 75000 };
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0;
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1;
|
||||
uint32_t channel_filter_pass_f = 0;
|
||||
uint32_t channel_filter_stop_f = 0;
|
||||
|
||||
dsp::demodulate::FM demod;
|
||||
dsp::decimate::DecimateBy2CIC4Real audio_dec_1;
|
||||
dsp::decimate::DecimateBy2CIC4Real audio_dec_2;
|
||||
dsp::decimate::DecimateBy2CIC4Real audio_dec_3;
|
||||
const fir_taps_real<64>& audio_filter_taps = taps_64_lp_156_198;
|
||||
dsp::decimate::FIR64AndDecimateBy2Real audio_filter { audio_filter_taps.taps };
|
||||
dsp::decimate::FIR64AndDecimateBy2Real audio_filter;
|
||||
|
||||
IIRBiquadFilter audio_hpf { audio_hpf_config };
|
||||
AudioOutput audio_output;
|
||||
|
||||
SpectrumCollector channel_spectrum;
|
||||
size_t spectrum_interval_samples = 0;
|
||||
size_t spectrum_samples = 0;
|
||||
|
||||
bool configured { false };
|
||||
void configure(const WFMConfigureMessage& message);
|
||||
};
|
||||
|
||||
#endif/*__PROC_WFM_AUDIO_H__*/
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "i2s.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "dsp_fft.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -33,37 +30,45 @@ using namespace lpc43xx;
|
||||
|
||||
#include <array>
|
||||
|
||||
void WidebandSpectrum::execute(buffer_c8_t buffer) {
|
||||
void WidebandSpectrum::execute(const buffer_c8_t& buffer) {
|
||||
// 2048 complex8_t samples per buffer.
|
||||
// 102.4us per buffer. 20480 instruction cycles per buffer.
|
||||
|
||||
static int phase = 0;
|
||||
|
||||
if( phase == 0 ) {
|
||||
std::fill(spectrum.begin(), spectrum.end(), 0);
|
||||
}
|
||||
|
||||
if( (phase & 7) == 0 ) {
|
||||
for(size_t i=0; i<spectrum.size(); i++) {
|
||||
// TODO: Removed window-presum windowing, due to lack of available code RAM.
|
||||
// TODO: Apply window to improve spectrum bin sidelobes.
|
||||
for(size_t i=0; i<channel_spectrum.size(); i++) {
|
||||
spectrum[i] += std::complex<float> { buffer.p[i].real(), buffer.p[i].imag() };
|
||||
}
|
||||
spectrum[i] += buffer.p[i + 0];
|
||||
spectrum[i] += buffer.p[i + 1024];
|
||||
}
|
||||
|
||||
if( phase == 23 ) {
|
||||
if( channel_spectrum_request_update == false ) {
|
||||
fft_swap(spectrum, channel_spectrum);
|
||||
channel_spectrum_sampling_rate = buffer.sampling_rate;
|
||||
channel_filter_pass_frequency = 0;
|
||||
channel_filter_stop_frequency = 0;
|
||||
channel_spectrum_request_update = true;
|
||||
events_flag(EVT_MASK_SPECTRUM);
|
||||
phase = 0;
|
||||
}
|
||||
if( phase == 127 ) {
|
||||
const buffer_c16_t buffer_c16 {
|
||||
spectrum.data(),
|
||||
spectrum.size(),
|
||||
buffer.sampling_rate
|
||||
};
|
||||
channel_spectrum.feed(
|
||||
buffer_c16,
|
||||
0, 0
|
||||
);
|
||||
phase = 0;
|
||||
} else {
|
||||
phase++;
|
||||
}
|
||||
|
||||
i2s::i2s0::tx_mute();
|
||||
}
|
||||
|
||||
void WidebandSpectrum::on_message(const Message* const message) {
|
||||
switch(message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
channel_spectrum.on_message(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#define __PROC_WIDEBAND_SPECTRUM_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
@@ -30,12 +33,16 @@
|
||||
|
||||
class WidebandSpectrum : public BasebandProcessor {
|
||||
public:
|
||||
void execute(buffer_c8_t buffer) override;
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
size_t sample_count = 0;
|
||||
SpectrumCollector channel_spectrum;
|
||||
|
||||
std::array<std::complex<float>, 256> spectrum;
|
||||
std::array<complex16_t, 256> spectrum;
|
||||
|
||||
size_t phase = 0;
|
||||
};
|
||||
|
||||
#endif/*__PROC_WIDEBAND_SPECTRUM_H__*/
|
||||
|
||||
@@ -157,7 +157,7 @@ bool is_enabled() {
|
||||
}
|
||||
|
||||
void disable() {
|
||||
gpdma_channel.disable_force();
|
||||
gpdma_channel.disable();
|
||||
}
|
||||
|
||||
rf::rssi::buffer_t wait_for_buffer() {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
class RSSIStatisticsCollector {
|
||||
public:
|
||||
template<typename Callback>
|
||||
void process(rf::rssi::buffer_t buffer, Callback callback) {
|
||||
void process(const rf::rssi::buffer_t& buffer, Callback callback) {
|
||||
auto p = buffer.p;
|
||||
if( p == nullptr ) {
|
||||
return;
|
||||
|
||||
63
firmware/baseband/rssi_thread.cpp
Normal file
63
firmware/baseband/rssi_thread.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 "rssi_thread.hpp"
|
||||
|
||||
#include "rssi.hpp"
|
||||
#include "rssi_dma.hpp"
|
||||
#include "rssi_stats_collector.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
WORKING_AREA(rssi_thread_wa, 128);
|
||||
|
||||
Thread* RSSIThread::start(const tprio_t priority) {
|
||||
return chThdCreateStatic(rssi_thread_wa, sizeof(rssi_thread_wa),
|
||||
priority, ThreadBase::fn,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
void RSSIThread::run() {
|
||||
rf::rssi::init();
|
||||
rf::rssi::dma::allocate(4, 400);
|
||||
|
||||
RSSIStatisticsCollector stats;
|
||||
|
||||
while(true) {
|
||||
// TODO: Place correct sampling rate into buffer returned here:
|
||||
const auto buffer_tmp = rf::rssi::dma::wait_for_buffer();
|
||||
const rf::rssi::buffer_t buffer {
|
||||
buffer_tmp.p, buffer_tmp.count, sampling_rate
|
||||
};
|
||||
|
||||
stats.process(
|
||||
buffer,
|
||||
[](const RSSIStatistics& statistics) {
|
||||
const RSSIStatisticsMessage message { statistics };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
rf::rssi::dma::free();
|
||||
}
|
||||
@@ -19,10 +19,28 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __IRQ_IPC_M4_H__
|
||||
#define __IRQ_IPC_M4_H__
|
||||
#ifndef __RSSI_THREAD_H__
|
||||
#define __RSSI_THREAD_H__
|
||||
|
||||
void m0apptxevent_interrupt_enable();
|
||||
void m0apptxevent_interrupt_disable();
|
||||
#include "thread_base.hpp"
|
||||
|
||||
#endif/*__IRQ_IPC_M4_H__*/
|
||||
#include <ch.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class RSSIThread : public ThreadBase {
|
||||
public:
|
||||
RSSIThread(
|
||||
) : ThreadBase { "rssi" }
|
||||
{
|
||||
}
|
||||
|
||||
Thread* start(const tprio_t priority);
|
||||
|
||||
private:
|
||||
void run() override;
|
||||
|
||||
const uint32_t sampling_rate { 400000 };
|
||||
};
|
||||
|
||||
#endif/*__RSSI_THREAD_H__*/
|
||||
130
firmware/baseband/spectrum_collector.cpp
Normal file
130
firmware/baseband/spectrum_collector.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include "dsp_fft.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
void SpectrumCollector::on_message(const Message* const message) {
|
||||
switch(message->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
update();
|
||||
break;
|
||||
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
set_state(*reinterpret_cast<const SpectrumStreamingConfigMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumCollector::set_state(const SpectrumStreamingConfigMessage& message) {
|
||||
if( message.mode == SpectrumStreamingConfigMessage::Mode::Running ) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumCollector::start() {
|
||||
streaming = true;
|
||||
ChannelSpectrumConfigMessage message { &fifo };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void SpectrumCollector::stop() {
|
||||
streaming = false;
|
||||
fifo.reset_in();
|
||||
}
|
||||
|
||||
void SpectrumCollector::set_decimation_factor(
|
||||
const size_t decimation_factor
|
||||
) {
|
||||
channel_spectrum_decimator.set_factor(decimation_factor);
|
||||
}
|
||||
|
||||
/* TODO: Refactor to register task with idle thread?
|
||||
* It's sad that the idle thread has to call all the way back here just to
|
||||
* perform the deferred task on the buffer of data we prepared.
|
||||
*/
|
||||
|
||||
void SpectrumCollector::feed(
|
||||
const buffer_c16_t& channel,
|
||||
const uint32_t filter_pass_frequency,
|
||||
const uint32_t filter_stop_frequency
|
||||
) {
|
||||
// Called from baseband processing thread.
|
||||
channel_filter_pass_frequency = filter_pass_frequency;
|
||||
channel_filter_stop_frequency = filter_stop_frequency;
|
||||
|
||||
channel_spectrum_decimator.feed(
|
||||
channel,
|
||||
[this](const buffer_c16_t& data) {
|
||||
this->post_message(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SpectrumCollector::post_message(const buffer_c16_t& data) {
|
||||
// Called from baseband processing thread.
|
||||
if( streaming && !channel_spectrum_request_update ) {
|
||||
fft_swap(data, channel_spectrum);
|
||||
channel_spectrum_sampling_rate = data.sampling_rate;
|
||||
channel_spectrum_request_update = true;
|
||||
EventDispatcher::events_flag(EVT_MASK_SPECTRUM);
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumCollector::update() {
|
||||
// Called from idle thread (after EVT_MASK_SPECTRUM is flagged)
|
||||
if( streaming && channel_spectrum_request_update ) {
|
||||
/* Decimated buffer is full. Compute spectrum. */
|
||||
fft_c_preswapped(channel_spectrum);
|
||||
|
||||
ChannelSpectrum spectrum;
|
||||
spectrum.sampling_rate = channel_spectrum_sampling_rate;
|
||||
spectrum.channel_filter_pass_frequency = channel_filter_pass_frequency;
|
||||
spectrum.channel_filter_stop_frequency = channel_filter_stop_frequency;
|
||||
for(size_t i=0; i<spectrum.db.size(); i++) {
|
||||
// Three point Hamming window.
|
||||
const auto corrected_sample = channel_spectrum[i] * 0.54f
|
||||
+ (channel_spectrum[(i-1) & 0xff] + channel_spectrum[(i+1) & 0xff]) * -0.23f;
|
||||
const auto mag2 = magnitude_squared(corrected_sample);
|
||||
const float db = complex16_mag_squared_to_dbv_norm(mag2);
|
||||
constexpr float mag_scale = 5.0f;
|
||||
const unsigned int v = (db * mag_scale) + 255.0f;
|
||||
spectrum.db[i] = std::max(0U, std::min(255U, v));
|
||||
}
|
||||
fifo.in(spectrum);
|
||||
}
|
||||
|
||||
channel_spectrum_request_update = false;
|
||||
}
|
||||
72
firmware/baseband/spectrum_collector.hpp
Normal file
72
firmware/baseband/spectrum_collector.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 __SPECTRUM_COLLECTOR_H__
|
||||
#define __SPECTRUM_COLLECTOR_H__
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
#include "block_decimator.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
class SpectrumCollector {
|
||||
public:
|
||||
constexpr SpectrumCollector(
|
||||
) : channel_spectrum_decimator { 1 }
|
||||
{
|
||||
}
|
||||
|
||||
void on_message(const Message* const message);
|
||||
|
||||
void set_decimation_factor(const size_t decimation_factor);
|
||||
|
||||
void feed(
|
||||
const buffer_c16_t& channel,
|
||||
const uint32_t filter_pass_frequency,
|
||||
const uint32_t filter_stop_frequency
|
||||
);
|
||||
|
||||
private:
|
||||
BlockDecimator<256> channel_spectrum_decimator;
|
||||
ChannelSpectrumFIFO fifo;
|
||||
|
||||
volatile bool channel_spectrum_request_update { false };
|
||||
bool streaming { false };
|
||||
std::array<std::complex<float>, 256> channel_spectrum;
|
||||
uint32_t channel_spectrum_sampling_rate { 0 };
|
||||
uint32_t channel_filter_pass_frequency { 0 };
|
||||
uint32_t channel_filter_stop_frequency { 0 };
|
||||
|
||||
void post_message(const buffer_c16_t& data);
|
||||
|
||||
void set_state(const SpectrumStreamingConfigMessage& message);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif/*__SPECTRUM_COLLECTOR_H__*/
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -19,4 +19,32 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
#ifndef __THREAD_BASE_H__
|
||||
#define __THREAD_BASE_H__
|
||||
|
||||
#include <ch.h>
|
||||
|
||||
class ThreadBase {
|
||||
public:
|
||||
constexpr ThreadBase(
|
||||
const char* const name
|
||||
) : name { name }
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
static msg_t fn(void* arg) {
|
||||
auto obj = static_cast<ThreadBase*>(arg);
|
||||
chRegSetThreadName(obj->name);
|
||||
obj->run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* const name;
|
||||
|
||||
virtual void run() = 0;
|
||||
};
|
||||
|
||||
#endif/*__THREAD_BASE_H__*/
|
||||
@@ -122,7 +122,7 @@ bool is_enabled() {
|
||||
}
|
||||
|
||||
void disable() {
|
||||
gpdma_channel.disable_force();
|
||||
gpdma_channel.disable();
|
||||
}
|
||||
|
||||
} /* namespace dma */
|
||||
|
||||
Reference in New Issue
Block a user