Complete rewrite of clock recovery, lots of refactoring.

This commit is contained in:
Jared Boone 2015-09-25 22:13:36 -07:00
parent 8b2e75a299
commit a3cce5632e
4 changed files with 125 additions and 75 deletions

View File

@ -20,12 +20,3 @@
*/ */
#include "clock_recovery.hpp" #include "clock_recovery.hpp"
void ClockRecovery::configure(
const uint32_t symbol_rate,
const uint32_t sampling_rate
) {
phase_increment = phase_increment_u32(
fractional_symbol_rate(symbol_rate, sampling_rate)
);
}

View File

@ -22,72 +22,125 @@
#ifndef __CLOCK_RECOVERY_H__ #ifndef __CLOCK_RECOVERY_H__
#define __CLOCK_RECOVERY_H__ #define __CLOCK_RECOVERY_H__
#include <cstdint> #include <cstddef>
#include <array>
#include <functional>
class ClockRecovery { #include "linear_resampler.hpp"
namespace clock_recovery {
class GardnerTimingErrorDetector {
public: public:
void configure( static constexpr size_t samples_per_symbol { 2 };
const uint32_t symbol_rate,
const uint32_t sampling_rate
);
/*
Expects retimed samples at a rate of twice the expected symbol rate.
Calculates timing error, sends symbol and error to handler.
*/
template<typename SymbolHandler> template<typename SymbolHandler>
void execute( void operator()(
const float in, const float in,
SymbolHandler symbol_handler SymbolHandler symbol_handler
) { ) {
const bool phase_0 = (phase_last >> 31) & (!(phase >> 31)); /* NOTE: Algorithm is sensitive to input magnitude. Timing error value
const bool phase_180 = (!(phase_last >> 31)) & (phase >> 31); * will scale proportionally. Best practice is to use error sign only.
*/
t[2] = t[1];
t[1] = t[0];
t[0] = in;
if( phase_0 || phase_180 ) { if( symbol_phase == 0 ) {
t2 = t1; const auto symbol = t[0];
t1 = t0; const float lateness = (t[0] - t[2]) * t[1];
symbol_handler(symbol, lateness);
const uint32_t phase_boundary = phase_180 ? (1U << 31) : 0;
const float alpha = (phase_boundary - phase_last) / float(phase_increment + phase_adjustment);
const float t = last_sample + alpha * (in - last_sample);
t0 = t;
} }
if( phase_0 ) { symbol_phase = (symbol_phase + 1) % samples_per_symbol;
symbol_handler(t0);
const float error = (t0 - t2) * t1;
// + error == late == decrease/slow phase
// - error == early == increase/fast phase
error_filtered = 0.75f * error_filtered + 0.25f * error;
// Correct phase (don't change frequency!)
phase_adjustment = -phase_increment * error_filtered / 200.0f;
}
phase_last = phase;
phase += phase_increment + phase_adjustment;
last_sample = in;
} }
private: private:
uint32_t phase { 0 }; std::array<float, 3> t { { 0.0f, 0.0f, 0.0f } };
uint32_t phase_last { 0 }; size_t symbol_phase { 0 };
uint32_t phase_adjustment { 0 }; };
uint32_t phase_increment { 0 };
float last_sample { 0 };
float t0 { 0 };
float t1 { 0 };
float t2 { 0 };
float error_filtered { 0 };
static constexpr float fractional_symbol_rate( class LinearErrorFilter {
const uint32_t symbol_rate, public:
const uint32_t sampling_rate float operator()(
const float error
) { ) {
return float(symbol_rate) / float(sampling_rate); error_filtered = filter_alpha * error_filtered + (1.0f - filter_alpha) * error;
return error_filtered * error_weight;
} }
static constexpr uint32_t phase_increment_u32(const float fractional_symbol_rate) { private:
return 4294967296.0f * fractional_symbol_rate; float filter_alpha { 0.95f };
float error_filtered { 0.0f };
float error_weight { 0.5f };
};
class FixedErrorFilter {
public:
float operator()(
const float lateness
) {
return (lateness < 0.0f) ? weight : -weight;
}
private:
float weight { 1.0f / 16.0f };
};
class ClockRecovery {
public:
ClockRecovery(
const float sampling_rate,
const float symbol_rate,
std::function<void(const float)> symbol_handler
) : resampler(sampling_rate, symbol_rate * timing_error_detector.samples_per_symbol),
symbol_handler { symbol_handler }
{
}
void configure(
const float sampling_rate,
const float symbol_rate
) {
resampler.configure(sampling_rate, symbol_rate * timing_error_detector.samples_per_symbol);
}
void operator()(
const float baseband_sample
) {
resampler(baseband_sample,
[this](const float interpolated_sample) {
this->resampler_callback(interpolated_sample);
}
);
}
private:
dsp::interpolation::LinearResampler resampler;
GardnerTimingErrorDetector timing_error_detector;
FixedErrorFilter error_filter;
std::function<void(const float)> symbol_handler;
void resampler_callback(const float interpolated_sample) {
timing_error_detector(interpolated_sample,
[this](const float symbol, const float lateness) {
this->symbol_callback(symbol, lateness);
}
);
}
void symbol_callback(const float symbol, const float lateness) {
symbol_handler(symbol);
const float adjustment = error_filter(lateness);
resampler.advance(adjustment);
} }
}; };
} /* namespace clock_recovery */
#endif/*__CLOCK_RECOVERY_H__*/ #endif/*__CLOCK_RECOVERY_H__*/

