
简介面向MIMO与空时编码方向的研究者这份压缩包提供了一套基于MATLAB的STTC仿真代码重点评估4天线系统中不同状态数2、8、16态在2×1与2×2天线配置下的误码率与帧错误率表现。包内共8个m文件体积仅4KB涵盖数据生成、STTC网格编码、调制、信道模拟、检测及性能统计的完整仿真链路STTC_SIMULATION.m为主程序配合trellis_encoder.m编码器、branch_metric.m分支度量计算、channel1.m信道模型等模块可直接运行并输出不同状态下的BER/FER对比曲线。目前已有175人学习适合无线通信专业的本科高年级学生或工程技术人员参考。通过这套代码读者可以直观理解不同状态数下STTC的复杂度和性能折中掌握在不同天线配置下系统抗衰落与抗干扰能力的变化规律并利用现成模块进一步调整状态数和信道参数为MIMO系统设计和编码策略优化提供实验依据。1. STTC 的格码本质4 天线仿真包里被反复比较的那几行参数STTCSpace-Time Trellis Code空时格码是把卷积编码与多天线发射绑在同一个格型图上的编码方式。与 Alamouti 这类空时分组码相比它靠状态记忆换来编码增益不牺牲速率代价是译码端要跑 Viterbi。标题里这个 zip 出现的4 天线STTC 2STTC 8 16正好对应一次典型的状态数扫描同一套 4 天线 MIMO 链路对比 2、8、16 状态格码的误码率表现。这类工程包通常面向刚入手空时编码仿真、想复现并改参数的研究生或算法工程师核心就两件事看懂查表式编码再自己动天线数、调制阶数和衰落模型。2. 从延迟分集到 STTC 格型状态数、记忆深度与 4 天线码本设计2.1 延迟分集是 STTC 的退化起点记忆从哪来4 天线的延迟分集Delay Diversity是理解状态数最直观的入口。设天线 k 在时刻 t 发送 x_{t-d_k}延迟取 d [0 1 2 3]接收端第 j 根天线拿到的信号是y_t^j Σ_{k1..4} h_{k,j} · x_{t-d_k} n_t^j当前符号 x_t 因此会出现在连续 4 个时刻的接收矢量里等效分集阶数到 4。但符号之间除了被错开没有任何约束拿不到编码增益。要让误码率曲线整体向左移就得给相邻时刻的符号加上编码关联这就是 STTC 相对延迟分集多出来的部分。接收机把 x_t 从一串接收矢量里联合还原等价于在一个记忆状态为 (x_{t-1}, x_{t-2}, x_{t-3}) 的格型上做序列检测状态数差异由此而来。2.2 状态转移查表编码器其实只有一张表STTC 编码器在实现层面就是一张查表。当前状态 s 是最近 mem 个输入符号按 M 进制拼成的整数输入 u 是当前 log2(M) 个比特的索引查表得到两个结果Nt 根天线同时发射的复符号向量 c(s,u)以及下一状态 nxt(s,u)。状态数 ns M^mem。BPSKM2下4 天线延迟 [0 1 2 3] 对应 mem3、ns8延迟改成 [0 1 2 4] 就把 mem 推到 4、ns16只保留一个延迟 [0 0 0 1] 则 ns2。标题里的 STTC 8 16 基本就是这类记忆深度扫描的产物三档对照如下。配置调制延迟组合示例mem状态数每符号候选路径数STTC 2BPSK[0 0 0 1]124STTC 8BPSK[0 1 2 3]3816STTC 16BPSK[0 1 2 4]41632也有人用 QPSK 配 mem2 拿 16 状态每符号传 2 bit频谱效率翻倍但码本得重新搜索。4 天线下手写生成多项式已经不现实工程上先拿延迟分集当基线再做码搜索或直接套用文献里的标准码表。2.3 设计准则与状态数的工程取舍慢衰落信道下STTC 的两条设计准则是秩准则rank criterion与行列式准则determinant criterion。对 4 天线而言满发射分集要求任意两条不同码字序列的差矩阵满秩 4。延迟分集天然满足这条状态少的码却不一定2 状态意味着记忆只有一个符号4 根天线里至少 3 根在发同一个当前符号码字差异在时间维上没有铺开秩常常达不到 4分集阶数掉下来高信噪比段误码率曲线的斜率就变缓。所以 STTC 2 在实际工程里只当对照基线真正决定主配置的是 8 状态和 16 状态那两条曲线怎么拉开差距。STTC 的定位是在固定频谱效率下同时拿分集与编码增益逼近 MIMO 信道容量给出的速率-分集折中边界但状态数翻倍带来的增益是边际递减的16 状态相对 8 状态通常只有 1~3 dB。3. MATLAB 搭建 4 天线 STTC 链路编码、信道与 Viterbi 译码3.1 先把格型查表建出来下面这段把上一章的查表关系落成 MATLAB 结构体。状态编号用 M 进制整数最低位放最近符号这样下一状态能用一次取模算出来不用维护历史数组。function trellis sttc_build_trellis(Nt, M, delay, const) % Nt: 发射天线数; M: 调制阶数(符号种类数) % delay: 1xNt 整数向量, 天线 k 的延迟 % const: 1xM 复数星座, BPSK 为 [1 -1] mem max(delay); ns M^mem; trellis.ns ns; trellis.M M; trellis.Nt Nt; trellis.out zeros(ns, M, Nt); % 三维表: 状态×输入×天线 trellis.nxt zeros(ns, M); for s 0:ns-1 % 状态整数拆成 M 进制位, 得到记忆符号序列 idx s; hist zeros(1, mem); for m 1:mem hist(m) mod(idx, M); % hist(1)x_{t-1}, hist(2)x_{t-2} idx floor(idx/M); end for u 0:M-1 for k 1:Nt d delay(k); if d 0, sym u; else, sym hist(d); end trellis.out(s1, u1, k) const(sym1); end % 新状态 丢最老位, 左移一位, 移入当前输入 trellis.nxt(s1, u1) u M * mod(s, M^(mem-1)); end end ends1和u1是 MATLAB 从 1 编号的索引状态整数本身从 0 开始方便位运算。mod(s, M^(mem-1))的作用是丢掉 M 进制最高位最老符号再把新输入 u 放进最低位。这套查表写法与生成多项式表示完全等价但 16 状态这种小规模格型下查表比卷积式实现更快也不容易把多项式系数写错。编码端就是在这个格型上按输入比特逐符号走一遍function [tx, bits] sttc_encoder(trellis, frame_len) bits randi([0 1], 1, frame_len); % BPSK: 每符号 1 bit s 1; tx zeros(frame_len, trellis.Nt); for t 1:frame_len u bits(t) 1; tx(t,:) squeeze(trellis.out(s, u, :)).; s trellis.nxt(s, u) 1; % 状态沿格型前进 end end这里假设初始状态为 0帧末尾直接截断不回收尾比特。帧长取几百个符号时截断导致的边缘损失可忽略。3.2 平坦瑞利信道与噪声电平换算信道按每发射天线归一保证任何 Nt 下接收端平均功率一致这是不同天线数配置的 Eb/N0 曲线能对齐比较的前提H (randn(Nt, Nr) 1i*randn(Nt, Nr)) / sqrt(2*Nt); % 块衰落, 每元素方差 1/Nt ref squeeze(trellis.out(s1, u1, :)). * H; % 无噪接收, 1xNr N0 1 / (R * 10^(EbN0dB/10)); % 复噪声总方差 rx ref sqrt(N0/2) * (randn(1,Nr) 1i*randn(1,Nr));squeeze把 (1,1,Nt) 切片变成 Nt×1 列转置后乘 H 得到 1×Nr 接收矢量。R 是每符号比特数BPSK 取 1、QPSK 取 2这里设平均符号能量 Es1所以 N0 Es / (R·Eb/N0)。常见的错误是忘记 H 除以 sqrt(Nt)那会让高天线数配置虚高二三 dB。3.3 Viterbi 译码按状态累积欧氏距离与卷积码的 Viterbi 完全同构只是分支度量换成接收矢量与候选空时符号矢量的欧氏距离function bits_hat sttc_viterbi(rx, H, trellis) ns trellis.ns; M trellis.M; pm inf(1, ns); pm(1) 0; % 初始只有状态 0 可达 surv zeros(size(rx,1), ns); choice zeros(size(rx,1), ns); for t 1:size(rx,1) pm_new inf(1, ns); for s 1:ns for u 1:M ref squeeze(trellis.out(s,u,:)). * H; dm sum(abs(rx(t,:) - ref).^2); ns2 trellis.nxt(s,u) 1; if pm(s) dm pm_new(ns2) pm_new(ns2) pm(s) dm; surv(t,ns2) s; % 记录前驱状态 choice(t,ns2) u; % 记录输入符号 end end end pm pm_new; end [~, st] min(pm); bits_hat zeros(1, size(rx,1)); for t size(rx,1):-1:1 bits_hat(t) choice(t, st); st surv(t, st); % 逆推前驱 end end分支度量里没有先验概率项因为输入比特等概。复杂度主力在两层遍历状态与输入16 状态 BPSK 每符号 32 个候选单帧开销不大真正吃时间的是大批量帧的重复仿真所以主循环要固定随机种子见第 4 章。4. STTC 2 / 8 / 16 状态对比仿真参数、批量跑法与结果解读4.1 批量仿真怎么组织才公平三档状态共用同一批信道实现和噪声序列是公平对比的前提。我一般会把三组配置写进一个 cell 数组循环随机种子在循环外固定rng(2024); % 三档配置共享同一随机流 EbN0dB 0:2:16; for cfg {sttc2, sttc8, sttc16} trellis load_trellis(cfg{1}); % 读对应 g 结构体 for snr 1:numel(EbN0dB) nerr 0; nbit 0; while nerr 200 nbit 5e6 [tx, bits] sttc_encoder(trellis, 400); H (randn(4,4) 1i*randn(4,4)) / sqrt(8); % 4x4 MIMO N0 1 / 10^(EbN0dB(snr)/10); rx tx * H sqrt(N0/2)*(randn(400,4) 1i*randn(400,4)); bits_hat sttc_viterbi(rx, H, trellis); nerr nerr sum(bits_hat ~ bits); nbit nbit numel(bits); end ber(snr) nerr / nbit; end end停机条件用nerr 200而不是跑满固定帧数否则高信噪比处的误码点太少曲线拖尾抖动大1e-5 量级根本看不见。帧长 400 对 16 状态格型也够边缘效应占比不到百分之一。4.2 结果曲线怎么读斜率先行假设 4 发 4 收、块衰落典型趋势是三条曲线逐渐分开。STTC 2 在 BER 1e-3 附近就要付出 4~6 dB 的代价而且曲线明显变缓说明分集阶数没拿满。STTC 8 与 STTC 16 在高信噪比段斜率趋近一致——都趋向满分集 4×416——差距主要体现在平移量上也就是行列式准则对应的编码增益。判读顺序是先看斜率再看平移斜率不同是分集问题斜率相同只差平移是编码增益问题改码本才有用单纯加大发射功率救不回来。4.3 帧长、回溯与归一化的三个坑回溯深度在块式仿真里取整帧即可以后若改成流式译码回溯窗取约束长度 5~7 倍就够不必等于帧长。信道归一化前面强调过这里补一个最便宜的自检把 H 换成单位阵代入 Viterbi无噪声时译码应零误码任一位出错都说明查表索引或者星座映射写反了。若要做 QPSK 或者更高阶调制记得星座点能量归一为 1且分支度量里对非等能量星座要加上先验项否则高信噪比段会出现不该有的错误平台。5. zip 工程复现前的三个检查码表、自检与最小改动5.1 核对码表文件与仿真脚本是不是同一套状态数拿到压缩包先不看仿真主函数先找码表装载或构建函数确认它生成的状态数、调制阶数和天线数与文件名声称的一致。很多包的问题是脚本改了状态数、码表文件却没同步跑出来的 16 状态曲线其实还是 8 状态的行为。核对办法很简单把 trellis.ns、trellis.M 打印出来与标题里的 STTC 2 / 8 16 对应上一分钟能省一整天排错。5.2 单位信道与单帧逐符号核对把 H 固定为单位阵发射一串已知比特逐符号检查编码器的 tx 与查表输出是否一致。再单独验证nxt的状态闭环从状态 0 走一圈回到状态 0 的路径必须存在否则格型不封闭Viterbi 回溯到帧首时会取到 inf 度量。这两个自检通过后再进衰落信道此时出错只可能是噪声或度量实现问题。5.3 最值得先改的三个参数第一是接收天线数 Nr把 4 收改成 2 收能直观看到分集阶数减半后曲线斜率变化理解秩准则比读十遍公式都有效。第二是调制阶数BPSK 换 QPSK 时记得同时把每符号比特数 R 从 1 改成 2并重新确认码表维数。第三是衰落模型把块衰落里每帧抽一次 H 改成每个符号独立抽快衰落下的设计准则从秩变为乘积距离16 状态的相对优势通常会缩小这也是往 MIMO 虚拟阵列方向扩展前必须做的一次摸底。改参数时每轮只动一个变量三个配置共用固定随机种子结果可比性就不会被随机性污染。本文还有配套的精品资源点击获取