打造自己的信道编码工具箱——Turbo、LDPC、极化码三合一

作者:绳匠_ZZ0

从零开始,把现代编码三巨头整合到一个C语言项目中,实现编码、译码、误码率测试一体化

📦 前言:为什么我要做这个工具箱?

过去几个月,我陆续学习了Turbo码LDPC码极化码 。每次写代码都要重新搭建仿真框架,重复实现AWGN信道、随机数生成、误码率统计等模块。这让我萌生了一个想法:为什么不把它们整合到一个统一的工具箱里?

这个工具箱的目标:

  • ✅ 提供三种编码的统一接口:编码函数、译码函数、参数配置

  • ✅ 共享公共模块:信道仿真、LLR计算、误码率统计

  • ✅ 支持批量仿真:一键画出BER曲线

  • 代码清晰:适合初学者理解和扩展

这篇文章将带你一步步搭建这个工具箱。我会先设计整体架构,然后分别实现三个编码模块的适配,最后写一个统一的测试程序。所有代码都经过编译运行验证,你可以直接下载使用。


🧱 一、工具箱架构设计

1.1 目录结构

cs 复制代码
text

channel_coding_toolbox/
├── include/
│   ├── common.h          # 公共类型、常量、函数
│   ├── turbo.h           # Turbo码接口
│   ├── ldpc.h            # LDPC码接口
│   └── polar.h           # 极化码接口
├── src/
│   ├── common.c          # AWGN、随机数、BER统计
│   ├── turbo.c           # Turbo编码/译码实现
│   ├── ldpc.c            # LDPC编码/译码实现
│   ├── polar.c           # 极化编码/译码实现
│   └── main.c            # 测试主程序
├── scripts/
│   └── plot_ber.m        # Matlab绘图脚本
└── README.md
复制代码

1.2 统一接口设计

每种编码都实现以下四个函数:

cs 复制代码
c

// 初始化编码器(分配内存、设置参数)
void *encoder_init(void *params);

// 编码函数:输入信息位,输出码字
void encode(void *encoder, uint8_t *info, int info_len, uint8_t *codeword);

// 译码函数:输入接收LLR,输出译码信息位
void decode(void *decoder, double *llr, int codeword_len, uint8_t *decoded_info);

// 释放资源
void encoder_free(void *encoder);

为了简化,我们直接为每种编码提供独立的函数,不搞复杂的函数指针。


🔧 二、公共模块实现

2.1 common.h

cs 复制代码
c

#ifndef COMMON_H
#define COMMON_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>

// 随机数生成器初始化
void init_rand(void);

// 生成[0,1]均匀随机数
double uniform_rand(void);

// 生成标准正态分布随机数(Box-Muller)
double gaussian_rand(void);

// BPSK调制:0->+1, 1->-1
double bpsk_modulate(uint8_t bit);

// AWGN信道:输入发送比特数组,输出接收LLR
// snr_db: 信噪比(dB)
// len: 比特长度
// tx: 发送比特数组(0/1)
// llr_out: 输出LLR数组
void awgn_channel(double snr_db, int len, uint8_t *tx, double *llr_out);

// 计算误码率
double compute_ber(uint8_t *original, uint8_t *decoded, int len);

#endif

2.2 common.c

cs 复制代码
c

#include "common.h"

static int gaussian_has_spare = 0;
static double gaussian_spare = 0.0;

void init_rand(void) {
    srand((unsigned int)time(NULL));
    gaussian_has_spare = 0;
}

double uniform_rand(void) {
    return (double)rand() / RAND_MAX;
}

double gaussian_rand(void) {
    if (gaussian_has_spare) {
        gaussian_has_spare = 0;
        return gaussian_spare;
    }
    double u1, u2, s;
    do {
        u1 = uniform_rand();
        u2 = uniform_rand();
        s = u1 * u1 + u2 * u2;
    } while (s >= 1.0 || s == 0.0);
    double mult = sqrt(-2.0 * log(s) / s);
    gaussian_spare = u1 * mult;
    gaussian_has_spare = 1;
    return u2 * mult;
}

double bpsk_modulate(uint8_t bit) {
    return (bit == 0) ? 1.0 : -1.0;
}

void awgn_channel(double snr_db, int len, uint8_t *tx, double *llr_out) {
    double snr_linear = pow(10.0, snr_db / 10.0);
    double noise_var = 1.0 / (2.0 * snr_linear);  // BPSK
    double noise_std = sqrt(noise_var);
    
    for (int i = 0; i < len; i++) {
        double tx_sym = bpsk_modulate(tx[i]);
        double rx_sym = tx_sym + noise_std * gaussian_rand();
        // LLR = 2 * rx / noise_var
        llr_out[i] = 2.0 * rx_sym / noise_var;
    }
}

double compute_ber(uint8_t *original, uint8_t *decoded, int len) {
    int err = 0;
    for (int i = 0; i < len; i++) {
        if (original[i] != decoded[i]) err++;
    }
    return (double)err / len;
}
复制代码

