Implemented POCSAG decoder.

master
Hannes Matuschek 11 years ago
parent 0b7bc758d5
commit 0ad4ec8634

@ -14,6 +14,9 @@ IF(SDR_WITH_PORTAUDIO)
add_executable(sdr_rtty sdr_rtty.cc) add_executable(sdr_rtty sdr_rtty.cc)
target_link_libraries(sdr_rtty ${LIBS} libsdr) target_link_libraries(sdr_rtty ${LIBS} libsdr)
add_executable(sdr_pocsag sdr_pocsag.cc)
target_link_libraries(sdr_pocsag ${LIBS} libsdr)
ENDIF(SDR_WITH_PORTAUDIO) ENDIF(SDR_WITH_PORTAUDIO)

@ -22,14 +22,14 @@ int main(int argc, char *argv[]) {
if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; } if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; }
AutoCast< int16_t > cast; AutoCast< int16_t > cast;
AFSK demod(1200, 1200, 2200, AFSK::TRANSITION); FSKDetector demod(1200, 1200, 2200);
AX25 decode; BitStream bits(1200, BitStream::TRANSITION);
TextDump dump; AX25 decode;
src.connect(&cast); src.connect(&cast);
cast.connect(&demod); cast.connect(&demod);
demod.connect(&decode); demod.connect(&bits);
decode.connect(&dump); bits.connect(&decode);
Queue::get().addIdle(&src, &WavSource::next); Queue::get().addIdle(&src, &WavSource::next);
src.addEOS(&queue, &Queue::stop); src.addEOS(&queue, &Queue::stop);

@ -0,0 +1,71 @@
#include "autocast.hh"
#include "portaudio.hh"
#include "wavfile.hh"
#include "afsk.hh"
#include "utils.hh"
#include "pocsag.hh"
#include <iostream>
#include <cmath>
#include <csignal>
using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
Queue::get().stop();
}
int main(int argc, char *argv[])
{
if (2 != argc) {
std::cerr << "Usage: sdr_posag FILENAME" << std::endl;
return -1;
}
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register handler:
signal(SIGINT, __sigint_handler);
PortAudio::init();
Queue &queue = Queue::get();
WavSource src(argv[1]);
PortSink sink;
AutoCast<int16_t> cast;
ASKDetector<int16_t> detector;
BitStream bits(1200, BitStream::NORMAL);
POCSAG pocsag;
//BitDump dump;
// Playback
//src.connect(&sink);
// Cast to int16
src.connect(&cast);
// ASK detector
cast.connect(&detector);
detector.connect(&bits);
// Baudot decoder
// dump to std::cerr
bits.connect(&pocsag);
// on idle -> read next buffer from input file
queue.addIdle(&src, &WavSource::next);
// on end-of-file -> stop queue
src.addEOS(&queue, &Queue::stop);
// Start queue
queue.start();
// wait for queue to exit
queue.wait();
// terminate port audio system properly
PortAudio::terminate();
// quit.
return 0;
}

@ -37,7 +37,8 @@ int main(int argc, char *argv[])
WavSource src(argv[1]); WavSource src(argv[1]);
PortSink sink; PortSink sink;
AutoCast<int16_t> cast; AutoCast<int16_t> cast;
AFSK fsk(90.90, 930., 1100., AFSK::NORMAL); FSKDetector fsk(90.90, 930., 1100.);
BitStream bits(90.90, BitStream::NORMAL);
Baudot decoder; Baudot decoder;
TextDump dump; TextDump dump;
@ -47,8 +48,9 @@ int main(int argc, char *argv[])
src.connect(&cast); src.connect(&cast);
// FSK demod // FSK demod
cast.connect(&fsk); cast.connect(&fsk);
fsk.connect(&bits);
// Baudot decoder // Baudot decoder
fsk.connect(&decoder); bits.connect(&decoder);
// dump to std::cerr // dump to std::cerr
decoder.connect(&dump); decoder.connect(&dump);

@ -2,13 +2,13 @@
set(LIBSDR_SOURCES set(LIBSDR_SOURCES
buffer.cc node.cc queue.cc traits.cc buffer.cc node.cc queue.cc traits.cc
portaudio.cc utils.cc wavfile.cc portaudio.cc utils.cc wavfile.cc
exception.cc logger.cc psk31.cc options.cc afsk.cc ax25.cc baudot.cc) exception.cc logger.cc psk31.cc options.cc afsk.cc ax25.cc baudot.cc pocsag.cc bch31_21.cc)
set(LIBSDR_HEADERS sdr.hh math.hh set(LIBSDR_HEADERS sdr.hh math.hh
buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh
siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh
fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh
combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh afsk.hh ax25.hh combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh afsk.hh ax25.hh
baudot.hh) baudot.hh pocsag.hh bch31_21.hh)
if(SDR_WITH_PORTAUDIO) if(SDR_WITH_PORTAUDIO)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc)

