In the previous tutorials we have created only the basic building blocks for wireless communication. We have also used the basic framegen64 and framesync64 objects to transmit and receive simple framing data. This tutorial extends the work on the previous tutorials by introducing a flexible framing structure that uses a parallel data transmission scheme that permits arbitrary parametrization (modulation, forward error-correction, payload length, etc.) with minimal reconfiguration at the receiver.

Problem Statement

The framing tutorial loaded data serially onto a single carrier. Another option is to load data onto many carriers in parallel; however it is desirable to do so such that bandwidth isn't wasted. By allowing the "subcarriers" to overlap in frequency, the system approaches the theoretical maximum capacity of the channel. Several multiplexing schemes are possible, but by far the most common is generically known as orthogonal frequency divisional multiplexing (OFDM) which uses a square temporal pulse shaping filter for each subcarrier, separated in frequency by the inverse of the symbol rate. This conveniently allows data to be loaded into the input of an inverse discrete Fourier transform (DFT) at the transmitter and (once time and carrier synchronized) de-multiplexed with a regular DFT at the receiver. For computational efficiency the DFT may be implemented with a fast Fourier transform (FFT) which is mathematically equivalent but considerably faster. Furthermore, because of the cyclic nature of the DFT a certain portion (usually on the order of 10%) of the tail of the generated symbol may be copied to its head before transmitting; this is known as the cyclic prefix which can eliminate inter-symbol interference in the presence of multi-path channel environments. Carrier frequency and symbol timing offsets can be tracked and corrected by inserting known pilot subcarriers in the signal at the transmitter; because the receiver knows the pilot symbols it can make an accurate estimate of the channel conditions for each OFDM symbol. As an example, the well-known Wi-Fi 802.11a standard uses OFDM with 64 subcarriers (52 for data, 4 pilots, and 8 disabled for guard bands) and a 16-sample cyclic prefix.

In this tutorial we will create a simple pair of OFDM framing objects; the generator ( ofdmflexframegen ), like the framegen64 object, has a simple interface that accepts raw data in, frame samples out. The synchronizer ( ofdmflexframesync ), like the framesync64 object, accepts samples and invokes a callback function for each frame that it detects, compensating for sample timing and carrier offsets and multi-path channels. The framing objects can be created with nearly any even-length transform (number of subcarriers), cyclic prefix, and arbitrary null/pilot/data subcarrier allocation.

Note: While nearly any arbitrary configuration is supported, the performance of synchronization is greatly dependent upon the choice of the number, type, and allocation of subcarriers.

Furthermore, the OFDM frame generator permits many different parameters (e.g. modulation/coding schemes, payload length) which are detected automatically at the receiver without any work on your part.

Setting up the Environment

As with the other tutorials I assume that you are using gcc to compile your programs and link to appropriate libraries. Create a new file ofdmflexframe.c and include the headers stdio.h , stdlib.h , math.h , complex.h , and liquid/liquid.h . Add the int main() definition so that your program looks like this:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <liquid/liquid.h>

int main() {
    printf("done.\n");
    return 0;
}

Compile and link the program using gcc :

$ gcc -Wall -o ofdmflexframe ofdmflexframe.c -lm -lc -lliquid

The flag " -Wall " tells the compiler to print all warnings (unused and uninitialized variables, etc.), " -o ofdmflexframe " specifies the name of the output program is " ofdmflexframe ", and " -lm -lc -lliquid " tells the linker to link the binary against the math, standard C, and liquid DSP libraries, respectively. Notice that the above command invokes both the compiler and the linker collectively. If the compiler did not give any errors, the output executable ofdmflexframe is created which can be run as

$ ./ofdmflexframe

and should simply print " done. " to the screen. You are now ready to add functionality to your program.

OFDM Framing Structure

In this tutorial we will be using the ofdmflexframegen and ofdmflexframesync objects in liquid which realize the framing generator (transmitter) and synchronizer (receiver). The OFDM framing structure is briefly described here (for a more detailed description, see[ref:section-framing-ofdmflexframe] ). The ofdmflexframe generator and synchronizer objects together realize a simple framing structure for loading data onto a reconfigurable OFDM physical layer. The generator encapsulates an 8-byte user-defined header and a variable-length buffer of uncoded payload data and fully encodes a frame of OFDM symbols ready for transmission. The user may define many physical-layer parameters of the transmission, including the number of subcarriers and their allocation (null/pilot/data), cyclic prefix length, forward error-correction coding, modulation scheme, and others. The synchronizer requires the same number of subcarriers, cyclic prefix, and subcarrier allocation as the transmitter, but can automatically determine the payload length, modulation scheme, and forward error-correction of the receiver. Furthermore, the receiver can compensate for carrier phase/frequency and timing offsets as well as multi-path fading and noisy channels. The received data are returned via a callback function which includes the modulation and error-correction schemes used as well as certain receiver statistics such as its received signal strength and error vector magnitude .

Creating the Frame Generator

The ofdmflexframegen object can be generated with the ofdmflexframegen_create(M,c,t,p,props) method which accepts five arguments:

  • \(M\) is an unsigned int representing the total number of subcarriers
  • \(c\) is an unsigned int representing the length of the cyclic prefix
  • \(t\) is an unsigned int representing the length of the overlapping tapered window between OFDM symbols ( 0\(\leq\) t\(\leq\) c ).
  • \(\vec{p}\) is an \(M\) -element array of unsigned char which gives the subcarrier allocation (e.g. which subcarriers are nulled/disabled, which are pilots, and which carry data). Setting to NULL tells the ofdmflexframegen object to use the default subcarrier allocation (see [ref:section-framing-ofdmflexframe-subcarrier-allocation] for details);
  • props is a special structure called ofdmflexframegenprops_s which gives some basic properties including the inner/outer forward error-correction scheme(s) to use ( fec0 , fec1 ), and the modulation scheme ( mod_scheme ) and depth ( mod_depth ). The properties object can be initialized to its default by using ofdmflexframegenprops_init_default() .

To begin, create a frame generator having 64 subcarriers with cyclic prefix of 16 samples, a tapering window of 4 samples, the default subcarrier allocation, and default properties as

// create frame generator with default parameters
ofdmflexframegen fg = ofdmflexframegen_create(64, 16, 4, NULL, NULL);

As with all structures in liquid you will need to invoke the corresponding destroy() method when you are finished with the object.

Now allocate memory for the header (8 bytes) and payload (120 bytes) data arrays. Raw "message" data are stored as arrays of type unsigned char in liquid .

unsigned char header[8];
unsigned char payload[120];

Initialize the header and payload arrays with whatever values you wish. Finally you will need to create a buffer for storing the frame samples. Unlike the framegen64 object in the previous tutorial which generates the entire frame at once, the ofdmflexframegen object generates each symbol independently. For this framing structure you will need to allocate a buffer of samples of type float complex which can be any size you wish.

unsigned int  buf_len = 200;
float complex buf[buf_len];

Generating the frame consists of two steps: assemble and write. Assembling the frame simply involves invoking the ofdmflexframegen_assemble(fg,header,payload,payload_len) method which accepts the frame generator object as well as the header and payload arrays we initialized earlier. Internally, the object encodes and modulates the frame, but does not write the OFDM symbols yet. To write the OFDM time-series symbols, invoke the ofdmflexframegen_write() method. This method accepts three arguments: the frame generator object, the output buffer we created earlier, the length of the buffer. Invoking the ofdmflexframegen_write() method repeatedly generates each symbol of the frame and returns a flag indicating if the entire frame has been written to the buffer (excess samples are produced as zeros).

Add the instructions to assemble and write a frame one symbol at a time to your source code:

// assemble the frame and print
ofdmflexframegen_assemble(fg, header, payload, payload_len);
ofdmflexframegen_print(fg);

// generate the frame one OFDM symbol at a time
int frame_complete = 0;     // flag indicating if frame is completed
while (!frame_complete) {
    // write samples to the buffer
    frame_complete = ofdmflexframegen_write(fg, buf, buf_len);

    // print status
    printf("ofdmflexframegen wrote to buffer%s\n",
        frame_complete ? " (frame complete)" : "");
}

That's it! This completely assembles the frame complete with error-correction coding, pilot subcarriers, and the preamble necessary for synchronization. You may generate another frame simply by initializing the data in your header and payload arrays, assembling the frame, and then writing the symbols to the buffer. Keep in mind, however, that the buffer is overwritten each time you invoke ofdmflexframegen_write() , so you will need to do something with the data with each iteration of the loop. Your program should now look similar to this:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#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)

    // allocate memory for header, payload, sample buffer
    unsigned int  buf_len = 200;            // buffer size
    float complex buf[buf_len];             // time-domain buffer
    unsigned char header[8];                // header
    unsigned char payload[payload_len];     // payload

    // create frame generator object with default properties
    ofdmflexframegen fg = ofdmflexframegen_create(M, cp_len, taper_len, NULL, NULL);

    unsigned int i;

    // initialize header/payload and assemble frame
    for (i=0; i<8; i++)           header[i]  = i      & 0xff;
    for (i=0; i<payload_len; i++) payload[i] = rand() & 0xff;
    ofdmflexframegen_assemble(fg, header, payload, payload_len);
    ofdmflexframegen_print(fg);

    // generate frame one OFDM symbol at a time
    int frame_complete = 0;
    while (!frame_complete) {
        // generate symbol (write samples to buffer)
        frame_complete = ofdmflexframegen_write(fg, buf, buf_len);

        // print status
        printf("ofdmflexframegen wrote to buffer%s\n",
            frame_complete ? " (frame complete)" : "");
    }

    // destroy objects and return
    ofdmflexframegen_destroy(fg);
    printf("done.\n");
    return 0;
}

Running the program should produce an output similar to this:

ofdmflexframegen:
    num subcarriers     :   64
      * NULL            :   14
      * pilot           :   6
      * data            :   44
    cyclic prefix len   :   16
    taper len           :   4
    properties:
      * mod scheme      :   quaternary phase-shift keying
      * fec (inner)     :   none
      * fec (outer)     :   none
      * CRC scheme      :   CRC (32-bit)
    frame assembled     :   yes
    payload:
      * decoded bytes   :   120
      * encoded bytes   :   124
      * modulated syms  :   496
    total OFDM symbols  :   22
      * S0 symbols      :   2 @ 80
      * S1 symbols      :   1 @ 80
      * header symbols  :   7 @ 80
      * payload symbols :   12 @ 80
    spectral efficiency :   0.5455 b/s/Hz
ofdmflexframegen wrote to buffer
ofdmflexframegen wrote to buffer
ofdmflexframegen wrote to buffer
  ...
ofdmflexframegen wrote to buffer (frame complete)
done.

Notice that the ofdmflexframegen_print() method gives a lot of information, including the number of null, pilot, and data subcarriers, the number of modulated symbols, the number of OFDM symbols, and the resulting spectral efficiency. Furthermore, notice that the first three symbols have only 64 samples while the remaining have 80; these first three symbols are actually part of the preamble to help the synchronizer detect the presence of a frame and estimate symbol timing and carrier frequency offsets.

Creating the Frame Synchronizer

The OFDM frame synchronizer's purpose is to detect the presence of a frame, correct for channel impairments (such as a carrier frequency offset), decode the data (correct for errors in the presence of noise), and pass the resulting data back to the user. In our program we will pass to the frame synchronizer samples in the buffer created by the generator, without adding noise, carrier frequency offsets, or other channel impairments. The ofdmflexframesync object can be generated with the ofdmflexframesync_create(M,c,t,p,callback,userdata) method which accepts six arguments:

  • \(M\) is an unsigned int representing the total number of subcarriers
  • \(c\) is an unsigned int representing the length of the cyclic prefix
  • \(t\) is an unsigned int representing the length of the overlapping tapered window between OFDM symbols ( 0\(\leq\) t\(\leq\) c ).
  • \(\vec{p}\) is an \(M\) -element array of unsigned char which gives the subcarrier allocation (see [ref:section-tutorial-ofdmflexframe-framegen] )
  • callback is a pointer to your callback function which will be invoked each time a frame is found and decoded.
  • userdata is a void pointer that is passed to the callback function each time it is invoked. This allows you to easily pass data from the callback function. Set to NULL if you don't wish to use this.

Notice that the first three arguments are the same as in the ofdmflexframegen_create() method; the values of these parameters at the synchronizer need to match those at the transmitter in order for the synchronizer to operate properly. When the synchronizer does find a frame, it attempts to decode the header and payload and invoke a user-defined callback function. A basic description of how callback functions work is given in the basic framing tutorial.

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. Here is a brief description of the callback function's arguments:

  • _header is a pointer to the 8 bytes of decoded header data (remember that header[8] array you created with the ofdmflexframegen object?). This pointer is not static and cannot be used after returning from the callback function. This means that it needs to be copied locally before returning in order for you to retain the data.
  • _header_valid is simply a flag to indicate if the header passed its cyclic redundancy check (" 0 " means invalid, " 1 " means valid). 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 pointer to the decoded payload data. Like the header, this pointer is not static and cannot be used after returning from the callback function. Again, this means that it needs to be copied locally for you to retain the 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 that indicates some useful PHY information to the user (such as RSSI and EVM). We will ignore this information in our program, but it can be quite useful for certain applications. For more information on the framesyncstats_s structure, see[ref:section-framing-framesyncstats_s] .
  • _userdata is a void pointer given to the ofdmflexframesync_create() method that is passed to this callback function and can represent anything you want it to. 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.

This can seem a bit overwhelming at first, but relax! The next version of our program will only add about 20 lines of code to our previous program.

Putting it All Together

First create your callback function at the beginning of the file, just before the int main() definition; you may give it whatever name you like (e.g. mycallback() ). For now ignore all the function inputs and just print a message to the screen that indicates that the callback has been invoked, and return the integer zero ( 0 ). This return value for the callback function should always be zero and is reserved for future development. Within your main() definition, create an instance of ofdmflexframesync using the ofdmflexframesync_create() method, passing it 64 for the number of subcarriers, 16 for the cyclic prefix length, NULL for the subcarrier allocation (default), mycallback , and NULL for the userdata. Print the newly created synchronizer object to the screen if you like:

ofdmflexframesync fs = ofdmflexframesync_create(64, 16, 4, NULL, mycallback, NULL);

Within the while loop that writes the frame symbols to the buffer, invoke the synchronizer's execute() method, passing to it the frame synchronizer object you just created ( fs ), the buffer of frame symbols, and the number of samples written to the buffer ( num_written ):

ofdmflexframesync_execute(fs, buffer, num_written);

Finally, destroy the frame synchronizer object along with the frame generator at the end of the file. That's it! Your program should look something like this:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#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");
    printf("  header (%s)\n",  _header_valid  ? "valid" : "INVALID");
    printf("  payload (%s)\n", _payload_valid ? "valid" : "INVALID");
    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 int payload_len = 120;         // length of payload (bytes)

    // allocate memory for header, payload, sample buffer
    unsigned int  buf_len = 200;            // buffer size
    float complex buf[buf_len];             // time-domain buffer
    unsigned char header[8];                // header
    unsigned char payload[payload_len];     // payload

    // create frame generator object with default properties
    ofdmflexframegen fg =
        ofdmflexframegen_create(M, cp_len, taper_len, NULL, NULL);

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

    unsigned int i;

    // initialize header/payload and assemble frame
    for (i=0; i<8; i++)           header[i]  = i      & 0xff;
    for (i=0; i<payload_len; i++) payload[i] = rand() & 0xff;
    ofdmflexframegen_assemble(fg, header, payload, payload_len);

    ofdmflexframegen_print(fg);
    ofdmflexframesync_print(fs);

    // generate frame and synchronize
    int frame_complete = 0;
    while (!frame_complete) {
        // generate symbol (write samples to buffer)
        frame_complete = ofdmflexframegen_write(fg, buf, buf_len);

        // receive symbol (read samples from buffer)
        ofdmflexframesync_execute(fs, buf, buf_len);
    }

    // destroy objects and return
    ofdmflexframegen_destroy(fg);
    ofdmflexframesync_destroy(fs);
    printf("done.\n");
    return 0;
}

Compile and run your program as before and verify that your callback function was indeed invoked. Your output should look something like this:

ofdmflexframegen:
    ...
