This repository contains the Physical and Coding Layer as defined in the CCSDS Layered model. More specifically, this implements:
- Pre-coded GMSK Modulation and Demodulation (CCSDS 413.0-G-3)
- GMSK Detector Algorithm (DSLWP-B GMSK detector)
- OQPSK Modulation (CCSDS 413.0-G-3)
- BCH Decoder (CCSDS 231.0-B-4)
- LDPC Encoder (CCSDS 130.1-G-3)
- Convolutional Encoder (CCSDS 130.1-G-3)
The CCSDS 231.0-B-4 book defines a (63,56) modified Bose-Chaudhuri-Hocquenghem (BCH) code, using the following generator polynomial:
The BCH codeword consists of three fields: 56 information bits, 7 parity bits, and 1 appended filler bit.
The (63,56) modified BCH code generator is shown in the image below.
During the transmission of information bits, the switch is set to position (1)
and during the transmission of parity bits, the switch is set to position (2)
.
Position (3)
is reserved for the filler bit.
The decoder is defined in inc/BCHDecoder.hpp
[[nodiscard]] etl::optional<uint64_t> decodeBCH(uint64_t codeword)
codeword
: Provide the input to the decoder in the form of a 64bit unsigned integer- Returns an optional object. The optional is empty in case of an input that contains more than one mistakes. Otherwise, a 64bit unsigned integer with only the 56 least significant bits occupied can be unwrapped from the returned object.
The CCSDS 130.1-G-3 (7,1/2) Convolutional Encoder (meaning rate r = 1/2 and constraint length k = 7) is implemented. The Convolutional Encoder is shown in the image below. It consists of a shift register and some exclusive OR gates that implement the two parity checks. Preceding bits are considered to be 0.
The encoder is defined in inc/Convolutional.hpp
void encode(const etl::bitset<inputLength>& input, etl::bitset<outputLength>& output);
input
: Provide the input to the encoder in the form of a reference to an etl::bitset of sizeinputLength
output
: Provide the input to the encoder in the form of a reference to an etl::bitset of sizeoutputLength
The CCSDS 130.1-G-3 LDPC Encoder at rate of r = 4/5 with information block length k = 4096 (encoded size is then 5120) is implemented.
The encoder is defined in inc/LDPCEncoder.hpp
. inc/PostionRowsEsoteric.hpp
and inc\RowsParityBitsEsoteric.hpp
are also used in the implementation.
void encode(const etl::bitset<inputLength>& input, etl::bitset<outputLength>& output);
input
: Provide the input to the encoder in the form of a reference to an etl::bitset of sizeinputLength
output
: Provide the input to the encoder in the form of a reference to an etl::bitset of sizeoutputLength
The GMSK and OQPSK modulation were mostly ported to C++ from the codec2 project by Rowetel.
As part of the GMSK modulator, an FM modulator is also provided:
FMTranscoder(uint32_t samplingFrequency, uint32_t maxModulatingFrequency, uint32_t basebandFrequency, uint16_t maxDeviation, bool preDeEmphasis, bool limitDeltaPhase)
samplingFrequency
: Sample frequency of the signalmaxModulatingFrequency
: Highest frequency component present in the modulated signalbasebandFrequency
: Baseband frequency (usually set to 0)maxDeviation
: Maximum deviation from the carrier frequencypreDeEmphasis
: Defines whether pre- and and de-emphasis filters of high frequencies is applied
modulate(const double *signal, uint16_t signal_length, double *in_phase_signal, double *quadrature_signal)
signal
: Pointer to the input signal to be modulatedsignal_length
: Input signal lengthin_phase_signal
: Pointer to output in-phase componentquadrature_signal
: Pointer to output quadrature component
Warning: The class internally initializes internal buffers (size TBC with configurable sps) for calculations that can't happen in-place. So take this into account that if you have severe space restrictions, this will take-up a non-negligible amount of memory
The GMSK modulator also automatically uses NRZ encoding in the input to minimize the BER on the receiving end. The filter on the transceiver uses pre-coded taps for BTs = 0.5 (for more information refer to CCSDS 413.0-G-3 Section 3.1)
GMSKTranscoder(uint32_t sampleFrequency, uint32_t symbolRate, bool equalize)
sampleFrequency
: Sampling frequencysymbolRate
: Symbol rateequalize
: Whether equalization will be used in the demodulator to offset the effects of the (non-Nyquist) Gaussian filter used in the modulator
modulate(bool *signal, uint16_t signalLength, double *inPhaseSignal, double *quadratureSignal)
signal
: Pointer to the input signal to be modulatedsignalLength
: Input signal signal lengthinPhaseSignal
: Pointer to output in-phase componentquadratureSignal
: Pointer to output quadrature component
demodulate(double *inputInPhaseSignal, double *inputQuadratureSignal, uint16_t signalLength, bool *signal)
inputInPhaseSignal
: Pointer to input in-phase componentinputQuadratureSignal
: Pointer to input quadrature componentsignalLength
: Input signal lengthsignal
: Pointer to output signal
Warning: The class internally initializes internal buffers (size TBC with configurable sps) for calculations that can't happen in-place. So take this into account that if you have severe space restrictions, this will take-up a non-negligible amount of memory
OQPSKTranscoder(uint32_t samplingFrequency, uint32_t symbolRate)
sampleFrequency
: Sampling frequencysymbolRate
: Symbol rate
modulate(bool *signal, uint16_t signalLength, double *inPhaseSignal, double *quadratureSignal)
signal
: Pointer to the input signal to be modulatedsignalLength
: Input signal lengthinPhaseSignal
: Pointer to output in-phase componentquadratureSignal
: Pointer to output quadrature component
The GMSK detector/synchronization algorithm used, is based on an article by Daniel Estevez: "DSLWP-B GMSK detector"
The gmsk_detector.py contains code that is based on the jupyter_notebooks repository and mainly on the gmsk_dslwp.py GMSK detector script. The Synchronizer
class is based on this implementation.
Synchronizer(int sampleFrequency, int symbolRate)
sampleFrequency
: Sampling frequencysymbolRate
: Symbol Rate
void computeCorrelation(double *inPhaseSignal, double *quadratureSignal, int signalLength)
inPhaseSignal
: Pointer to input in-phase componentquadratureSignal
: Pointer to input quadrature signalsignalLength
: Input signal length
This method takes as input the GMSK modulated received signal and calculates the acquisition matrix in acqBuffer
. From this matrix it is possible to compute the correlation of the received signal to a synchronization marker to then conclude whether the signal contains the marker and at which sample it begins. An example is showcased in syncTest.cpp
.
If a spike exists in the correlation signal then the synchronization marker exists, and starts at the sample the spike is