Engee 文档
Notebook

OFDM调制:操作原理和实际实现*

正交频分复用(OFDM)是一种现代调制技术,已成为许多无线通信标准的基础,包括Wi–Fi,LTE和DVB数字电视。 其关键思想是将一个高速串行数据流变换成一组并行的低速流,每个低速流在单独的正交子载波频率上传输。 这种方法从根本上解决了码间干扰(ISI)和抗频率选择性衰落的问题,这些问题在现代无线系统典型的多径信号传播信道中尤为明显。

本文讨论了OFDM系统中信号处理的整个周期-从理论基础到Julia编程语言的实际实现。 我们将逐步分析整个过程:从生成正交幅度调制(QAM)符号开始,通过形成OFDM信号并对其通过扭曲信道进行建模,并以接收,对齐和解调结束,这允许您恢复原

OFDM旨在解决的问题对于具有单载波和高传输速率的系统尤其相关。 它们需要较短的字符间隔,因此需要较宽的带宽。 当这样的宽带信号通过具有频率选择性属性的多径信道时,该信道的脉冲响应可以随时间拉伸并叠加在几个后续码元上。 这是码间干扰,导致接收错误。

OFDM提供了一种优雅的解决方案:不是在宽频带中传输单个快速流,而是在狭窄的正交子带中使用多个慢流。 由于每个这样的子信道具有低得多的字符速率,因此每个字符的持续时间相对于信道中的延迟时间显着增加。 结果,每个符号在其窄频带内"看到"信道的几乎平坦的(未失真的)频率响应,这实际上消除了码间干扰。

从数学的角度来看,OFDM信号是由独立数据符号调制的N个正交子载波的总和。 调制器的离散输出可以写为来自复数调制符号的矢量的离散傅立叶逆变换(IDFT)。 在实践中,快速傅里叶逆变换(ifft)算法被用于高效地计算这种变换,这使得OFDM实现即使在数百或数千中具有大量子载波的情况下也在计算上高效。

接下来是实现,下面的代码包含两个演示函数,它们可视化了OFDM技术的基本概念。 根据主要功能的肌瘤,有辅助的 rectpulserectpuls 用于形成所需形状的矩形脉冲。

第一个函数, helperPlotMultipath(),清楚地表明了OFDM旨在解决的主要问题-频率选择性信道中的码间干扰。 它产生两个信号:具有短字符持续时间的高速和具有延长字符持续时间的低速。 该函数模拟它们通过多径信道,并在时域和频域中绘制它们。 时域图示出了信道的脉冲响应如何叠加在高速信号的几个符号上,引起它们的干扰,而低速信号由于较长的符号而保持不失真。 在频域中,很明显,低速信号的带宽位于通道频率响应的几乎平坦的部分内,而宽带信号在其整个频带内经历显着的失真。

第二个功能, helperPlotOFDM(),说明了OFDM的关键原理-子载波的正交性。 它在时域中创建四个矩形脉冲,频率偏移量等于符号速度。 该函数绘制了这些脉冲的时间形状及其光谱特征。 频域中的曲线图尤其重要,这表明这些脉冲的频谱以这样一种方式排列,即每个频谱的峰值落在所有其他的零值上。 正是这种正交性允许子载波不相互干扰,尽管它们的频谱重叠,这确保了频带的有效使用。

In [ ]:
include("Helperplot.jl")
include("HeplerplotOFDM.jl")
display(helperPlotMultipath())
display(helperPlotOFDM())
(nothing, nothing)
(nothing, nothing)

