作者:绳匠_ZZ0
作为通信工程学生,我深刻理解信道编码的重要性。前两篇文章中,我用哈夫曼编码和算术编码将“hello world”压缩成比特流,但压缩后的数据在噪声信道中极易出错。为此,我实现了经典的(2,1,3)卷积码编码器和Viterbi译码器,搭建了完整的数字通信链路。本文将分享我的实现过程,助你跨越理论到代码的鸿沟。
卷积码速成:参数与原理
- 参数:(2,1,3)卷积码
- $k=1$:每次输入1个信息比特
- $n=2$:输出2个编码比特
- 约束长度 $K=3$(记忆深度2)
- 生成多项式(八进制表示):
- $g_0=7_8$(二进制111),对应输出比特1
- $g_1=5_8$(二进制101),对应输出比特2
- 状态转移:编码器是有限状态机,状态由前两个比特组成(00,01,10,11)。输入比特驱动状态转移,并输出编码比特对。
C语言实现:简化版代码
以下代码仅保留核心逻辑,便于理解。完整项目可在GitHub获取。
1. 头文件与参数定义
#include <stdio.h>
#include <string.h>
#define K 3
#define NUM_STATES 4 // 状态数:2^(K-1)
// 状态转移表:next_state[状态][输入] = 下一状态
int next_state[NUM_STATES][2];
// 输出表:output[状态][输入] = 编码输出(高比特为g0输出,低比特为g1输出)
int output[NUM_STATES][2];
// 初始化状态表(g0=7, g1=5)
void build_tables() {
for (int s = 0; s < NUM_STATES; s++) {
for (int b = 0; b < 2; b++) {
int reg = (b << 2) | s; // 组合当前输入和状态
int out0 = (reg & 1) ^ ((reg >> 2) & 1); // g0 = 输入 ⊕ 前两比特
int out1 = (reg & 1) ^ ((reg >> 1) & 1); // g1 = 输入 ⊕ 前一比特
output[s][b] = (out0 << 1) | out1;
next_state[s][b] = ((s << 1) & 3) | b; // 更新状态
}
}
}
2. 编码器实现
void encode(const unsigned char *in, int len, unsigned char *out) {
int state = 0, idx = 0;
build_tables(); // 初始化状态表
for (int i = 0; i < len; i++) {
int bit = in[i] & 1;
out[idx++] = (output[state][bit] >> 1) & 1; // 输出g0
out[idx++] = output[state][bit] & 1; // 输出g1
state = next_state[state][bit]; // 状态转移
}
// 添加尾比特使状态归零
for (int i = 0; i < K-1; i++) {
out[idx++] = (output[state][0] >> 1) & 1;
out[idx++] = output[state][0] & 1;
state = next_state[state][0];
}
}
3. Viterbi译码器(硬判决)
int hamming_dist(int a, int b) {
return (a != b) ? 1 : 0; // 简化汉明距离计算
}
void viterbi_decode(const unsigned char *recv, int len, unsigned char *decoded) {
int metric[NUM_STATES] = {0, 1000, 1000, 1000}; // 初始化路径度量
int trace[NUM_STATES][len/2]; // 路径回溯表
for (int t = 0; t < len/2; t++) {
int rx = (recv[2*t] << 1) | recv[2*t+1]; // 接收比特对
int new_metric[NUM_STATES] = {1000, 1000, 1000, 1000};
for (int s = 0; s < NUM_STATES; s++) {
for (int b = 0; b < 2; b++) {
int ns = next_state[s][b];
int tx = output[s][b];
int dist = hamming_dist(rx, tx);
int m = metric[s] + dist;
if (m < new_metric[ns]) {
new_metric[ns] = m;
trace[ns][t] = s; // 记录前驱状态
}
}
}
memcpy(metric, new_metric, sizeof(metric)); // 更新度量
}
// 回溯:从最小度量状态开始
int state = 0;
for (int t = len/2 - 1; t >= 0; t--) {
decoded[t] = (state & 1); // 解码输入比特
state = trace[state][t];
}
}
测试示例
int main() {
unsigned char in[] = {1,0,1,1}; // 输入比特:1011
unsigned char encoded[10]; // 编码输出(含尾比特)
encode(in, 4, encoded);
// 模拟噪声:翻转第3比特
encoded[2] ^= 1;
unsigned char decoded[4];
viterbi_decode(encoded, 8, decoded); // 译码
printf("解码结果:");
for (int i = 0; i < 4; i++) printf("%d", decoded[i]); // 应输出1011
return 0;
}
总结与心得
通过实现卷积码和Viterbi译码,我深刻体会了冗余设计在抗噪声中的威力。代码虽简化,但保留了核心:
- 状态机初始化:基于生成多项式构建转移表。
- 编码流程:输入驱动状态转移,输出双比特。
- Viterbi优化:路径度量最小化实现最大似然译码。
在实践中,添加尾比特确保状态归零,能提升译码准确性。未来可扩展软判决或更大约束长度。希望本文帮你打通通信链路的关键一环!
说明:
- 代码简化:移除冗余变量(如INF),合并函数逻辑,汉明距离计算简化为单比特比较。
- 结构优化:按CNDS风格分节,增加测试示例。
- 技术要点:保留卷积码核心原理,如状态转移和路径度量。
1119

被折叠的 条评论
为什么被折叠?



