Added some comments.

master
Hannes Matuschek 12 years ago
parent 5f673f1df1
commit 2b2336691b

@ -7,6 +7,7 @@ using namespace sdr;
Varicode::Varicode() Varicode::Varicode()
: Sink<uint8_t>(), Source() : Sink<uint8_t>(), Source()
{ {
// Fill code table
_code_table[1023] = '!'; _code_table[87] = '.'; _code_table[895] = '\''; _code_table[1023] = '!'; _code_table[87] = '.'; _code_table[895] = '\'';
_code_table[367] = '*'; _code_table[495] = '\\'; _code_table[687] = '?'; _code_table[367] = '*'; _code_table[495] = '\\'; _code_table[687] = '?';
_code_table[475] = '$'; _code_table[701] = '@'; _code_table[365] = '_'; _code_table[475] = '$'; _code_table[701] = '@'; _code_table[365] = '_';
@ -17,6 +18,8 @@ Varicode::Varicode()
_code_table[693] = '}'; _code_table[503] = ')'; _code_table[1749] = '%'; _code_table[693] = '}'; _code_table[503] = ')'; _code_table[1749] = '%';
_code_table[471] = '>'; _code_table[991] = '+'; _code_table[251] = '['; _code_table[471] = '>'; _code_table[991] = '+'; _code_table[251] = '[';
_code_table[85] = '='; _code_table[943] = '/'; _code_table[29] = '\n'; _code_table[85] = '='; _code_table[943] = '/'; _code_table[29] = '\n';
_code_table[31] = '\r'; _code_table[747] = '\n';
// ^- Encode EOT as LN
_code_table[443] = '|'; _code_table[1] = ' '; _code_table[125] = 'A'; _code_table[443] = '|'; _code_table[1] = ' '; _code_table[125] = 'A';
_code_table[235] = 'B'; _code_table[173] = 'C'; _code_table[181] = 'D'; _code_table[235] = 'B'; _code_table[173] = 'C'; _code_table[181] = 'D';
_code_table[119] = 'E'; _code_table[219] = 'F'; _code_table[253] = 'G'; _code_table[119] = 'E'; _code_table[219] = 'F'; _code_table[253] = 'G';
@ -35,8 +38,7 @@ Varicode::Varicode()
_code_table[21] = 'r'; _code_table[23] = 's'; _code_table[5] = 't'; _code_table[21] = 'r'; _code_table[23] = 's'; _code_table[5] = 't';
_code_table[55] = 'u'; _code_table[123] = 'v'; _code_table[107] = 'w'; _code_table[55] = 'u'; _code_table[123] = 'v'; _code_table[107] = 'w';
_code_table[223] = 'x'; _code_table[93] = 'y'; _code_table[469] = 'z'; _code_table[223] = 'x'; _code_table[93] = 'y'; _code_table[469] = 'z';
_code_table[183] = '0'; _code_table[445] = '1'; _code_table[237] = '2'; _code_table[183] = '0'; _code_table[189] = '1'; _code_table[237] = '2';
// ^- Collides with ';'!
_code_table[511] = '3'; _code_table[375] = '4'; _code_table[859] = '5'; _code_table[511] = '3'; _code_table[375] = '4'; _code_table[859] = '5';
_code_table[363] = '6'; _code_table[941] = '7'; _code_table[427] = '8'; _code_table[363] = '6'; _code_table[941] = '7'; _code_table[427] = '8';
_code_table[951] = '9'; _code_table[951] = '9';

@ -8,33 +8,55 @@
namespace sdr { 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> template <class Scalar>
class BPSK31: public Sink< std::complex<Scalar> >, public Source class BPSK31: public Sink< std::complex<Scalar> >, public Source
{ {
public: 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) BPSK31(double dF=0.1)
: Sink< std::complex<Scalar> >(), Source(), : Sink< std::complex<Scalar> >(), Source(),
_P(0), _F(0), _Fmin(-dF), _Fmax(dF) _P(0), _F(0), _Fmin(-dF), _Fmax(dF)
{ {
// Assemble carrier PLL gains:
double damping = std::sqrt(2)/2; double damping = std::sqrt(2)/2;
double bw = M_PI/100; double bw = M_PI/100;
double tmp = 1. + 2*damping*bw + bw*bw; double tmp = 1. + 2*damping*bw + bw*bw;
_alpha = 4*damping*bw/tmp; _alpha = 4*damping*bw/tmp;
_beta = 4*bw*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; _dl = Buffer< std::complex<float> >(2*8); _dl_idx = 0;
for (size_t i=0; i<16; i++) { _dl[i] = 0; } for (size_t i=0; i<16; i++) { _dl[i] = 0; }
_mu = 0.25; _gain_mu = 0.01;
// Inner PLL: // constant phase shift of the signal
_theta = 0; _mu = 0.25; _gain_mu = 0.01; _theta = 0;
// Phase detection:
_omega = _min_omega = _max_omega = 0; _omega = _min_omega = _max_omega = 0;
_omega_rel = 0.001; _gain_omega = 0.001; _omega_rel = 0.001; _gain_omega = 0.001;
_p_0T = _p_1T = _p_2T = 0; _p_0T = _p_1T = _p_2T = 0;
_c_0T = _c_1T = _c_2T = 0; _c_0T = _c_1T = _c_2T = 0;
// Super-sample (256 samples per symbol) // Super-sample (64 phase samples per symbol)
_superSample = 64; _superSample = 64;
} }
@ -42,9 +64,11 @@ public:
// pass... // pass...
} }
virtual void config(const Config &src_cfg) { virtual void config(const Config &src_cfg)
{
// Requires type, sample rate & buffer size // Requires type, sample rate & buffer size
if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; } if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.hasBufferSize()) { return; }
// Check buffer type // Check buffer type
if (Config::typeId< std::complex<Scalar> >() != src_cfg.type()) { if (Config::typeId< std::complex<Scalar> >() != src_cfg.type()) {
ConfigError err; ConfigError err;
@ -53,18 +77,29 @@ public:
throw err; throw err;
} }
// Check input sample rate
double Fs = src_cfg.sampleRate(); 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 // Compute samples per symbol
_omega = Fs/(_superSample*31.25); _omega = Fs/(_superSample*31.25);
// Obtain limits for the sub-sample rate
_min_omega = _omega*(1.0 - _omega_rel); _min_omega = _omega*(1.0 - _omega_rel);
_max_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 = Buffer<float>(_superSample);
_hist_idx = 0; _hist_idx = 0;
_last_constellation = 1; _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 node sends a bit-stream with 31.25 baud.
this->setConfig(Config(Traits<uint8_t>::scalarId, 31.25, src_cfg.bufferSize(), 1)); this->setConfig(Config(Traits<uint8_t>::scalarId, 31.25, src_cfg.bufferSize(), 1));
@ -80,10 +115,15 @@ public:
} }
// Then, ... // Then, ...
if (i<buffer.size()) { if (i<buffer.size()) {
// Subsample
std::complex<float> sample = interpolate(_dl.sub(_dl_idx, 8), _mu); std::complex<float> sample = interpolate(_dl.sub(_dl_idx, 8), _mu);
// Update error tracking
_errorTracking(sample); _errorTracking(sample);
// Update carrier PLL
_updatePLL(sample); _updatePLL(sample);
// done. real(sample) constains phase of symbol // 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); _hist[_hist_idx] = std::real(sample);
// Check if there is a constellation transition at the current time // Check if there is a constellation transition at the current time
if ((_hist_idx>1) && (_hasTransition())) { if ((_hist_idx>1) && (_hasTransition())) {
@ -109,8 +149,8 @@ public:
} }
} }
} }
// If at least 1 bit was decoded -> send result
this->send(_buffer.head(o)); if (o>0) { this->send(_buffer.head(o)); }
} }
@ -145,77 +185,113 @@ protected:
} }
inline void _updateSampler(const std::complex<Scalar> &value) { 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; }
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)); 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()); std::complex<float> sample = fac * std::complex<float>(value.real(), value.imag());
// store sample into delay line
_dl[_dl_idx] = sample; _dl[_dl_idx] = sample;
_dl[_dl_idx+8] = sample; _dl[_dl_idx+8] = sample;
_dl_idx = (_dl_idx + 1) % 8; _dl_idx = (_dl_idx + 1) % 8;
} }
inline void _errorTracking(const std::complex<float> &sample) { 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; _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; _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> x = (_c_0T - _c_2T) * std::conj(_p_1T);
std::complex<float> y = (_p_0T - _p_2T) * std::conj(_c_1T); std::complex<float> y = (_p_0T - _p_2T) * std::conj(_c_1T);
// Get phase error
float err = std::real(y-x); float err = std::real(y-x);
if (err > 1.0) { err = 1.0; } if (err > 1.0) { err = 1.0; }
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; _omega += _gain_omega * err;
if ( (_omega-_omega_mid) > _omega_rel) { _omega = _omega_mid + _omega_rel; } // Limit omega on _omega_mid +/- _omega*_omega_rel
else if ( (_omega-_omega_mid) < -_omega_rel) { _omega = _omega_mid - _omega_rel; } _omega = std::max(_min_omega, std::min(_max_omega, _omega));
else { _omega = _omega_mid + _omega-_omega_mid; } // Update mu as +_omega (samples per symbol) and a small correction term
_mu += _omega + _gain_mu * err; _mu += _omega + _gain_mu * err;
} }
protected: protected:
// Carrier PLL stuff /** Holds the number of phase constellations per bit. */
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
size_t _superSample; 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; Buffer<float> _hist;
/** Current phase history index. */
size_t _hist_idx; size_t _hist_idx;
/** The last output constellation. */
int _last_constellation; int _last_constellation;
// Output buffer /** Output buffer. */
Buffer<uint8_t> _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 class Varicode: public Sink<uint8_t>, public Source
{ {
public: public:
/** Constructor. */
Varicode(); Varicode();
/** Destructor. */
virtual ~Varicode(); virtual ~Varicode();
/** Configures the node. */
virtual void config(const Config &src_cfg); 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); virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
protected: protected:
/** The shift register of the last received bits. */
uint16_t _value; uint16_t _value;
/** The output buffer. */
Buffer<uint8_t> _buffer; Buffer<uint8_t> _buffer;
/** The conversion table. */
std::map<uint16_t, char> _code_table; std::map<uint16_t, char> _code_table;
}; };
} }

Loading…
Cancel
Save