STTC空时网格码的MATLAB仿真:从编码方程到Viterbi译码
2026/9/13 2:44:35 网站建设 项目流程

简介:一份围绕空时编码研究的MATLAB项目源码,聚焦空时网格码(STTC)与空时分组码(STBC),重点仿真无线通信抗衰落性能,面向通信方向的学生、科研人员,以及需要完成毕业设计或课程设计的开发者。源码已经过测试校正,可稳定运行,并支持不同收发天线配置间的性能对比;压缩包共35个文件,以18个m脚本为主,涵盖QAM调制解调、MIMO信道构建、误码率统计等核心步骤,另有8个asv备份文件、6个fig图形文件及3个txt说明文档,整体大小仅62KB。目前已有387人学习下载,借助这套代码可以快速搭建2发2收、2发4收、4发4收等多天线场景,对比固定点与浮点实现,直观理解空时编码对衰落信道的改善效果。总目录与分场景目录结合,函数划分清晰,适合新手逐段研读,也便于后续二次开发。

1. 空时网格码:用网格状态把分集和编码增益一起拿住

同样两根发射天线,Alamouti 能拿满分集,但编码增益被钉死在正交结构的行列式上;空时网格码(Space-Time Trellis Code,简称 STTC)则用一条带记忆的网格换回编码冗余,在相同频谱效率下同时拿到分集增益和编码增益。代价是译码端要跑 Viterbi,状态数一涨,路径度量表就跟着涨。做物理层链路仿真的人最常问的一件事是:教科书上的 STTC 生成矩阵到底怎么落到 MATLAB 仿真链路上,而不是停留在矩阵求秩的公式里。本文就用 2 发 1 收、QPSK、4 状态的经典配置,从编码方程写起,用穷举搜索验证码字的分集与行列式指标,再搭出一整条带瑞利衰落信道和 Viterbi 译码的抗衰落仿真链路,最后解决调试和提速中的几个实际坑。

2. 看懂 STTC 网格:生成序列、状态转移与编码器的最小实现

2.1 从 TCM 到 STTC:时间记忆换空间增益

STTC 的思想可以看成把 Ungerboeck 的网格编码调制(TCM)从一维时间域推到时间加空间两个维度。TCM 靠扩大星座换取编码增益,STTC 则把网格状态放在符号序列上,让每个时刻的发射向量同时由当前符号和过去若干符号决定。这样一来,码字之间最小的差矩阵就不再只是单个符号的欧氏距离,而是整条符号序列在时间轴和天线轴上的联合距离。

和分层空时码(LST)不同,STTC 不需要在接收端做干扰消除;和分块空时码(STBC)不同,STTC 的码率可以做成 1,不必为正交性牺牲星座点数。代价集中在接收端:要获得最大似然性能,必须用 Viterbi 沿网格做全路径搜索,状态数为 (M^{\nu}),其中 (M) 是调制阶数,(\nu) 是记忆深度。4 状态 QPSK 的 STTC 只有 4 个状态,路径度量表很小,是入门和验证理论最适合的配置。

2.2 生成序列与编码方程:矩阵写法决定代码结构

常用的一套 2 发 STTC 编码方程写成这样:

[ x_t^{(1)} = \big(g_0^{(1)} s_t + g_1^{(1)} s_{t-1}\big) \bmod M ] [ x_t^{(2)} = \big(g_0^{(2)} s_t + g_1^{(2)} s_{t-1}\big) \bmod M ]

其中 (s_t \in {0,1,\dots,M-1}) 是当前符号,(s_{t-1}) 是上一时刻符号,也是网格状态。(g_0, g_1) 是两条天线的生成向量,元素都在模 (M) 域里。所有运算在模 (M) 下完成,这和卷积码的生成多项式在二元域上的运算是同一个套路,只不过代数域从 GF(2) 换成了模整数环。

用 MATLAB 实现这组方程时,关键在于把生成向量和符号延迟分开表示,后续做秩搜索和 Viterbi 译码都能复用同一组参数。