下面的代码是基本OFDM调制器的实际实现。 中依次执行OFDM信号生成的关键阶段。 首先,初始化用于处理傅立叶变换的组件并设置系统的主要参数:使用16-QAM调制,其中每个符号编码4位信息,傅立叶变换的大小设置为128点。 然后创建一个随机整数生成器,它生成一个128个字符的流进行传输。 这些符号被发送到16-QAM调制器,在那里它们中的每一个被显示在复平面上的对应点。 调制星座被预归一化以提供单个平均信号功率。 下一步是OFDM信号生成的核心。 复数符号的结果向量 txgrid 将快速傅立叶逆变换(ifft)操作应用于输入。 执行的操作 conj (复共轭)变换前后是实现的技术特征,确保符合ifft的数学定义。 结果也通过除以变换的长度来缩放。 输出为临时OFDM信号。 txout,其是由相应QAM符号调制的所有128个正交子载波的总和。

结果通过在时域中绘制生成的OFDM信号的实部来可视化,其中每个样本显示为垂直列。 该曲线图允许我们观察OFDM符号的特征性多分量结构,其是调制符号指定的具有不同幅度和相位的多个正弦子载波的干扰的结果。

In [ ]:
using EngeeComms
using EngeeDSP

var_fft = EngeeFFT();
var_fftshift = EngeeFFTshift();   
bps = 4;    # 每个字符的位数
M = 2^bps;  # 16QAM
nFFT = 128; # FFT单元数

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,)
Out[0]:

下面的代码段模拟了在理想条件下接收和解调OFDM信号的简化过程。 OFDM信号生成后 txout 它进入通信信道。 首先,创建加性白高斯噪声(AWGN)信道模型。 参数 EbNo=100 设置一个非常高的信噪比,这实际上对应于无噪声条件,并允许您将算法的操作与其影响隔离开来。 生成的OFDM信号通过该信道,并且在输出端接收接收信号。 rxin. 然后执行OFDM解调的关键步骤-使用直接快速傅立叶变换(FFT)将接收的时间信号转换回频域。 该操作与发射机侧的IFFT相反,分离正交子载波并提取应用于它们的调制符号。 结果 rxgrid 频小区中接受的复数符号的向量。 接下来,创建一个16-QAM解调器,调谐到调制期间使用的相同星座。 这个解调器通过比较每个接收到的复数计数来决定哪个字符已经被发送 rxgrid 与参考星座的最近点。 解调器的输出是整数流 rxsymbols. 最后,检查整个处理链的正确性。 原始传输的字符 txsymbols 与解调的接受符号相比 rxsymbols. 由于仿真是在几乎无声的信道中进行的,并且没有考虑到多径传播或频率偏移等现实影响,因此解调结果应该是理想的。 消息"恢复的符号对应于发送的符号"确认调制(QAM)、变换(IFFT/FFT)和解调的基本算法在该简化方案中正确地工作。 这作为通过添加实信道效应(例如频率选择性衰落)使模型进一步复杂化的基础,这将需要引入额外的处理步骤,特别是信号均衡。

In [ ]:
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
"Восстановленные символы соответствуют переданным символам."

下面的代码设置测试输入信号 u1 和信道的脉冲响应 h,然后将它们可视化。 这种准备对于清楚地表明关键问题是必要的:在没有循环前缀的系统中,信号与信道的线性卷积导致字符间干扰,因为脉冲响应的"尾部"叠加在后续字符 理解这种效应是在OFDM中引入循环前缀的基础,该循环前缀将线性卷积转换为循环卷积,而循环卷积又允许在频域中进行简单对齐。

In [ ]:
using EngeeDSP.Functions

u1 = [1:8...]; 
h = [0.4,1,0.4];

p1 = plot(u1,linecolor = :blue, line=:stem,marker=:dot, title = "的输入信号");
ylims!(0,10);
xlims!(0,10);

p2 = plot(h,linecolor = :blue, line=:stem,marker=:dot,title ="通道的脉冲响应");
ylims!(0,2);
xlims!(0,10);
plots1 = plot(p1,p2,layout=(2,1),legend = false)
WARNING: using Functions.cos in module Main conflicts with an existing identifier.
WARNING: using Functions.sin in module Main conflicts with an existing identifier.
WARNING: using Functions.rectpuls in module Main conflicts with an existing identifier.
Out[0]:

