The ofdmflexframe family of objects (generator and synchronizer) realize a simple way to load data onto an OFDM physical layer system. OFDM has several benefits over traditional "narrowband" communications systems such as the flexframe objects ([ref:section-framing-frames] ). These objects allow the user to abstractly specify the number of subcarriers, their assignment (null/pilot/data), forward error-correction and modulation scheme. Furthermore, the framing structure includes a provision for a brief user-defined header which can be used for source/destination address, packet identifier, etc.

Sending data in parallel channels has some distinct advantages over serial transmission: equalization in the presence of multi-path channel environments is much simpler, inter-symbol interference can be eliminated with a properly-chosen cyclic prefix length, and capacity can be increased by modulating data appropriately on subcarriers relative to their signal-to-noise ratio. Multi-carrier systems, however, are significantly more sensitive to carrier frequency offsets and Doppler shifts, leading to inter-carrier interference. OFDM can therefore be more difficult to synchronize and maintain data fidelity in mobile environments. To assist in synchronization, the transmitter inserts special preamble symbols at the beginning of each frame which assist the synchronizer in estimating the carrier frequency offset, recovering the symbol timing, and compensating for effects of the channel.

This section gives specifics for the OFDM flexible framing structure and is really intended only as a reference; for a tutorial on how to use the generator/synchronizer objects without getting into detail, please refer to the OFDM Framing tutorial ([ref:tutorial-ofdmflexframe] ).

Operational description

Like the frame64 and flexframe structures, the ofdmflexframe structure consists of three main components: the preamble, the header, and the payload.

  • Preamble The preamble consists of two types of phasing symbols: the Section s\ and Section l\ sequences. The Section s\ symbols are necessary for coarse carrier frequency and timing offsets while the Section l\ sequence is used for fine timing acquisition and equalizer gain estimation. The transmitter generates two Section s\ symbols and just a single Section l\ symbol. This aligns the receiver's timing to that of the transmitter, signaling the start of the header.
  • Header The header consists of one or more OFDM symbols; the exact number of OFDM symbols depends on the number of subcarriers allocated and the assignment of these subcarriers (null/pilot/data). The header carries exactly 14 bytes of information, 6 of which are used internally and the remaining 8 are user-defined. The internal header data provide framing information to the receiver including the modulation, forward error-correction, and data validation schemes of the payload as well as its length in bytes. These data are encoded with a forward error-correction scheme and modulated onto the first several OFDM symbols.
  • Payload The payload consists of zero or more OFDM symbols. Like the header, the exact number of OFDM symbols depends on the number of subcarriers allocated and the assignment of these subcarriers.
doc/ofdmflexframe/ofdmflexframe_structure.png

Figure [fig-framing-ofdmflexframe_structure]. Timing structure used for the ofdmflexframegen and ofdmflexframesync objects. The cyclic prefix is highlighted.

The full frame is assembled according to[ref:fig-framing-ofdmflexframe_structure] . Notice that the Section s\ symbols do not contain a cyclic prefix; this is to ensure continuity between contiguous Section s\ symbols and is necessary to eliminate inter-symbol interference. The single Section l\ symbol at the end of the preamble is necessary for timing alignment and an initial equalizer estimate. Once the frame has been detected, the header is received and decoded. The number of symbols in the header depends on the number of data subcarriers allocated to each OFDM symbol. The header includes the modulation, coding schemes, and length of the remainder of the frame. If the synchronizer successfully decodes the header, it will automatically reconfigure itself to decode the payload.

Subcarrier Allocation

Subcarriers may be arbitrarily allocated into three types:

  • OFDMFRAME_SCTYPE_NULL : The null option disables this subcarrier from transmission. This is useful for spectral notching and guard bands (see [ref:fig-framing-ofdmflexframe_spectrum] ). Guard bands are necessary for interpolation of the signal before transmission;
  • OFDMFRAME_SCTYPE_PILOT : Pilot subcarriers are used to estimate channel impairments including carrier phase/frequency offsets as well as timing offsets. Pilot subcarriers are necessary for coherent demodulation in OFDM systems. The ofdmflexframe structure requires that at least two subcarriers be designated as pilots. Performance improves if the pilots are evenly spaced and separated as much as possible (see [ref:fig-framing-ofdmflexframe_spectrum] ), but the exact location of pilots is not restricted;
  • OFDMFRAME_SCTYPE_DATA : Data subcarriers are reserved for carrying the payload of the frame, modulated with the desired scheme. The spectral efficiency of the transmission improves with more data subcarriers. The ofdmflexframe structure requires that at least one subcarrier be designated for data.

