北斗B1C/B2a信号MATLAB仿真:从基带生成到捕获验证

发布时间:2026/10/27 11:37:19
北斗B1C/B2a信号MATLAB仿真:从基带生成到捕获验证

简介:这份资源面向卫星导航信号处理方向的学习者与研究人员,提供基于MATLAB的北斗B1C、B2a信号生成与仿真代码,可用于理解新一代北斗民用信号的码结构、调制方式与仿真流程,适合具备一定MATLAB基础、正在做课程设计或课题研究的中高级读者。压缩包共6个文件,全部为m脚本,整体约5KB,分别对应B1C与B2a的导频、数据及辅助码生成逻辑,覆盖从码序列构造到信号仿真的关键环节,结构紧凑、便于直接调用与二次修改。目前已有1161人学习下载,说明该方向存在稳定的学习需求。读者可借助这些脚本快速搭建B1C/B2a信号仿真链路,对照代码理解各分量码的生成规则,并在此基础上扩展捕获、跟踪或调制实验,为后续算法验证与工程实现提供可复用的基础代码。

1. 北斗 B1C/B2a 信号仿真:从标准公式到可跑通的 MATLAB 基带链路

做卫星导航接收机验证的人,迟早会撞上一件事:手上只有一台接收机,却想验证捕获、跟踪、解调在异常场景下的表现,比如弱信号、多径、电离层延迟突变。真值场景没法反复复现,外场采集成本又高,这时候信号仿真就成了唯一的后悔药。北斗三号 B1C 和 B2a 是当前公开服务的主力频点,B1C 落在 1575.42 MHz,B2a 落在 1176.45 MHz,两者都采用 QMBOC/BPSK 类调制,但扩频码、子载波、电文结构完全不同。这篇笔记讲的就是怎么用 MATLAB 把这两路信号的基带生成、调制、加噪、捕获验证整条链路搭起来,让新手能照着跑出第一张捕获相关峰,熟手能看清码相位对齐、采样率选取、多普勒搜索这些边界参数。适合做接收机算法验证、教学演示、半实物仿真接口联调的从业者。

2. B1C 与 B2a 的信号结构差异:先搞清楚再动手

2.1 B1C 的 QMBOC 调制与分量构成

B1C 信号不是单一 BPSK,它由两路分量复合而成:一路是数据分量 B1C_data,走 BPSK(1) 扩频,承载电文;另一路是导频分量 B1C_pilot,走 BOC(1,1) 扩频,不带电文。两路正交复用后形成 QMBOC(6,1,4/33) 调制。这个 4/33 是功率配比参数,意思是 BOC(6,1) 分量占总功率的 4/33,剩下的归 BOC(1,1)。实际生成时,导频支路的扩频码和子载波要单独算,不能拿数据支路的直接套。

扩频码方面,B1C 用的是 Weil 码,主码长度 10230,周期 10 ms,码速率 1.023 Mcps。数据分量和导频分量各有一组 Weil 码,导频还额外叠加了子码。Weil 码的生成涉及两个 m 序列的截断与异或,MATLAB 里没有现成函数,得自己按 ICD 里的相位差表构造。常见做法是先把两个 10230 长的 m 序列生成出来,再按索引做循环移位异或。

子载波是 BOC 调制的核心。BOC(1,1) 表示子载波频率 1.023 MHz、码速率 1.023 MHz,方波子载波与扩频码相乘。BOC(6,1) 则是子载波 6.138 MHz。QMBOC 把这两路合在一起,频谱主瓣分裂,抗多径能力比纯 BPSK 强,但捕获时相关峰更窄,对码相位搜索步进要求更高。

2.2 B2a 的 BPSK(10) 与双分量结构

B2a 相对简单一些,数据分量和导频分量都是 BPSK(10) 调制,码速率 10.23 Mcps,主码长度 10230,周期 1 ms。注意这里周期和数据速率跟 B1C 不一样,B2a 的码周期是 1 ms,意味着每秒要重复 1000 次,捕获时相干积分时间的选择要重新算。B2a 的扩频码同样用 Weil 码,但生成多项式和相位差表跟 B1C 不同,不能混用。