下面的代码清楚地演示了线性卷积和循环卷积之间的区别,这是OFDM解决的一个基本问题。 首先,计算线性卷积 yl1 信号 u1 带通道 h,模拟通信信道中的真实失真。 然后,得到循环卷积 yc1 矢量用相同长度的零填充,并通过FFT在频域中相乘,然后将结果转换回时域。 具有两个重叠序列的图清楚地表明线性和循环卷积的结果不匹配。 正是这种差异导致码间干扰,并解释了为什么在OFDM中添加循环前缀:它人为地使卷积循环,这是随后在频域中简单对齐的必要条件。

In [ ]:
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 = "线性的");
p4 = plot!(yc1,linecolor = :red, line=:stem,marker=:cross, label = "循环");
p3
Out[0]:

接下来,使用OFDM基础的方法—添加循环前缀(CP)来演示前面所示问题的解决方案。 最后一次 L 原始信号的样本 u1 它们被复制并添加到它的开头,形成一个扩展信号。 u2. 当这个已经具有循环前缀的信号通过信道时,它与脉冲响应的线性卷积 h 使用FFT计算,给出结果 yl2. 从开头去掉前缀后 yl2 结果是完全匹配先前计算的循环卷积的序列。 yc1. 该图证实了这种巧合。 因此,该代码清楚地显示了循环前缀如何神奇地将真实信道的线性卷积转换为循环卷积,这是OFDM技术的基石,然后使得可以简单有效地补偿频域中的信道失真。

In [ ]:
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 = "线性的");
p6 = plot!(yc1,linecolor = :red, line=:stem,marker=:plus, label = "循环");
p5
Out[0]:

下面的代码是OFDM系统的发射部分(调制器)的完整实现,其包括在真实信道中操作所必需的所有实用元素。 关键参数设置:16-QAM调制,128个子载波(FFT点)和长度为8个采样的循环前缀。 该过程始于随机符号的产生及其调制成16-QAM星座图的复数点。 然后,与前面的简化示例不同,这里执行关键步骤-使用逆FFT在时域中形成OFDM符号以及随后添加循环前缀。 循环前缀是通过复制最后一个前缀来创建的 nCP 对所生成的OFDM符号进行计数并将此副本添加到其开头。 结果是准备发射信号。 txout,其已经包含允许其抵抗多径信道中的码间干扰的保护间隔。 该阶段完成信号的准备,以便通过扭曲介质传输。

In [ ]:
using EngeeComms
using EngeeDSP.Functions

var_fft = EngeeFFT();
var_fftshift = EngeeFFTshift();

bps = 4;    # 每个字符的位数
M = 2^bps;  # 调制的阶数
nFFT = 128; # FFT单元数
nCP = 8;    # 循环前缀的长度
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);
# 要处理多个字符,请对txout矩阵进行矢量化
txout = txout[:];
txcp = txout[nFFT-nCP+1:nFFT];
txout = vcat(txcp,txout);

下面对用于发送准备好的OFDM信号的现实通信信道进行建模。

In [ ]:
hchan = [0.4 1 0.4]'[:,1];
st = AWGN( EbNo=100, BitsPerSymbol=M, SignalPower=1);

rxin = step!(st,txout)                    # 增加噪音
rxin = conv(rxin,hchan);          # 增加频率依赖性
Out[0]:
138×1 Matrix{ComplexF64}:
 -0.004613386424821975 + 0.01018410397582413im
  0.013652038144603758 + 0.043197035894136454im
    0.0492270347950972 + 0.06620316556688652im
  0.001454364935108754 + 0.07097230525150806im
  -0.05289394861331751 + 0.05033647217286611im
  -0.10623842534926266 + 0.007631835760060518im
  -0.07220724444152067 + 0.0793883297034132im
  -0.08507639447568857 + 0.058457945333152114im
 -0.059006500236081214 + 0.02197075541671166im
 -0.027234998054386908 + 0.08887937404769576im
  -0.07888122025262403 + 0.18952011862455057im
 -0.008990523559159254 + 0.1256668429507964im
   0.11094712063193093 + 0.00907828481820107im
                       ⋮
   0.09460731132990707 - 0.0004812715748047959im
   0.07015399117915509 + 0.008909309963818066im
   -0.0371498347875724 + 0.02023774785437928im
 -0.013717251481083662 + 0.04775861884404914im
   0.04922799919124048 + 0.06620289608421998im
  0.001458269015173954 + 0.07096986997488722im
 -0.052889984395700716 + 0.050335079514633896im
  -0.10623688395213834 + 0.007637221385099306im
  -0.07220640713224899 + 0.07939103226752264im
  -0.08507420603847964 + 0.05846119723940281im
  -0.06888399812904436 + 0.012094766301959506im
 -0.016912783242377258 + 0.0017110779803630666im

