From 0ad4ec86348eda4af7328ab588bbebe9c1264744 Mon Sep 17 00:00:00 2001 From: Hannes Matuschek Date: Tue, 2 Jun 2015 11:55:22 +0200 Subject: [PATCH] Implemented POCSAG decoder. --- examples/CMakeLists.txt | 3 + examples/sdr_afsk1200.cc | 10 +- examples/sdr_pocsag.cc | 71 +++++++++++++ examples/sdr_rtty.cc | 6 +- src/CMakeLists.txt | 4 +- src/afsk.cc | 205 +++++++++++++++++++++---------------- src/afsk.hh | 187 +++++++++++++++++++--------------- src/ax25.hh | 3 +- src/bch31_21.cc | 213 +++++++++++++++++++++++++++++++++++++++ src/bch31_21.hh | 14 +++ src/pocsag.cc | 195 +++++++++++++++++++++++++++++++++++ src/pocsag.hh | 105 +++++++++++++++++++ src/psk31.hh | 2 +- src/wavfile.cc | 2 +- 14 files changed, 839 insertions(+), 181 deletions(-) create mode 100644 examples/sdr_pocsag.cc create mode 100644 src/bch31_21.cc create mode 100644 src/bch31_21.hh create mode 100644 src/pocsag.cc create mode 100644 src/pocsag.hh diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fabce14..f63c53f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,6 +14,9 @@ IF(SDR_WITH_PORTAUDIO) add_executable(sdr_rtty sdr_rtty.cc) 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) diff --git a/examples/sdr_afsk1200.cc b/examples/sdr_afsk1200.cc index fdc530a..4563c88 100644 --- a/examples/sdr_afsk1200.cc +++ b/examples/sdr_afsk1200.cc @@ -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; } AutoCast< int16_t > cast; - AFSK demod(1200, 1200, 2200, AFSK::TRANSITION); - AX25 decode; - TextDump dump; + FSKDetector demod(1200, 1200, 2200); + BitStream bits(1200, BitStream::TRANSITION); + AX25 decode; src.connect(&cast); cast.connect(&demod); - demod.connect(&decode); - decode.connect(&dump); + demod.connect(&bits); + bits.connect(&decode); Queue::get().addIdle(&src, &WavSource::next); src.addEOS(&queue, &Queue::stop); diff --git a/examples/sdr_pocsag.cc b/examples/sdr_pocsag.cc new file mode 100644 index 0000000..ddac8e2 --- /dev/null +++ b/examples/sdr_pocsag.cc @@ -0,0 +1,71 @@ +#include "autocast.hh" +#include "portaudio.hh" +#include "wavfile.hh" +#include "afsk.hh" +#include "utils.hh" +#include "pocsag.hh" + +#include +#include +#include + +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 cast; + ASKDetector 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; +} diff --git a/examples/sdr_rtty.cc b/examples/sdr_rtty.cc index 14fc8de..9c4a62c 100644 --- a/examples/sdr_rtty.cc +++ b/examples/sdr_rtty.cc @@ -37,7 +37,8 @@ int main(int argc, char *argv[]) WavSource src(argv[1]); PortSink sink; AutoCast cast; - AFSK fsk(90.90, 930., 1100., AFSK::NORMAL); + FSKDetector fsk(90.90, 930., 1100.); + BitStream bits(90.90, BitStream::NORMAL); Baudot decoder; TextDump dump; @@ -47,8 +48,9 @@ int main(int argc, char *argv[]) src.connect(&cast); // FSK demod cast.connect(&fsk); + fsk.connect(&bits); // Baudot decoder - fsk.connect(&decoder); + bits.connect(&decoder); // dump to std::cerr decoder.connect(&dump); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 41310aa..09841c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,13 +2,13 @@ set(LIBSDR_SOURCES buffer.cc node.cc queue.cc traits.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 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 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 - baudot.hh) + baudot.hh pocsag.hh bch31_21.hh) if(SDR_WITH_PORTAUDIO) set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) diff --git a/src/afsk.cc b/src/afsk.cc index dc8e6ab..f89d518 100644 --- a/src/afsk.cc +++ b/src/afsk.cc @@ -6,18 +6,14 @@ using namespace sdr; -AFSK::AFSK(double baud, double Fmark, double Fspace, Mode mode) - : Sink(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace), _mode(mode) +FSKDetector::FSKDetector(float baud, float Fmark, float Fspace) + : Sink(), Source(), _baud(baud), _corrLen(0), _Fmark(Fmark), _Fspace(Fspace) { // pass... } -AFSK::~AFSK() { - // pass... -} - void -AFSK::config(const Config &src_cfg) +FSKDetector::config(const Config &src_cfg) { // Check if config is complete if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } @@ -25,144 +21,175 @@ AFSK::config(const Config &src_cfg) // Check if buffer type matches if (Config::typeId() != src_cfg.type()) { 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(); throw err; } - // The input sample rate - _sampleRate = src_cfg.sampleRate(); - - // Samples per bit - _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(2*8); - for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; } - _dl_idx = 0; - - // Assemble phase LUT: - _markLUT = Buffer< std::complex >(_corrLen); - _spaceLUT = Buffer< std::complex >(_corrLen); - - // Allocate ring-buffers for mark and space symbols - _markHist = Buffer< std::complex >(_corrLen); + _corrLen = int(src_cfg.sampleRate()/_baud); + _markLUT = Buffer< std::complex >(_corrLen); + _spaceLUT = Buffer< std::complex >(_corrLen); + _markHist = Buffer< std::complex >(_corrLen); _spaceHist = Buffer< std::complex >(_corrLen); - _symbols = Buffer(_corrLen); // Initialize LUTs and ring-buffers double phiMark=0, phiSpace=0; for (size_t i=0; i<_corrLen; i++) { _markLUT[i] = std::exp(std::complex(0.0, phiMark)); _spaceLUT[i] = std::exp(std::complex(0.0, phiSpace)); - phiMark += (2.*M_PI*_Fmark)/_sampleRate; - phiSpace += (2.*M_PI*_Fspace)/_sampleRate; + phiMark += (2.*M_PI*_Fmark)/src_cfg.sampleRate(); + phiSpace += (2.*M_PI*_Fspace)/src_cfg.sampleRate(); // Apply Window functions //_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)); - _markHist[i] = 0; _spaceHist[i] = 0; _symbols[i] = 0; + _markHist[i] = 0; _spaceHist[i] = 0; } - // Ring buffer indices - _lutIdx = 0; _symbolIdx = 0; + // Ring buffer index + _lutIdx = 0; - // Get phase increment per symbol - _phase = 0; _omega = _baud/_symbolRate; + // Allocate output buffer + _buffer = Buffer(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::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 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 &buffer, bool allow_overwrite) { + for (size_t i=0; isend(_buffer.head(buffer.size()), false); +} + + +BitStream::BitStream(float baud, Mode mode) + : Sink(), 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() != src_cfg.type()) { + ConfigError err; + err << "Can not configure FSKBitStreamBase: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + 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 _omegaMin = _omega - 0.005*_omega; _omegaMax = _omega + 0.005*_omega; // PLL gain - _gainOmega = 0.0005; + _pllGain = 0.0005; - _symSum = 0; - _lastSymSum = 0; + // symbol moving average + _symbols = Buffer(_corrLen); + for (size_t i=0; i<_corrLen; i++) { _symbols[i] = 0; } + _symIdx = 0; _symSum = 0; _lastSymSum = 0; - // Allocate output buffer: - _buffer = Buffer(src_cfg.bufferSize()/_corrLen + 1); + // Reset bit hist + _lastBits = 0; + + // Allocate output buffer + _buffer = Buffer(1+src_cfg.bufferSize()/_corrLen); LogMessage msg(LOG_DEBUG); - msg << "Config AFSK node: " << std::endl - << " input sample rate: " << _sampleRate << " Hz" << std::endl - << " samples per symbol: " << _muIncr << std::endl - << " symbols per bit: " << _corrLen << std::endl - << " symbol rate: " << _symbolRate << " Hz" << std::endl - << " bit rate: " << _symbolRate/_corrLen << " baud" << std::endl - << " phase incr/symbol: " << float(_omega) << std::endl - << " bit mode: " << ((TRANSITION == _mode) ? "transition" : "normal"); + msg << "Config FSKBitStreamBase node: " << std::endl + << " input sample rate: " << src_cfg.sampleRate() << " Hz" << std::endl + << " baud rate: " << _baud << std::endl + << " samples per bit: " << 1./_omega << std::endl + << " phase incr/symbol: " << _omega; Logger::get().log(msg); // Forward config. this->setConfig(Config(Traits::scalarId, _baud, _buffer.size(), 1)); } - void -AFSK::process(const Buffer &buffer, bool allow_overwrite) +BitStream::process(const Buffer &buffer, bool allow_overwrite) { - size_t i=0, o=0; - while (i=1) && (i= 1) { continue; } - - // Get interpolated symbol - float symbol = interpolate(_dl.sub(_dl_idx, 8), _mu); _mu += _muIncr; - // store symbol + size_t o=0; + for (size_t i=0; i=0) ? 1 : -1 ); - _symSum += _symbols[_symbolIdx]; - _symbolIdx = ((_symbolIdx+1) % _corrLen); + _symSum -= _symbols[_symIdx]; + _symbols[_symIdx] = ( buffer[i] ? 1 : -1 ); + _symSum += _symbols[_symIdx]; + _symIdx = ((_symIdx+1) % _corrLen); + // Advance phase _phase += _omega; - // Sample bit + // Sample bit ... if (_phase >= 1) { // Modulo "2 pi", phase is defined on the interval [0,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)); // Put decoded bit in output buffer if (TRANSITION == _mode) { // transition -> 0; no transition -> 1 - _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1); + _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 0x1) & 0x1); } else { // mark -> 1, space -> 0 - _buffer[o++] = _lastBits & 1; + _buffer[o++] = _lastBits & 0x1; } } // If there was a symbol transition if (((_lastSymSum < 0) && (_symSum>=0)) || ((_lastSymSum >= 0) && (_symSum<0))) { // Phase correction - /**std::cerr << "Transition at phi=" << _phase << std::endl - << " update omega from " << _omega << " to "; */ // 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 - else { _omega -= _gainOmega*(_phase-0.5); } + else { _omega -= _pllGain*(_phase-0.5); } // Limit 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)); } } - diff --git a/src/afsk.hh b/src/afsk.hh index 92990cd..62cdc12 100644 --- a/src/afsk.hh +++ b/src/afsk.hh @@ -2,15 +2,95 @@ #define __SDR_AFSK_HH__ #include "node.hh" +#include "traits.hh" +#include "logger.hh" + namespace sdr { -/** A simple (Audio) Frequency Shift Keying (AFSK) demodulator. - * This node consists of two convolution peak-filters at the mark and space frequencies, a - * interpolating sub-sampler to match the baud-rate exactly and a PLL to lock to the symbol - * transitions. The node will decode the (A)FSK signal and will send a bit-stream (uint8_t). - * @ingroup demodulator */ -class AFSK: public Sink, public Source +/** Implements the basic FSK/AFSK symbol detection. + * @ingroup demods */ +class FSKDetector: public Sink, public Source +{ +public: + FSKDetector(float baud, float Fmark, float Fspace); + + void config(const Config &src_cfg); + void process(const Buffer &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 > _markLUT; + /** Space frequency FIR filter LUT. */ + Buffer< std::complex > _spaceLUT; + /** FIR filter buffer. */ + Buffer< std::complex > _markHist; + /** FIR filter buffer. */ + Buffer< std::complex > _spaceHist; + /** Output buffer. */ + Buffer _buffer; +}; + + +template +class ASKDetector: public Sink, public Source +{ +public: + ASKDetector() + : Sink(), 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() != src_cfg.type()) { + ConfigError err; + err << "Can not configure ASKDetector: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + throw err; + } + + // Allocate output buffer + _buffer = Buffer(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::scalarId, src_cfg.sampleRate(), _buffer.size(), 1)); + } + + void process(const Buffer &buffer, bool allow_overwrite) { + for (size_t i=0; i0); + } + this->send(_buffer.head(buffer.size()), false); + } + +protected: + Buffer _buffer; +}; + + +/** Decodes a bitstream with the desired baud rate. */ +class BitStream: public Sink, public Source { public: /** Possible bit decoding modes. */ @@ -20,96 +100,45 @@ public: } Mode; public: - /** Constructs a AFSK node with the specified @c baud rate and @c Fmark, @c Fspace frequencies. - * 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(); + BitStream(float baud, Mode mode = TRANSITION); - /** Configures the node. */ - virtual void config(const Config &src_cfg); - /** Processes the given buffer. */ - virtual void process(const Buffer &buffer, bool allow_overwrite); + void config(const Config &src_cfg); + void process(const Buffer &buffer, bool allow_overwrite); protected: - /** Performs the convolution filtering of the mark & space frequencies. */ - inline double _getSymbol() { - std::complex 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; - /** Mark "tone" frequency. */ - float _Fmark; - /** 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 > _markLUT; - /** Space frequency FIR filter LUT. */ - Buffer< std::complex > _spaceLUT; + Mode _mode; + size_t _corrLen; - /** FIR filter buffer. */ - Buffer< std::complex > _markHist; - /** FIR filter buffer. */ - Buffer< std::complex > _spaceHist; + Buffer _symbols; + size_t _symIdx; - /** 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 _symbols; - /** Insertion index. */ - size_t _symbolIdx; - /** Sum of the current @c _corrLen symbols. */ int32_t _symSum; - /** Sum of the last @c _corrLen symbols. */ int32_t _lastSymSum; - /** Last received bits. */ - uint32_t _lastBits; - /** Current PLL phase. */ float _phase; - /** PLL phase speed. */ float _omega; - /** Maximum PLL phase speed. */ float _omegaMin; - /** Minimum PLL phase speed. */ float _omegaMax; - /** PLL gain. */ - float _gainOmega; + float _pllGain; + + uint8_t _lastBits; - /** Output buffer. */ Buffer _buffer; }; +class BitDump : public Sink +{ +public: + void config(const Config &src_cfg) {} + void process(const Buffer &buffer, bool allow_overwrite) { + for (size_t i=0; i, public Source { public: diff --git a/src/bch31_21.cc b/src/bch31_21.cc new file mode 100644 index 0000000..8acdec8 --- /dev/null +++ b/src/bch31_21.cc @@ -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<>= 1; } + --n; + data ^= (1<>= 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; +} + diff --git a/src/bch31_21.hh b/src/bch31_21.hh new file mode 100644 index 0000000..4a81a0f --- /dev/null +++ b/src/bch31_21.hh @@ -0,0 +1,14 @@ +#ifndef __SDR_BCH31_21_HH__ +#define __SDR_BCH31_21_HH__ + +#include + +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__ diff --git a/src/pocsag.cc b/src/pocsag.cc new file mode 100644 index 0000000..b7f3e22 --- /dev/null +++ b/src/pocsag.cc @@ -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() +{ + // pass... +} + +void +POCSAG::config(const Config &src_cfg) { + if (! src_cfg.hasType()) { return; } + // Check if buffer type matches + if (Config::typeId() != src_cfg.type()) { + ConfigError err; + err << "Can not configure POCSAG: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + throw err; + } + + _state = WAIT; + _bits = 0; +} + +void +POCSAG::process(const Buffer &buffer, bool allow_overwrite) +{ + for (size_t i=0; i>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); + } +} + diff --git a/src/pocsag.hh b/src/pocsag.hh new file mode 100644 index 0000000..8def917 --- /dev/null +++ b/src/pocsag.hh @@ -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 +{ +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 _payload; + }; + +protected: + typedef enum { + WAIT, + RECEIVE, + CHECK_CONTINUE + } State; + +public: + POCSAG(); + + void config(const Config &src_cfg); + void process(const Buffer &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 _queue; +}; + +} + +#endif // POSAG_HH diff --git a/src/psk31.hh b/src/psk31.hh index 5b2c537..c8073bb 100644 --- a/src/psk31.hh +++ b/src/psk31.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 * 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. - * @ingroup demod */ + * @ingroup demods */ template class BPSK31: public Sink< std::complex >, public Source { diff --git a/src/wavfile.cc b/src/wavfile.cc index ad87d29..4dc5ff9 100644 --- a/src/wavfile.cc +++ b/src/wavfile.cc @@ -150,7 +150,7 @@ WavSource::open(const std::string &filename) LogMessage msg(LOG_DEBUG); msg << "Configured WavSource:" << std::endl << " file: " << filename << std::endl - << " type:" << _type << std::endl + << " type: " << _type << std::endl << " sample-rate: " << _sample_rate << std::endl << " frame-count: " << _frame_count << std::endl << " duration: " << _frame_count/_sample_rate << "s" << std::endl