B2a 数据分量和导频分量正交复用,导频分量上调制了子码,用于辅助同步。电文方面,B2a 走的是 B-CNAV2 格式,帧结构跟 B1C 的 B-CNAV1 有区别,仿真时如果只做基带验证,电文可以先填随机比特或固定图案,但要做解调验证就得按帧格式组包。

2.3 采样率与中频的选取原则

仿真链路里采样率选多少,直接决定后面捕获能不能跑、跑多快。B2a 码速率 10.23 Mcps,按奈奎斯特至少 20.46 MHz,但实际为了保留 BOC 子载波边带,通常取 4 倍码速率以上,也就是 40.92 MHz 起步。B1C 码速率 1.023 Mcps,但 BOC(6,1) 子载波到 6.138 MHz,采样率建议不低于 24.552 MHz,工程上常取 30.69 MHz 或 40.92 MHz 统一处理双频。

中频选择上,仿真时一般把中频设在采样率的 1/4 附近,方便数字下变频。比如采样率 40.92 MHz,中频取 10.23 MHz,这样混频后频谱搬移干净,不会混叠。如果中频设得太低,BOC 子载波的高频边带会被折叠回来,捕获相关峰会出现伪峰,这是血泪经验。

提示:采样率和中频一旦定下,后面所有码相位、多普勒搜索步进都要跟着改,建议先在纸上算清楚再写代码。

3. 用 MATLAB 生成 B1C/B2a 基带信号的最小链路

3.1 Weil 码生成:从多项式到码序列

Weil 码的生成分三步:先生成两个长度 10230 的 m 序列,再按 ICD 给的相位差做循环移位,最后异或输出。下面这段代码以 B1C 导频分量为例,展示生成框架。实际使用时,两个 m 序列的生成多项式要换成 B1C 对应的那组,相位差也要查表。

% B1C Weil 码生成框架(导频分量示例) function code = generate_weil_code(poly1, poly2, phase_shift, code_len) % poly1, poly2: m序列生成多项式对应的抽头位置 % phase_shift: ICD中给出的相位差 % code_len: 码长度,B1C为10230 % 生成第一个m序列 reg1 = ones(1, max(poly1)); seq1 = zeros(1, code_len); for n = 1:code_len seq1(n) = reg1(end); fb = mod(sum(reg1(poly1)), 2); reg1 = [fb, reg1(1:end-1)]; end % 生成第二个m序列 reg2 = ones(1, max(poly2)); seq2 = zeros(1, code_len); for n = 1:code_len seq2(n) = reg2(end); fb = mod(sum(reg2(poly2)), 2); reg2 = [fb, reg2(1:end-1)]; end % 按相位差循环移位后异或 seq2_shifted = circshift(seq2, [0, phase_shift]); code = mod(seq1 + seq2_shifted, 2); % 映射为双极性 code = 2 * code - 1; end

这段代码里poly1和poly2是 m 序列的抽头位置,比如[1,3]表示反馈抽头在第 1 和第 3 级。phase_shift是 ICD 里查到的相位差,B1C 数据分量和导频分量各有一个值,不能搞混。code_len固定 10230。生成的code是 ±1 双极性序列,方便后面直接与子载波相乘。注意循环移位用circshift时,移位量要对 code_len 取模,否则索引越界。

3.2 BOC 子载波与 QMBOC 复合

BOC 子载波就是一个方波,频率是码速率的整数倍。BOC(1,1) 的子载波频率等于码速率,BOC(6,1) 是 6 倍码速率。生成时用square函数或者直接按采样点判断符号。QMBOC 复合时,BOC(1,1) 分量和 BOC(6,1) 分量按功率配比加权后相加。

% 生成BOC子载波并复合QMBOC fs = 40.92e6; % 采样率 fc = 1.023e6; % B1C码速率 N = 10230; % 一个码周期的采样点数需按fs/fc换算 t = (0:N-1) / fs; % BOC(1,1)子载波 sub_11 = square(2*pi*fc*t); % BOC(6,1)子载波 sub_61 = square(2*pi*6*fc*t); % 假设code_data和code_pilot已生成 % QMBOC复合:导频走BOC(1,1)和BOC(6,1)加权,数据走BOC(1,1) alpha = sqrt(4/33); % BOC(6,1)功率配比 s_pilot = (sqrt(1-alpha^2)*sub_11 + alpha*sub_61) .* code_pilot; s_data = sub_11 .* code_data; % 正交复用 s_b1c = s_data + 1j * s_pilot;