@ -6,18 +6,14 @@
using namespace sdr; using namespace sdr;
AFSK::AFSK(double baud, double Fmark, double Fspace, Mode mode) FSKDetector::FSKDetector(float baud, float Fmark, float Fspace)
: Sink<int16_t>(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace), _mode(mode) : Sink<int16_t>(), Source(), _baud(baud), _corrLen(0), _Fmark(Fmark), _Fspace(Fspace)
{ {
// pass... // pass...
} }
AFSK::~AFSK() {
// pass...
}
void void
AFSK::config(const Config &src_cfg) FSKDetector::config(const Config &src_cfg)
{ {
// Check if config is complete // Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
@ -25,144 +21,175 @@ AFSK::config(const Config &src_cfg)
// Check if buffer type matches // Check if buffer type matches
if (Config::typeId<int16_t>() != src_cfg.type()) { if (Config::typeId<int16_t>() != src_cfg.type()) {
ConfigError err; ConfigError err;
err << "Can not configure AFSK1200: Invalid type " << src_cfg.type() err << "Can not configure FSKBase: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>(); << ", expected " << Config::typeId<int16_t>();
throw err; throw err;
} }
// The input sample rate _corrLen = int(src_cfg.sampleRate()/_baud);
_sampleRate = src_cfg.sampleRate(); _markLUT = Buffer< std::complex<float> >(_corrLen);
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
// Samples per bit _markHist = Buffer< std::complex<float> >(_corrLen);
_corrLen = int(_sampleRate/_baud);
// Compute symbol rate:
_symbolRate = std::max(_baud, _baud*_corrLen);
// Samples per symbol (fractional):
_muIncr = _sampleRate/_symbolRate;
_mu = _muIncr;
// Delayline for interpolating sub-sampler
_dl = Buffer<float>(2*8);
for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; }
_dl_idx = 0;
// Assemble phase LUT:
_markLUT = Buffer< std::complex<float> >(_corrLen);
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
// Allocate ring-buffers for mark and space symbols
_markHist = Buffer< std::complex<float> >(_corrLen);
_spaceHist = Buffer< std::complex<float> >(_corrLen); _spaceHist = Buffer< std::complex<float> >(_corrLen);
_symbols = Buffer<int16_t>(_corrLen);
// Initialize LUTs and ring-buffers // Initialize LUTs and ring-buffers
double phiMark=0, phiSpace=0; double phiMark=0, phiSpace=0;
for (size_t i=0; i<_corrLen; i++) { for (size_t i=0; i<_corrLen; i++) {
_markLUT[i] = std::exp(std::complex<float>(0.0, phiMark)); _markLUT[i] = std::exp(std::complex<float>(0.0, phiMark));
_spaceLUT[i] = std::exp(std::complex<float>(0.0, phiSpace)); _spaceLUT[i] = std::exp(std::complex<float>(0.0, phiSpace));
phiMark += (2.*M_PI*_Fmark)/_sampleRate; phiMark += (2.*M_PI*_Fmark)/src_cfg.sampleRate();
phiSpace += (2.*M_PI*_Fspace)/_sampleRate; phiSpace += (2.*M_PI*_Fspace)/src_cfg.sampleRate();
// Apply Window functions // Apply Window functions
//_markLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen)); //_markLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen));
//_spaceLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen)); //_spaceLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen));
_markHist[i] = 0; _spaceHist[i] = 0; _symbols[i] = 0; _markHist[i] = 0; _spaceHist[i] = 0;
} }
// Ring buffer indices // Ring buffer index
_lutIdx = 0; _symbolIdx = 0; _lutIdx = 0;
// Get phase increment per symbol // Allocate output buffer
_phase = 0; _omega = _baud/_symbolRate; _buffer = Buffer<int8_t>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Config FSKDetector node: " << std::endl
<< " sample/symbol rate: " << src_cfg.sampleRate() << " Hz" << std::endl
<< " target baud rate: " << _baud << std::endl
<< " approx. samples per bit: " << _corrLen;
Logger::get().log(msg);
// Forward config.
this->setConfig(Config(Traits<uint8_t>::scalarId, src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
}
uint8_t
FSKDetector::_process(int16_t sample) {
_markHist[_lutIdx] = float(sample)*_markLUT[_lutIdx];
_spaceHist[_lutIdx] = float(sample)*_spaceLUT[_lutIdx];
// inc _lutIdx, modulo LUT length
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
std::complex<float> markSum(0), spaceSum(0);
for (size_t i=0; i<_corrLen; i++) {
markSum += _markHist[i];
spaceSum += _spaceHist[i];
}
float f = markSum.real()*markSum.real() +
markSum.imag()*markSum.imag() -
spaceSum.real()*spaceSum.real() -
spaceSum.imag()*spaceSum.imag();
return (f>0);
}
void
FSKDetector::process(const Buffer<int16_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = _process(buffer[i]);
}
this->send(_buffer.head(buffer.size()), false);
}
BitStream::BitStream(float baud, Mode mode)
: Sink<uint8_t>(), Source(), _baud(baud), _mode(mode), _corrLen(0)
{
// pass...
}
void
BitStream::config(const Config &src_cfg) {
// Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure FSKBitStreamBase: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
// # of symbols for each bit
_corrLen = int(src_cfg.sampleRate()/_baud);
// Config PLL for bit detection
_phase = 0;
// exact bits per sample (<< 1)
_omega = _baud/src_cfg.sampleRate();
// PLL limits +/- 10% around _omega // PLL limits +/- 10% around _omega
_omegaMin = _omega - 0.005*_omega; _omegaMin = _omega - 0.005*_omega;
_omegaMax = _omega + 0.005*_omega; _omegaMax = _omega + 0.005*_omega;
// PLL gain // PLL gain
_gainOmega = 0.0005; _pllGain = 0.0005;
_symSum = 0; // symbol moving average
_lastSymSum = 0; _symbols = Buffer<int8_t>(_corrLen);
for (size_t i=0; i<_corrLen; i++) { _symbols[i] = 0; }
_symIdx = 0; _symSum = 0; _lastSymSum = 0;
// Allocate output buffer: // Reset bit hist
_buffer = Buffer<uint8_t>(src_cfg.bufferSize()/_corrLen + 1); _lastBits = 0;
// Allocate output buffer
_buffer = Buffer<uint8_t>(1+src_cfg.bufferSize()/_corrLen);
LogMessage msg(LOG_DEBUG); LogMessage msg(LOG_DEBUG);
msg << "Config AFSK node: " << std::endl msg << "Config FSKBitStreamBase node: " << std::endl
<< " input sample rate: " << _sampleRate << " Hz" << std::endl << " input sample rate: " << src_cfg.sampleRate() << " Hz" << std::endl
<< " samples per symbol: " << _muIncr << std::endl << " baud rate: " << _baud << std::endl
<< " symbols per bit: " << _corrLen << std::endl << " samples per bit: " << 1./_omega << std::endl
<< " symbol rate: " << _symbolRate << " Hz" << std::endl << " phase incr/symbol: " << _omega;
<< " bit rate: " << _symbolRate/_corrLen << " baud" << std::endl
<< " phase incr/symbol: " << float(_omega) << std::endl
<< " bit mode: " << ((TRANSITION == _mode) ? "transition" : "normal");
Logger::get().log(msg); Logger::get().log(msg);
// Forward config. // Forward config.
this->setConfig(Config(Traits<uint8_t>::scalarId, _baud, _buffer.size(), 1)); this->setConfig(Config(Traits<uint8_t>::scalarId, _baud, _buffer.size(), 1));
} }
void void
AFSK::process(const Buffer<int16_t> &buffer, bool allow_overwrite) BitStream::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{ {
size_t i=0, o=0; size_t o=0;
while (i<buffer.size()) { for (size_t i=0; i<buffer.size(); i++)
// Update sub-sampler {
while ((_mu>=1) && (i<buffer.size())) { // store symbol & update _symSum and _lastSymSum
_markHist[_lutIdx] = float(buffer[i])*_markLUT[_lutIdx];
_spaceHist[_lutIdx] = float(buffer[i])*_spaceLUT[_lutIdx];
// inc _lutIdx, modulo LUT length
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
// Get symbol from FIR filter results
float symbol = _getSymbol();
// Put symbol into delay line
_dl[_dl_idx] = symbol; _dl[_dl_idx+8] = symbol;
_dl_idx = (_dl_idx+1)%8; _mu -= 1; i++;
}
if (_mu >= 1) { continue; }
// Get interpolated symbol
float symbol = interpolate(_dl.sub(_dl_idx, 8), _mu); _mu += _muIncr;
// store symbol
_lastSymSum = _symSum; _lastSymSum = _symSum;
_symSum -= _symbols[_symbolIdx]; _symSum -= _symbols[_symIdx];
_symbols[_symbolIdx] = ( (symbol>=0) ? 1 : -1 ); _symbols[_symIdx] = ( buffer[i] ? 1 : -1 );
_symSum += _symbols[_symbolIdx]; _symSum += _symbols[_symIdx];
_symbolIdx = ((_symbolIdx+1) % _corrLen); _symIdx = ((_symIdx+1) % _corrLen);
// Advance phase // Advance phase
_phase += _omega; _phase += _omega;
// Sample bit // Sample bit ...
if (_phase >= 1) { if (_phase >= 1) {
// Modulo "2 pi", phase is defined on the interval [0,1) // Modulo "2 pi", phase is defined on the interval [0,1)
while (_phase>=1) { _phase -= 1; } while (_phase>=1) { _phase -= 1; }
// Estimate bit by majority vote on all symbols // Estimate bit by majority vote on all symbols (_symSum)
_lastBits = ((_lastBits<<1) | (_symSum>0)); _lastBits = ((_lastBits<<1) | (_symSum>0));
// Put decoded bit in output buffer // Put decoded bit in output buffer
if (TRANSITION == _mode) { if (TRANSITION == _mode) {
// transition -> 0; no transition -> 1 // transition -> 0; no transition -> 1
_buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1); _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 0x1) & 0x1);
} else { } else {
// mark -> 1, space -> 0 // mark -> 1, space -> 0
_buffer[o++] = _lastBits & 1; _buffer[o++] = _lastBits & 0x1;
} }
} }
// If there was a symbol transition // If there was a symbol transition
if (((_lastSymSum < 0) && (_symSum>=0)) || ((_lastSymSum >= 0) && (_symSum<0))) { if (((_lastSymSum < 0) && (_symSum>=0)) || ((_lastSymSum >= 0) && (_symSum<0))) {
// Phase correction // Phase correction
/**std::cerr << "Transition at phi=" << _phase << std::endl
<< " update omega from " << _omega << " to "; */
// transition at [-pi,0] -> increase omega // transition at [-pi,0] -> increase omega
if (_phase < 0.5) { _omega += _gainOmega*(0.5-_phase); } if (_phase < 0.5) { _omega += _pllGain*(0.5-_phase); }
// transition at [0,pi] -> decrease omega // transition at [0,pi] -> decrease omega
else { _omega -= _gainOmega*(_phase-0.5); } else { _omega -= _pllGain*(_phase-0.5); }
// Limit omega // Limit omega
_omega = std::min(_omegaMax, std::max(_omegaMin, _omega)); _omega = std::min(_omegaMax, std::max(_omegaMin, _omega));
//_phase += _gainOmega*(_phase-0.5);
/* std::cerr << _omega << std::endl; */
} }
} }
if (0 < o) { this->send(_buffer.head(o)); } if (o>0) { this->send(_buffer.head(o)); }
} }

