Start the implementation of a generic frame synchronizer

The generic frame synchronizer will be able to adapt in a variety of
common framing schemes used by popular modems.
This commit is contained in:
Manolis Surligas 2018-12-02 00:47:56 +02:00
parent 2122b531ae
commit 4bcd9c8aaa
9 changed files with 492 additions and 55 deletions

View File

@ -1,7 +1,7 @@
image: debian:latest
before_script:
- apt-get update -qq && apt-get install -y -qq gnuradio-dev libcppunit-dev libpng++-dev libvorbis-dev libnova-dev cmake swig pkg-config build-essential
- apt-get update -qq && apt-get install -y -qq gnuradio-dev libcppunit-dev libpng++-dev libvorbis-dev cmake swig pkg-config build-essential git
test:
script:

View File

@ -51,6 +51,7 @@ list(APPEND enabled_blocks
satnogs_quad_demod_filter_ff.xml
satnogs_ogg_source.xml
satnogs_noaa_apt_sink.xml
satnogs_whitening.xml
)
if(${INCLUDE_DEBUG_BLOCKS})

View File

@ -41,4 +41,5 @@
<block>satnogs_quad_demod_filter_ff</block>
<block>satnogs_ccsds_rs_decoder_mm</block>
<block>satnogs_decoder_8b10b</block>
<block>variable_whitening</block>
</cat>

35
grc/satnogs_whitening.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<block>
<name>Whitening</name>
<key>variable_whitening</key>
<import>import satnogs</import>
<var_make>self.$(id) = $(id) = satnogs.whitening_make($mask, $seed, $order)</var_make>
<var_value>satnogs.whitening_make($mask, $seed, $order)</var_value>
<make></make>
<param>
<name>Ignore Me</name>
<key>value</key>
<value>'ok'</value>
<type>raw</type>
<hide>all</hide>
</param>
<param>
<name>Mask</name>
<key>mask</key>
<type>int</type>
</param>
<param>
<name>Seed</name>
<key>seed</key>
<type>int</type>
</param>
<param>
<name>Order</name>
<key>order</key>
<type>int</type>
</param>
</block>

View File

@ -22,6 +22,7 @@
#define INCLUDED_SATNOGS_FRAME_ACQUISITION_H
#include <satnogs/api.h>
#include <satnogs/whitening.h>
#include <gnuradio/sync_block.h>
namespace gr
@ -39,19 +40,42 @@ class SATNOGS_API frame_acquisition : virtual public gr::sync_block
public:
typedef boost::shared_ptr<frame_acquisition> sptr;
/*!
* \brief Return a shared_ptr to a new instance of satnogs::frame_acquisition.
*
* To avoid accidental use of raw pointers, satnogs::frame_acquisition's
* constructor is in a private implementation
* class. satnogs::frame_acquisition::make is the public interface for
* creating new instances.
*/
typedef enum {
CRC_NONE = 0,
CRC16,
CRC32
} checksum_t;
static sptr
make (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold);
make_generic_var_len (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold,
size_t frame_size_len = 1,
checksum_t crc = CRC_NONE,
whitening::sptr descrambler = nullptr,
size_t max_frame_len = 2048);
static sptr
make_generic_const_len(const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold,
size_t frame_len = 1,
checksum_t crc = CRC_NONE,
whitening::sptr descrambler = nullptr,
size_t max_frame_len = 2048);
static sptr
make_golay24_var_len (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold,
checksum_t crc = CRC_NONE,
whitening::sptr descrambler = nullptr,
size_t max_frame_len = 2048);
};
} // namespace satnogs

View File

@ -23,41 +23,52 @@
#include <satnogs/api.h>
#include <gnuradio/digital/lfsr.h>
#include <boost/shared_ptr.hpp>
namespace gr
{
namespace satnogs
{
namespace satnogs
{
/*!
* \brief Performs data whitening and de-whitening
*
*/
class SATNOGS_API whitening
{
public:
whitening (uint32_t mask, uint32_t seed, uint32_t order);
/*!
* \brief Performs data whitening and de-whitening
*
*/
class SATNOGS_API whitening
{
public:
static int base_unique_id;
void
reset();
int
unique_id ();
void scramble(uint8_t *out, const uint8_t *in, size_t len,
bool msb = false);
void descramble(uint8_t *out, const uint8_t *in, size_t len,
bool msb = false);
typedef boost::shared_ptr<whitening> sptr;
void
scramble_one_bit_per_byte (uint8_t *out, const uint8_t *in,
size_t bits_num);
void
descramble_one_bit_per_byte (uint8_t *out, const uint8_t *in,
size_t bits_num);
static sptr
make(uint32_t mask, uint32_t seed, uint32_t order);
private:
digital::lfsr d_lfsr;
};
whitening (uint32_t mask, uint32_t seed, uint32_t order);
} // namespace satnogs
void
reset ();
void
scramble (uint8_t *out, const uint8_t *in, size_t len, bool msb = false);
void
descramble (uint8_t *out, const uint8_t *in, size_t len, bool msb = false);
void
scramble_one_bit_per_byte (uint8_t *out, const uint8_t *in, size_t bits_num);
void
descramble_one_bit_per_byte (uint8_t *out, const uint8_t *in,
size_t bits_num);
private:
digital::lfsr d_lfsr;
int d_id;
};
} // namespace satnogs
} // namespace gr
#endif /* INCLUDED_SATNOGS_WHITENING_H */

