前言
OFDM Frame Equalizer 的功能是对标记的 OFDM 帧执行一维或二维均衡,本文对 OFDM Frame Equalizer 模块的底层 C 源码进行剖析。
一、OFDM Frame Equalizer 模块简介
- 输入与输出:
- 输入:一系列带标签的 OFDM 符号
- 输出:与输入相同,但经过均衡和频率校正。
- 参数:
- FFT length:用于设置输入和输出向量大小
- CP length:样本中循环前缀的长度(校正频率偏移所需)
- Equalizer:将执行实际工作的均衡器对象
- Length Tag Key:TSB密钥
- Propagate Channel State:如果为 true,则最后一个符号之后的通道状态将作为标记添加到第一个符号
- Fixed frame length: 设置帧长度是否固定。当给定此值时,长度标签键可以留空,但即使在输入处使用标记流时它也是有用的。
- 实现原理
- 首先,它会移除粗略的载波偏移。如果在第一个项目中找到带有
'ofdm_sync_carr_offset'
键的标签,这将被解释为以载波数量表示的粗略频率偏移。 - 接下来,它在一个或两个维度上对标记的 OFDM 帧进行均衡。实际的均衡由一个名为 ofdm_frame_equalizer 的对象完成,该对象位于该块的外部。
- 请注意,带有粗略载波偏移的标签没有被移除。该块下游的块不应尝试也去纠正这个偏移。
- 首先,它会移除粗略的载波偏移。如果在第一个项目中找到带有
二、C 具体实现
1、初始化和配置参数
代码语言:javascript复制ofdm_frame_equalizer_vcvc_impl::ofdm_frame_equalizer_vcvc_impl(
ofdm_equalizer_base::sptr equalizer,
int cp_len,
const std::string& tsb_key, // 一个标签流块(Tagged Stream Block)的关键字,用于处理流中的数据包。
bool propagate_channel_state, // 决定是否传播通道状态
int fixed_frame_len) // 固定帧长,如果设置了固定帧长,则在处理时将使用这个长度
: tagged_stream_block(
"ofdm_frame_equalizer_vcvc",
io_signature::make(1, 1, sizeof(gr_complex) * equalizer->fft_len()),
io_signature::make(1, 1, sizeof(gr_complex) * equalizer->fft_len()),
tsb_key),
d_fft_len(equalizer->fft_len()),
d_cp_len(cp_len),
d_eq(equalizer),
d_propagate_channel_state(propagate_channel_state),
d_fixed_frame_len(fixed_frame_len),
d_channel_state(equalizer->fft_len(), gr_complex(1, 0))
{
// *************************错误处理*********************************
/*
这些行检查输入参数的有效性。如果既没有指定TSB标签也没有指定固定帧长,或者指定的帧长小于0,则抛出异常。
*/
if (tsb_key.empty() && fixed_frame_len == 0) {
throw std::invalid_argument("Either specify a TSB tag or a fixed frame length!");
}
if (d_fixed_frame_len < 0) {
throw std::invalid_argument("Invalid frame length!");
}
// *************************设置输出倍数*********************************
/*
如果设置了固定帧长,这行代码设置输出数据块的大小为固定帧长的倍数。
*/
if (d_fixed_frame_len) {
set_output_multiple(d_fixed_frame_len);
}
set_relative_rate(1, 1); // 设置输入和输出数据的相对速率,这里设置为1:1,表示输入和输出速率相同
// Really, we have TPP_ONE_TO_ONE, but the channel state is not propagated
set_tag_propagation_policy(TPP_DONT); // 设置标签传播策略为不传播,这意味着这个块不会自动把输入的标签复制到输出。
}
2、解析出所需的输入项目数量
代码语言:javascript复制// 该函数的目的是从一组标签中解析出所需的输入项目数量
void ofdm_frame_equalizer_vcvc_impl::parse_length_tags(
const std::vector<std::vector<tag_t>>& tags, gr_vector_int& n_input_items_reqd)
/*
tags: 每个 tag_t 代表一个数据流中的标签
n_input_items_reqd: 用于设置这个块处理每个数据流所需的项目(样本)数量
*/
{
if (d_fixed_frame_len) { // 如果设置了固定帧长度,就直接将n_input_items_reqd[0]设置为这个固定值
n_input_items_reqd[0] = d_fixed_frame_len;
} else { // 如果没有固定的帧长度,代码会遍历tags[0]中的每个标签。(tags[0]表示与当前块相关的第一条数据流的所有标签。)
for (unsigned k = 0; k < tags[0].size(); k ) {
if (tags[0][k].key == pmt::string_to_symbol(d_length_tag_key_str)) { // 如果标签的键与 d_length_tag_key_str(这是一个字符串,代表帧长度相关的标签键)相匹配
n_input_items_reqd[0] = pmt::to_long(tags[0][k].value); // 在当前处理周期内,该块需要读取的输入项目数
}
}
}
}
3、处理 OFDM 信号的均衡
代码语言:javascript复制// 用于处理OFDM信号的均衡
int ofdm_frame_equalizer_vcvc_impl::work(int noutput_items, // 指定输出项的数量
gr_vector_int& ninput_items, // 含有各个输入流的项数
gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items)
{
const gr_complex* in = (const gr_complex*)input_items[0];
gr_complex* out = (gr_complex*)output_items[0];
int carrier_offset = 0; // 初始化载波偏移和帧长度变量
int frame_len = 0;
if (d_fixed_frame_len) { // 如果有固定的帧长度,则使用该长度;否则使用输入项的数量作为帧长度
frame_len = d_fixed_frame_len;
} else {
frame_len = ninput_items[0];
}
// *************************获取并处理标签*********************************
/*
从输入流中获取标签并处理。特别关注通道状态和载波偏移的标签。
*/
std::vector<tag_t> tags;
get_tags_in_window(tags, 0, 0, 1); // 从输入流的当前处理窗口中获取所有标签,并存储在 tags 向量中
for (unsigned i = 0; i < tags.size(); i ) { // 遍历标签,查找关键标签ofdm_sync_chan_taps和ofdm_sync_carr_offset,分别更新通道状态和载波偏移。
if (pmt::symbol_to_string(tags[i].key) == "ofdm_sync_chan_taps") {
d_channel_state = pmt::c32vector_elements(tags[i].value);
}
if (pmt::symbol_to_string(tags[i].key) == "ofdm_sync_carr_offset") {
carrier_offset = pmt::to_long(tags[i].value);
}
}
// Copy the frame and the channel state vector such that the symbols are shifted to
// the correct position
// 调整输入输出数据的对齐和拷贝
/*
根据载波偏移调整输入数据的位置,并将输入数据复制到输出缓冲区。
如果载波偏移是负数,输出数组的开始部分将被清零,然后从输入数据中复制偏移后的数据。
如果偏移是正数,则在数据复制后,输出数组的末尾部分被清零。
*/
if (carrier_offset < 0) {
memset((void*)out, 0x00, sizeof(gr_complex) * (-carrier_offset));
memcpy((void*)&out[-carrier_offset],
(void*)in,
sizeof(gr_complex) * (d_fft_len * frame_len carrier_offset));
} else {
memset((void*)(out d_fft_len * frame_len - carrier_offset),
0x00,
sizeof(gr_complex) * carrier_offset);
memcpy((void*)out,
(void*)(in carrier_offset),
sizeof(gr_complex) * (d_fft_len * frame_len - carrier_offset));
}
// Correct the frequency shift on the symbols
// *************************频率偏移校正*********************************
/*
这部分代码通过乘以一个相位校正因子来补偿频率偏移。
d_cp_len / d_fft_len 这两个参数的比例用于确定每个样本的相位偏差累积速率。
具体来说,循环前缀的长度相对于FFT长度的比例影响了因频率偏移而导致的每个样本的相位变化
*/
gr_complex phase_correction;
for (int i = 0; i < frame_len; i ) {
phase_correction =
gr_expj(-GR_M_TWOPI * carrier_offset * d_cp_len / d_fft_len * (i 1));
for (int k = 0; k < d_fft_len; k ) {
out[i * d_fft_len k] *= phase_correction;
}
}
// Do the equalizing
// 均衡处理
d_eq->reset(); // 重置均衡器的状态
d_eq->equalize(out, frame_len, d_channel_state); // 调用均衡器来处理输出数据,根据当前的通道状态进行均衡。
d_eq->get_channel_state(d_channel_state); // 更新通道状态,以便在后续处理中使用
// Update the channel state regarding the frequency offset
phase_correction =
gr_expj(GR_M_TWOPI * carrier_offset * d_cp_len / d_fft_len * frame_len);
for (int k = 0; k < d_fft_len; k ) {
d_channel_state[k] *= phase_correction;
}
// Propagate tags (except for the channel state and the TSB tag)
// 传播标签(排除通道状态和 TSB 标签)
get_tags_in_window(tags, 0, 0, frame_len); // 从处理的数据流窗口中获取所有标签,并存储在tags向量中
for (size_t i = 0; i < tags.size(); i ) {
if (tags[i].key != CHAN_TAPS_KEY && // 如果标签的键不是通道状态(CHAN_TAPS_KEY)和不是固定帧长度关键字(d_length_tag_key_str),则将该标签添加到输出流的标签中。
tags[i].key != pmt::mp(d_length_tag_key_str)) {
add_item_tag(0, tags[i]);
}
}
// Housekeeping
// 状态维护
if (d_propagate_channel_state) { // 检查是否传播通道状态
add_item_tag(0,
nitems_written(0),
pmt::string_to_symbol("ofdm_sync_chan_taps"),
pmt::init_c32vector(d_fft_len, d_channel_state));
}
// 处理固定帧长度
/*
consume_each 这个调用通知框架当前块已经处理并准备“消费”frame_len数量的输入项目。
这是流处理中的标准操作,用于更新框架关于数据流进度的内部状态。
*/
if (d_fixed_frame_len && d_length_tag_key_str.empty()) {
consume_each(frame_len);
}
return frame_len;
}