@ -2,15 +2,95 @@
#define __SDR_AFSK_HH__ #define __SDR_AFSK_HH__
#include "node.hh" #include "node.hh"
#include "traits.hh"
#include "logger.hh"
namespace sdr { namespace sdr {
/** A simple (Audio) Frequency Shift Keying (AFSK) demodulator. /** Implements the basic FSK/AFSK symbol detection.
* This node consists of two convolution peak-filters at the mark and space frequencies, a * @ingroup demods */
* interpolating sub-sampler to match the baud-rate exactly and a PLL to lock to the symbol class FSKDetector: public Sink<int16_t>, public Source
* transitions. The node will decode the (A)FSK signal and will send a bit-stream (uint8_t). {
* @ingroup demodulator */ public:
class AFSK: public Sink<int16_t>, public Source FSKDetector(float baud, float Fmark, float Fspace);
void config(const Config &src_cfg);
void process(const Buffer<int16_t> &buffer, bool allow_overwrite);
protected:
/** Updates the mark/space FIR filter and returns the sampled symbol. */
uint8_t _process(int16_t sample);
protected:
float _baud;
size_t _corrLen;
/** The current FIR filter LUT index. */
size_t _lutIdx;
float _Fmark;
float _Fspace;
/** Mark frequency FIR filter LUT. */
Buffer< std::complex<float> > _markLUT;
/** Space frequency FIR filter LUT. */
Buffer< std::complex<float> > _spaceLUT;
/** FIR filter buffer. */
Buffer< std::complex<float> > _markHist;
/** FIR filter buffer. */
Buffer< std::complex<float> > _spaceHist;
/** Output buffer. */
Buffer<int8_t> _buffer;
};
template <class Scalar>
class ASKDetector: public Sink<Scalar>, public Source
{
public:
ASKDetector()
: Sink<Scalar>(), Source()
{
// pass...
}
void config(const Config &src_cfg) {
// Check if config is complete
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
// Check if buffer type matches
if (Config::typeId<int16_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure ASKDetector: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<int16_t>();
throw err;
}
// Allocate output buffer
_buffer = Buffer<uint8_t>(src_cfg.bufferSize());
LogMessage msg(LOG_DEBUG);
msg << "Config ASKDetector node: " << std::endl
<< " detection threshold: " << 0 << std::endl
<< " sample/symbol rate: " << src_cfg.sampleRate() << " Hz";
Logger::get().log(msg);
// Forward config.
this->setConfig(Config(Traits<uint8_t>::scalarId, src_cfg.sampleRate(), _buffer.size(), 1));
}
void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
_buffer[i] = (buffer[i]>0);
}
this->send(_buffer.head(buffer.size()), false);
}
protected:
Buffer<uint8_t> _buffer;
};
/** Decodes a bitstream with the desired baud rate. */
class BitStream: public Sink<uint8_t>, public Source
{ {
public: public:
/** Possible bit decoding modes. */ /** Possible bit decoding modes. */
@ -20,96 +100,45 @@ public:
} Mode; } Mode;
public: public:
/** Constructs a AFSK node with the specified @c baud rate and @c Fmark, @c Fspace frequencies. BitStream(float baud, Mode mode = TRANSITION);
* The default valuse corresponds to those used for 1200 baud packet radio. */
AFSK(double baud=1200.0, double Fmark=1200.0, double Fspace=2200.0,
Mode mode=TRANSITION);
/** Destructor. */
virtual ~AFSK();
/** Configures the node. */ void config(const Config &src_cfg);
virtual void config(const Config &src_cfg); void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
/** Processes the given buffer. */
virtual void process(const Buffer<int16_t> &buffer, bool allow_overwrite);
protected: protected:
/** Performs the convolution filtering of the mark & space frequencies. */
inline double _getSymbol() {
std::complex<float> markSum(0), spaceSum(0);
for (size_t i=0; i<_corrLen; i++) {
markSum += _markHist[i];
spaceSum += _spaceHist[i];
}
double f = markSum.real()*markSum.real() +
markSum.imag()*markSum.imag() -
spaceSum.real()*spaceSum.real() -
spaceSum.imag()*spaceSum.imag();
return f;
}
protected:
/** The sample rate of the input signal. */
float _sampleRate;
/** A multiple of the baud rate. */
float _symbolRate;
/** The baud rate. */
float _baud; float _baud;
/** Mark "tone" frequency. */ Mode _mode;
float _Fmark; size_t _corrLen;
/** Space "tone" frequency. */
float _Fspace;
/** Bit encoding mode. */
Mode _mode;
/** Correlation length, the number of "symbols" per bit. */
uint32_t _corrLen;
/** The current FIR filter LUT index. */
uint32_t _lutIdx;
/** Mark frequency FIR filter LUT. */
Buffer< std::complex<float> > _markLUT;
/** Space frequency FIR filter LUT. */
Buffer< std::complex<float> > _spaceLUT;
/** FIR filter buffer. */ Buffer<int8_t> _symbols;
Buffer< std::complex<float> > _markHist; size_t _symIdx;
/** FIR filter buffer. */
Buffer< std::complex<float> > _spaceHist;
/** Symbol subsampling counter. */
float _mu;
/** Symbol subsampling. */
float _muIncr;
/** Delay line for the 8-pole interpolation filter. */
Buffer< float > _dl;
/** Delay line index. */
size_t _dl_idx;
/** Ring buffer of the last @c _corrLen symbols. */
Buffer<int16_t> _symbols;
/** Insertion index. */
size_t _symbolIdx;
/** Sum of the current @c _corrLen symbols. */
int32_t _symSum; int32_t _symSum;
/** Sum of the last @c _corrLen symbols. */
int32_t _lastSymSum; int32_t _lastSymSum;
/** Last received bits. */
uint32_t _lastBits;
/** Current PLL phase. */
float _phase; float _phase;
/** PLL phase speed. */
float _omega; float _omega;
/** Maximum PLL phase speed. */
float _omegaMin; float _omegaMin;
/** Minimum PLL phase speed. */
float _omegaMax; float _omegaMax;
/** PLL gain. */ float _pllGain;
float _gainOmega;
uint8_t _lastBits;
/** Output buffer. */
Buffer<uint8_t> _buffer; Buffer<uint8_t> _buffer;
}; };
class BitDump : public Sink<uint8_t>
{
public:
void config(const Config &src_cfg) {}
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
for (size_t i=0; i<buffer.size(); i++) {
std::cerr << int(buffer[i]) << " ";
}
std::cerr << std::endl;
}
};
} }
#endif // __SDR_AFSK_HH__ #endif // __SDR_AFSK_HH__