Typically the subcarriers at the band edges are disabled to avoid aliasing during up-conversion/interpolation. The elements of \(\vec{p}\) are given in the same order as the FFT input (that is, \(p_0\) holds the DC subcarrier and \(p_{M/2}\) holds the subcarrier at half the sampling frequency). The ofdmframe_init_default_sctype(M,*p) interface initializes the subcarrier allocation array \(\vec{p}\) for a system with \(M\) channels that is expected to perform relatively well under a variety of channel conditions.

doc/ofdmflexframe/ofdmflexframe_spectrum.png

Figure [fig-framing-ofdmflexframe_spectrum]. Example spectral response for the ofdmflexframegen and ofdmflexframesync objects.

[ref:fig-framing-ofdmflexframe_spectrum] depicts an example spectral response of the ofdmflexframe structure with evenly-spaced pilot subcarriers, guard bands, and a spectral notch in the lower band.

Pilot Subcarriers

Pilot subcarriers are used to assist the synchronizer in tracking to slowly-varying channel impairments such as moderate to low carrier frequency/phase offsets and slowly-varying timing frequency offsets (residual error from initial estimation). The pilots themselves are BPSK symbols with a pseudo-random phase generated by a linear feedback shift register. To improve the peak-to-average power ratio, the pilots are different not only from one symbol to another, but within each OFDM symbol.

Window Tapering

liquid includes the option for window tapering to reduce the spectral side-lobes of the transmitted OFDM signal by allowing adjacent symbols gracefully transition from one to the next.

doc/ofdmflexframe/ofdm_cyclic_prefix_diagram.png

Figure [fig-framing-ofdmflexframe-cyclic-prefix]. OFDM cyclic prefix

[ref:fig-framing-ofdmflexframe-cyclic-prefix] demonstrates the typical cyclic prefix extension with OFDM. The tapering window is sequence of \(N_t\) samples\(\vec{w} = \{ w_0, w_1, \ldots, w_{N_t-1} \}\) such that \(w_k \in [0,1]\,\,\, \forall_{k \in \{0,N_t-1\}}\) and\(w_k + w_{N_t-k-1} = 1\) . Note that the tapering window length must be less than or equal to the cyclic prefix length, i.e. \(N_t \leq N_c\) . liquid uses the sine-squared window defined as {cite:802.11:standard((4))}

$$ w_n = \sin^2\Bigl( \pi\frac{n + 1/2}{N_t} \Bigr) $$
doc/ofdmflexframe/ofdm_window_tapering_diagram.png

Figure [fig-framing-ofdmflexframe-tapering]. OFDM window tapering

[ref:fig-framing-ofdmflexframe-tapering] demonstrates how the window is applied to each OFDM symbol; the cyclic postfix of the previous symbol overlaps the current symbol's cyclic prefix with the applied window providing a graceful transition region. Notice that the tapering window does not extend the length of the symbol beyond its cyclic prefix.

Example Subcarrier Allocation Spectrum

doc/ofdmflexframe/ofdmflexframe_psd.png

Figure [fig-ofdmflexframe-psd]. Power spectral density for the ofdmflexframe generator with custom subcarrier allocation.

The frame in the above figure has 1200 subcarriers with 10% disabled on each edge for guard bands, the DC component disabled, and 15% notched in the lower half of the band. Tapering is used on 72 of he 80 samples in the cyclic prefix to reduce the side-lobes and improve spectral compactness.

ofdmflexframegen

The ofdmflexframegen object is responsible for assembling raw data bytes into contiguous OFDM time-domain symbols which the ofdmflexframesync object can receive. The life cycle of the generator is as follows:

  • create the frame generator, passing the number of subcarriers, cyclic prefix length, subcarrier allocation, and framing properties (modulation scheme, forward error-correction coding, payload length, etc);
  • assemble a frame consisting of raw header and payload bytes;
  • write the OFDM symbols (time series) to a buffer until the entire frame has been generated;
  • repeat the "assemble" and "write symbol" steps for as many frames as is desired;
  • destroy the frame generator object.

This listing gives a basic demonstration to the interface to the ofdmflexframegen object:

#include <liquid/liquid.h>

