#ifndef __SDR_PSK31_HH__ #define __SDR_PSK31_HH__ #include "node.hh" #include "traits.hh" #include "interpolate.hh" namespace sdr { /** 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 BPSK31: public Sink< std::complex >, 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 >(), 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 for the interpolating sub-sampler _dl = Buffer< std::complex >(2*8); _dl_idx = 0; for (size_t i=0; i<16; i++) { _dl[i] = 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 (64 phase samples per symbol) _superSample = 64; } /** Destructor. */ virtual ~BPSK31() { // unreference buffers _dl.unref(); _hist.unref(); _buffer.unref(); } 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 >() != src_cfg.type()) { ConfigError err; err << "Can not configure BPSK31: Invalid type " << src_cfg.type() << ", expected " << Config::typeId< std::complex >(); 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); // Decoding history, contains up to 64 phases (if in sync) _hist = Buffer(_superSample); _hist_idx = 0; _last_constellation = 1; // Output buffer size_t bsize = 1 + int(Fs/31.25); _buffer = Buffer(bsize); // This node sends a bit-stream with 31.25 baud. this->setConfig(Config(Traits::scalarId, 31.25, src_cfg.bufferSize(), 1)); } virtual void process(const Buffer< std::complex > &buffer, bool allow_overwrite) { size_t i=0, o=0; while (i 1) && (i 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())) { if (_hist_idx<(_superSample/2)) { // If less than superSample values in hist -> drop _hist_idx = 0; } else { // Otherwise decode int cconst = _currentContellation(); _buffer[o++] = (_last_constellation == cconst); _last_constellation = cconst; _hist_idx = 0; } } else if (_hist_idx == (_superSample-1)) { // If the symbol is complete: int cconst = _currentContellation(); _buffer[o++] = (_last_constellation == cconst); _last_constellation = cconst; _hist_idx = 0; } else { // Next sample _hist_idx++; } } } // If at least 1 bit was decoded -> send result if (o>0) { this->send(_buffer.head(o)); } } protected: /** Returns @c true if there is a phase transition at the current sample. */ inline bool _hasTransition() const { return ((_hist[_hist_idx-1]>=0) && (_hist[_hist_idx]<=0)) || ((_hist[_hist_idx-1]<=0) && (_hist[_hist_idx]>=0)); } /** Returns the current constellation. */ inline int _currentContellation() const { float value = 0; for (size_t i=0; i<=_hist_idx; i++) { value += _hist[i]; } return (value > 0) ? 1 : -1; } /** Computes the phase error. */ inline float _phaseError(const std::complex &value) const { float r2 = value.real()*value.real(); float i2 = value.imag()*value.imag(); float nrm2 = r2+i2; if (0 == nrm2) { return 0; } return -value.real()*value.imag()/nrm2; } /** Updates the PLL (@c _F and @c _P). */ inline void _updatePLL(const std::complex &sample) { float phi = _phaseError(sample); _F += _beta*phi; _P += _F + _alpha*phi; while (_P>( 2*M_PI)) { _P -= 2*M_PI; } while (_P<(-2*M_PI)) { _P += 2*M_PI; } _F = std::min(_Fmax, std::max(_Fmin, _F)); //std::cerr << "Update PLL: P=" << _P << "; F=" << _F << ", err= " << phi << std::endl; } /** Updates the sub-sampler. */ inline void _updateSampler(const std::complex &value) { // 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 fac = std::exp(std::complex(0, _P+_theta)); // Down-convert singal std::complex sample = fac * std::complex(value.real(), value.imag()); // store sample into delay line _dl[_dl_idx] = sample; _dl[_dl_idx+8] = sample; _dl_idx = (_dl_idx + 1) % 8; } /** Updates the PPL state (@c _mu and @c _omega). */ inline void _errorTracking(const std::complex &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 x = (_c_0T - _c_2T) * std::conj(_p_1T); std::complex 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; // 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: /** 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; /** Lower frequency limit of the carrier PLL. */ float _Fmin; /** Upper frequency limit of the carrier PLL. */ float _Fmax; /** Gain factor of the carrier PLL. */ float _alpha; /** Gain factor of the carrier PLL. */ float _beta; /** The delay line for the interpolating sub-sampler. */ Buffer< std::complex > _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; /** Minimum of the sub-sample rate. */ float _min_omega; /** Maximum of the sub-sample rate. */ float _max_omega; /** Gain of the sub-sample rate correction. */ float _gain_omega; /** Phase at T = 0 (samples). */ std::complex _p_0T; /** Phase at T=-1 (samples). */ std::complex _p_1T; /** Phase at T=-2 (samples). */ std::complex _p_2T; /** Constellation at T=0 (samples). */ std::complex _c_0T; /** Constellation at T=-1 (samples). */ std::complex _c_1T; /** Constellation at T=-2 (samples). */ std::complex _c_2T; /** The last @c _superSample phases. */ Buffer _hist; /** Current phase history index. */ size_t _hist_idx; /** The last output constellation. */ int _last_constellation; /** Output buffer. */ Buffer _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, 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 &buffer, bool allow_overwrite); protected: /** The shift register of the last received bits. */ uint16_t _value; /** The output buffer. */ Buffer _buffer; /** The conversion table. */ std::map _code_table; }; } #endif // __SDR_PSK31_HH__