前言
最近在学习用GNURadio实现OFDM通信功能,看到有一篇英文博客写的非常详细,于是便按照自己的理解翻译成中文转载,但还有很多地方不理解,如果有疑惑还是看看原文吧
1.Introduction
GNURadio中所提供的模块 OFDM Transmitter
and OFDM Receiver
是分级的,其内部由其他的模块组成。OFDM的核心思想是使子载波正交(独立)的方法,即在接收端计算信号的IFFT,并在每组数据片段添加循环前缀
2.Transmission
这是对 gr-digital-python/digital/ofdm_txrx.py
的重构
2.1 File source
将字节中包含比特的情况简化设定为每字节含 1 比特,然后按需求设定长度为对应 2 个字节来进行后续相关操作等情况的说明。
2.2 Stream to tagged stream
Classic Stream是指普通的字节流,不包含任何额外信息或分组。
实现这一目的的技术是每n个样本附上一个“标签”,这个标签仅表明跟随组的长度。从本质上来说,标签只是一对键值对。
2.3 Stream CRC32
这个模块是在数据最后加CRC32检错码的,这个检错码为32bit长,让我们传输的字节长度变成了 32 8 + 2 = 6 \frac{32}{8}+2=6 832+2=6
2.4 Header generator
名词介绍:
复符号:编码数据的基本单元,一般可以用星座图来描述
OFDM symbol:由n个复符号组成的集合,这里的n由FFT大小决定
OFDM frame/burst:OFDM帧,是一组OFDM符号所组成的数据传输单元和其他内容。这里的其他内容包括包括三个额外的 OFDM 符号:一个头部 OFDM 符号以及两个 “同步字”。后面我们可以看到这些内容的作用,
在调制之前OFDM的头部符号包含三部分数据:
1.12bit用于表示数据包的长度(这里是6,包括来自CRC的额外4个字节)
2.12bit用于表示头部编号,在每个数据包之后递增(对十六静止数xFFFF取模)
3.8bit是由上述数字组合计算得出的CRC值
但是如果想要实际传输这些数字量,必须要将其映射到复符号上,这取决于所使用的调制方案。在实际映射之前,我们需要先将其分开编码(原文说的是encode,但我感觉就是分开了)
如果我们使用BPSK进行编码,BPSK本质上是在一个复符号的相位中编码1比特信息,并且得到的是全实的复符号。使用BPSK时,编码后每个字节只有LSB代表实际数据,使用QPSK时编码后每个字节的最低2比特代表数据。这样做的原因是我们需要将每个字节映射到复符号,所以不能将一个字节完全填满数据
假设我们使用BPSK,32bit的数据会被编码成32字节(1字节只编码了1bit的数据),但我们将其作为一个单独的OFDM符号发送。有些敏锐的读者还会注意到,BPSK编码会导致实际数据包的长度变成了 6 × 8 = 48 6×8=48 6×8=48,这和头部符号的内容不符,但在后面会有模块利用补偿因子8使其相符。
2.5 Repack bits
重新打包bit数据流,功能是将bit数据流中的bit按编码方式重新打包放入新的byte中。例如假如使用BPSK编码数据流[0][F],就是将每一位数据放入一个新的字节,那么得到的新数据流就是[0][0][0][0][1][1][1][1],这里面[]中代表一个字节。
2.6 Chunks to symbols
这里将每个字节按照星座图(Constellation)映射成复符号,其主要思想就是将比特序列映射到复平面上的一个“位置”,即一对实部或虚部坐标,这样他们就可以作为正交信号进行传输。
假如我们使用BPSK,就是将每个字节分别映射复符号的实部上,例如将bit 1映射成实部1,将bit 0映射成实部-1
2.7 Tagged stream mux
这个模块将两个数据流按顺序合并,输出是第一个数据流后跟随着第二个数据流
这里是按符号将数据流合并。
2.8 OFDM carrier allocator
(1)基本原理
现在我们输入的是一串位数长度为80的复符号数据流(总长度80=头部符号长度32+传输数据长度48,实际上只有两个符号)。而这些符号实际上是需要调制在正交子载波上传输的,IEEE标准一般使用64个正交子载波,但一般建议在-26到26的子载波上进行传输,总共52个子载波,也就是[-32,-26]底部6个子载波和[26,31]顶部5个子载波应该保持为空,这是为了防止过多的带外发射;而在传输带内,零子载波也要保持为空,这是针对载波频率处有干扰的硬件设备的措施;而子载波(-21,-7,7,21)为导频子载波,传输的是收发端都默认的(1,1,1,-1),用于信道估计和补偿,所以实际上有效载荷OFDM符号只使用64-11-1-4=48个,即可以承载48个复符号
(2)符号构成
随便找了张网图,里面的signal可以大致理解成OFDM Header
Header = Syn1 + Syn2 + OFDM Header
这里我们需要传输的有一个头部符号,两个用于同步字的符号,一个用于传输数据+CRC检错码的符号
同步字1:通常用于时间同步,即估计传输数据何时开始,所以它首先被发送
同步字2:用于信道估计和粗频率偏移
(3)操作过程
①直接将每个同步字复制到输出缓冲区中(每个同步字都是64个复符号)
②对于头部符号和数据符号,将一个符号复制到每个已占用的子载波上
③用导频序列填充导频子载波
④返回向量大小确保其格式正确
我们将四个符号变成了一串含有4个OFDM符号的向量
(4)streams vs vectors
stream是逐个传递每个样本,而vector则是在一个向量中包含若干数量的样本,对向量的操作被认为是并行运算
2.9 FFT
这个模块是对长度为64的OFDM符号并行的运用IFFT,操作对象是向量数据,输出结果同样是长度为64的时域OFDM符号
2.10 OFDM cyclic prefixer
循环前缀就是从一个长度为64的块的末尾取一些时域符号添加到块的开头。添加循环前缀可以将数据与信道的线性卷积变为循环卷积,因为进行循环卷积的矩阵在傅里叶基底下可对角化,这意味着通过前后乘以IDFT和DFT矩阵可以去除符号间干扰,同时还可以实现低成本的信道均衡技术。
该模块有两种工作模式,一种是在carrier allocator之前使用的packet模式,另一种是在FFT之后使用的freewheeling模式。
2.11 output
3. Reception
3.1 Schmidl & Cox
接收的第一步是弄清楚OFDM burst何时开始,该模块实现了两个功能:第一个是确认帧何时开始,第二个是估计精细的频率偏移,矫正抵消短期载波频率偏移,
(1)定时同步
由于第一个同步字具有对称性(symmetric),即前半部分和后半部分完全相等,已知一个OFDM符号有64个子载波,每个子载波承载了一个64个复数信号,即一个OFDM符号由64个复数符号组成,接收端将输入信号延迟32个样本取其复数共轭与非延迟信号相乘,相当于在进行一种特殊的自相关操作,当延迟后信号中同步字的前半部分和原信号同步字的后半部分对齐时,此时结果最大。将其进行低通滤波以平滑数据,接着对其归一化,寻找结果中的plateau(平稳段或高原区)来进一步确定同步字的准确位置从而完成定时同步。
其中较小的峰值是因为其中所使用数据符号中的有效载荷载波除了导频载波以外全为1,这也导致数据符号也具有具有对称的前后两半部分
(2)精细频率偏移估计
载波频率偏移会导致相移,我们可以通过比较同步字的上半部分和下半部分的相位来同步。
首先,计算信号和延迟了 OFDM 符号长度一半(也就是 32 个样本)的信号之间的相关性。接着对其进行系统滤波,然后计算幅角(求它的反正切)。从向量的角度来看,类似于两个单位向量相乘后求反正切值即可求出两向量间夹角,从这里看就是两个复信号相乘后求正切值即为器相位偏移 ϕ ˆ \^{\phi} ϕˆ
第一个问题是,由于添加了长度为16个符号的循环前缀,所以我们需要使用延迟了(64+16)=80个符号的信号
第二个问题是如何通过相位偏移矫正信号频率偏移。通过将信号与 e − j 2 t ϕ ^ 1 T e^{-j2t\hat{\phi}\frac{1}{T}} e−j2tϕ^T1相乘来实现信号频率偏移 Δ f = ϕ ^ π T \Delta f=\frac{\hat{\phi}}{\pi T} Δf=πTϕ^,也就是将频偏的信号矫正回原位,但需要注意的是这里的 t t t实际上指的是复数符号索引, T T T实际上等于FFT的大小,同样的 Δ f \Delta f Δf也不是实际带宽,而是指归一化后的载波间隔。
总之,我们需要做的就是进行相位偏移估计,生成一个通过频率调制校正信号的波形,然后将该波形与延迟的接收信号相乘
但延迟之后的接收信号长度会变成320+80=400,两个信号相乘,较长的信号会被截断,这意味着将丢失一个有效载荷符号,所以需要将信号延迟和频率调制模块长度需匹配,否则可能丢失数据,可通过在输入流末尾添加噪声或常数解决。
3.2 Header/Payload Demux
该模块是从OFDM burst之中解析出数据包的模块,是一种状态机,默认状态是等待触发端口数据。需要特别注意的是,参数中 “items” 指实际复数采样点,“symbols” 指 OFDM 符号,此模块期望输入和输出为 OFDM 符号形式
3.2.1 Trigger state
默认状态下,搜索发送到trigger端口的字节数组,找到第一个数据字节的索引,跳转到header状态
3.2.2 Header state
此状态时将一定数量的包头数据(由 header_len
决定)复制到输出 0(out_hdr
)。等待消息发送到 headr_data
端口
当 headr_data
端口接收到PMT_F时,会把状态切换成HEADER_RX_FAIL;此外当payload_length为负,payload_length大于output buffer 的一半也会把状态切换成失败。
3.2.3 Header RX Success
这是使用header项和填充项的过渡状态
3.2.4 Payload
将接收到的PMT包中的 length_tag_key
对应的值作为有效载荷的长度,将对应数量的item数复制到out_payload端口(如果切换了output symbols选项输出将是symbols),因此payload将是长度为payload_length的向量,每个元素是一个包含64个复数的数组
在仿真时只有当实际预期足够样本时,才会触发有效负载状态
3.3 Header chain
头部符号处理链(Header chain) :
3.3.1 FFT 模块:
进行OFDM解调,由于 64 长度向量符号在时域但编码在频域,因此用前向 FFT 模块将其从时域转换到频域。
3.3.2 OFDM Channel Estimation :
利用两个同步字计算粗载波频率偏移和 “信道抽头”(信道频率响应),输出向量不含同步字仅留头部,信道抽头计算根据同步字(优先用第二个,不存在时用第一个)
GR 采用简单技术(同步字接收元素除以对应已知元素),需根据载波偏移调整抽头计算位置,一个同步字时因只有偶数频率有数据,奇数频率信道抽头为 0,GR 采用简单插值策略(设为相邻偶数频率值),源文件中有关于实现红色噪声阈值处理的待办事项。
3.3.3 OFDM Frame Equalizer :
使用上一个模块OFDM Channel Estimation产生的标签均衡 OFDM 帧,该模块自身不做均衡而是使用用户传入的均衡器对象
①载波频率偏移校正
如果频偏g为负数,则在输出开头设置g个0,并将头部向前移动g个项;如果频偏为正数,则在输出最后设置g个0,并从偏移位置开始将头部直接复制到缓冲区,同时丢弃g个项
②偏移引起的相位校正
由于载波偏移会导致相位偏移,模块会对每个OFDM符号的每个项进行相位校正
③均衡操作
模块根据接收到的符号和子载波信息,以及从标签中获取的信道状态信息对信号进行处理。对于每个符号i和每个子载波k,如果是导频子载波,则将k的信道状态信息更新为
α
∗
H
[
k
]
+
(
1
−
α
)
∗
s
i
,
k
p
j
\alpha*H[k]+(1-\alpha)*\frac{s_{i,k}}{p_j}
α∗H[k]+(1−α)∗pjsi,k ,其中H[k]为信道的频率响应,
α
=
0.1
\alpha=0.1
α=0.1是一个常数
如果不是导频子载波也以类似的操作进行均衡,先让让接收到的符号除以信道状态信息即为
s
i
,
k
H
k
\frac{s_{i,k}}{H_k}
Hksi,k,再计算星座图上哪个点距离接收符号点最近,将接受符号映射到该点即可。
在知道了映射后的符号是什么之后,我们就可以利用映射后的符号对信道状态信息进行再次更新
3.3.4 OFDM Serializer :
从每个符号的数据子载波提取数据并串成流,需注意输入的两个长度标签(“length tag key” 用于模块内部,“packet length tag key” 指输出流长度,若未提供标准长度标签键则重新计算帧长度),其余操作是提取每个符号数据子载波上的复数项,确保考虑载波偏移(第 i i i个输出项对应第 s i , k + g s_{i,k+g} si,k+g个输入项)。
3.3.5 Constellation decoder :
将复数符号解码回比特(1 字节表示 1 比特),计算输入复符号与星座点距离,选择最近星座点并输出对应字节,若之前使用了 simple DFE做信道均衡,那这里复符号会很容易 “捕捉” 到星座点,距离为 0。
3.3.6 Packet header parser :
与头部生成器相反,目的是恢复有效载荷长度和数据包编号,通过从 32 比特数据中提取信息(数据包长度,Header编号, CRC),计算 CRC 并验证恢复数据正确性,若正确则将有效载荷长度和头部编号作为标签放到流上并发送包含标签的消息(PMT 字典形式),有效载荷长度标签对 HPD 继续操作至关重要。
3.3 Payload chain
操作与头部处理链基本相同,但不使用信道估计模块,因为包含信道频率响应的标签通过Header/Payload Demux得以保留,可直接使用标签上的数据。
3.4 Repack bits
由于调制方案,接收的每个字节仅含 1 比特信息,需重新打包为完整字节。
3.5 Stream CRC32
检查每个发送数据包中的 4 字节 CRC32检错码,确保数据包正确恢复,若正确则接收完成。