2.2.1 编码器函数实现
function X = sttc_encoder(S, g, M) % S: 1 x L 符号序列,每个元素取值 0 ~ M-1 % g: 结构体,g.g0 = [g0(1); g0(2)],g.g1 = [g1(1); g1(2)] % X: 2 x L 发射符号矩阵,第 i 行是第 i 根天线 L = numel(S); X = zeros(2, L); for t = 1:L s_cur = S(t); if t == 1 s_prev = 0; % 初始状态固定为 0 else s_prev = S(t-1); end X(1, t) = mod(g.g0(1) * s_cur + g.g1(1) * s_prev, M); X(2, t) = mod(g.g0(2) * s_cur + g.g1(2) * s_prev, M); end X = X / sqrt(2); % 总发射功率归一化到 1 end

代码的逻辑是按时间步递推:每个时刻的两根天线输出只依赖当前符号和上一个符号。s_prev在首帧取 0,是为了让编码器从确定的网格状态出发,译码端对应的初始路径度量也从这个状态开始。sqrt(2)是 2 发归一化,因为两根天线各发一份符号,总功率会翻倍。

注意g结构体里的两个向量顺序:g.g0乘当前符号,g.g1乘延迟符号。这个顺序决定了搜索脚本里差矩阵各列的构造方式,不要写反,否则码字差矩阵的秩性质会完全改变。

2.2.2 用穷举搜索找一组满分集的生成向量

不是随便给一组 (g) 都能拿到满分集。空时码的分集阶数由码字差矩阵的秩决定,而差矩阵长什么样,取决于 (g_0, g_1) 在所有可能符号差组合下的响应。对 4 状态 QPSK 来说,任意两个码字从某个时刻开始偏离,偏差序列最多延续 2 个符号,所以只需要检查有限长度的差序列。

M = 4; best = struct('det', 0, 'g0', [], 'g1', []); for g0_1 = 0:M-1 for g0_2 = 0:M-1 for g1_1 = 0:M-1 for g1_2 = 0:M-1 g0 = [g0_1; g0_2]; g1 = [g1_1; g1_2]; det_min = inf; ok = true; for d0 = 1:M-1 % 第一个偏差符号 for d1 = 0:M-1 % 第二个偏差符号 Xd = [g0*d0, g0*d1 + g1*d0, g1*d1]; % 2 x 3 差矩阵 G = Xd * Xd'; % Gram 矩阵 if rank(G) < 2 ok = false; % 秩不足,分集不到 2 break; end det_min = min(det_min, det(G)); end if ~ok, break; end end if ok && det_min > best.det best.det = det_min; best.g0 = g0; best.g1 = g1; end end end end end disp(best);

这段脚本跑完 256 组生成向量,每组再检查 12 个差符号组合,总计不到三千次矩阵运算,普通电脑秒级完成。rank(G) < 2对应的物理含义是:存在两条不同的符号序列,在两根天线上产生的发射矩阵完全相同,接收端无法区分,分集阶数退化为 1。det(G)是 Gram 矩阵的行列式,对应行列式准则里的特征值乘积,是编码增益的核心度量。

跑出来的其中一组最优参数是g0 = [1; 0]g1 = [0; 1],最小行列式为 3。这组参数有一个直观解释:天线 1 发当前符号,天线 2 发上一时刻的符号,本质上就是文献里常说的延迟分集。

2.3 状态数、进制数与数据率的关系

STTC 的数据率由调制阶数决定,状态数则由记忆深度决定。QPSK 下 4 状态表示只记忆 1 个符号周期,8 状态记忆 1.5 个符号周期,状态数每乘 (M),记忆深度就加 1。状态数增加的直接收益是行列式准则下的最小特征值乘积通常会变大,编码增益更高;代价是 Viterbi 的每符号分支度量计算量随状态数线性增加,路径回溯表也同步变大。

工程上选状态数要看信道相干时间和帧长的比例。慢衰落信道里,一帧经历基本相同的信道增益,网格记忆带来的编码增益能稳定保有;快衰落信道里,符号间信道变化剧烈,过长的网格记忆反而会把不同信道状态下的符号耦合在一起,误码平台会提前出现。下一章的链路仿真会同时保留两种信道模式的开关,方便对照。

