Flexible Framing Structure (flexframe)

liquid comes packaged with two basic framing structures: frame64 and flexframe which can be used with little modification to transmit data over a wireless link. The interface for both of these objects is intended to be as simple as possible while allowing control over some of the parameters of the system. On the transmitter side, the appropriate frame generator object is created, configured, and executed. The receiver side uses an appropriate frame synchronizer object which simply picks packets of a stream of samples, invoking a callback function for each packet it finds. The simplicity of the receiver is that the frame synchronizer object automatically reconfigures itself for packets of different size, modulation scheme, and other parameters.

flexframe description

The flexframegen and flexframesync objects are similar to their frame[gen|sync]64 counterparts, however extend functionality to include a number of options in structuring the frame.

Framing Structures

While the specifics of the frame64 and flexframe structures are different, both frames consist of six basic parts:

  • ramp/up gracefully increases the output signal level to avoid "key clicking" and reduce spectral side-lobes in the transmitted signal. Furthermore, it allows the receiver's automatic gain control unit to lock on to the incoming signal, preventing sharp transitions in its output.
  • preamble phasing is a BPSK pattern which flips phase for each transmitted symbol ( +1,-1,+1,-1,... ). This sequence serves several purposes but primarily to help the receiver's symbol synchronization circuit lock onto the proper timing phase. This works because the phasing pattern maximizes the number of symbol transitions.
  • p/n sequence is an \(m\) -sequence (see [section-sequence] ) exhibiting good auto- and cross-correlation properties. This sequence aligns the frame synchronizers to the remainder of the frame, telling them when to start receiving and decoding the frame header, as well as if the phase of the received signal needs to be reversed. At this point, the receiver's AGC, carrier PLL, and timing PLL should all have locked. The p/n sequence is of length 64 for both the frame64 and flexframe structures (63-bit \(m\) -sequence with additional padded bit).
  • header is a fixed-length data sequence which contains a small amount of information about the rest of the frame. The headers for the frame64 and flexframe structures are vastly different and are described independently.
  • payload is the meat of the frame, containing the raw data to be transferred across the link. For the frame64 structure, the payload is fixed at 64 bytes (hence its moniker), encoded using the Hamming(12,8) code ( [section-fec] ), and modulated using QPSK. The flexframe structure has a variable length payload and can be modulated using whatever schemes the user desires, however forward error-correction is executed externally. In both cases the synchronizer object invokes the callback upon receiving the payload.
  • ramp/down gracefully decreases the output signal level as per ramp/up.

Figure [fig-framing-structure]. Framing structure used for the frame64 and flexframe objects.

A graphical depiction of the framing signal level can be seen in [fig-framing-structure] . The relative lengths of each section are not necessarily to scale, particularly as the flexframe structure allows many of these sections to be variable in length. NOTE: while the flexframegen and flexframesync objects are intended to be used in conjunction with one another, the output of flexframegen requires matched-filtering interpolation before the flexframesync object can recover the data.

The Decoding Process

Both the frame64 and flexframe objects operate very similarly in their decoding processes. On the receiver, frames are pulled from a stream of input samples which can exhibit channel impairments such as noise, sample timing offset, and carrier frequency and phase offsets. The receiver corrects for these impairments as best it can using various other signal processing elements in liquid and attempts to decode the frame. If at any time a frame is decoded (even if improperly), its appropriate user-defined callback function is invoked. When seeking a frame the synchronizer initially sets its internal loop bandwidths high for acquisition, including those for the automatic gain control, symbol timing recovery, and carrier frequency/phase recovery. This is known as acquisition mode, and is typical for packet-based communications systems. Once the p/n sequence has been found, the receiver assumes it has a sufficient lock on the channel impairments and reduces its control loop bandwidths significantly, moving to tracking mode.

framesyncprops_s (frame synchronizer properties)