View File

@ -23,36 +23,96 @@
#endif
#include <gnuradio/io_signature.h>
#include "frame_acquisition_impl.h"
#include <satnogs/golay24.h>
namespace gr
{
namespace satnogs
{
frame_acquisition::sptr
frame_acquisition::make (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold)
frame_acquisition::make_generic_var_len (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold,
size_t frame_size_len,
checksum_t crc,
whitening::sptr descrambler,
size_t max_frame_len)
{
return gnuradio::get_initial_sptr (
new frame_acquisition_impl (preamble, preamble_threshold, sync,
sync_threshold));
new frame_acquisition_impl (frame_acquisition_impl::GENERIC_VAR_FRAME_LEN,
preamble,
preamble_threshold, sync, sync_threshold,
frame_size_len, 0, crc, descrambler,
max_frame_len));
}
frame_acquisition_impl::frame_acquisition_impl (
const std::vector<uint8_t>& preamble, size_t preamble_threshold,
const std::vector<uint8_t>& sync, size_t sync_threshold) :
frame_acquisition::sptr
frame_acquisition::make_generic_const_len (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold,
size_t frame_len, checksum_t crc,
whitening::sptr descrambler,
size_t max_frame_len)
{
return gnuradio::get_initial_sptr (
new frame_acquisition_impl (frame_acquisition_impl::GENERIC_CONSTANT_FRAME_LEN,
preamble,
preamble_threshold, sync, sync_threshold,
0, frame_len, crc, descrambler,
max_frame_len));
}
frame_acquisition::sptr
frame_acquisition::make_golay24_var_len (const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold, checksum_t crc,
whitening::sptr descrambler,
size_t max_frame_len)
{
return gnuradio::get_initial_sptr (
new frame_acquisition_impl (frame_acquisition_impl::GOLAY24_CODED_FRAME_LEN,
preamble,
preamble_threshold, sync, sync_threshold,
0, 0, crc, descrambler,
max_frame_len));
}
frame_acquisition_impl::frame_acquisition_impl (variant_t variant,
const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold,
size_t frame_size_len,
size_t frame_len,
checksum_t crc,
whitening::sptr descrambler,
size_t max_frame_len) :
gr::sync_block ("frame_acquisition",
gr::io_signature::make (1, 1, sizeof(uint8_t)),
gr::io_signature::make (0, 0, 0)),
d_variant(variant),
d_preamble(preamble.size() * 8),
d_preamble_shift_reg(preamble.size() * 8),
d_preamble_len(preamble.size() * 8),
d_preamble_thrsh(preamble_threshold),
d_sync(sync.size() * 8),
d_sync_shift_reg(sync.size() * 8),
d_sync_len(sync.size() * 8),
d_sync_thrsh(sync_threshold)
d_sync_thrsh(sync_threshold),
d_state(SEARCHING),
d_cnt(0),
d_frame_size_field_len(frame_size_len),
d_frame_len(frame_len),
d_max_frame_len(max_frame_len),
d_crc(crc),
d_whitening(descrambler)
{
set_output_multiple(8);
for(uint8_t b : preamble) {
@ -77,6 +137,45 @@ frame_acquisition_impl::frame_acquisition_impl (
d_sync <<= ((b >> 1) & 0x1);
d_sync <<= (b & 0x1);
}
/* Parameters checking */
if (max_frame_len == 0) {
throw std::invalid_argument (
"The maximum frame size should be at least 1 byte");
}
if(d_sync_len < 8) {
throw std::invalid_argument("SYNC word should be at least 8 bits");
}
if(d_preamble_len < 8) {
throw std::invalid_argument("Preamble should be at least 8 bits");
}
if (d_preamble_len < 2 * d_preamble_thrsh) {
throw std::invalid_argument (
"Too many error bits are allowed for the preamble."
"Consider lowering the threshold");
}
if (d_sync_len < 2 * d_sync_thrsh) {
throw std::invalid_argument (
"Too many error bits are allowed for the sync word. "
"Consider lowering the threshold");
}
if (d_frame_size_field_len > 4) {
throw std::invalid_argument ("Frame length field can be up to 4 bytes");
}
if (d_frame_size_field_len == 0) {
throw std::invalid_argument ("Frame length field cannot be 0");
}
d_pdu = new uint8_t[max_frame_len];
/* Register the PMT message queue */
message_port_register_out(pmt::mp("out"));
}
/*
@ -84,6 +183,7 @@ frame_acquisition_impl::frame_acquisition_impl (
*/
frame_acquisition_impl::~frame_acquisition_impl ()
{
delete [] d_pdu;
}
@ -95,10 +195,196 @@ frame_acquisition_impl::work (int noutput_items,
{
const uint8_t *in = (const uint8_t *) input_items[0];
// Do <+signal processing+>
switch(d_state)
{
case SEARCHING:
return searching_preamble(in, noutput_items);
case SEARCHING_SYNC:
return searching_sync(in, noutput_items);
case DECODING_GENERIC_FRAME_LEN:
return dec_generic_frame_len(in, noutput_items);
case DECODING_GOLAY24_FRAME_LEN:
return dec_golay24_frame_len(in, noutput_items);
case DECODING_PAYLOAD:
return decoding(in, noutput_items);
default:
return noutput_items;
}
}
// Tell runtime system how many output items we produced.
return noutput_items;
int
frame_acquisition_impl::searching_preamble (const uint8_t* in, int len)
{
for(int i = 0; i < len; i++) {
d_preamble_shift_reg <<= in[i];
shift_reg tmp = d_preamble_shift_reg ^ d_preamble;
if(tmp.count() <= d_preamble_thrsh) {
d_state = SEARCHING_SYNC;
d_cnt = 0;
return i+1;
}
}
return len;
}
int
frame_acquisition_impl::searching_sync (const uint8_t* in, int len)
{
for (int i = 0; i < len; i++) {
d_sync_shift_reg <<= in[i];
shift_reg tmp = d_sync_shift_reg ^ d_sync;
d_cnt++;
if (tmp.count () <= d_sync_thrsh) {
switch(d_variant) {
case GENERIC_VAR_FRAME_LEN:
d_state = DECODING_GENERIC_FRAME_LEN;
break;
case GENERIC_CONSTANT_FRAME_LEN:
d_state = DECODING_GENERIC_FRAME_LEN;
break;
}
d_cnt = 0;
return i + 1;
}
/* The sync word should be available by now */
if(d_cnt > d_preamble_len * 2 + d_sync_len) {
reset();
return i + 1;
}
}
return len;
}
int
frame_acquisition_impl::dec_generic_frame_len (const uint8_t* in, int len)
{
const int s = std::min(len / 8, (int) d_frame_size_field_len);
for(int i = 0; i < s; i++) {
uint8_t b = 0x0;
b |= in[i * 8] << 7;
b |= in[i * 8 + 1] << 6;
b |= in[i * 8 + 2] << 5;
b |= in[i * 8 + 3] << 4;
b |= in[i * 8 + 4] << 3;
b |= in[i * 8 + 5] << 2;
b |= in[i * 8 + 6] << 1;
b |= in[i * 8 + 7];
d_frame_len <<= 8;
d_frame_len |= b;
d_cnt++;
if(d_cnt == d_frame_size_field_len) {
/* Most of the available modems apply whitening on the frame length too */
if(d_whitening) {
uint32_t descrambled = 0x0;
d_whitening->descramble((uint8_t *)&descrambled,
(const uint8_t *)&d_frame_len,
d_frame_size_field_len, true);
d_frame_len = descrambled;
}
if(d_frame_len < d_max_frame_len) {
d_state = DECODING_PAYLOAD;
}
else{
reset();
return (i + 1) * 8;
}
d_cnt = 0;
return (i + 1) * 8;
}
}
return s * 8;
}
int
frame_acquisition_impl::dec_golay24_frame_len (const uint8_t* in, int len)
{
/* Golay24 needs 3 bytes to decode */
const int s = std::min(len / 8, 3);
for(int i = 0; i < s; i++) {
uint8_t b = 0x0;
b |= in[i * 8] << 7;
b |= in[i * 8 + 1] << 6;
b |= in[i * 8 + 2] << 5;
b |= in[i * 8 + 3] << 4;
b |= in[i * 8 + 4] << 3;
b |= in[i * 8 + 5] << 2;
b |= in[i * 8 + 6] << 1;
b |= in[i * 8 + 7];
d_frame_len <<= 8;
d_frame_len |= b;
d_cnt++;
/* Try to decode the frame length */
if (d_cnt == 3) {
if(d_whitening) {
uint32_t descrambled = 0x0;
d_whitening->descramble((uint8_t *)&descrambled,
(const uint8_t *)&d_frame_len, 3, true);
d_frame_len = descrambled;
}
golay24 g = golay24 ();
uint16_t tmp = 0;
if (g.decode24 (&tmp, d_frame_len)) {
d_frame_len = tmp;
/* Check if the payload can fit in the buffer */
if(d_frame_len > d_max_frame_len) {
reset();
return (i + 1) * 8;
}
else{
d_state = DECODING_PAYLOAD;
}
}
else {
reset ();
return (i + 1) * 8;
}
d_cnt = 0;
return (i + 1) * 8;
}
}
return s * 8;
}
int
frame_acquisition_impl::decoding (const uint8_t* in, int len)
{
const int s = len / 8;
for(int i = 0; i < s; i++) {
uint8_t b = 0x0;
b |= in[i * 8] << 7;
b |= in[i * 8 + 1] << 6;
b |= in[i * 8 + 2] << 5;
b |= in[i * 8 + 3] << 4;
b |= in[i * 8 + 4] << 3;
b |= in[i * 8 + 5] << 2;
b |= in[i * 8 + 6] << 1;
b |= in[i * 8 + 7];
d_pdu[d_cnt++] = b;
if(d_cnt == d_frame_len) {
if(d_whitening) {
d_whitening->descramble(d_pdu, d_pdu, d_frame_len, true);
}
/* TODO */
reset();
return (i+1) * 8;
}
}
return len;
}
void
frame_acquisition_impl::reset ()
{
if(d_whitening) {
d_whitening->reset();
}
d_cnt = 0;
d_state = SEARCHING;
d_preamble_shift_reg.reset();
d_sync_shift_reg.reset();
}
} /* namespace satnogs */

View File

@ -33,10 +33,23 @@ class frame_acquisition_impl : public frame_acquisition
{
public:
frame_acquisition_impl (const std::vector<uint8_t>& preamble,
typedef enum {
GENERIC_VAR_FRAME_LEN = 0,
GENERIC_CONSTANT_FRAME_LEN,
GOLAY24_CODED_FRAME_LEN
} variant_t;
frame_acquisition_impl (variant_t variant,
const std::vector<uint8_t>& preamble,
size_t preamble_threshold,
const std::vector<uint8_t>& sync,
size_t sync_threshold);
size_t sync_threshold,
size_t frame_size_len,
size_t frame_len,
checksum_t crc,
whitening::sptr descrambler,
size_t max_frame_len);
~frame_acquisition_impl ();
// Where all the action really happens
@ -45,12 +58,53 @@ public:
gr_vector_void_star &output_items);
private:
/**
* Decoding FSM
*/
typedef enum
{
SEARCHING, //!< when searching for the start of the preamble
SEARCHING_SYNC,
DECODING_GENERIC_FRAME_LEN,
DECODING_GOLAY24_FRAME_LEN,
DECODING_PAYLOAD
} decoding_state_t;
const variant_t d_variant;
shift_reg d_preamble;
shift_reg d_preamble_shift_reg;
const size_t d_preamble_len;
const size_t d_preamble_thrsh;
shift_reg d_sync;
shift_reg d_sync_shift_reg;
const size_t d_sync_len;
const size_t d_sync_thrsh;
decoding_state_t d_state;
uint32_t d_cnt;
const uint32_t d_frame_size_field_len;
uint32_t d_frame_len;
const uint32_t d_max_frame_len;
const checksum_t d_crc;
whitening::sptr d_whitening;
uint8_t *d_pdu;
int
searching_preamble(const uint8_t *in, int len);
int
searching_sync(const uint8_t *in, int len);
int dec_generic_frame_len(const uint8_t *in, int len);
int dec_golay24_frame_len(const uint8_t *in, int len);
int
decoding(const uint8_t *in, int len);
void
reset();
};
} // namespace satnogs

View File

@ -31,6 +31,23 @@ namespace gr
namespace satnogs
{
int whitening::base_unique_id = 1;
/**
* Data whitening and de-whitening class
* @param mask the polynomial mask
* @param seed the initial seed
* @param order the order of the shift register. This is equal to the
* number of memory stages.
*/
whitening::sptr
whitening::make (uint32_t mask, uint32_t seed, uint32_t order)
{
return whitening::sptr(new whitening(mask, seed, order));
}
/**
* Data whitening and de-whitening class
* @param mask the polynomial mask
@ -39,8 +56,16 @@ namespace satnogs
* number of memory stages.
*/
whitening::whitening (uint32_t mask, uint32_t seed, uint32_t order) :
d_lfsr (mask, seed, order)
d_lfsr (mask, seed, order),
d_id(0)
{
d_id = base_unique_id++;
}
int
whitening::unique_id ()
{
return d_id;
}
/**