做了这么多年通信仿真,我一直觉得汉明码加Simulink是个特别适合上手却又容易翻车的组合。尤其是要把AWGN信道和BSC信道的误码率性能放在一起对比的时候,很多初学者会卡在同一个地方:明明照着教程搭好了模型,得到的误码率曲线却怎么都对不上理论值,要么横移几个dB,要么直接在0.5附近横着走。这篇文章就是把我在MATLAB Simulink里完整做汉明码、AWGN信道、BSC信道误码率仿真时踩过的坑和最终跑通的参数配置,原原本本分享出来,适合正在做通信原理课程设计、毕业设计,或者刚开始玩信道编码仿真的人参考。
1. 为什么把汉明码当作信道编码仿真的第一课
1.1 汉明码的纠错能力:一个码字最多敢说保一个比特
汉明码是线性分组码里最经典的一种,它的特点是码字长度n和消息长度k满足“校验位够用”的关系,最常用的(7,4)汉明码用4个信息比特加上3个校验位,组成7比特的码字。3个校验位可以表示8种状态,其中一种表示“无错误”,剩下7种正好对应7个比特位置,所以(7,4)汉明码可以纠正任意1位错误。
这个设计思路其实特别朴素。打个比方:一个7人小队里每个人负责一个位置,如果队长收到了一条情报,他会通过约定的校验规则检查出哪个位置上的信息被篡改了,然后直接把它改回来。但如果两个位置同时被篡改,队长就没法判断到底哪儿错了,只能给出一个错误的结果。
正因为(7,4)码只承诺纠1位错,它成为验证信道编码原理的最小可玩单位。码率不算低(4/7),实现又不复杂,无论是手写编码还是用Simulink模块都非常直观,非常适合拿来做信道编码性能的第一课。而且它和后面更复杂的BCH码、RS码、LDPC码之间是一脉相承的思路,搞懂了汉明码的仿真流程,后面换编码方式就是换模块参数的事。
1.2 (7,4)码的编码与译码到底做了什么
我当年学汉明码的时候,最头疼的就是生成矩阵和校验矩阵那一堆线性代数。其实在Simulink里你不需要手写矩阵,模块会帮你算好,但如果你连大致的流程都不清楚,后面出了问题根本没法排查。
编码端的逻辑是这样的:4位信息比特乘以生成矩阵G,得到7位码字。常见的生成矩阵形式为:
- G = [I4 | P],其中I4是4×4单位矩阵,P是一个4×3的校验子矩阵
校验位的计算可以理解为几个信息位的模2加法。比如取一种常见形式,校验位p1、p2、p3分别由不同信息位的组合相加得到。译码端则是计算伴随式s,接收到的7位码字乘以校验矩阵H的转置,得到一个3位伴随式,如果伴随式为0就认为没有错误,否则根据伴随式对应列的位置去翻转那一位。
我之前给同学演示过一个小例子:信息位[1 0 1 1]编码后得到码字[1 0 1 1 0 1 0],如果在传输过程中第7位被反转,变成[1 0 1 1 0 1 1],译码器算出的伴随式会直接指向第7位,翻转回来就恢复原样了。这个“定位”的过程在Simulink里就是Hamming Decoder模块内部做的事,快得你根本感觉不到。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Simulink模型搭建:两条链路对比着一个模型里跑
2.1 模块选型与整体数据流
要做AWGN信道和BSC信道的误码率对比,我建议把两条链路放在同一个Simulink模型里,这样信号源完全一致,比较结果才公平。整体数据流如下:
- Bernoulli Binary Generator产生随机0/1信息比特
- 送入Hamming Encoder进行(7,4)编码
- 编码后的7位码字分成两路
- 支路A:经过BPSK调制送入AWGN信道,再BPSK解调后进Hamming Decoder
- 支路B:直接送入BSC信道,输出再进另一个Hamming Decoder
- 两条支路的译码输出分别和原始信息比特做误码率统计
这样做的好处很明显:两条支路共用同一个信源,只需要设置好AWGN信道和BSC信道的参数对应关系,就能直接比较两种信道模型下汉明码的性能差异。模型结构也算清晰,每个模块的职责一目了然。
我用的模块清单和路径整理在下面这张表里:
| 模块 | 库路径 | 关键参数 |
|---|---|---|
| Bernoulli Binary Generator | Communications Toolbox > Comm Sources > Random Data Sources | Probability of zero=0.5,Sample time=0.001,Samples per frame=4 |
| Hamming Encoder | Communications Toolbox > Error Detection and Correction > Block | Codeword length N=7,Message length K=4 |
| BPSK Modulator Baseband | Communications Toolbox > Modulation > Digital Baseband Modulation > BPSK | 默认参数即可 |
| AWGN Channel | Communications Toolbox > Comm Channels | Mode选择Signal to noise ratio (Eb/N0),具体下面细讲 |
| BSC | Communications Toolbox > Comm Channels | Error probability设为一个变量p |
| Hamming Decoder | Communications Toolbox > Error Detection and Correction > Block | N=7,K=4 |
| Error Rate Calculation | Communications Toolbox > Comm Sinks | 关键在Receive delay,后面重点讲 |
| Display / To Workspace | Simulink > Sinks | 可视化或者把误码率存到工作区 |
2.2 核心参数配置:编码器、AWGN、BSC、误码仪
先说Hamming Encoder,模块里有两个参数:Codeword length N和Message length K。题目里是(7,4)汉明码,所以N填7,K填4。这里要特别注意输入数据的组织形式,Simulink里这个模块默认要求输入是一个K×1的帧结构,也就是说每次给它4个比特作为一帧。所以前面的Bernoulli Binary Generator我直接设置成Samples per frame=4,省得后面再接Buffer打包,那个Buffer用不好特别容易把帧边界弄错,导致译码全乱。
AWGN Channel模块是重头戏。它有两种常用的信噪比模式,一种是SNR (Eb/N0),另一种是SNR (Es/N0)。对本项目来说,我建议直接选Signal to noise ratio (Eb/N0)模式,因为后面画误码率曲线时横轴就是Eb/N0,这样最直观。三个参数需要格外留意:
- Eb/N0(dB):设成一个工作区变量,比如EbN0dB,后面用sim命令循环赋值
- Input signal power:通常填1,表示归一化信号功率
- Symbol period:填编码后每个符号的持续时间,这个最容易错,我下面单独说
Symbol period的计算逻辑是这样的:信源每0.001秒产生1个比特,一帧4个比特,帧周期就是0.004秒。经汉明编码后变成7位码字,但帧周期仍然是0.004秒,也就是说7个码字符号要在这0.004秒内发完,每个符号的周期就是0.004/7秒。这个值不设对,AWGN模块内部算噪声功率时会按错误的符号速率算,结果就是整条BER曲线横移好几个dB。我后来用半途半猜的方式验证过,这个参数差一个数量级,曲线就能偏出去3到5个dB,非常坑。
BSC信道的参数就一个:Error probability。这个概率p不是随便填的,它要和AWGN支路对应起来,也就是同一个Eb/N0下BPSK硬判决后信道的实际比特翻转概率。具体换算关系放在第3节详细说,这里先记住做法:在模型里把BSC的Error probability设成一个工作区变量p,循环仿真时每次先根据当前EbN0_dB算出p,再赋给BSC模块。
Hamming Decoder、BPSK调制解调器这些模块基本保持默认就行。BPSK解调器里有一个Decision mode参数,默认是Hard decision,这在当前场景下是正确的,因为我们后面要讨论的是硬判决译码。如果你以后想探索软判决,可以把解调器输出改成LLR,那个是另一套玩法,后面扩展部分再提。
2.3 BPSK调制这一步为什么绕不开
有一个问题我经常被人问:汉明编码器出来明明是比特流,为什么不能直接把汉明编码器的输出接到AWGN信道上?这个问题问得很好,因为它正好点出了AWGN和BSC的本质区别。
BSC信道本身就是二进制对称信道,输入输出都是0/1,信道要做的只是以概率p翻转比特,所以编码比特可以直接送进去。但AWGN信道是连续信号信道,高斯白噪声叠加在模拟信号上,你必须先决定用什么物理方式把0/1比特映射成可传输的信号。最简单的方式就是BPSK,0映射成-1,1映射成+1。
如果跳过BPSK调制,直接把0/1比特序列送进AWGN信道,那就要么被认为是OOK开关键控,等效的信号结构完全变了,噪声功率计算方法也不对,误码率曲线自然对不上。而且从原理上说,AWGN信道加上BPSK硬判决解调,在数学上就等价于一个BSC信道,这个等价关系正好是第3节理论推导的核心。
所以搭建模型的时候别偷懒,AWGN支路上必须走完“BPSK调制—AWGN—BPSK解调”这一整条链路。我最初就犯过这个懒,结果折腾了半天,得到的误码率比理论值差得离谱。
3. 理论误码率曲线:先算出来再跑仿真
3.1 BSC交叉概率与AWGN的Eb/N0如何互相换算
把理论曲线搞清楚,是这次仿真里最值得做的事。因为只有先知道理论值是什么样,你才能判断Simulink模型到底是搭对了还是搭错了。
先从不编码的BPSK说起。BPSK在AWGN信道下的误比特率是:
Pb = Q(√(2·Eb/N0))
Q是高斯尾函数,MATLAB里直接调用qfunc函数就行。现在考虑(7,4)汉明码,它的码率R=4/7。编码会带来两个效应:一是加了校验位,码字里每个比特实际承载的信息量变少了;二是在相同信息比特信噪比Eb/N0下,传输一个编码比特所用的能量变少了,只相当于R·Eb,所以编码比特的误码率不再是原来那个公式。
具体来说,经过汉明编码后,每个编码比特仍然用BPSK传输,那么编码比特的错误概率p为:
p = Q(√(2·R·Eb/N0)) = Q(√(2·(4/7)·Eb/N0))
这个p不是最终的用户误码率,它是“进入译码器之前”的信道误比特率,也就是BSC信道的交叉概率。换句话说,把AWGN+BPSK硬判决这条链路等效成一个BSC信道,它的p就是这个值。这也是模型中两条支路参数联动的依据。
我写这段的时候特意回顾一遍这个推导,因为它太容易被忽略。很多人直接拿未编码的p=Q(√(2·Eb/N0))去填BSC信道参数,结果AWGN支路和BSC支路的曲线差出一大截,还以为Simulink出bug了。其实数学关系很清楚,编码后码率变小,每个编码比特的等效信噪比是R·Eb/N0,p必须变小才是对的。
3.2 汉明码译码后的误码率理论公式与代码
有了信道比特误码率p之后,还要计算(7,4)汉明码译码后的用户误码率。汉明码只能纠1位错,所以在7位码字里,如果发生了0个或1个错误,译码都能恢复;如果发生了2个及以上的错误,译码就会失败或者可能误纠成别的码字。
一个常用的近似公式是这样:设一个码字内发生i个比特错误的概率是C(7,i)·p^i·(1-p)^(7-i),译码失败时近似认为这i个错都反映到最终输出上。于是译码后的误比特率为:
Pb ≈ (1/7)·Σ(i=2到7) i·C(7,i)·p^i·(1-p)^(7-i)
这个公式算起来很简单,而且和蒙特卡洛仿真结果在中等信噪比区间拟合得很好。我直接在MATLAB里写成脚本:
matlab复制EbN0_dB = 0:0.5:10;
EbN0 = 10.^(EbN0_dB/10);
% 未编码BPSK理论误码率
Pb_uncoded = qfunc(sqrt(2*EbN0));
% 编码后信道比特误码率p
p_code = qfunc(sqrt(2*(4/7)*EbN0));
% (7,4)汉明码译码后误码率
n = 7;
Pb_hamming = zeros(size(p_code));
for m = 1:length(p_code)
p = p_code(m);
sum_val = 0;
for i = 2:n
sum_val = sum_val + i * nchoosek(n,i) * p^i * (1-p)^(n-i);
end
Pb_hamming(m) = sum_val / n;
end
semilogy(EbN0_dB, Pb_uncoded, 'k--', 'LineWidth', 1.5);
hold on;
semilogy(EbN0_dB, Pb_hamming, 'b-o', 'LineWidth', 1.2);
grid on;
xlabel('Eb/N0 (dB)');
ylabel('误码率 BER');
legend('未编码BPSK', '(7,4)汉明码+硬判决');
这段代码跑出来的曲线,就是你Simulink仿真结果的“参考答案”。在低信噪比区域,汉明码的误码率甚至会高于未编码BPSK,这是码率损失造成的正常现象。大约在4到5dB之后,汉明码开始追上来,体现编码增益。这个拐点的位置很有参考价值,如果你仿真结果里没有这个交叉现象,那模型多半有问题。
4. 实测曲线长什么样,以及我踩过的五个坑
4.1 实验结果与理论曲线的对照情况
先说我最终跑通之后的结果。用for循环让EbN0_dB从0扫到10,步长1dB,每个点用sim命令跑一个Simulink仿真,AWGN支路的误码率用Error Rate Calculation的To Workspace输出拿到,BSC支路的误码率一样。把两条支路的散点叠到理论曲线上,基本是贴合的,尤其在误码率低于1e-2的区间,AWGN支路和BSC支路的仿真点几乎重合在一起。
这个结果其实就验证了前面的理论:AWGN信道加BPSK硬判决,在数字域上等价于一个BSC信道。两条支路仿真点大致重合不是偶然,而是这个等价关系的直观体现。如果你跑出来两条曲线明显分开,第一件事不是怀疑随机数,而是检查BSC的p到底有没有按第3节的公式换算。
另外在误码率比较高的区域,比如Eb/N0=0到2dB,仿真点和理论线会有一点点偏差,这很正常。因为理论公式里的译码失败后错误比特数用了近似值,而且高误码率时汉明码的误纠行为更复杂,近似公式的误差会被放大。
4.2 坑一:误码仪一直显示0.5,原来是延迟没对齐
这是Simulink仿真汉明码时遇到概率最高的问题,我当年差点因为这个以为自己的模型报废了。现象是Display里的误码率直接显示0.5左右,或者来回跳,完全不正常。原因几乎可以肯定是Error Rate Calculation模块的Receive delay没设对。
汉明编码器和译码器都是帧处理模块,信号从信源到译码输出之间会引入固定的帧延迟。Error Rate Calculation模块会拿接收信号和发送端的参考信号逐比特做比较,如果两边在时间上没对齐,比较的就是风马牛不相干的比特,误码率自然接近0.5。
排查方法很简单,先把AWGN信道和BSC信道的噪声强度设到极小,让信道基本不会出错,然后看误码率。如果显示0,说明延迟没问题;否则就调大Receive delay,每次加1,直到误码率降到0为止。我实际遇到的情况是接收延迟设成几个样本就能对齐,但有人模型里如果多加了Buffer或者其他帧处理模块,延迟量会变大,可能需要按帧数去算。
还有一种更省心的做法是加一个Variable Integer Delay模块,放在原始信息比特那条参考通路上,手动调节延迟样本数,调到两路信号在Scope上完全重合。我后来为了省事直接在Error Rate Calculation里设置Receive delay,效果一样。
4.3 坑二:AWGN信道的信噪比设对了,曲线还是横移几个dB
如果你确认接收延迟没问题,但AWGN支路的误码率曲线整体比理论曲线偏左或偏右,那问题多半出在AWGN Channel模块的Symbol period或者Input signal power上。
我之前调试时,曲线在低误码率端比理论值大了差不多10倍,算下来相当于信噪比偏移了2到3个dB。后来把AWGN模块的Symbol period改成0.004/7,也就是第2节里讲的编码后符号周期,曲线立刻回到理论线附近。
判断方法也很直接:把汉明编码器绕过去,直接让信源接BPSK调制、AWGN、解调,跑一条未编码BPSK的仿真曲线,和理论未编码曲线对比。如果这条线能对上,说明信道模块配置没问题,问题出在编码链路的连接或延迟;如果这条线都对不上,那就是AWGN模块或者BPSK调制器的参数配置有问题,跟汉明码没有关系。
我还试过把AWGN模块的模式从Eb/N0改成Es/N0,然后手动把EsN0dB设成EbN0dB+10*log10(4/7),结果也是一样的。两种模式本质相同,但Es/N0模式更容易让人弄混,所以建议还是固定用Eb/N0模式,逻辑最顺。
4.4 坑三:BSC支路的点特别“毛”,低误码率时乱跳
BSC支路的仿真有个特点:在高信噪比区域,p本身已经很小,要产生足够的错误比特数需要非常长的仿真时间。我之前在Eb/N0=10dB附近跑BSC支路,仿真停止时间只够传几十万个比特,统计出来的误码率在1e-5和1e-6之间剧烈抖动,根本没法看。
解决办法有两个层面。一是增加仿真时间,让每个信噪比点的统计错误数至少达到100个以上,这样误码率统计才比较可靠。比如预期误码率是1e-5,那就需要跑至少1000万个比特,按信源比特率1kbps算,仿真时间得设到10000秒。听起来很长,但Simulink跑起来其实很快,因为模型本身很简单。
二是利用Error Rate Calculation模块自带的Stop simulation功能。在模块参数里有一个选项可以让误码率统计达到设定错误数后自动停止仿真,比如达到100个错误就停。这样既保证统计精度,又不用盲猜仿真时间。我第一次用这个功能的时候就觉得,真该早点知道,以前都是傻乎乎设一个固定时间然后祈祷仿真够长。
另外别忘了,AWGN支路和BSC支路的等价性需要足够的统计样本才能体现出来。如果每条支路只跑几万个比特,两条曲线可能看起来不重合,那是统计波动,不是模型错了。我当时为了验证,专门在低误码率点把仿真时间拉长,看到两条支路的点都落在理论线附近才放心。
4.5 坑四:信源帧格式没配对,译码结果全错
还有一个看似低级但真有人踩的坑:Bernoulli Binary Generator如果设成Samples per frame=1,后面直接接Hamming Encoder,Simulink会报错,因为汉明编码器要求输入是K×1的帧格式。这时候有人会用Buffer模块把4个比特打包成一帧,但Buffer的帧长度设置一旦和编码器的K对不上,或者和后面的BPSK调制器不匹配,就会出现莫名其妙的错位。
我的建议是别手动Buffer,直接让信源模块输出Samples per frame=4。这样一来信源每次输出正好一组4比特信息位,汉明编码器按帧处理,编码完成为7比特帧,后续BPSK调制器也能正常工作。少一个Buffer就少一个出错点,这个经验对所有Simulink通信链路仿真都适用。
同样在配置Hamming Encoder的时候,注意N和K的对应关系必须和译码器完全一致。如果编码器配成N=7、K=4,译码器却默认成N=7、K=4但不同的码生成方式,也可能出现译码后误码率异常高的情况。通信模块库里的汉明码一般会让你选择系统形式还是非系统形式,记得编码器和译码器要选同一个形式,不然内部码字映射不一致。
4.6 坑五:仿真时间是“玄学”,不知道怎么设才够
最后一个坑是关于仿真停止时间的。很多新手不知道设定多少合适,就随便填一个1000,结果低误码率区显示误码率为0,其实是因为错误比特还没出现。这在视觉上会带来一个假象:哦,编码效果太好了,误码率是0!其实根本没有统计意义。
判断仿真时间是否够用的经验是:看仿真结束后Error Rate Calculation输出的三个数,第二个是错误比特数,第三个是总比较比特数。如果错误比特数只有个位数,或者干脆是0,那这个误码率数据不可信,需要延长仿真时间。如果你看到误码率在几个信噪比点之间来回跳,也是样本不足的表现。
我后来写了一个循环脚本,在每个信噪比点跑之前先估算需要的最短仿真时间,公式大概是仿真时间=100个错误/(比特率×预期误码率),然后把Simulink模型的StopTime设成这个值。这样做的效率很高,不会像固定时间那样要么不够要么浪费时间。
5. 做完这个实验后,还能往哪儿延伸
5.1 把汉明码换成BCH、RS、卷积码
汉明码跑通之后,你手头的Simulink模型其实已经是一个通用的信道编码误码率测试平台了。瓶颈不在模型结构,而在你选哪个编码模块。BCH码是汉明码的推广,可以纠正多位错误,比如BCH(15,11)能纠1位,BCH(15,7)能纠2位,参数改一改就能跑。RS码是多进制码,适合突发错误,但在Simulink里的数据格式和汉明码不太一样,需要额外处理整数或者二进制帧之间的转换。
卷积码是另一个方向,它和分组码最大的区别是编码器有记忆性,译码用Viterbi算法。替换起来也不难,把Hamming Encoder换成Convolutional Encoder,Hamming Decoder换成Viterbi Decoder,再把AWGN支路的解调器输出模式改成软判决就行。做完之后你会发现,软判决Viterbi比硬判决能多拿大约2dB的增益,这个数字亲手跑出来比看教材有冲击力得多。
5.2 从硬判决走向软判决
汉明码的部分,解码器接收的是硬判决比特,也就是0或1。但你思考一个问题:AWGN信道里BPSK解调后,收到一个+0.2的软值,它是0的概率其实比硬判决后的1要低,但硬判决直接把它判成1了,丢弃了可信度信息。软判决就是把这种置信度送给译码器,让译码器利用这些软信息做更聪明的纠错。
严格说汉明码做软判决不如卷积码自然,但你可以用MATLAB脚本先实现一个简单的软输出译码来对比。Simulink里更实用的做法是把BPSK Demodulator Baseband的Decision mode改成LLR,然后接到Viterbi Decoder上。你会发现同样的Eb/N0下,软判决曲线比硬判决明显往左移,那就是信道信息被充分利用的效果。
这也引出一个更广的视角:AWGN信道和BSC信道的对比,本质上是“连续信道”和“二元离散信道”的信息损失对比。硬判决等价于BSC,但BSC不是AWGN的最优表示,软判决就是试图保留更多AWGN信道信息。从这个角度再看这次仿真实验,汉明码只是载体,真正值钱的是你理解了信道建模和判决方式对系统性能的影响。
我记得自己第一次把两条支路的结果叠在同一张图上的时候,发现它们几乎重合,那种“理论终于照进现实”的感觉,是啃多少教材都换不来的。最后给个建议:如果你时间有限,先用第3节里的理论公式脚本把曲线画出来,再回头搭Simulink模型,哪怕中间有配置问题,你对最终结果也会有个预判,排错会快很多。
