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)
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)

@ -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);
FSKDetector demod(1200, 1200, 2200);
BitStream bits(1200, BitStream::TRANSITION);
AX25 decode;
TextDump dump;
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);

@ -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]);
PortSink sink;
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;
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);

@ -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)

@ -6,18 +6,14 @@
using namespace sdr;
AFSK::AFSK(double baud, double Fmark, double Fspace, Mode mode)
: Sink<int16_t>(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace), _mode(mode)
FSKDetector::FSKDetector(float baud, float Fmark, float Fspace)
: Sink<int16_t>(), 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<int16_t>() != 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<int16_t>();
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<float>(2*8);
for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; }
_dl_idx = 0;
// Assemble phase LUT:
_corrLen = int(src_cfg.sampleRate()/_baud);
_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);
_symbols = Buffer<int16_t>(_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<float>(0.0, phiMark));
_spaceLUT[i] = std::exp(std::complex<float>(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<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
_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<int8_t>(_corrLen);
for (size_t i=0; i<_corrLen; i++) { _symbols[i] = 0; }
_symIdx = 0; _symSum = 0; _lastSymSum = 0;
// Allocate output buffer:
_buffer = Buffer<uint8_t>(src_cfg.bufferSize()/_corrLen + 1);
// Reset bit hist
_lastBits = 0;
// Allocate output buffer
_buffer = Buffer<uint8_t>(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<uint8_t>::scalarId, _baud, _buffer.size(), 1));
}
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;
while (i<buffer.size()) {
// Update sub-sampler
while ((_mu>=1) && (i<buffer.size())) {
_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
size_t o=0;
for (size_t i=0; i<buffer.size(); i++)
{
// store symbol & update _symSum and _lastSymSum
_lastSymSum = _symSum;
_symSum -= _symbols[_symbolIdx];
_symbols[_symbolIdx] = ( (symbol>=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)); }
}

@ -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<int16_t>, public Source
/** Implements the basic FSK/AFSK symbol detection.
* @ingroup demods */
class FSKDetector: public Sink<int16_t>, public Source
{
public:
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:
/** 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<int16_t> &buffer, bool allow_overwrite);
void config(const Config &src_cfg);
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
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;
/** 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<float> > _markLUT;
/** Space frequency FIR filter LUT. */
Buffer< std::complex<float> > _spaceLUT;
size_t _corrLen;
/** FIR filter buffer. */
Buffer< std::complex<float> > _markHist;
/** FIR filter buffer. */
Buffer< std::complex<float> > _spaceHist;
Buffer<int8_t> _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<int16_t> _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<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__

@ -16,8 +16,7 @@ namespace sdr {
* 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
* for unpacking and handling the received datagram.
*
* @ingroup datanode */
* @ingroup datanodes */
class AX25: public Sink<uint8_t>, public Source
{
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
* 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 Scalar>
class BPSK31: public Sink< std::complex<Scalar> >, public Source
{

@ -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

Loading…
Cancel
Save