前言
使用 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
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 位。
- 每个输入的字节位数为 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 发送
GNURadio+USRP+OFDM实现文件传输(二)https://developer.aliyun.com/article/1474029