5G NR 信号检测:从 PSS 相关到 SSB 栅格恢复
摘要 :本文以 5G NR 下行同步为主线,贯通 PSS 滑窗相关检测 → SSB 起始定位 → 4 符号 OFDM 解调 → nCellID2 验证 的完整链路;并在此基础上给出 定点/时钟/资源复用 等 HDL 落地要点。文中附上 MATLAB + 5G Toolbox 的可跑示例 与两幅核心图像(检测指标、SSB 频域栅格),并对结果进行工程化分析。
1. 场景与目标
-
同步对象 :SSB(4 个 OFDM 符号,中心 240 子载波),其中 PSS 长度 127,用于初始定时与 nCellID2(0/1/2)判定。
-
核心步骤:
-
用三条 PSS 序列做滑动相关 ,峰值判定是否存在 5G 信号并给出符号级定时;
-
按定时截取 4 个符号,去 CP + FFT 得到 SSB 栅格;
-
在频域对三条 PSS 候选再做相干匹配 ,得到 nCellID2;
-
将浮点参考过渡到 定点/HDL ,实现资源复用 与流同步。
-
采用设置:Fs=7.68MSPSF_s=7.68\text{MSPS}Fs=7.68MSPS,NFFT=256N_{\text{FFT}}=256NFFT=256,NCP=18N_{\text{CP}}=18NCP=18,活动子载波 Nactive=240N_\text{active}=240Nactive=240,SCS=30kHzSCS=30\text{kHz}SCS=30kHz。本文给出的实验加入了适度 CFO(300Hz300\text{Hz}300Hz)、时间偏移(505050 样点)与噪声(SNR 15dB15\text{dB}15dB)。
2. 算法脉络与关键公式
这一部分是整个 5G NR 信号检测算法的"灵魂主线"。
它串起了从滑动相关检测 PSS → 自适应门限判决 → SSB 起点回溯 → 栅格恢复与小区识别的完整流程。
匹配滤波 / 相关度量:找到信号的"心跳"
首先,检测器要在噪声背景中找到那条熟悉的 PSS 序列。
我们使用"滑动相关"(也可视为匹配滤波)来检测接收信号 x[n]
和参考 PSS 符号 r_ref[n]
的相似程度:
y[k]=∑nx[n]⋅rref∗(k−n),metric[k]=∣y[k]∣2 y[k] = \sum_n x[n] \cdot r_{\mathrm{ref}}^*(k - n), \qquad \text{metric}[k] = |y[k]|^2 y[k]=n∑x[n]⋅rref∗(k−n),metric[k]=∣y[k]∣2
- 这里,x[n]x[n]x[n] 是接收的复基带信号。
- rref[n]r_{\mathrm{ref}}[n]rref[n] 是"仅含 PSS"的 OFDM 符号模板(先 IFFT,再加上 CP)。
- 相关结果 y[k]y[k]y[k] 描述了在第 kkk 个采样点窗口下,输入与模板的相似程度。
- 取平方模得到的 metric[k]\text{metric}[k]metric[k]就像"心跳强度图",越高代表越接近目标。
由于卷积实现中窗口是从头扫到尾 的,所以当出现相关峰时,窗口尾端刚好对齐 PSS 符号的末尾。
因此,我们要往前回退一个符号长度(包括 CP),才能定位到 PSS 的起点:
n^PSS_start=kpeak−(NFFT+NCP−1) \hat{n}{\mathrm{PSS\start}} = k{\mathrm{peak}} - \bigl(N{\mathrm{FFT}} + N_{\mathrm{CP}} - 1\bigr) n^PSS_start=kpeak−(NFFT+NCP−1)
- kpeakk_{\mathrm{peak}}kpeak:相关输出峰值所在的索引。
- NFFTN_{\mathrm{FFT}}NFFT:OFDM 符号的有效长度(例如 256 点)。
- NCPN_{\mathrm{CP}}NCP:循环前缀长度(例如 18 点)。
在实测中,你的结果是 kpeak=1535k_{\mathrm{peak}} = 1535kpeak=1535,代入后得到
n^PSS_start=1535−(256+18−1)=1262\hat{n}_{\mathrm{PSS\_start}} = 1535 - (256 + 18 - 1) = 1262n^PSS_start=1535−(256+18−1)=1262,
恰好对应于实验输出中"估计 PSS 符号起点: 1262"。这说明定时逻辑完全正确。
自适应门限:教系统"聪明地判断"信号存在
仅靠固定阈值去判断是否检测到峰,在不同信噪比下会非常不稳。
因此我们引入"能量驱动"的自适应门限:
T[k]=max (α⋅Es[k], Tmin),α=10PSSTh10 T[k] = \max\!\left(\alpha \cdot E_s[k], \, T_{\min}\right), \qquad \alpha = 10^{\frac{\mathrm{PSSTh}}{10}} T[k]=max(α⋅Es[k],Tmin),α=1010PSSTh
其中:
- Es[k]E_s[k]Es[k]:当前窗口内的能量估计,通常用移动平均获得;
- α\alphaα:能量缩放系数(门限比例因子),由参数 PSSTh\mathrm{PSSTh}PSSTh(单位 dB)决定;
- TminT_{\min}Tmin:防止门限过低的保护值。
实验中设定 PSSTh=−6 dB\mathrm{PSSTh} = -6\,\text{dB}PSSTh=−6dB,即门限为窗口平均能量的约 25%。
这样,当信号能量比背景高出 6 dB 时,相关峰即可轻松越过门限而触发检测,
而噪声或副峰难以误触发。
在图 1 中可以看到橙色门限线正好压住噪声区,只有真正的信号峰突破它。
SSB 栅格恢复与 nCellID2 估计:从"检测"走向"识别"
检测到 PSS 后,我们就知道了符号边界。
接下来要从该起点回推整块 SSB 的开始位置,并恢复它的频域结构。
-
回推 SSB 起点
已知 PSS 是第 pssSymIdxpssSymIdxpssSymIdx 个符号(通常是第 3 个),
单符号长度为 NFFT+NCPN_{\mathrm{FFT}} + N_{\mathrm{CP}}NFFT+NCP,
因此:
n^SSB_start=n^PSS_start−pssSymIdx×(NFFT+NCP) \hat{n}{\mathrm{SSB\start}} = \hat{n}{\mathrm{PSS\start}} - pssSymIdx \times (N{\mathrm{FFT}} + N{\mathrm{CP}}) n^SSB_start=n^PSS_start−pssSymIdx×(NFFT+NCP)例如本实验 pssSymIdx=2pssSymIdx = 2pssSymIdx=2,则回退两个符号长度即可获得 SSB 起点。
-
OFDM 解调与栅格恢复
从起点开始截取连续 4 个 OFDM 符号,逐个去 CP、做 FFT,并取中心 240 个子载波。
这样得到一个 240×4240 \times 4240×4的复数矩阵,称为 SSB 频域栅格。
-
频域匹配验证 nCellID2
在第 3 个符号的中心 127 个子载波上,提取出估计的 PSS 向量 p^\hat{\mathbf{p}}p^,
并与标准的三条候选序列 p0,p1,p2\mathbf{p}_0, \mathbf{p}_1, \mathbf{p}_2p0,p1,p2 做相干匹配:
sm=∣pmHp^∣,m∈{0,1,2} s_m = \bigl|\mathbf{p}_m^{\mathrm H}\hat{\mathbf{p}}\bigr|, \qquad m \in \{0,1,2\} sm= pmHp^ ,m∈{0,1,2}
最终取相关幅度最大的那条序列对应的索引:
n^CellID2=argmaxmsm \hat{n}_{\mathrm{CellID2}} = \arg\max_m s_m n^CellID2=argmmaxsm- p^\hat{\mathbf{p}}p^:解调得到的 PSS 子载波复数向量(127 维)。
- pm\mathbf{p}_mpm:标准 NR PSS 序列(由
nrPSS(m)
生成)。 - sms_msm:第 m 条候选序列的匹配强度。
小结
先检测心跳(PSS) → 再锁定节拍(定时) → 最后确认身份(nCellID2)。
3. 一键可跑的 MATLAB
完整版代码;
生成:
pssFD = nrPSS(nCellID2_tx)
→ 映射到 NFFT → IFFT+CP检测:
conv(rx, conj(flipud(refSymTD)), 'same')
→ 峰值与门限比较解调:按估计起点截取 4 符号 → 去 CP + FFT → 取中心 240
验证:抽取中心 127 与
nrPSS(0:2)
比对
%% 5G Toolbox 版:PSS检测 -> SSB起始定位 -> OFDM解调 -> nCellID2 验证
% 需求:5G Toolbox(用 nrPSS 产生标准 PSS)
clear; clc; close all;
%% ----------------------- 参数区 -----------------------
rng(42); % 结果可复现
Fs = 7.68e6; % 采样率 (SCS=30k 常见基础率)
SCS = 30e3; % 子载波间隔 (Hz)
Nfft = 256; % FFT 点数(覆盖 240 活动子载波)
Ncp = 18; % CP 样点(演示用的固定CP,工程可替换为精确CP表)
Nactive = 240; % 活动子载波(SSB 宽度)
Khalf = Nactive/2;
numSSBSyms = 4; % SSB 共4个OFDM符号
pssLen = 127; % PSS 长度(3GPP 固定 127)
pssSymIdx = 2; % PSS 放在第3个符号(0-based:0,1,[2],3)
% 发射端配置(演示)
nCellID2_tx = 1; % 取 0/1/2 之一
SNRdB = 15; % AWGN SNR
CFO_Hz = 300; % 载波频偏(Hz)
TIMING_OFFSET = 50; % 采样级时间偏移(样点)
% 检测门限(自适应)
PSSThreshold_dB = -6; % 相对能量门限(dB)
Tmin = Nfft*(2^-12)^2; % 最小门限,防止低能触发
%% ----------------------- 发送端:构造简化 SSB 栅格 -----------------------
% 频域 SSB 网格:Nactive x 4,中心对齐(直流空)
SSB_grid = complex(zeros(Nactive, numSSBSyms));
% 生成标准 NR PSS(5G Toolbox)
pssFD = nrPSS(nCellID2_tx); % 127x1, BPSK, 3GPP 38.211 标准序列
% 将 PSS 居中插入第 pssSymIdx 个符号的 240 子载波中央
SSB_grid(:, pssSymIdx+1) = insertCentered(zeros(Nactive,1), pssFD);
% 其它符号给一点已知/未知占位,这里用 QPSK PRBS(仅演示)
for s = 1:numSSBSyms
if s-1 ~= pssSymIdx
SSB_grid(:, s) = qpskPRBS(Nactive, s);
end
end
% 频域 -> 时域:Nfft IFFT + CP
symTD = [];
for s = 1:numSSBSyms
Xk = mapToNfft(SSB_grid(:, s), Nfft); % 映射到 Nfft,直流对齐
xt = ifft(ifftshift(Xk), Nfft); % IFFT
xt_cp = [xt(end-Ncp+1:end); xt]; % 加CP
symTD = [symTD; xt_cp]; %#ok<AGROW>
end
ssbTD = symTD;
% 前后导零,加入 CFO、时间偏移、AWGN
leadingZeros = zeros(800,1);
trailingZeros = zeros(800,1);
tx = [leadingZeros; ssbTD; trailingZeros];
n = (0:length(tx)-1).';
tx_cfo = tx .* exp(1j*2*pi*CFO_Hz*n/Fs); % 加 CFO
tx_off = [zeros(TIMING_OFFSET,1); tx_cfo]; % 加 时间偏移
rx = awgn_local(tx_off, SNRdB); % 加 AWGN(不需 Comm TB)
%% ----------------------- 接收端:PSS滑窗相关检测 -----------------------
% 构造"仅含PSS"的参考 OFDM 符号(频域放入240中间 -> Nfft IFFT -> +CP)
refSymTD = buildPSSOFDMRef(pssFD, Nactive, Nfft, Ncp);
% 用卷积实现匹配滤波(相关):峰在"窗口末端"
h = conj(flipud(refSymTD));
corrOut = conv(rx, h, 'same');
metric = abs(corrOut).^2;
% 与能量滑窗产生的自适应门限比较
win = ones(length(refSymTD),1);
Es = conv(abs(rx).^2, win, 'same');
alpha = 10^(PSSThreshold_dB/10);
thres = max(Es*alpha, Tmin);
% 峰值检测(大于门限 + 局部极大)
locs = findPeaks(metric, thres, round(0.5*length(refSymTD)));
if isempty(locs)
error('未检测到 PSS 峰,请调低 PSSThreshold_dB 或提高 SNR。');
end
[~,iPick] = max(metric(locs)); % 这里选最大峰更鲁棒
peakIdx = locs(iPick);
% 根据"峰在窗口末端"回推该 PSS 符号起点
pssSymStart = peakIdx - length(refSymTD) + 1;
fprintf('检测到 PSS 峰位置: %d | 估计 PSS 符号起点: %d\n', peakIdx, pssSymStart);
%% ----------------------- 回推 SSB 起始并 OFDM 解调 -----------------------
oneSymLen = Nfft + Ncp;
ssbStart = pssSymStart - pssSymIdx*oneSymLen;
needLen = ssbStart + numSSBSyms*oneSymLen - 1;
if ssbStart < 1 || needLen > length(rx)
error('SSB 截取越界,请增大前后导零或减小时间偏移。');
end
rxGrid = zeros(Nactive, numSSBSyms);
for s = 1:numSSBSyms
segIdx = ssbStart + (s-1)*oneSymLen;
seg = rx(segIdx : segIdx + oneSymLen - 1);
seg_noCP = seg(Ncp+1:end);
Xk = fftshift(fft(seg_noCP, Nfft));
rxGrid(:, s) = Xk(Nfft/2-Khalf+1 : Nfft/2+Khalf);
end
%% ----------------------- 频域验证:估计 nCellID2 -----------------------
rxPSS_fd = extractCentered(rxGrid(:, pssSymIdx+1), pssLen); % 中间127抽出
candSeqs = zeros(pssLen,3);
for m = 0:2
candSeqs(:,m+1) = nrPSS(m);
end
scores = abs(candSeqs' * conj(rxPSS_fd)); % 三个候选的相关幅度
[~, estID2] = max(scores);
estID2 = estID2 - 1;
fprintf('频域匹配估计 nCellID2 = %d(真实发送 = %d)\n', estID2, nCellID2_tx);
%% ----------------------- 可视化 -----------------------
figure('Name','PSS检测指标');
subplot(3,1,1); plot(real(rx)); grid on; title('接收波形 实部'); xlim([1 length(rx)]);
subplot(3,1,2); plot(metric); hold on; plot(thres,'--'); grid on;
title('相关度量 | 自适应门限'); legend('|corr|^2','thres');
xline(peakIdx,'r--','peak');
subplot(3,1,3); stem(abs(rxPSS_fd)); grid on; title('解调得到的 PSS (|FD|, 中心127)');
figure('Name','SSB频域网格幅度');
imagesc(20*log10(abs(rxGrid)+1e-6)); axis xy;
xlabel('OFDM 符号 (1..4)'); ylabel('子载波 (中心240)'); colorbar; title('SSB 解调频域幅度(dB)');
disp('完成:PSS检测、SSB起始定位、OFDM解调与 nCellID2 频域验证。');
%% ====================== 本地函数 ======================
function x = qpskPRBS(N, seed)
% 简单 PRBS -> QPSK(演示占位)
prng = rng; rng(1000+seed);
bits = randi([0 1], 2*N, 1);
b0 = bits(1:2:end); b1 = bits(2:2:end);
x = (1-2*b0) + 1j*(1-2*b1);
x = x / sqrt(2);
rng(prng);
end
function y = insertCentered(vec240, mid127)
% 把长度127的 mid127 居中插到 240 长向量中
Nactive = length(vec240); L = length(mid127);
assert(Nactive>=L);
s = floor((Nactive - L)/2)+1;
y = vec240;
y(s:s+L-1) = mid127;
end
function y = extractCentered(vec240, L)
% 从 240 长向量中间取出长度 L 段
Nactive = length(vec240);
s = floor((Nactive - L)/2)+1;
y = vec240(s:s+L-1);
end
function Xk = mapToNfft(active240, Nfft)
% 把中心 240 子载波映射到 Nfft 频域(直流对齐)
assert(length(active240) <= Nfft, 'active 超过 Nfft');
Xk = zeros(Nfft,1);
half = length(active240)/2;
Xk(Nfft/2 - half + 1 : Nfft/2) = active240(1:half);
Xk(Nfft/2 + 1 : Nfft/2 + half) = active240(half+1:end);
end
function refSymTD = buildPSSOFDMRef(pssFD, Nactive, Nfft, Ncp)
% 构造仅含 PSS 的 OFDM 符号(频域居中->IFFT->加CP)
vec240 = zeros(Nactive,1);
vec240 = insertCentered(vec240, pssFD);
Xk = mapToNfft(vec240, Nfft);
xt = ifft(ifftshift(Xk), Nfft);
refSymTD = [xt(end-Ncp+1:end); xt];
end
function idx = findPeaks(metric, thres, guard)
% 简易峰值检测:metric > thres & 局部极大(分簇取峰)
L = length(metric);
cand = find(metric > thres);
if isempty(cand), idx=[]; return; end
idx = [];
s = cand(1); prev = cand(1);
for k = 2:length(cand)
if cand(k) - prev > guard
[~,im] = max(metric(s:prev));
idx(end+1) = s+im-1; %#ok<AGROW>
s = cand(k);
end
prev = cand(k);
end
[~,im] = max(metric(s:prev));
idx(end+1) = s+im-1;
end
function y = awgn_local(x, SNRdB)
% 本地 AWGN:按输入信号功率加噪,不依赖 Comm TB 的 awgn
Px = mean(abs(x).^2);
SNRlin = 10^(SNRdB/10);
N0 = Px / SNRlin;
n = sqrt(N0/2) * (randn(size(x)) + 1j*randn(size(x)));
y = x + n;
end
4. 仿真结果图

-
接收波形:中段能量明显高于前后静默,符合"静默--SSB--静默"的发射结构。
-
相关度量与门限 :在约 样点 1535 处出现尖峰,并越过门限;说明检测器在目标时刻对齐了参考 PSS。
-
频域 PSS(中心 127) :幅度分布平滑,没有明显泄漏或起伏突变,表明 去 CP、FFT 起点 与 中心抽取均正确。

-
横轴为 OFDM 符号 1--4 ,纵轴为 中心 240 子载波。
-
四幅"竖条纹"能量分布均匀、无倾斜与断裂:说明 FFT 起点正确 、CP 去除准确 、流同步良好。
-
第 3 个符号有 PSS,能量略高或结构特征明显;其余符号为占位 QPSK,整体平坦。
-
未见"斜纹/梳状"失真 → 300 Hz CFO 对正交性影响很小,未引发明显 ICI。