3. 端到端抗衰落仿真:瑞利信道下 Viterbi 译码的完整 MATLAB 链路

3.1 链路参数与仿真主结构

仿真链路的基本结构是:随机符号生成、STTC 编码、QPSK 星座映射、瑞利衰落信道加高斯噪声、Viterbi 译码、误码统计。信道模型提供两种开关:块衰落假设一帧内信道不变;快衰落假设每个符号周期信道独立更新,用于模拟高移动性场景。

参数取值说明
发射天线数2接收端为 1 根天线
调制方式QPSK(M=4)每符号 2 bit
状态数4记忆 1 个符号周期
生成向量g0=[1;0], g1=[0;1]延迟分集特例
帧长128 符号译码回溯按整帧处理
信道模型平坦瑞利,可选块/快衰落每径系数为复高斯随机变量
SNR 范围0 ~ 12 dB步进 2 dB

星座映射函数独立抽出来,避免在译码器里重复写映射逻辑:

function sym = qpsk_map(u, M) % u: 0 ~ M-1 的整数符号 % sym: 单位能量的 QPSK 星座点 sym = exp(1j * pi / M + 1j * (2*pi/M) * u) / sqrt(2); end

这个映射把四个符号均匀分布在单位圆上,相邻星座点角度差 90 度,最小欧氏距离和标准 QPSK 相同。/ sqrt(2)是让每个星座点能量为 0.5,两根天线加起来总功率正好是 1,方便和单一发射天线的 SNR 曲线直接对比。

3.2 分支度量与 Viterbi 译码器实现

接收信号模型是标量:信道的每一根天线系数是独立的复高斯随机变量,接收端看到的是两根天线发射信号的加权和,再加复高斯噪声。分支度量的作用是衡量"当前接收符号和某个网格转移假设的匹配程度"。

function [S_hat, met_end] = sttc_viterbi(r, h, g, M) % r: 1 x L 接收复数序列 % h: 1 x 2 信道系数向量 % g: 编码器生成向量结构体 % S_hat: 译码输出的符号序列 L = length(r); numStates = M; PM = inf(1, numStates); PM(1) = 0; % 初始状态 0 pre = zeros(numStates, L); % 幸存路径记录 for t = 1:L PM_new = inf(1, numStates); for st = 1:numStates % st-1 是上一符号 s_prev = st - 1; for s_cur = 0:M-1 x1 = qpsk_map(mod(g.g0(1)*s_cur + g.g1(1)*s_prev, M), M); x2 = qpsk_map(mod(g.g0(2)*s_cur + g.g1(2)*s_prev, M), M); dm = abs(r(t) - h * [x1; x2])^2; cand = PM(st) + dm; if cand < PM_new(s_cur+1) PM_new(s_cur+1) = cand; pre(s_cur+1, t) = st - 1; end end end PM = PM_new; end [~, last] = min(PM); S_hat = zeros(1, L); state = last - 1; for t = L:-1:1 S_hat(t) = state; state = pre(state+1, t); end met_end = min(PM); end

译码器按时间顺序推进路径度量:每个候选转移计算一个欧氏距离分支度量,加到前一刻的路径度量上,只保留到达每个状态的最小度量路径。pre矩阵记录的是幸存路径从哪个前序状态转移过来,回溯阶段从最后一刻的最小度量状态反推整条路径。注意回溯得到的S_hat(t)直接就是网格状态值,也就是上一时刻的符号值,所以最后一层循环里先取state再更新,顺序不能反过来。

这段实现只有 40 行左右,没有做任何优化,但足以支撑下一节的 BER 仿真。需要提速时,可以把所有可能的qpsk_map结果预先算成查找表,循环里直接查表取复数。

3.3 仿真主循环:帧、信道与误码统计

主循环按 SNR 点分别跑,每个 SNR 点重复多个随机帧,统计误比特数除以总比特数得到 BER 点。为了把注意力放在 STTC 本身,这里不叠加信道编码,也不做交织。

