简介:面向无线通信初学者的空时分组码(STBC)编码译码实现代码包,提供从编码、译码到误码率统计的完整仿真链路。代码结构清晰,核心函数、测试入口与绘图模块划分明确,读者可快速定位各环节并理解空时分组码的编译码原理,以及不同信噪比下的误码率性能表现,适合课程设计、毕业设计、自学入门及无线通信实验课程使用。资源包共3个m文件,分别承担主处理、运行测试与结果绘图功能,压缩包整体仅1KB,轻量且便于逐行研读与调试。该资源已有2318人学习下载,运行后可直接生成误码率曲线,帮助初学者直观对比仿真结果与理论趋势,验证代码正确性;同时为后续扩展调制方式、天线数目或信道模型打下了良好基础,可作为进一步研究空时编码技术的起点。作为空时分组码方向简明实用的入门参考,能有效降低学习门槛,也适合教师作为授课演示素材。
1. 空时分组码STBC:两发一收为何能扛住深衰落
做单天线链路仿真时,最让人头疼的不是调制,而是瑞利信道下的BER曲线在10^-3附近突然变得像断崖后的缓坡,再提高信噪比也压不下去。这种"躺平"在物理层叫无分集时的衰落地板。把发射端换成两根天线,使用Alamouti空时分组码(STBC),在符号级做正交编码,接收端用一根天线和一个线性译码器,就能拿到二阶分集增益。STBC不依赖信道反馈、不扩展带宽,代价只是编码延迟两个符号周期。对做MIMO基带仿真、课程设计或物理层算法验证的从业者来说,STBC是最值得先复现的分集方案:代码量小,但分集阶数、正交性、译码复杂度、BER随信噪比的变化斜率都能看得明明白白。这篇笔记按编码、译码、仿真搭建到踩坑的顺序展开,最后给一个可以自检的验证手法。
2. 编码端:Alamouti矩阵与发射功率归一化
2.1 为什么选Alamouti:分集阶数决定BER斜率
在瑞利衰落信道下,没有分集时误码率在高信噪比区域大约按1/SNR下降,也就是说信噪比每提高10 dB,BER只改善10倍左右。换上Alamouti两天线编码后,同样高信噪比区域BER大约按1/SNR²下降,信噪比每提高10 dB,BER改善接近100倍。这个斜率变化不是小数点的差异,它决定了系统和单天线相比能不能在深衰落场景下继续工作。
Alamouti码是正交空时分组码里最特别的一个:两天线配置下码率是1,也就是两个时隙传输两个符号,没有吞吐率损失。它不要求接收端知道发射天线数量以外的额外反馈,信道估计也是常规的每径估计。这使它成为进入STBC领域最容易下手的切入点。实际工程里,如果只想要分集增益而不是空间复用增益,Alamouti仍然是性价比最高的发射分集方案之一。
从数学结构看,Alamouti编码矩阵的核心是正交性。它把两个数据符号在两个时隙、两根天线上展开,让两根发射天线上的信号在时间维度上满足内积为零。这个正交性在后面译码时能直接消除两根天线之间的符号干扰,把二维联合最优检测退化成两个独立的一维检测。这正是STBC比空间复用更适合低复杂度接收机的原因。
2.2 编码矩阵细节:两个时隙、两根天线、四个符号
假设一个时隙内来了两个QPSK符号s1和s2,编码矩阵写出来是这样:
function X = stbc_encoder(s) % s: 1x2 复调制符号,单位能量 [s1, s2] % X: 2x2 编码矩阵,行=时隙,列=发射天线 X = [s(1) s(2); -conj(s(2)) conj(s(1))] / sqrt(2); end第一个时隙,天线0发送s1,天线1发送s2;第二个时隙,天线0发送-s2的共轭,天线1发送s1的共轭。矩阵行对应时隙,列对应天线,所以第1行第1列是时隙1天线0的发射信号。
这里的/ sqrt(2)是很多脚本里最容易丢掉的一步。它保证每个时隙两根天线的总发射功率与单天线系统相同。如果不除,两天线发射的总功率就是单天线的两倍,仿真出来的BER曲线会凭空多出3 dB增益,看起来误码率很好,但那是功率换来的,不是分集带来的。许多课程设计里的STBC仿真翻车,源头都在这里。
编码矩阵的列向量也可以用来验证正交性:
s = [1+1j, -1+1j] / sqrt(2); X = stbc_encoder(s); corr = X(:, 1)' * X(:, 2); disp(corr); % 理想情况下应该为0[逻辑说明] 这里取第1列和第2列做内积,第1列是两时隙天线0的信号,第2列是两时隙天线1的信号。Alamouti码设计成这两列内积为零,意味着两天线的发射信号在接收端叠加后不会互相制造符号间干扰。
[参数说明]s的每个元素幅度是1,所以能量为1。stbc_encoder内部除sqrt(2)后,每个符号发射幅度变成1/sqrt(2),每个时隙总功率 = |s1|²/2 + |s2|²/2 = 1,正好和单天线每时隙发一个单位能量符号的总功率一致。确认corr输出为0后,编码端才算真正对齐了。
2.3 码率与正交性:1码率的代价与四天线扩展
两天线Alamouti码率是2符号/2时隙=1,是复正交STBC里唯一的满码率方案。扩展到四根发射天线时,想继续保持完全正交,码率会掉到3/4甚至1/2,这是数学上已经证明的约束。换句话说,天线多了,分集阶数高了,但有效吞吐率会打折扣。
| 方案 | 发射天线数 | 码率 | 分集阶数 | 典型译码方式 |
|---|---|---|---|---|
| Alamouti | 2 | 1 | 2 | 线性合并 |
| 通用四天线OSTBC | 4 | 3/4 | 4 | 线性合并 |
| 低码率四天线OSTBC | 4 | 1/2 | 4 | 线性合并 |
实际项目中,如果需要更高数据率,通常会转向空间复用或波束成形,而不是强行增加STBC天线数。STBC更适合把可靠性放在第一位的场景,比如控制信道、低速反馈链路。做仿真时,建议先把两天线Alamouti跑透,再去看四天线扩展,因为译码端的线性合并思想完全一样,只是等效信道矩阵维度变了。
3. 译码端:最大似然合并与信道误差敏感性
3.1 接收模型与等效信道矩阵
假设两根发射天线到接收天线的信道系数分别是h0和h1,都是复高斯随机变量,每个时隙的噪声为n1和n2。接收信号可以写成:
r1 = h0 * X(1,1) + h1 * X(1,2) + n1 r2 = h0 * X(2,1) + h1 * X(2,2) + n2
把编码矩阵带进去:
r1 = (h0 * s1 + h1 * s2) / sqrt(2) + n1 r2 = (-h0 * conj(s2) + h1 * conj(s1)) / sqrt(2) + n2
直接看r2,里面既包含s1的共轭又包含s2的共轭,还不是标准的s1、s2形式。处理办法是对r2取共轭,把接收向量变成[r1, conj(r2)]。这样等效信道矩阵就变成一个2x2矩阵:
[r1; conj(r2)] = (1/sqrt(2)) * [h0, h1; conj(h1), -conj(h0)] * [s1; s2] + [n1; conj(n2)]
这个等效信道矩阵有一个重要性质:它的共轭转置和自身相乘,结果是对角矩阵,对角元素都是|h0|²+|h1|²。也就是说,s1和s2在等效信道里被完全解耦了。这就是正交STBC能线性译码的数学基础。
3.2 ML译码实现:从联合搜索退化成线性合并
最大似然译码本来要在所有可能的(s1, s2)组合里做二维搜索,QPSK下就是16次距离计算。但利用等效信道矩阵的正交性,可以先做一次线性变换,分离出s1和s2的独立统计量,再各自做一维最小距离判决。译码函数写成这样:
function s_hat = stbc_decoder(r, h) % r: 1x2 接收向量 [r1, r2] % h: 1x2 信道向量 [h0, h1] % 输出两个合并后的符号估计值 h0 = h(1); h1 = h(2); g = abs(h0)^2 + abs(h1)^2; % 线性合并,分离s1和s2 z1 = conj(h0) * r(1) + h1 * conj(r(2)); z2 = conj(h1) * r(1) - h0 * conj(r(2)); % 恢复发射幅度,并归一化信道增益 s1 = sqrt(2) * z1 / g; s2 = sqrt(2) * z2 / g; s_hat = [s1, s2]; end[逻辑说明] z1的推导就是拿等效信道矩阵的第一行共轭转置去乘接收向量。展开后,s2的系数是conj(h0)h1 - h1conj(h0),恰好等于0,s1的系数是|h0|²+|h1|²。这样s2被完整消掉。z2同理。所以这里不需要遍历符号组合,两行矩阵乘法就完成了最大似然合并。
[参数说明] r是直接从信道仿真里拿到的复数向量;h是当前帧的信道系数,单位能量复高斯。g是两径幅度平方和,在合并后必须用它归一化,否则输出符号的幅度会随信道随机起伏。乘sqrt(2)是因为编码端做了总功率归一化,发射符号幅度被缩小了sqrt(2)倍,这里要补偿回来,才能拿输出值和标准QPSK星座点直接比较。
得到s_hat后,对每个符号找最近的星座点即可。这个译码器没有迭代,也没有矩阵求逆,复杂度几乎可以忽略。
3.3 信道估计误差:正交性被破坏时的平台效应
上面的推导假设接收端完全知道h0和h1。实际系统里信道是从导频估出来的,估计值和真实值之间存在误差。一旦h0、h1带偏差,合并后的s2系数不再是严格的零,而是残留一个和h0、h1估计误差相关的干扰项。高信噪比下,这个残留干扰不再随噪声下降,BER曲线会出现明显的错误平台。
仿真时如果要模拟非理想信道,常见做法是在真实信道h上叠加一个零均值复高斯误差项,比如估计误差标准差0.05或0.1。误差越大,曲线平台越高。这个现象也解释了为什么STBC虽然译码简单,但工程上对信道估计质量仍然敏感。做课程设计时,如果只跑理想信道估计,会把问题掩盖住;想看到真实的性能边界,建议把导频估计加进去。
4. BER仿真搭建:从QPSK符号到2x1误码率曲线
4.1 仿真参数表:功率归一化与信噪比口径
仿真框架不复杂,但参数口径必须一开始就定死。下面的表是我常用的配置,适合QPSK调制下的Alamouti 2x1验证。
| 参数 | 取值 | 说明 |
|---|---|---|
| 调制方式 | QPSK | 每个符号2比特,单位能量 |
| 编码方式 | Alamouti 2x1 | 2发1收 |
| 信道模型 | 单径瑞利块衰落 | 每帧独立生成h |
| 发射功率约束 | 总功率归一化 | 编码矩阵内部除sqrt(2) |
| SNR定义 | Es/N0 | Es为每时隙总发射能量 |
| 噪声生成 | 复高斯 | 实部虚部方差各No/2 |
| 蒙特卡洛帧数 | 5000 | 每个SNR点独立统计 |
| SNR范围 | 0到20 dB | 步进2 dB |
SNR定义为每时隙总发射能量除以噪声功率谱密度,也就是总功率约束下的信噪比。这样1x1和2x1的发射总功率是一致的,BER曲线差异才真正来自分集增益。如果采用每符号能量Es=1、编码矩阵不归一化,那么2x1会多出3 dB发射功率优势,对比就不公平了。
4.2 瑞利信道与复噪声生成
瑞利衰落信道系数用两个独立实高斯构造:
h = (randn(1, 2) + 1j * randn(1, 2)) / sqrt(2);每个元素的幅度服从瑞利分布,平均功率为1。randn(1,2)生成两径的实部,1j*randn(1,2)生成虚部,除sqrt(2)后每径方差为1。
噪声的功率和SNR绑定,用下面的方式生成:
No = 10^(-snr_dB / 10); n = sqrt(No / 2) * (randn(1, 2) + 1j * randn(1, 2));[逻辑说明] 因为每时隙总发射功率是1,SNR = 1/No,所以No = 10^(-snr_dB/10)。复噪声实部和虚部各占No/2的方差,生成时先构造两个标准实高斯,再乘sqrt(No/2)。
4.3 主仿真循环与误码统计
完整的仿真循环可以直接复制,改造点只在调制和解调部分。
clear; clc; snr_dB = 0:2:20; numFrames = 5000; ber = zeros(size(snr_dB)); for sIdx = 1:length(snr_dB) snr_dB_cur = snr_dB(sIdx); No = 10^(-snr_dB_cur / 10); errors = 0; totalBits = 0; for f = 1:numFrames % 生成两个QPSK符号,共4比特 bits = randi([0 1], 1, 4); s = qpsk_mod(bits); % 编码 X = stbc_encoder(s); % 信道与噪声 h = (randn(1, 2) + 1j * randn(1, 2)) / sqrt(2); n = sqrt(No / 2) * (randn(1, 2) + 1j * randn(1, 2)); % 接收,两个时隙 r1 = h(1) * X(1, 1) + h(2) * X(1, 2) + n(1); r2 = h(1) * X(2, 1) + h(2) * X(2, 2) + n(2); r = [r1, r2]; % 译码 s_hat = stbc_decoder(r, h); % 硬判决并转比特 bits_hat = qpsk_demod(s_hat); errors = errors + sum(bits ~= bits_hat); totalBits = totalBits + length(bits); end ber(sIdx) = errors / totalBits; end semilogy(snr_dB, ber, 'o-'); xlabel('SNR (dB)'); ylabel('BER'); grid on;调制和解调函数:
function s = qpsk_mod(bits) % 输入4比特,输出2个QPSK符号 % Gray映射: 00->1, 01->1j, 11->-1, 10->-1j s = zeros(1, 2); for m = 1:2 b0 = bits(2*m-1); b1 = bits(2*m); if b0 == 0 && b1 == 0 s(m) = 1; elseif b0 == 0 && b1 == 1 s(m) = 1j; elseif b0 == 1 && b1 == 1 s(m) = -1; else s(m) = -1j; end end end function bits_hat = qpsk_demod(s_hat) % 软值硬判决,映射表与调制严格一致 % 星座点顺序: 1, 1j, -1, -1j bits_hat = zeros(1, 4); for m = 1:2 d = s_hat(m); % 找最近星座点 [~, idx] = min(abs(d - [1, 1j, -1, -1j])); % 反查比特: 1->00, 1j->01, -1->11, -1j->10 map_bits = [0 0; 0 1; 1 1; 1 0]; bits_hat(2*m-1:2*m) = map_bits(idx, :); end end[逻辑说明] 主循环里每个帧生成4个随机比特,映射成2个QPSK符号。编码后经过两个时隙的发射和接收,得到r1、r2。译码输出s_hat后,在QPSK星座点里做最近距离判决,再反查比特。
[参数说明]numFrames=5000时,每个SNR点统计20000比特,BER在10^-3左右仍有大约20个错误,曲线不会太跳。如果只想看趋势,2000帧也能用;想精确读曲线,建议加到10000帧。No的计算和4.2节一致,注意它随SNR循环变化,不要放在循环外面。
4.4 结果查看:斜率、增益和参考曲线
跑完后的曲线如果横轴是SNR,对数纵轴是BER,2x1 STBC曲线在高信噪比段的斜率应该明显比1x1更陡。具体看BER=10^-3时,2x1需要的SNR通常比1x1低接近10 dB,但这是包含功率约束在内的综合结果。
如果想和理论对比,可以选1x1 QPSK的瑞利闭式解做参考。2x1 STBC的误符号率和两根接收天线最大比合并完全相同,也可以用已知的MRC理论公式近似。关键是不要拿AWGN曲线去对比,瑞利信道下两者差别很大。
还有一个容易混淆的点:如果横轴换成Eb/N0,QPSK的Es/N0和Eb/N0差10*log10(2)约3 dB。仿真报告里必须写明用的是Es/N0还是Eb/N0,否则别人复现时会对不上曲线位置。这个细节在答辩和代码评审里几乎必被问。
5. 避坑与排查:STBC仿真里五个最常见的翻车点
5.1 能量归一化没做对,2x1比1x1还差
现象:把两天线STBC的BER曲线和1x1放在一起,2x1不仅没有分集增益,还整体偏右几个dB。
原因:编码矩阵没有除sqrt(2),或者把每根天线的功率都保持成单天线满功率,总发射功率等于别人的两倍。表面看曲线好,但一改成公平比较就翻车;反过来,如果符号能量定成了1,编码矩阵除sqrt(2)后又忘记在译码端乘sqrt(2),等效信噪比会损失3 dB。
解决:统一按总功率归一化处理。编码端除sqrt(2),译码端在除以g后乘sqrt(2),两个动作必须成对出现。自检方法:在高信噪比下看散点图,合并后的符号聚类中心应该落在标准QPSK星座点上,幅度为1而不是0.707。
5.2 复数信道共轭顺序写反,译码结果像随机噪声
现象:BER一直在0.5附近,无论信噪比如何提高都不下降,星座图上的点彻底散开。
原因:在把r2转成conj(r2)时,信道和符号的共轭顺序写错。比如错误地用了conj(h1)r1 + h0conj(r2)这类式子,s2的干扰项消不掉,两个符号互相打架。
解决:回到等效信道矩阵,重新核对矩阵第二行的符号。正确写法是z2 = conj(h1)r(1) - h0conj(r(2))。最容易记错的是第二项的减号和h0不带共轭。建议先在无噪声条件下跑一帧,打印译码输出是否能完全恢复原始符号。
5.3 QPSK映射与硬判决表不一致,高SNR出现错误平台
现象:低信噪比时BER看起来正常,但SNR提高到一定值后BER不再下降,维持在10^-1或10^-2的平台。
原因:调制时用了Gray映射,解调硬判决表却按自然二进制反查。两种映射下,星座点本身没错,但判决回比特时会把相邻星座点映射成相差2比特的错误,BER就出现平台。
解决:调制函数和解调函数里的映射表必须一一对应。代码里用同一个常量表维护映射关系,不要在两个函数里各写一遍数字。如果调制和解调拆成独立模块,至少写一段单元测试,输入固定比特,确认解调后能还原。
5.4 蒙特卡洛样本数不够,曲线抖动到没法看
现象:同一段代码跑两次,BER曲线差异很大,特别是在高SNR区域,前后能差一个数量级。
原因:高信噪比时错误事件变得稀疏,每个SNR点的错误数太少,统计方差大。比如BER=10^-4时每帧只有4比特错误,5000帧大约200个错误,还算可用;如果只跑500帧,就可能只抓到几个错误甚至零错误。
解决:要么增加帧数,要么动态决定帧数。常见做法是设定最小错误数阈值,比如累计到100个比特错误才停止当前SNR点的循环。这样低信噪比不会多跑无用帧,高信噪比不会因为错误太少而抖动。
5.5 星座图看着正常,BER却按比特数算错了
现象:星座聚类位置完美,BER数值却和理论对照差固定倍数,比如偏高2倍或4倍。
原因:误码率统计时分母用的是符号数而不是比特数,或者反过来了。QPSK每个符号2比特,计算BER必须用比特错误总数除以比特总数。符号错误率转比特错误率在小信噪比时也不是简单的除以2。
解决:统计循环里明确写totalBits = totalBits + length(bits),错误累加用errors = errors + sum(bits ~= bits_hat)。不要偷懒用符号计数器。报告里如果同时需要SER和BER,就各算各的,混在一起会在评审时被一眼看穿。
6. 进阶验证:用等效信道正交性自检译码与功率
很多人在跑通STBC后就直接收工,其实还有一个很低成本的验证手段:直接构造等效信道矩阵,检查H'*H是否为对角阵。这个检查能一次性确认编码矩阵、信道合并顺序、接收向量拼接方式三者是否一致。
h0 = 0.3 + 1j * 0.8; h1 = -0.5 - 0.2j; H = [h0, h1; conj(h1), -conj(h0)]; D = H' * H; disp(D);[逻辑说明] H就是接收端实际起作用的等效信道矩阵,D的非对角元素理论上是0,对角元素是|h0|²+|h1|²。如果构造H时把共轭位置写错,D立刻会出现明显的非零交叉项,这比等到看BER曲线再排查快得多。
我习惯在每次新仿真里把这个检查放在最前面,跑完无噪声一帧恢复后,再做一次带噪声的完整蒙特卡洛。三个检查按顺序过:第一,H'*H对角;第二,无噪声译码输出和发送符号完全一致;第三,BER曲线的高信噪比斜率明显比无分集时陡。三步都通过,STBC部分才算真正落地。
从那以后,我每次拿到新的MIMO仿真脚本,都会强制走一遍这个三步自检,特别是第一步的等效信道正交性检查。这个习惯帮我挡掉了至少五次因为共轭写反或天线顺序错乱导致的无效仿真。希望帮到你。
本文还有配套的精品资源,点击获取