☰
BCH编译码C语言实现:手撕有限域与BM算法的嵌入式硬核方案
2026/10/20 9:53:27 网站建设 项目流程

简介:本资源是一份面向通信工程、嵌入式开发与数字信号处理学习者的BCH纠错编码C语言实现代码,聚焦底层编解码原理落地,解决实际项目中突发错误纠正与硬件资源受限场景下的高效编码需求。压缩包为5KB的ZIP文件,仅含1个核心C源文件(bch_bm.c),完整实现了BCH码的生成多项式计算、编码与基于伯利坎普-梅西(BM)算法的解码逻辑,涵盖伽罗华域GF(2^n)运算、多项式模2除法、错误定位与校正等关键模块,代码结构清晰、注释充分,便于理解算法细节并移植至MCU等资源受限平台。目前已有1425人学习下载,适合具备C语言基础与初步编码理论认知的中阶开发者,通过阅读和调试该代码,可深入掌握BCH码从数学原理到可执行程序的全链路实现,快速构建纠错通信模块原型或完成课程设计验证。

1. BCH 编译码 C 语言实现:不是调库,是手撕有限域、生成多项式与伴随式计算的硬核落地

你手头有一份嵌入式设备的通信协议文档,明确要求用 BCH(15,7) 纠正双比特错误,但芯片资源紧张——连标准 C 库的malloc都被禁用,更别说 Python 或 MATLAB。这时候,网上搜“BCH 编码”出来的全是调scipy或comm工具箱的教程,或者只给个数学公式推导,根本没法烧进单片机。这份「BCH 编译码 C 语言实现」就是为这种场景生的:它不依赖任何外部库,所有运算在栈上完成;核心逻辑全部展开为可读、可调试、可裁剪的纯 C 函数;从伽罗瓦域 GF(2⁴) 的本原多项式构造、生成多项式计算、编码时的除法模 2 运算,到解码时的伴随式计算、错误位置多项式迭代(BM 算法)、钱搜索(Chien Search)和纠错,全部用不到 200 行紧凑 C 代码实现。它适合嵌入式固件工程师、FPGA 软件协同开发者、以及需要透彻理解 BCH 底层机制的通信算法初学者——不是让你“会用”,而是让你“能改、能验、能塞进 4KB ROM”。


2. 为什么选 BCH?从纠错能力、实现开销到硬件友好性三重锚定

BCH 码不是唯一选择,但它是嵌入式通信中少有的、在纠错能力、计算复杂度和硬件映射之间取得强平衡的线性分组码。要真正用好这份 C 实现,得先明白它为什么被选中,而不是 LDPC 或 RS。

2.1 BCH vs. 其他常见信道码:纠错粒度与资源消耗的硬对比

很多开发者一上来就问:“为什么不用 RS 码?”——RS 确实强大,但它在二进制信道上天然存在“字节对齐”约束:RS(n,k) 处理的是符号(symbol),每个符号至少 8 位。而嵌入式传感器数据常以 bit 流或 4-bit 半字节形式存在,强制升维成 byte 会引入填充开销和边界错位风险。BCH 则直接工作在比特域,参数灵活:BCH(15,7)、BCH(31,21)、BCH(63,51) 均可按需配置,且码长 n 总是 2^m − 1 形式,天然适配移位寄存器结构。更重要的是,BCH 解码的核心——BM 迭代和 Chien 搜索——全部由加法(异或)、乘法(查表或移位)和条件跳转构成,无浮点、无除法、无动态内存,完美匹配 Cortex-M0/M3 或 RISC-V 内核的指令集特征。

提示:本实现默认采用 GF(2⁴) 构造 BCH(15,7),因为其本原多项式 p(x) = x⁴ + x + 1 最简,对应查表空间仅 16×16=256 字节,可在绝大多数 MCU 的 SRAM 中静态分配;若需 BCH(31,21),只需替换GF_TABLE_SIZE为 32,重算本原元 α 并更新alpha_to,index_of两张表——后面章节会给出完整生成脚本。

