
简介一份围绕空时编码研究的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 根天线调制方式QPSKM4每符号 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_cur1) PM_new(s_cur1) cand; pre(s_cur1, 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(state1, 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.g0和best.g1替换到编码器和译码器里BER 曲线会随最小行列式的大小明显分层。同一状态数下最小行列式从 3 提升到 6 左右时误码率曲线在中等 SNR 段通常有 0.5 ~ 1 dB 的增益。4.3 天线数、状态数、多普勒频率的取舍天线数决定分集阶数的上限。2 发配置下秩最大为 23 发配置下最大为 3但接收端的信道估计开销和译码分支度量计算量同步增加。快衰落场景中每符号独立抽样让码字差矩阵的秩更充分地体现块衰落场景里一帧若短于网格的记忆长度分集阶数会被帧长截断。多普勒频率的影响主要体现在信道系数变化的快慢上。快衰落模式下每个符号都在经历独立衰落STTC 的网格记忆成为时间分集的有效手段块衰落模式下网格记忆不能提供时间分集编码增益部分被压缩。这是 STTC 和纯 STBC 的本质区别它的抗衰落能力有一部分来自时间维度上的符号交织信道的时间选择性反而成为可以利用的资源。场景建议配置理由慢衰落、帧短4 状态 QPSK网格记忆有效状态数少译码简单快衰落、帧长8 或 16 状态更长的记忆换时间分集高吞吐需求8PSK 或 16QAM数据率提高但星座约束变紧分集设计更严调参顺序建议是先定调制阶数再定状态数最后用 4.2 节的搜索脚本找该配置下的最优生成向量。不要让状态数脱离信道相干时间单独增长否则会出现网格还没走完信道已经变了的失配问题。5. 收尾技巧让 STTC 仿真工程可验证、可提速、可回归5.1 用直连模式验证编码译码闭环拿到一组新生成向量先别急着跑完整信道仿真。把信道置为 1、噪声置为 0帧长设 1000跑一次编码和译码如果 BER 不为 0问题出在编码器和译码器的网格定义不一致。最常见的原因是初始状态约定不一致编码器首符号按s_prev0编码而译码器初始路径度量给了所有状态相同概率。修复方法是在译码器的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_prev1, s_cur1, 1) qpsk_map(mod(g.g0(1)*s_cur g.g1(1)*s_prev, M), M); tab(s_prev1, s_cur1, 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 仿真就不再是跑出一条曲线而是可以稳定对比不同码字和信道配置的工程基准。本文还有配套的精品资源点击获取