LTE小区搜索Matlab仿真:PSS/SSS检测与频偏校正实践
简介面向通信工程师与在校学生这份LTE小区搜索MATLAB仿真资源系统覆盖初级同步、频率校准、时间同步三大阶段完整实现从主同步信号PSS/辅同步信号SSS检测、物理小区ID识别到下行信道定时调整的关键流程。包内共95个文件以94个.m脚本为核心按信号生成、加扰调制、信道模型、噪声添加、接收端解调与性能评估等模块划分另有1个.py辅助脚本整体仅101KB结构紧凑便于阅读调试。已有1147人学习下载。仿真中可灵活调整信噪比、多径衰落及频偏等参数直观观察各阶段对小区搜索性能的影响并通过误检率与漏检率指标优化系统设计。代码模块化程度高支持逐步调试与二次开发适合用于本科/研究生通信实验课程也为深入理解LTE底层机制、开展5G相关研究打下基础。对于计划快速搭建小区搜索仿真环境的读者可基于现有工程直接运行并修改参数大幅缩短实验周期。 手机开机之后从按下电源键到屏幕左上角出现信号格中间大概隔着一两秒钟。这一两秒里终端干的第一件事不是上网也不是注册而是先回答三个问题周围有没有小区这个小区是谁现在的时间和频率坐标能不能对上这三个问题合起来就是LTE小区搜索。用Matlab做仿真的时候很多朋友把这个题目想简单了以为调一下lteCellSearch就算完事也有朋友把它想复杂了一上来就卡在ZC序列和m序列的公式里出不来。这篇文章想做的是把PSS/SSS结构、检测算法、Matlab实现放到一条完整的仿真链路上把每一步为什么这样设计讲清楚再把我自己在仿真里踩过的坑交代一遍。适合刚开始接触LTE物理层、打算用Matlab搭小区搜索仿真链路的朋友也适合已经能跑通lteCellSearch、但想搞清楚内部机理的工程师。1. 开机第一件事小区搜索在整个接入流程里到底干了什么1.1 终端必须先回答的三个问题很多仿真新手会忽略一个前提小区搜索不是孤立的一段信号处理它是终端开机后整个接入流程的第一步。终端上电之后先做频带扫描在支持的频段里逐个试找到可能存在LTE信号的频点然后检测PSS主同步信号和SSS辅同步信号完成物理小区IDPCI识别、符号定时、帧定时和频偏估计再往下才轮到PBCH解调、读取MIB、获取系统信息、发起随机接入。把这一串流程摆出来你就明白小区搜索要完成的任务边界了它不需要知道小区里有多少用户不需要知道信道质量多好只需要在低信噪比、大频偏、未知信道冲击响应的情况下给后续的PBCH解调提供一个可靠的起点。凡是影响这个起点的因素——定时偏差、频率偏差、PCI判错——都是小区搜索仿真必须覆盖的问题。所以在写Matlab仿真之前先想清楚你想验证什么。是验证PSS/SSS的检测概率是验证频偏估计算法的精度还是验证整套同步流程在特定信道下的鲁棒性目标不同链路设计差很多。我见过不少同学一上来就搭了20MHz带宽的完整收发链路结果跑一次蒙特卡洛要好几分钟根本没法调参数。这种问题基本都出在没想清楚仿真边界。1.2 为什么PSS和SSS挤在中心1.08MHz有个细节对仿真影响很大PSS和SSS只占用中心6个RB也就是72个子载波约1.08MHz带宽和系统实际带宽无关。终端开机的瞬间不知道系统带宽是1.4MHz还是20MHz所以同步信号必须放在所有UE都能找到的位置。中心频点是唯一的锚点工作在固定采样率下终端可以在不知道系统带宽的情况下完成同步之后再通过MIB获取真实带宽。这个设计对仿真链路有直接启发仿真小区搜索根本不需要搭完整带宽的OFDM链路。我自己常用的做法是直接用1.4MHz带宽配置FFT大小128采样率1.92MHz在这个配置下把同步信号、信道、检测器全部跑通。这样仿真速度快调试方便而且算法逻辑和20MHz完全一致。等性能验证通过之后再考虑要不要在更宽的带宽配置下做验证。1.3 帧结构里的关键时间点LTE FDD帧结构有几个时间点必须刻在脑子里一个无线帧10ms包含10个子帧每个子帧1ms每个子帧2个slot每个slot 0.5ms。常规CP下一个slot包含7个OFDM符号。PSS位于子帧0和子帧5的slot 0的最后一个OFDM符号SSS位于同一个slot的倒数第二个OFDM符号也就是PSS前一个符号。PSS和SSS相邻相隔一个OFDM符号的时间。这意味着PSS每隔5ms出现一次SSS也是。但PSS自己无法区分你检测到的是子帧0还是子帧5的PSS——这就是所谓5ms模糊需要SSS来消除。这个机制直接影响检测器的设计后面我会专门讲。把这些参数整理一下项目参数无线帧长度10ms子帧/时隙1ms / 0.5ms常规CP下每slot符号数7个PSS/SSS位置子帧0和5的slot 0同步信号带宽中心6个RB72子载波1.08MHz物理小区ID范围0~5032. PSS和SSS的结构两个序列如何装下504个小区ID2.1 PSS三条ZC序列和NID2物理小区IDPCI的公式是 PCI 3 * NID1 NID2其中NID1范围0~167NID2范围0~2共504个小区ID。PSS负责携带NID2SSS负责携带NID1两者拼起来就是完整PCI。PSS用的是长度63的ZC序列但实际只用其中62个点因为DC子载波空置。序列生成公式d_u(n) exp(-j * pi * u * n * (n1) / 63)其中n 0,1,...,61u是ZC根索引。协议只用了三个根索引u25对应NID20u29对应NID21u34对应NID22。选择ZC序列的原因很工程化ZC序列的自相关特性好峰均比低而且在时域和频域之间通过傅里叶变换可以互相转换便于接收端做低复杂度相关检测。三条候选序列做相关哪个峰最高就判哪个NID2这个思路简单到让人觉得不真实但它就是PSS检测的基本原理。在Matlab里生成PSS频域序列核心就这几行function pssFreq genPSS(NID2, Nfft) % NID2: 0, 1, 2 uTable [25 29 34]; u uTable(NID2 1); n 0:61; % ZC序列 d exp(-1j * pi * u .* n .* (n 1) / 63); % 频域映射到中心72个子载波6 RB pssFreq zeros(Nfft, 1); k0 Nfft / 2 1; % DC子载波位置 pssFreq(k0-31 : k0-1) d(1:31); % 负频率部分 pssFreq(k01 : k031) d(32:62); % 正频率部分 end这里有个很容易错的地方ZC序列d(1)和d(62)分别映射到最低频和最高频子载波而不是按频谱中心对称展开。我早期实现时在这里吃过亏相关峰死活出不来最后逐点核对才知道映射顺序反了。2.2 SSS两组m序列交织和一个查表SSS的结构比PSS复杂一个量级。它由两个长度31的m序列交织而成子帧0和子帧5的序列排列方式不同目的就是解决5ms模糊。SSS在频域上的生成逻辑大致是将两个长度31的m序列s0和s1交织排列——偶数位置放一组奇数位置放另一组——再用依赖NID2的扰码序列c0、c1以及一个依赖m0的序列z1加扰。核心是m0和m1这两个循环移位值它们由NID1通过协议里的查表唯一确定范围0~30且m0 m1。把168个NID1映射到m0/m1组合再通过子帧0偶子帧和子帧5奇子帧交换s0和s1的位置接收端就能区分当前检测的是10ms帧的前半还是后半。对于仿真来说如果你装了LTE Toolbox直接用lteSSS函数最省事ncellid 42; nid2 mod(ncellid, 3); nid1 floor(ncellid / 3); sssSeq lteSSS(3 * nid1 nid2); % 返回62x1频域序列如果没装工具箱手写SSS会牵扯到m序列移位、Gold序列生成、扰码表三重逻辑。不是不能写但工作量不小。我的建议是第一个仿真版本先用工具箱生成标准序列把链路跑通再回头手写生成器做交叉验证。同步信号生成器本身就是很好的练手项目但不应该在研究检测算法时被它卡住进度。2.3 同步信号在资源格上的具体位置PSS和SSS在频域上占用的子载波位置相同都是中心72个子载波中的62个排除DC。在时域上它们相邻。用Matlab搭资源格的时候可以按如下方式理解整个OFDM资源格是一个Nfft x nsym的矩阵每列是一个OFDM符号。PSS和SSS分别占用相邻两列。以1.4MHz配置、常规CP为例一个slot有7个符号SSS放在第6个符号索引5PSS放在第7个符号索引6。由于PSS/SSS只占用中间部分子载波资源格中其余位置可以填0。这一步对后面OFDM调制很关键因为你得保证PSS/SSS和CP的长度配置、FFT大小都对齐。3. 一条能跑的Matlab链路从资源格到带噪接收波形3.1 参数设定为什么要选1.4MHz带宽仿真链路第一步是定参数。我强烈建议你从1.4MHz带宽开始因为它对应的采样率最低仿真速度最快而且同步信号相关的算法逻辑与其他带宽完全一致。参数如下参数值带宽1.4MHzFFT大小128采样率1.92MHz子载波间隔15kHz常规CP长度符号0为10采样点符号1~6为9采样点每slot符号数7有人会问采样率1.92MHz怎么来的1.4MHz带宽需要至少覆盖72个子载波加上保护带用128点FFT在1.92MHz采样率下完全够用。这个配置也正好让CP长度变成整数做仿真省心。CP长度还有个细节常规CP下每个slot第一个符号的CP是10个采样点其余6个符号是9个采样点。很多自写OFDM调制代码会把所有符号的CP长度当成一样结果接收端解调出来的星座图是斜的PSS相关峰也变得很钝。这个坑后面细说。3.2 发射端生成PSS/SSS并映射到资源格发射端的逻辑是生成PSS和SSS频域序列放到资源格的指定位置然后做OFDM调制。NID2和NID1可以随机选一个比如验证时固定NID21、NID120对应PCI61后面统计误判率时再随机扫。Nfft 128; ncellid 61; nid2 mod(ncellid, 3); nid1 floor(ncellid / 3); % 生成PSS/SSS频域序列 pssFreq genPSS(nid2, Nfft); sssFreq lteSSS(ncellid); % 62x1需要LTE Toolbox % 构造一个slot的资源格128 x 7 grid zeros(Nfft, 7); k0 Nfft / 2 1; % 把SSS放到倒数第二个符号PSS放到最后一个符号 grid(k0-31:k0-1, 5) sssFreq(1:31); grid(k01:k031, 5) sssFreq(32:62); grid(k0-31:k0-1, 6) pssFreq(k0-31:k0-1); grid(k01:k031, 6) pssFreq(k01:k031); % OFDM调制 cpLen [10 9 9 9 9 9 9]; % 常规CP一个slot txSlot ofdmMod(grid, cpLen);ofdmMod的自写实现看起来是这样的function tx ofdmMod(grid, cpLen) [Nfft, nsym] size(grid); tx []; for sym 1:nsym ifftOut ifft(ifftshift(grid(:, sym)), Nfft); cp ifftOut(end - cpLen(sym) 1 : end); tx [tx; cp; ifftOut]; end end注意发送端用了ifftshift再ifft这决定了频域序列和子载波位置的对应关系。接收端解调时要反过来用fftshift再fft。这两端只要有一处不一致整个频域映射就乱了。3.3 信道与接收端加噪、加频偏、抓数据块信道建模最朴素的做法是AWGN加固定频偏。频偏是小区搜索仿真里必须加的一项因为真实终端开机时本地晶振和基站晶振存在偏差比如20ppm的晶振在2.6GHz频段会产生5.2kHz频偏这个值已经接近子载波间隔的三分之一足够让PSS相关峰明显退化。AWGN加噪和加频偏的代码如下snr 5; % dB实际要扫描 % 加噪声 signalPower mean(abs(txSlot).^2); noisePower signalPower / (10^(snr/10)); noise sqrt(noisePower / 2) * (randn(size(txSlot)) 1j * randn(size(txSlot))); rxSlot txSlot noise; % 加频偏 fs 1.92e6; freqOffset 1000; % 1kHz t (0:length(rxSlot)-1). / fs; rxSlot rxSlot .* exp(1j * 2 * pi * freqOffset * t);接收端要做的事就是从这串带噪带频偏的时域波形里先把PSS找到再顺藤摸瓜找到SSS。这里需要注意接收端不能假设自己知道OFDM符号边界所以检测PSS时要用滑动相关在一个较长的接收窗口里搜索而不是直接解调资源格。4. PSS检测的工程细节相关峰、门限、降采样和频偏4.1 时域滑动相关及其计算量问题PSS检测最直观的做法是把接收时域波形和本地生成的三种PSS时域模板做滑动相关找峰值。本地模板就是把PSS频域序列做ifft变到时域pssTime ifft(ifftshift(pssFreq), Nfft);假设接收了一段10ms的波形19200个采样点每个采样点都要和128点模板做一次相关三种NID2各做一遍。算下来不算复杂但这只是理想情况。如果系统工作在20MHz带宽采样率30.72MHz一个10ms帧就有307200个采样点直接做全采样率的滑动相关计算量会非常难看。所以工程上几乎一定会做降采样。由于PSS只占用中心1.08MHz接收端可以先对原始信号做低通滤波然后抽取到1.92MHz采样率。这样在1.4MHz配置下可以直接省去降采样这一步但在20MHz配置下30.72MHz降到1.92MHz数据量直接缩小到1/16计算量大幅下降。4.2 分段相关频偏存在时的保命手段频偏对PSS相关的影响在于接收信号经过频偏后本地模板和接收信号之间会叠加一个随时间线性增长的相位差。整个模板长度的相关积分会把不同位置的相位矢量加在一起频偏大了之后矢量相互抵消相关峰被“抹平”。一个工程上常用的抗频偏方法是分段相关。把128点的PSS时域模板分成前后两半分别和接收信号对应段做相关再把两个相关值取模合并。这样每半段内部的相位积累只有原来的1/2对频偏的容忍度提高了一倍。更进一步的差分相关是把接收信号相邻点共轭相乘后和本地差分序列做相关理论上对固定频偏完全不敏感但噪声会加倍。实际仿真里分段相关是性价比最高的方案。% 分段相关示意 tplHalf1 pssTime(1:Nfft/2); tplHalf2 pssTime(Nfft/21:end); c1 abs(sum(rxSeg(1:Nfft/2) .* conj(tplHalf1))).^2; c2 abs(sum(rxSeg(Nfft/21:end) .* conj(tplHalf2))).^2; corrPower c1 c2;4.3 门限怎么定才不容易误检PSS检测门限是新手最容易糊弄过去的地方。常见做法是取一个固定门限比如相关峰超过某个值就算检测到。但问题在于相关峰的绝对幅度跟信号功率、噪声功率、AGC增益都有关固定门限在某个SNR下好使换一个SNR就失灵。更稳的做法是自适应门限把当前搜索窗口内所有相关值的平均功率作为噪声底门限设为噪声底的若干倍。因为正确PSS位置的相关峰通常是尖锐的而噪声相关值散布在底噪附近峰值与噪声底的比值在一定SNR范围内相对稳定。% 搜索完一个窗口后 noiseFloor mean(corrPowers(:)); threshold 8 * noiseFloor; [maxVal, maxIdx] max(corrPowers(:)); if maxVal threshold % 判定检测到PSS end倍数取多少需要根据目标检测概率和虚警概率权衡。我的经验是先跑一组SNR-5dB到5dB的仿真观察正确峰和噪声底的比值分布再取一个比噪声底高出一截但又不至于漏检的值。8到12倍是我自己常用的区间但不同信道下要重新标定。4.4 PSS检测完成后能拿到什么检测到PSS峰值之后你可以从峰值位置反推出符号定时。峰值对应的时刻就是PSS符号的起始位置附近结合CP长度可以定位到PSS符号的精确边界。同时峰值最高的那条本地模板对应的NID2就是小区组内ID的估计值。但此时还缺两样东西完整PCI和10ms帧边界。PSS只告诉你“这里有PSSNID2某值”却无法告诉你这是子帧0的还是子帧5的PSS。因为两个位置的PSS完全相同。这就是5ms模糊。要消掉它必须把SSS也解出来。5. SSS检测与PCI确认把5ms定时校准到10ms帧边界5.1 核心思路利用相邻符号的位置关系既然PSS和SSS是紧挨着的两个OFDM符号你在PSS检测到定时之后只要把接收窗口向前推一个符号就能把SSS符号完整抓出来。这个符号在频域上解调后提取中心62个子载波就得到接收SSS频域序列。接下来的任务是和所有候选SSS序列比相关。这里有两种做法第一种是用lteSSS遍历168个NID1每个都生成一遍候选序列找相关峰。简单直接但每次要生成序列搜索次数多。第二种是直接解析m0和m1。接收SSS解出后把偶数位和奇数位的序列分别和本地m序列做相关得到m0和m1估计值再反向查表得到NID1。第二种效率高得多也更贴近真实终端的实现方式但代码复杂度高一些。对于仿真验证我推荐先用第一种方式把链路跑通等整体流程没问题了再回头优化成第二种。你先要的是正确结果然后才是速度。% 提取接收SSS频域假设已解调得到rxSssFreq62x1 corrResults zeros(168, 1); for nid1 0:167 candidate lteSSS(3 * nid1 nid2); % 62x1 corrResults(nid1 1) abs(sum(rxSssFreq .* conj(candidate))).^2; end [maxVal, maxIdx] max(corrResults); estNID1 maxIdx - 1;5.2 SSS如何在子帧0和子帧5之间切换SSS解决5ms模糊的关键在于协议把子帧0和子帧5的SSS序列故意做成了“不对称”的两种一种在偶数位置放s0、奇数位置放s1另一种交换位置偶数位置放s1、奇数位置放s0。接收端只要把两种假设都试一遍相关峰值更高的那个就对应正确的子帧索引。顺便也就拿到了10ms帧边界。这意味着SSS检测结果会同时告诉你NID1和当前帧的半个周期位置——是子帧0还是子帧5。这个信息对接下来的PBCH解调至关重要因为PSS/SSS只给出了一个粗略的定时框架PBCH必须知道确切的子帧位置才能正确解资源映射。5.3 综合判决连续多帧确认实际仿真里我会额外加一个工程加固步骤连续检测两帧或三帧只有当PCI估计结果一致时才判定检测成功。原因很简单单帧检测在低SNR下存在一定的误判概率而后续的PBCH解调对PCI错误极其敏感——一旦PCI错了解扰完全失败整条链路白搭。这个多帧确认逻辑在小区搜索仿真里非常值得写进去因为它能显著提升检测成功率指标而且真实终端也确实会做类似的时间分集操作。代价只是多等几个帧周期对于仿真定标来说完全可以接受。6. 仿真最容易翻车的三个地方和交叉验证方法6.1 坑一频偏让相关峰掉下来不是算法写得不对我在自测时遇到过这种情况SNR0dB频偏设成5kHzPSS相关峰几乎看不出来了。第一反应是算法写错了反复查代码查到凌晨后来才意识到是频偏本身已经把相关峰抹掉了。5kHz频偏对128点模板来说首尾相位差已经累积到大约2.1个周期这已经远超普通相关的容忍范围。这意味着仿真频偏参数和算法选择必须配套。你设了5kHz频偏却用整段相关去检测算法必然崩。分段相关是第一步如果频偏更大就得设计两级频率搜索先把频偏粗略对齐到子载波间隔内比如用CP相关或频偏扫描再用分段相关做精确同步。仿真链路里建议先把频偏控制在1~2kHz验证算法正确性之后再做极限测试。6.2 坑二CP长度、采样率和DC子载波的位置这三个参数任何一个对不上仿真结果都像见了鬼似的。CP长度容易错在一个细节普通帧配置下每个slot第一个OFDM符号的CP是10个采样点1.4MHz配置其余6个是9个采样点。如果你把所有CP都设成9或都设成10接收端的符号定时就会产生偏移PSS相关峰也会错位。DC子载波的位置同样容易错。在128点FFT里DC子载波对应索引651-indexedPSS占34到96中的62个点DC位置本身空置。如果你的频域序列映射到了索引33到97这种错位区间相关结果会完全对不上。还有一个隐蔽问题发射端ifftshift接收端fftshift。两端的频域排列如果不对称低频率子载波和高频率子载波会整体交换相关峰直接消失。排查这类问题最快的办法是先不加噪声、不加频偏从接收端把PSS符号解出来和本地PSS序列做一次相关。如果相关值接近1说明频域映射没问题如果不正常从资源格映射开始逐段检查。6.3 坑三蒙特卡洛次数太少性能曲线没法看小区搜索仿真的输出通常是检测概率随SNR变化的曲线。检测概率本质上是伯努利试验的成功率统计误差和试验次数直接相关。SNR0dB时检测概率90%跑100次蒙特卡洛95%置信区间大约是±6%曲线毛刺会非常明显。至少要跑500次关键点可以跑到1000次以上。还有一点每次蒙特卡洛试验要随机换一个小区ID、随机换一次噪声种子、随机换频偏值不要固定参数只在噪声上重复。否则你的检测概率只反映了一种特定配置下的情况换到别的小区ID可能性能就变了尤其是SSS检测对m0/m1组合有细微差异时。6.4 用LTE Toolbox做交叉验证如果你装了LTE ToolboxlteCellSearch函数可以直接从接收波形里估计NCellID、定时偏移和频偏非常适合当“参考答案”。我的习惯是先跑自己手写的检测器把结果存下来再用lteCellSearch对同一段接收波形做一次检测两边的NCellID和定时估计一致才算通过。这样做的好处是有个独立实现帮你兜底一旦两边对不上说明手写实现里大概率有bug。ueConfig struct(NCellID, 0, DuplexMode, FDD, CyclicPrefix, Normal); % 注意NCellID这里只是占位lteCellSearch会覆盖 [cellInfo, symOffset, freqOffset] lteCellSearch(ueConfig, rxWaveform, SamplingRate, fs);lteCellSearch返回的cellInfo里包含检测到的NCellIDsymOffset是符号定时估计freqOffset是频偏估计。这几个值和手写实现的检测结果放一起对比问题瞬间就能暴露出来。我自己做这套仿真最深的体会是小区搜索仿真真正难的不是某一个检测算法而是把信号生成、频域映射、OFDM调制、信道模拟、同步检测、统计评估整条链路串起来的能力。任何一处细节出错最终都会体现在检测概率上而且数据还特别容易看起来“差不多对”——只有逐段验证才能把问题揪出来。如果你正在做LTE相关的Matlab仿真建议严格按“先AWGN无频偏、再AWGN加频偏、最后多径信道”的顺序递进每步都确认性能再进下一步。这样仿真过程会顺畅得多最终结果也更可信。本文还有配套的精品资源点击获取

相关新闻

最新新闻

日新闻

周新闻

月新闻