2.2 本实现的三层架构:域运算 → 编码引擎 → 解码流水线

该 C 实现并非扁平函数堆砌,而是清晰分层:

  • 底层:有限域 GF(2^m) 运算封装
    包含gf_mul(),gf_div(),gf_pow()三个核心函数,全部基于对数/反对数查表实现(非实时计算),避免循环移位中的边界判断开销。关键设计是:index_of[0] = -1显式标记零元素,使gf_div(a,0)可安全返回 0 而不崩溃——这是嵌入式环境下防止单点故障的关键冗余。

  • 中层:编码器(Encoder)
    输入 k 比特信息位,输出 n 比特码字。核心是模 2 多项式除法:将信息多项式 m(x)·x^(n−k) 除以生成多项式 g(x),余式即校验位。本实现用移位+异或的纯位运算模拟长除法,不构造多项式结构体,内存占用恒定为 O(n−k)。

  • 上层:解码器(Decoder)
    输入 n 比特接收码字 r(x),输出纠正后的码字 c̃(x) 及错误标志。流程严格遵循经典 BCH 解码四步:① 计算伴随式 S₁…S₂ₜ;② BM 算法求错误位置多项式 Λ(x);③ Chien 搜索定位错误位置;④ Forney 算法计算错误值并翻转比特。每一步均提供中间变量打印宏(如DEBUG_SYNDROME),方便逻辑验证。

2.3 参数可配置性:如何快速切换 BCH(31,21) 或 BCH(63,51)

本实现通过宏定义控制码参数,无需修改算法逻辑:

// bch_config.h #define GF_M 5 // GF(2^5) → n = 2^5 - 1 = 31 #define BCH_T 2 // 纠正 t=2 个错误 → 最小码距 d_min = 2t+1 = 5 #define BCH_N (1 << GF_M) - 1 // n = 31 #define BCH_K (BCH_N - GF_M * BCH_T) // k = 31 - 5*2 = 21 #define GEN_POLY_DEG (GF_M * BCH_T) // g(x) 次数 = 10

切换时只需改GF_M和BCH_T,其余由宏自动推导。注意:BCH_K的计算公式n − m·t是 BCH 码维度的理论下界,实际可用 k 可能略大(因生成多项式可能有公因子),本实现已通过穷举验证GF_M=5, BCH_T=2时g(x)确实为 10 次不可约多项式,故BCH_K=21有效。

注意:GEN_POLY_DEG必须严格等于GF_M * BCH_T,这是保证纠错能力的充要条件。若手动指定生成多项式(如从文献抄来),必须用gf_poly_degree()函数校验其次数,否则 BM 算法迭代步数会错配,导致解码失败。


3. 编码器详解:从信息位到位串的模 2 除法手撕过程

编码的本质是将 k 比特信息映射为 n 比特码字,满足c(x) ≡ 0 mod g(x)。本实现不使用矩阵乘法(内存爆炸),而用硬件友好的移位寄存器方式模拟多项式长除法。

3.1 生成多项式 g(x) 的构造与固化

BCH 码的生成多项式g(x)是若干最小多项式的最小公倍式。对 BCH(15,7)(即GF_M=4, BCH_T=2),需取 α¹, α² 的最小多项式 lcm(m₁(x), m₂(x))。由于 α² 的最小多项式与 α¹ 相同(在 GF(2⁴) 中),故g(x) = m₁(x) = x⁴ + x + 1?错!这是常见误解。实际BCH(15,7)要求纠正 2 错,需包含 α¹, α², α³, α⁴ 的根,其g(x)次数为 8,具体为:
g(x) = (x⁴ + x + 1)(x⁴ + x³ + 1) = x⁸ + x⁷ + x⁶ + x⁴ + 1
本实现将g(x)固化为 uint16_t 常量:

// bch_codec.c const uint16_t GEN_POLY = 0x1D1; // 二进制 1 1101 0001 → x^8 + x^7 + x^6 + x^4 + 1 const uint8_t GEN_POLY_DEG = 8;