这里alpha是 BOC(6,1) 的幅度加权,功率配比 4/33 对应幅度是 sqrt(4/33)。s_data和s_pilot分别生成后,用复数形式正交相加,实部数据、虚部导频。注意N的取值要按采样率和码周期换算,B1C 一个码周期 10 ms,采样率 40.92 MHz 时对应 409200 个点,上面写 10230 只是示意,实际要改。

3.3 B2a 的 BPSK(10) 生成与参数对照

B2a 生成更直接,码序列生成后直接与载波相乘,不需要子载波。码速率 10.23 Mcps,周期 1 ms,采样率 40.92 MHz 时一个周期 40920 个点。

% B2a BPSK(10)基带生成 fs = 40.92e6; fc_b2a = 10.23e6; N_b2a = 40920; % 1ms周期对应采样点数 t_b2a = (0:N_b2a-1) / fs; % 生成B2a Weil码(数据分量和导频分量各一组) code_b2a_data = generate_weil_code(poly1_b2a, poly2_b2a, shift_data, 10230); code_b2a_pilot = generate_weil_code(poly1_b2a, poly2_b2a, shift_pilot, 10230); % 上采样到采样率 code_data_up = repelem(code_b2a_data, fs/fc_b2a); code_pilot_up = repelem(code_b2a_pilot, fs/fc_b2a); % 正交复用 s_b2a = code_data_up + 1j * code_pilot_up;

repelem做零阶保持上采样,把码速率提到采样率。B2a 数据分量和导频分量的 Weil 码相位差不同,shift_data和shift_pilot要分别查表。生成后的s_b2a是复基带信号,实部数据、虚部导频。

3.4 加噪与多普勒:让仿真接近真实场景

基带生成完,要加多普勒频移和噪声才能验证捕获。多普勒用复指数调制,噪声用awgn或手动加高斯白噪声。

% 加多普勒和噪声 doppler_hz = 5000; % 多普勒频移 t_total = (0:length(s_b1c)-1) / fs; s_b1c_doppler = s_b1c .* exp(1j*2*pi*doppler_hz*t_total); % 加噪声,SNR按需设置 snr_db = -20; s_b1c_noisy = awgn(s_b1c_doppler, snr_db, 'measured');

doppler_hz按实际场景设,地面静态接收机多普勒一般在 ±5 kHz 以内,动态场景可到 ±10 kHz。snr_db设 -20 dB 是弱信号场景,捕获算法能不能扛住就看这一步。awgn的'measured'参数让函数先测信号功率再按 SNR 加噪,避免手动算功率出错。

4. 捕获验证:相关峰对不对,一眼就能看出来

4.1 码相位与多普勒二维搜索

捕获的本质是在码相位和多普勒两个维度上做二维搜索,找相关峰。B1C 码周期 10 ms,码相位搜索范围 0 到 10229,多普勒搜索范围按场景定,步进一般 500 Hz。B2a 码周期 1 ms,搜索范围小,但码速率高,同样码相位点数下时间分辨率更细。

% 二维捕获搜索(简化版) fs = 40.92e6; fc = 1.023e6; code_len = 10230; samples_per_chip = fs / fc; % 本地码生成(与接收信号同码) local_code = generate_weil_code(poly1, poly2, phase_shift, code_len); local_code_up = repelem(local_code, round(samples_per_chip)); % 多普勒搜索 doppler_range = -10000:500:10000; corr_peak = zeros(1, length(doppler_range)); for k = 1:length(doppler_range) % 本地载波混频 t = (0:length(s_b1c_noisy)-1) / fs; local_carrier = exp(-1j*2*pi*doppler_range(k)*t); mixed = s_b1c_noisy .* local_carrier; % 循环相关 corr = ifft(fft(mixed) .* conj(fft([local_code_up, zeros(1, length(mixed)-length(local_code_up))]))); corr_peak(k) = max(abs(corr)); end [~, idx] = max(corr_peak); estimated_doppler = doppler_range(idx);

