ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STTC与波束空时编码联合技术:原理、实现与常见调试要点

STTC与波束空时编码联合技术:原理、实现与常见调试要点 简介一份面向无线通信方向学习者与工程技术人员的 MATLAB 仿真资源聚焦波束形成、空时编码及二者结合的波束空时编码BSTC技术。压缩包体积仅 2KB共 4 个文件其中包含 2 个 MATLAB 脚本、1 个 fig 仿真图像和 1 个说明文档脚本可用于搭建 STTC 仿真流程图像用于直观查看多径或衰落信道下的性能对比文档则对关键技术点作补充说明。已有 137 人学习适合正在研究 MIMO 系统、空时编码或波束赋形的入门与进阶人群。通过运行与分析这套仿真读者可以理解波束形成如何利用天线阵列提升定向增益、STTC 如何借助空间与时间分集改善可靠性并观察到两者结合后的误码率与频谱效率变化为课程设计、课题预研或后续链路优化提供可复现的参考样例。1. 4.1.rar 里的 STTC 与波束空时编码一个问题两个增益源拿到一个名字叫4.1.rar_STTC_波束_波束 空时编码_波束空时编码_空时编码的压缩包第一件事不是解压是看命名。STTC 是 Space-Time Trellis Code空时格码波束空时编码是把波束形成和空时格码放在同一条链路上做联合仿真。4.1 在绝大多数这类课题里指 4 发天线、频谱效率 1 bit/s/Hz 的配置也可能是课题编号但按 4 天线速率 1 去理解后面所有参数都能对上。这套方案要解决的是一个很具体的问题单靠波束形成只有阵列增益单靠空时编码只有分集与编码增益两者合在一起能不能在相同天线数和相同带宽下拿到更陡的误码率曲线。适合谁做 MIMO 物理层仿真的研究生以及评估阵列通信链路的工程师。下文不依赖压缩包内文件只按这个标题对应的标准实现路径展开。2. 空时格码先立住从格状图到 4 状态 4 发天线的编码器实现2.1 STTC 为什么不用逐符号映射格状结构带来的编码增益STTC 和 Alamouti、SFBC 这类空时分组码最大的区别在于它不把符号当独立单元处理。分组码每个时刻只对当前符号或符号块做空时映射块与块之间没有记忆STTC 不一样它内部有一个卷积编码器结构当前时刻发出的 4 个符号不仅取决于当前输入比特还取决于编码器寄存器里存着的之前几个比特。这个记忆就是格状结构它让符号序列在时间轴上形成一条路径接收端译码时不再逐个符号判而是用最大似然序列检测找整条路径上距离最小的那条。代价是译码复杂度上去了收益是除了分集增益之外多出一块编码增益。8 状态甚至 16 状态、32 状态的码在慢衰落信道里能比同天线数的分组码多出 12 dB 的编码增益这个增益在波束形成链路上依然保留。实际工程里选 4 状态还是 8 状态取决于信道相干时间和接收端算力状态数翻倍Viterbi 译码的加比选计算量近似翻倍但误码率曲线不会等比例下降。做联合仿真时我通常先跑 4 状态把链路调通再切 8 状态看增益上限。2.2 4 发天线、速率 1 的生成矩阵把论文表变成 MATLAB 查找表空时格码的编码器由一组生成系数决定。每个状态、每个输入比特组合对应一组输出符号输出符号个数等于发射天线数。4 发天线速率 1 的配置下每个时隙输入 2 比特输出 4 个符号符号取自 QPSK 星座速率 1 正好是 2 bit/symbol 除 2编码器无冗余。这组系数在 Tarokh 那篇经典论文里有现成表格可以抄我做工程时不重新推导生成矩阵直接把表和状态转移图落到 MATLAB 里。下面这段代码把编码器的核心数据结构建出来。状态数 4每个状态存两件事下一状态表和输出符号表。索引方式是当前状态03和输入比特032 bit 组合。% sttc_trellis_4_1.m % 4发天线, 速率1, 4状态STTC编码器查找表 % 状态转移表: next_state(state1, input1) % 输出符号表: tx_symbols(state1, input1, :) 存4个天线的QPSK符号索引 Nstate 4; % 4状态, 记忆长度2 bit Ntx 4; % 4发天线 M 4; % QPSK星座大小 % 生成多项式系数, 按经典4状态4天线速率1配置填入 % g110, g122, g212, g220, 其余天线系数见论文表 gen zeros(Ntx, 2); gen(1,:) [0 2]; % 天线1: 输入比特1和2的系数 gen(2,:) [2 0]; gen(3,:) [0 1]; gen(4,:) [1 2]; next_state zeros(Nstate, M); tx_symbols zeros(Nstate, M, Ntx); % 编码器寄存器状态按 (b2*2 b1) 编码, b1为最早输入 for state 0:Nstate-1 s1 bitand(state, 1); % 寄存器第1级 s2 bitand(bitshift(state,-1), 1); % 寄存器第2级 for input 0:M-1 b1 bitand(input, 1); b2 bitand(bitshift(input,-1), 1); % 下一状态 新输入移入寄存器 next_state(state1, input1) b1 b2*2; % 每个天线输出 mod(当前输入*系数 寄存器*系数, M) for tx 1:Ntx sym_idx mod(b1*gen(tx,1) b2*gen(tx,2) ... s1*gen(mod(tx1,Ntx)1, 1) s2*gen(mod(tx2,Ntx)1, 2), M); tx_symbols(state1, input1, tx) sym_idx; end end end生成矩阵的系数不是随便填的。这四个天线的多项式要保证任意两条格状路径在接收端的欧氏距离足够大才能拿到分集阶数。上面代码的符号索引是 03 的 QPSK 相位索引实际发送时要映射成复星座点exp(1j*pi/2*sym_idx)。注意寄存器里存的是最早输入那 2 bit不是卷积码里常见的当前输入历史输入排列这个顺序决定了状态编号后面 Viterbi 译码做回溯时必须一致。很多仿真结果对不上就是发射端状态编码和接收端回溯顺序不一致导致的。2.3 编码器实现状态转移、输出符号映射与最小驱动代码查找表建好之后编码器本体就是纯粹的查表。每个时隙读 2 bit映射为一个 03 的整数更新状态取出 4 个符号索引映射成复QPSK。写成一个函数输入是二进制比特流输出是 Ntx 路并行符号序列。% sttc_encode.m function tx_seq sttc_encode(bits, trellis) % bits : 列向量, 长度必须为偶数 % trellis : 含 next_state 和 tx_symbols 的结构体 % tx_seq : 每个时隙4天线符号, 尺寸 Ntime x Ntx Ntime length(bits) / 2; tx_seq zeros(Ntime, trellis.Ntx); state 0; QPSK exp(1j * pi/2 * (0:3).); % QPSK星座点, 列向量 for t 1:Ntime input bits(2*t-1) bits(2*t)*2; % 2bit合成索引, 注意bit顺序 sym_idx trellis.tx_symbols(state1, input1, :); tx_seq(t, :) QPSK(sym_idx(:)1).; state trellis.next_state(state1, input1); end end编码器最小驱动代码就这么短核心在于 bit 合成顺序。bits(2*t-1) bits(2*t)*2把先到的比特作为低位对应 2.2 节寄存器编码里最早输入在低位的约定。如果你把顺序反过来编码器仍然在工作输出序列看起来也正常但接收端按正确格状图译码时路径度量会完全对不上误码率直接平层在 0.1 附近。这个坑我在第一次搭 STTC 链路时踩过排查了一整天最后是拿单天线无格状编码的暴力对照才定位到。编码器本身没有太多玄学真正的复杂度在接收端 Viterbi 译码和波束形成上。4 状态 4 发射天线的 STTC 每时隙有 4 个状态、每个状态 4 条分支但每条分支上要计算 4 个发射符号与接收观测值的欧氏距离之和这个分支度量的计算量占整个译码器的六成以上。优化分支度量计算比优化状态转移更快。3. 波束 空时编码联合链路时域波束形成与接收端联合译码3.1 发射端导向矢量加权让 STTC 符号带着空间指向性发出去波束形成和 STTC 联合的方式常见做法不是在 STTC 编码器内部改结构而是在编码器输出之后、进信道之前对每一路天线的符号乘一个复加权系数。这个系数就是导向矢量它把 4 个发射天线上的符号在空间上对齐到某个方向。接收端在期望方向上收到同相叠加阵列增益就出来了。这个方案业界叫时域波束形成因为它直接在符号级做相位旋转不做子载波级处理OFDM 系统里也能逐个符号乘。导向矢量的计算依赖阵列几何。4 发天线最常见的是半波长等距线阵第 i 个天线和第 1 个天线的相位差是-2π * (i-1) * d * sin(theta) / lambdad 取半波长时d/lambda 0.5。发送方向 theta 是相对于阵列法线的角度。代码里直接生成 4x1 的复向量乘到 STTC 输出符号上。% beam_steering_vector.m function w beam_steering_vector(theta_deg, Ntx, d_over_lambda) % 均匀线阵导向矢量, 方向角theta_deg相对法线 % d_over_lambda: 天线间距与波长比, 默认0.5 if nargin 3, d_over_lambda 0.5; end theta theta_deg * pi / 180; w exp(-1j * 2 * pi * d_over_lambda * (0:Ntx-1). * sin(theta)); end这段代码有个容易忽略的细节导向矢量乘在 STTC 符号上之后每个天线发射的符号幅度不变只改相位所以波束形成不改变 STTC 码字的最小欧氏距离结构。编码增益和分集增益完全保留阵列增益是额外加的。这让联合方案的风险变小——波束方向估计错了最多损失阵列增益不会破坏分集。发射端加了波束加权后4 路信号的合成方向图会在 theta 方向出现主瓣。注意波束指向和目标接收机位置要匹配做链路仿真时如果接收机随机撒位置就不能用固定 theta需要每个包根据接收机角度重新算导向矢量。这个角度失配是第 4 章要展开的坑先在这里埋个伏笔。3.2 信道模型与接收端空间匹配滤波 Viterbi 联合译码接收端不能简单地先做波束形成再译码也不能先译码再见波束。正确做法是先对接收向量做空间匹配滤波把 4 路观测合并成一个标量序列再把这个标量序列喂给 Viterbi 译码器。为什么不能先译码因为 STTC 译码需要 4 个发射符号各自的观测值才能算欧氏距离但波束形成后的信号是 4 路符号的加权叠加丢掉了每一路独立的幅度相位信息。所以顺序必须是空间合并 → 得到对发射符号加权和的观测 → 在 Viterbi 分支度量里显式包含波束加权。信道用平坦瑞利衰落4 发 1 收每根接收天线看到 4 个发射天线的独立衰落系数再叠加 AWGN。接收信号是r h. * (w .* tx_symbols) noise注意这里h是 4x1 信道向量w是导向矢量tx_symbols是 4x1 发射符号。等效信道是h_eff sum(h .* w)但每个符号的系数不一样。Viterbi 译码的分支度量要按发射符号加权和来算。第 t 时隙状态 s 到状态 s 的分支上有 4 个符号索引接收观测是 r(t)。对 QPSK 符号分支度量是abs(r(t) - h_eff * sum(w .* qpsk_symbols))^2但这里有个问题h_eff 是叠加后的标量系数不能直接乘到每个符号上。更严谨的写法是逐天线恢复等效信道把 r(t) 看成 4 个符号分别经过 h_i * w_i 衰落后的叠加。Viterbi 度量按这个叠加来算。% sttc_viterbi_decode.m function bits_est sttc_viterbi_decode(rx_seq, h, w, trellis) % rx_seq : 接收符号序列, Ntime x 1 % h : 4x1 信道衰落系数 % w : 4x1 波束导向矢量 % trellis : STTC查找表 QPSK exp(1j * pi/2 * (0:3).); Ntime length(rx_seq); Nstate trellis.Nstate; Ntx trellis.Ntx; % 路径度量 path_metric inf(Nstate, 1); path_metric(1) 0; % 从状态0开始 survivor zeros(Nstate, Ntime); h_eff h .* w; % 联合信道: 衰落 x 波束加权 for t 1:Ntime new_metric inf(Nstate, 1); for state 0:Nstate-1 if isinf(path_metric(state1)), continue; end for input 0:3 sym_idx trellis.tx_symbols(state1, input1, :); syms QPSK(sym_idx(:)1); % 4x1 % 接收期望值: 4路符号经h_eff衰落后的叠加 expected sum(h_eff .* syms); branch_metric abs(rx_seq(t) - expected)^2; ns trellis.next_state(state1, input1); tmp path_metric(state1) branch_metric; if tmp new_metric(ns1) new_metric(ns1) tmp; survivor(ns1, t) state; end end end path_metric new_metric; end % 回溯: 取最后度量最小状态, 沿survivor回溯 [~, state] min(path_metric); bits_est zeros(2*Ntime, 1); for t Ntime:-1:1 % 根据幸存路径反查输入比特 prev_state survivor(state1, t); input -1; for inp 0:3 if trellis.next_state(prev_state1, inp1) state input inp; break; end end bits_est(2*t-1) bitand(input, 1); bits_est(2*t) bitand(bitshift(input, -1), 1); state prev_state; end end这段代码是整条链路最值得调试的部分。Viterbi 译码的复杂度主要在两层循环状态数和输入组合数。4 状态 4 输入内层 16 次迭代符号数一上来就慢了但作为链路验证没问题。三个细节要盯住一是h_eff是逐天线乘不是先对 h 求和再乘 w二是分支度量的expected必须用 4 路符号的叠加不能把 h_eff 提到求和号外三是回溯时输入比特反查依赖发射端状态编码约定2.2 里寄存器顺序错了这里必然错。3.3 把链路代码拼起来主程序结构、循环与信噪比扫描联合链路的主程序就是三大块发射端生成 STTC 符号并加波束加权信道加衰落和噪声接收端做空间匹配和 Viterbi 译码统计误码率。信噪比扫描时注意定义要统一SNR 是每个接收天线上的符号信噪比还是所有天线合并后的总信噪比这个不统一曲线横轴就对不上。% run_sttc_beam_link.m % 主仿真: 扫SNR, 统计BER clear; clc; load_trellis () sttc_trellis_4_1(); trellis load_trellis(); theta_tx 20; % 发射波束指向, 度 Ntx 4; w_tx beam_steering_vector(theta_tx, Ntx, 0.5); Nbits 4000; % 每帧bit数 Nframe 200; % 帧数 snr_dB 0:2:16; % 归一化SNR扫描 ber zeros(size(snr_dB)); for idx 1:length(snr_dB) snr_lin 10^(snr_dB(idx)/10); err_cnt 0; bit_cnt 0; for f 1:Nframe bits randi([0 1], Nbits, 1); tx_seq sttc_encode(bits, trellis); % Ntime x 4 tx_beam tx_seq .* (w_tx.); % 逐天线加波束权重 h (randn(Ntx,1) 1j*randn(Ntx,1)) / sqrt(2); % 瑞利衰落 % 接收: 4路合并叠加噪声, SNR按符号能量归一化 rx tx_beam * h sqrt(0.5/snr_lin) * (randn(size(tx_seq,1),1) 1j*randn(size(tx_seq,1),1)); % 接收端: 已知信道和波束方向, 直接联合译码 bits_est sttc_viterbi_decode(rx, h, w_tx, trellis); err_cnt err_cnt sum(bits ~ bits_est); bit_cnt bit_cnt Nbits; end ber(idx) err_cnt / bit_cnt; fprintf(SNR%3.1f dB BER%.3e\n, snr_dB(idx), ber(idx)); end semilogy(snr_dB, ber, o-); grid on;这段主程序每帧都重新生成信道是快衰落假设每帧内信道恒定但帧间独立。如果要做慢衰落应该每帧只生成一次信道并且多帧共用。SNR 归一化的地方容易出问题tx_beam每路符号功率是 14 路合并后接收功率是sum(abs(h_i*w_i)^2)大约等于 4因为 w 模值为 1所以噪声功率按0.5/snr_lin加是合理的。这里 SNR 定义的是每接收天线的符号信噪比和发射总功率对不上没关系只要发射端和接收端用同一个定义就行。实际工程里我建议在代码顶部注释写清楚SNR每符号每接收天线否则拿给别人复现时横轴对不齐。4. 波束空时编码常见翻车点5 个必须排查的细节4.1 现象误码率曲线在分集增益之外提前平层——生成多项式查表错位我见过最多次的翻车是生成矩阵系数抄错。现象是低信噪比区和理论曲线贴合信噪比抬高后误码率下降变缓出现一个明显平台典型的分集阶数不够长相。原因是 QPSK 映射顺序搞反了论文里符号索引 03 对应相位 0、90、180、270但 MATLAB 的exp(1j*pi/2*(0:3))是从 0 开始递增这两者如果有一个环节差了 90 度部分分支距离变短分集阶数就从 4 掉到 2。解决方法是把编码器输出做一次全排列检查把 4 个天线每个天线固定发射符号 0其余天线遍历 03看接收端欧氏距离矩阵是否对称。不对称就是映射错位。这个检查脚本我保留着每换一种调制方式就重跑一次。4.2 现象加波束后误码率反而不如不加——导向矢量与信道空域方向失配这往往是波束指向和信道主径方向不一致且信道里存在第二条较强路径。波束把能量集中到 theta 方向但信道的那条强径在另一个角度结果主径被抑制了阵列增益变负。解决分两种情况如果信道已知且是单径发射端应把 theta 对准实际到达角仿真时必须让 theta_tx 和信道生成角度一致如果是多径信道波束形成本身就不该用单导向矢量固定指向要用最大比合并或统计波束形成。链路仿真里最常见的问题是 channel 模型里隐含了随机到达角但发射端固执地用固定 20 度。我把信道到达角作为显式参数传入这样失配是可复现的而不是随机出现。4.3 现象高信噪比区域 BER 反而抬升——Viterbi 截断深度不够STTC 的 Viterbi 译码需要回溯深度深度不够时幸存路径还没收敛就被强制判决误码在高信噪比下不降反升。4 状态码的记忆长度是 2理论上回溯深度取 45 倍记忆长度就够也就是 10 个时隙左右但实际仿真里帧长几千个时隙时我遇到过用 5 时隙回溯深度在 14 dB 处 BER 开始反弹的情况。解决方法是把回溯统一放到帧尾做一次而不是流式译码每步都回溯。上面的代码就是这么做的——整帧存 survivor最后从最小度量状态一次性回溯这等效于无限回溯深度没有截断问题。如果要做流式译码回溯深度至少取 5 倍状态记忆加调制阶数。4.4 现象时域波束形成下慢衰减造成码字交织不足——深度交织的取舍STTC 的编码增益依赖时间分集也就是格状路径跨越的时隙越多、信道衰落越独立增益越完整。时域波束形成本身不改变信道时间相关性但加了波束方向约束后等效信道是sum(h_i * w_i)如果 4 个衰落系数高度相关比如天线间距小、角度扩展小等效信道的时间相关性比单天线更高深衰落持续更久。这时 STTC 的格状路径在一段衰落内有效分集下降。工程做法是在编码器后加一个深度交织器把 4 个符号在时间上打散。注意交织深度要超过信道相干时间不是越长越好。4.5 现象仿真速度慢到没法扫参数——分支度量计算的向量化改造上面的 Viterbi 代码双重循环在 Nframe200、SNR 点 9 个、每帧 4000bit 时MATLAB 跑完要十几分钟甚至更久。瓶颈在分支度量那层内层循环。我一般做两步优化第一步把 QPSK 符号和 h_eff 提前预计算成 4x4 的期望值表每时隙按状态和输入直接查表取expected避免重复复数乘第二步把状态循环改成矩阵运算一次处理所有状态。向量化之后同样的仿真参数能快 58 倍这个优化对后面要扫网格参数信噪比、波束角度、多普勒几乎是必须的不然真跑不动。5. 验证这套链路的三种做法误码率对比、分集阶数检验与卫星下行场景推演链路写完之后别急着信前两条 BER 曲线先做三件事验证。第一件是误码率对比基线把波束形成去掉w_tx 全 14 发天线 STTC 的 BER 曲线和经典论文里的结果对比斜率是否一致。第二件是分集阶数检验把 BER 曲线的斜率取出来理论上 4 发天线满分集是 4曲线在误差范围内斜率应为 4 的左右如果算出 2 左右回到第 4 章的映射错位去查。第三件是加波束后的增益分离在同一 BER 目标比如 1e-3下对比不加波束的 SNR 和加波束的 SNR 差值应该接近 10*log10(4)6 dB 的阵列增益理想值实际会略低如果低太多说明导向矢量和信道失配。这三项都过了链路才算可信。最后一个进阶技巧是把它推演到卫星下行波束覆盖场景。低轨卫星或者静止轨道卫星的波束覆盖有个特点波束指向随卫星运动持续变化但 STTC 编码器状态和格状路径不依赖波束方向只要在发射端根据当前覆盖区中心实时更新导向矢量就行。这个场景里我习惯把波束方向和信道到达角都做成时变参数每帧读取一次验证多普勒变化下 BER 是否平稳。实际评估时用 cesium 卫星波束覆盖那类工具算出的地面站经纬度反算到达角喂给上面的链路模型能直接回答一个问题波束空时编码在这种动态指向下相比固定指向能多留多少链路余量。我自己的习惯是保留一份最小可复现版本只留 4 状态 4 天线和固定波束以后换信道模型、换调制方式都在这个版本上改而不是在完整工程上动刀。这样每次改动后先跑基线再跑改动出问题能立刻分清是自己引入的还是本来就有的。这套方案值不值得投入答案取决于你有没有一个需要同时吃下分集和阵列增益的单链路场景如果有STTC 加波束的联合方案比分开做两次仿真省的时间够你把参数调上好几轮。希望帮到你。本文还有配套的精品资源点击获取
返回列表