@ -16,8 +16,7 @@ namespace sdr {
* The node does not process the actual AX.25 packages, it only checks the frame check sequence and * The node does not process the actual AX.25 packages, it only checks the frame check sequence and
* forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible * forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible
* for unpacking and handling the received datagram. * for unpacking and handling the received datagram.
* * @ingroup datanodes */
* @ingroup datanode */
class AX25: public Sink<uint8_t>, public Source class AX25: public Sink<uint8_t>, public Source
{ {
public: public:

@ -0,0 +1,213 @@
#include "bch31_21.hh"
#include "stdlib.h"
#include "string.h"
using namespace sdr;
/*
* the code used by POCSAG is a (n=31,k=21) BCH Code with dmin=5,
* thus it could correct two bit errors in a 31-Bit codeword.
* It is a systematic code.
* The generator polynomial is:
* g(x) = x^10+x^9+x^8+x^6+x^5+x^3+1
* The parity check polynomial is:
* h(x) = x^21+x^20+x^18+x^16+x^14+x^13+x^12+x^11+x^8+x^5+x^3+1
* g(x) * h(x) = x^n+1
*/
#define BCH_POLY 03551 /* octal */
#define BCH_N 31
#define BCH_K 21
static inline unsigned char even_parity(uint32_t data)
{
unsigned int temp = data ^ (data >> 16);
temp = temp ^ (temp >> 8);
temp = temp ^ (temp >> 4);
temp = temp ^ (temp >> 2);
temp = temp ^ (temp >> 1);
return temp & 1;
}
static unsigned int
pocsag_syndrome(uint32_t data)
{
uint32_t shreg = data >> 1; /* throw away parity bit */
uint32_t mask = 1L << (BCH_N-1), coeff = BCH_POLY << (BCH_K-1);
int n = BCH_K;
for(; n > 0; mask >>= 1, coeff >>= 1, n--) {
if (shreg & mask) { shreg ^= coeff; }
}
if (even_parity(data)) {
shreg |= (1 << (BCH_N - BCH_K));
}
return shreg;
}
static void
bitslice_syndrome(uint32_t *slices)
{
const int firstBit = BCH_N - 1;
int i, n;
uint32_t paritymask = slices[0];
// do the parity and shift together
for (i = 1; i < 32; ++i) {
paritymask ^= slices[i];
slices[i-1] = slices[i];
}
slices[31] = 0;
// BCH_POLY << (BCH_K - 1) is
// 20 21 22 23
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ONE, 0, 0, ONE,
// 24 25 26 27 28 29 30 31
// 0, ONE, ONE, 0, ONE, ONE, ONE, 0
for (n = 0; n < BCH_K; ++n) {
// one line here for every '1' bit in coeff (above)
const int bit = firstBit - n;
slices[20 - n] ^= slices[bit];
slices[23 - n] ^= slices[bit];
slices[25 - n] ^= slices[bit];
slices[26 - n] ^= slices[bit];
slices[28 - n] ^= slices[bit];
slices[29 - n] ^= slices[bit];
slices[30 - n] ^= slices[bit];
slices[31 - n] ^= slices[bit];
}
// apply the parity mask we built up
slices[BCH_N - BCH_K] |= paritymask;
}
static uint32_t
transpose_n(int n, uint32_t *matrix)
{
uint32_t out = 0;
int j;
for (j = 0; j < 32; ++j) {
if (matrix[j] & (1<<n)) {
out |= (1<<j);
}
}
return out;
}
static uint32_t *
transpose_clone(uint32_t src, uint32_t *out)
{
int i;
if (!out) { out = (uint32_t *)malloc(sizeof(uint32_t)*32); }
for (i = 0; i < 32; ++i) {
if (src & (1<<i)) {
out[i] = 0xffffffff;
} else {
out[i] = 0;
}
}
return out;
}
// This might not be elegant, yet effective!
// Error correction via bruteforce ;)
//
// It's a pragmatic solution since this was much faster to implement
// than understanding the math to solve it while being as effective.
// Besides that the overhead is neglectable.
int
sdr::pocsag_repair(uint32_t &data)
{
// Check if data is correct
if (0 == pocsag_syndrome(data)) { return 0; }
int i, n, b1, b2;
uint32_t res;
uint32_t *xpose = 0, *in = 0;
// check for single bit errors
xpose = (uint32_t *) malloc(sizeof(uint32_t)*32);
in = (uint32_t *) malloc(sizeof(uint32_t)*32);
transpose_clone(data, xpose);
for (i = 0; i < 32; ++i) { xpose[i] ^= (1<<i); }
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
res = ~res;
if (res) {
int n = 0;
while (res) { ++n; res >>= 1; }
--n;
data ^= (1<<n);
goto returnfree;
}
//check for two bit errors
n = 0;
transpose_clone(data, xpose);
for (b1 = 0; b1 < 32; ++b1) {
for (b2 = b1; b2 < 32; ++b2) {
xpose[b1] ^= (1<<n);
xpose[b2] ^= (1<<n);
if (++n == 32) {
memcpy(in, xpose, sizeof(uint32_t)*32);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
res = ~res;
if (res) {
int n = 0;
while (res) { ++n; res >>= 1; }
--n;
data = transpose_n(n, in);
goto returnfree;
}
transpose_clone(data, xpose);
n = 0;
}
}
}
if (n > 0) {
memcpy(in, xpose, sizeof(uint32_t)*32);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
res = ~res;
if (res) {
int n = 0;
while (res) { ++n; res >>= 1; }
--n;
data = transpose_n(n, in);
goto returnfree;
}
}
if (xpose) { free(xpose); }
if (in) { free(in); }
return 1;
returnfree:
if (xpose)
free(xpose);
if (in)
free(in);
return 0;
}

@ -0,0 +1,14 @@
#ifndef __SDR_BCH31_21_HH__
#define __SDR_BCH31_21_HH__
#include <cinttypes>
namespace sdr {
/** Checks and repairs a POCSAG message with its
* BCH(31,21) ECC. */
int pocsag_repair(uint32_t &data);
}
#endif // __SDR_BCH31_21_HH__

@ -0,0 +1,195 @@
#include "pocsag.hh"
#include "bch31_21.hh"
using namespace sdr;
inline bool is_address(uint32_t word) {
return (0 == (0x80000000 & word));
}
POCSAG::POCSAG()
: Sink<uint8_t>()
{
// pass...
}
void
POCSAG::config(const Config &src_cfg) {
if (! src_cfg.hasType()) { return; }
// Check if buffer type matches
if (Config::typeId<uint8_t>() != src_cfg.type()) {
ConfigError err;
err << "Can not configure POCSAG: Invalid type " << src_cfg.type()
<< ", expected " << Config::typeId<uint8_t>();
throw err;
}
_state = WAIT;
_bits = 0;
}
void
POCSAG::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
{
for (size_t i=0; i<buffer.size(); i++)
{
// put bit into shift register
_bits = ( (_bits<<1) | (buffer[i] & 0x01) );
// Dispatch by state
if (WAIT == _state) {
// Wait for the sync word to appear
uint32_t word = (_bits & 0xffffffff);
if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) {
// init messages
_reset_all_messages();
_state = RECEIVE; _bitcount = 0; _slot = 0;
}
} else if (RECEIVE == _state) {
// Receive 64 bit (2 words)
_bitcount++;
if (64 == _bitcount) {
_bitcount=0;
// get and check 1st word bits
uint32_t word = ( (_bits>>32) & 0xffffffff );
if (0 == pocsag_repair(word)) { _process_word(word); }
// get and check 2nd word bits
word = ( _bits & 0xffffffff );
if (0 == pocsag_repair(word)) { _process_word(word); }
// Advance slot counter
_slot++;
if (8 == _slot) {
// If all slots (8) has been processed -> wait for continuation
_state = CHECK_CONTINUE;
}
}
} else if (CHECK_CONTINUE == _state) {
// Wait for an immediate sync word
_bitcount++;
if (32 == _bitcount) {
uint32_t word = (_bits&0xffffffff);
if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) {
// If a sync word has been received -> continue with reception of slot 0
_state = RECEIVE; _slot = 0; _bitcount = 0;
} else {
// Otherwise -> end of transmission, wait for next sync
_finish_all_messages(); _state = WAIT;
this->handleMessages();
}
}
}
}
}
void
POCSAG::_process_word(uint32_t word)
{
// Check if slot is skipped
if (0x7A89C197 == word) { // Skip slot
_finish_message(_slot); return;
}
// Check if word is an address word
if (is_address(word)) {
_finish_message(_slot);
// Assemble address
uint32_t addr = (((word>>13) & 0x03ffff)<<3) | (_slot & 0x03);
uint8_t func = ((word>>11) & 0x03);
_messages[_slot] = Message(addr, func);
} else {
if (_messages[_slot].isEmpty()) {
std::cerr << "Oops: Payload w/o address in slot " << int(_slot)
<< " word: " << std::hex << word << std::dec << std::endl;
}
_messages[_slot].addPayload(word);
}
}
void
POCSAG::_reset_message(uint8_t slot) {
_messages[slot] = Message();
}
void
POCSAG::_reset_all_messages() {
for (uint8_t i=0; i<8; i++) {
_reset_message(i);
}
}
void
POCSAG::_finish_message(uint8_t slot) {
if (_messages[slot].isEmpty()) { return; }
_queue.push_back(_messages[slot]);
_reset_message(slot);
}
void
POCSAG::_finish_all_messages() {
for (uint8_t i=0; i<8; i++) {
_finish_message(i);
}
}
void
POCSAG::handleMessages() {
// You may re-implement this virutal method to process the queued messages in _queue.
while (_queue.size()) {
Message msg = _queue.back(); _queue.pop_back();
std::cerr << "POCSAG: @" << msg.address()
<< ", F=" << int(msg.function())
<< ", bits=" << msg.bits() << std::endl;
}
}
POCSAG::Message::Message()
: _address(0), _function(0), _empty(true), _bits(0)
{
// pass...
}
POCSAG::Message::Message(uint32_t addr, uint8_t func)
: _address(addr), _function(func), _empty(false), _bits(0)
{
// pass...
}
POCSAG::Message::Message(const Message &other)
: _address(other._address), _function(other._function), _empty(other._empty),
_bits(other._bits), _payload(other._payload)
{
// pass...
}
POCSAG::Message &
POCSAG::Message::operator =(const Message &other) {
_address = other._address;
_function = other._function;
_empty = other._empty;
_bits = other._bits;
_payload = other._payload;
return *this;
}
void
POCSAG::Message::addPayload(uint32_t word) {
// Add 20 LSB bits from data to payload vector
uint32_t mask = 0x40000000;
for (size_t i=0; i<20; i++) {
// on new byte
if (0 == (_bits % 8)) { _payload.push_back(0x00); }
// add bit to last byte of payload
_payload.back() = ((_payload.back()<<1)|(word&mask));
// Increment bit counter and update mask
_bits++; mask = (mask>>1);
}
}

@ -0,0 +1,105 @@
#ifndef __SDR_POSAG_HH__
#define __SDR_POSAG_HH__
#include "node.hh"
namespace sdr {
/** Implements a POCSAG decoder.
* In conjecture with the @c FSKDetector or @c AFSDetector and the @c BitStream nodes, this node
* can be used to receive and process POCSAG (pages) messages.
*
* The POCSAG protocol is defined as followig:
*
* 1) at least 576 bits of alternating value (1 0 1 0 ...)
* 2) a 32-bit sync word (0x7CD215D8)
* 3) 16 data words (each 32 bit)
*
* Unused data words are send as 0x7A89C197. Each dataword is either a address word (bit 31 = 0)
* or message word (bit 31 = 1).
*
* Address word:
*
* +---+---+---+---+---+---+---+---+
* | 0 | Address (18bits) |
* +---+---+---+---+---+---+---+---+
* | ... |
* +---+---+---+---+---+---+---+---+
* | ... | F1 F0 | ECC ... |
* +---+---+---+---+---+---+---+---+
* | ... (10 bits) | P |
* +---+---+---+---+---+---+---+---+
*
* Message word
*
* +---+---+---+---+---+---+---+---+
* | 0 | Data (20bits) |
* +---+---+---+---+---+---+---+---+
* | ... |
* +---+---+---+---+---+---+---+---+
* | ... | ECC ... |
* +---+---+---+---+---+---+---+---+
* | ... (10 bits) | P |
* +---+---+---+---+---+---+---+---+
*
* @ingroup datanodes */
class POCSAG: public Sink<uint8_t>
{
public:
class Message {
public:
Message();
Message(uint32_t addr, uint8_t func);
Message(const Message &other);
Message &operator=(const Message &other);
inline bool isEmpty() const { return _empty; }
inline uint32_t address() const { return _address; }
inline uint8_t function() const { return _function; }
inline uint32_t bits() const { return _bits; }
void addPayload(uint32_t word);
protected:
uint32_t _address;
uint8_t _function;
bool _empty;
uint32_t _bits;
std::vector<uint8_t> _payload;
};
protected:
typedef enum {
WAIT,
RECEIVE,
CHECK_CONTINUE
} State;
public:
POCSAG();
void config(const Config &src_cfg);
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
virtual void handleMessages();
protected:
void _process_word(uint32_t word);
void _reset_all_messages();
void _reset_message(uint8_t slot);
void _finish_all_messages();
void _finish_message(uint8_t slot);
protected:
State _state;
uint64_t _bits;
uint8_t _bitcount;
uint8_t _slot;
Message _messages[8];
std::list<Message> _queue;
};
}
#endif // POSAG_HH

@ -12,7 +12,7 @@ namespace sdr {
* at least 2000Hz and produces a bitstream with 31.25 Hz "sample rate". Use the @c Varicode node * 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 * 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. * node uses a simple PLL to adjust for small detunings.
* @ingroup demod */ * @ingroup demods */
template <class Scalar> template <class Scalar>
class BPSK31: public Sink< std::complex<Scalar> >, public Source class BPSK31: public Sink< std::complex<Scalar> >, public Source
{ {

@ -150,7 +150,7 @@ WavSource::open(const std::string &filename)
LogMessage msg(LOG_DEBUG); LogMessage msg(LOG_DEBUG);
msg << "Configured WavSource:" << std::endl msg << "Configured WavSource:" << std::endl
<< " file: " << filename << std::endl << " file: " << filename << std::endl
<< " type:" << _type << std::endl << " type: " << _type << std::endl
<< " sample-rate: " << _sample_rate << std::endl << " sample-rate: " << _sample_rate << std::endl
<< " frame-count: " << _frame_count << std::endl << " frame-count: " << _frame_count << std::endl
<< " duration: " << _frame_count/_sample_rate << "s" << std::endl << " duration: " << _frame_count/_sample_rate << "s" << std::endl

Loading…
Cancel
Save