Governing the behavior any frame synchronizer in liquid is the framesyncprops_s object. In general the frame synchronizer open the bandwidths of their control loops until a certain sequence has been detected; this helps reduce acquisition time of the received signal. After the frame has been detected the control loop bandwidths are reduced to improve stability and reduce the possibility of losing a lock on the signal. Listed below is a description of the framesyncprops_ object members.

  • agc_bw0/agc_bw1 are the respective open/closed automatic gain control bandwidths. The default values are \(10^{-3}\) and \(10^{-5}\) , respectively.
  • agc_gmin/agc_gmax are the respective maximum/minimum automatic gain control gain values. The default values are \(10^{-3}\) and \(10^{4}\) , respectively.
  • sym_bw0/sym_bw1 are the respective open/closed symbol synchronizer bandwidths. The default values are \(0.08\) and \(0.05\) , respectively.
  • pll_bw0/pll_bw1 are the respective open/closed carrier phase-locked loop bandwidths. The default values are \(0.02\) and \(0.005\) , respectively.
  • k represents the matched filter's samples per symbol; however this parameter is reserved for future development. At present this number should be equal to 2 and should not be changed.
  • npfb represents the number of filters in the internal symbol timing recovery object's polyphase filter bank (see [section-filter-symsync] ); however this parameter is reserved for future development and should not be changed. The default value is 32.
  • m represents the matched filter's symbol delay; however this parameter is reserved for future development and should not be changed. the default value is 3.
  • beta represents the matched filter's excess bandwidth factor; however this parameter is reserved for future development and should not be changed. the default value is 0.7.
  • squelch_enabled is a flag that specifies if the automatic gain control's squelch is enabled (see [section-agc-squelch] ). Enabling the squelch (setting squelch_enabled equal to 1 ) will ignore received signals below the squelch_threshold value (see below) to help prevent the receiver's control loops from drifting. Enabling the squelch is usually desirable; however care must be taken to properly set the threshold|ideally about 4 dB above the noise floor|so as not to miss frames with a weak signal. By default the squelch is disabled .
  • autosquelch_enabled is a flag that specifies if the automatic gain control's auto-squelch is enabled (see [section-agc-squelch-auto] ). In brief, the auto-squelch attempts to track the signal's power to automatically squelch signals 4 dB above the noise floor. By default the auto-squelch is disabled.
  • squelch_threshold is the squelch threshold value in dB (see [section-agc-squelch] ). The default value is -35.0, but the ideal value is about 4 dB above the noise floor.
  • eq_len specifies the length of the internal equalizer (see [section-equalization] ). By default the length is set to zero which disables equalization of the receiver.
  • eqrls_lambda is the recursive least-squares equalizer forgetting factor \(\lambda\) (see [section-equalization-eqrls] ). The default value is \(\lambda=0.999\) .

framesyncstats_s (frame synchronizer statistics)

When the synchronizer finds a frame and invokes the user-defined callback function, a special structure is passed to the callback that includes some useful information about the frame. This information is contained within the framesyncstats_s structure. While useful, the information contained within the structure is not necessary for decoding and can be ignored by the user. Listed below is a description of the framesyncstats_ object members.

  • evm is an estimate of the received error vector magnitude in decibels of the demodulated header (see [section-modem-digital] ).
  • rssi is an estimate of the received signal strength in dB. This is derived from the synchronizer's internal automatic gain control object (see [section-agc] ).
  • framesyms a pointer to an array of the frame symbols (e.g. QPSK) at complex baseband before demodulation. This is useful for plotting purposes. This pointer is not static and cannot be used after returning from the callback function. This means that it needs to be copied locally for you to retain the data.
  • num_framesyms the length of the framesyms pointer array.
  • mod_scheme the modulation scheme of the frame (see [section-modem] ).
  • mod_bps the modulation depth (bits per symbol) of the modulation scheme used in the frame.
  • check the error-detection scheme (e.g. cyclic redundancy check) used in the payload of the frame (see [section-fec] ).
  • fec0 the inner forward error-correction code used in the payload (see [section-fec] ).
  • fec1 the outer forward error-correction code used in the payload (see [section-fec] ).

A simple way to display the information in an instance of framesyncstats_s is to use the framesyncstats_print() method.


#include <liquid/liquid.h>

// static callback function
static int callback(unsigned char *  _header,
                    int              _header_valid,
                    unsigned char *  _payload,
                    unsigned int     _payload_len,
                    int              _payload_valid,
                    framesyncstats_s _stats,
                    void *           _userdata)
    printf("*** callback invoked ***\n");
    return 0;

int main() {
    // create frame generator with default properties
    flexframegen fg = flexframegen_create(NULL);

    // create frame synchronizer using default properties
    flexframesync fs = flexframesync_create(callback,NULL);

    // data header and payload
    unsigned int  payload_len = 1234;   // number of bytes in payload
    unsigned char header[14];           // user-defined header
    unsigned char payload[payload_len]; // user-defined payload

    // .. initialize header and payload data ..

    // assemble the frame
    flexframegen_assemble(fg, header, payload, payload_len);

    // generate the frame in blocks
    unsigned int  buf_len = 256;
    float complex buf[buf_len];

    int frame_complete = 0;
    while (!frame_complete) {
        // write samples to buffer
        frame_complete = flexframegen_write_samples(fg, buf, buf_len);

        // run through frame synchronizer
        flexframesync_execute(fs, buf, buf_len);

    // clean up allocated objects