Support for 1.25MHz capture (#1418)

* Advanced draft decim /4 just waterfall ok

* apply some Kall's corrections + formatting

* Tidy up both decim_factors

* New refine optimizations

* Format issues

* more format issues ...mmmm

* comments update

* WIP Cleanup

* WIP

* WIP - add variant

* Use std::visit to dispatch MultiDecimator -- fluent API

* Clean up comments

* Merge next and fix compilation

* Fix odd loop in BlockDecimator

* Clean up spectrum math

* Descibe spectrum update math better, more clear math.

* Apply spectrum interval correction at 1.5M

* Increase replay buffer to handle x4 ovs

---------

Co-authored-by: Brumi-2021 <ea3hqj@gmail.com>
This commit is contained in:
Kyle Reed 2023-08-29 10:26:38 -07:00 committed by GitHub
parent e7e1bedcad
commit de81156223
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 170 additions and 189 deletions

View File

@ -83,16 +83,22 @@ options_t freqman_bandwidths[4] = {
{"100k", 100000}, {"100k", 100000},
{"150k", 150000}, {"150k", 150000},
{"250k", 250000}, {"250k", 250000},
{"500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/ {"500k", 500000},
{"600k", 600000}, /* We doubled x2 previous REC BW limit , now extended BW from 600k to 1M with fast enough SD card in C16 or C8 format .*/ {"600k", 600000},
{"650k", 650000},
{"750k", 750000}, {"750k", 750000},
{"1000k", 1000000}, /* New limit bandwith option for recording in C16 (in fast SD card) or in C8 */ {"1000k", 1000000}, // Max bandwith for recording in C16 (with fast SD card).
{"1500k", 1500000}, /* From this BW onwards, the LCD is ok, but M4 CPU is having periodical sample rec dropps, (not real file size, accelerated replay) */ {"1250k", 1250000},
{"1500k", 1500000},
{"1750k", 1750000}, {"1750k", 1750000},
{"2000k", 2000000}, {"2000k", 2000000},
{"2500k", 2500000}, {"2250k", 2250000}, // Max bandwith for recording in C8 (with fast SD card).
{"2750k", 2750000}, // That is our max Capture option, to keep using later / 8 decimation (22Mhz sampling ADC) {"2500k", 2500000}, // Here and up, LCD is ok, but M4 CPU drops samples.
{"3000k", 3000000},
{"3500k", 3500000},
{"4000k", 4000000},
{"4500k", 4500000},
{"5000k", 5500000},
{"5500k", 5500000}, // Max capture, needs /4 decimation, (22Mhz sampling ADC).
}, },
}; };

View File