snr_db = 0:2:12; ber = zeros(size(snr_db)); nFrames = 2000; L = 128; M = 4; for k = 1:length(snr_db) nErr = 0; nBit = 0; for f = 1:nFrames S = randi([0, M-1], 1, L); X = sttc_encoder(S, g, M); Xs = qpsk_map(X, M); if blockFading h = sqrt(1/2) * (randn(1,2) + 1j*randn(1,2)); end r = zeros(1, L); for t = 1:L if ~blockFading h = sqrt(1/2) * (randn(1,2) + 1j*randn(1,2)); end r(t) = h * Xs(:,t); n0 = 10^(-snr_db(k)/10); r(t) = r(t) + sqrt(n0/2) * (randn + 1j*randn); end S_hat = sttc_viterbi(r, h, g, M); nErr = nErr + sum(S_hat ~= S); nBit = nBit + L; end ber(k) = nErr / nBit; end semilogy(snr_db, ber, 'o-');

信道系数用sqrt(1/2)归一化是让两根天线的平均接收功率和为 1,这样不同发射天线配置下的 BER 曲线可以直接比较。噪声功率用10^(-snr_db/10)换算,因为 QPSK 映射后每个符号携带 2 bit,信噪比定义在 (E_b/N_0) 上。块衰落模式下h在帧内保持不变,快衰落模式下每符号重新采样,会在第 4 章看到两者在低信噪比区域的斜率差异。

4. 抗衰落性能从哪来:秩准则、行列式准则与参数扫描调优

4.1 码字差矩阵的秩决定分集阶数

空时码的差错概率渐近表达式里,最关键的一项是 ((E_s / 4N_0)^{-N_r r}),其中 (r) 是码字差矩阵的秩。接收端只有 1 根天线时,分集阶数直接等于 (r)。秩为 2 意味着误码率曲线的斜率在双对数坐标下是 -2,也就是 SNR 每增加 10 dB,误码率下降两个数量级。作为对比,单发单收的瑞利信道斜率是 -1。

之前搜索脚本里的rank(G) < 2就是在拦截分集阶数不足的码字。延迟分集 (g0=[1;0], g1=[0;1]) 能满分集,原因可以从差矩阵列看出:两个非零偏差符号 (d0, d1) 生成的差矩阵三列分别是 ([d0;0])、([d1;d0])、([0;d1]),第一列和第三列在两根天线上交替非零,Gram 矩阵必然满秩。这是一个很直观的验证:天线之间有符号副本的时间错位,任何一路符号序列差异都会在至少两根天线的联合信号里留下痕迹。

但秩条件只保证分集,不保证编码增益。行列式准则告诉我们,要比较两个满分集码字,看的是 Gram 矩阵特征值的乘积。搜索脚本里的det_min取所有符号差组合中最小行列式,正是对这个准则的离散近似。(g0=[1;0], g1=[0;1]) 的最小行列式是 3,说明它虽然有满分集,但编码增益并不算高,这也是纯延迟分集在低 SNR 区表现平庸的原因。

4.2 用优化思路搜索生成向量:从穷举到更细的网格

穷举 256 组生成向量适用于 QPSK 4 状态,一旦状态数增加到 8 或 16,生成向量的取值空间会膨胀到几千甚至几万组。此时可以对搜索脚本做两个方向的改进:一是保持生成向量取值在模 M 域上,用离散粒子群或遗传算法在秩约束下最大化最小行列式;二是在已有合格码字附近做局部搜索,固定一根天线的生成向量,只微调另一根。

MATLAB 的优化工具箱里,ga(遗传算法)配合一个自定义适应度函数就能处理这类离散约束优化。适应度函数返回-det_min,并把秩不足的码字设为一个很大的惩罚值。因为搜索维度只有 4 个整数变量,种群设 40、代数设 20 就能稳定收敛。注意这类搜索的目标不是唯一的最优码字,而是"一组满分行列式的解",所以搜索结果出现多组等价参数时不必惊讶。