逻辑说明:0x1D1是g(x)的系数位串,最高位x^8对应 bit8(第 9 位),因此GEN_POLY_DEG = 8。该值直接决定移位寄存器长度和校验位数量n−k = 8。

3.2 模 2 长除法:用 8 级移位寄存器实现

编码过程等价于:将信息位左移n−k位(即补 0),再对此扩展位串除以g(x),余式即校验位。C 实现用 8-bit 寄存器reg模拟:

void bch_encode(const uint8_t *data, uint8_t *code) { uint8_t reg = 0; // 8-bit shift register for remainder int i, j; // Step 1: Load k bits (7 bits) into upper part of reg + shift left // We'll process data bit-by-bit, MSB first for (i = 0; i < BCH_K; i++) { uint8_t bit = (data[i / 8] >> (7 - i % 8)) & 1; uint8_t top_bit = (reg >> (GEN_POLY_DEG - 1)) & 1; reg <<= 1; reg |= bit; if (top_bit) { reg ^= GEN_POLY; // XOR with generator polynomial } } // Step 2: Flush remaining bits (n-k steps) to get full remainder for (j = 0; j < GEN_POLY_DEG; j++) { uint8_t top_bit = (reg >> (GEN_POLY_DEG - 1)) & 1; reg <<= 1; if (top_bit) { reg ^= GEN_POLY; } } // Now reg holds the parity bits (LSB first in reg) // Copy data bits to code[0..k-1], parity to code[k..n-1] memcpy(code, data, (BCH_K + 7) / 8); // copy full bytes of data code[BCH_K / 8] &= ~((1 << (8 - BCH_K % 8)) - 1); // clear tail bits // ... bit-level copy of reg into parity positions (omitted for brevity) }

参数说明:

  • BCH_K = 7,故外层循环 7 次,每次取data中一位(MSB 优先);
  • reg初始为 0,每次左移后填入新 bit,若最高位为 1 则与GEN_POLY异或——这正是模 2 除法中“减去”操作的等价(因 GF(2) 中加减同义);
  • 第二个循环GEN_POLY_DEG=8次,将寄存器中剩余内容“挤出”,得到最终 8-bit 余式(校验位);
  • 最后需将 7-bit 数据和 8-bit 校验位按位拼接为 15-bit 码字,涉及跨字节位操作,本实现提供bch_pack_bits()辅助函数。

3.3 编码输出验证:用已知测试向量交叉检验

可靠编码必须通过标准测试向量。本实现附带test_vectors.h,含 BCH(15,7) 的权威向量:

信息位(7bit)期望码字(15bit)
0x00(0000000)0x0000(0000000 00000000)
0x40(1000000)0x1D10(1000000 111010001)

验证代码:

uint8_t test_data[1] = {0x40}; uint8_t test_code[2] = {0}; // 15 bits → 2 bytes bch_encode(test_data, test_code); uint16_t actual = (test_code[0] << 8) | test_code[1]; assert(actual == 0x1D10); // Passes only if GEN_POLY and logic are correct

若断言失败,90% 概率是GEN_POLY值错误或位序(MSB/LSB 优先)不一致——这是新手第一大坑,必须用示波器抓 I/O 或仿真器单步确认位流顺序。


4. 解码器核心:BM 迭代 + Chien 搜索的 C 语言直译与边界处理

解码比编码复杂一个数量级。本节聚焦最易出错的两步:Berlekamp-Massey(BM)算法求错误位置多项式 Λ(x),以及 Chien 搜索定位错误位置。它们共同决定了能否把“收到的乱码”变回“原始数据”。

4.1 伴随式 Sᵢ 计算:从接收码字到 GF 域元素的映射

伴随式Sᵢ = r(αⁱ)是解码起点。对 BCH(15,7),需计算S₁, S₂, S₃, S₄(因 t=2,需 2t=4 个)。r(x)是接收码字多项式,αⁱ是 GF(2⁴) 中的本原元幂次。计算本质是:对码字每一位rⱼ(j=0..14),计算rⱼ · α^(i·j)并累加(GF 加法为异或)。

void bch_calc_syndrome(const uint8_t *recv, uint16_t *S) { int i, j; for (i = 1; i <= 2*BCH_T; i++) { S[i] = 0; for (j = 0; j < BCH_N; j++) { uint8_t bit = (recv[j/8] >> (7 - j%8)) & 1; if (bit) { // α^(i*j) = alpha_to[(i*j) % 15] because α^15 = 1 uint8_t exp = (i * j) % BCH_N; S[i] ^= alpha_to[exp]; // GF addition = XOR } } } }

关键点:

  • alpha_to[]是预计算表:alpha_to[k] = αᵏ,大小为BCH_N+1(索引 0..15);
  • exp = (i * j) % BCH_N利用有限域周期性,避免大指数溢出;
  • S[i]是 uint16_t,但实际只用低 4-bit(因 GF(2⁴) 元素占 4bit),高位清零。

4.2 Berlekamp-Massey 算法:用 3 个数组实现迭代更新

BM 算法目标是找到最低次多项式 Λ(x) = 1 + λ₁x + λ₂x² + … + λₜxᵗ,使得对所有 i=1..2t,有∑_{j=0}^t λⱼ S_{i−j} = 0(约定 λ₀=1, S₀=0)。本实现用三个数组管理状态:

uint16_t lambda[BCH_T+1] = {1}; // Λ(x) coefficients, lambda[0]=1 uint16_t lambda_old[BCH_T+1] = {0}; int L = 0; // current degree of lambda int m = 1; // number of iterations for (int n = 1; n <= 2*BCH_T; n++) { // Compute discrepancy Δ = S_n + Σ_{i=1}^L lambda_i * S_{n-i} uint16_t delta = S[n]; for (int i = 1; i <= L; i++) { if (n >= i) delta ^= gf_mul(lambda[i], S[n-i]); } if (delta != 0) { // Update: lambda_new = lambda_old - delta * lambda_old_shifted for (int i = 0; i <= BCH_T; i++) { lambda_old[i] = lambda[i]; } int deg_diff = n - L - m; if (deg_diff > 0) { // Shift lambda_old by deg_diff positions for (int i = BCH_T; i >= deg_diff; i--) { lambda[i] = lambda_old[i-deg_diff]; } for (int i = 0; i < deg_diff; i++) { lambda[i] = 0; } } // Multiply shifted lambda_old by delta and subtract from lambda uint16_t inv_delta = gf_inv(delta); for (int i = 0; i <= BCH_T; i++) { lambda[i] ^= gf_mul(delta, lambda_old[i]); } L = n - m; m = n - L + 1; } }

参数说明:

  • lambda[0..t]存储当前 Λ(x) 系数,初始lambda[0]=1;
  • L是当前 Λ(x) 次数,m是上一次更新时的迭代步数;
  • delta为 0 表示当前 Λ(x) 已满足前 n 个方程,无需更新;
  • gf_inv()是 GF 域求逆,通过index_of[]查表实现:gf_inv(a) = alpha_to[(15 - index_of[a]) % 15]。

4.3 Chien 搜索:遍历所有 αʲ 找 Λ(αʲ)=0 的根

Λ(x) 的根对应错误位置。Chien 搜索对每个j=0..14,计算Λ(αʲ) = 1 + λ₁αʲ + λ₂α²ʲ + … + λₜαᵗʲ,若结果为 0,则位置j有错。

int error_pos[BCH_T]; // store positions where error occurs int num_errors = 0; for (int j = 0; j < BCH_N; j++) { uint16_t sum = 1; // Λ(0) = 1 always uint16_t alpha_power = alpha_to[j]; // α^j for (int i = 1; i <= L; i++) { sum ^= gf_mul(lambda[i], alpha_power); alpha_power = gf_mul(alpha_power, alpha_to[j]); // α^(i*j) } if (sum == 0) { error_pos[num_errors++] = j; if (num_errors > BCH_T) break; // more errors than correctable } }

注意:j是码字位置索引(0-based),但实际纠错时需映射到比特序号。例如j=3表示码字第 4 位(从左或右?)出错——这取决于你定义r(x) = r₀ + r₁x + … + r₁₄x¹⁴时r₀是 MSB 还是 LSB。本实现约定r₀为最高位(即recv[0]的 bit7),故j=3对应recv[0]的 bit4。务必与编码端位序严格一致!


5. 避坑指南:5 条血泪经验总结的 BCH C 实现高频翻车点

写完代码只是开始,真正在裸机上跑通 BCH 才是噩梦的起点。以下 5 条是某开发者在 STM32F0 上调试三天后记下的真实踩坑记录,每一条都曾让 LED 灯疯狂闪烁、串口输出乱码、或解码结果永远多一个错。

5.1 现象:伴随式全为 0,但接收码字明显有错

原因:Sᵢ计算中alpha_to[]表未正确初始化,或index_of[]中alpha_to[0]被设为 0(应为 1,因 α⁰=1)。alpha_to[0]若为 0,则Sᵢ累加时bit=1时异或 0,导致所有Sᵢ=0。
解决:在gf_init_tables()中显式设置alpha_to[0] = 1; index_of[1] = 0;,并用for(i=0;i<16;i++) printf("%d ", alpha_to[i]);打印验证是否为[1,2,4,8,3,6,12,11,5,10,7,14,15,13,9,1](对应 p(x)=x⁴+x+1)。

5.2 现象:BM 算法迭代后lambda[1]为 0,Λ(x) 恒为 1

原因:delta计算时未处理S₀。BM 要求S₀=0,但代码中若S[0]未初始化为 0,delta计算lambda[1]*S[n-1]时当n=1会访问S[0],其垃圾值导致delta永远非 0,算法发散。
解决:声明uint16_t S[2*BCH_T+1] = {0};,显式初始化整个数组为 0,确保S[0]安全。

5.3 现象:Chien 搜索找到 3 个根,但BCH_T=2,程序报“不可纠正”

原因:错误位置j计算中j从 0 开始,但alpha_to[j]表大小为BCH_N=15,索引j=15会越界访问alpha_to[15](不存在)。当j=15时%15=0,但代码未做j < BCH_N边界检查,导致alpha_to[15]读取随机内存。
解决:Chien 循环必须为for(j=0; j<BCH_N; j++),且alpha_to数组大小声明为[BCH_N],而非[BCH_N+1]。

5.4 现象:编码输出与理论值一致,但解码后数据位全错

原因:编码时信息位data和校验位parity拼接顺序与解码时解析顺序相反。例如编码将data[0..6]放高位、parity[0..7]放低位,但解码时误将recv[0]当作parity而非data。
解决:统一定义码字结构体:

typedef struct { uint8_t data[BCH_K_BITS]; // k bits, MSB first uint8_t parity[BCH_N-K_BITS]; // n-k bits, MSB first } bch_codeword_t;

并在bch_encode()和bch_decode()中严格按此布局读写recv缓冲区。

5.5 现象:在优化等级-O2下解码失败,-O0正常

原因:reg寄存器变量被编译器优化掉,或alpha_to[]表未加const修饰,导致地址计算错误。更隐蔽的是:gf_mul(a,b)中若a或b为 0,查表alpha_to[index_of[a] + index_of[b]]会因index_of[0] = -1导致负索引。
解决:

  • 所有查表数组声明为static const uint8_t alpha_to[16];
  • gf_mul()开头加保护:
if (a == 0 || b == 0) return 0; return alpha_to[(index_of[a] + index_of[b]) % 15];
  • 关键函数加__attribute__((optimize("O0")))(GCC)强制关闭优化。

6. 进阶技巧:用 Python 生成任意 BCH 参数的 C 查表与验证向量

手写alpha_to[]和index_of[]表既枯燥又易错。我一般会用 Python 脚本自动生成,确保数学正确性,并同步产出测试向量供 C 端验证。这不是“偷懒”,而是把重复劳动交给机器,把精力留给真正的逻辑调试。

6.1 自动生成 GF(2^m) 表的 Python 脚本核心逻辑

def generate_gf_tables(m, p_poly): """Generate alpha_to and index_of for GF(2^m) with primitive polynomial p_poly""" n = (1 << m) - 1 # p_poly is integer, e.g., 0b10011 for x^4+x+1 alpha_to = [0] * (n + 1) index_of = [-1] * (1 << m) # alpha^0 = 1 alpha_to[0] = 1 index_of[1] = 0 # Compute alpha^1, alpha^2, ..., alpha^(n-1) by multiplication curr = 1 for i in range(1, n): curr = curr << 1 if curr & (1 << m): # if degree >= m, reduce by p_poly curr ^= p_poly alpha_to[i] = curr index_of[curr] = i return alpha_to, index_of # Example: GF(2^4) with p(x) = x^4 + x + 1 = 0b10011 = 19 alpha_to, index_of = generate_gf_tables(4, 0b10011) print("alpha_to =", alpha_to) print("index_of =", index_of)

运行输出:

alpha_to = [1, 2, 4, 8, 3, 6, 12, 11, 5, 10, 7, 14, 15, 13, 9, 1] index_of = [-1, 0, 1, 4, 2, 8, 5, 10, 3, 14, 9, 7, 6, 13, 11, 12]

注意:index_of[0] = -1是硬编码,表示 0 无对数;alpha_to[15] = 1验证了周期为 15,正确。

6.2 一键生成 C 头文件的模板引擎

将上述 Python 输出注入 C 模板:

template = '''// Auto-generated GF tables for GF(2^{m}) #ifndef GF_TABLES_H #define GF_TABLES_H #include <stdint.h> #define GF_M {m} #define GF_N {(1<<m)-1} static const uint8_t alpha_to[{n}] = {{{alpha_str}}}; static const int8_t index_of[{size}] = {{{index_str}}}; #endif ''' with open("gf_tables.h", "w") as f: f.write(template.format( m=4, n=16, alpha_str=", ".join(map(str, alpha_to)), size=16, index_str=", ".join(map(str, index_of)) ))

生成的gf_tables.h可直接#include到 C 工程中,杜绝手输错误。

6.3 用 Python 模拟完整编解码链路,生成黄金测试向量

def bch_encode_py(data_bits, g_poly, n, k): # Pad data with n-k zeros padded = data_bits + [0] * (n - k) # Polynomial division reg = 0 for bit in padded: top = (reg >> (len(bin(g_poly))-3)) & 1 # degree of g_poly reg <<= 1 reg |= bit if top: reg ^= g_poly # Final reg is parity return data_bits + list(map(int, format(reg, f'0{n-k}b'))) # Generate all 2^7 = 128 test vectors for BCH(15,7) g_poly = 0x1D1 # x^8 + x^7 + x^6 + x^4 + 1 vectors = [] for i in range(128): data = [(i >> j) & 1 for j in range(6, -1, -1)] # 7 bits, MSB first code = bch_encode_py(data, g_poly, 15, 7) vectors.append({ 'data': hex(int("".join(map(str, data)), 2)), 'code': hex(int("".join(map(str, code)), 2)) }) # Write to C header with open("test_vectors.h", "w") as f: f.write("#ifndef TEST_VECTORS_H\n#define TEST_VECTORS_H\n") f.write("const struct {uint8_t data; uint16_t code;} test_vecs[] = {\n") for v in vectors[:10]: # first 10 for quick test f.write(f" {{{v['data']}, {v['code']} }},\n") f.write("};\n#endif\n")

这样生成的test_vectors.h与 C 端完全兼容,bch_encode()输出可与之逐位比对,误差为 0 才算真正通过。

从那以后我每次启动新项目,第一件事就是跑这个 Python 脚本生成表和向量,再把bch_codec.c丢进 Keil 或 GCC 编译,用test_vecs[0]过一遍 encode/decode。只要第一步过不了,绝不碰第二行业务代码——省下的三天调试时间,够我把整个协议栈重写两遍。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询