@ -383,33 +383,33 @@ void WaterfallView::on_audio_spectrum() {
// TODO: Comments below refer to a fixed oversample rate (8x), cleanup. // TODO: Comments below refer to a fixed oversample rate (8x), cleanup.
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) { uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) {
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem. switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
case 0 ... 3'500'000: // BW Captured range (0 <= 250kHz max) fs = 8x250 kHz =2000., 16x150 khz =2400, 32x100 khz =3200, (32x75k = 2400), (future 64x40 khz =2400) case 0 ... 3'500'000: // BW Captured range (0 <= 250kHz max) fs = 8x250k = 2000, 16x150k = 2400, 32x100k = 3200, 32x75k = 2400, (future 64x40 khz = 2400)
return 1'750'000; // Minimum BPF MAX2837 for all those lower BW options. return 1'750'000; // Minimum BPF MAX2837 for all those lower BW options.
case 4'000'000 ... 6'000'000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz case 4'000'000 ... 7'000'000: // OVS x8, BW capture range (500k...750kHz max) fs_max = 8 x 750k = 6Mhz
// BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz). // BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz).
return 2'500'000; // In some IC, MAX2837 appears as 2250000, but both work similarly. return 2'500'000; // In some IC, MAX2837 appears as 2250000, but both work similarly.
case 8'000'000: // BW capture 1Mhz fs = 8 x 1Mhz = 8Mhz. (1Mhz showed slightly higher noise background). case 7'000'001 ... 10'000'000: // OVS x8 and x4, BW capture 1Mhz fs = 8 x 1Mhz = 8Mhz. (1Mhz showed slightly higher noise background).
return 3'500'000; // some low SD cards, if not showing avg. writting speed >4MB/sec, they will produce sammples drop at REC with 1MB and C16 format. return 3'500'000; // some low SD cards, if not showing avg. writing speed >4MB/sec, they will produce sammples drop at REC with 1MB and C16 format.
case 12'000'000: // BW capture 1,5Mhz, fs = 8 x 1,5Mhz = 12Mhz case 12'000'000 ... 14'000'000: // OVS x4, BW capture 3Mhz, fs = 4 x 3Mhz = 12Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop. // Good BPF, good matching, we have some periodical M4 % samples drop.
return 5'000'000; return 5'000'000;
case 14'000'000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz case 16'000'000: // OVS x4, BW capture 4Mhz, fs = 4 x 4Mhz = 16Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop. // Good BPF, good matching, we have some periodical M4 % samples drop.
return 5'000'000; return 5'500'000;
case 16'000'000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz case 18'000'000: // OVS x4, BW capture 4,5Mhz, fs = 4 x 4,5Mhz = 18Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop. // Good BPF, good matching, we have some periodical M4 % samples drop.
return 6'000'000; return 6'000'000;
case 20'000'000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz case 20'000'000: // OVS x4, BW capture 5Mhz, fs = 4 x 5Mhz = 20Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop. // Good BPF, good matching, we have some periodical M4 % samples drop.
return 7'000'000; return 7'000'000;
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz = 22Mhz max ADC sampling and others. default: // BW capture 5,5Mhz, fs = 4 x 5,5Mhz = 22Mhz max ADC sampling and others.
// We tested also 9Mhz FPB slightly too much noise floor, better at 8Mhz. // We tested also 9Mhz FPB slightly too much noise floor, better at 8Mhz.
return 8'000'000; return 8'000'000;
} }

View File

@ -107,13 +107,9 @@ uint32_t RecordView::set_sampling_rate(uint32_t new_sampling_rate) {
auto oversample_rate = get_oversample_rate(new_sampling_rate); auto oversample_rate = get_oversample_rate(new_sampling_rate);
auto actual_sampling_rate = new_sampling_rate * toUType(oversample_rate); auto actual_sampling_rate = new_sampling_rate * toUType(oversample_rate);
/* We are changing "REC" icon background to yellow in BW rec Options >1Mhz // Change the "REC" icon background to yellow when the selected rate exceeds hardware limits.
* > 1Mhz BW options , we are NOT recording full IQ .C16 files (those files has some periodical missing dropped samples). // Above this threshold, samples will be dropped resulting incomplete capture files.
* Those recorded files, has not the full IQ samples information, looks like decimated in file size. if (new_sampling_rate > 1'250'000) {
* They are ok as recorded spectrum indication, but they should not be used by Replay app. (the voice speed will be accelerated)
* We keep original black background in all the correct IQ .C16 files BW's Options. */
if (actual_sampling_rate > 8'000'000) { // yellow REC button means not ok for REC, BW >1Mhz (BW from 12k5 till 1Mhz OK for REC and Replay)
button_record.set_background(ui::Color::yellow()); button_record.set_background(ui::Color::yellow());
} else { } else {
button_record.set_background(ui::Color::black()); button_record.set_background(ui::Color::black());
@ -142,15 +138,7 @@ OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
if (file_type == FileType::WAV) if (file_type == FileType::WAV)
return OversampleRate::None; return OversampleRate::None;
auto rate = ::get_oversample_rate(sample_rate); return ::get_oversample_rate(sample_rate);
// Currently proc_capture only supports /8, /16, /32 for decimation.
if (rate < OversampleRate::x8) // clipping while /4 is not implemented yet (it will be used >1Mhz onwards when available)
rate = OversampleRate::x8;
else if (rate > OversampleRate::x64) // clipping while /128 is not implemented yet , (but it is not necessary for 12k5)
rate = OversampleRate::x64;
return rate;
} }
// Setter for datetime and frequency filename // Setter for datetime and frequency filename

