mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-08-25 05:47:44 +00:00
Merge remote-tracking branch 'upstream/master'
Conflicts: firmware/application/Makefile firmware/application/core_control.cpp firmware/application/touch.cpp firmware/application/ui_debug.cpp firmware/application/ui_debug.hpp firmware/application/ui_navigation.cpp firmware/baseband/baseband_thread.cpp
This commit is contained in:
@@ -127,6 +127,7 @@ CPPSRC = main.cpp \
|
||||
message_queue.cpp \
|
||||
event.cpp \
|
||||
event_m4.cpp \
|
||||
thread_wait.cpp \
|
||||
gpdma.cpp \
|
||||
baseband_dma.cpp \
|
||||
baseband_sgpio.cpp \
|
||||
@@ -145,6 +146,7 @@ CPPSRC = main.cpp \
|
||||
proc_wideband_spectrum.cpp \
|
||||
proc_tpms.cpp \
|
||||
proc_ert.cpp \
|
||||
proc_capture.cpp \
|
||||
dsp_squelch.cpp \
|
||||
clock_recovery.cpp \
|
||||
packet_builder.cpp \
|
||||
@@ -155,7 +157,7 @@ CPPSRC = main.cpp \
|
||||
rssi.cpp \
|
||||
rssi_dma.cpp \
|
||||
rssi_thread.cpp \
|
||||
audio.cpp \
|
||||
audio_compressor.cpp \
|
||||
audio_output.cpp \
|
||||
audio_dma.cpp \
|
||||
audio_stats_collector.cpp \
|
||||
|
52
firmware/baseband/audio_compressor.cpp
Normal file
52
firmware/baseband/audio_compressor.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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_compressor.hpp"
|
||||
|
||||
float GainComputer::operator()(const float x) const {
|
||||
const auto abs_x = std::abs(x);
|
||||
const auto db = (abs_x < lin_floor) ? db_floor : log2_db_k * fast_log2(abs_x);
|
||||
const auto overshoot_db = db - threshold_db;
|
||||
if( knee_width_db > 0.0f ) {
|
||||
const auto w2 = knee_width_db / 2.0f;
|
||||
const auto a = w2 / (knee_width_db * knee_width_db);
|
||||
const auto in_transition = (overshoot_db > -w2) && (overshoot_db < w2);
|
||||
const auto rectified_overshoot = in_transition ? (a * std::pow(overshoot_db + w2, 2.0f)) : std::max(overshoot_db, 0.0f);
|
||||
return rectified_overshoot * slope;
|
||||
} else {
|
||||
const auto rectified_overshoot = std::max(overshoot_db, 0.0f);
|
||||
return rectified_overshoot * slope;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedForwardCompressor::execute_in_place(const buffer_f32_t& buffer) {
|
||||
constexpr float makeup_gain = std::pow(10.0f, (threshold - (threshold / ratio)) / -20.0f);
|
||||
for(size_t i=0; i<buffer.count; i++) {
|
||||
buffer.p[i] = execute_once(buffer.p[i]) * makeup_gain;
|
||||
}
|
||||
}
|
||||
|
||||
float FeedForwardCompressor::execute_once(const float x) {
|
||||
const auto gain_db = gain_computer(x);
|
||||
const auto peak_db = -peak_detector(-gain_db);
|
||||
const auto gain = fast_pow2(peak_db * (3.321928094887362f / 20.0f));
|
||||
return x * gain;
|
||||
}
|
102
firmware/baseband/audio_compressor.hpp
Normal file
102
firmware/baseband/audio_compressor.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 __AUDIO_COMPRESSOR_H__
|
||||
#define __AUDIO_COMPRESSOR_H__
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
/* Code based on article in Journal of the Audio Engineering Society
|
||||
* Vol. 60, No. 6, 2012 June, by Dimitrios Giannoulis, Michael Massberg,
|
||||
* Joshua D. Reiss "Digital Dynamic Range Compressor Design – A Tutorial
|
||||
* and Analysis"
|
||||
*/
|
||||
|
||||
class GainComputer {
|
||||
public:
|
||||
constexpr GainComputer(
|
||||
float ratio,
|
||||
float threshold
|
||||
) : ratio { ratio },
|
||||
slope { 1.0f / ratio - 1.0f },
|
||||
threshold_db { threshold }
|
||||
{
|
||||
}
|
||||
|
||||
float operator()(const float x) const;
|
||||
|
||||
private:
|
||||
const float ratio;
|
||||
const float slope;
|
||||
const float threshold_db;
|
||||
|
||||
static constexpr float knee_width_db = 0.0f;
|
||||
|
||||
static constexpr float db_floor = -120.0f;
|
||||
static constexpr float lin_floor = std::pow(10.0f, db_floor / 20.0f);
|
||||
static constexpr float log2_db_k = 20.0f * std::log10(2.0f);
|
||||
};
|
||||
|
||||
class PeakDetectorBranchingSmooth {
|
||||
public:
|
||||
constexpr PeakDetectorBranchingSmooth(
|
||||
float att_a,
|
||||
float rel_a
|
||||
) : att_a { att_a },
|
||||
rel_a { rel_a }
|
||||
{
|
||||
}
|
||||
|
||||
float operator()(const float db) {
|
||||
const auto a = (db > state) ? att_a : rel_a;
|
||||
state = db + a * (state - db);
|
||||
return state;
|
||||
}
|
||||
|
||||
private:
|
||||
float state { 0.0f };
|
||||
const float att_a;
|
||||
const float rel_a;
|
||||
};
|
||||
|
||||
class FeedForwardCompressor {
|
||||
public:
|
||||
void execute_in_place(const buffer_f32_t& buffer);
|
||||
|
||||
private:
|
||||
static constexpr float fs = 12000.0f;
|
||||
static constexpr float ratio = 10.0f;
|
||||
static constexpr float threshold = -30.0f;
|
||||
|
||||
GainComputer gain_computer { ratio, threshold };
|
||||
PeakDetectorBranchingSmooth peak_detector { tau_alpha(0.010f, fs), tau_alpha(0.300f, fs) };
|
||||
|
||||
float execute_once(const float x);
|
||||
|
||||
static constexpr float tau_alpha(const float tau, const float fs) {
|
||||
return std::exp(-1.0f / (tau * fs));
|
||||
}
|
||||
};
|
||||
|
||||
#endif/*__AUDIO_COMPRESSOR_H__*/
|
@@ -24,9 +24,22 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "buffer.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
struct sample_t {
|
||||
union {
|
||||
struct {
|
||||
int16_t left;
|
||||
int16_t right;
|
||||
};
|
||||
uint32_t raw;
|
||||
};
|
||||
};
|
||||
|
||||
using buffer_t = buffer_t<sample_t>;
|
||||
|
||||
namespace dma {
|
||||
|
||||
void init();
|
||||
|
@@ -46,7 +46,7 @@ void AudioOutput::write(
|
||||
) {
|
||||
std::array<float, 32> audio_f;
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio_f[i] = audio.p[i];
|
||||
audio_f[i] = audio.p[i] * ki;
|
||||
}
|
||||
write(buffer_f32_t {
|
||||
audio_f.data(),
|
||||
@@ -77,24 +77,27 @@ void AudioOutput::on_block(
|
||||
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();
|
||||
if( !audio_present ) {
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio.p[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fill_audio_buffer(audio);
|
||||
fill_audio_buffer(audio, audio_present);
|
||||
}
|
||||
|
||||
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio) {
|
||||
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo) {
|
||||
std::array<int16_t, 32> audio_int;
|
||||
|
||||
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_int = audio.p[i] * k;
|
||||
const int32_t sample_saturated = __SSAT(sample_int, 16);
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = sample_saturated;
|
||||
audio_int[i] = sample_saturated;
|
||||
}
|
||||
if( send_to_fifo ) {
|
||||
stream.write(audio_int.data(), audio_buffer.count * sizeof(audio_int[0]));
|
||||
}
|
||||
|
||||
feed_audio_stats(audio);
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_squelch.hpp"
|
||||
|
||||
#include "stream_input.hpp"
|
||||
#include "block_decimator.hpp"
|
||||
#include "audio_stats_collector.hpp"
|
||||
|
||||
@@ -44,18 +45,23 @@ public:
|
||||
void write(const buffer_f32_t& audio);
|
||||
|
||||
private:
|
||||
static constexpr float k = 32768.0f;
|
||||
static constexpr float ki = 1.0f / k;
|
||||
|
||||
BlockDecimator<float, 32> block_buffer { 1 };
|
||||
|
||||
IIRBiquadFilter hpf;
|
||||
IIRBiquadFilter deemph;
|
||||
FMSquelch squelch;
|
||||
|
||||
StreamInput stream { 14 };
|
||||
|
||||
AudioStatsCollector audio_stats;
|
||||
|
||||
uint64_t audio_present_history = 0;
|
||||
|
||||
void on_block(const buffer_f32_t& audio);
|
||||
void fill_audio_buffer(const buffer_f32_t& audio);
|
||||
void fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo);
|
||||
void feed_audio_stats(const buffer_f32_t& audio);
|
||||
};
|
||||
|
||||
|
@@ -42,8 +42,8 @@ bool AudioStatsCollector::update_stats(const size_t sample_count, const size_t s
|
||||
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.rms_db = mag2_to_dbv_norm(squared_sum / count);
|
||||
statistics.max_db = mag2_to_dbv_norm(max_squared);
|
||||
statistics.count = count;
|
||||
|
||||
squared_sum = 0;
|
||||
|
@@ -32,6 +32,8 @@ using namespace lpc43xx;
|
||||
|
||||
#include "portapack_dma.hpp"
|
||||
|
||||
#include "thread_wait.hpp"
|
||||
|
||||
namespace baseband {
|
||||
namespace dma {
|
||||
|
||||
@@ -99,21 +101,19 @@ 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 Semaphore semaphore;
|
||||
|
||||
static volatile const gpdma::channel::LLI* next_lli = nullptr;
|
||||
static ThreadWait thread_wait;
|
||||
|
||||
static void transfer_complete() {
|
||||
next_lli = gpdma_channel_sgpio.next_lli();
|
||||
chSemSignalI(&semaphore);
|
||||
const auto next_lli_index = gpdma_channel_sgpio.next_lli() - &lli_loop[0];
|
||||
thread_wait.wake_from_interrupt(next_lli_index);
|
||||
}
|
||||
|
||||
static void dma_error() {
|
||||
thread_wait.wake_from_interrupt(-1);
|
||||
disable();
|
||||
}
|
||||
|
||||
void init() {
|
||||
chSemInit(&semaphore, 0);
|
||||
gpdma_channel_sgpio.set_handlers(transfer_complete, dma_error);
|
||||
|
||||
// LPC_GPDMA->SYNC |= (1 << gpdma_src_peripheral);
|
||||
@@ -138,9 +138,6 @@ void configure(
|
||||
void enable(const baseband::Direction direction) {
|
||||
const auto gpdma_config = config(direction);
|
||||
gpdma_channel_sgpio.configure(lli_loop[0], gpdma_config);
|
||||
|
||||
chSemReset(&semaphore, 0);
|
||||
|
||||
gpdma_channel_sgpio.enable();
|
||||
}
|
||||
|
||||
@@ -153,16 +150,11 @@ void disable() {
|
||||
}
|
||||
|
||||
baseband::buffer_t wait_for_rx_buffer() {
|
||||
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].destaddr), transfer_samples };
|
||||
} else {
|
||||
return { };
|
||||
}
|
||||
const auto next_index = thread_wait.sleep();
|
||||
|
||||
if( next_index >= 0 ) {
|
||||
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 { };
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@
|
||||
#include "proc_wideband_spectrum.hpp"
|
||||
#include "proc_tpms.hpp"
|
||||
#include "proc_ert.hpp"
|
||||
#include "proc_capture.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
@@ -85,7 +86,7 @@ void BasebandThread::run() {
|
||||
baseband_sgpio.init();
|
||||
baseband::dma::init();
|
||||
|
||||
const auto baseband_buffer = new std::array<baseband::sample_t, 8192>();
|
||||
const auto baseband_buffer = std::make_unique<std::array<baseband::sample_t, 8192>>();
|
||||
baseband::dma::configure(
|
||||
baseband_buffer->data(),
|
||||
direction()
|
||||
@@ -123,22 +124,6 @@ void BasebandThread::run() {
|
||||
delete baseband_buffer;
|
||||
}
|
||||
|
||||
char ram_loop[32];
|
||||
typedef int (*fn_ptr)(void);
|
||||
fn_ptr loop_ptr;
|
||||
|
||||
void ram_loop_fn(void) {
|
||||
while(1) {}
|
||||
}
|
||||
|
||||
void BasebandThread::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)();
|
||||
}
|
||||
|
||||
BasebandProcessor* BasebandThread::create_processor(const int32_t mode) {
|
||||
switch(mode) {
|
||||
case 0: return new NarrowbandAMAudio();
|
||||
@@ -148,7 +133,7 @@ BasebandProcessor* BasebandThread::create_processor(const int32_t mode) {
|
||||
case 4: return new WidebandSpectrum();
|
||||
case 5: return new TPMSProcessor();
|
||||
case 6: return new ERTProcessor();
|
||||
case 255: wait_for_switch();
|
||||
case 7: return new CaptureProcessor();
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
@@ -30,11 +30,6 @@
|
||||
|
||||
class BasebandThread : public ThreadBase {
|
||||
public:
|
||||
BasebandThread(
|
||||
) : ThreadBase { "baseband" }
|
||||
{
|
||||
}
|
||||
|
||||
Thread* start(const tprio_t priority);
|
||||
|
||||
void on_message(const Message* const message);
|
||||
@@ -49,9 +44,10 @@ public:
|
||||
|
||||
Thread* thread_main { nullptr };
|
||||
Thread* thread_rssi { nullptr };
|
||||
BasebandProcessor* baseband_processor { nullptr };
|
||||
|
||||
private:
|
||||
BasebandProcessor* baseband_processor { nullptr };
|
||||
|
||||
BasebandConfiguration baseband_configuration;
|
||||
|
||||
void run() override;
|
||||
|
@@ -49,7 +49,7 @@ public:
|
||||
|
||||
if( count >= samples_per_update ) {
|
||||
const float max_squared_f = max_squared;
|
||||
const int32_t max_db = complex16_mag_squared_to_dbv_norm(max_squared_f);
|
||||
const int32_t max_db = mag2_to_dbv_norm(max_squared_f * (1.0f / (32768.0f * 32768.0f)));
|
||||
callback({ max_db, count });
|
||||
|
||||
max_squared = 0;
|
||||
|
@@ -129,7 +129,7 @@
|
||||
* @note The default is @p TRUE.
|
||||
*/
|
||||
#if !defined(CH_USE_REGISTRY) || defined(__DOXYGEN__)
|
||||
#define CH_USE_REGISTRY TRUE
|
||||
#define CH_USE_REGISTRY FALSE
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@@ -116,19 +116,21 @@ private:
|
||||
template<typename ErrorFilter>
|
||||
class ClockRecovery {
|
||||
public:
|
||||
using SymbolHandler = std::function<void(const float)>;
|
||||
|
||||
ClockRecovery(
|
||||
const float sampling_rate,
|
||||
const float symbol_rate,
|
||||
ErrorFilter error_filter,
|
||||
std::function<void(const float)> symbol_handler
|
||||
) : symbol_handler { symbol_handler }
|
||||
SymbolHandler symbol_handler
|
||||
) : symbol_handler { std::move(symbol_handler) }
|
||||
{
|
||||
configure(sampling_rate, symbol_rate, error_filter);
|
||||
}
|
||||
|
||||
ClockRecovery(
|
||||
std::function<void(const float)> symbol_handler
|
||||
) : symbol_handler { symbol_handler }
|
||||
SymbolHandler symbol_handler
|
||||
) : symbol_handler { std::move(symbol_handler) }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ private:
|
||||
dsp::interpolation::LinearResampler resampler;
|
||||
GardnerTimingErrorDetector timing_error_detector;
|
||||
ErrorFilter error_filter;
|
||||
std::function<void(const float)> symbol_handler;
|
||||
const SymbolHandler symbol_handler;
|
||||
|
||||
void resampler_callback(const float interpolated_sample) {
|
||||
timing_error_detector(interpolated_sample,
|
||||
@@ -166,7 +168,12 @@ private:
|
||||
}
|
||||
|
||||
void symbol_callback(const float symbol, const float lateness) {
|
||||
symbol_handler(symbol);
|
||||
// NOTE: This check is to avoid std::function nullptr check, which
|
||||
// brings in "_ZSt25__throw_bad_function_callv" and a lot of extra code.
|
||||
// TODO: Make symbol_handler known at compile time.
|
||||
if( symbol_handler) {
|
||||
symbol_handler(symbol);
|
||||
}
|
||||
|
||||
const float adjustment = error_filter(lateness);
|
||||
resampler.advance(adjustment);
|
||||
|
@@ -177,6 +177,20 @@ static inline uint32_t scale_round_and_pack(
|
||||
return __PKHBT(saturated_real, saturated_imag, 16);
|
||||
}
|
||||
|
||||
template<typename Tap>
|
||||
static void taps_copy(
|
||||
const Tap* const source,
|
||||
Tap* const target,
|
||||
const size_t count,
|
||||
const bool shift_up
|
||||
) {
|
||||
const uint32_t negate_pattern = shift_up ? 0b1110 : 0b0100;
|
||||
for(size_t i=0; i<count; i++) {
|
||||
const bool negate = (negate_pattern >> (i & 3)) & 1;
|
||||
target[i] = negate ? -source[i] : source[i];
|
||||
}
|
||||
}
|
||||
|
||||
// FIRC8xR16x24FS4Decim4 //////////////////////////////////////////////////
|
||||
|
||||
void FIRC8xR16x24FS4Decim4::configure(
|
||||
@@ -184,13 +198,7 @@ void FIRC8xR16x24FS4Decim4::configure(
|
||||
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;
|
||||
}
|
||||
taps_copy(taps.data(), taps_.data(), taps_.size(), shift == Shift::Up);
|
||||
output_scale = scale;
|
||||
z_.fill({});
|
||||
}
|
||||
@@ -246,13 +254,7 @@ void FIRC8xR16x24FS4Decim8::configure(
|
||||
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;
|
||||
}
|
||||
taps_copy(taps.data(), taps_.data(), taps_.size(), shift == Shift::Up);
|
||||
output_scale = scale;
|
||||
z_.fill({});
|
||||
}
|
||||
@@ -563,20 +565,18 @@ buffer_c16_t DecimateBy2CIC3::execute(
|
||||
*/
|
||||
uint32_t t1 = _iq0;
|
||||
uint32_t t2 = _iq1;
|
||||
uint32_t t3, t4;
|
||||
const uint32_t taps = 0x00000003;
|
||||
auto s = src.p;
|
||||
auto d = dst.p;
|
||||
const auto d_end = &dst.p[src.count / 2];
|
||||
uint32_t i, q;
|
||||
while(d < d_end) {
|
||||
i = __SXTH(t1, 0); /* 1: I0 */
|
||||
q = __SXTH(t1, 16); /* 1: Q0 */
|
||||
uint32_t i = __SXTH(t1, 0); /* 1: I0 */
|
||||
uint32_t q = __SXTH(t1, 16); /* 1: Q0 */
|
||||
i = __SMLABB(t2, taps, i); /* 1: I1*3 + I0 */
|
||||
q = __SMLATB(t2, taps, q); /* 1: Q1*3 + Q0 */
|
||||
|
||||
t3 = *__SIMD32(s)++; /* 3: Q2:I2 */
|
||||
t4 = *__SIMD32(s)++; /* Q3:I3 */
|
||||
const uint32_t t3 = *__SIMD32(s)++; /* 3: Q2:I2 */
|
||||
const uint32_t t4 = *__SIMD32(s)++; /* Q3:I3 */
|
||||
|
||||
i = __SMLABB(t3, taps, i); /* 1: I2*3 + I1*3 + I0 */
|
||||
q = __SMLATB(t3, taps, q); /* 1: Q2*3 + Q1*3 + Q0 */
|
||||
@@ -645,6 +645,15 @@ buffer_s16_t FIR64AndDecimateBy2Real::execute(
|
||||
return { dst.p, src.count / 2, src.sampling_rate / 2 };
|
||||
}
|
||||
|
||||
void FIRAndDecimateComplex::configure_common(
|
||||
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;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRAndDecimateComplex::execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
|
@@ -241,12 +241,14 @@ private:
|
||||
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;
|
||||
configure_common(taps_count, decimation_factor);
|
||||
std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]);
|
||||
}
|
||||
|
||||
void configure_common(
|
||||
const size_t taps_count,
|
||||
const size_t decimation_factor
|
||||
);
|
||||
};
|
||||
|
||||
class DecimateBy2CIC4Real {
|
||||
|
@@ -42,8 +42,8 @@ buffer_f32_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);
|
||||
*(dst_p++) = __builtin_sqrtf(mag_sq0);
|
||||
*(dst_p++) = __builtin_sqrtf(mag_sq1);
|
||||
*(dst_p++) = __builtin_sqrtf(mag_sq0) * k;
|
||||
*(dst_p++) = __builtin_sqrtf(mag_sq1) * k;
|
||||
}
|
||||
|
||||
return { dst.p, src.count, src.sampling_rate };
|
||||
@@ -57,10 +57,10 @@ buffer_f32_t SSB::execute(
|
||||
const auto src_end = &src.p[src.count];
|
||||
auto dst_p = dst.p;
|
||||
while(src_p < src_end) {
|
||||
*(dst_p++) = (src_p++)->real();
|
||||
*(dst_p++) = (src_p++)->real();
|
||||
*(dst_p++) = (src_p++)->real();
|
||||
*(dst_p++) = (src_p++)->real();
|
||||
*(dst_p++) = (src_p++)->real() * k;
|
||||
*(dst_p++) = (src_p++)->real() * k;
|
||||
*(dst_p++) = (src_p++)->real() * k;
|
||||
*(dst_p++) = (src_p++)->real() * k;
|
||||
}
|
||||
|
||||
return { dst.p, src.count, src.sampling_rate };
|
||||
@@ -99,8 +99,8 @@ buffer_f32_t FM::execute(
|
||||
const auto t0 = multiply_conjugate_s16_s32(s0, z);
|
||||
const auto t1 = multiply_conjugate_s16_s32(s1, s0);
|
||||
z = s1;
|
||||
*(dst_p++) = angle_precise(t0) * k;
|
||||
*(dst_p++) = angle_precise(t1) * k;
|
||||
*(dst_p++) = angle_precise(t0) * kf;
|
||||
*(dst_p++) = angle_precise(t1) * kf;
|
||||
}
|
||||
z_ = z;
|
||||
|
||||
@@ -122,9 +122,9 @@ buffer_s16_t FM::execute(
|
||||
const auto t0 = multiply_conjugate_s16_s32(s0, z);
|
||||
const auto t1 = multiply_conjugate_s16_s32(s1, s0);
|
||||
z = s1;
|
||||
const int32_t theta0_int = angle_approx_0deg27(t0) * k;
|
||||
const int32_t theta0_int = angle_approx_0deg27(t0) * ks16;
|
||||
const int32_t theta0_sat = __SSAT(theta0_int, 16);
|
||||
const int32_t theta1_int = angle_approx_0deg27(t1) * k;
|
||||
const int32_t theta1_int = angle_approx_0deg27(t1) * ks16;
|
||||
const int32_t theta1_sat = __SSAT(theta1_int, 16);
|
||||
*__SIMD32(dst_p)++ = __PKHBT(
|
||||
theta0_sat,
|
||||
@@ -137,5 +137,15 @@ buffer_s16_t FM::execute(
|
||||
return { dst.p, src.count, src.sampling_rate };
|
||||
}
|
||||
|
||||
void FM::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
|
||||
*/
|
||||
kf = static_cast<float>(1.0f / (2.0 * pi * deviation_hz / sampling_rate));
|
||||
ks16 = 32767.0f * kf;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,9 @@ public:
|
||||
const buffer_c16_t& src,
|
||||
const buffer_f32_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
static constexpr float k = 1.0f / 32768.0f;
|
||||
};
|
||||
|
||||
class SSB {
|
||||
@@ -41,6 +44,9 @@ public:
|
||||
const buffer_c16_t& src,
|
||||
const buffer_f32_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
static constexpr float k = 1.0f / 32768.0f;
|
||||
};
|
||||
|
||||
class FM {
|
||||
@@ -55,18 +61,12 @@ public:
|
||||
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));
|
||||
}
|
||||
void configure(const float sampling_rate, const float deviation_hz);
|
||||
|
||||
private:
|
||||
complex16_t::rep_type z_ { 0 };
|
||||
float k { 0 };
|
||||
float kf { 0 };
|
||||
float ks16 { 0 };
|
||||
};
|
||||
|
||||
} /* namespace demodulate */
|
||||
|
@@ -86,11 +86,9 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
shared_memory.baseband_queue.handle([this](Message* const message) {
|
||||
this->on_message(message);
|
||||
});
|
||||
}
|
||||
|
||||
void EventDispatcher::on_message(const Message* const message) {
|
||||
|
@@ -261,7 +261,7 @@
|
||||
* lower priority, this may slow down the driver a bit however.
|
||||
*/
|
||||
#if !defined(SDC_NICE_WAITING) || defined(__DOXYGEN__)
|
||||
#define SDC_NICE_WAITING TRUE
|
||||
#define SDC_NICE_WAITING FALSE
|
||||
#endif
|
||||
|
||||
/*===========================================================================*/
|
||||
|
@@ -42,7 +42,6 @@
|
||||
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include "gcc.hpp"
|
||||
@@ -73,19 +72,10 @@ void __late_init(void) {
|
||||
}
|
||||
|
||||
static void init() {
|
||||
i2s::i2s0::configure(
|
||||
audio::i2s0_config_tx,
|
||||
audio::i2s0_config_rx,
|
||||
audio::i2s0_config_dma
|
||||
);
|
||||
|
||||
audio::dma::init();
|
||||
audio::dma::configure();
|
||||
audio::dma::enable();
|
||||
|
||||
i2s::i2s0::tx_start();
|
||||
i2s::i2s0::rx_start();
|
||||
|
||||
LPC_CREG->DMAMUX = portapack::gpdma_mux;
|
||||
gpdma::controller.enable();
|
||||
nvicEnableVector(DMA_IRQn, CORTEX_PRIORITY_MASK(LPC_DMA_IRQ_PRIORITY));
|
||||
|
@@ -38,6 +38,7 @@ void MatchedFilter::configure(
|
||||
taps_reversed_ = std::make_unique<taps_t>(taps_count);
|
||||
taps_count_ = taps_count;
|
||||
decimation_factor_ = decimation_factor;
|
||||
output = 0;
|
||||
std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]);
|
||||
}
|
||||
|
||||
|
@@ -71,7 +71,7 @@ private:
|
||||
size_t taps_count_ { 0 };
|
||||
size_t decimation_factor_ { 1 };
|
||||
size_t decimation_phase { 0 };
|
||||
float output;
|
||||
float output { 0 };
|
||||
|
||||
void shift_by_decimation_factor();
|
||||
|
||||
|
93
firmware/baseband/ook.hpp
Normal file
93
firmware/baseband/ook.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 __OOK_HPP__
|
||||
#define __OOK_HPP__
|
||||
|
||||
#include "phase_detector.hpp"
|
||||
#include "phase_accumulator.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <complex>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
class OOKSlicerMagSquaredInt {
|
||||
public:
|
||||
using symbol_t = bool;
|
||||
|
||||
constexpr OOKSlicerMagSquaredInt(
|
||||
const float samples_per_symbol
|
||||
) : mag2_threshold_leak_factor {
|
||||
static_cast<uint32_t>(
|
||||
factor_sq(-1.0f / (8.0f * samples_per_symbol)) * float(1ULL << 32)
|
||||
)
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
symbol_t operator()(const std::complex<int16_t> in) {
|
||||
const uint32_t real2 = in.real() * in.real();
|
||||
const uint32_t imag2 = in.imag() * in.imag();
|
||||
const uint32_t mag2 = real2 + imag2;
|
||||
|
||||
const uint32_t mag2_attenuated = mag2 >> 3; // Approximation of (-4.5dB)^2
|
||||
mag2_threshold = (uint64_t(mag2_threshold) * uint64_t(mag2_threshold_leak_factor)) >> 32;
|
||||
mag2_threshold = std::max(mag2_threshold, mag2_attenuated);
|
||||
const bool symbol = (mag2 > mag2_threshold);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t mag2_threshold_leak_factor;
|
||||
uint32_t mag2_threshold = 0;
|
||||
|
||||
constexpr float factor_sq(float db) {
|
||||
return std::pow(10.0f, db / (10.0f / 2));
|
||||
}
|
||||
};
|
||||
|
||||
class OOKClockRecovery {
|
||||
public:
|
||||
constexpr OOKClockRecovery(
|
||||
const float samples_per_symbol
|
||||
) : symbol_phase_inc_nominal { static_cast<uint32_t>(std::round((1ULL << 32) / samples_per_symbol)) },
|
||||
phase_detector { samples_per_symbol },
|
||||
phase_accumulator { symbol_phase_inc_nominal }
|
||||
{
|
||||
}
|
||||
|
||||
template<typename SymbolHandler>
|
||||
void operator()(const uint32_t slicer_history, SymbolHandler symbol_handler) {
|
||||
if( phase_accumulator() ) {
|
||||
const auto detector_result = phase_detector(slicer_history);
|
||||
phase_accumulator.set_inc(symbol_phase_inc_nominal + detector_result.error * (symbol_phase_inc_nominal >> 3));
|
||||
symbol_handler(detector_result.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t symbol_phase_inc_nominal;
|
||||
PhaseDetectorEarlyLateGate phase_detector;
|
||||
PhaseAccumulator phase_accumulator;
|
||||
};
|
||||
|
||||
#endif/*__OOK_HPP__*/
|
@@ -53,8 +53,8 @@ public:
|
||||
const PreambleMatcher preamble_matcher,
|
||||
const UnstuffMatcher unstuff_matcher,
|
||||
const EndMatcher end_matcher,
|
||||
const PayloadHandlerFunc payload_handler
|
||||
) : payload_handler { payload_handler },
|
||||
PayloadHandlerFunc payload_handler
|
||||
) : payload_handler { std::move(payload_handler) },
|
||||
preamble(preamble_matcher),
|
||||
unstuff(unstuff_matcher),
|
||||
end(end_matcher)
|
||||
@@ -89,8 +89,13 @@ public:
|
||||
}
|
||||
|
||||
if( end(bit_history, packet.size()) ) {
|
||||
packet.set_timestamp(Timestamp::now());
|
||||
payload_handler(packet);
|
||||
// NOTE: This check is to avoid std::function nullptr check, which
|
||||
// brings in "_ZSt25__throw_bad_function_callv" and a lot of extra code.
|
||||
// TODO: Make payload_handler known at compile time.
|
||||
if( payload_handler ) {
|
||||
packet.set_timestamp(Timestamp::now());
|
||||
payload_handler(packet);
|
||||
}
|
||||
reset_state();
|
||||
} else {
|
||||
if( packet_truncated() ) {
|
||||
|
50
firmware/baseband/phase_accumulator.hpp
Normal file
50
firmware/baseband/phase_accumulator.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 __PHASE_ACCUMULATOR_HPP__
|
||||
#define __PHASE_ACCUMULATOR_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class PhaseAccumulator {
|
||||
public:
|
||||
constexpr PhaseAccumulator(
|
||||
const uint32_t phase_inc
|
||||
) : phase_inc { phase_inc }
|
||||
{
|
||||
}
|
||||
|
||||
bool operator()() {
|
||||
const auto last_phase = phase;
|
||||
phase += phase_inc;
|
||||
return (phase < last_phase);
|
||||
}
|
||||
|
||||
void set_inc(const uint32_t new_phase_inc) {
|
||||
phase_inc = new_phase_inc;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t phase { 0 };
|
||||
uint32_t phase_inc;
|
||||
};
|
||||
|
||||
#endif/*__PHASE_ACCUMULATOR_HPP__*/
|
68
firmware/baseband/phase_detector.hpp
Normal file
68
firmware/baseband/phase_detector.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 __PHASE_DETECTOR_HPP__
|
||||
#define __PHASE_DETECTOR_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cmath>
|
||||
|
||||
class PhaseDetectorEarlyLateGate {
|
||||
public:
|
||||
using history_t = uint32_t;
|
||||
|
||||
using symbol_t = bool;
|
||||
using error_t = int;
|
||||
|
||||
struct result_t {
|
||||
symbol_t symbol;
|
||||
error_t error;
|
||||
};
|
||||
|
||||
constexpr PhaseDetectorEarlyLateGate(
|
||||
const float samples_per_symbol
|
||||
) : late_mask { (1U << static_cast<size_t>(std::ceil(samples_per_symbol / 2))) - 1 },
|
||||
early_mask { late_mask << static_cast<size_t>(std::floor(samples_per_symbol / 2)) },
|
||||
sample_bit { static_cast<size_t>(std::floor(samples_per_symbol / 2)) }
|
||||
{
|
||||
}
|
||||
|
||||
result_t operator()(const history_t symbol_history) const {
|
||||
// history = ...0111, early
|
||||
// history = ...1110, late
|
||||
|
||||
const symbol_t symbol = (symbol_history >> sample_bit) & 1;
|
||||
const int late_side = __builtin_popcount(symbol_history & late_mask);
|
||||
const int early_side = __builtin_popcount(symbol_history & early_mask);
|
||||
const int lateness = late_side - early_side;
|
||||
const int direction = lateness; //std::min(std::max(lateness, -1), 1);
|
||||
const error_t error = direction;
|
||||
return { symbol, error };
|
||||
}
|
||||
|
||||
private:
|
||||
const history_t late_mask;
|
||||
const history_t early_mask;
|
||||
const size_t sample_bit;
|
||||
};
|
||||
|
||||
#endif/*__PHASE_DETECTOR_HPP__*/
|
@@ -40,6 +40,7 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
|
||||
channel_spectrum.feed(channel_out, channel_filter_pass_f, channel_filter_stop_f);
|
||||
|
||||
auto audio = demodulate(channel_out);
|
||||
audio_compressor.execute_in_place(audio);
|
||||
audio_output.write(audio);
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
|
||||
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)));
|
||||
channel_spectrum.set_decimation_factor(std::floor(channel_filter_output_fs / (channel_filter_pass_f + channel_filter_stop_f)));
|
||||
modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB);
|
||||
audio_output.configure(message.audio_hpf_config);
|
||||
|
||||
|
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "audio_compressor.hpp"
|
||||
|
||||
#include "audio_output.hpp"
|
||||
#include "spectrum_collector.hpp"
|
||||
@@ -64,7 +65,7 @@ private:
|
||||
bool modulation_ssb = false;
|
||||
dsp::demodulate::AM demod_am;
|
||||
dsp::demodulate::SSB demod_ssb;
|
||||
|
||||
FeedForwardCompressor audio_compressor;
|
||||
AudioOutput audio_output;
|
||||
|
||||
SpectrumCollector channel_spectrum;
|
||||
|
87
firmware/baseband/proc_capture.cpp
Normal file
87
firmware/baseband/proc_capture.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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_capture.hpp"
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
CaptureProcessor::CaptureProcessor() {
|
||||
const auto& decim_0_filter = taps_200k_decim_0;
|
||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
||||
|
||||
const auto& decim_1_filter = taps_200k_decim_1;
|
||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
||||
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
||||
|
||||
const auto& channel_filter = decim_1_filter;
|
||||
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
|
||||
constexpr size_t channel_decimation = 1;
|
||||
const size_t channel_filter_output_fs = channel_filter_input_fs / channel_decimation;
|
||||
|
||||
decim_0.configure(decim_0_filter.taps, 33554432);
|
||||
decim_1.configure(decim_1_filter.taps, 131072);
|
||||
|
||||
channel_filter_pass_f = channel_filter.pass_frequency_normalized * channel_filter_input_fs;
|
||||
channel_filter_stop_f = channel_filter.stop_frequency_normalized * channel_filter_input_fs;
|
||||
|
||||
spectrum_interval_samples = channel_filter_output_fs / spectrum_rate_hz;
|
||||
spectrum_samples = 0;
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
|
||||
stream = std::make_unique<StreamInput>(15);
|
||||
}
|
||||
|
||||
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
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;
|
||||
const auto& channel = decimator_out;
|
||||
|
||||
if( stream ) {
|
||||
const size_t bytes_to_write = sizeof(*decimator_out.p) * decimator_out.count;
|
||||
const auto result = stream->write(decimator_out.p, bytes_to_write);
|
||||
}
|
||||
|
||||
feed_channel_stats(channel);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureProcessor::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;
|
||||
}
|
||||
}
|
65
firmware/baseband/proc_capture.hpp
Normal file
65
firmware/baseband/proc_capture.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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_CAPTURE_HPP__
|
||||
#define __PROC_CAPTURE_HPP__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
|
||||
#include "spectrum_collector.hpp"
|
||||
|
||||
#include "stream_input.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class CaptureProcessor : public BasebandProcessor {
|
||||
public:
|
||||
CaptureProcessor();
|
||||
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 2457600;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
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;
|
||||
uint32_t channel_filter_pass_f = 0;
|
||||
uint32_t channel_filter_stop_f = 0;
|
||||
|
||||
std::unique_ptr<StreamInput> stream;
|
||||
|
||||
SpectrumCollector channel_spectrum;
|
||||
size_t spectrum_interval_samples = 0;
|
||||
size_t spectrum_samples = 0;
|
||||
};
|
||||
|
||||
#endif/*__PROC_CAPTURE_HPP__*/
|
@@ -76,8 +76,8 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) {
|
||||
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(message.audio_hpf_config, message.audio_deemph_config, 12288);
|
||||
channel_spectrum.set_decimation_factor(std::floor(channel_filter_output_fs / (channel_filter_pass_f + channel_filter_stop_f)));
|
||||
audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, 0.5f);
|
||||
|
||||
configured = true;
|
||||
}
|
||||
|
@@ -21,31 +21,8 @@
|
||||
|
||||
#include "proc_tpms.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
// 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);
|
||||
@@ -66,6 +43,18 @@ void TPMSProcessor::execute(const buffer_c8_t& buffer) {
|
||||
clock_recovery(mf.get_output());
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t i=0; i<decim_1_out.count; i+=channel_decimation) {
|
||||
const auto sliced = ook_slicer_5sps(decim_1_out.p[i]);
|
||||
slicer_history = (slicer_history << 1) | sliced;
|
||||
|
||||
ook_clock_recovery_subaru(slicer_history, [this](const bool symbol) {
|
||||
this->packet_builder_ook_subaru.execute(symbol);
|
||||
});
|
||||
ook_clock_recovery_gmc(slicer_history, [this](const bool symbol) {
|
||||
this->packet_builder_ook_gmc.execute(symbol);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TPMSProcessor::consume_symbol(
|
||||
@@ -78,6 +67,6 @@ void TPMSProcessor::consume_symbol(
|
||||
void TPMSProcessor::payload_handler(
|
||||
const baseband::Packet& packet
|
||||
) {
|
||||
const TPMSPacketMessage message { packet };
|
||||
const TPMSPacketMessage message { tpms::SignalType::FLM, packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
@@ -32,7 +32,10 @@
|
||||
#include "packet_builder.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
|
||||
#include "ook.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -83,6 +86,38 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr float channel_rate_in = 307200.0f;
|
||||
static constexpr size_t channel_decimation = 8;
|
||||
static constexpr float channel_sample_rate = channel_rate_in / channel_decimation;
|
||||
OOKSlicerMagSquaredInt ook_slicer_5sps { 5 };
|
||||
uint32_t slicer_history { 0 };
|
||||
|
||||
OOKClockRecovery ook_clock_recovery_subaru {
|
||||
channel_sample_rate / 8192.0f
|
||||
};
|
||||
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> packet_builder_ook_subaru {
|
||||
{ 0b010101010101010101011110, 24, 0 },
|
||||
{ },
|
||||
{ 80 },
|
||||
[](const baseband::Packet& packet) {
|
||||
const TPMSPacketMessage message { tpms::SignalType::Subaru, packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
};
|
||||
OOKClockRecovery ook_clock_recovery_gmc {
|
||||
channel_sample_rate / 8400.0f
|
||||
};
|
||||
|
||||
PacketBuilder<BitPattern, NeverMatch, FixedLength> packet_builder_ook_gmc {
|
||||
{ 0b01010101010101010101010101100101, 32, 0 },
|
||||
{ },
|
||||
{ 192 },
|
||||
[](const baseband::Packet& packet) {
|
||||
const TPMSPacketMessage message { tpms::SignalType::GMC, packet };
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
};
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
};
|
||||
|
@@ -33,6 +33,8 @@ using namespace lpc43xx;
|
||||
#include "portapack_dma.hpp"
|
||||
#include "portapack_adc.hpp"
|
||||
|
||||
#include "thread_wait.hpp"
|
||||
|
||||
namespace rf {
|
||||
namespace rssi {
|
||||
namespace dma {
|
||||
@@ -99,20 +101,19 @@ static buffers_config_t buffers_config;
|
||||
static sample_t *samples { nullptr };
|
||||
static gpdma::channel::LLI *lli { nullptr };
|
||||
|
||||
static Semaphore semaphore;
|
||||
static volatile const gpdma::channel::LLI* next_lli = nullptr;
|
||||
static ThreadWait thread_wait;
|
||||
|
||||
static void transfer_complete() {
|
||||
next_lli = gpdma_channel.next_lli();
|
||||
chSemSignalI(&semaphore);
|
||||
const auto next_lli_index = gpdma_channel.next_lli() - &lli[0];
|
||||
thread_wait.wake_from_interrupt(next_lli_index);
|
||||
}
|
||||
|
||||
static void dma_error() {
|
||||
thread_wait.wake_from_interrupt(-1);
|
||||
disable();
|
||||
}
|
||||
|
||||
void init() {
|
||||
chSemInit(&semaphore, 0);
|
||||
gpdma_channel.set_handlers(transfer_complete, dma_error);
|
||||
|
||||
// LPC_GPDMA->SYNC |= (1 << gpdma_peripheral);
|
||||
@@ -147,8 +148,6 @@ void free() {
|
||||
void enable() {
|
||||
const auto gpdma_config = config();
|
||||
gpdma_channel.configure(lli[0], gpdma_config);
|
||||
|
||||
chSemReset(&semaphore, 0);
|
||||
gpdma_channel.enable();
|
||||
}
|
||||
|
||||
@@ -161,16 +160,11 @@ void disable() {
|
||||
}
|
||||
|
||||
rf::rssi::buffer_t wait_for_buffer() {
|
||||
const auto status = chSemWait(&semaphore);
|
||||
if( status == RDY_OK ) {
|
||||
const auto next = next_lli;
|
||||
if( next ) {
|
||||
const size_t next_index = next - &lli[0];
|
||||
const size_t free_index = (next_index + buffers_config.count - 2) % buffers_config.count;
|
||||
return { reinterpret_cast<sample_t*>(lli[free_index].destaddr), buffers_config.items_per_buffer };
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
const auto next_index = thread_wait.sleep();
|
||||
|
||||
if( next_index >= 0 ) {
|
||||
const size_t free_index = (next_index + buffers_config.count - 2) % buffers_config.count;
|
||||
return { reinterpret_cast<sample_t*>(lli[free_index].destaddr), buffers_config.items_per_buffer };
|
||||
} else {
|
||||
// TODO: Should I return here, or loop if RDY_RESET?
|
||||
return { nullptr, 0 };
|
||||
|
@@ -30,11 +30,6 @@
|
||||
|
||||
class RSSIThread : public ThreadBase {
|
||||
public:
|
||||
RSSIThread(
|
||||
) : ThreadBase { "rssi" }
|
||||
{
|
||||
}
|
||||
|
||||
Thread* start(const tprio_t priority);
|
||||
|
||||
private:
|
||||
|
@@ -117,8 +117,8 @@ void SpectrumCollector::update() {
|
||||
// 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);
|
||||
const auto mag2 = magnitude_squared(corrected_sample * (1.0f / 32768.0f));
|
||||
const float db = mag2_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));
|
||||
|
@@ -35,7 +35,8 @@
|
||||
class SpectrumCollector {
|
||||
public:
|
||||
constexpr SpectrumCollector(
|
||||
) : channel_spectrum_decimator { 1 }
|
||||
) : channel_spectrum_decimator { 1 },
|
||||
fifo { fifo_data, ChannelSpectrumConfigMessage::fifo_k }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -52,6 +53,7 @@ public:
|
||||
private:
|
||||
BlockDecimator<complex16_t, 256> channel_spectrum_decimator;
|
||||
ChannelSpectrumFIFO fifo;
|
||||
ChannelSpectrum fifo_data[1 << ChannelSpectrumConfigMessage::fifo_k];
|
||||
|
||||
volatile bool channel_spectrum_request_update { false };
|
||||
bool streaming { false };
|
||||
|
73
firmware/baseband/stream_input.hpp
Normal file
73
firmware/baseband/stream_input.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 __STREAM_INPUT_H__
|
||||
#define __STREAM_INPUT_H__
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "fifo.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
class StreamInput {
|
||||
public:
|
||||
StreamInput(const size_t K) :
|
||||
K { K },
|
||||
data { std::make_unique<uint8_t[]>(1UL << K) },
|
||||
fifo { data.get(), K }
|
||||
{
|
||||
// TODO: Send stream creation message.
|
||||
shared_memory.FIFO_HACK = &fifo;
|
||||
}
|
||||
|
||||
~StreamInput() {
|
||||
// TODO: Send stream distruction message.
|
||||
shared_memory.FIFO_HACK = nullptr;
|
||||
}
|
||||
|
||||
size_t write(const void* const data, const size_t length) {
|
||||
const auto written = fifo.in(reinterpret_cast<const uint8_t*>(data), length);
|
||||
|
||||
const auto last_bytes_written = bytes_written;
|
||||
bytes_written += written;
|
||||
if( (bytes_written & event_bytes_mask) < (last_bytes_written & event_bytes_mask) ) {
|
||||
creg::m4txevent::assert();
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
uint64_t written() const {
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t K;
|
||||
const uint64_t event_bytes_mask = (1ULL << (K - 2)) - 1;
|
||||
uint64_t bytes_written = 0;
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
FIFO<uint8_t> fifo;
|
||||
};
|
||||
|
||||
#endif/*__STREAM_INPUT_H__*/
|
@@ -26,24 +26,17 @@
|
||||
|
||||
class ThreadBase {
|
||||
public:
|
||||
constexpr ThreadBase(
|
||||
const char* const name
|
||||
) : name { name }
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~ThreadBase() = default;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user