接收信号进行实时同步处理并转换到频域。 首先,产生随机时间偏移,模拟发射机和接收机之间同步的不完善。 然后从失真的信号 rxin 循环前缀被去除,并且OFDM符号的剩余有用部分被分配给 rxsync. 关键步骤是对这个同步片段应用直接FFT,它将信号从时间表示转换回频率表示,分离正交子载波并为随后的对齐和解调准备数据。

In [ ]:
var_rand1 = RandomIntegerGenerator(SetSize=nCP,InitialSeed=1234,SampleTime=1.0,SamplesPerFrame=1);
offset = Int64(step!(var_rand1,0.0)[1])      # 随机偏移量小于循环前缀的长度
# 去除循环前缀并同步接收信号
# rxsync = rxin[nCP+1+1-offset:end];
rxsync = rxin[nCP+1:end];
rxgrid = step!(var_fft,rxsync[1:nFFT]);

下一码块完成接收链,执行均衡和解调的关键操作。 如果均衡器打开,则首先计算信道的频率响应。 hfchan 使用FFT。 然后在频域中接收到的符号 rxgrid 它们除以该响应,该响应补偿了通道引入的幅度和相位失真。 产生的对齐字符 rxgrideq 它们被馈送到16-QAM解调器,该解调器将它们转换回整数流。 最终检查将解调的字符与原始传输的字符进行比较。 当系统在启用对准的情况下正常工作时,它们必须完全匹配,这证实了OFDM结合循环前缀以对抗频率选择性信道中的失真的有效性。

In [ ]:
useEqualizer = true;                # 启用和禁用均衡
if useEqualizer
    hfchan = step!(var_fft,vcat(hchan,zeros(128-length(hchan))))
    # 与时间位移相关的线性相移
    offsetf = exp.(-1im * 2*pi*offset * (0:nFFT-1)/nFFT);
    # rxgrideq = rxgrid ./ (hfchan .* offsetf);
    rxgrideq = rxgrid ./ (hfchan);
else # 在没有均衡的情况下发生错误
    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
"Восстановленные символы соответствуют переданным символам."

输出

这项工作是对OFDM系统的操作原理和实际实现的全面演示,涵盖了该技术的所有关键方面:从子载波正交性的理论论证到解决真实通信信道中同步和对准的实际问题。 特别注意循环前缀的基本作用,循环前缀是将具有信道脉冲响应的信号的线性卷积转换为循环前缀的元素。 这种变换是在频域中应用简单高效对准的先决条件。 已经实验证实,即使在存在频率选择性衰落和不超过循环前缀长度的时间延迟的情况下,OFDM系统也能够提供传输数据的可靠恢复。

使用Julia在Engee平台上展示的实现不仅清楚地展示了OFDM算法的正确操作,而且还展示了现代计算环境在通信领域建模的实际优势。 借助这些工具,研究人员和工程师可以快速制作复杂信号处理算法的原型、测试和可视化,从而加深对其工作方式的理解。 该模型为进一步研究现代通信系统的更复杂方面提供了坚实的基础,例如自适应调制、多址接入方法(OFDMA)或用于估计和跟踪信道参数的算法。