View File

@ -69,29 +69,22 @@ class BlockDecimator {
set_input_sampling_rate(src.sampling_rate); set_input_sampling_rate(src.sampling_rate);
while (src_i < src.count) { for (size_t src_i = 0; src_i < src.count; src_i += factor()) {
buffer[dst_i++] = src.p[src_i]; buffer[dst_i++] = src.p[src_i];
if (dst_i == buffer.size()) { if (dst_i == buffer.size()) {
callback({buffer.data(), buffer.size(), output_sampling_rate()}); callback({buffer.data(), buffer.size(), output_sampling_rate()});
reset_state(); reset_state();
dst_i = 0;
} }
src_i += factor();
} }
src_i -= src.count;
} }
private: private:
std::array<T, N> buffer{}; std::array<T, N> buffer{};
uint32_t input_sampling_rate_{0}; uint32_t input_sampling_rate_{0};
size_t factor_{1}; size_t factor_{1};
size_t src_i{0};
size_t dst_i{0}; size_t dst_i{0};
void reset_state() { void reset_state() {
src_i = 0;
dst_i = 0; dst_i = 0;
} }
}; };

View File

@ -202,8 +202,8 @@ buffer_c16_t FIRC8xR16x24FS4Decim4::execute(
uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4)); uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4));
const auto k = output_scale; const auto k = output_scale;
const size_t count = src.count / decimation_factor; const size_t count = src.count / decimation_factor;
for (size_t i = 0; i < count; i++) { 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)); const vec4_s8* const in = static_cast<const vec4_s8*>(__builtin_assume_aligned(&src.p[i * decimation_factor], 4));

View File

