GNU Radio's DVBS2RX Package
gr::dvbs2rx::frame_sync Class Reference

Frame Synchronizer. More...

#include <pl_frame_sync.h>

Inheritance diagram for gr::dvbs2rx::frame_sync:

Public Member Functions

 frame_sync (int debug_level, uint8_t unlock_thresh=3)
 Construct a new frame sync object.
 
bool step (const gr_complex &in)
 Process the next input symbol.
 
void set_frame_len (uint32_t len)
 Set the current PLFRAME length.
 
bool is_locked () const
 Check whether frame lock has been achieved.
 
bool is_locked_or_almost () const
 Check whether frame lock has been achieved or a SOF has been found.
 
uint32_t get_sym_count () const
 Get the symbol count on the internal payload buffer.
 
uint32_t get_sof_interval () const
 Get the interval between the last two detected SOFs.
 
const gr_complex * get_plheader () const
 Get the PLHEADER buffered internally.
 
const gr_complex * get_payload () const
 Get the PLFRAME payload (data + pilots) buffered internally.
 
const gr_complex * get_sof_corr_taps () const
 Get the SOF correlator taps.
 
const gr_complex * get_plsc_corr_taps () const
 Get the PLSC correlator taps.
 
float get_timing_metric () const
 Get the last evaluated timing metric.
 
std::chrono::system_clock::time_point get_lock_time ()
 Get the frame lock timestamp.
 
- Public Member Functions inherited from gr::dvbs2rx::pl_submodule
 pl_submodule (const std::string name, int debug_level)
 Verbose logger.
 

Additional Inherited Members

- Protected Attributes inherited from gr::dvbs2rx::pl_submodule
gr::logger_ptr d_logger
 Debug level.
 

Detailed Description

Frame Synchronizer.

Searches for the start of PLFRAMEs by computing and adding independent cross-correlations between the SOF and the PLSC parts of the PLHEADER with respect to their expected values known a priori. The cross-correlations are based on the so-called differential metric given by x[n]*conj(x[n+1]), namely based on the angle difference between consecutive symbols. This non-coherent approach allows for frame synchronization despite the presence of large frequency offsets. That's crucial because the fine frequency offset estimation requires correct decoding of the PLSC, which, in turn, requires the frame search provided by the frame synchronizer. Hence, the frame synchronizer should act first, before the carrier recovery.

Due to the interleaved Reed-Muller codeword construction (with a XOR given by the 7th PLSC bit), each pair of consecutive PLSC bits is either of equal or opposite bits. When the 7th PLSC bit is 0, all pairs of bits are equal, i.e., b[2i+1] = b[2i]. In this case, the corresponding pair of scrambled bits are either equal to the original scrambler bits (when b[2i] = b[2i+1] = 0) or their opposite (when b[2i] = b[2i+1] = 1). In either case, the complex differential x[2i]*conj(x[2i+1]) is the same due to the pi/2 BPSK mapping (refer to the even-to-odd mapping rules in the body of function demap_bpsk_diff() from pi2_bpsk.cc). Hence, when the 7th PLSC bit is 0, the differential is determined by the scrambler sequence, not the actual PLSC value that is unknown at this point.

