|
|
|
|
@ -8,33 +8,55 @@
|
|
|
|
|
|
|
|
|
|
namespace sdr {
|
|
|
|
|
|
|
|
|
|
/** A simple BPSK31 "demodulator". */
|
|
|
|
|
/** A simple BPSK31 "demodulator". This node consumes a complex input stream with a sample-rate of
|
|
|
|
|
* at least 2000Hz and produces a bitstream with 31.25 Hz "sample-rate". Use the @c Varicode node
|
|
|
|
|
* to decode this bitstream to ASCII chars. The BPSK31 signal should be centered around 0Hz, this
|
|
|
|
|
* node uses a simple PLL to adjust for small detunings. */
|
|
|
|
|
template <class Scalar>
|
|
|
|
|
class BPSK31: public Sink< std::complex<Scalar> >, public Source
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
/** Constructs a new BPSK31 demodulator.
|
|
|
|
|
*
|
|
|
|
|
* This node first subsamples the input signal to a multiple of 31.25 Hz (by default to
|
|
|
|
|
* 2000Hz = 64*31.25) using an interpolating sub-sampler. Therefore, the input signal must be
|
|
|
|
|
* filtered sufficiently well to avoid artifacts of the interpolating sub-sampler.
|
|
|
|
|
* Then, the phase-constellation of the signal is determined as either -pi or pi while the
|
|
|
|
|
* frequency of the carrier is tracked using a simple PLL. Finally, the BPSK31 bit stream is
|
|
|
|
|
* decoded by detecting a phase-change (0) or not (1).
|
|
|
|
|
*
|
|
|
|
|
* @note This node uses floating point arithmetic, hence it should not be used on streams with
|
|
|
|
|
* a high sample rate! Which is not neccessary as it only decodes a BPSK signal with approx.
|
|
|
|
|
* 31 baud.
|
|
|
|
|
*
|
|
|
|
|
* @param dF Specfies the (relative anglular) frequency range of the PLL to adjust for small
|
|
|
|
|
* deviations of the BPSK31 signal from 0Hz. */
|
|
|
|
|
BPSK31(double dF=0.1)
|
|
|
|
|
: Sink< std::complex<Scalar> >(), Source(),
|
|
|
|
|
_P(0), _F(0), _Fmin(-dF), _Fmax(dF)
|
|
|
|
|
{
|
|
|
|
|
// Assemble carrier PLL gains:
|
|
|
|
|
double damping = std::sqrt(2)/2;
|
|
|
|
|
double bw = M_PI/100;
|
|
|
|
|
double tmp = 1. + 2*damping*bw + bw*bw;
|
|
|
|
|
_alpha = 4*damping*bw/tmp;
|
|
|
|
|
_beta = 4*bw*bw/tmp;
|
|
|
|
|
|
|
|
|
|
// Init Delay line
|
|
|
|
|
// Init delay line for the interpolating sub-sampler
|
|
|
|
|
_dl = Buffer< std::complex<float> >(2*8); _dl_idx = 0;
|
|
|
|
|
for (size_t i=0; i<16; i++) { _dl[i] = 0; }
|
|
|
|
|
_mu = 0.25; _gain_mu = 0.01;
|
|
|
|
|
|
|
|
|
|
// Inner PLL:
|
|
|
|
|
_theta = 0; _mu = 0.25; _gain_mu = 0.01;
|
|
|
|
|
// constant phase shift of the signal
|
|
|
|
|
_theta = 0;
|
|
|
|
|
|
|
|
|
|
// Phase detection:
|
|
|
|
|
_omega = _min_omega = _max_omega = 0;
|
|
|
|
|
_omega_rel = 0.001; _gain_omega = 0.001;
|
|
|
|
|
_p_0T = _p_1T = _p_2T = 0;
|
|
|
|
|
_c_0T = _c_1T = _c_2T = 0;
|
|
|
|
|
|
|
|
|
|
// Super-sample (256 samples per symbol)
|
|
|
|
|
// Super-sample (64 phase samples per symbol)
|
|
|
|
|
_superSample = 64;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -42,9 +64,11 @@ public:
|
|
|
|
|
// pass...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void config(const Config &src_cfg) {
|
|
|
|
|
virtual void config(const Config &src_cfg)
|
|
|
|
|
{
|
|
|
|
|
// Requires type, sample rate & buffer size
|
|
|
|
|
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
|
|
|
|
|
|
|
|
|
|
// Check buffer type
|
|
|
|
|
if (Config::typeId< std::complex<Scalar> >() != src_cfg.type()) {
|
|
|
|
|
ConfigError err;
|
|
|
|
|
@ -53,18 +77,29 @@ public:
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check input sample rate
|
|
|
|
|
double Fs = src_cfg.sampleRate();
|
|
|
|
|
if (2000 > Fs) {
|
|
|
|
|
ConfigError err;
|
|
|
|
|
err << "Can not configure BPSK31: Input sample rate too low! The BPSK31 node requires at "
|
|
|
|
|
<< "least a sample rate of 2000Hz, got " << Fs << "Hz";
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute samples per symbol
|
|
|
|
|
_omega = Fs/(_superSample*31.25);
|
|
|
|
|
// Obtain limits for the sub-sample rate
|
|
|
|
|
_min_omega = _omega*(1.0 - _omega_rel);
|
|
|
|
|
_max_omega = _omega*(1.0 + _omega_rel);
|
|
|
|
|
_omega_mid = 0.5*(_min_omega+_max_omega);
|
|
|
|
|
|
|
|
|
|
// Decoding history, contains up to 64 phases (if in sync)
|
|
|
|
|
_hist = Buffer<float>(_superSample);
|
|
|
|
|
_hist_idx = 0;
|
|
|
|
|
_last_constellation = 1;
|
|
|
|
|
|
|
|
|
|
_buffer = Buffer<uint8_t>(src_cfg.bufferSize());
|
|
|
|
|
// Output buffer
|
|
|
|
|
size_t bsize = 1 + int(Fs/31.25);
|
|
|
|
|
_buffer = Buffer<uint8_t>(bsize);
|
|
|
|
|
|
|
|
|
|
// This node sends a bit-stream with 31.25 baud.
|
|
|
|
|
this->setConfig(Config(Traits<uint8_t>::scalarId, 31.25, src_cfg.bufferSize(), 1));
|
|
|
|
|
@ -80,10 +115,15 @@ public:
|
|
|
|
|
}
|
|
|
|
|
// Then, ...
|
|
|
|
|
if (i<buffer.size()) {
|
|
|
|
|
// Subsample
|
|
|
|
|
std::complex<float> sample = interpolate(_dl.sub(_dl_idx, 8), _mu);
|
|
|
|
|
// Update error tracking
|
|
|
|
|
_errorTracking(sample);
|
|
|
|
|
// Update carrier PLL
|
|
|
|
|
_updatePLL(sample);
|
|
|
|
|
// done. real(sample) constains phase of symbol
|
|
|
|
|
|
|
|
|
|
// Now fill history with phase constellation and try to decode a bit
|
|
|
|
|
_hist[_hist_idx] = std::real(sample);
|
|
|
|
|
// Check if there is a constellation transition at the current time
|
|
|
|
|
if ((_hist_idx>1) && (_hasTransition())) {
|
|
|
|
|
@ -109,8 +149,8 @@ public:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->send(_buffer.head(o));
|
|
|
|
|
// If at least 1 bit was decoded -> send result
|
|
|
|
|
if (o>0) { this->send(_buffer.head(o)); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -145,77 +185,113 @@ protected:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void _updateSampler(const std::complex<Scalar> &value) {
|
|
|
|
|
_mu-=1; _P += _F;
|
|
|
|
|
// decrease fractional sub-sample counter
|
|
|
|
|
_mu-=1;
|
|
|
|
|
// Update phase
|
|
|
|
|
_P += _F;
|
|
|
|
|
// Limit phase
|
|
|
|
|
while (_P>( 2*M_PI)) { _P -= 2*M_PI; }
|
|
|
|
|
while (_P<(-2*M_PI)) { _P += 2*M_PI; }
|
|
|
|
|
// Calc down-conversion factor (consider look-up table)
|
|
|
|
|
std::complex<float> fac = std::exp(std::complex<float>(0, _P+_theta));
|
|
|
|
|
// Down-convert singal
|
|
|
|
|
std::complex<float> sample = fac * std::complex<float>(value.real(), value.imag());
|
|
|
|
|
// store sample into delay line
|
|
|
|
|
_dl[_dl_idx] = sample;
|
|
|
|
|
_dl[_dl_idx+8] = sample;
|
|
|
|
|
_dl_idx = (_dl_idx + 1) % 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void _errorTracking(const std::complex<float> &sample) {
|
|
|
|
|
// Update last 2 constellation and phases
|
|
|
|
|
_p_2T = _p_1T; _p_1T = _p_0T; _p_0T = sample;
|
|
|
|
|
_c_2T = _c_1T; _c_1T = _c_0T; _c_0T = (sample.real() > 0) ? -1 : 1;
|
|
|
|
|
|
|
|
|
|
// Compute difference between phase and constellation
|
|
|
|
|
std::complex<float> x = (_c_0T - _c_2T) * std::conj(_p_1T);
|
|
|
|
|
std::complex<float> y = (_p_0T - _p_2T) * std::conj(_c_1T);
|
|
|
|
|
// Get phase error
|
|
|
|
|
float err = std::real(y-x);
|
|
|
|
|
if (err > 1.0) { err = 1.0; }
|
|
|
|
|
if (err < -1.0) { err = -1.0; }
|
|
|
|
|
// Update symbol rate (approx 31.25*64)
|
|
|
|
|
_omega += _gain_omega * err;
|
|
|
|
|
if ( (_omega-_omega_mid) > _omega_rel) { _omega = _omega_mid + _omega_rel; }
|
|
|
|
|
else if ( (_omega-_omega_mid) < -_omega_rel) { _omega = _omega_mid - _omega_rel; }
|
|
|
|
|
else { _omega = _omega_mid + _omega-_omega_mid; }
|
|
|
|
|
// Limit omega on _omega_mid +/- _omega*_omega_rel
|
|
|
|
|
_omega = std::max(_min_omega, std::min(_max_omega, _omega));
|
|
|
|
|
// Update mu as +_omega (samples per symbol) and a small correction term
|
|
|
|
|
_mu += _omega + _gain_mu * err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
// Carrier PLL stuff
|
|
|
|
|
float _P, _F, _Fmin, _Fmax;
|
|
|
|
|
float _alpha, _beta;
|
|
|
|
|
// Delay line
|
|
|
|
|
Buffer< std::complex<float> > _dl;
|
|
|
|
|
size_t _dl_idx;
|
|
|
|
|
// Second PLL
|
|
|
|
|
float _theta;
|
|
|
|
|
float _mu, _gain_mu;
|
|
|
|
|
float _omega, _min_omega, _max_omega;
|
|
|
|
|
float _omega_mid, _omega_rel, _gain_omega;
|
|
|
|
|
std::complex<float> _p_0T, _p_1T, _p_2T;
|
|
|
|
|
std::complex<float> _c_0T, _c_1T, _c_2T;
|
|
|
|
|
// Third "PLL" -> bit decoder
|
|
|
|
|
/** Holds the number of phase constellations per bit. */
|
|
|
|
|
size_t _superSample;
|
|
|
|
|
/** Phase of the carrier PLL. */
|
|
|
|
|
float _P;
|
|
|
|
|
/** Frequency of the carrier PLL. */
|
|
|
|
|
float _F;
|
|
|
|
|
/** Upper and lower frequency limit of the carrier PLL. */
|
|
|
|
|
float _Fmin, _Fmax;
|
|
|
|
|
/** Gain factors of the carrier PLL. */
|
|
|
|
|
float _alpha, _beta;
|
|
|
|
|
/** The delay line for the interpolating sub-sampler. */
|
|
|
|
|
Buffer< std::complex<float> > _dl;
|
|
|
|
|
/** The current index of the delay line. */
|
|
|
|
|
size_t _dl_idx;
|
|
|
|
|
/** Holds the fractional sub-sampling counter. */
|
|
|
|
|
float _mu;
|
|
|
|
|
/** Gain factor of the sub-sampler. */
|
|
|
|
|
float _gain_mu;
|
|
|
|
|
/** Constant phase shift between real axis and first constellation. (Currently unused). */
|
|
|
|
|
float _theta;
|
|
|
|
|
/** Current sub-sample rate. */
|
|
|
|
|
float _omega;
|
|
|
|
|
/** Relative error of the subsample rate. */
|
|
|
|
|
float _omega_rel;
|
|
|
|
|
/** Limits of the sub-sample rate. */
|
|
|
|
|
float _min_omega, _max_omega;
|
|
|
|
|
/** Gain of the sub-sample rate correction. */
|
|
|
|
|
float _gain_omega;
|
|
|
|
|
/** Last 3 phases. */
|
|
|
|
|
std::complex<float> _p_0T, _p_1T, _p_2T;
|
|
|
|
|
/** Last 3 constellations. */
|
|
|
|
|
std::complex<float> _c_0T, _c_1T, _c_2T;
|
|
|
|
|
/** The last @c _superSample phases. */
|
|
|
|
|
Buffer<float> _hist;
|
|
|
|
|
/** Current phase history index. */
|
|
|
|
|
size_t _hist_idx;
|
|
|
|
|
/** The last output constellation. */
|
|
|
|
|
int _last_constellation;
|
|
|
|
|
// Output buffer
|
|
|
|
|
/** Output buffer. */
|
|
|
|
|
Buffer<uint8_t> _buffer;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Simple varicode (Huffman code) decoder node. It consumes a bit-stream (uint8_t)
|
|
|
|
|
* and produces a uint8_t stream of ascii chars. Non-printable chars (except for new-line) are
|
|
|
|
|
* ignored. The output stream has no samplerate! */
|
|
|
|
|
class Varicode: public Sink<uint8_t>, public Source
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
/** Constructor. */
|
|
|
|
|
Varicode();
|
|
|
|
|
/** Destructor. */
|
|
|
|
|
virtual ~Varicode();
|
|
|
|
|
|
|
|
|
|
/** Configures the node. */
|
|
|
|
|
virtual void config(const Config &src_cfg);
|
|
|
|
|
|
|
|
|
|
/** Converts the input bit stream to ASCII chars. */
|
|
|
|
|
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
/** The shift register of the last received bits. */
|
|
|
|
|
uint16_t _value;
|
|
|
|
|
/** The output buffer. */
|
|
|
|
|
Buffer<uint8_t> _buffer;
|
|
|
|
|
/** The conversion table. */
|
|
|
|
|
std::map<uint16_t, char> _code_table;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|