文章目录
- 前言
- 一、发送端
- 1、参数配置
- 1)Random Source
- 2)stream to Tagged stream
- 3)Stream CRC32
- 4)Protocol Formatter
- 5)Repack Bits
- 6)Virtual Sink
- 7)Chunks to Symbols
- 8)Tagged Stream Mux
- 9)OFDM Carrier Allocator
- 10)FFT
- 11)OFDM Cyclic Prefixer
- 12)Multiply Const
- 13)Tag Gate
- 14)USRP Sink
- 2、发送端 grc 图
- 1)生成 OFDM 头部和有效载荷
- 2)调制后组成一帧 OFDM 信息
- 3)添加循环前缀
- 4)经过 USRP 发送
- 3、发送结果
- 1)仿真结果
- 2)USRP 发射实际频谱图
- 二、接收端
- 1、参数设置
- 1)Schmidl & Cox OFDM synch
- 2)Frequency Mod
- 3)Delay
- 4)Header/Payload Demux
- 5)OFDM Channel Estimation
- 6)OFDM Frame Equalizer
- 7)OFDM Serializer
- 8)Constellation Decoder
- 9)Packet Header Parser
- 2、接收端 grc 图
- 1)提取数据流中头部和负载
- 2)提取 OFDM 头原始数据
- 3)提取 OFDM 有效负载
- 4)解调 OFDM 有效负载
- 三、结果验证
- 1、仿真验证
- 2、USRP 验证
- 1)误码率测试
- 2)结果图
- 四、资源自取
前言
使用 GNU Radio Companion 驱动 USRP N320 实现 OFDM 自收自发测试。(Ubuntu20.04LTS + GNURadio 3.8 + UHD 3.15)
一、发送端
该模块由随机数信号源、CRC、符号映射器、FFT、循环前缀加法器、放大器组成。主要目标是传输随机信号,根据调制方式的选择对有效载荷进行重新包装。选择的调制是 QPSK,所以 2 位被重新打包在一起。然后将有效载荷和报头分别映射到 QPSK 和 BPSK 的复星座矢量中。OFDM 载波分配器分配占用载波、导频载波、导频符号和同步字。FFT(Reverse)或 IFFT采用复数值向量并计算 IFFT 它表示输出。循环前缀以 OFDM 符号作为其输入,从而产生具有循环前缀的输出符号。
1、参数配置
1)Random Source
Random Source:
- 生成一些 [min, max] 随机数的样本,这意味着最大值将不包括在内。如果指定重复样品。用于创建用于测试调制器的信息字节。
-
- 输出类型是 “字节”,取值范围 0~255,输出中生成的样本总数为 1000,指定生成重复样品
2)stream to Tagged stream
①、变量:packet_len
②、stream to Tagged stream
- 将普通流转换为标记流。这个块所做的就是按一定的间隔添加长度标签。它可用于将常规流连接到gr::tagged_stream_block。这个块意味着直接连接到一个带标签的流块。
- 输出类型为 “字节”,每个带标签的流数据包的长度为 1 包。
3)Stream CRC32
①、变量:length_tag_key
②、Stream CRC32
- 字节流 CRC(循环冗余校验) 块
- Stream CRC 32是一个带标签的流块,需要一个 Length tag key,因此前面加了一个 stream to Tagged stream
下面举例介绍:
CRC32 之后的数据图如下所示,CRC 已经被添加到每个分组的末尾,并且分组长度标签已经从 100 字节被更新到104 字节,其中额外的 4 个字节用干 CRC
4)Protocol Formatter
①、变量:length_tag_key
②、变量:occupied_carriers
- -26~26 范围内不包括子载波索引为 -21,-7,0,7,21
③、变量:hdr_format
- digital.header_format_ofdm(occupied_carriers, 1, length_tag_key,)
- 用于生成 OFDM 的头部格式
occupied_carriers
: 用于指定 OFDM 系统中被占用的载波序列。1
: 用于指定 OFDM 头部的长度length_tag_key
: 用于指定标记头部长度的 key
- 用于生成 OFDM 的头部格式
header_format_ofdm C++ 实现源码如下:
header_format_ofdm::header_format_ofdm(
const std::vector<std::vector<int>>& occupied_carriers,
int n_syms,
const std::string& len_key_name,
const std::string& frame_key_name,
const std::string& num_key_name,
int bits_per_header_sym,
int bits_per_payload_sym,
bool scramble_header)
: header_format_crc(len_key_name, num_key_name),
d_frame_key_name(pmt::intern(frame_key_name)),
d_occupied_carriers(occupied_carriers),
d_bits_per_payload_sym(bits_per_payload_sym)
{
d_header_len = 0;
for (int i = 0; i < n_syms; i++) {
d_header_len += occupied_carriers[i].size();
}
d_syms_per_set = 0;
for (unsigned i = 0; i < d_occupied_carriers.size(); i++) {
d_syms_per_set += d_occupied_carriers[i].size();
}
// Init scrambler mask
d_scramble_mask = std::vector<uint8_t>(header_nbits(), 0);
if (scramble_header) {
// These are just random values which already have OK PAPR:
gr::digital::lfsr shift_reg(0x8a, 0x6f, 7);
for (size_t i = 0; i < header_nbytes(); i++) {
for (int k = 0; k < bits_per_header_sym; k++) {
d_scramble_mask[i] ^= shift_reg.next_bit() << k;
}
}
}
}
④、Protocol Formatter
- 使用报头格式对象从标记的流数据包创建报头。这个块接受标记流并创建一个标头,通常用于 mac 级处理。
5)Repack Bits
①、Repack Bits
- 将输入流中的位重新打包到输出流的位上。这里没有丢失任何信息;k(每个输入的字节位数)和 l(每个输出的字节位数)的任何值([1, 8] 内)都是允许的。在每个新输入字节上,它开始读取 LSB,并开始复制到 LSB。
- 每个输入字节的位数 (k)
- 输入流上的相关位数
- 每个输出字节的位数 (l)
- 输出流上的相关位数
- 长度标签键
- 如果不为空,则这是长度标签的键。
- 字节顺序
- 输出数据流的字节顺序(LSB 或 MSB)。
- 包对齐
- 当提供长度标签键时,它控制输入或输出是否对齐。Repack Bits 对标记的流进行操作。在这种情况下,当 k * 输入长度 ≠ l * 输出长度时,可能会发生输入数据或输出数据变得不对齐的情况。在这种情况下,Pack Alignment 参数用于决定对齐哪个数据包。通常,Pack Alignment 设置为用于解包的输入(k=8,l < 8)和用于反转的输出。
例如,假设你正在发送 8-PSK,因此在调制器之前的发送侧设置 k=8、l=3。现在假设您正在传输单个字节(8位)的数据。您的传入标记流的长度为 1(现在共 8+1=9 位,多出 1 位为标记流的长度),传出的长度为 3。但是,第三项实际上仅携带 2 位相关数据(多出来的 1 位标记流),这些位与边界不对齐
。因此,您将 Pack Alignment 设置为 Input,因为输出可能不对齐。`现在假设您正在执行相反的操作:将这三项打包为完整字节。你如何解释这三个字节?如果没有这个标志,您必须假设其中有 9 个相关位,因此最终会得到 2 个字节的输出数据。但在打包的情况下,您希望输出对齐;所有输出位都必须有用。通过断言此标志,打包算法尝试执行此操作,并且在本例中假设由于我们在 8 位之后进行了对齐,因此可以丢弃第 9 位。
- 当提供长度标签键时,它控制输入或输出是否对齐。Repack Bits 对标记的流进行操作。在这种情况下,当 k * 输入长度 ≠ l * 输出长度时,可能会发生输入数据或输出数据变得不对齐的情况。在这种情况下,Pack Alignment 参数用于决定对齐哪个数据包。通常,Pack Alignment 设置为用于解包的输入(k=8,l < 8)和用于反转的输出。
- 每个输入的字节位数为 8,每个输出的字节位数为 1
8PSK 星座图如下:
②、变量:occupied_carriers
③、Repack Bits
- digital.constellation_qpsk().bits_per_symbol()
- bits_per_symbol() 函数会根据 QPSK 调制的特性来计算每个符号所携带的平均比特数,这里为 2
6)Virtual Sink
Virtual Sink:
- 用于接收和处理流图中的数据,并且在流图运行时不产生实际的输出
7)Chunks to Symbols
①、变量:header_mod
②、Chunks to Symbols
- 将数据分块转换成符号序列。被分成较小的块,然后通过调制技术转换成符号序列,以便在信道上传输。
- OFDM 头部采用 BPSK,OFDM 有效载荷采用 QPSK
8)Tagged Stream Mux
Tagged Stream Mux:
- 将多个带有标签的数据流(Tagged Stream)合并成一个数据流,输出信号具有新的长度标签,它是所有单独长度标签的总和,旧的长度标签将被丢弃。
9)OFDM Carrier Allocator
①、变量:occupied_carriers
②、变量:pilot_carriers
- 导频子载波索引设置为 -21,-7,7,21,载波索引始终使得索引 0 是 DC 载波(注意:您不应分配此载波)
③、变量:pilot_symbols
- 导频符号:1,1,1,-1
④、变量:sync_word1、sync_word2
- 同步字1:长度为 64,
[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]
- 同步字2:长度为 64,
[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0, 0]
⑤、OFDM Carrier Allocator:
- 该模块将复杂的标量调制符号流转换为矢量,作为 OFDM 发射机中 IFFT 的输入。它还支持将导频符号放置到载波上的可能性。载波可以自由分配,如果未分配载波,则将其设置为零。这允许进行 OFDMA 类型的载波分配。
10)FFT
①、fft_len
②、FFT:
- Reverse 代表做的是 IFFT
- 进行“fft 移位”,将 DC (0 Hz) 置于中心
11)OFDM Cyclic Prefixer
①、变量:rolloff
②、OFDM Cyclic Prefixer
- 添加循环前缀并对 OFDM 符号执行脉冲整形(这里没有进行脉冲整形)
- 滚降 (%) = (滚降 (样本)/ FFT_len) * 100,这里设置为0
12)Multiply Const
Multiply Const:
将输入流乘以标量或向量常量(如果向量,则按元素)
13)Tag Gate
Tag Gate:
- 控制标签传播。使用此块可以阻止标签传播,这里阻止了标签传播。
14)USRP Sink
①、参数:address0
- 设置 USRP 发端的 IP 地址为 192.168.10.2,主时钟频率为 200MHz
②、变量:samp_rate
③、参数:carrier_freq
④、参数:tx_gain
⑤、USRP Sink
2、发送端 grc 图
1)生成 OFDM 头部和有效载荷
- “1” 处的包长度为 100 字节
- “2” 处的包长度为 6 字节
- “3” 处的包长度为 48 字节
- “4” 处的包长度为 400 字节
2)调制后组成一帧 OFDM 信息
OFDM 头部和有效载荷调制后组成一帧 OFDM 信息。
在 OFDM(正交频分复用)系统中,帧通常由头部(Header)和有效载荷(Payload)两部分组成。
- 头部(Header): 头部是帧的开头部分,通常包含了一些元数据和控制信息,用于管理和识别帧的类型、长度、版本等信息。在头部中可能包括以下内容:
- 帧起始标志(Frame Start)
- 帧类型标识(Frame Type)
- 帧长度(Frame Length)
- 版本号(Version)
- CRC(循环冗余校验)等校验信息
- 其他控制信息,如信道状态、编码方式、调制方式等
- 有效载荷(Payload): 有效载荷是帧的主要部分,包含了需要传输的实际数据。在数字通信系统中,有效载荷通常是用户数据,如音频、视频、文本等。在 OFDM 系统中,有效载荷会被分成多个符号进行调制,然后通过信道传输。
3)添加循环前缀
将上面的一帧 OFDM 信号通过载波分配器,配置好数据子载波、导频子载波、导频符号、同步字,并将 OFDM 信号通过 IFFT 调制到子载波上,并添加循环前缀。
4)经过 USRP 发送
3、发送结果
1)仿真结果
2)USRP 发射实际频谱图
- 2M*52/64 = 1,625,000Hz
二、接收端
接收机主要由 OFDM同步器、频率调制器、信号混频器、FFT 和帧采集组成。来自天线的输入信号,采取的带宽对应于包含实际数据的载波数,直接发送到同步器(Schmidl & Cox OFDM synch)。该同步器负责定时同步和频率误差校正。**频率误差校正被馈送到频率调制器,以产生与同步块的频率误差成比例的信号。然后将其与接收到的数据混合以校正错误并输入到解复用器。**解复用器一旦得到数据包的开头,就开始接收数据,并输出报头和有效载荷进行解调。
1、参数设置
1)Schmidl & Cox OFDM synch
Schmidl & Cox OFDM synch:
- Schmidl & Cox OFDM Sync 是 GNU Radio 中用于实现 OFDM(正交频分复用)系统的同步的模块之一。它的作用是对接收到的信号进行同步,以便正确解调和处理数据。
- Schmidl & Cox OFDM Sync 模块采用了一种基于循环前缀(Cyclic Prefix)的同步方法,称为 Schmidl & Cox 算法。这种方法通常包括以下步骤:
信号检测
: 首先,接收端需要检测到信号并确定接收到的信号是否包含 OFDM 数据。这可能涉及到自动增益控制(AGC)和能量检测等技术。同步序列检测
: 然后,接收端需要在接收到的信号中找到用于同步的特定序列,通常是 OFDM 帧的循环前缀(Cyclic Prefix)。频率偏移估计
: 接着,模块会估计接收信号的频率偏移,以便进行频率校正。频率偏移可能是由于发送端和接收端的本地振荡器不精确而引起的。时间同步
: 最后,模块会根据找到的同步序列进行时间同步,以确定接收到的信号的起始位置,从而进行正确的数据解调。
- detect 引脚通常用于指示同步算法是否成功检测到 OFDM 信号的起始点,当 detect 引脚为真时,表示同步算法已经成功地检测到了 OFDM 信号的起始点,即找到了帧同步。这意味着接收到的信号可以进一步处理,例如进行解调、解码等操作。
2)Frequency Mod
Frequency Mod:
- 这个模块是一个输入振幅控制复正弦。它输出一个信号,该信号具有与灵敏度和输入幅度成比例的瞬时相位增加。
- 以产生与同步块的频率误差成比例的信号
3)Delay
Delay:
- 将输入延迟一定数量的样本,正延迟在流的开头插入零个项目
4)Header/Payload Demux
Header/Payload Demux:
- 将数据流中的头部和负载分开。这在通信系统中非常常见,因为数据帧通常包含头部(Header)和负载(Payload),头部用于携带一些控制信息、帧类型、地址等,而负载则携带实际的用户数据。
- Header/Payload Demux 模块的 trigger 输入端用于指示何时开始解析下一个数据帧的头部信息。这个触发输入可以是一个布尔型信号,当它为真时,模块将开始解析下一个数据帧的头部。
- 在一个无线通信系统中,当接收到数据帧的结束符或者校验通过时,可以发送一个触发信号给 Header/Payload Demux 模块,以指示当前帧的头部信息已经完整接收,并且可以开始解析下一个帧的头部信息了。
- 在一个无线通信系统中,当接收到数据帧的结束符或者校验通过时,可以发送一个触发信号给 Header/Payload Demux 模块,以指示当前帧的头部信息已经完整接收,并且可以开始解析下一个帧的头部信息了。
5)OFDM Channel Estimation
OFDM Channel Estimation:
- 对接收到的 OFDM 信号进行信道估计,以获取信道的频率响应信息。
6)OFDM Frame Equalizer
OFDM Frame Equalizer:
- 首先,它消除了粗略的载波偏移,接下来,它对标记的 OFDM 帧执行一维或二维均衡。
7)OFDM Serializer
OFDM Serializer:
- 将 OFDM 子载波中的复数调制符号串行化
- 这是OFDM 载波分配器的逆块。它将复数数据符号作为标记流输出,并丢弃导频符号
8)Constellation Decoder
Constellation Decoder:
- 星座解码器,根据对象的映射将星座点从复数空间解码为(解包)位
9)Packet Header Parser
Packet Header Parser:
- 这是数据包头生成器的逆块。不同的是,解析后的 header 不是作为流输出,而是作为 PMT 字典输出,并发布到 ID 为“header_data”的消息端口。
2、接收端 grc 图
1)提取数据流中头部和负载
2)提取 OFDM 头原始数据
3)提取 OFDM 有效负载
4)解调 OFDM 有效负载
符号倍解映射,重新打包和检查字节对应的头数据和发送到文件接收器。
三、结果验证
1、仿真验证
首先不使用 USRP 进行收发,先进行仿真验证直接将发送端送给接收端,在信号源端将发送的数据保存到本地为 send_data.bin,在最后的接收端将接收的数据保存到本地为 recv_data.bin,使用 beyond compare 进行二进制对比,可以看到如下结果,两个文件一致,误码率为0。
2、USRP 验证
1)误码率测试
①、在实际测试过程中发现将发送增益和接收增益都设置为 0 时,收不到数据,因此增加发送及接收增益,发送端增益设置为 20,接收端增益设置为 25,采样率为 1MHz时,对比发送和接收文件,发现存在误码情况,手动计算了一下大约 2.4e-4 的误码率量级
②、采样率为 781.25KHz 时,对比发送和接收文件,发现存在误码情况,手动计算了一下的误码率量级为 7.8e-5
③、采样率为 390.625KHz 时,对比发送和接收文件,未发现存在误码情况,误码率为 0
2)结果图
传输的文本本间对比,左面为发送的数据,右面为接收到的数据
发送端OFDM 时域信号
接收端及发射端OFDM 频谱图
OFDM Header 采用 BPSK 调制方式,Payload 采用 QPSK 调制方式
整体流程图:
四、资源自取
GNURadio+USRP+OFDM实现文件传输
我的qq:2442391036,欢迎交流!