int main() {
    // options
    unsigned int M = 64;                // number of subcarriers
    unsigned int cp_len = 16;           // cyclic prefix length
    unsigned int taper_len = 4;         // taper length
    unsigned int payload_len = 120;     // length of payload (bytes)

    // buffers
    float complex buffer[M + cp_len];   // time-domain buffer
    unsigned char header[8];            // header data
    unsigned char payload[payload_len]; // payload data
    unsigned char p[M];                 // subcarrier allocation (null/pilot/data)

    // initialize frame generator properties
    ofdmflexframegenprops_s fgprops;
    ofdmflexframegenprops_init_default(&fgprops);
    fgprops.check           = LIQUID_CRC_32;
    fgprops.fec0            = LIQUID_FEC_NONE;
    fgprops.fec1            = LIQUID_FEC_HAMMING128;
    fgprops.mod_scheme      = LIQUID_MODEM_QAM16;

    // initialize subcarrier allocation to default
    ofdmframe_init_default_sctype(M, p);

    // create frame generator
    ofdmflexframegen fg = ofdmflexframegen_create(M,cp_len,taper_len,p,&fgprops);

    // ... initialize header/payload ...

    // assemble frame
    ofdmflexframegen_assemble(fg, header, payload, payload_len);

    // generate frame
    int last_symbol=0;
    while (!last_symbol) {
        // generate each OFDM symbol
        last_symbol = ofdmflexframegen_writesymbol(fg, buffer);
    }

    // destroy the frame generator object
    ofdmflexframegen_destroy(fg);
}

Listed below is the full interface to the ofdmflexframegen object.

  • ofdmflexframegen_create(M,c,t,*p,*fgprops) creates and returns an ofdmflexframegen object with \(M\) subcarriers (\(M\) must be an even integer), a cyclic prefix length of \(c\) samples, a taper length of \(t\) samples, a subcarrier allocation determined by \(\vec{p}\) , and a set of properties determined by fgprops . If \(\vec{p}\) is set to NULL , the default subcarrier allocation is used. If fgprops is set to NULL , the default frame generator properties are used.
  • ofdmflexframegen_destroy(q) destroys an ofdmflexframegen object, freeing all internally-allocated memory.
  • ofdmflexframegen_reset(q) resets the ofdmflexframegen object, including all internal buffers.
  • ofdmflexframegen_print(q) prints the internal state of the ofdmflexframegen object.
  • ofdmflexframegen_is_assembled(q) returns a flag indicating if the frame has been assembled yet ( 1 if yes, 0 if no).
  • ofdmflexframegen_setprops(q,props) sets the configurable properties of the frame generator, including the error-detection scheme (e.g. LIQUID_CRC_24 ), the inner and outer error-correction scheme (e.g. LIQUID_FEC_HAMMING128 ), and the modulation scheme (e.g. LIQUID_MODEM_QPSK ).
  • ofdmflexframegen_getframelen(q) returns the number of OFDM symbols (not samples) in the frame, including the preamble, header, and payload.
  • ofdmflexframegen_assemble(q,*header,*payload,n) assembles the OFDM frame, internal to the ofdmflexframegen object; with an 8-byte header, and an \(n\) -byte payload. Unlike the flexframegen object, samples are not written to a buffer at this point, but are generated with the writesymbol() method, below.
  • ofdmflexframegen_writesymbol(q,*buffer) writes OFDM symbols (time series) to the buffer and returns a flag indicating if this symbol is the last in the frame. The buffer needs to be \(M+c\) samples long, the length of each symbol.

ofdmflexframesync

The ofdmflexframesync object is responsible for detecting frames generated by the ofdmflexframesync object, decoding the header and payloads, and passing the results back to the user by way of a callback function. This listing gives a basic demonstration to the interface to the ofdmflexframegen object:

#include <liquid/liquid.h>