View File

@ -43,8 +43,8 @@ FSKProcessor::~FSKProcessor() {
} }
void FSKProcessor::configure(const FSKConfiguration new_configuration) { void FSKProcessor::configure(const FSKConfiguration new_configuration) {
demod.configure(76800, 2 * new_configuration.symbol_rate); demod.configure(sampling_rate, 2 * new_configuration.symbol_rate);
clock_recovery.configure(new_configuration.symbol_rate, 76800); clock_recovery.configure(sampling_rate / 4, new_configuration.symbol_rate);
access_code_correlator.configure( access_code_correlator.configure(
new_configuration.access_code, new_configuration.access_code,
new_configuration.access_code_length, new_configuration.access_code_length,
@ -78,12 +78,6 @@ void FSKProcessor::execute(buffer_c8_t buffer) {
decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized
); );
const auto symbol_handler_fn = [this](const float value) {
const uint_fast8_t symbol = (value >= 0.0f) ? 1 : 0;
const bool access_code_found = this->access_code_correlator.execute(symbol);
this->consume_symbol(symbol, access_code_found);
};
// 76.8k // 76.8k
const buffer_s16_t work_demod_buffer { const buffer_s16_t work_demod_buffer {
@ -93,16 +87,16 @@ void FSKProcessor::execute(buffer_c8_t buffer) {
auto demodulated = demod.execute(channel, work_demod_buffer); auto demodulated = demod.execute(channel, work_demod_buffer);
i2s::i2s0::tx_mute(); // TODO: Factor out this hidden decimation magic.
for(size_t i=0; i<demodulated.count; i+=4) {
for(size_t i=0; i<demodulated.count; i++) { clock_recovery(demodulated.p[i] / 32768.0f);
clock_recovery.execute(demodulated.p[i], symbol_handler_fn);
} }
i2s::i2s0::tx_mute();
} }
void FSKProcessor::consume_symbol( void FSKProcessor::consume_symbol(
const uint_fast8_t symbol, const float raw_symbol
const bool access_code_found
) { ) {
const auto payload_handler_fn = [this]( const auto payload_handler_fn = [this](
const std::bitset<256>& payload, const std::bitset<256>& payload,
@ -111,8 +105,12 @@ void FSKProcessor::consume_symbol(
this->payload_handler(payload, bits_received); this->payload_handler(payload, bits_received);
}; };
const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0;
const auto decoded_symbol = nrzi_decode(sliced_symbol);
const bool access_code_found = access_code_correlator.execute(decoded_symbol);
packet_builder.execute( packet_builder.execute(
symbol, decoded_symbol,
access_code_found, access_code_found,
payload_handler_fn payload_handler_fn
); );

View File

@ -30,6 +30,7 @@
#include "dsp_fir_taps.hpp" #include "dsp_fir_taps.hpp"
#include "clock_recovery.hpp" #include "clock_recovery.hpp"
#include "symbol_coding.hpp"
#include "access_code_correlator.hpp" #include "access_code_correlator.hpp"
#include "packet_builder.hpp" #include "packet_builder.hpp"
@ -49,18 +50,25 @@ public:
void execute(buffer_c8_t buffer) override; void execute(buffer_c8_t buffer) override;
private: private:
const size_t sampling_rate = 76800;
ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By16 }; ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By16 };
const fir_taps_real<64>& channel_filter_taps = taps_64_lp_031_070_tfilter; const fir_taps_real<64>& channel_filter_taps = taps_64_lp_031_070_tfilter;
dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { channel_filter_taps.taps }; dsp::decimate::FIRAndDecimateBy2Complex<64> channel_filter { channel_filter_taps.taps };
dsp::demodulate::FM demod { 76800, 9600 * 2 }; dsp::demodulate::FM demod { sampling_rate, 9600 * 2 };
ClockRecovery clock_recovery; clock_recovery::ClockRecovery clock_recovery {
sampling_rate / 4,
9600,
[this](const float symbol) { this->consume_symbol(symbol); }
};
symbol_coding::NRZIDecoder nrzi_decode;
AccessCodeCorrelator access_code_correlator; AccessCodeCorrelator access_code_correlator;
PacketBuilder packet_builder; PacketBuilder packet_builder;
MessageHandlerMap& message_handlers; MessageHandlerMap& message_handlers;
void consume_symbol(const uint_fast8_t symbol, const bool access_code_found); void consume_symbol(const float symbol);
void payload_handler(const std::bitset<256>& payload, const size_t bits_received); void payload_handler(const std::bitset<256>& payload, const size_t bits_received);
}; };