这段代码用 FFT 做循环相关,比逐点滑动快得多。samples_per_chip是每个码片的采样点数,B1C 在 40.92 MHz 下约 40 个点。doppler_range步进 500 Hz,范围 ±10 kHz,共 41 个搜索单元。corr_peak存每个多普勒下的最大相关值,最后找峰值对应的多普勒。注意local_code_up要补零到跟接收信号等长,否则 FFT 维度不匹配。

4.2 相关峰判读与门限设定

捕获结果出来,要看相关峰是否明显高于噪声底。正常情况下,正确码相位和多普勒处会出现一个尖峰,峰值比次高峰高 2 到 3 倍以上。如果峰值不明显,可能是码相位没对齐、多普勒步进太粗、或者 SNR 太低。

门限设定常用两种:固定门限和自适应门限。固定门限按噪声功率的倍数设,比如噪声均值的 8 倍。自适应门限按每次搜索的峰值统计设,比如取所有相关值的均值和标准差,门限设均值加 6 倍标准差。弱信号下自适应门限更稳,但计算量稍大。

注意:如果相关峰出现在码相位 0 附近但多普勒偏离预期,先检查本地载波频率符号有没有搞反,这是最常见的翻车点。

4.3 用捕获结果反推信号生成是否正确

捕获不只是验证算法,也是验证信号生成对不对的手段。如果生成的 B1C 信号用 B1C 本地码捕不到峰,但用 B2a 本地码能捕到,说明码生成那步搞混了。如果峰值位置对应的码相位跟预设的延迟对不上,说明码序列的循环移位量错了。如果多普勒估计值跟预设的差一个固定偏移,说明载波频率符号或中频设置有问题。

实际操作时,建议先生成无噪声、无多普勒的理想信号,用本地码直接相关,确认峰值在码相位 0 处。然后逐步加多普勒、加噪声,观察峰值移动和降低是否符合预期。这个过程能排掉大部分生成链路的错误。

5. 避坑与排查:仿真链路上最容易翻车的五个点

5.1 相关峰消失或出现在错误码相位

现象:捕获时找不到明显峰值,或者峰值出现在跟预设延迟差很远的码相位上。

原因:Weil 码的相位差表查错,或者两个 m 序列的生成多项式用反了。B1C 数据分量和导频分量的 Weil 码不同,B2a 也不同,混用会导致本地码跟接收信号不相关。

解决:先用理想无噪信号做自相关,确认本地码跟生成码完全对齐。如果自相关峰不在 0 处,检查circshift的移位方向和移位量。移位量要对 code_len 取模,负移位要转成正移位。

5.2 BOC 子载波频率搞错导致频谱折叠

现象:生成的 B1C 信号频谱主瓣位置不对,捕获时出现多个伪峰。

原因:BOC(6,1) 子载波频率算成了 6 倍码速率但采样率不够,或者中频设得太低,高频边带折叠回基带。

解决:确认采样率至少是最高子载波频率的 4 倍。B1C 的 BOC(6,1) 子载波 6.138 MHz,采样率建议 40.92 MHz。中频设在采样率 1/4 附近,比如 10.23 MHz。用fftshift(fft(s_b1c))看频谱,主瓣应该在预期位置。

5.3 采样率与码速率不整除导致码相位漂移

现象:长时间积分后相关峰展宽或分裂,捕获性能下降。

原因:采样率除以码速率不是整数,repelem上采样时每个码片的采样点数不一致,累积后码相位漂移。

解决:选采样率时确保是码速率的整数倍。B1C 码速率 1.023 Mcps,采样率取 40.92 MHz 正好 40 倍。B2a 码速率 10.23 Mcps,40.92 MHz 是 4 倍。如果必须用非整数倍采样率,改用插值上采样而不是repelem。

5.4 多普勒搜索步进太粗漏掉峰值

现象:捕获峰值比预期低很多,或者多普勒估计值偏差超过步进的一半。

原因:多普勒搜索步进设得太大,比如 1 kHz,而实际多普勒落在两个搜索单元中间,相关峰被分裂。

解决:步进按相干积分时间的倒数来设。相干积分 1 ms 时,步进不超过 1 kHz;积分 10 ms 时,步进不超过 100 Hz。B1C 码周期 10 ms,可以用 100 Hz 步进,但搜索单元多,计算量大。折中方案是先用 500 Hz 粗搜,再在峰值附近细搜。

5.5 噪声功率设置错误导致 SNR 失真

