最近在运行 GNURadio 中的 OFDM 例程时发现了丢帧的问题。
2022.08.01更新:
1、突然又想起了这个问题,今年年初的时候与西电的一个师兄又重新讨论了这个问题,有了更深一步的理解,这里记录一下:其实这个掉帧的问题本质上还是前一步的 同步 没有做好导致的,源码里用的同步算法官网也就是同步算法不行,要想彻底解决这个问题可以换一个更鲁棒的同步算法试试。
2、下面的解释其实可以理解为:在同步效果不好的前提下掉帧的原理,以及这种情况下如何避免掉帧。这个方法虽然可以在一定程度上解决掉帧问题,但是在同步性能极差的情况下也是无能为力的。所以仅供参考哈~
使用的 gnuradio 是 3.8 版本的,Ubuntu为20.04,而且至少目前来说 gnuradio 3.9 也存在这个问题,下面是问题原因及解决方法。
当使用原始的例程(一次发送10帧960个字节的数据)进行测试时还没有丢帧现象出现,但当我们把要发送的数据换成图片数据进行发送时却无法进行正确的接收,即使是在仿真中将信道条件改为理想无噪无畸变信道时也仍然会在同一位置丢同样数量的数据帧。
在调试中发现,当定时信息的两个触发信号之间的间隔一般有 4 种情况,分别为958、959、960、961。但是只有当差为958(一个完整 OFDM 数据帧为960个 QPSK 数据)时才会出现丢帧现象,其他都没问题,而 958与正常的 960差了两个 QPSK 数据。另外,用于定时的触发信号的产生是正常的,问题应该就出在触发信号产生后使用该触发信号的模块中。
在重新分析了例程中数据发送及解析的流程后,觉得可能是 Header/Payload Demux 模块源码中的问题,因此就看了下该模块的源码。
![](https://img-blog.csdnimg.cn/20210925102249132.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5L2g5Y235oiR5LiN5Y23,size_20,color_FFFFFF,t_70,g_se,x_16)
源码流程及问题所在:
总的来说,丢帧的原因就是相邻两个定时信号的间隔过短时,导致当前帧提取数据时将后一个帧数据的定时信号作为当前帧的数据一并读入,这样就丢失了下一帧数据的定时信号,因此就造成了丢帧的现象。这种现象是源码中固有的问题。具体分析如下:
图 2 中数据与触发信号是严格执行对应位置的并行传输关系,Header/Payload Demux 模块先读取 trigger 信号,当读到值为 1 时就被认为是一帧数据的开始,这时就从数据信号的相应位置开始往后提取 1919 个数据作为当前帧的数据进行输出。
![](https://img-blog.csdnimg.cn/20210926114826297.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5L2g5Y235oiR5LiN5Y23,size_20,color_FFFFFF,t_70,g_se,x_16)
根据源码的数据处理过程,源码中每次接收到定时信号后,都会提取紧跟着该定时信号后面的959个数据作为当前帧进行输出,因此这对定时信号的精确型提出了很高的要求,如果相邻两个定时信号的间隔出现了小于正常数据帧长度的偏差,比如正常间隔为 960,如果此时出现了间隔为 958 的间隔,如图 3,则在提取后续 959个数据的时候就会正好把下一帧的定时信号当作当前帧的数据一起读入,这样就丢失了下一帧数据的定时信号,因此就造成了丢帧的现象。
![](https://img-blog.csdnimg.cn/20210926114915831.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5L2g5Y235oiR5LiN5Y23,size_20,color_FFFFFF,t_70,g_se,x_16)
解决这个问题的方法就是在源码中进行修改,在保证相邻定时信号不想相互干扰的基础上再重新进行源码的编译安装。需要修改的源码部分为 gr-digital/lib/header_payload_demux_impl.cc 以及 gr-digital/lib/header_payload_demux_impl.h 。首先在 header_payload_demux_impl.h 中添加变量:
int d_next_trigger_offset;
然后在 header_payload_demux_impl.cc 中更改 find_trigger_signal 函数如下:
......
int trigger_nums = 0;
if (max_rel_offset < skip_items) {
return rel_offset;
}
if (in_trigger) {
for (int i = skip_items; i < max_rel_offset; i++) {
if (in_trigger[i]) {
trigger_nums++;
// record location of the first trigger
if(trigger_nums == 1) {
rel_offset = i;
}
// If there is a second trigger signal,record offset of between the first and second trigger
else if (trigger_nums == 2) {
d_next_trigger_offset = i - rel_offset;
break;
}
}
}
}
......
然后修改 general_work() 中的 case STATE_PAYLOAD 状态中的 consume 过程如下:
。。。。。。
// Calculate whether the current frame consumes the trigger signal of the next frame data
// If yes, then make corrections
int consume_compensation = 0;
int consume_nums = 0;
// Calculate total consumption
consume_nums = (d_curr_payload_len + d_header_len) * (d_items_per_symbol + d_gi) +
d_header_padding_total_items +
d_curr_payload_offset -
items_padding ;
// make a decision
if(d_next_trigger_offset <= consume_nums) {
consume_compensation = consume_nums - d_next_trigger_offset + 1;
}
else {
consume_compensation = 0;
}
// add the corrections
const int items_to_consume =
d_curr_payload_len * (d_items_per_symbol + d_gi) - items_padding - consume_compensation;
。。。。。。
修改后从新对源码进行编译安装,问题解决~