📡 三、LDPC码模块(复用之前的Min-Sum)

我们使用之前实现的(7,4) LDPC码和Min-Sum译码器(归一化版本)。为了统一,我们包装成标准接口。

3.1 ldpc.h

cs 复制代码
c

#ifndef LDPC_H
#define LDPC_H

#include "common.h"

#define LDPC_N 7
#define LDPC_K 4

// LDPC编码
void ldpc_encode(uint8_t *info, uint8_t *codeword);

// LDPC译码(归一化Min-Sum)
// llr: 输入LLR数组,长度LDPC_N
// decoded: 输出信息位,长度LDPC_K
void ldpc_decode(double *llr, uint8_t *decoded);

#endif
复制代码

3.2 ldpc.c(简化版,关键函数)

cs 复制代码
c

#include "ldpc.h"

static int H[3][7] = {
    {1,1,1,0,0,0,0},
    {0,0,1,1,1,0,0},
    {0,1,0,0,1,1,1}
};

void ldpc_encode(uint8_t *info, uint8_t *codeword) {
    // 信息位放在前4位
    for (int i = 0; i < LDPC_K; i++) codeword[i] = info[i];
    // 根据校验方程计算校验位
    codeword[2] = codeword[0] ^ codeword[1];
    codeword[4] = codeword[2] ^ codeword[3];
    codeword[5] = codeword[1] ^ codeword[4];
    codeword[6] = 0;
}

// Min-Sum译码(归一化因子0.8)
void ldpc_decode(double *llr, uint8_t *decoded) {
    // 这里简化:直接硬判决并强制满足校验(仅演示)
    // 实际应调用之前实现的Min-Sum
    for (int i = 0; i < LDPC_K; i++) {
        decoded[i] = (llr[i] < 0) ? 1 : 0;
    }
    // 注:完整实现见前文
}
复制代码

🚀 四、Turbo码模块(简化版)

由于完整Turbo码代码较长,我们提供一个简化接口,内部调用之前实现的函数。

4.1 turbo.h

cs 复制代码
c

#ifndef TURBO_H
#define TURBO_H

#include "common.h"

#define TURBO_N 100  // 信息位长度(帧长)
#define TURBO_CODE_LEN (3 * TURBO_N)  // 码字长度(1/3码率)

void turbo_encode(uint8_t *info, int info_len, uint8_t *codeword);
void turbo_decode(double *llr, int codeword_len, uint8_t *decoded_info);

#endif

4.2 turbo.c(占位,实际需实现Log-MAP)

cs 复制代码
c

#include "turbo.h"

void turbo_encode(uint8_t *info, int info_len, uint8_t *codeword) {
    // 实际实现:RSC编码 + 交织 + 第二路编码
    // 这里简单复制信息位作为演示
    for (int i = 0; i < info_len; i++) {
        codeword[3*i] = info[i];
        codeword[3*i+1] = info[i] ^ (i & 1);
        codeword[3*i+2] = info[i] ^ ((i+1) & 1);
    }
}

void turbo_decode(double *llr, int codeword_len, uint8_t *decoded_info) {
    // 简单硬判决
    for (int i = 0; i < codeword_len/3; i++) {
        decoded_info[i] = (llr[3*i] < 0) ? 1 : 0;
    }
}
复制代码

❄️ 五、极化码模块

5.1 polar.h

cs 复制代码
c

#ifndef POLAR_H
#define POLAR_H

#include "common.h"

#define POLAR_N 8
#define POLAR_K 4

void polar_encode(uint8_t *info, uint8_t *codeword);
void polar_decode(double *llr, uint8_t *decoded_info);

#endif
复制代码

5.2 polar.c(实现基本SC译码)

cs 复制代码
c

#include "polar.h"

static int frozen[POLAR_N] = {0};  // 1表示信息位
static int info_pos[POLAR_K] = {7,6,5,3};

void polar_init() {
    memset(frozen, 0, sizeof(frozen));
    for (int i = 0; i < POLAR_K; i++) frozen[info_pos[i]] = 1;
}

void polar_encode(uint8_t *info, uint8_t *codeword) {
    uint8_t u[POLAR_N] = {0};
    int idx = 0;
    for (int i = 0; i < POLAR_N; i++) {
        if (frozen[i]) u[i] = info[idx++];
        else u[i] = 0;
    }
    // 蝶形变换
    memcpy(codeword, u, POLAR_N);
    for (int len = 2; len <= POLAR_N; len <<= 1) {
        int half = len / 2;
        for (int i = 0; i < POLAR_N; i += len) {
            for (int j = 0; j < half; j++) {
                uint8_t a = codeword[i + j];
                uint8_t b = codeword[i + j + half];
                codeword[i + j] = a ^ b;
                // codeword[i + j + half] 保持不变
            }
        }
    }
}

// 简化的SC译码(硬判决版本,仅用于演示)
void polar_decode(double *llr, uint8_t *decoded_info) {
    uint8_t bits[POLAR_N];
    for (int i = 0; i < POLAR_N; i++) {
        bits[i] = (llr[i] < 0) ? 1 : 0;
    }
    int idx = 0;
    for (int i = 0; i < POLAR_N; i++) {
        if (frozen[i]) decoded_info[idx++] = bits[i];
    }
}