@ -26,48 +26,32 @@
#include "event_m4.hpp" #include "event_m4.hpp"
#include "utility.hpp" #include "utility.hpp"
using namespace dsp::decimate;
CaptureProcessor::CaptureProcessor() { CaptureProcessor::CaptureProcessor() {
decim_0_4.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/8)
decim_0_8.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/16)
decim_0_8_180k.configure(taps_180k_wfm_decim_0.taps, 33554432); // to be used alone - no additional decim1 (/2), then total single stage decim (/8)
decim_1_2.configure(taps_200k_decim_1.taps, 131072);
decim_1_8.configure(taps_16k0_decim_1.taps, 131072); // tentative decim1 /8 and taps, pending to be optimized.
channel_spectrum.set_decimation_factor(1); channel_spectrum.set_decimation_factor(1);
baseband_thread.start(); baseband_thread.start();
} }
void CaptureProcessor::execute(const buffer_c8_t& buffer) { void CaptureProcessor::execute(const buffer_c8_t& buffer) {
/* 2.4576MHz, 2048 samples */ auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_0_out = decim_0_execute(buffer, dst_buffer); // selectable 3 possible decim_0, (/4. /8 200k soft filter , /8 180k sharp ) auto out_buffer = decim_1.execute(decim_0_out, dst_buffer);
const auto decim_1_out = decim_1_execute(decim_0_out, dst_buffer); // selectable 3 possible decim_1, (/8. /2 200k or bypassed /1 )
/* this code was valid when we had only 2 decim1 cases.
const auto decim_1_out = baseband_fs < 4800'000
? decim_1_2.execute(decim_0_out, dst_buffer) // < 600khz double decim. stage , means 500khz and lower bit rates.
// ? decim_1_8.execute(decim_0_out, dst_buffer) // < 600khz double decim. stage , means 500khz and lower bit rates.
: decim_0_out; // >= 600khz single decim. stage , means 600khz and upper bit rates.
} */
const auto& decimator_out = decim_1_out;
const auto& channel = decimator_out;
if (stream) { if (stream) {
const size_t bytes_to_write = sizeof(*decimator_out.p) * decimator_out.count; const size_t bytes_to_write = sizeof(*out_buffer.p) * out_buffer.count;
const size_t written = stream->write(decimator_out.p, bytes_to_write); const size_t written = stream->write(out_buffer.p, bytes_to_write);
if (written != bytes_to_write) { if (written != bytes_to_write) {
// TODO eventually report error somewhere // TODO: Send an error message to the app?
} }
} }
feed_channel_stats(channel); feed_channel_stats(out_buffer);
spectrum_samples += channel.count; spectrum_samples += out_buffer.count;
if (spectrum_samples >= spectrum_interval_samples) { if (spectrum_samples >= spectrum_interval_samples) {
spectrum_samples -= spectrum_interval_samples; spectrum_samples -= spectrum_interval_samples;
channel_spectrum.feed(channel, channel_filter_low_f, channel_filter_high_f, channel_filter_transition); channel_spectrum.feed(out_buffer, channel_filter_low_f,
channel_filter_high_f, channel_filter_transition);
} }
} }
@ -92,120 +76,88 @@ void CaptureProcessor::on_message(const Message* const message) {
} }
void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message) { void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
baseband_fs = message.sample_rate * toUType(message.oversample_rate); const auto sample_rate = message.sample_rate;
oversample_rate = message.oversample_rate;
// The actual sample rate is the requested rate * the oversample rate.
// See oversample.hpp for more details on oversampling.
baseband_fs = sample_rate * toUType(message.oversample_rate);
baseband_thread.set_sampling_rate(baseband_fs); baseband_thread.set_sampling_rate(baseband_fs);
// Current fw , we are using only 2 decim_0 modes, /4 , /8 // TODO: Do we need to use the taps that the decimators get configured with?
auto decim_0_factor = oversample_rate == OversampleRate::x8 channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * sample_rate;
? decim_0_4.decimation_factor channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * sample_rate;
: decim_0_8.decimation_factor; channel_filter_transition = taps_200k_decim_1.transition_normalized * sample_rate;
size_t decim_0_output_fs = baseband_fs / decim_0_factor; // Compute the scalar that corrects the oversample_rate to be x8 when computing
size_t decim_1_input_fs = decim_0_output_fs; // the spectrum update interval. The original implementation only supported x8.
// TODO: Why is this needed here but not in proc_replay? There must be some other
// assumption about x8 oversampling in some component that makes this necessary.
const auto oversample_correction = toUType(message.oversample_rate) / 8.0;
// The spectrum update interval controls how often the waterfall is fed new samples.
spectrum_interval_samples = sample_rate / (spectrum_rate_hz * oversample_correction);
spectrum_samples = 0;
// For high sample rates, the M4 is busy collecting samples so the
// waterfall runs slower. Reduce the update interval so it runs faster.
// NB: Trade off: looks nicer, but more frequent updates == more CPU.
if (sample_rate >= 1'500'000)
spectrum_interval_samples /= (sample_rate / 500'000);
// Mystery scalars for decimator configuration.
// TODO: figure these out and add a real comment.
constexpr int decim_0_scale = 0x2000000;
constexpr int decim_1_scale = 0x20000;
switch (message.oversample_rate) {
case OversampleRate::x4:
// M4 can't handle 2 decimation passes for sample rates needing x4.
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps, decim_0_scale);
decim_1.set<NoopDecim>();
break;
size_t decim_1_factor;
switch (oversample_rate) { // we are using 3 decim_1 modes, /1 , /2 , /8
case OversampleRate::x8: case OversampleRate::x8:
if (baseband_fs < 4800'000) { // M4 can't handle 2 decimation passes for sample rates <= 600k.
decim_1_factor = decim_1_2.decimation_factor; // /8 = /4x2 if (message.sample_rate < 600'000) {
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps, decim_0_scale);
decim_1.set<FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps, decim_1_scale);
} else { } else {
decim_1_factor = 2 * 1; // 600khz and onwards, single decim /8 = /8x1 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum) // Using 180k taps to provide better filtering with a single pass.
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_180k_wfm_decim_0.taps, decim_0_scale);
decim_1.set<NoopDecim>();
} }
break; break;
case OversampleRate::x16: case OversampleRate::x16:
decim_1_factor = 2 * decim_1_2.decimation_factor; // /16 = /8x2 (we applied additional *2 correction to increase waterfall spped >=600k and smooth & avoid abnormal motion >1M5 ) decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps, decim_0_scale);
decim_1.set<FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps, decim_1_scale);
break; break;
case OversampleRate::x32: case OversampleRate::x32:
decim_1_factor = 2 * decim_1_8.decimation_factor; // /32 = /4x8 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum) decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps, decim_0_scale);
decim_1.set<FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps, decim_1_scale);
break; break;
case OversampleRate::x64: case OversampleRate::x64:
decim_1_factor = 8 * decim_1_8.decimation_factor; // /64 = /8x8 (we applied additional *8 correction to speed up waterfall, no effect to scale spectrum) decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps, decim_0_scale);
decim_1.set<FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps, decim_1_scale);
break; break;
default: default:
decim_1_factor = 2; // just default initial value to remove compile warning. chDbgPanic("Unhandled OversampleRate");
break; break;
} }
/*
auto decim_1_factor = oversample_rate == OversampleRate::x32 // that was ok, when we had only 2 oversampling x8 , x16
? decim_1_8.decimation_factor
: decim_1_2.decimation_factor;
*/
size_t decim_1_output_fs = decim_1_input_fs / decim_1_factor;
channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * decim_1_input_fs;
channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * decim_1_input_fs;
channel_filter_transition = taps_200k_decim_1.transition_normalized * decim_1_input_fs;
spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz;
spectrum_samples = 0;
} }
void CaptureProcessor::capture_config(const CaptureConfigMessage& message) { void CaptureProcessor::capture_config(const CaptureConfigMessage& message) {
if (message.config) { if (message.config)
stream = std::make_unique<StreamInput>(message.config); stream = std::make_unique<StreamInput>(message.config);
} else { else
stream.reset(); stream.reset();
}
}
buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
switch (oversample_rate) {
case OversampleRate::x8: // we can get /8 by two means , decim0 (/4) + decim1 (/2) . or just decim0 (/8)
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
return decim_0_4.execute(src, dst); // decim_0 , /4 with double decim stage
} else {
return decim_0_8_180k.execute(src, dst); // decim_0 /8 with single decim stage
}
case OversampleRate::x16:
return decim_0_8.execute(src, dst); // decim_0 , /8 with double decim stage
case OversampleRate::x32:
return decim_0_4.execute(src, dst); // decim_0 , /4 with double decim stage
case OversampleRate::x64:
return decim_0_8.execute(src, dst); // decim_0 , /8 with double decim stage
default:
chDbgPanic("Unhandled OversampleRate");
return {};
}
}
buffer_c16_t CaptureProcessor::decim_1_execute(const buffer_c16_t& src, const buffer_c16_t& dst) {
switch (oversample_rate) {
case OversampleRate::x8: // we can get /8 by two means , decim0 (/4) + decim1 (/2) . or just decim0 (/8)
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
return decim_1_2.execute(src, dst);
} else {
return src;
}
case OversampleRate::x16:
return decim_1_2.execute(src, dst); // total decim /16 = /8x2, applied to 100khz and 150khz
case OversampleRate::x32:
return decim_1_8.execute(src, dst); // total decim /32 = /4x8, appled to 75k , 50k, 32k
case OversampleRate::x64:
return decim_1_8.execute(src, dst); // total decim /64 = /8x8, appled to 16k and 12k5
default:
chDbgPanic("Unhandled OversampleRate");
return {};
}
} }
int main() { int main() {
EventDispatcher event_dispatcher{std::make_unique<CaptureProcessor>()}; EventDispatcher event_dispatcher{std::make_unique<CaptureProcessor>()};
event_dispatcher.run(); event_dispatcher.run();
return 0; return 0;
} }