Next, consider the case when the 7th PLSC bit is 1 such that each pair of PLSC bits is composed of opposite bits, i.e., b[2i+1] = !b[2i]. In this case, if b[2i] = 0 and b[2i+1] = 1, the pair of scrambled bits becomes (s[2i], !s[2i+1]). Otherwise, if b[2i] = 1 and b[2i+1] = 0, the pair of scrambled bits becomes (!s[2i], s[2i+1]). In either case, the complex differential is equal to the differential due to the scrambler sequence alone, but shifted by 180 degrees (i.e., by expj(j*pi), due to the pi/2 BPSK mapping rules (again, see demap_bpsk_diff()). Thus, if the differentials due to the PLSC scrambler sequence are used as the correlator taps, the cross-correlation still yields a peak when processing the PLSC. The only difference is that the phase of the peak will be shifted by 180 degrees, but the magnitude will be the same. In this case, the 180-degree shift can be undone by taking the negative of the correlator peak.

In the end, the PLSC correlator is implemented based on the scrambler sequence alone (known a priori), and it is independent of the actual PLSC embedded on each incoming PLHEADER. This correlator is composed of 32 taps only, given that only the pairwise PLSC differentials are known a priori. In contrast, the SOF correlator is based on all the 25 known SOF differentials, given that the entire 26-symbol SOF sequence is known a priori.

The two correlators (SOF and PLSC) are expected to peak when they observe the SOF or PLSC in the input symbol sequence. The final timing metric is given by the sum or difference of these correlators, whichever has the largest magnitude. The sum (SOF + PLSC) peaks when the 7th PLSC bit is 0, and the difference (SOF - PLSC) peaks when the 7th PLSC bit is 1. That is, the difference metric essentially undoes the 180-degree shift on the PLSC correlator peak that would arise when the 7th bit is 1.

Furthermore, as stated before, the implementation is robust to frequency offsets. The input symbol sequence can have any frequency offset, as long as it doesn't change significantly in the course of the PLHEADER, which is typically the case given that the PLHEADER is short enough (for typical DVB-S2 baud rates). If the frequency offset is the same for symbols x[n] and x[n+1], the differential metric includes a factor given by:

exp(j*2*pi*f0*n) * conj(exp(j*2*pi*f0*(n+1))) = exp(-j*2*pi*f0).

Moreover, if the frequency offset remains the same over the entire PLHEADER, all differentials include this factor. Ultimately, the cross-correlation peak is still observed, just with a different phase (shifted by -2*pi*f0). In fact, the phase of the complex timing metric (sum or difference between the correlator peaks) could be used to estimate the coarse frequency offset affecting the PLHEADER. However, a better method is implemented on the dedicated freq_sync class.

Lastly, aside from the correlators, the implementation comprises a state machine with three states: "searching", "found", and "locked". As soon as an SOF is found, the state machine changes to the "found" state. At this point, the caller should decode the corresponding PLSC and call method set_frame_len() to inform the expected PLFRAME length following the detected SOF. Then, if the next SOF comes exactly after the informed frame length, the state machine changes into the "locked" state. From this point on, the frame synchronizer will check the correlation peak (i.e., the so-called "timing metric") at the expected index on every frame.

Whenever the timing metric does not exceed a specific magnitude threshold, the implementation will increment an internal count for unlocking. After a chosen number of consecutive timing metric failures, this block will assume the frame lock has been lost and transition back to the "searching" state. At this point, it takes at least two more PLHEADERs to recover the lock, as the state machine needs to go over the "found" and "locked" states again.

Constructor & Destructor Documentation

◆ frame_sync()

gr::dvbs2rx::frame_sync::frame_sync ( int  debug_level,
uint8_t  unlock_thresh = 3 
)

Construct a new frame sync object.

Parameters
debug_level(int) Target debugging log level (0 disables logs).
unlock_thresh(uint8_t) Number of consecutive frame detection failures before unlocking. A failure occurs when the timing metric does not exceed the expected magnitude threshold. By default, 3 failures will lead to unlocking.
Note
The number of consecutive timing metric failures before unlocking must be tuned to avoid unlocking prematurely under high noise, when the timing metric deviates significantly from the nominal peak of 57 for unit-energy symbols (57 due to the 26+32=57 correlator taps). On the other hand, this threshold parameter should not be very high to avoid too much delay in unlocking. For example, if a PLSC decoding error occurs and a wrong PLFRAME length is informed to the frame synchronizer, the timing metric observed after the wrong frame length will most certainly fail to exceed the threshold. In this scenario, all subsequent unlock_thresh frames will likely fail, as the frame synchronizer will search for their PLHEADERs in wrong indexes. Hence, in this example, it is better to unlock reasonably fast than to wait further.

Member Function Documentation

◆ get_lock_time()

std::chrono::system_clock::time_point gr::dvbs2rx::frame_sync::get_lock_time ( )
inline

Get the frame lock timestamp.

Returns
std::chrono::system_clock::time_point Timestamp in UTC time corresponding to when the frame synchronizer locked the frame timing. Valid only when locked.

Referenced by gr::dvbs2rx::plsync_cc_impl::get_lock_time().

◆ get_payload()

const gr_complex * gr::dvbs2rx::frame_sync::get_payload ( ) const
inline

Get the PLFRAME payload (data + pilots) buffered internally.

The payload observed between consecutive SOFs is buffered internally. If a SOF is missed such that the last two observed SOFs are spaced by more than the maximum payload length, only up to MAX_PLFRAME_PAYLOAD symbols are buffered internally.

Returns
(const gr_complex*) Pointer to the internal payload buffer.

◆ get_plheader()

const gr_complex * gr::dvbs2rx::frame_sync::get_plheader ( ) const
inline

Get the PLHEADER buffered internally.

Returns
(const gr_complex*) Pointer to the internal PLHEADER buffer.

References gr::dvbs2rx::cdeque< T >::back().

◆ get_plsc_corr_taps()

const gr_complex * gr::dvbs2rx::frame_sync::get_plsc_corr_taps ( ) const
inline

Get the PLSC correlator taps.

Returns
(const gr_complex*) Pointer to the PLSC correlator taps.

◆ get_sof_corr_taps()

const gr_complex * gr::dvbs2rx::frame_sync::get_sof_corr_taps ( ) const
inline

Get the SOF correlator taps.

Returns
(const gr_complex*) Pointer to the SOF correlator taps.

◆ get_sof_interval()

uint32_t gr::dvbs2rx::frame_sync::get_sof_interval ( ) const
inline

Get the interval between the last two detected SOFs.

Returns
(uint32_t) Interval in symbol periods.

◆ get_sym_count()

uint32_t gr::dvbs2rx::frame_sync::get_sym_count ( ) const
inline

Get the symbol count on the internal payload buffer.

Returns
uint32_t Current number of payload symbols buffered internally if locked.

References MAX_PLFRAME_PAYLOAD.

◆ get_timing_metric()

float gr::dvbs2rx::frame_sync::get_timing_metric ( ) const
inline

Get the last evaluated timing metric.

Once locked, the timing metric updates only once per frame. Before that, it updates after every input symbol.

Returns
(float) Last evaluated timing metric.

◆ is_locked()

bool gr::dvbs2rx::frame_sync::is_locked ( ) const
inline

Check whether frame lock has been achieved.

Returns
(bool) True if locked.

◆ is_locked_or_almost()

bool gr::dvbs2rx::frame_sync::is_locked_or_almost ( ) const
inline

Check whether frame lock has been achieved or a SOF has been found.

Returns
(bool) True if locked or if at least a SOF has been found.

◆ set_frame_len()

void gr::dvbs2rx::frame_sync::set_frame_len ( uint32_t  len)

Set the current PLFRAME length.

This information is used to predict when the next SOF should be observed. If a timing metric peak is indeed observed at the next expected SOF index, the synchronizer achieves frame lock.

Parameters
len(uint32_t) Current PLFRAME length.

◆ step()

bool gr::dvbs2rx::frame_sync::step ( const gr_complex &  in)

Process the next input symbol.

Parameters
in(gr_complex &) Input symbol.
Returns
(bool) Whether the input symbol consists of the last PLHEADER symbol, where the timing metric is expected to peak.
Note
This function should return true for the last PLHEADER symbol only. For all other symbols, it should return false.

The documentation for this class was generated from the following file: