Orthogonal Frequency Division Multiplexing (OFDM) is a modern modulation technology that has become the basis for many wireless communication standards, including Wi–Fi, LTE, and DVB digital television. Its key idea is to transform a high-speed serial data stream into a set of parallel low-speed streams, each of which is transmitted on a separate orthogonal subcarrier frequency. This approach fundamentally solves the problem of intersymbol interference (ISI) and resistance to frequency-selective fading, which are especially pronounced in multipath signal propagation channels typical of modern wireless systems.
This article discusses the full cycle of signal processing in the OFDM system – from the theoretical foundations to practical implementation in the Julia programming language. We will analyze the entire process step by step: starting with the generation of quadrature amplitude modulation (QAM) symbols, through the formation of an OFDM signal and modeling its passage through a distorting channel, and ending with reception, alignment, and demodulation, which allows you to restore the original data.
The problem that OFDM is designed to solve is particularly relevant for systems with a single carrier and high transmission rates. They require short character intervals and, as a result, wide bandwidth. When such a broadband signal passes through a multipath channel with frequency-selective properties, the pulse response of the channel can be stretched over time and superimposed on several subsequent symbols. This is the intersymbol interference, which leads to reception errors.
OFDM offers an elegant solution: instead of transmitting a single fast stream in a wide band, multiple slow streams are used in narrow orthogonal sub-bands. Since each such subchannel has a much lower character rate, the duration of each character increases significantly relative to the delay time in the channel. As a result, each symbol "sees" an almost flat (undistorted) frequency response of the channel within its narrow band, which practically eliminates intersymbol interference.
From a mathematical point of view, the OFDM signal is the sum of N orthogonal subcarriers modulated by independent data symbols. The discrete output of the modulator can be written as the inverse discrete Fourier transform (IDFT) from a vector of complex modulation symbols. In practice, the inverse fast Fourier Transform (IFFT) algorithm is used to efficiently calculate this transformation, which makes the OFDM implementation computationally efficient even with a large number of subcarriers in the hundreds or thousands.
Moving on to the implementation, the code below contains two demo functions that visualize the fundamental concepts of OFDM technology. According to the myoma of the main functions, there are auxiliary ones rectpulserectpuls
The first function, helperPlotMultipath()
The second function, helperPlotOFDM()
include("Helperplot.jl")
include("HeplerplotOFDM.jl")
display(helperPlotMultipath())
display(helperPlotOFDM())
The code below is a practical implementation of the basic OFDM modulator. The key stages of OFDM signal generation are performed sequentially in it. First, the components for working with the Fourier transform are initialized and the main parameters of the system are set: 16-QAM modulation is used, where each symbol encodes 4 bits of information, and the size of the Fourier transform is set to 128 points. Then a random integer generator is created, which generates a stream of 128 characters for transmission. These symbols are sent to the 16-QAM modulator, where each of them is displayed at the corresponding point on the complex plane. The modulation constellation is pre-normalized to provide a single average signal power. The next step is the core of OFDM signal generation. The resulting vector of complex symbols txgridconjtxout
The result is visualized by plotting the real part of the generated OFDM signal in the time domain, where each sample is displayed as a vertical column. This graph allows us to observe the characteristic multicomponent structure of the OFDM symbol, which is the result of interference of multiple sinusoidal subcarriers with different amplitudes and phases specified by the modulation symbols.
using EngeeComms
using EngeeDSP
var_fft = EngeeFFT();
var_fftshift = EngeeFFTshift();
bps = 4; # Number of bits per character
M = 2^bps; # 16QAM
nFFT = 128; # Number of FFT cells
var_rand = RandomIntegerGenerator(SetSize=M,InitialSeed=1234,SampleTime=1.0,SamplesPerFrame=nFFT);
var_qam = GeneralQAMModulatorBaseband(Constellation=[-3+3im,-3+1im,-3-3im,-3-1im,-1+3im,-1+1im,-1-3im,-1-1im,3+3im,3+1im,3-3im,3-1im,1+3im,1+1im,1-3im,1-1im]./sqrt(10));
txsymbols = step!(var_rand,0.0);
txgrid = step!(var_qam,vec(txsymbols));
txout = conj(step!(var_fft,conj(txgrid)))/length(txgrid);
pl = plot([1:nFFT...],real(txout),linecolor = :blue, line=:stem,marker=:dot,)
The following code snippet simulates the simplified process of receiving and demodulating an OFDM signal under ideal conditions. After the OFDM signal is generated txoutEbNo=100rxinrxgridrxgridrxsymbolstxsymbolsrxsymbols
st = AWGN( EbNo=100, BitsPerSymbol=M, SignalPower=1);
rxin = step!(st, txout);
rxgrid =step!(var_fft, rxin);
var_demod = GeneralQAMDemodulatorBaseband(Constellation=[-3+3im,-3+1im,-3-3im,-3-1im,-1+3im,-1+1im,-1-3im,-1-1im,3+3im,3+1im,3-3im,3-1im,1+3im,1+1im,1-3im,1-1im]./sqrt(10),OutType="Integer");
rxsymbols = step!(var_demod, vec(rxgrid));
if isequal(vec(txsymbols),vec(rxsymbols))
display("Восстановленные символы соответствуют переданным символам.")
else
display("Восстановленные символы не соответствуют переданным символам.")
end
The code below sets the test input signal u1h
using EngeeDSP.Functions
u1 = [1:8...];
h = [0.4,1,0.4];
p1 = plot(u1,linecolor = :blue, line=:stem,marker=:dot, title = "The input signal");
ylims!(0,10);
xlims!(0,10);
p2 = plot(h,linecolor = :blue, line=:stem,marker=:dot,title ="Pulse response of the channel");
ylims!(0,2);
xlims!(0,10);
plots1 = plot(p1,p2,layout=(2,1),legend = false)
The following code clearly demonstrates the difference between linear and cyclic convolution, a fundamental problem that OFDM solves. First, the linear convolution is calculated yl1u1hyc1
yl1 = conv(u1,h);
yc1 = ifft(fft(u1).*fft([h;zeros(length(u1)-length(h))];));
p3 = plot(yl1,linecolor = :blue, line=:stem,marker=:dot, label = "Linear");
p4 = plot!(yc1,linecolor = :red, line=:stem,marker=:cross, label = "Cyclic");
p3
Next, a solution to the problem shown earlier is demonstrated using the method underlying OFDM — adding a cyclic prefix (CP). Last Lu1u2hyl2yl2yc1
L = length(h); # Length of channel
N = length(u1); # Length of input signal
ucp = u1[N-L+1:N]; # Use last samples of input signal as the CP
u2 = vcat(ucp,u1); # Prepend the CP to the input signal
yl2 = real(ifft(fft(u2).*fft([h;zeros(length(u2)-length(h))];))); # Convolution of input+CP and channel
yl2 = yl2[L+1:end]; # Remove CP to compare signals
p5 = plot(yl2,linecolor = :blue, line=:stem,marker=:dot, label = "Linear");
p6 = plot!(yc1,linecolor = :red, line=:stem,marker=:plus, label = "Cyclic");
p5
The code below is a complete implementation of the transmitting part (modulator) of the OFDM system, which includes all the practical elements necessary for operation in a real channel. The key parameters are set: 16-QAM modulation, 128 subcarriers (FFT points) and a cyclic prefix with a length of 8 samples. The process begins with the generation of random symbols and their modulation into complex points of the 16-QAM constellation. Then, unlike the previous simplified example, a critical step is performed here — the formation of an OFDM symbol in the time domain using the inverse FFT and the subsequent addition of a cyclic prefix. A cyclic prefix is created by copying the last ones nCPtxout
using EngeeComms
using EngeeDSP.Functions
var_fft = EngeeFFT();
var_fftshift = EngeeFFTshift();
bps = 4; # Number of bits per character
M = 2^bps; # The order of modulation
nFFT = 128; # number of FFT cells
nCP = 8; # Length of the cyclic prefix
var_rand = RandomIntegerGenerator(SetSize=M,InitialSeed=1234,SampleTime=1.0,SamplesPerFrame=nFFT);
var_qam = GeneralQAMModulatorBaseband(Constellation=[-3+3im,-3+1im,-3-3im,-3-1im,-1+3im,-1+1im,-1-3im,-1-1im,3+3im,3+1im,3-3im,3-1im,1+3im,1+1im,1-3im,1-1im]./sqrt(10));
txsymbols = vec(step!(var_rand,0.0));
txgrid = step!(var_qam, txsymbols);
txout = conj(step!(var_fft,conj(txgrid)))/length(txgrid);
# To process multiple characters, vectorize the txout matrix
txout = txout[:];
txcp = txout[nFFT-nCP+1:nFFT];
txout = vcat(txcp,txout);
A realistic communication channel for transmitting a prepared OFDM signal is modeled below.
hchan = [0.4 1 0.4]'[:,1];
st = AWGN( EbNo=100, BitsPerSymbol=M, SignalPower=1);
rxin = step!(st,txout) # Adding noise
rxin = conv(rxin,hchan); # Adding frequency dependence
The received signal is processed below — time synchronization and conversion to the frequency domain. First, a random time offset is generated, simulating the imperfection of synchronization between the transmitter and receiver. Then from the distorted signal rxinrxsync
var_rand1 = RandomIntegerGenerator(SetSize=nCP,InitialSeed=1234,SampleTime=1.0,SamplesPerFrame=1);
offset = Int64(step!(var_rand1,0.0)[1]) # the random offset is less than the length of the cyclic prefix
# Removing the cyclic prefix and synchronizing the received signal
# rxsync = rxin[nCP+1+1-offset:end];
rxsync = rxin[nCP+1:end];
rxgrid = step!(var_fft,rxsync[1:nFFT]);
The next block of code completes the receiving chain, performing the key operation of equalization and demodulation. If the equalizer is turned on, the frequency response of the channel is calculated first. hfchanrxgridrxgrideq
useEqualizer = true; # Enabling and disabling equalization
if useEqualizer
hfchan = step!(var_fft,vcat(hchan,zeros(128-length(hchan))))
# Linear phase shift related to time displacement
offsetf = exp.(-1im * 2*pi*offset * (0:nFFT-1)/nFFT);
# rxgrideq = rxgrid ./ (hfchan .* offsetf);
rxgrideq = rxgrid ./ (hfchan);
else # Errors occur without equalization
rxgrideq = rxgrid;
end
var_demod = GeneralQAMDemodulatorBaseband(Constellation=[-3+3im,-3+1im,-3-3im,-3-1im,-1+3im,-1+1im,-1-3im,-1-1im,3+3im,3+1im,3-3im,3-1im,1+3im,1+1im,1-3im,1-1im]./sqrt(10),OutType="Integer");
rxsymbols = step!(var_demod, rxgrideq[:,1]);
if maximum(txsymbols - rxsymbols) < 1e-8
display("Восстановленные символы соответствуют переданным символам.");
else
display("Восстановленные символы не соответствуют переданным символам.")
end
This work is a comprehensive demonstration of the principles of operation and practical implementation of the OFDM system, covering all key aspects of the technology: from the theoretical justification of subcarrier orthogonality to solving practical problems of synchronization and alignment in a real communication channel. Special attention was paid to the fundamental role of the cyclic prefix, an element that transforms the linear convolution of a signal with a channel impulse response into a cyclic one. This transformation is a prerequisite for the application of simple and efficient alignment in the frequency domain. It has been experimentally confirmed that even in the presence of frequency-selective fading and time delays not exceeding the length of the cyclic prefix, the OFDM system is able to provide reliable recovery of transmitted data.
The presented implementation on the Engee platform using Julia clearly demonstrates not only the correct operation of OFDM algorithms, but also the practical advantages of modern computing environments for modeling in the field of communications. Such tools allow researchers and engineers to quickly prototype, test, and visualize complex signal processing algorithms, deepening their understanding of how they work. This model provides a solid foundation for further study of more complex aspects of modern communication systems, such as adaptive modulation, multiple access methods (OFDMA), or algorithms for estimating and tracking channel parameters.