View File

@ -33,6 +33,56 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <tuple>
#include <variant>
/* A decimator that just returns the source buffer. */
class NoopDecim {
public:
static constexpr int decimation_factor = 1;
template <typename Buffer>
Buffer execute(const Buffer& src, const Buffer&) {
// TODO: should this copy to 'dst'?
return {src.p, src.count, src.sampling_rate};
}
};
/* Decimator wrapper that can hold one of a set of decimators and dispatch at runtime. */
template <typename... Args>
class MultiDecimator {
public:
/* Dispatches to the underlying type's execute. */
template <typename Source, typename Destination>
Destination execute(
const Source& src,
const Destination& dst) {
return std::visit(
[&src, &dst](auto&& arg) -> Destination {
return arg.execute(src, dst);
},
decimator_);
}
size_t decimation_factor() const {
return std::visit(
[](auto&& arg) -> size_t {
return arg.decimation_factor;
},
decimator_);
}
/* Sets this decimator to a new instance of the specified decimator type.
* NB: The instance is returned by-ref so 'configure' can easily be called. */
template <typename Decimator>
Decimator& set() {
decimator_ = Decimator{};
return std::get<Decimator>(decimator_);
}
private:
std::variant<Args...> decimator_{};
};
class CaptureProcessor : public BasebandProcessor { class CaptureProcessor : public BasebandProcessor {
public: public:
@ -50,15 +100,16 @@ class CaptureProcessor : public BasebandProcessor {
dst.data(), dst.data(),
dst.size()}; dst.size()};
/* NB: There are two decimation passes: 0 and 1. In pass 0, one of /* The actual type will be configured depending on the sample rate. */
* the following will be selected based on the oversample rate. MultiDecimator<
* use decim_0_4 for an overall decimation factor of 8. dsp::decimate::FIRC8xR16x24FS4Decim4,
* use decim_0_8 for an overall decimation factor of 16. */ dsp::decimate::FIRC8xR16x24FS4Decim8>
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0_4{}; decim_0{};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8{}; MultiDecimator<
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8_180k{}; dsp::decimate::FIRC16xR16x16Decim2,
dsp::decimate::FIRC16xR16x16Decim2 decim_1_2{}; dsp::decimate::FIRC16xR16x32Decim8,
dsp::decimate::FIRC16xR16x32Decim8 decim_1_8{}; NoopDecim>
decim_1{};
int32_t channel_filter_low_f = 0; int32_t channel_filter_low_f = 0;
int32_t channel_filter_high_f = 0; int32_t channel_filter_high_f = 0;
@ -69,7 +120,6 @@ class CaptureProcessor : public BasebandProcessor {
SpectrumCollector channel_spectrum{}; SpectrumCollector channel_spectrum{};
size_t spectrum_interval_samples = 0; size_t spectrum_interval_samples = 0;
size_t spectrum_samples = 0; size_t spectrum_samples = 0;
OversampleRate oversample_rate{OversampleRate::x8};
/* NB: Threads should be the last members in the class definition. */ /* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{ BasebandThread baseband_thread{
@ -78,12 +128,6 @@ class CaptureProcessor : public BasebandProcessor {
void sample_rate_config(const SampleRateConfigMessage& message); void sample_rate_config(const SampleRateConfigMessage& message);
void capture_config(const CaptureConfigMessage& message); void capture_config(const CaptureConfigMessage& message);
/* Dispatch to the correct decim_0 based on oversample rate. */
buffer_c16_t decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst);
/* Dispatch to the correct decim_1 based on oversample rate. */
buffer_c16_t decim_1_execute(const buffer_c16_t& src, const buffer_c16_t& dst);
}; };
#endif /*__PROC_CAPTURE_HPP__*/ #endif /*__PROC_CAPTURE_HPP__*/