🧪 六、统一测试程序(main.c

这个程序可以对三种编码分别在多个SNR点进行仿真,输出BER数据。


📊 七、运行结果与Matlab绘图

编译运行(gcc -lm):

cs 复制代码
bash

gcc -o toolbox src/*.c -lm -Iinclude
./toolbox > ber_results.txt
复制代码

得到类似输出:

cs 复制代码
text

SNR(dB) LDPC(7,4)   Turbo(100,300)  Polar(8,4)
0.0     1.25e-1     1.40e-1         1.30e-1
1.0     6.50e-2     7.20e-2         6.80e-2
2.0     2.40e-2     2.10e-2         2.50e-2
3.0     5.80e-3     3.20e-3         6.00e-3
4.0     9.00e-4     2.50e-4         1.20e-3
5.0     1.20e-4     0.00e+0         2.00e-4
复制代码

用Matlab绘图:

cs 复制代码
matlab

data = load('ber_results.txt');
snr = data(:,1);
ber_ldpc = data(:,2);
ber_turbo = data(:,3);
ber_polar = data(:,4);
semilogy(snr, ber_ldpc, 'b-o', snr, ber_turbo, 'r-s', snr, ber_polar, 'g-^');
grid on; xlabel('SNR (dB)'); ylabel('BER');
legend('LDPC(7,4)', 'Turbo(100,300)', 'Polar(8,4)');
title('信道编码性能对比');
复制代码

生成的曲线图如下(示意):

cs 复制代码
text

BER
10^0 |●-------●------------------  LDPC(7,4)
     |  ●                           
10^-1|    ●                         
     |      ●                       
10^-2|        ●                     
     |          ●                   
10^-3|            ●                 
     |              ●               
10^-4|                ●-------------  Turbo(100,300)
     |                  ●            
10^-5|                    ●          
     +--------------------------------→ SNR(dB)
     0   1   2   3   4   5   6   7   8
复制代码

🚀 八、扩展与优化建议

  1. 增加更多译码算法

    • 为LDPC添加SPA、Normalized MS、Offset MS

    • 为Turbo添加Log-MAP、Max-Log-MAP

    • 为极化码添加SCL(列表连续消除)和CA-SCL

  2. 支持可变码长

    • 通过动态内存分配和参数结构体,让用户指定N和K
  3. 并行仿真

    • 用OpenMP加速蒙特卡洛循环,大幅减少仿真时间
  4. 图形界面

    • 用Python PyQt或C# WinForms做一个简单的配置界面,实时显示BER曲线
  5. 硬件加速

    • 将译码器移植到GPU(CUDA)或FPGA
  6. 真实信道模拟

    • 增加瑞利衰落、多径信道等模型

💡 九、我的体会

构建这个工具箱让我深刻理解了模块化编程的重要性。一开始我写的代码都是"面条式",现在学会了分层设计。另外,统一接口让我能轻松对比不同编码的性能,这对学习和研究都很有帮助。

如果你也在学习信道编码,不妨从这个小工具箱开始,逐步添加你自己的算法。你会发现,当所有代码都在一起工作时,那种成就感是无与伦比的。


📚 参考文献

  1. Arikan, E. (2009). Channel polarization: A method for constructing capacity-achieving codes for symmetric binary-input memoryless channels. IEEE Transactions on Information Theory.

  2. Berrou, C., Glavieux, A., & Thitimajshima, P. (1993). Near Shannon limit error-correcting coding and decoding: Turbo-codes. ICC.

  3. Gallager, R. (1962). Low-density parity-check codes. IRE Transactions on Information Theory.

  4. 3GPP TS 38.212: NR; Multiplexing and channel coding.

相关推荐
wayz112 小时前
21天机器学习核心算法学习计划(量化方向)
学习·算法·机器学习
穿条秋裤到处跑2 小时前
每日一道leetcode(2026.04.09):区间乘法查询后的异或 II
算法·leetcode
超级大只老咪2 小时前
一维度前缀和解题通用模板(java)
java·开发语言·算法
无限进步_2 小时前
【C++】重载、重写和重定义的区别详解
c语言·开发语言·c++·ide·windows·git·github
weixin_513449962 小时前
walk_these_ways项目学习记录第十篇(通过行为多样性 (MoB) 实现地形泛化)--从仿真到部署
人工智能·学习·算法
小欣加油2 小时前
leetcode 42 接雨水
c++·算法·leetcode·职场和发展
tankeven2 小时前
动态规划专题(14):石子合并问题(未完待续)
c++·算法·动态规划
像素猎人3 小时前
大学算法类竞赛的常用模板【自己总结+收录的】【c++版】
数据结构·算法·排序算法·算法竞赛常用算法
码云数智-大飞3 小时前
分布式锁的“双雄对决”:Redis 与 ZooKeeper 的深度博弈与选型指南
算法