mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-04 23:45:26 +00:00
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:
parent
e7e1bedcad
commit
de81156223
@ -83,16 +83,22 @@ options_t freqman_bandwidths[4] = {
|
||||
{"100k", 100000},
|
||||
{"150k", 150000},
|
||||
{"250k", 250000},
|
||||
{"500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/
|
||||
{"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 .*/
|
||||
{"650k", 650000},
|
||||
{"500k", 500000},
|
||||
{"600k", 600000},
|
||||
{"750k", 750000},
|
||||
{"1000k", 1000000}, /* New limit bandwith option for recording in C16 (in fast SD card) or in C8 */
|
||||
{"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) */
|
||||
{"1000k", 1000000}, // Max bandwith for recording in C16 (with fast SD card).
|
||||
{"1250k", 1250000},
|
||||
{"1500k", 1500000},
|
||||
{"1750k", 1750000},
|
||||
{"2000k", 2000000},
|
||||
{"2500k", 2500000},
|
||||
{"2750k", 2750000}, // That is our max Capture option, to keep using later / 8 decimation (22Mhz sampling ADC)
|
||||
{"2250k", 2250000}, // Max bandwith for recording in C8 (with fast SD card).
|
||||
{"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).
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -383,33 +383,33 @@ void WaterfallView::on_audio_spectrum() {
|
||||
// TODO: Comments below refer to a fixed oversample rate (8x), cleanup.
|
||||
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.
|
||||
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.
|
||||
|
||||
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).
|
||||
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).
|
||||
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.
|
||||
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. 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
|
||||
// Good BPF, good matching, we have some periodical M4 % samples drop.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
return 8'000'000;
|
||||
}
|
||||
|
@ -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 actual_sampling_rate = new_sampling_rate * toUType(oversample_rate);
|
||||
|
||||
/* We are changing "REC" icon background to yellow in BW rec Options >1Mhz
|
||||
* > 1Mhz BW options , we are NOT recording full IQ .C16 files (those files has some periodical missing dropped samples).
|
||||
* Those recorded files, has not the full IQ samples information, looks like decimated in file size.
|
||||
* 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)
|
||||
// Change the "REC" icon background to yellow when the selected rate exceeds hardware limits.
|
||||
// Above this threshold, samples will be dropped resulting incomplete capture files.
|
||||
if (new_sampling_rate > 1'250'000) {
|
||||
button_record.set_background(ui::Color::yellow());
|
||||
} else {
|
||||
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)
|
||||
return OversampleRate::None;
|
||||
|
||||
auto rate = ::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;
|
||||
return ::get_oversample_rate(sample_rate);
|
||||
}
|
||||
|
||||
// Setter for datetime and frequency filename
|
||||
|
@ -69,29 +69,22 @@ class BlockDecimator {
|
||||
|
||||
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];
|
||||
if (dst_i == buffer.size()) {
|
||||
callback({buffer.data(), buffer.size(), output_sampling_rate()});
|
||||
reset_state();
|
||||
dst_i = 0;
|
||||
}
|
||||
|
||||
src_i += factor();
|
||||
}
|
||||
|
||||
src_i -= src.count;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<T, N> buffer{};
|
||||
uint32_t input_sampling_rate_{0};
|
||||
size_t factor_{1};
|
||||
size_t src_i{0};
|
||||
size_t dst_i{0};
|
||||
|
||||
void reset_state() {
|
||||
src_i = 0;
|
||||
dst_i = 0;
|
||||
}
|
||||
};
|
||||
|
@ -202,8 +202,8 @@ buffer_c16_t FIRC8xR16x24FS4Decim4::execute(
|
||||
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));
|
||||
|
||||
|
@ -26,48 +26,32 @@
|
||||
#include "event_m4.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace dsp::decimate;
|
||||
|
||||
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);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
const auto decim_0_out = decim_0_execute(buffer, dst_buffer); // selectable 3 possible decim_0, (/4. /8 200k soft filter , /8 180k sharp )
|
||||
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;
|
||||
auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
auto out_buffer = decim_1.execute(decim_0_out, dst_buffer);
|
||||
|
||||
if (stream) {
|
||||
const size_t bytes_to_write = sizeof(*decimator_out.p) * decimator_out.count;
|
||||
const size_t written = stream->write(decimator_out.p, bytes_to_write);
|
||||
const size_t bytes_to_write = sizeof(*out_buffer.p) * out_buffer.count;
|
||||
const size_t written = stream->write(out_buffer.p, 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) {
|
||||
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) {
|
||||
baseband_fs = message.sample_rate * toUType(message.oversample_rate);
|
||||
oversample_rate = message.oversample_rate;
|
||||
const auto sample_rate = message.sample_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);
|
||||
|
||||
// Current fw , we are using only 2 decim_0 modes, /4 , /8
|
||||
auto decim_0_factor = oversample_rate == OversampleRate::x8
|
||||
? decim_0_4.decimation_factor
|
||||
: decim_0_8.decimation_factor;
|
||||
// TODO: Do we need to use the taps that the decimators get configured with?
|
||||
channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * sample_rate;
|
||||
channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * sample_rate;
|
||||
channel_filter_transition = taps_200k_decim_1.transition_normalized * sample_rate;
|
||||
|
||||
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
||||
size_t decim_1_input_fs = decim_0_output_fs;
|
||||
// Compute the scalar that corrects the oversample_rate to be x8 when computing
|
||||
// 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:
|
||||
if (baseband_fs < 4800'000) {
|
||||
decim_1_factor = decim_1_2.decimation_factor; // /8 = /4x2
|
||||
// M4 can't handle 2 decimation passes for sample rates <= 600k.
|
||||
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 {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
decim_1_factor = 2; // just default initial value to remove compile warning.
|
||||
chDbgPanic("Unhandled OversampleRate");
|
||||
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) {
|
||||
if (message.config) {
|
||||
if (message.config)
|
||||
stream = std::make_unique<StreamInput>(message.config);
|
||||
} else {
|
||||
else
|
||||
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() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<CaptureProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -33,6 +33,56 @@
|
||||
|
||||
#include <array>
|
||||
#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 {
|
||||
public:
|
||||
@ -50,15 +100,16 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
|
||||
/* NB: There are two decimation passes: 0 and 1. In pass 0, one of
|
||||
* the following will be selected based on the oversample rate.
|
||||
* use decim_0_4 for an overall decimation factor of 8.
|
||||
* use decim_0_8 for an overall decimation factor of 16. */
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0_4{};
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8{};
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8_180k{};
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1_2{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1_8{};
|
||||
/* The actual type will be configured depending on the sample rate. */
|
||||
MultiDecimator<
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4,
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8>
|
||||
decim_0{};
|
||||
MultiDecimator<
|
||||
dsp::decimate::FIRC16xR16x16Decim2,
|
||||
dsp::decimate::FIRC16xR16x32Decim8,
|
||||
NoopDecim>
|
||||
decim_1{};
|
||||
|
||||
int32_t channel_filter_low_f = 0;
|
||||
int32_t channel_filter_high_f = 0;
|
||||
@ -69,7 +120,6 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
SpectrumCollector channel_spectrum{};
|
||||
size_t spectrum_interval_samples = 0;
|
||||
size_t spectrum_samples = 0;
|
||||
OversampleRate oversample_rate{OversampleRate::x8};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
@ -78,12 +128,6 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
|
||||
void sample_rate_config(const SampleRateConfigMessage& 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__*/
|
||||
|
@ -69,7 +69,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
chDbgPanic("Output not div.");
|
||||
|
||||
// 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.");
|
||||
#endif
|
||||
|
||||
|
@ -45,7 +45,7 @@ class ReplayProcessor : public BasebandProcessor {
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
// 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_high_f = 0;
|
||||
|
@ -816,8 +816,8 @@ enum class OversampleRate : uint8_t {
|
||||
None = 1,
|
||||
x1 = None,
|
||||
|
||||
// 4x would make sense to have, but need to ensure it doesn't
|
||||
// overrun the IQ read buffer in proc_replay.
|
||||
/* Oversample rate of 4 times the sample rate. */
|
||||
x4 = 4,
|
||||
|
||||
/* Oversample rate of 8 times the sample rate. */
|
||||
x8 = 8,
|
||||
@ -830,9 +830,6 @@ enum class OversampleRate : uint8_t {
|
||||
|
||||
/* Oversample rate of 64 times the sample rate. */
|
||||
x64 = 64,
|
||||
|
||||
/* Oversample rate of 128 times the sample rate. */
|
||||
x128 = 128,
|
||||
};
|
||||
|
||||
class SampleRateConfigMessage : public Message {
|
||||
|
@ -42,26 +42,27 @@
|
||||
* In testing, a minimum rate of 400kHz seems to the functional minimum.
|
||||
*
|
||||
* There are several different concepts or terms related to Capture and Replay,
|
||||
* (1) oversampling (x8, x16 ,...) / decimation (/8, /16...)
|
||||
* In Capture App , when ADC can not handle directly a requiered low sample rates ,
|
||||
* we need to apply oversampling (x8. x16 ex) , getting more real samples than needed) by "x_number" ,
|
||||
* and later to write it to SD card with the proper needed real sample rate , we apply Decimation , "/ number" .
|
||||
* (1) Oversampling (x8, x16, ...) and Decimation (/8, /16...)
|
||||
* In Capture App, the ADC can not directly handle low sample rates.
|
||||
* We need to apply oversampling (x8, x16, ...), collecting more "x number" additional real samples.
|
||||
* 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, ...)
|
||||
* In Replay-list App, when we got too low bit rate data for Hackrf ,
|
||||
* we need to upsampling or interpolate or resampling to increase those low bit rates
|
||||
* to a proper sample rate higher than the min. to be able to be transmitted by Hackrf.
|
||||
* (2) Up-sampling or re-scale or interpolation. (x8, x16, ...).
|
||||
* In Replay-list App, when the bit rate data is too low for HackRF.
|
||||
* We need to upsample or interpolate to increase those low bit rates
|
||||
* to a rate higher than the hardware nminimum to be transmitted by Hackrf.
|
||||
*/
|
||||
|
||||
/* Gets the oversample rate for a given sample rate.
|
||||
* 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. */
|
||||
inline OversampleRate get_oversample_rate(uint32_t sample_rate) {
|
||||
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 < 250'000) return OversampleRate::x16; // 100k and 150k.
|
||||
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 < 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.
|
||||
|
Loading…
Reference in New Issue
Block a user