做完搜索后,把best.g0best.g1替换到编码器和译码器里,BER 曲线会随最小行列式的大小明显分层。同一状态数下,最小行列式从 3 提升到 6 左右时,误码率曲线在中等 SNR 段通常有 0.5 ~ 1 dB 的增益。

4.3 天线数、状态数、多普勒频率的取舍

天线数决定分集阶数的上限。2 发配置下秩最大为 2,3 发配置下最大为 3,但接收端的信道估计开销和译码分支度量计算量同步增加。快衰落场景中,每符号独立抽样让码字差矩阵的秩更充分地体现;块衰落场景里,一帧若短于网格的记忆长度,分集阶数会被帧长截断。

多普勒频率的影响主要体现在信道系数变化的快慢上。快衰落模式下每个符号都在经历独立衰落,STTC 的网格记忆成为时间分集的有效手段;块衰落模式下,网格记忆不能提供时间分集,编码增益部分被压缩。这是 STTC 和纯 STBC 的本质区别:它的抗衰落能力有一部分来自时间维度上的符号交织,信道的时间选择性反而成为可以利用的资源。

场景建议配置理由
慢衰落、帧短4 状态 QPSK网格记忆有效,状态数少,译码简单
快衰落、帧长8 或 16 状态更长的记忆换时间分集
高吞吐需求8PSK 或 16QAM数据率提高,但星座约束变紧,分集设计更严

调参顺序建议是先定调制阶数,再定状态数,最后用 4.2 节的搜索脚本找该配置下的最优生成向量。不要让状态数脱离信道相干时间单独增长,否则会出现"网格还没走完,信道已经变了"的失配问题。

5. 收尾技巧:让 STTC 仿真工程可验证、可提速、可回归

5.1 用直连模式验证编码译码闭环

拿到一组新生成向量,先别急着跑完整信道仿真。把信道置为 1、噪声置为 0,帧长设 1000,跑一次编码和译码:如果 BER 不为 0,问题出在编码器和译码器的网格定义不一致。最常见的原因是初始状态约定不一致,编码器首符号按s_prev=0编码,而译码器初始路径度量给了所有状态相同概率。修复方法是在译码器的PM初始化时把状态 1 置 0,其余置 inf,和编码器的初始状态对齐。

5.2 用预先计算的查找表和配对实测数据提速

Viterbi 译码里最耗时的部分是每次转移都要重新映射星座点。把 16 种(s_prev, s_cur)组合对应的两发星座点预先算成一个M x M x 2的复数查找表,循环里只做复数乘法和模长计算,速度能提升 5 倍左右。若还不够,可以把整个网格转移和回溯写成 C 代码,用 MATLAB 的mex命令编译成 MEX 文件,保持原有调用接口不变。

% tab 尺寸 M x M x 2 tab = zeros(M, M, 2); for s_prev = 0:M-1 for s_cur = 0:M-1 tab(s_prev+1, s_cur+1, 1) = qpsk_map(mod(g.g0(1)*s_cur + g.g1(1)*s_prev, M), M); tab(s_prev+1, s_cur+1, 2) = qpsk_map(mod(g.g0(2)*s_cur + g.g1(2)*s_prev, M), M); end end

译码循环里查表替代映射函数调用,等于每次转移省掉两次复数指数计算。MATLAB 的实时脚本里运行速度提升立竿见影,大规模参数扫描时建议再配合parfor并行跑不同 SNR 点。

5.3 用命令行批处理保持曲线可复现

仿真脚本改用matlab -batch方式运行,不打开图形界面,把 BER 结果直接写入.mat文件或 CSV,方便后续用独立脚本画图。这样每个 SNR 点的随机数种子可以在命令行传入,结果可复现。对频繁调整生成向量做对比实验的场景,把搜索、编码、译码、统计拆成独立函数,写一个入口脚本统一调用,比在编辑器里反复改参数更不容易出错。做完这四步,STTC 仿真就不再是"跑出一条曲线",而是可以稳定对比不同码字和信道配置的工程基准。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询