Matlab实现(7,4)汉明码编码译码与纠错仿真详解
发布时间:2026/9/14 13:33:08来源:尧图网络
简介汉明码是一种经典的单比特纠错编码常用于通信与存储系统中的误码防护。这份MATLAB例程面向信号处理、通信原理及编码课程的学习者提供最基础的汉明码7,4编码与解码实现帮助读者快速理解校验位生成和错误定位/纠正过程。压缩包共3个文件包括2个.m脚本hammingbianma.m与hammingjiema.m和1份txt说明文件包体仅1KB代码精简、便于阅读。两个脚本分别实现编码与解码功能编码函数接收4位数据经异或运算生成3位校验位并输出7位汉明码解码函数核对校验结果定位并纠正单比特错误最后剔除校验位还原原始数据。配套txt对函数用法和流程做了简要说明适合初学者对照学习或在课堂实验中直接调用。目前该资源已有200人学习浏览对于希望在MATLAB中动手验证汉明码原理的读者是一份轻量而实用的入门参考。1. hanmingma.zip 里的 Matlab 例程绕不开的 (7,4) 汉明码拿到一份取名 hanmingma.zip 的 Matlab 例程很多人习惯先找 decode 脚本丢一段噪声数据进去看到能纠正一位错误就觉得“这就是汉明码”。但汉明码真正值钱的地方不只是那一个纠错位而是它背后一整套“冗余位怎么设计、校正子怎么映射、什么情况下会纠错失败”的工程判断。它的典型应用场景是误码率不高、又不允许单个 bit 翻错的链路比如内存 ECC、NAND Flash 的坏块管理、低速无线遥控帧。那份 Matlab 例程通常就围绕 (7,4) 码把编码、译码、单比特翻转测试、误码率曲线串起来适合当通信原理的作业框架也适合改造成嵌入式 C 的原型。这篇文章就从这份例程最常见的实现路径往下拆把生成矩阵、校正子查表、BSC 信道仿真和 SECDED 扩展一次讲到位。2. 汉明码原理与生成矩阵 G、校验矩阵 H 的 Matlab 构造2.1 从 (7,4) 码开始为什么最小汉明距离为 3 的码能纠一位错(7,4) 汉明码把 4 个数据位扩成 7 个码字位多出的 3 位是校验位。码长 7意味着译码端需要区分“没有错误”以及“7 个不同位置中的任意一个发生错误”共 8 种情况。3 个校验位正好有 2^3 8 个比特组合和这个需求完全匹配。这就是汉明码能用最少校验位纠正单比特错误的根本原因也决定了它的最小汉明距离是 3。从线性分组码的角度看数据向量 m [d1 d2 d3 d4] 经过生成矩阵 G 得到码字 c写成 c m * G。在 Matlab 例程里最常见的构造是系统码形式% 构造 (7,4) 汉明码的生成矩阵和校验矩阵 P [1 1 0; 1 0 1; 0 1 1; 1 1 1]; % 4x3 校验子矩阵 G [eye(4) P]; % 生成矩阵前4列是单位阵 H [P eye(3)]; % 校验矩阵后3列是单位阵这段代码里G 的前 4 列是单位矩阵说明码字前 4 位直接就是数据位P 的每一列决定一个校验位的生成规则。比如 P 的第一列是 [1 1 0 1]对应校验位 p1 d1 d2 d4加号是模 2 加法。这样设计的好处是编码简单、译码时数据位可以直接从码字前 4 位提取。校验矩阵 H 和生成矩阵 G 必须满足 H * G 0模 2才能保证所有合法码字都在 H 的零空间里。例程里应该加一句自检否则矩阵敲错了后面找问题非常痛苦assert(isequal(mod(H * G, 2), zeros(3, 4)), 校验矩阵和生成矩阵不正交);这句assert看起来很基础但能挡住大部分抄矩阵时抄错的情况。H 有 7 列每一列正好对应一个错误位置后面 syndrome 查表纠错靠的就是这个性质。2.2 syndrome 与错误定位表用 Matlab 先生成查询表接收向量 r 如果等于发送的码字 cr * H 全零如果某一位出错r c e其中 e 是只有该位为 1 的错误图样那么 syndrome e * H结果恰好是 H 对应那一列的转置。用 Matlab 把 7 个错误位置对应的 syndrome 打印出来syndromeTable zeros(7, 3); for j 1:7 e zeros(1, 7); e(j) 1; syndromeTable(j, :) mod(e * H, 2); end disp(syndromeTable);逻辑很简单人为构造只在第 j 位为 1 的错误向量乘上 H 再做模 2得到的就是第 j 个错误位置对应的 syndrome。这样一张表可以直接作为译码器的查表依据。按上面的 P 矩阵得到的对应关系如下表码字中出错的位置syndrome [s1 s2 s3]11 1 021 0 130 1 141 1 151 0 060 1 070 0 1注意 syndrome 的顺序不是简单的二进制递增这和 H 的列顺序直接相关。只要 H 的列没有重复表就是单射可以放心用它纠错。如果两个列相同那么对应两个错误位置会产生同一个 syndrome汉明码的“最小距离为 3”就不成立整个例程也就失效了。2.3 mod 2 运算和可用的参数约定Matlab 里做汉明码运算最直观的方式是mod(x, 2)而不是bitxor。原因有两个第一矩阵形式下m * G已经包含了所有位的线性组合mod(..., 2)把整块结果折叠到 0/1第二bitxor是对两个等长数组逐位异或需要先算完每一位再拼起来没有矩阵乘法直观。实际例程中如果输入是logical类型mod的结果仍然是 double但最初logical * double会隐式转换问题不大。真正要小心的是从文件读进来的数据可能带着字符 0、1 或者字符串必须先str2double或者r 1转成数值后面再统一double(r)。汉明码还有一个容易被忽略的参数是码字比特的排列顺序。很多教材把码字写成 [d1 d2 d3 p1 p2 p3 d4] 这种非系统顺序和这里使用的 [d1 d2 d3 d4 p1 p2 p3] 不一样。如果例程里同时出现编码、译码和误码率统计必须保证三处都使用同一个排列。否则 syndrome 表是乱的纠错率会直接变成错误率。3. 用 Matlab 例程实现汉明码编码器/译码器与单比特纠错3.1 ham74_encode.m生成矩阵下 4 位数据变 7 位码字动手写第一个函数时我一般会把它做成支持“多帧同时编码”的形式也就是输入一个 N x 4 的矩阵每一行是一组 4 位数据。这样后面做误码率仿真时不需要一个 for 循环逐帧调用性能会好很多。function c ham74_encode(m) % m : N x 4 的 double 矩阵每行是 4 个数据位 % c : N x 7 的 double 矩阵每行是编码后的码字 assert(size(m, 2) 4, 输入数据位必须是 4 列); P [1 1 0; 1 0 1; 0 1 1; 1 1 1]; G [eye(4) P]; c mod(m * G, 2); end参数说明m的行数不限列数必须是 4函数内部生成 G不需要在调用处额外传参。mod(m * G, 2)中m * G是普通矩阵乘法尺寸为 N x 7结果里每个元素可能超出 1模 2 后全部归入 0/1。assert只检查列数因为空矩阵和数值类型在乘法里会自行展开列数错了乘出来的矩阵尺寸也会错但错误信息不够明确不如先拦一道。试着一个例子m [1 0 1 1]; c ham74_encode(m); % c [1 0 1 1 0 1 0]手工验证p1 d1 d2 d4 101 0p2 d1 d3 d4 111 1p3 d2 d3 d4 011 0。所以码字是 [1 0 1 1 0 1 0]和函数输出一致。数据位 [d1 d2 d3 d4]校验位 [p1 p2 p3]完整码字1 0 1 10 1 01 0 1 1 0 1 0这个例子可以直接放进例程的 demo 脚本里用来验证编码器没有把矩阵列顺序弄反。3.2 ham74_decode.m校正子查表后做单比特翻转译码器要做的分三步计算 syndrome在 H 的列里找匹配位置翻转该比特然后截取前 4 位作为数据输出。同样支持多帧输入function [d_est, c_corr, syndrome] ham74_decode(r) % r : N x 7 接收码字矩阵0/1 double % d_est : N x 4 纠错后的数据位 % c_corr : N x 7 纠错后的完整码字 % syndrome: N x 3 每一帧的校正子 P [1 1 0; 1 0 1; 0 1 1; 1 1 1]; H [P eye(3)]; r double(r); % 统一转成 double避免 logical 引发数据类型问题 syndrome mod(r * H, 2); % N x 3 校正子 c_corr r; for row 1:size(r, 1) s syndrome(row, :); if any(s) % 在 H 中找与 s 相同的列列号就是错误位置 idx find(all(H s, 1)); if ~isempty(idx) c_corr(row, idx) mod(c_corr(row, idx) 1, 2); end end end d_est c_corr(:, 1:4); % 前4位是数据位 endall(H s, 1)这段值得单独解释H 是 3 x 7s是 3 x 1两者比较会得到 3 x 7 的逻辑矩阵all(..., 1)按第一维判断也就是“每一列是否全部相等”结果为 1 x 7find返回满足条件的列号。由于 H 的每一列都不同idx最多只有一个值。如果 syndrome 全零any(s)为 false直接跳过不会误翻转。用随机的 10 帧做单元测试rng(1); m randi([0 1], 10, 4); c ham74_encode(m); e zeros(10, 7); e(2, 1) 1; % 只让第2帧的第1位翻转 r mod(c e, 2); [d, ~, s] ham74_decode(r); assert(isequal(d(2, :), m(2, :)), 第2帧纠错失败); disp(s(2, :)); % 显示 syndrome这里r mod(c e, 2)模拟的是“翻转一位”后在接收端看到的码字。assert里比较的是译码出来的d(2,:)和原始数据m(2,:)相等说明纠错路径完整。输出 syndrome 应该是 H 第一列的转置也就是 [1 1 0]对应上表第 1 位。3.3 输入类型的坑double 和 logical 混用会让例程出错Matlab 例程从不同来源读数据时最容易翻车的不是算法而是数据类型。如果 r 本身是 logicalr * H会先把 logical 转成 double结果通常没问题但如果代码里用了xor(r(:,1), r(:,2))这类逐位操作logical 和 double 混在一起会出现不可预期的维度或类型转换错误。更隐蔽的是文本导入readmatrix遇到 0、1 字符有时会返回 cell 数组需要先r char(r) 1转成 logical再喂给译码器。所以在例程的公共工具里我一般会加一行r double(r);宁可在入口处多花一次转换也不让后面每个函数各自处理类型。assert(size(r, 2) 7, 接收码字必须是 7 列)也建议加上这能避免把 8 位扩展码字误传给普通 (7,4) 译码器时出现的隐性错误。调通编码、译码两个函数之后才有资格谈误码率仿真。4. 汉明码 Matlab 例程的仿真与边界条件处理4.1 用 BSC 信道模拟随机位翻转画误码率曲线BSC 信道也就是二进制对称信道是验证汉明码最直接的模型。所谓“对称”意思是每个 bit 以固定概率 p 翻转成相反值0 变 1、1 变 0 的概率一样。仿真脚本里用rand(size(c)) p生成翻转掩码再和码字做模 2 加法就能得到接收端。clear; rng(0); nFrames 2e4; % 数据帧数 pList logspace(-3, -1, 5); % 信道位错误概率 berCoded zeros(size(pList)); berRaw zeros(size(pList)); for idx 1:numel(pList) p pList(idx); m randi([0 1], nFrames, 4); c ham74_encode(m); r mod(c (rand(size(c)) p), 2); d ham74_decode(r); tmp r(:, 1:4); berCoded(idx) mean(d(:) ~ m(:)); berRaw(idx) mean(tmp(:) ~ m(:)); end semilogy(pList, max(berRaw, eps), o-, ... pList, max(berCoded, eps), s-); grid on; xlabel(信道位错误概率 p); ylabel(信息位错误率); legend(不译码直接取数据位, 使用汉明码译码, Location, southeast);代码里mean(d(:) ~ m(:))统计的是所有测试帧里信息位总错误占比注意 d 是 N x 4m 也是 N x 4展开比较后求平均得到的是信息位错误率不是码字位错误率。berRaw是拿接收码字的前 4 位直接当数据用不经过纠错两组曲线对比才能看出汉明码的收益。max(..., eps)是为了避免某一段 p 下译码后错误率为 0semilogy画不出 0 值用 eps 占位后曲线不会断。当 p 0.001 时一个 7 位码字中出现两位错误的概率大约是 C(7,2) * 0.001^2 约等于 2.1e-5而在 2e4 帧下出现双错帧的期望才 0.42 帧所以译码后错误率大概率是 0。这说明仿真帧数必须足够大否则曲线上会出现很多空点看不出真实趋势。4.2 双比特错误的边界为什么解码器会把错“纠”成三比特错误汉明码的最小距离是 3理论上能检测 2 位错误但没有能力分辨“哪两位错了”。更麻烦的是某些 2 位错误图样的 syndrome 恰好和某一个 1 位错误图样相同。举一个具体例子第 1 位和第 2 位同时翻转syndrome 等于 H 第 1 列和第 2 列的模 2 和H [[1 1 0; 1 0 1; 0 1 1; 1 1 1] eye(3)]; e2 zeros(1, 7); e2([1 2]) 1; s2 mod(e2 * H, 2); disp(s2); % 0 1 1得到的 [0 1 1] 正好是 H 第 3 列的转置。译码器会以为第 3 位出错然后把第 3 位翻转。于是原本两位错纠正后变成第 1、2、3 位三位错。这个反直觉的结果告诉我们汉明码只能在单比特错误占绝对主导的信道里使用。如果 p 偏高双比特错误比例上升纠错反而会制造更多错误误码率比不纠还高。在实际例程中如果想验证这一点可以写一个双比特注入测试随机选两个位置翻转统计译码后信息位错误数。经常会发现译码后的错误位数量是 3 的倍数这属于正常现象也是汉明码的固有边界。4.3 批量帧解码的提速思路ismember 查表代替逐行查找上面ham74_decode用for循环逐帧查找错误位置逻辑清晰但在 nFrames 较大时效率偏低。一个常见的优化是用ismember一次给所有帧查表因为 syndrome 的有效取值只有 8 种完全适合做批量匹配function d_est ham74_decode_fast(r) P [1 1 0; 1 0 1; 0 1 1; 1 1 1]; H [P eye(3)]; r double(r); syndrome mod(r * H, 2); [~, loc] ismember(syndrome, H., rows); c_corr r; rows find(loc 0); for i rows c_corr(i, loc(i)) mod(c_corr(i, loc(i)) 1, 2); end d_est c_corr(:, 1:4); endismember的第一个输入是 N x 3 的 syndrome 矩阵第二个输入是 7 x 3 的H.rows表示按整行匹配。第二个输出loc给出 syndrome 每一行在H.中的行号这个行号就等于 H 的列号也即错误位置。全零 syndrome 在 H 中找不到loc为 0随后被rows find(loc 0)过滤掉。剩下的循环只针对有错的帧通常远小于总帧数所以整体速度会明显提升。需要留意的是ismember按行匹配时如果 H 中存在相同行它只返回第一次出现的位置。好在 (7,4) 汉明码的 H 列互不相同不存在歧义。如果以后把例程改成别的码型必须保证校验矩阵的列两两不同否则ismember的结果不能直接用于纠错。5. 把例程改造成可复用的扩展SECDED 与 hammgen 对照5.1 加一位全局偶校验让 (7,4) 变成具备双错检测的 (8,4)如果项目需要的不只是“纠一位错”还要保证“一定不把双错当单错”常见做法是在 (7,4) 码后面追加一位全局偶校验得到 SECDED 结构也就是 (8,4) 码。编码时第 8 位取前 7 位的偶校验function c8 ham74_secded_encode(m) c7 ham74_encode(m); c8 [c7, mod(sum(c7, 2), 2)]; end译码时先对前 7 位算 syndrome再对 8 位整体算偶校验位 error根据组合决定动作parity_err mod(sum(r8, 2), 2); if ~any(s) parity_err % 只有第8位翻转直接修正 r8(end) mod(r8(end) 1, 2); elseif any(s) parity_err % 单比特错误沿用原来的查表纠错 elseif any(s) ~parity_err % 双比特错误只报告不纠正 else % 无错误 end这个逻辑判据是偶数个错误会让全局偶校验仍然成立所以syndrome 非零且 parity_err 为 0时一定是至少两个 bit 出错不能再按单个错误的位置去翻转。SECDED 在内存 ECC 场景里非常常见例程里加这个扩展后可以直接回答“汉明码能不能检双错”这个问题。5.2 用 hammgen 验证手写校验矩阵的列顺序如果安装了 Communications Toolbox可以用自带的hammgen验证矩阵是否正确hammgen(3)生成 (7,4) 汉明码的校验矩阵。由于列顺序可能和手写的不一致直接用isequal会很脆弱先按行排序再比较Htool hammgen(3); isequal(sortrows(H.), sortrows(Htool.))返回 true 说明两个矩阵只是列顺序不同纠错能力等价。如果返回 false还有一个更可靠的自检mod(H * G, 2)是否全零。这个条件不依赖列顺序是“合法码字必须落在 H 零空间”的本质要求。例程里把这两条检查放进初始化脚本以后换码长时比如改成 (15,11)只需要把 P 换成维度为 11 x 4 的矩阵再让hammgen(4)做交叉验证就不用手算每一列。最后给一个调参技巧当例程从 (7,4) 扩到 (15,11) 时别只改 G 和 H 的尺寸还要同步改decode函数里的数据位截取位置。把“数据位长度”“校验位长度”“总码长”抽成三个参数放在脚本头部会让整个例程的复用性高很多。替换后重新跑一遍带assert的单元测试确保 H * G 为零再投到仿真里看曲线。本文还有配套的精品资源点击获取
网站建设高端定制企业官网