ofdmflexframesync:
    num subcarriers     :   64
      * NULL            :   14
      * pilot           :   6
      * data            :   44
    cyclic prefix len   :   16
    taper len           :   4
***** callback invoked!
  header (valid)
  payload (valid)
done.

Your program has demonstrated the basic functionality of the OFDM frame generator and synchronizer. The previous tutorial on framing ([ref:section-tutorial-framing] ) added a carrier offset and noise to the signal before synchronizing; these channel impairments are addressed in the next section.

Final Program

In this last portion of the OFDM framing tutorial, we will modify our program to change the modulation and coding schemes from their default values as well as add channel impairments (noise and carrier frequency offset). Information on different modulation schemes can be found in [ref:section-modem-digital] ; information on different forward error-correction schemes and validity checks cane be found in [ref:section-fec] . To begin, add the following parameters to the beginning of your main() definition with the other options:

modulation_scheme ms = LIQUID_MODEM_PSK8;   // payload modulation scheme
fec_scheme fec0  = LIQUID_FEC_NONE;         // inner FEC scheme
fec_scheme fec1  = LIQUID_FEC_HAMMING128;   // outer FEC scheme
crc_scheme check = LIQUID_CRC_32;           // data validity check
float dphi  = 0.001f;                       // carrier frequency offset
float SNRdB = 20.0f;                        // signal-to-noise ratio [dB]

The first five options define which modulation, coding, and error-checking schemes should be used in the framing structure. The dphi and SNRdB are the carrier frequency offset (\(\Delta\phi\) ) and signal-to-noise ratio (in decibels), respectively. To change the framing generator properties, create an instance of the ofdmflexframegenprops_s structure, query the current properties list with ofdmflexframegen_getprops() , override with the properties of your choice, and then reconfigure the frame generator with ofdmflexframegen_setprops() , viz.

// re-configure frame generator with different properties
ofdmflexframegenprops_s fgprops;
ofdmflexframegen_getprops(fg, &fgprops);
fgprops.check           = check;
fgprops.fec0            = fec0;
fgprops.fec1            = fec1;
fgprops.mod_scheme      = ms;
ofdmflexframegen_setprops(fg, &fgprops);

Add this code somewhere after you create the frame generator, but before you assemble the frame.

Adding channel impairments can be a little tricky. We have specified the signal-to-noise ratio in decibels (dB) but need to compute the equivalent noise standard deviation. Assuming that the signal power is unity, the noise standard deviation is just \(\sigma_n = 10^{-\textup{SNRdB}/20}\) . The carrier frequency offset can by synthesized with a phase variable that increases by a constant for each sample, \(k\) . That is, \(\phi_{k} = \phi_{k-1} + \Delta\phi\) . Each sample in the buffer can be multiplied by the resulting complex sinusoid generated by this phase, with noise added to the result:

$$ \textup{buffer}[k] \leftarrow \textup{buffer}[k] e^{j\phi_{k}} + \sigma_n(n_i + jn_q) $$

Initialize the variables for noise standard deviation and carrier phase before the while loop as

float nstd = powf(10.0f, -SNRdB/20.0f); // noise standard deviation
float phi = 0.0f;                       // channel phase

Create an inner loop (inside the while loop) that modifies the contents of the buffer after the frame generator, but before the frame synchronizer:

// channel impairments
for (i=0; i < num_written; i++) {
    buffer[i] *= cexpf(_Complex_I*phi); // apply carrier offset
    phi += dphi;                        // update carrier phase
    cawgn(&buffer[i], nstd);            // add noise
}

