GNU Radio's DVBS2RX Package
pl_freq_sync.h
Go to the documentation of this file.
1/* -*- c++ -*- */
2/*
3 * Copyright (c) 2021 Igor Freire.
4 *
5 * This file is part of gr-dvbs2rx.
6 *
7 * SPDX-License-Identifier: GPL-3.0-or-later
8 */
9
10#ifndef INCLUDED_DVBS2RX_PL_FREQ_SYNC_H
11#define INCLUDED_DVBS2RX_PL_FREQ_SYNC_H
12
13#include "pl_submodule.h"
15#include <gnuradio/gr_complex.h>
16#include <volk/volk_alloc.hh>
17
18const double fine_foffset_corr_range = 3.3875e-4;
19/* The pilot-mode fine frequency offset estimate is based on the phase difference
20 * accumulated between consecutive pilot blocks, i.e., after an interval of 1440+36=1476
21 * symbols. Hence, the maximum observable frequency offset is given by:
22 *
23 * 1/(2*(1440 + 36)) = 3.3875e-4
24 *
25 * When the frequency offset exceeds this, the phase changes by more than +-pi from pilot
26 * segment to pilot segment. Consequently, the fine estimation approach does not work.
27 *
28 * When including the PLHEADER phase within the fine frequency estimate, note the interval
29 * between the PLHEADER and the first pilot block is slightly different (of 1440 + 90
30 * symbols). However, the fine frequency offset estimator considers only the last 36
31 * symbols of the plheader, which preserves the interval of 1476 symbols.
32 *
33 * Besides, note the pilotless-mode fine frequency offset estimate has a different upper
34 * limit for the observable frequency offset, which depends on the PLFRAME length. Hence,
35 * the pilotless-mode estimator does not use the above constant. See the implementation at
36 * `estimate_fine_pilotless_mode()`.
37 */
38
39namespace gr {
40namespace dvbs2rx {
41
42/**
43 * @brief Frequency Synchronizer
44 *
45 * Provides methods to estimate the coarse and fine frequency offsets disturbing DVB-S2
46 * frames, as well as methods to estimate the phases of various frame segments (SOF,
47 * PLHEADER, and pilot blocks). These methods are meant to be used in conjunction with an
48 * external frequency correction (or de-rotator/rotator) block. This class supplies the
49 * frequency offset estimates, while the external block applies the corrections, an
50 * operation denominated "closed-loop mode". In other words, this class is not responsible
51 * for frequency offset correction. Instead, it focuses on estimation only.
52 *
53 * Due to the closed-loop operation, when estimating the phases of the SOF, PLHEADER, and
54 * pilot blocks, this class assumes the symbols are not rotating. This assumption holds
55 * closely as soon as the external rotator block converges to an accurate frequency
56 * correction. Thus, the phase estimates are obtained by assuming the symbols are only
57 * disturbed by white Gaussian noise. The only exception is on the `derotate_plheader()`
58 * method, which offers an "open-loop" option, documented there.
59 *
60 * Once the frequency offset estimates are accurate enough, the external derotator block
61 * applies accurate corrections and the frequency offset observed by this block becomes
62 * sufficiently low. Moreover, once the normalized frequency offset magnitude falls below
63 * 3.268e-4, this class infers the system is already "coarse-corrected", and the
64 * corresponding state can be fetched through the `is_coarse_corrected()` method. At this
65 * point, it makes sense to start computing the fine frequency offset estimate. Before
66 * that, the fine frequency offset estimates are not reliable.
67 *
68 * Once a fine frequency offset becomes available, this class returns true on method
69 * `has_fine_foffset_est()`. As of this version, a fine offset can be computed whenever
70 * the processed DVB-S2 frames contain pilot blocks and the system is already
71 * coarse-corrected. The estimate is based on the independent phases of each pilot block
72 * composing the frame, and is obtained by calling method `estimate_fine_pilot_mode()`.
73 *
74 * In contrast, the coarse frequency offset can be computed regardless of the presence of
75 * pilots. Also, unlike the fine frequency offset estimation, which is computed and
76 * refreshed on every frame, the coarse estimation is based on several consecutive frames.
77 * The number of frames considered in the computation is determined by the `period`
78 * parameter provided to the constructor.
79 *
80 * In any case, the most recent coarse and fine frequency offset estimates can be fetched
81 * independently through the `get_coarse_foffset()` and `get_fine_foffset()` methods.
82 *
83 */
85{
86private:
87 /* Parameters */
88 unsigned int period; /**< estimation periodicity in frames */
89
90 /* Coarse frequency offset estimation state */
91 double coarse_foffset; /**< most recent freq. offset estimate */
92 unsigned int i_frame; /**< frame counter */
93 unsigned int N; /**< "preamble" length */
94 unsigned int L; /**< used phase differentials (<= N) */
95 bool coarse_corrected; /**< residual offset is sufficiently low */
96
97 /* NOTE: In principle, we could make N equal to the SOF length (26) and L =
98 * N-1 (i.e. 25), in which case coarse frequency offset estimation would be
99 * based on the SOF symbols only and would not require decoding of the
100 * PLSC. However, this would waste all the other 64 known PLHEADER symbols,
101 * which can improve coarse estimation performance substantially. So N in
102 * the end will be set as 90 and L to 89. Nonetheless, the variables are
103 * kept here for flexibility on experiments. */
104
105 /* Fine frequency offset estimation state */
106 double fine_foffset;
107 float w_angle_avg; /**< weighted angle average */
108 bool fine_est_ready; /**< whether a fine estimate is available/initialized */
109
110 /* Volk buffers */
111 volk::vector<gr_complex> plheader_conj; /**< conjugate of PLHEADER symbols */
112 volk::vector<gr_complex> pilot_mod_rm; /**< modulation-removed received pilots */
113 volk::vector<gr_complex> pp_sof; /**< derotated SOF symbols */
114 volk::vector<gr_complex> pp_plheader; /**< derotated PLHEADER symbols */
115
116 /* Coarse estimation only */
117 volk::vector<gr_complex> pilot_corr; /**< mod-removed autocorrelation */
118 volk::vector<float> angle_corr; /**< autocorrelation angles */
119 volk::vector<float> angle_diff; /**< angle differences */
120 volk::vector<float> w_window_f; /**< weight window for the full PLHEADER */
121 volk::vector<float> w_window_s; /**< weight window for the SOF only */
122 volk::vector<float> w_angle_diff; /**< weighted angle differences */
123 volk::vector<gr_complex> unmod_pilots; /**< conjugate of un-modulated pilots */
124
125 /* Fine estimation only */
126 volk::vector<float> angle_pilot; /**< average angle of pilot segments */
127 volk::vector<float> angle_diff_f; /**< diff of average pilot angles */
128
129 /**
130 * \brief Data-aided phase estimation
131 *
132 * \param in Input symbols disturbed by frequency/phase offset.
133 * \param expected Expected symbols known a priori.
134 * \param len Number of symbols to consider for the estimation.
135 * \return float Phase estimate in radians within -pi to +pi.
136 */
137 float estimate_phase_data_aided(const gr_complex* in,
138 const gr_complex* expected,
139 unsigned int len);
140
141public:
142 /**
143 * \brief Construct the frequency synchronizer object.
144 *
145 * \param period Interval in PLFRAMEs between coarse frequency offset
146 * estimations.
147 * \param debug_level Debugging log level (0 disables logs).
148 */
149 freq_sync(unsigned int period, int debug_level);
150
151 /**
152 * \brief Data-aided coarse frequency offset estimation.
153 *
154 * The implementation accumulates `period` frames before outputting an
155 * estimate, where `period` comes from the parameter provided to the
156 * constructor.
157 *
158 * \param in (gr_complex *) Pointer to the start of frame.
159 * \param full (bool) Whether to use the full PLHEADER for the
160 * estimation. When set to false, only the SOF symbols
161 * are used. Otherwise, the full PLHEADER is used and the
162 * PLSC dataword must be indicated so that the correct
163 * PLHEADER sequence is used by the data-aided estimator.
164 * \param plsc (uint8_t) PLSC corresponding to the PLHEADER being
165 * processed. Must be within the range from 0 to 127.
166 * It is ignored if full=false.
167 * \return (bool) Whether a new estimate was computed in this iteration.
168 *
169 * \note The coarse frequency offset estimate is kept internally. It can be
170 * fetched using the `get_coarse_foffset()` method.
171 */
172 bool estimate_coarse(const gr_complex* in, bool full, uint8_t plsc = 0);
173
174 /**
175 * \brief Estimate the average phase of the SOF.
176 * \param in (gr_complex *) Pointer to the SOF symbol array.
177 * \return (float) The phase estimate in radians within -pi to +pi.
178 */
179 float estimate_sof_phase(const gr_complex* in);
180
181 /**
182 * \brief Estimate the average phase of the PLHEADER.
183 * \param in (gr_complex *) Pointer to the PLHEADER symbol array.
184 * \param plsc (uint8_t) PLSC corresponding to the PLHEADER being
185 * processed. Must be within the range from 0 to 127.
186 * \return (float) The phase estimate within -pi to +pi.
187 *
188 * \note plsc indicates the expected PLHEADER symbols so that the
189 * phase estimation can be fully data-aided.
190 */
191 float estimate_plheader_phase(const gr_complex* in, uint8_t plsc);
192
193 /**
194 * \brief Estimate the average phase of a pilot block.
195 *
196 * \param in (gr_complex *) Pointer to the pilot symbol array.
197 * \param i_blk (int) Index of this pilot block within the PLFRAME
198 * \return (float) The phase estimate within -pi to +pi.
199 */
200 float estimate_pilot_phase(const gr_complex* in, int i_blk);
201
202 /**
203 * \brief Pilot-aided fine frequency offset estimation.
204 *
205 * Should be executed only for PLFRAMEs containing pilot symbols, and after the coarse
206 * correction is sufficiently accurate (after reaching the coarse-corrected state).
207 *
208 * \param p_plheader (const gr_complex*) Pointer to the frame's PLHEADER.
209 * \param p_payload (const gr_complex*) Pointer to the descrambled PLFRAME payload.
210 * \param n_pilot_blks (uint8_t) Number of pilot blocks in the PLFRAME being
211 * processed.
212 * \param plsc (uint8_t) PLSC corresponding to the PLHEADER being
213 * processed. Must be within the range from 0 to 127.
214 *
215 * \note The fine frequency offset estimate is kept internally. It can be
216 * fetched using the `get_fine_foffset()` method.
217 * \note The payload pointed by `p_payload` must be descrambled. This function
218 * assumes the pilot symbols on this array are descrambled already.
219 */
220 void estimate_fine_pilot_mode(const gr_complex* p_plheader,
221 const gr_complex* p_payload,
222 uint8_t n_pilot_blks,
223 uint8_t plsc);
224
225 /**
226 * \brief Pilotless fine frequency offset estimation.
227 *
228 * Works for any PLFRAME, but should only be called for PLFRAMEs without pilots. For
229 * frames containing pilot symbols, the pilot-mode estimator should be preferred.
230 *
231 * \param curr_plheader_phase (float) Phase of the current PLHEADER.
232 * \param next_plheader_phase (float) Phase of the next PLHEADER.
233 * \param curr_plframe_len (uint16_t) Length of the current PLFRAME.
234 * \param curr_coarse_foffset (double) Coarse frequency offset over the current frame.
235 * \return (bool) Whether a new estimate was computed during this call.
236 *
237 * \note The fine frequency offset estimate is kept internally. It can be
238 * fetched using the `get_fine_foffset()` method.
239 *
240 * \note This function can only compute a new fine frequency offset estimate if the
241 * residual coarse frequency offset lies within an acceptable range. Otherwise, it
242 * returns early and does not produce a new estimate. Hence, before accessing the
243 * estimate, check the result returned by the `has_fine_foffset_est()` method.
244 *
245 * \note Even though this class stores the most recent coarse frequency offset
246 * estimate as an attribute, the coarse offset that matters is the one affecting the
247 * current PLFRAME. This important distinction arises when the current payload is only
248 * processed after handling the subsequent PLHEADER (whose phase is
249 * `next_plheader_phase`), as is the case on the PL Sync logic. In this scenario, by
250 * the time this function is called, the coarse estimate held internally may already
251 * be that of the subsequent PLHEADER. Hence, to avoid confusion, the coarse offset
252 * distubing the current frame must be provided by argument.
253 */
254 bool estimate_fine_pilotless_mode(float curr_plheader_phase,
255 float next_plheader_phase,
256 uint16_t curr_plframe_len,
257 double curr_coarse_foffset);
258
259 /**
260 * \brief De-rotate PLHEADER symbols.
261 *
262 * \param in (const gr_complex *) Input rotated PLHEADER buffer.
263 * \param open_loop (bool) Whether to assume this block is running in open
264 * loop, without an external frequency correction block. In this case, it is
265 * assumed the most recent frequency offset estimate is still uncorrected
266 * and disturbing the input PLHEADER, so this method attempts to compensate
267 * for this frequency offset when derotating the PLHEADER.
268 *
269 * \note The de-rotated PLHEADER is saved internally and can be accessed
270 * using the `get_plheader()` method.
271 *
272 * \note The open-loop option is useful when there is too much uncertainty about the
273 * the frequency offset estimate, for example while still searching for a DVB-S2
274 * signal. By running `derotate_plheader()` in open loop, only the PLHEADER will be
275 * derotated based on the internal frequency offset estimate, with no need to send the
276 * estimate to an external rotator block. At a minimum, if this derotation is
277 * successful, it can be determinant for a successful PLSC decoding, which then leads
278 * to frame locking. After that, the caller can be more certain about the frequency
279 * offset estimates being valid and switch to the usual closed-loop operation, while
280 * sending the frequency offset estimates to the external rotator block.
281 */
282 void derotate_plheader(const gr_complex* in, bool open_loop = false);
283
284 /**
285 * \brief Get the last PLHEADER phase estimate.
286 *
287 * The estimate is kept internally after a call to the
288 * `estimate_plheader_phase()` method.
289 *
290 * \return (float) Last PLHEADER phase estimate in radians within -pi to +pi.
291 */
292 float get_plheader_phase() { return angle_pilot[0]; }
293
294 /**
295 * \brief Get the phase estimate corresponding to a pilot block.
296 *
297 * This phase estimate becomes available only after calling
298 * `estimate_fine_pilot_mode()`. Otherwise, it's undefined.
299 *
300 * \param i_blk (int) Pilot block index from 0 up to 21.
301 * \return (float) Phase estimate in radians within -pi to +pi.
302 */
303 float get_pilot_phase(int i_blk) { return angle_pilot[i_blk + 1]; }
304
305 /**
306 * \brief Get the last coarse frequency offset estimate.
307 *
308 * The estimate is kept internally after a call to the
309 * `estimate_coarse()` method.
310 *
311 * \return (double) Last normalized coarse frequency offset estimate.
312 */
313 double get_coarse_foffset() { return coarse_foffset; }
314
315 /**
316 * \brief Get the last fine frequency offset estimate.
317 *
318 * The estimate is kept internally after a call to the
319 * `estimate_fine_pilot_mode()` method.
320 *
321 * \return (double) Last normalized fine frequency offset estimate.
322 */
323 double get_fine_foffset() { return fine_foffset; }
324
325 /**
326 * \brief Check whether the coarse frequency correction has been achieved.
327 *
328 * The coarse corrected state is considered achieved when the coarse
329 * frequency offset estimate falls within the fine frequency offset
330 * estimation range.
331 *
332 * \return (bool) Coarse corrected state.
333 */
334 bool is_coarse_corrected() { return coarse_corrected; }
335
336 /**
337 * \brief Check whether a fine frequency offset estimate is available already.
338 *
339 * An estimate becomes available internally after a call to the
340 * `estimate_fine_pilot_mode()` method.
341 *
342 * \return (bool) True when a fine frequency offset estimate is available.
343 */
344 bool has_fine_foffset_est() { return fine_est_ready; }
345
346 /**
347 * \brief Get the post-processed/de-rotated PLHEADER kept internally.
348 *
349 * A de-rotated version of the PLHEADER is stored internally after a call to
350 * the `derotate_plheader()` method.
351 *
352 * \return (const gr_complex*) Pointer to the de-rotated PLHEADER.
353 */
354 const gr_complex* get_plheader() { return pp_plheader.data(); }
355};
356
357} // namespace dvbs2rx
358} // namespace gr
359
360#endif /* INCLUDED_DVBS2RX_PL_FREQ_SYNC_H */
Frequency Synchronizer.
Definition pl_freq_sync.h:85
float get_plheader_phase()
Get the last PLHEADER phase estimate.
Definition pl_freq_sync.h:292
void estimate_fine_pilot_mode(const gr_complex *p_plheader, const gr_complex *p_payload, uint8_t n_pilot_blks, uint8_t plsc)
Pilot-aided fine frequency offset estimation.
bool estimate_fine_pilotless_mode(float curr_plheader_phase, float next_plheader_phase, uint16_t curr_plframe_len, double curr_coarse_foffset)
Pilotless fine frequency offset estimation.
freq_sync(unsigned int period, int debug_level)
Construct the frequency synchronizer object.
bool has_fine_foffset_est()
Check whether a fine frequency offset estimate is available already.
Definition pl_freq_sync.h:344
float get_pilot_phase(int i_blk)
Get the phase estimate corresponding to a pilot block.
Definition pl_freq_sync.h:303
float estimate_sof_phase(const gr_complex *in)
Estimate the average phase of the SOF.
void derotate_plheader(const gr_complex *in, bool open_loop=false)
De-rotate PLHEADER symbols.
double get_fine_foffset()
Get the last fine frequency offset estimate.
Definition pl_freq_sync.h:323
float estimate_plheader_phase(const gr_complex *in, uint8_t plsc)
Estimate the average phase of the PLHEADER.
const gr_complex * get_plheader()
Get the post-processed/de-rotated PLHEADER kept internally.
Definition pl_freq_sync.h:354
bool estimate_coarse(const gr_complex *in, bool full, uint8_t plsc=0)
Data-aided coarse frequency offset estimation.
float estimate_pilot_phase(const gr_complex *in, int i_blk)
Estimate the average phase of a pilot block.
double get_coarse_foffset()
Get the last coarse frequency offset estimate.
Definition pl_freq_sync.h:313
bool is_coarse_corrected()
Check whether the coarse frequency correction has been achieved.
Definition pl_freq_sync.h:334
Definition pl_submodule.h:25
#define DVBS2RX_API
Definition include/gnuradio/dvbs2rx/api.h:19
Fixed-length double-ended queue with contiguous volk-aligned elements.
Definition gr_bch.h:22
const double fine_foffset_corr_range
Definition pl_freq_sync.h:18