// callback function
int mycallback(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() {
    // options
    unsigned int M = 64;        // number of subcarriers
    unsigned int cp_len = 16;   // cyclic prefix length
    unsigned int taper_len = 4; // taper length
    unsigned char p[M];         // subcarrier allocation (null/pilot/data)
    void * userdata;            // user-defined data

    // initialize subcarrier allocation to default
    ofdmframe_init_default_sctype(M, p);

    // create frame synchronizer
    ofdmflexframesync fs = ofdmflexframesync_create(M, cp_len, taper_len, p,
                                                    mycallback, userdata);

    // grab samples from source and push through synchronizer
    float complex buffer[20];   // time-domain buffer (any length)
    {
        // push received samples through synchronizer
        ofdmflexframesync_execute(fs, buffer, 20);
    }

    // destroy the frame synchronizer object
    ofdmflexframesync_destroy(fs);
}

Notice that the input buffer can be any length, regardless of the synchronizer object's properties. Listed below is the full interface to the ofdmflexframesync object.

  • ofdmflexframesync_create(M,c,t,*p,*callback,*userdata) creates and returns an ofdmflexframegen object with \(M\) subcarriers (\(M\) must be an even integer), a cyclic prefix length of \(c\) samples, a taper length of \(t\) samples, a subcarrier allocation determined by \(\vec{p}\) , a user-defined callback function (see description, below) and user-defined data pointer. If \(\vec{p}\) is set to NULL , the default subcarrier allocation is used.
  • ofdmflexframesync_destroy(q) destroys an ofdmflexframesync object, freeing all internally-allocated memory.
  • ofdmflexframesync_print(q) prints the internal properties of the ofdmflexframesync object to the standard output.
  • ofdmflexframesync_reset(q) resets the internal state of the ofdmflexframesync object.
  • ofdmflexframesync_execute(q,*buffer,n) runs the synchronizer on an input buffer with \(n\) samples of type float complex . Whenever a frame is found and decoded, the synchronizer will invoke the callback function given when created. The input buffer can be any length, irrespective of any of the properties of the frame.
  • ofdmflexframesync_get_cfo(q) queries the frame synchronizer for the received carrier frequency offset (relative to sampling rate).
  • ofdmflexframesync_get_rssi(q) queries the frame synchronizer for the received signal strength of the input (given in decibels).

The callback function for the ofdmflexframesync object has seven arguments and looks like this:

int ofdmflexframesync_callback(unsigned char *  _header,
                               int              _header_valid,
                               unsigned char *  _payload,
                               unsigned int     _payload_len,
                               int              _payload_valid,
                               framesyncstats_s _stats,
                               void *           _userdata);

The callback is typically defined to be static and is passed to the instance of ofdmflexframesync object when it is created. The return value can be ignored for now and is reserved for future development. Here is a brief description of the callback function's arguments:

  • _header is a volatile pointer to the 8 bytes of decoded user-defined header data from the frame generator.
  • _header_valid is simply a flag to indicate if the header passed its cyclic redundancy check. If the check fails then the header data have been corrupted beyond the point that internal error correction can recover; in this situation the payload cannot be recovered.
  • _payload is a volatile pointer to the decoded payload data. When the header cannot be decoded ( _header_valid == 0 ) this value is set to NULL .
  • _payload_len is the length (number of bytes) of the payload array. When the header cannot be decoded ( _header_valid == 0 ) this value is set to 0 .
  • _payload_valid is simply a flag to indicate if the payload passed its cyclic redundancy check (" 0 " means invalid, " 1 " means valid). As with the header, if this flag is zero then the payload almost certainly contains errors.
  • _stats is a synchronizer statistics construct ( framesyncstats_s ) that indicates some useful PHY information to the user (see [ref:section-framing-framesyncstats_s] ).
  • _userdata is the void pointer given to the ofdmflexframesync_create() method. Typically this pointer is a vehicle for getting the header and payload data (as well as any other pertinent information) back to your main program.

Performance

doc/ofdmflexframe/ofdmflexframe_performance.png

Figure [fig-framing-ofdmflexframe_performance]. Performance of the ofdmflexframe structure with \(M=64\) subcarriers (default allocation).

[ref:fig-framing-ofdmflexframe_performance] shows the performance characteristics of the ofdmflexframe structure for\(M=64\) subcarriers with default subcarrier allocation. The frame can be detected with a 90% probability with an SNR level of just -0.4 dB. Furthermore, the probability of detecting the frame and decoding the header reaches 90% at just 2.6 dB SNR. Decoding the remainder of the frame depends on many factors such as the modulation scheme and forward error-correction schemes applied. Here are a few general guidelines for good performance:

  • Equalization improves with more subcarriers, but the carrier frequency offset requirements have tighter restrictions;
  • While the interface supports nearly any number of subcarriers desired, synchronization greatly improves with at least \(M=32\) active (pilot/data) subcarriers;
  • More pilot subcarriers can improve performance in low SNR environments at the penalty of reduced throughput (fewer subcarriers are allocated for data);
  • Increasing the cyclic prefix is really only necessary for high multi-path environment with a large delay spread. Capacity can be increased for most short-range applications by reducing the cyclic prefix to 4 samples, regardless of the number of subcarriers;
  • Most hardware have highly non-linear RF front ends (mixers, amplifiers, etc.) which require a transmit power back-off by a few dB to ensure linearity, particularly when many subcarriers are used.