Your program should look something like this:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#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");
    printf("  header (%s)\n",  _header_valid  ? "valid" : "INVALID");
    printf("  payload (%s)\n", _payload_valid ? "valid" : "INVALID");
    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 int payload_len = 120;             // length of payload (bytes)
    modulation_scheme ms = LIQUID_MODEM_PSK8;   // payload modulation scheme
    fec_scheme fec0  = LIQUID_FEC_NONE;         // inner FEC scheme
    fec_scheme fec1  = LIQUID_FEC_HAMMING128;   // outer FEC scheme
    crc_scheme check = LIQUID_CRC_32;           // data validity check
    float dphi  = 0.001f;                       // carrier frequency offset
    float SNRdB = 20.0f;                        // signal-to-noise ratio [dB]

    // allocate memory for header, payload, sample buffer
    unsigned int  buf_len = 200;                // buffer size
    float complex buf[buf_len];                 // time-domain buffer
    unsigned char header[8];                    // header
    unsigned char payload[payload_len];         // payload

    // create frame generator with default properties
    ofdmflexframegen fg =
        ofdmflexframegen_create(M, cp_len, taper_len, NULL, NULL);

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

    unsigned int i;

    // re-configure frame generator with different properties
    ofdmflexframegenprops_s fgprops;
    ofdmflexframegen_getprops(fg,&fgprops); // query the current properties
    fgprops.check           = check;        // set the error-detection scheme
    fgprops.fec0            = fec0;         // set the inner FEC scheme
    fgprops.fec1            = fec1;         // set the outer FEC scheme
    fgprops.mod_scheme      = ms;           // set the modulation scheme
    ofdmflexframegen_setprops(fg,&fgprops); // reconfigure the frame generator

    // initialize header/payload and assemble frame
    for (i=0; i<8; i++)           header[i]  = i      & 0xff;
    for (i=0; i<payload_len; i++) payload[i] = rand() & 0xff;
    ofdmflexframegen_assemble(fg, header, payload, payload_len);
    ofdmflexframegen_print(fg);

    // channel parameters
    float nstd = powf(10.0f, -SNRdB/20.0f); // noise standard deviation
    float phi = 0.0f;                       // channel phase

    // generate frame and synchronize
    int frame_complete = 0;
    while (!frame_complete) {
        // generate symbol (write samples to buffer)
        frame_complete = ofdmflexframegen_write(fg, buf, buf_len);

        // channel impairments
        for (i=0; i<buf_len; i++) {
            buf[i] *= cexpf(_Complex_I*phi); // apply carrier offset
            phi += dphi;                        // update carrier phase
            cawgn(&buf[i], nstd);            // add noise
        }

        // receive symbol (read samples from buffer)
        ofdmflexframesync_execute(fs, buf, buf_len);
    }

    // destroy objects and return
    ofdmflexframegen_destroy(fg);
    ofdmflexframesync_destroy(fs);
    printf("done.\n");
    return 0;
}

Run this program to verify that the frame is indeed detected and the payload is received free of errors. For a more detailed program, see examples/ofdmflexframesync_example.c in the main liquid directory; this example also demonstrates setting different properties of the frame, but permits options to be passed to the program from the command line, rather than requiring the program to be re-compiled. Play around with various combinations of options in the program, such as increasing the number of subcarriers, modifying the modulation scheme, decreasing the signal-to-noise ratio, and applying different forward error-correction schemes.

What happens to the spectral efficiency of the frame when you increase the payload from 120 bytes to 400?

Answer: the spectral efficiency increases from 0.5455 to 0.8511 because the preamble accounts for less of frame (less overhead)

when you decrease the cyclic prefix from 16 samples to 4?

Answer: the spectral efficiency increases from 0.5455 to 0.6417 because fewer samples are used for each OFDM symbol (less overhead)

when you increase the number of subcarriers from 64 to 256?

Answer: the spectral efficiency decreases from 0.5455 to 0.4412 because the preamble accounts for *more* of the frame (increased overhead).

What happens when the frame generator is created with 64 subcarriers and the synchronizer is created with only 62?

Answer: the synchronizer cannot detect frame because the subcarriers don't match (different pilot locations, etc.

when the cyclic prefix lengths don't match?

Answer: the synchronizer cannot decode header because of symbol timing mis-alignment.

What happens when you decrease the SNR from 20 dB to 10 dB?

Answer: the payload will probably be invalid because of too many errors.

when you decrease the SNR to 0 dB?

Answer: the frame header will probably be invalid because of too many errors.

when you decrease the SNR to -10 dB?

Answer: the frame synchronizer will probably miss the frame entirely because of too much noise.

What happens when you increase the carrier frequency offset from 0.001 to 0.1?

Answer: the frame isn't detected because the carrier offset is too large for the synchronizer to correct. Try decreasing the number of subcarriers from 64 to 32 and see what happens.