简介:一份面向无线通信初学者的 MATLAB 仿真资源,聚焦 STBC 与 Alamouti 编码在空间调制(SM)中的联合应用,帮助理解 MIMO 系统发射分集原理与仿真流程。压缩包仅含 1 个 m 文件,大小仅 2KB,代码精简但覆盖完整:从数据符号生成(如 QPSK/16-QAM)、Alamouti 编码映射,到瑞利信道模拟、接收端解码,再到误码率计算与绘图,可直接运行观察不同信噪比下的性能表现。已有 211 人学习下载,适合正在学习 MIMO、STBC 或空间调制课程的学生,以及希望快速搭建 Alamouti-SM 仿真模型的科研入门者。通过该脚本,读者可以直观对比 Alamouti 码带来的空间分集增益,并基于代码进一步修改调制阶数或信道参数,开展扩展实验。 做MIMO物理层仿真的人,基本没人绕得过Alamouti这个名字。我之前在整理“stbc_sm_Alamouti_sm_”这个项目时,正好把Alamouti空时分组码(STBC)和空间调制(SM)放进同一条链路里做联合仿真,既要满分集增益,又想把频谱效率往上抬一抬。这篇把这套系统的原理、仿真实现、参数选取和调试中踩过的坑完整捋一遍,给正在做无线通信课程设计、毕业设计,或者准备复现论文的同学一个能直接落地的参考。
这套方案最吸引人的点在于,它不是单纯把两个模块拼在一起,而是在Alamouti正交编码的基础上,把“哪一对天线被激活”本身也变成信息比特的一部分。既有STBC的分集优势,又有SM的频谱效率提升。下面先从核心概念讲起,再给系统模型、关键代码和问题排查记录,按一个完整工程的顺序来。
1. 项目到底做什么:STBC、SM和Alamouti的三角关系
1.1 先厘清三个关键词
很多同学第一次看到“stbc_sm_Alamouti_sm_”这个工程名会有点蒙,其实拆开就三块:STBC、SM、Alamouti。
STBC是空时分组码(Space-Time Block Code),核心思想是把一个符号块同时映射到时间和空间两个维度上,让接收机拿到同一份信息的多个独立衰落副本。Alamouti编码则是STBC里最经典的方案,最早由Alamouti在1998年提出,用两根发射天线、一到多根接收天线实现满分集。它最大的优点是译码简单——因为码矩阵的列是正交的,接收端不需要暴力搜索就能把两个符号分离出来。
SM是空间调制(Spatial Modulation),概念更颠覆一点。它不把天线阵列当作并行数据管道,而是在某个时刻只激活一根(或一组)天线,天线索引本身也传递比特。比如四根发射天线,哪根在发信号,这个“哪根”的信息就对应两位二进制数。这样就好比两个人对话时,不仅说话的内容有意义,谁在说话这个事实本身也在传递信息。
1.2 为什么要把Alamouti和空间调制放在一起
单独用Alamouti的问题很明确:性能好,但频谱效率被发射天线数和码率卡死。以2发1收、QPSK调制为例,一个Alamouti块传两个符号,占用两个时隙,平均每时隙只传2比特。单独用SM呢,2发天线加QPSK,每时隙能传1比特天线索引加2比特符号,共3比特,但SM没有分集增益,信道一旦深衰落,整块信息直接丢。
STBC-SM的思路就是把二者嫁接:仍然用Alamouti编码提供分集,同时把“选择哪一对天线发射”当作额外的信息维度。因为Alamouti需要两根天线协同工作,所以我们在发射端准备多组“天线对”,每一组都执行同样的Alamouti编码,而选择哪一组则由额外比特决定。这样接收机要同时解出两个符号和天线对的索引,频谱效率变成每时隙2.5比特,比纯Alamouti高了0.5比特,同时保留了Alamouti的2阶分集。
1.3 整体仿真链路设计
我在工程里实现的链路是这样的:
- 随机比特生成,每5个比特为一组;
- 第1个比特决定使用哪一对发射天线(本工程4发天线、2种天线对,所以是1比特);
- 剩余4个比特分成两对,分别映射为两个QPSK符号;
- 选中的天线对执行标准Alamouti编码,跨两个时隙发送;
- 信号经过准静态瑞利平坦衰落信道,叠加高斯白噪声;
- 接收端用最大似然(ML)联合检测,同时估计天线对索引和两个QPSK符号;
- 统计误码率(BER),画误码率曲线。
整个项目不需要复杂的信道估计模块,先假设接收机有理想信道状态信息,专注于验证STBC-SM方案本身的性能。这是做物理层算法仿真比较建议的路线:先验证理想条件下的增益,再逐步加入估计误差、相关信道等非理想因素。
2. 系统模型与核心公式推导
2.1 Alamouti编码的收发模型
先复习一下最基础的2发1收Alamouti模型,因为后面所有推导都建立在这上面。
第一个时隙,天线1发送符号s1,天线2发送符号s2;第二个时隙,天线1发送-s2*,天线2发送s1*。如果两根发射天线到接收天线的信道系数分别是h1和h2,那么在两个时隙收到的信号是:
y1 = h1 * s1 + h2 * s2 + n1
y2 = -h1 * s2* + h2 * s1* + n2
为了检测方便,通常对y2取共轭,构造等效模型:
[y1; conj(y2)] = [h1, h2; conj(h2), -conj(h1)] * [s1; s2] + [n1; conj(n2)]
这里等效信道矩阵H_eff的每一列是正交的,因为:
第一列[h1; conj(h2)]和第二列[h2; -conj(h1)]的内积为h1conj(h2) + conj(h2)(-conj(h1)) = 0。
正交性意味着接收端可以分别估计两个符号,不需要联合遍历,而且能获得2阶分集。这就是Alamouti在2发1收下的全部精华。
2.2 SM天线对索引的引入
本地工程用的是4根发射天线,但Alamouti只需要两根天线工作,于是把天线分成两组天线对:(1,2)和(3,4)。额外1比特的取值决定当前块使用哪一组。
为什么这样分组?因为要保证不同天线对之间的信道尽量独立。在仿真中我们直接假设4根天线的信道相互独立,所以(1,2)和(3,4)是天然合理的选择。如果考虑真实的紧凑天线阵列,天线对的选择需要做相关性优化,比如从所有6种天线对组合里挑出信道相关最小的两组,那就更接近工程实际了。
这里有个容易忽略的细节:天线对被选中后,另外两根天线在这个块的两个时隙内完全不发射。发射信号矩阵是一个2时隙x4天线的稀疏矩阵,每时隙只有两个位置有值。
2.3 接收端联合检测算法
接收端不知道发送端选的是哪一对天线,所以要做联合检测。对候选天线对p(p=1,2)和候选符号组合(s1,s2)计算欧氏距离:
metric(p, s1, s2) = || r - H_eff(p) * [s1; s2] ||^2
其中r = [y1; conj(y2)],H_eff(p)是第p组天线对对应的等效信道矩阵。遍历所有候选组合,取metric最小的那一组作为判决结果。
对于QPSK来说,每根天线的符号候选数是4,两两组合是16种,再乘以2组天线对,一共32次距离计算。这个复杂度很低,直接暴力遍历没有任何压力。如果将来换到64QAM,符号组合会变成4096种,那时候才需要考虑用球形译码或者先做线性检测缩小候选集。
3. 仿真实现的关键环节
3.1 仿真参数设置与信噪比定义
直接贴出我用的参数表,后面解释每个选择的理由:
| 参数 | 取值 | 说明 |
|---|---|---|
| 发射天线数Nt | 4 | 实际每次只有2根工作 |
| 接收天线数Nr | 1 | 验证基本分集性能 |
| 调制方式 | QPSK | 星座点为(±1±j)/2 |
| 天线对集合 | (1,2)、(3,4) | 1比特索引 |
| 每块比特数 | 5 | 1比特选天线对 + 4比特两个QPSK符号 |
| 信噪比范围 | 0~24dB,步进2dB | 覆盖BER从10^-1到10^-4 |
| 信道模型 | 准静态瑞利平坦衰落 | 每个块内信道保持不变 |
| 最小帧数 | 2000 | 保证低SNR下也能有统计意义 |
| 最大误码数 | 100 | 高SNR下提前终止,节省时间 |
信噪比定义上,我采用每时隙总发射功率归一化为1的做法。QPSK星座点取(1+j, 1-j, -1+j, -1-j)/2,这样每个符号能量是0.5。每时隙有两根天线同时发射,总功率正好是1。此时噪声方差直接设置为sigma2 = 10^(-snr_db/10),横轴就是Es/N0。
3.2 发射端发送流程与代码逻辑
核心发送代码不长,重点是理解矩阵维度和功率缩放:
% 参数配置 Nt = 4; M = 4; pair_list = [1 2; 3 4]; % 两组天线对 const = [1+1i, 1-1i, -1+1i, -1-1i] / 2; % QPSK,符号能量0.5 Nbits_per_block = 2*log2(M) + 1; % 5比特 noise_var = 10^(-snr_db(si)/10); % 一个数据块内的发送过程 bits = randi([0 1], 1, Nbits_per_block); pair_idx = bits(1) + 1; % 第1比特选天线对 ta = pair_list(pair_idx, 1); tb = pair_list(pair_idx, 2); % 后面4比特映射为两个QPSK符号 s1 = const(bi2de(bits(2:3)) + 1); s2 = const(bi2de(bits(4:5)) + 1); % 发射矩阵:2个时隙 x 4根天线 tx = zeros(2, Nt); tx(1, ta) = s1; tx(1, tb) = s2; tx(2, ta) = -conj(s2); tx(2, tb) = conj(s1);这里我特意没有在发射端再乘1/sqrt(2)的归一化因子,而是直接把星座点设计成能量0.5。很多教程把归一化放在最后一步乘系数,思路没问题,但容易在换调制方式时漏改,导致信号功率和噪声功率对不上。
3.3 接收端ML检测代码解析
接收这端,我们先构造等效接收向量r,再遍历候选:
% 信道:每个块生成一次,准静态假设 h = (randn(1, Nt) + 1i*randn(1, Nt)) / sqrt(2); % 两个时隙的接收信号 y = zeros(1, 2); for t = 1:2 y(t) = h * tx(t, :).' + sqrt(noise_var/2)*(randn + 1i*randn); end % ML联合检测 r = [y(1); conj(y(2))]; best_metric = inf; dec_bits = zeros(1, Nbits_per_block); for p = 1:size(pair_list, 1) H_eff = [h(pair_list(p,1)), h(pair_list(p,2)); conj(h(pair_list(p,2))), -conj(h(pair_list(p,1)))]; for idx1 = 1:M c1 = const(idx1); for idx2 = 1:M c2 = const(idx2); metric = sum(abs(r - H_eff * [c1; c2]).^2); if metric < best_metric best_metric = metric; dec_bits = [p-1, de2bi(idx1-1, 2), de2bi(idx2-1, 2)]; end end end end err = sum(dec_bits ~= bits);ML检测的核心在于,我们同时把天线对索引和符号对一起估计,所以判决变量是五比特的完整组合。注意H_eff的形式:第二行是h(b)的共轭和-h(a)的共轭,千万不要把共轭和转置搞混。MATLAB里h'是共轭转置,h.'才是纯转置,这里用的是元素本身,直接取conj(h)最稳妥。
3.4 能量归一化的坑
这里要专门提醒一下能量归一化,因为至少一半的误码率曲线异常都出在这个环节。
一个常见错误是:星座点使用单位能量(1+j, 1-j, -1+j, -1-j)/sqrt(2),然后在Alamouti编码时没有乘任何系数。这样每个时隙两路符号总能量是2,噪声方差却按信噪比1/sigma2来计算,画出来的曲线会整体向左偏移约3dB,怎么看都不对。
正确的做法是保证每时隙总发射功率为1。要么把星座点能量设成0.5(我采用的方式),要么在构造tx矩阵时统一乘1/sqrt(2)。两者等效,但前者更不容易出错,因为只要换星座点定义一次,后面所有符号都自动满足功率约束。
提示:用MATLAB做蒙特卡洛仿真,建议在同一个脚本里把“发射总功率”“接收SNR定义”“星座缩放系数”三者的关系用注释写清楚,不然过两周回来看代码真的会疯掉。
4. 仿真结果与性能分析
4.1 误码率曲线解读
实测下来的BER曲线有几个典型特征。首先,随着SNR增大,曲线呈线性下降趋势,斜率对应2阶分集增益。因为在高信噪比区间,误码主要由成对错误概率主导,而Alamouti给每个符号提供了两条独立衰落路径,所以BER近似正比于SNR的-2次方。
其次,在中低SNR区间,STBC-SM的曲线会比纯Alamouti(2发1收)略差一点。这个不难理解:多出来的天线对索引比特也要参与检测,索引判决错误会直接拉低整体误码率,而低信噪比下信道噪声很容易让接收端猜错“用哪对天线发射”。随着SNR升高,索引判决逐渐可靠,性能差会慢慢收窄。
第三,对比纯SM(2发1收、QPSK、无分集),STBC-SM在高SNR下有压倒性优势。纯SM只有1阶分集,曲线上表现为相对平缓的下降,而STBC-SM在20dB以后能轻松压到10^-4以下,纯SM要到更高的SNR才能摸到同样水平。
4.2 频谱效率与分集增益的权衡
用表格把各方案摆在一起看更直观:
| 方案 | 发射天线 | 接收天线 | 每时隙比特 | 分集阶数 | 适用场景 |
|---|---|---|---|---|---|
| 纯Alamouti QPSK | 2 | 1 | 2 | 2 | 可靠性优先 |
| 纯SM QPSK | 2 | 1 | 3 | 1 | 频谱效率优先 |
| STBC-SM QPSK | 4 | 1 | 2.5 | 2 | 效率和可靠性折中 |
为什么STBC-SM是2.5而不是3?因为两个时隙总共传5比特,其中一个比特用来选天线对,四个比特给两个QPSK符号。相比纯SM少了0.5比特,但分集阶数从1跳到2,在高SNR下这个交换非常划算。实际系统中,如果对频谱效率有更极致的要求,可以选择更多的天线对,比如8根天线分成4组,天线对索引就能传2比特,每时隙比特数变成3,和2发纯SM持平,但依然保留2阶分集。
这里也要提醒一点:天线对数增加后,天线组之间的信道相关性会变复杂,尤其在天线间距有限的设备上,索引检测错误率不会按理想趋势下降。所以这个方案在论文仿真里效果很漂亮,落地时要结合天线布局认真评估。
5. 常见问题与调试记录
5.1 误码率曲线出现平台,怎么排查都下不去
这是我在调试时遇到最多的情况。曲线在某个SNR后突然不再下降,像贴了一条地板。排查顺序我建议固定下来:
- 检查发射总功率是否为1;
- 检查噪声方差是否等于10^(-snr/10);
- 检查每个数据块的信道是否重新生成,生成后有没有在块内被意外更新;
- 检查检测端是否用了同一个信道,有没有和发射端串位。
其中第3条特别重要。Alamouti的正交性建立在“相邻两个时隙信道不变”的假设上。如果代码里每个时隙都独立生成信道,两时隙的信道系数不同,H_eff的正交性直接被破坏,高SNR下会出现一条明显的错误平台,误码率卡在10^-2到10^-3之间下不去。我最初就是犯了这个错误,后来把信道生成挪到块循环外一次搞定,问题立刻消失。
5.2 天线对映射与信道系数不对齐
四根发射天线的信道向量h是1x4,检测时h(pair_list(p,1))和h(pair_list(p,2))取得是哪两根天线的信道,就必须和发送端tx矩阵里填入的两列严格一致。这个问题在代码初期很容易隐蔽地出现,因为MATLAB不报错,只是性能差一点。建议把发送端的天线对索引和接收端的天线对索引定义成同一个变量,不要分两处写。我用pair_list统一管理后,这类问题基本不再出现。
5.3 蒙特卡洛统计次数怎么选才科学
固定帧数跑仿真有两个问题:低SNR下误码率收敛快,但高SNR下误码事件极少,跑半天一个错都没有,BER算出来是零,画log坐标时直接缺一个点。我后来改成双停止条件:最小帧数保证低SNR有足够样本,最大误码数保证高SNR不会无限跑下去。经验值是最小帧数2000、最大误码数100,这样在20dB以上的区间也能稳定统计到误码。如果追求更平滑的曲线,可以把最大误码数提到200,代价是仿真时间翻倍,看自己耐心盘算。
5.4 一个简化检测的备选方案
如果只是快速验证系统跑通,不需要追求最优性能,可以用线性合并替代ML检测。因为Alamouti的H_eff是正交矩阵,利用这个性质,s1和s2可以先分别合并:
s1_hat = conj(h(ta)) * y1 + h(tb) * conj(y2)
s2_hat = conj(h(tb)) * y1 - h(ta) * conj(y2)
然后分别做QPSK判决。但对天线对索引p,还是要遍历两种可能,分别计算合并后两个判决符号的似然代价,选择更小的一组。这种方法的复杂度比完整ML低,性能损失在高SNR下约为1dB,用来验证链路是否通很合适。
我在这个项目里最重要的体会就是:仿真曲线一旦不对,九成是功率归一化和信道假设的锅,跟算法本身关系不大。先把这两点查清楚,再去怀疑检测逻辑。这套“先简单线性检测通链路、再上ML进一步压缩性能”的开发节奏,也帮我省掉了大量无意义的调试时间。如果有人把这个方案往更高阶调制或者更多接收天线扩展,建议把公开数据集跑出来的BER基准留一份,以后每次改功能都能快速回归对比。
本文还有配套的精品资源,点击获取