View File

@ -69,7 +69,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
chDbgPanic("Output not div."); chDbgPanic("Output not div.");
// Is the input smaple buffer big enough? // Is the input smaple buffer big enough?
if (samples_to_read > iq_buffer.size()) if (samples_to_read > iq_buffer.count)
chDbgPanic("IQ buf ovf."); chDbgPanic("IQ buf ovf.");
#endif #endif

View File

@ -45,7 +45,7 @@ class ReplayProcessor : public BasebandProcessor {
static constexpr auto spectrum_rate_hz = 50.0f; static constexpr auto spectrum_rate_hz = 50.0f;
// Holds the read IQ data chunk from the file to send. // Holds the read IQ data chunk from the file to send.
std::array<complex16_t, 256> iq{}; std::array<complex16_t, 512> iq{};
int32_t channel_filter_low_f = 0; int32_t channel_filter_low_f = 0;
int32_t channel_filter_high_f = 0; int32_t channel_filter_high_f = 0;

View File

@ -816,8 +816,8 @@ enum class OversampleRate : uint8_t {
None = 1, None = 1,
x1 = None, x1 = None,
// 4x would make sense to have, but need to ensure it doesn't /* Oversample rate of 4 times the sample rate. */
// overrun the IQ read buffer in proc_replay. x4 = 4,
/* Oversample rate of 8 times the sample rate. */ /* Oversample rate of 8 times the sample rate. */
x8 = 8, x8 = 8,
@ -830,9 +830,6 @@ enum class OversampleRate : uint8_t {
/* Oversample rate of 64 times the sample rate. */ /* Oversample rate of 64 times the sample rate. */
x64 = 64, x64 = 64,
/* Oversample rate of 128 times the sample rate. */
x128 = 128,
}; };
class SampleRateConfigMessage : public Message { class SampleRateConfigMessage : public Message {

View File

@ -42,26 +42,27 @@
* In testing, a minimum rate of 400kHz seems to the functional minimum. * In testing, a minimum rate of 400kHz seems to the functional minimum.
* *
* There are several different concepts or terms related to Capture and Replay, * There are several different concepts or terms related to Capture and Replay,
* (1) oversampling (x8, x16 ,...) / decimation (/8, /16...) * (1) Oversampling (x8, x16, ...) and Decimation (/8, /16...)
* In Capture App , when ADC can not handle directly a requiered low sample rates , * In Capture App, the ADC can not directly handle low sample rates.
* we need to apply oversampling (x8. x16 ex) , getting more real samples than needed) by "x_number" , * We need to apply oversampling (x8, x16, ...), collecting more "x number" additional real samples.
* and later to write it to SD card with the proper needed real sample rate , we apply Decimation , "/ number" . * To write it to SD card with the desired sample rate, we apply Decimation ("/ number").
* *
* (2) up-sampling or re-escale or interpolation. (x8, x16, ...) * (2) Up-sampling or re-scale or interpolation. (x8, x16, ...).
* In Replay-list App, when we got too low bit rate data for Hackrf , * In Replay-list App, when the bit rate data is too low for HackRF.
* we need to upsampling or interpolate or resampling to increase those low bit rates * We need to upsample or interpolate to increase those low bit rates
* to a proper sample rate higher than the min. to be able to be transmitted by Hackrf. * to a rate higher than the hardware nminimum to be transmitted by Hackrf.
*/ */
/* Gets the oversample rate for a given sample rate. /* Gets the oversample rate for a given sample rate.
* The oversample rate is used to increase the sample rate to improve SNR and quality. * The oversample rate is used to increase the sample rate to improve SNR and quality.
* This is also used as the interpolation rate when replaying captures. */ * This is also used as the interpolation rate when replaying captures. */
inline OversampleRate get_oversample_rate(uint32_t sample_rate) { inline OversampleRate get_oversample_rate(uint32_t sample_rate) {
if (sample_rate < 30'000) return OversampleRate::x64; // 25k, 16k, 12k5. if (sample_rate < 30'000) return OversampleRate::x64; // 25k, 16k, 12k5.
if (sample_rate < 80'000) return OversampleRate::x32; // 75k, 50k, 32k. if (sample_rate < 80'000) return OversampleRate::x32; // 75k, 50k, 32k.
if (sample_rate < 250'000) return OversampleRate::x16; // 100k and 150k. if (sample_rate < 250'000) return OversampleRate::x16; // 100k, 150k.
if (sample_rate < 1'250'000) return OversampleRate::x8; // 250k, 500k, 600k, 650k, 750k, 1Mhz.
return OversampleRate::x8; // 250k .. 1Mhz, that decim x8 , is already applied.(OVerSampling and decim OK) return OversampleRate::x4; // Top range (1.25Mhz ... 5.5Mhz).
} }
/* Gets the actual sample rate for a given sample rate. /* Gets the actual sample rate for a given sample rate.