简介:这套(7,4)循环码MATLAB实现资源,面向通信工程、计算机科学等专业学生及编码理论初学者,提供带GUI的编译码演示平台,用于快速理解循环码的差错检测与纠正机制。压缩包共3个文件,包含2个MATLAB脚本(.m)和1个图形界面文件(.fig),整体仅18KB,轻量易用,编码、解码及界面展示功能划分清晰。已有536人浏览学习,适合作为信息论与编码课程配套实践工具。借助生成多项式G(x)=x^3+x+1,脚本完整实现4位信息到7位码字的编码与接收纠错解码,GUI支持手动输入信息比特或码字并即时显示结果,便于对照原理观察循环移位特性。在此基础上可修改生成多项式与码长参数,灵活适配更多循环码场景,为课程设计或毕业设计提供可直接运行的参考示例,读者可借此快速验证不同输入序列下的编解码结果。
1. 从一份 74cyclic-code.zip 工程说起
搜到74cyclic-code.zip这个名字的人,多半是在做通信原理课设或数字通信相关项目,里面装的是 (7,4) 循环码的 MATLAB 实现,通常带一个 GUI 交互界面。它解决的核心问题是:把 4 位信息比特用生成多项式编码成 7 位码字送进信道,接收端靠伴随式查表纠正 1 位随机错误,再把信息还原出来。这类工程恰好覆盖了 MATLAB 里最典型的三块技能:GF(2) 多项式运算、查表译码逻辑、GUI 事件回调,也是"matlab 下载安装后找练手项目"的常见去处。适合通信原理课程设计、需要把"循环码"从公式落到可操作程序的工程师,以及想了解 matlab gui 从零搭建一套小工具的人。下面按编码、译码、GUI、参数扩展、验证发布这条链路完整走一遍。
2. (7,4) 循环码编译码的 MATLAB 实现
2.1 生成多项式先写进变量
(7,4) 循环码是汉明码的一种循环码形态,码长 n=7,信息位 k=4,最小汉明距离 d=3,纠错能力 t=1。所有这些性质都由生成多项式 g(x)=x^3+x+1 决定,二进制向量表示是[1 0 1 1]。也有教材选 x^3+x^2+1(向量1101),两者都是 GF(2) 上的本原多项式,都能构成循环码,只是伴随式到错误位置的映射表不同。
提示:MATLAB 的 Communications Toolbox 提供了
gfdeconv、encode、decode等现成函数,但课设题目通常要求自实现,下面给出的是只用基础 MATLAB 就能跑的算法。
在代码里我一般把生成多项式放在脚本开头作为参数,方便后续替换:
g = [1 0 1 1]; % g(x) = x^3 + x + 1,(7,4) 循环码生成多项式 n = 7; % 码长 k = 4; % 信息位长参数说明:
g向量从最高次到最低次排列,g(1)永远是 1;- 校验位数量 n-k=3,恰好等于
g向量长度减 1; - 判断一个 7 位码字是否合法的依据是它能否被
g(x)整除,这就是循环码线性约束的数学形式。
2.2 系统码编码:除以生成多项式取余数
循环码编码常做成系统码:信息位保留在前面,后面拼接校验位。步骤是先把 4 位信息向左移 3 位(等价于乘以 x^3),再对g(x)做模 2 除法,把余数放到尾部。以信息位[1 0 1 0]为例,被除式是 1010000,除以 1011,余数是 011,码字就是 1010011。
模 2 除法里没有借位,减法被异或取代,实现时用循环扫一遍即可:
function [q, r] = polydiv_gf2(a, g) % a: 被除式系数向量,高位在前 % g: 生成多项式系数向量,高位在前 % q: 商,r: 余数 a = a(find(a, 1):end); % 去掉被除式前导 0 g0 = g(find(g, 1):end); % 去掉生成式前导 0 r = [a zeros(1, length(g0)-1)]; % 扩展余数寄存器 q = zeros(1, length(r)); % 商向量,初始全 0 for i = 1:length(r)-length(g0)+1 if r(i) == 1 q(i) = 1; r(i:i+length(g0)-1) = xor(r(i:i+length(g0)-1), g0); end end r = r(end-length(g0)+2:end); % 取最后 length(g)-1 位作为余数 q = q(1:length(a)-length(g0)+1); end逻辑说明:r(i) == 1说明当前位的剩余部分最高位是 1,需要用生成多项式异或消去;如果r(i)是 0,商这一位记为 0 并继续右移。MATLAB 索引从 1 开始,循环上限length(r)-length(g0)+1保证最后一次异或不会越界。最后r(end-length(g0)+2:end)取的是余数的低 3 位,因为 (7,4) 码余数最高 2 次、正好 3 个系数。
有了除法,编码函数就是移位加取余:
function cw = enc74(msg, g) % msg 是 1x4 的 0/1 行向量,高位在前 shifted = [msg zeros(1, 3)]; % x^3 * m(x),左移 3 位 [~, rem] = polydiv_gf2(shifted, g); rem = rem(end-2:end); % 固定取出 3 位校验位 cw = [msg rem]; % 信息位在前,校验位在后 end参数说明:rem(end-2:end)比直接用length更稳,因为polydiv_gf2对前导零做了裁剪;如果换成 x^3+x^2+1 同样适用,这一行不用改。
验证一下:对 16 个 4 位信息遍历编码,用除法函数检查每个码字都能被g整除,说明编码过程没有写错,这一步建议在命令行直接跑:
g = [1 0 1 1]; for m = 0:15 msg = de2bi(m, 4, 'left-msb'); cw = enc74(msg, g); [~, r] = polydiv_gf2(cw, g); assert(all(r == 0), '编码结果不合法'); end disp('16 个码字全部通过整除校验');这段验证用了de2bi,它属于 Communications Toolbox;如果环境里没有,可以用bitget(m, 4:-1:1)替代。
2.3 伴随式译码:一张位置映射表完成定位
接收端拿到 7 位序列 rx 后,直接对g(x)取余数,得到的余数就是伴随式 s(x)。无错误时伴随式为全 0;发生 1 位错误时,伴随式与非零错误图样一一对应,所以只要预先把 7 种单比特错误的伴随式算出来,就能根据 s 反推出错误位置,翻转后取前 4 位恢复信息。
7 位错误位置与伴随式值的对应关系如下表(按 g=1011 计算):
| 错误位置(MATLAB 索引) | 错误多项式 | 伴随式二进制 s2s1s0 | 伴随式数值 |
|---|---|---|---|
| 1(最左位,x^6) | x^6 | 101 | 5 |
| 2 | x^5 | 111 | 7 |
| 3 | x^4 | 110 | 6 |
| 4 | x^3 | 011 | 3 |
| 5 | x^2 | 100 | 4 |
| 6 | x | 010 | 2 |
| 7(最右位,x^0) | 1 | 001 | 1 |
注意这张表依赖生成多项式,换成 g=1101 时必须重新生成,不能直接套用。表里规律来自 x^3 ≡ x+1 (mod g),手算每一项错误项对这个余数关系化简即可。
译码函数可以这样写:
function msg = dec74(rx, g) % rx: 1x7 接收向量,0/1 [~, s] = polydiv_gf2(rx, g); % 伴随式 = 余数 s = [zeros(1, 3-length(s)) s]; % 左补零到 3 位 s_val = s * [4 2 1]'; % 转成 1~7 的数值 pos_table = [7 6 4 5 1 3 2]; % 下标=伴随式值, 值=出错位MATLAB索引 if s_val ~= 0 err_idx = pos_table(s_val); % 取表定位 rx(err_idx) = 1 - rx(err_idx); % 异或 1 翻转该位 end msg = rx(1:4); % 纠完直接截取信息位 end逻辑说明:pos_table的下标就是上表的"伴随式数值",例如 s_val=5 查表得 1,表示错误落在最左位,翻转 rx(1)。s_val=0 时不查表,直接跳过。s * [4 2 1]'把多项式系数 x2、x、x0 还原成十进制,等价于二进制转十进制,不需要任何工具箱。
两个边界要说明:第一,如果接收向量发生 2 位及以上错误,伴随式也会落在某个非零值上,查表翻转后会得到错误结果,(7,4) 码只能保证 t=1 的纠错,这是编码理论本身的限制;第二,某些 2 位错误画出来的伴随式恰好在表外,属于不可检测错误,这套机制同样无能为力。
到这里,"编码 → 加错 → 译码"的计算环节就可以在命令行跑通了。把 enc74 和 dec74 放在同一个脚本里,反复模拟 1 位随机翻转,可以稳定看到 msg 原样恢复。
3. 用 MATLAB GUI 把循环码演示做成交互面板
3.1 界面方案选型:App Designer 还是 GUIDE
搜索这套题目的人会碰到很多用 GUIDE 写的 .fig 工程。GUIDE 从 R2019b 开始不再推荐,官方逐步把功能迁移到 App Designer,新写代码建议直接用 App Designer,别再做新的 .fig。App Designer 生成的 .mlapp 本质是类定义文件,界面元素是 properties 里的对象属性,回调函数是 methods 里的成员方法,比 GUIDE 的分散函数结构更好维护,也方便统一管理状态变量。
提示:如果手头是 .fig 老工程,R2021b 之后打开并另存时 GUIDE 会提示迁移,但实际项目里我一般直接重写,迁移过程对 align 和 callback 引用的处理经常比重写还费时间。
App Designer 里新建 Blank App,画布左侧是控件面板,右侧是代码视图。这个循环码演示面板需要 4 个区域:输入区(4 位信息位的编辑框)、编码显示区(7 位码字)、信道干扰区(下拉框选择翻转位置)、输出区(伴随式和译码结果)。控件清单如下:
| 控件类型 | 名称 | 作用 |
|---|---|---|
| 编辑框 | InfoEdit | 输入 4 位 0/1,如 1010 |
| 按钮 | EncodeButton | 触发编码 |
| 文本 | CodeText | 显示 7 位码字 |
| 下拉框 | ErrCombo | 选择 0~7 哪个位置翻转 |
| 按钮 | CorruptButton | 在码字上翻转所选位 |
| 文本 | RxText | 显示接收向量 |
| 文本 | SyndText | 显示伴随式 s2s1s0 |
| 按钮 | DecodeButton | 触发译码 |
| 文本 | MsgHatText | 显示译码恢复的信息位 |
布局上用 uigridlayout 固定行列,避免窗口拉伸时控件乱跑。下拉框的选项值处理与普通 figure 的 uicontrol 略有差异,要留意 Items 和 ItemsData 的配合。
3.2 控件初始化和状态存储
进入 App Designer 的 code view,在 properties 段保存当前码字和生成多项式,这两个状态需要在编码、加错、译码三个回调之间共享:
properties (Access = private) g = [1 0 1 1]; % 生成多项式 cw = zeros(1, 7); % 当前系统码码字 rx = zeros(1, 7); % 当前接收向量 end在 startupFcn 里初始化下拉框选项,选项的 Value 要和实际比特位置完全对应。这里有个容易踩的坑:下拉框显示"第 3 位"时,用户理解是从左往右第 3 位,而 MATLAB 索引正好也是 3,把选项 Value 直接设成索引就能省掉一次转换:
app.ErrCombo.Items = {'无错误', '第1位', '第2位', '第3位', ... '第4位', '第5位', '第6位', '第7位'}; app.ErrCombo.ItemsData = 0:7; % 0 表示不翻转ItemsData 是每个选项背后实际存储的值,读取用app.ErrCombo.Value得到的就是 0~7 的数值,不需要在回调里做字符串匹配。
3.3 回调函数:数据从控件流向算法
编码按钮的回调把输入框字符串解析成 4 位向量,调用前面写的 enc74,再渲染到界面:
function EncodeButtonPushed(app, event) str = strtrim(app.InfoEdit.Value); if length(str) ~= 4 || any(~ismember(str, '01')) uialert(app.UIFigure, '请输入4位二进制数,例如 1010', '输入错误'); return; end msg = double(str) - double('0'); % 字符转数值 app.cw = enc74(msg, app.g); % 调用算法层 app.CodeText.Value = sprintf('%d %d %d %d %d %d %d', app.cw); end逻辑说明:double(str) - double('0')把 '0' 和 '1' 字符统一转成 0/1 数值,这比 str2num 更可靠,str2num 解析 '1010' 会得到一个标量而不是向量;显示时用 sprintf 带空格便于人眼分组。
信道干扰按钮的回调读取 ErrCombo 中用户选的位置,完成翻转:
function CorruptButtonPushed(app, event) idx = app.ErrCombo.Value; % 0~7,0 表示不翻转 if idx == 0 app.rx = app.cw; else app.rx = app.cw; app.rx(idx) = 1 - app.rx(idx); % 翻转所选项 end app.RxText.Value = sprintf('%d %d %d %d %d %d %d', app.rx); end这里 idx 直接用作 MATLAB 索引,因为 ItemsData 里放的是 1~7。注意每次都要从app.cw重新拷贝再翻转,而不是在上一次的 rx 上继续翻,否则用户多点两次按钮就把两个位都翻转了,超出了 (7,4) 码的纠错范围,界面表现会让人误以为是 bug。
译码回调调用 dec74 并展示中间量:
function DecodeButtonPushed(app, event) msg_hat = dec74(app.rx, app.g); app.MsgHatText.Value = sprintf('%d%d%d%d', msg_hat); [~, s] = polydiv_gf2(app.rx, app.g); s = [zeros(1, 3-length(s)) s]; app.SyndText.Value = sprintf('%d%d%d', s); endSyndText 显示伴随式的意义在于把译码过程暴露出来:伴随式为 000 表示无错;非零时对应 2.3 节的查表关系。这一步直接把教材里的"查表译码"还原到界面上,正是这类演示工具最常见的用法。
到这里,一个最小闭环已经成立:编码 → 人为翻转 1 位 → 显示伴随式 → 译码恢复。如果还要体现"循环码"的循环性质,可以在界面加一个循环左移按钮,回调里每点一次把cw循环移位一位,并显示移位后仍然能被g整除。这是循环码独有的特征,也是标题里"循环"两个字最容易实操验证的点。
4. 循环码参数边界与工程化扩展
4.1 生成多项式换了,查表就要跟着换
(7,4) 循环码的另一个常用生成多项式是 x^3+x^2+1(向量 1101),它与 x^3+x+1 互为对偶关系,生成的码集合不同,伴随式查找表也不同。网上很多代码把表写死成常数数组,换多项式后译码直接错位,这是最常见的坑之一。
如果要把 GUI 里的生成多项式做成可下拉选择,我的做法是运行时动态生成查表,而不是维护两份手算表。用穷举生成:
function pos_table = build_table(g) n = 7; k = 4; e = eye(n); % 7 个单位错误向量 pos_table = zeros(1, 2^(n-k)-1); % 7 个非零伴随式 for i = 1:n [~, s] = polydiv_gf2(e(i,:), g); s = [zeros(1, 3-length(s)) s]; val = s * [4 2 1]'; pos_table(val) = i; % 伴随式值 -> 错误位置 end end参数说明:e(i,:)是第 i 位为 1 的错误图样;对每个错误图样求伴随式,把结果 val 作为下标,把位置 i 写入表。如果某个 val 冲突或表里有 0 残留,说明 g 不是本原多项式,(7,4) 结构不成立。这个函数把"换多项式就要改表"的处理自动化了,在 GUI 里切换生成多项式时,只需重新调用它。
4.2 不可检测错误与误用边界
(7,4) 码最小距离 3,只能保证纠正任意 1 位错误。若信道中发生 2 位错误,接收向量除以 g 的伴随式可能恰好等于某个单比特错误的伴随式,译码器会误判成 1 位错误并"纠正"成另一个合法码字,结果是错上加错。这不是代码 bug,是编码增益有限。
表现到 GUI 里很直观:信道误码率高于一定阈值时,界面显示"纠错后"的信息位仍然不对。我做演示时会在界面加一个备注区,提示"本程序针对 BSC 信道单比特错误设计,2 位及以上错误不在保证范围"。如果想提高纠错能力,要把 (7,4) 换成 (15,11) 汉明码或 BCH 码,生成多项式选用 x^4+x+1,编码结构和查表函数完全复用,只要改 n、k、g 三个参数。
4.3 从 7/4 扩展到 15/11 的长码形态
(15,11) 汉明码同样能纠 1 位错误,冗余 4 位,编码效率从 4/7 提升到 11/15。把 enc74/dec74 里的常数抽象成参数 n、k,就能得到通用版本:
function cw = enc_general(msg, g, n, k) r = n - k; % 校验位个数 shifted = [msg zeros(1, r)]; [~, rem] = polydiv_gf2(shifted, g); rem = [zeros(1, r-length(rem)) rem]; % 校验位左补零到 r 位 cw = [msg rem]; end注意这里的rem = [zeros(1, r-length(rem)) rem],因为余数在实际除法结果里可能不足 r 位,比如余数只有 2 位时需要在前面补一个 0,否则拼接出来的码字不是标准系统码。polydiv_gf2本身对前导零做了裁剪,所以补零这一行不能省。
(15,11) 的伴随式表会膨胀到 2^4-1=15 项,build_table 按伴随式数值做下标,天然支持表长变化,不需要手工改数组。
5. 循环码 GUI 的穷举验证与发布技巧
5.1 全部 112 种场景的自动回归
GUI 回调写完后,不要只靠鼠标点几个 case,最可靠的是写一个脱离界面的穷举脚本,把"16 个信息位 × 7 个翻转位置 = 112 种组合"全部跑一遍:
% verify74.m g = [1 0 1 1]; fail = 0; for m = 0:15 msg = de2bi(m, 4, 'left-msb'); cw = enc74(msg, g); for e = 1:7 % 最多翻转 1 位 rx = cw; rx(e) = 1 - rx(e); msg_hat = dec74(rx, g); if ~isequal(msg, msg_hat) fail = fail + 1; fprintf('失败: msg=%d%d%d%d e=%d\n', msg, e); end end end fprintf('完成,失败 %d 组\n', fail);这个脚本放在 GUI 工程目录的 test 子文件夹里,每次改动 polydiv_gf2 或查表逻辑后跑一遍,比直接在 App Designer 里点按钮高效得多。如果失败集中在某个特定错误位置,优先怀疑查表;如果全部失败,检查 polydiv_gf2 的余数截断。没有 Communications Toolbox 的环境里,把 de2bi 换成bitget(m, 4:-1:1)即可。
5.2 打包成独立程序给其他人跑
课设验收或把演示交给没有 MATLAB 的同学时,常见做法是导出独立可执行文件。App Designer 的 APP 选项卡里有 Package 按钮,底层调用 MATLAB Compiler 的compiler.build.standaloneApplication,生成的文件需要在目标机器上安装对应版本的 MATLAB Runtime,体积较大但不需要完整 MATLAB。如果对方本身有 MATLAB 环境,导出.mlappinstall安装包更方便,双击即可安装到 App 列表。
提示:打包前清掉 app 里的路径依赖,脚本里不要用
cd切目录,运行时的工作目录和开发机不同,任何依赖相对路径的文件都会在对方机器上报错。这类问题在实际打包里出现的概率远高于算法本身出错。
另一个容易让 GUI 卡顿的细节是:在回调里做长循环时要主动调用drawnow,或者把耗时计算放到parfor。对 (7,4) 这种规模完全不需要,但如果你把循环码扩展成更大码长,单次编码虽然是 O(n),GUI 里频繁点按钮重算余数仍然会有肉眼可见的延迟。到了几十毫秒量级就要考虑把计算结果缓存下来,比如把生成多项式 g 和查表结果在启动时算一次放进 properties,回调里只做取数。我把 build_table 放在 startupFcn 里执行就是这个原因。
如果还想给界面增加教学感,可以在 SyndText 旁边加一个文本框,把 polydiv_gf2 的中间异或步骤用字符串打印出来。这样伴随式不再是黑盒结果,而是可以逐位看到除法过程,对理解循环码原理、debug 查表、以及应对答辩提问都有实际帮助。
本文还有配套的精品资源,点击获取