现象:加噪后信号功率变化,实际 SNR 跟设定值对不上,捕获性能异常。

原因:awgn函数默认按信号功率加噪,但如果信号是复数且功率归一化没做,'measured'参数测出的功率可能不对。或者手动加噪时噪声方差算错。

解决:生成基带信号后先归一化功率,s = s / sqrt(mean(abs(s).^2)),再用awgn加噪。手动加噪时,噪声方差按10^(-snr_db/10)算,复噪声实部虚部各一半功率。

6. 进阶技巧:用参数化脚本一次跑通双频验证

6.1 把 B1C 和 B2a 抽成配置结构体

写仿真脚本最怕参数散落各处,改一个采样率要翻十处。我一般把 B1C 和 B2a 的参数抽成结构体,主脚本只调结构体,切换频点只改一个字段。

% 双频参数配置 cfg_b1c.fs = 40.92e6; cfg_b1c.fc = 1.023e6; cfg_b1c.code_len = 10230; cfg_b1c.code_period = 10e-3; cfg_b1c.subcarrier = 'QMBOC'; cfg_b2a.fs = 40.92e6; cfg_b2a.fc = 10.23e6; cfg_b2a.code_len = 10230; cfg_b2a.code_period = 1e-3; cfg_b2a.subcarrier = 'BPSK10'; % 统一生成入口 function s = generate_signal(cfg, doppler_hz, snr_db) % 根据cfg.subcarrier分支生成 % ... end

这样切换频点只需改cfg变量,生成函数内部按subcarrier字段分支。code_period用来算采样点数,fs和fc用来算每码片采样数。结构体方式还有一个好处:参数校验可以集中做,比如检查fs/fc是否整数,不满足就报错。

6.2 用捕获结果做闭环验证

生成信号后,用捕获模块反捕,把估计出的码相位和多普勒跟预设值对比,误差在允许范围内就算通过。这个闭环能自动跑,适合批量验证不同 SNR 和多普勒组合。

验证项预设值允许误差判读依据
码相位延迟采样点数±2 个采样点相关峰位置
多普勒设定频移±步进/2峰值对应频点
SNR设定值±1 dB噪声功率实测
码周期10 ms / 1 ms±1 采样点自相关峰间距

跑批量验证时,我习惯把 SNR 从 -30 dB 扫到 0 dB,步进 2 dB,每个点跑 100 次蒙特卡洛,统计捕获成功率。成功率低于 90% 的 SNR 点就是该配置下的捕获门限。这个门限值比理论值更有参考意义,因为它包含了实现损耗。

6.3 一个容易忽略的细节:码相位对齐的采样点级精度

仿真时码相位延迟通常按采样点设,但实际信号延迟是连续的。如果延迟不是采样点整数倍,直接circshift会引入半采样点误差,导致相关峰降低。解决办法是用分数延迟滤波器,或者把采样率提高再移位。

我一般先把采样率提到码速率的 8 倍以上,再做整数移位,误差就压到 1/8 码片以内。如果采样率不能提,用interp1做线性插值移位,但插值会引入高频衰减,BOC 信号边带会受影响。折中方案是只在验证码相位精度时用高采样率,批量跑性能时用低采样率。

6.4 从仿真到半实物:接口对接的注意事项

仿真链路跑通后,如果要接到硬件上做半实物验证,输出信号要转成硬件能接受的格式。常见做法是把复基带信号量化成定点数,按硬件接口的采样率和位宽输出。量化位数一般 8 到 16 bit,位宽不够会引入量化噪声,等效 SNR 下降。

接口对接时注意采样率匹配。硬件 DAC 的采样率可能跟仿真采样率不同,需要重采样。重采样用resample函数,但要注意抗混叠滤波器的群延迟,延迟不补偿会导致码相位偏移。我一般先在仿真里把群延迟算出来,输出时提前补偿。

这套双频仿真链路我从 B1C 单频开始搭,踩了码生成、子载波、采样率好几个坑,后来抽成配置结构体才顺过来。现在跑一次双频捕获验证,从生成到出相关峰图不到两分钟。如果你也在做北斗接收机验证,建议先把 B1C 单频跑通,确认相关峰对了再上 B2a,别一上来就双频联调,出了问题不好定位。希望帮到你。

本文还有配套的精品资源,点击获取