1. 先把DVB-RCS的老底翻出来:为什么值得专门建一个仿真
做卫星通信仿真的人,绝大多数时间都在和DVB-S/S2打交道,下行链路的资料一抓一大把。但一提到DVB-RCS(Digital Video Broadcasting – Return Channel via Satellite),网上的开源代码少得可怜,教材里的描述也含糊其辞,经常是“采用MF-TDMA体制,支持突发传输”一句话带过。我刚接触这个标准的时候,光是搞明白它的回传信道怎么把多个用户的突发数据塞进同一个载波,就花了整整一周。
这个项目要做的,就是基于Matlab搭建一个完整的DVB-RCS回传链路仿真,核心是两个关键词:双用户时隙和载波调制解调。通俗地讲,就是模拟两个卫星终端站(RCST,Return Channel Satellite Terminal)通过同一个卫星转发器向主站(网关)发送数据,各自占用不同的时隙,在接收端能正确解调出两路信号。
为什么这件事值得专门做一次仿真?因为DVB-RCS的物理层有个很烦人的特点——它不像DVB-S那样是连续载波调制,而是突发(burst)模式,用户只在分配到的时隙里发射信号,平时保持静默。这意味着接收端不能简单地用一个固定的载波恢复环路去跟踪信号,它必须在每个时隙开始时快速完成信号捕获、频偏估计、定时同步,然后在该时隙结束时把相位状态“清零”,准备迎接下一个用户。这种“间歇性”传输对解调器的鲁棒性要求极高,正好暴露了很多人在连续信号仿真里根本碰不到的问题。
这套仿真适合谁来参考?如果你是通信专业的研究生,正在做卫星通信方向的开题或者课程设计,这篇内容能帮你少走很多弯路;如果你是刚入职卫星通信公司的工程师,需要快速理解MF-TDMA突发解调的工程实现细节,这里面的参数设计和坑点同样有参考价值。后面我会把从帧结构设计、时隙分配到调制解调实现、误码率验证的全过程逐步拆开讲,附上可以直接改的Matlab代码思路。
关于DVB-RCS,有一点必须先说清楚:它规定的回传信道传输机制叫MF-TDMA(多频时分多址),意思是用户既可以在不同频率上区分,也可以在同一频率下按时间切片区分。我们这次仿真的重点是后一种方式——单载波下的时分复用,两个用户共享同一个载波,靠时隙错开传输,这是理解整个系统的最小可行示例。
2. 仿真系统整体设计:从信号流到参数选型
2.1 顶层架构与信号流
在设计仿真之前,先把链路逻辑理清楚。一个完整的DVB-RCS回传仿真可以拆成三大块:发射端(两个用户各自独立)、信道(叠加噪声和频偏)、接收端(解调两路用户数据)。发射端每个用户的处理流程是相同的,都是“信源比特 → 信道编码 → 突发组帧 → 基带调制 → 成型滤波 → 搬移到中频”,区别只在于各自的时隙偏移和扰码种子。
接收端的处理恰好是发射端的逆过程,但因为双用户是突发式接入,接收端不能一直保持同一个解调状态,而要在每个用户时隙到达时重新执行一轮同步。我最初犯的错误是直接把连续信号的接收结构搬过来——用一个锁相环从头锁到尾,结果第二路用户的信号因为来的太晚,要么捕获不到频偏,要么把两个用户的中心频率估计混在一起。后来改成“时隙窗口化处理”,先由帧同步模块判断当前时隙属于哪个用户,再独立解调,问题就解决了。
这个“时隙窗口化处理”是整个接收端架构的灵魂,后面我会在第三节里详细讲实现。
2.2 关键参数怎么定下来
仿真参数不能拍脑袋,必须和DVB-RCS标准对得上,同时兼顾Matlab仿真的计算复杂度。我用的参数组合如下:
| 参数 | 数值 | 说明 |
|---|---|---|
| 符号速率 Rs | 1 Msps | 便于观察,实际系统通常更高 |
| 调制方式 | QPSK / 8PSK | DVB-RCS必选调制 |
| 滚降系数 α | 0.25 | 标准中常用的根升余弦滚降 |
| 每符号采样点数 sps | 4 | 定时精度与计算量的折中 |
| 用户数 | 2 | 双用户场景 |
| 时隙数 | 每帧2个时隙,每用户1个 | 简单清晰的1:1分配 |
| 帧长度 | 2000 符号 | 每个时隙1000符号 |
| 载波频率偏移 | 用户1:+2 kHz;用户2:-3 kHz | 模拟卫星链路多普勒及本振误差 |
先说符号速率。1 Msps在DVB-RCS标准里属于偏低的档位(标准支持从0.5 Msps到几十Msps的跨度),但仿真和真实系统的区别在于:我们更关心算法是否work,而不是跑百万级符号验证吞吐量。用1 Msps配合每符号4个采样,采样率就是4 MHz,跑一次10万符号的误码率测试在普通PC上只要几秒钟,非常适合反复调整参数。
载波频偏的设置不是随意写的。真实卫星信道中,RCST本振晶振偏差通常在几十到几百Hz量级,加上卫星移动带来的多普勒频偏(低轨卫星场景下可达数十kHz),接收机必须有能力纠正这个偏移。我在这里故意设置为2 kHz和-3 kHz(相对接收机标称中心频率),相当于±0.2%的符号速率偏移,处于常见的“需要频偏估计但又不至于让滤波器彻底失真”的区间。
滚降系数0.25是DVB-S/RCS系统里最常见的选择,这意味着信号带宽是符号速率的1.25倍,即1.25 MHz带宽里传输1 Msps符号,频谱效率0.8 bit/s/Hz(QPSK时)。在实际卫星转发器中,带宽是严格受限的资源,选择较大的滚降系数会浪费宝贵的频谱,选择太小的(如0.15)则对接收端定时误差更敏感。0.25是工程上最稳当的平衡点。
2.3 用户时隙映射与突发结构
DVB-RCS的突发结构不是简单地把比特塞进时隙,它有一套固定格式。标准里定义了多种突发类型,最常见的是**前导符(preamble)+ 数据净荷(payload)**的结构。前导符用于接收端的同步——包括频率捕获、定时恢复和相位参考。我们仿真的突发结构设计为:
- 前导符长度:64符号(固定为已知的伪随机序列,QPSK映射)
- 数据长度:936符号
- 合计:1000符号
为什么前导符64符号?这是权衡了同步性能和开销的结果。64个QPSK符号在1 Msps下只有64微秒,带来的协议开销约6.4%,但对载波频偏估计而言,64个符号在低信噪比下用数据辅助估计能提供的精度也是够用的。如果你要做更高阶的8PSK调制,建议把前导符加长到128符号,因为8PSK的相位容限更小,需要更长序列来压制估计方差。
双用户的时隙在帧内的排布就是一个“排班表”的逻辑。一帧2000个符号,第0~999符号是用户1的时隙,第1000~1999符号是用户2的时隙。每个时隙起始处放一个固定的前导符序列,但在仿真中我给两个用户设置了不同的扰码种子,这样即便前导符序列在符号层面相同,数据部分的扰码不同,接收端可以据此区分用户。还有一种做法是给两个用户分配不同的前导符序列(标准中确实存在不同的前导序列ID),但为了演示起见,我这里用了扰码区分+时隙位置区分双重方案。
3. 双用户时隙调度:Matlab里的核心实现
3.1 帧结构定义与时隙分配
搞定参数后,第一步是在Matlab里把帧结构定义出来。
% 帧与突发参数 symbol_rate = 1e6; % 符号速率 1 Msps sps = 4; % 每符号采样数 rolloff = 0.25; % 滚降因子 filter_order = 40; % 滤波器阶数(合理长度) burst_length = 1000; % 每个突发的符号总数(含前导) preamble_len = 64; % 前导符号数 data_len = burst_length - preamble_len; % 数据符号数 frame_slots = 2; % 每帧2个时隙 frame_len = burst_length * frame_slots; % 2000符号这里定义一个burst_length为1000符号,两个burst组成一帧。没有直接定义一个大的帧数组然后把数据填进去,而是每个用户单独生成自己的burst信号,最后按时间维拼接。这样做的好处是代码逻辑和实际系统的发送过程一致——每个RCST是独立发送的,它根本不知道另一个用户在干什么,时隙管理由主站的MAC层负责,物理层只是“到点就发”。
时隙分配的代码更直白:
% 时隙起始时间(符号位置) slot_offset = [0, burst_length]; % 用户1从第0符号开始,用户2从第1000符号开始但在实际生成信号时,我们不是简单地把两个burst拼起来,而是把每个burst扩展成整个帧长度,让每个用户只在属于自己的时隙处有信号,其余位置补零:
% 为每个用户生成完整的帧信号(非占用时隙填零) tx_frame_user1 = [tx_burst_user1, zeros(1, burst_length)]; tx_frame_user2 = [zeros(1, burst_length), tx_burst_user2]; % 叠加两用户信号(此时尚未加噪声) tx_frame_combined = tx_frame_user1 + tx_frame_user2;这是仿真中一个非常重要的细节:在接收端看来,双用户信号是“同时存在”于同一段数据里的,只是能量分时出现。虽然两个用户没有在时间上交叠,但叠加这步操作会引入一个潜在的数值问题——如果两路信号的载波频偏不同,那么在拼接处会出现相位跳变,这个跳变会在后续的滤波处理中产生频谱扩展。我在第一次仿真时没注意这一点,结果在接收端频谱图上观察到时隙交界处的毛刺噪声,后来单独对每个时隙做带通滤波才解决。
3.2 发射端突发信号的生成流程
生成一个用户突发信号的标准流程是:
- 随机生成数据比特(数据长度 = data_len × 调制阶数,QPSK时2比特/符号)
- 加扰(使用不同的扰码种子)
- 前导符序列生成(固定PN序列,QPSK映射)
- QPSK/8PSK调制映射
- 插入前导符形成完整burst符号序列
- 根升余弦成型滤波
加扰这一步是DVB-RCS物理层的一个关键环节,目的有三个:能量扩散(避免长串0/1导致频谱线)、数据随机化(便于接收端时钟恢复)、用户区分(不同扰码种子)。在标准中,扰码序列由Gold序列生成器产生,初始化种子跟用户标识相关。
% 扰码实现(示意) % scrambler: 简单PN序列异或 pn_gen = comm.PNSequence(... 'Polynomial', [1 0 0 0 0 0 1 0 1], ... 'InitialConditions', user_seed_vec, ... 'SamplesPerFrame', total_bits); scramble_seq = pn_gen(); scrambled_bits = xor(data_bits, scramble_seq);注意这里的拓扑结构:先加扰再调制,调制得到的是复数符号序列。前导符序列也需要经过相同的调制映射,但它不经过加扰(前导符是收发双方已知的)。在实际的发射机里,前导符会经过相同的成型滤波,但和数据的扰码互不相干。
调制映射的定义参照DVB-RCS的星座图。QPSK星座点的映射是Gray编码的,这样相邻星座点只差1个比特,可以在误码率不理想时减小误比特率。8PSK则更复杂,标准的8PSK星座是均匀分布在单位圆上,每个符号3比特,Gray编码后的相邻相位差对应单比特翻转。
3.3 突发拼接和多用户信号合成
两个用户burst生成后分别做成型滤波,再各自做载波频偏搬移,最后叠加:
% 用户1的调制和滤波 sym_tx_user1 = [preamble_qpsk, mod_data_user1]; tx_sym_up_user1 = upsample(sym_tx_user1, sps); tx_filtered_user1 = filter(rrc_filter, tx_sym_up_user1); % 加入频偏(叠加在基带复信号上) t_axis = (0:length(tx_filtered_user1)-1) / (symbol_rate * sps); tx_freq_user1 = tx_filtered_user1 .* exp(1j*2*pi*freq_offset_user1*t_axis); % 同样的操作对用户2做一遍 % 拼帧 + 叠加 tx_signal = [tx_freq_user1, zeros(1, burst_length*sps), ...] + [zeros(1, burst_length*sps), tx_freq_user2, ...];这里有一个容易踩坑的地方:成型滤波会引入群延迟。根升余弦滤波器的群延迟等于滤波器阶数的一半乘以采样周期。当burst边界是精确对齐在1000符号整数倍的时候,滤波器的延迟会导致burst起点和终点“拖尾”,从而让信号泄漏到时隙边界之外。所以仿真时需要在burst前后各留出一段保护间隔,否则接收端做时隙截取时会丢失信号能量。
我用的保护符号数 = filter_order / 2,即20个符号(因为sps=4时滤波器阶数40对应10个符号的延迟)。如果burst长度为1000符号,那么实际送入信道的是“保护前缀 + 1000符号 + 保护后缀”,接收端在解调时再把这些保护段舍弃。
这里再补充一个我实测过的教训:如果保护间隔留少了,接收端误码率会在高信噪比时出现一个“平台效应”——无论怎么提高信噪比,误码率都不再下降。原因是截断窗口把成型滤波器的拖尾切掉了一部分,破坏了匹配滤波器的匹配条件,形成固定的码间干扰。这个现象在连续信号仿真里几乎遇不到,但在突发信号仿真里几乎必然出现,建议一上来就把保护间隔留足。
4. 载波调制解调:核心链路的逐步拆解
4.1 调制端:映射、成型滤波与上变频
调制端最重要的两个模块是星座映射和成型滤波。星座映射决定了信号的频谱效率和抗噪声能力——QPSK的星座点距离大,误码率低,但每符号只传2比特;8PSK在相同带宽下每符号传3比特,频谱效率提升50%,但星座点之间的欧氏距离变小,对噪声更敏感。仿真时我建议把两种调制都实现,用参数切换,方便后面做对比曲线。
成型滤波器用的是根升余弦(Root Raised Cosine,RRC),在Matlab里可以直接用rcosdesign函数生成:
rrc_filter = rcosdesign(rolloff, filter_order, sps, 'sqrt');成型滤波的本质是把矩形的符号脉冲变成带宽受限的平滑脉冲,同时让发射端和接收端的RRC滤波器级联后形成升余弦(RC)响应,满足奈奎斯特第一准则——即在最佳采样点上,相邻符号的串扰为零。
这里有个仿真里特别容易出问题的细节:滤波器的归一化。rcosdesign默认输出幅值不是单位增益的,直接对信号滤波会导致发射信号功率变小或变大。我通常在滤波之后对信号做功率归一化:
tx_filtered = tx_filtered / sqrt(mean(abs(tx_filtered).^2));保持发射信号的平均功率为1,这样后面加噪声时,信噪比的定义才清晰可控。
上变频这一步在仿真里通常以基带等效形式完成,即直接用复数乘相位旋转来模拟频偏。真实系统中会有I/Q两路混频器,但基带仿真的等效模型是:把频偏乘在基带信号上,信道就是叠加复高斯白噪声。这样仿真结果和真实通带系统的误码率是一致的,但计算量小一个量级。
4.2 接收端:时隙截取、匹配滤波与定时恢复
接收端处理的开始不是解调,而是帧同步。因为接收机不知道时隙的精确起始位置,需要从前导符中寻找定时基准。在仿真里,我们假设已经完成了粗帧同步(这本身就是另一个复杂课题),直接从正确位置截取每个时隙的接收数据。
截取之后,信号依次经过:
- 匹配滤波:用和发射端相同的RRC滤波器卷积,匹配滤波的实质是最大化信噪比。在加性高斯白噪声信道中,匹配滤波是最优的线性滤波。
- 下变频/去频偏:这里需要先估计残余频偏,把载波搬回零频。
- 定时恢复:从每个符号的4个采样点中,找到最佳采样时刻。
定时恢复的经典实现是Gardner算法,它是一种非数据辅助的前馈定时误差检测器,特别适合突发模式的快速同步(因为它收敛快、不需要载波同步的联合辅助)。核心代码:
% Gardner定时误差检测(简化示意) for n = 1:N_symbols-1 mid = y(n*sps + sps/2); % 两个采样点中间点 err = real(mid) * (real(y((n+1)*sps)) - real(y((n-1)*sps))) + ... imag(mid) * (imag(y((n+1)*sps)) - imag(y((n-1)*sps))); end但我在实际使用中发现,Gardner算法在滚降系数很小的系统中(如0.15)性能会明显下降,因为定时误差信号幅度随滚降系数的减小而减小。0.25的滚降下工作良好,这点要保持警惕。
还有一个常见的工程技巧:用前导符做数据辅助的定时估计。假设接收端已知前导符序列,可以用相关法计算出精确的定时偏移,然后用这个初始值去初始化Gardner的插值器(通常用Farrow结构的分数延迟滤波器实现)。这样既保留了Gardner的跟踪能力,又能快速锁定。
4.3 载波频偏估计与相位校正
频偏估计是本项目中最具挑战性的模块,因为双用户各自的频偏不同且突发的持续时间短,不能用连续信号那种长时间积分的方法。我把频偏估计分成了两步:粗估计和细估计。
粗估计:利用前导符与本地序列做滑动相关,相关峰的相位随时间变化的斜率就是频偏。数学上,接收信号可以建模为 y(n) = s(n)·exp(j2πΔfnT+φ),用已知前导 s(n) 共轭相乘后,得到 z(n) = |s(n)|²·exp(j2πΔfnT+φ),对 z(n) 做FFT,其峰值位置对应频偏。
% 粗频偏估计:基于前导的FFT法 rx_preamble = rx_signal(1:preamble_len*sps); z = rx_preamble .* conj(local_preamble_tx); % 去调制后做谱估计 Z = fftshift(fft(z, N_fft)); [~, idx] = max(abs(Z)); freq_est = (idx - N_fft/2 - 1) * (symbol_rate*sps) / N_fft;这里注意FFT分辨率的问题:假设时隙长度1000符号,前导64符号,做4096点FFT,频率分辨率大约是4 MHz/4096 ≈ 977 Hz。这个精度不足以支撑解调,需要细估计。
细估计:用数据辅助的相位差分法。在前导符号已知的情况下,计算相邻前导符号的相位差,然后取平均:
% 细频偏估计:前导差分相位 phase_diff = angle(z(2:end) .* conj(z(1:end-1))); freq_est_fine = mean(phase_diff) / (2*pi*sps*symbol_rate);这种方法的估计方差与信噪比成反比,且随着前导长度增加而减小。仿真中64个符号的前导在Eb/N0=10 dB时,频率估计的标准差能压到几十Hz量级,足够满足QPSK解调需求。
频偏补偿后,还有一个恒定相位偏移需要消除。由于频偏估计的残余误差,信号可能还存在整体相偏。对PSK解调来说,一个简单的办法是使用判决引导环。但在突发模式中,更常用的是在去频偏后对前导符做平均相位估计,因为前导符全部已知:
phase_est = angle(sum(rx_preamble_corrected .* conj(local_preamble))); rx_sym_corrected = rx_sym .* exp(-1j * phase_est);这样一次性把频偏和相偏都修正到位,后面就可以直接做符号判决了。注意如果前导符有残留的频偏估计误差,那么前导末端和数据的相位旋转量不同,细估计时要用加权平滑或者只取数据部分的判决引导算法来兜底。
5. 从误码率看仿真结果:性能验证这么做才靠谱
5.1 E_b/N_0 与信噪比的换算
在加噪声之前,先明确仿真中信噪比的定义。我习惯用 E_b/N_0(每比特能量与噪声功率谱密度之比)作为自变量,因为它可以公平地对比不同调制方式。E_b/N_0 和仿真中的复高斯噪声方差换算关系是:
N_0 = E_b / (E_b/N_0) 换算成功率域为:Noise_power = symbol_power / (2 * bits_per_symbol * EbN0_linear)
因为复噪声每维度(I/Q各一维)功率为 N_0/2,所以总噪声功率为 N_0。在Matlab里加噪声时:
function rx_signal = add_awgn(tx_signal, EbN0_dB, bits_per_symbol, sps) EbN0 = 10^(EbN0_dB/10); signal_power = mean(abs(tx_signal).^2); % 每符号能量 Es = Eb * bits_per_symbol % 噪声单边功率谱密度 N0 = Eb / EbN0_linear N0 = signal_power / (bits_per_symbol/2 * EbN0); % 注意实信号归一化 noise_power = N0 * (symbol_rate * sps) / (symbol_rate * sps); % 等效计算 noise = sqrt(noise_power/2) * (randn(size(tx_signal)) + 1j*randn(size(tx_signal))); rx_signal = tx_signal + noise; end这里有个容易混淆的点:信号功率用的是所有采样点(含过采样)的平均功率,但噪声功率密度 N_0 是每Hz的功率。在离散域中,噪声方差 = N_0 × 采样带宽,而采样带宽就是采样率。所以复AWGN样本的方差就是 N_0(I和Q维各 N_0/2 的方差)。
仿真里一个很实用的小技巧:不要每次循环都重新生成信道系数,而是固定一组随机种子,让同一个数据帧在不同Eb/N0下遍历一遍。这样可以显著降低误码率曲线的抖动,曲线更平滑,看起来也更专业。
5.2 双用户场景的误码率仿真循环
整个仿真的主循环结构如下:
users = struct('id', {1, 2}, 'freq_off', {2000, -3000}, ... 'scramble_seed', {randi(10000,1), randi(10000,1)}); EbN0_dB_list = 0:2:12; BER_user1 = zeros(size(EbN0_dB_list)); BER_user2 = zeros(size(EbN0_dB_list)); for idx = 1:length(EbN0_dB_list) error_bits = [0 0]; total_bits = [0 0]; for frame = 1:num_frames % 生成两用户的发射帧 tx_combined = generate_frame(users, mod_order); % 加噪声 rx_combined = add_awgn(tx_combined, EbN0_dB_list(idx), mod_order, sps); % 接收端处理 for u = 1:2 rx_burst = extract_slot(rx_combined, u, filter_delay); rx_burst = match_filter(rx_burst, rrc_filter); rx_burst = coarse_freq_est_correct(rx_burst, preamble); rx_burst = fine_freq_est_correct(rx_burst, preamble); rx_burst = timing_recovery(rx_burst, sps); rx_burst = phase_correction(rx_burst, preamble); rx_bits = demodulate_hard(rx_burst, mod_order); % 解扰并统计误码 end end end特别注意每帧仿真时,两用户的数据是独立随机生成的,而且每次都要重新随机化。这是为了模拟真实系统中数据不断变化的情况,避免因为测试数据过拟合接收机特性。
我跑完整个仿真的经验是:每个Eb/N0点至少重复200帧(每帧2×936个数据符号),才能在误码率10^-3量级获得可接受的统计置信度。乍一看200帧很多,但1 Msps符号率、4倍过采样下,一个200帧的循环在普通笔记本上也就几分钟,完全在可接受范围。
5.3 结果怎么解读
双用户在相同Eb/N0下的误码率曲线基本重合,因为它们各自独立处理,互不影响。但如果你仔细观察,会发现用户1和用户2的曲线有极小的差异(通常在一两个百分点)。这是正常的,原因是两用户的频偏符号不同,残余频偏经过相位校正模块后的残差略微不同。这个差异在仿真图中几乎不可见,但如果你的曲线差异超过0.5 dB,那说明接收端模块里大概率有bug——常见的是把一个用户的同步参数串到了另一个用户上。
和理论曲线对比也是一种必要的验证手段。QPSK在AWGN信道下的理论误比特率是:
BER_QPSK = Q(sqrt(2·E_b/N_0))
在Eb/N0=8 dB时,理论上QPSK误码率约10^-4量级。如果仿真结果和理论值差1 dB以上,需要回头检查是不是噪声功率算错了,或者滤波器归一化没做好。我见过很多新手在这里栽跟头——明明代码看起来每个模块都正确,但误码率就是离理论曲线差一截,最后发现是rcosdesign的滤波增益导致信号功率统计偏大,实际注入的噪声“相对偏小”,看上去信噪比虚高,曲线左移。
6. 实战中最容易踩的五个坑和排查技巧
6.1 时隙边界串扰
这个坑在仿真里极其隐蔽。成型滤波器的记忆效应使得每个burst的滤波输出会延伸到时隙边界之外,如果接收端的时隙截取窗口严格等于burst长度,那么窗口边缘的信号能量会被截掉,导致匹配滤波器看到的不是一个完整的突发脉冲,定时估计出现偏差。
排查方法:在接收端截取信号后,Visualize一下信号幅度包络,如果看到前后20个采样点幅度急剧衰减到0,那就是截断问题了。加上“保护前缀/后缀”并补偿滤波器延迟即可解决。
6.2 频偏估计产生了“负反馈”
在粗频偏估计里,如果FFT峰值搜索的范围包含镜像频率(即负频偏被误判为正频偏),后续的细估计和相位校正全部会错。特别是双用户频偏一正一负时,如果一个用户的频偏被估计成另一个用户的频偏值,那个用户整帧数据全都解不出来。
排查方法:检查每个用户的频偏估计结果,打印出来和真实值对比。如果出现“正好等于另一个用户的频偏”这种情况,大概率是FFT搜索范围设置不对,或者粗估计前没有做带通滤波把另一个时隙的泄漏信号滤掉。
我的做法是在粗估计前加一个简单的低通滤波器,通带宽度大约是符号速率1.2倍,这样可以先把时隙外的噪声和邻道泄漏压掉,再估计频偏。
6.3 Gardner定时恢复在低信噪比下失效
Gardner算法在Eb/N0低于3 dB时,定时误差信号会被噪声淹没,导致定时追踪发散。这在双用户突发系统中是一个真实威胁——如果信令时隙的信噪比很低,定时没锁住,后面数据全解错。
解决方案:两个思路。第一种是用前导符辅助,第二个是把定时误差信号用滑动窗口平均(比如16个符号窗口),提高估计稳定性。实测下来,加上16个符号的滑动平均后,Gardner在2 dB左右的Eb/N0仍然能保持锁定。
6.4 滤波器群延迟不一致导致星座图旋转
发射端和接收端的RRC滤波器参数必须完全一致,否则级联响应不满足升余弦性质,最佳采样点处会有残余码间干扰。这个坑看起来低级,但不同时刻版本的代码可能改了滤波器阶数或采样率,发射端和接收端引用的变量不是同一个,就在性能曲线种“莫名变差”。
排查方法:把接收端匹配滤波后的星座图画出来,如果星座点是“云团状”而非清晰的四个点簇,大概率是滤波器不匹配。检查发射端和接收端是否引用同一个rrc_filter变量,或者滤波器阶数和滚降系数是否一致。
6.5 误码率曲线出现平台(错误平层)
当误码率在高信噪比区域不再随Eb/N0增加而下降时,首先是检查时隙截取窗口是否切掉了信号拖尾,这会导致不可消除的码间干扰。这个现象我前面提到过,它与滤波器群延迟未补偿直接相关。
再有一个原因:相位模糊无法消除。QPSK解调存在4重相位模糊,如果前导符的相位估计落在错误的象限(相差90度/180度/270度),整个数据符号会全部翻转或等量旋转,误码率锁死在0.5。用差分调制或者在数据中插入已知导频符号可以解决。DVB-RCS标准中专门定义了导频符号来翻解相位模糊,这里如果用绝对相位编码,就必须解决模糊问题。
一个实用的变通方案:在解调判决前,对几个已知导频符号判决并与真实值比对,如果发现旋转了90度倍数,就反向旋转回去。这是实际系统中常用的“导频辅助相位解模糊”思路。
从一次双用户仿真的调试过程说起
最后说说我个人在这次仿真调试中的体会。最花时间的部分不是编写调制解调代码,而是排查“间歇性出现的错误”——有时候用户1解调正确、用户2全错,有时候两人都错,有时候只在某个Eb/N0点出错。后来发现,问题根源是频偏估计模块在低信噪比下偶尔把估计值“崩”到远离真实频偏的位置,形成偶发的恶劣事件。这种错误在连续信号里几乎不会发生,但突发信号的短时长特性放大了估计误差的概率密度。
解决办法是在频偏估计后加一个“合理性检查”:估计值与粗估计的最大可能偏差应在一个先验范围内,如果超出就保留粗估计结果。这个简单粗暴的逻辑在工程中反而比复杂的鲁棒估计算法更稳定。
这套仿真的扩展方向也很多:把时隙数从2扩展到8甚至更多时隙的多用户竞争接入,加入信道编码(DVB-RCS标准中有Turbo码或LDPC码选项)看编码增益,以及引入实际卫星信道的多径衰落。关键框架已经在这里了,换参数和换模块都是水到渠成的事。