Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ venv/
.pytest_cache/
.pylint.d/
.DS_Store
results/*.png

results/*.csv
results/*.json
!results/.gitkeep
Expand Down
95 changes: 95 additions & 0 deletions REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 无线通信技术实验报告:信道编码与信道均衡

## 1. 实验目的

1. 理解信道编码原理,包括 Hamming(7,4) 分组码的编码、伴随式计算和单比特纠错机制,体会编码增益的概念。
2. 掌握信道均衡技术,理解码间干扰的产生机理,学习迫零均衡器和 LMS 自适应均衡器的设计方法。
3. 熟悉 GitHub 自动评分流程,包括 Fork 仓库、本地开发、pytest 测试和自动评分的完整工作流。
4. 实践 Python 科学计算,运用 NumPy 进行矩阵运算和信号处理,使用 Matplotlib 可视化实验结果。

## 2. 实验原理

### 2.1 信道编码

1. Hamming(7,4) 码是一种线性分组码,将 4 位信息比特编码为 7 位码字,引入 3 位监督比特。其生成矩阵 G 为 4 乘 7 矩阵,满足 c 等于 m 点乘 G 后对 2 取模。
2. 伴随式的定义为:设接收码字为 r,校验矩阵为 H,则伴随式 s 等于 r 点乘 H 的转置后对 2 取模。若 s 等于 0,认为无错;若 s 不等于 0,则 s 对应 H 的某一列,指示错误位置。
3. 单比特纠错的原理:Hamming(7,4) 码的最小汉明距离 d_min 等于 3,根据编码理论,可纠正的错误位数 t 等于 d_min 减 1 后除以 2 再向下取整,结果为 1,因此能纠正任意单比特错误。
4. 编码增益是指通过引入冗余比特降低误码率,在相同信噪比下获得性能提升,代价是码率从 1 降至 4 除以 7。

### 2.2 信道均衡

1. 码间干扰的产生原因是多径信道导致信号经过不同路径到达接收端,产生时延扩展,造成前后符号相互干扰。
2. 迫零均衡器的设计思路是通过设计 FIR 滤波器 w,使得 h 与 w 的卷积近似等于冲激函数 delta[n],强制消除码间干扰。具体通过求解最小二乘问题得到,其中 A 为卷积矩阵,d 为中心为 1 的目标向量。
3. LMS 自适应均衡利用训练序列迭代更新抽头系数,更新公式为 w[n+1] 等于 w[n] 加上步长 mu 乘以误差 e[n] 再乘以输入向量 x[n]。其中 e[n] 等于期望输出 d[n] 减去实际输出 y[n]。

## 3. 实验环境

1. Python 版本为 Python 3.x。
2. 主要依赖包括 NumPy 用于矩阵运算,Matplotlib 用于绘图,pytest 用于单元测试。
3. AI 助手使用情况为使用 AI 辅助理解实验要求、调试代码逻辑、解释通信原理概念。

## 4. 实验方法与步骤

### 4.1 Part 1:信道编码

1. 编码,实现 hamming74_encode 函数,将输入比特按每 4 位分组,通过生成矩阵 G 在 GF(2) 上相乘得到 7 位码字。
2. 加噪与翻转,模拟 BSC 信道,以概率 p 随机翻转码字比特;或模拟 AWGN 信道加高斯噪声后硬判决。
3. 译码,实现 hamming74_syndrome 函数计算伴随式定位错误,hamming74_decode 函数翻转错误位并提取前 4 位信息比特。
4. 误码率对比,对比编码前后的误码率,绘制误码率与信噪比关系曲线。

### 4.2 Part 2:信道均衡

1. 多径信道建模,给定信道冲激响应如 h 等于 [0.9, 0.3, -0.2],产生码间干扰。
2. 迫零均衡器设计,构造卷积矩阵 A,求解最小二乘问题得到均衡器抽头。
3. LMS 自适应均衡,使用训练序列,按 LMS 算法迭代更新抽头系数,记录误差收敛曲线。
4. 效果评估,对比均衡前后的眼图、星座图,计算均方误差评估均衡性能。

## 5. 实验结果

![编码BER曲线](image-2.png)
![均衡眼图对比](image-3.png)
![LMS误差曲线](image-4.png)


## 6. 结果分析与讨论

回答:

1. Hamming(7,4) 为什么能纠正单比特错误?

Hamming(7,4) 码的最小汉明距离 d_min 等于 3。根据线性分组码的纠错能力公式,可纠正错误位数 t 等于 d_min 减 1 后除以 2 再向下取整,结果为 1。这意味着任意两个合法码字至少相差 3 位,因此当发生单比特错误时,接收序列距离原码字最近,距离为 1,而距离其他合法码字至少为 2,译码器能唯一确定错误位置并纠正。

2. 为什么信道编码会引入冗余并降低码率?

信道编码通过在信息比特中添加监督比特来实现检错纠错功能。Hamming(7,4) 将 4 位信息扩展为 7 位,码率 R 等于 4 除以 7,约等于 0.57,小于 1。冗余比特不携带原始信息,但提供了纠错所需的空间,使得接收端能够区分合法码字和错误图案。

3. ZF 均衡为什么可能放大噪声?

迫零均衡器的设计目标是完全消除码间干扰,即 H(f) 乘以 W(f) 等于 1。在信道频率响应 H(f) 存在深衰落的频率点,幅值很小,此时均衡器增益 W(f) 等于 1 除以 H(f) 会非常大,导致该频段的噪声被大幅放大。因此迫零均衡在消除码间干扰的同时会恶化信噪比,特别是在频率选择性深衰落信道下。

4. LMS 的步长过大或过小会出现什么问题?

当步长过大时,算法会发散,误差剧烈震荡不收敛。原因是更新步长超过最优值范围,在最优解附近来回跳跃甚至远离。
当步长过小时,收敛极慢,需要大量迭代才能接近最优。原因是每次更新幅度太小,难以快速跟踪信道变化。
当步长适中时,算法平稳收敛到最优解,满足 0 小于 mu 小于 2 除以最大特征值这一稳定条件。

5. 均衡前后 ISI 有什么变化?

均衡前眼图张开度较小,甚至闭合;均衡后眼图明显增大,清晰张开。
均衡前码间干扰严重,多径导致符号重叠;均衡后码间干扰大幅抑制,冲激响应接近理想。
均衡前误码率较高;均衡后误码率显著降低。
均衡前噪声为原始噪声水平;均衡后若使用迫零均衡,可能因噪声放大而略有增加。


## 7. 实验心得

通过本次实验,我对信道编码和信道均衡有了更深入的理解。
1. 关于信道编码,Hamming(7,4) 码虽然简单,但完整展示了冗余换可靠性的编码思想。伴随式译码的逻辑非常巧妙,将代数结构与物理错误建立了直接对应关系。实现过程中对 GF(2) 上的矩阵运算有了更直观的认识。
2. 关于信道均衡,迫零和 LMS 代表了两种截然不同的均衡思路。迫零是开环的基于信道先验知识的设计,LMS 是闭环的自适应学习。实际系统中往往需要结合两者优势,或采用更稳健的 MMSE 准则。
3. 关于自动评分与 GitHub 工作流,pytest 驱动的自动测试让代码质量有了客观标准,也培养了我先写测试、再实现功能的测试驱动开发思维。Fork 到 Clone 到 Commit 到 Push 的流程是现代软件工程的基础技能。
4. 关于 AI 编程辅助,AI 在解释通信原理、提供代码框架和调试思路上效率很高,但核心算法逻辑如 LMS 更新公式的推导、伴随式的物理意义仍需自己理解,否则难以应对测试中的边界情况。

## 8. 参考资料

- 课程课件:第6章 信道编码
- 课程课件:第7章 均衡
60 changes: 0 additions & 60 deletions REPORT_TEMPLATE.md

This file was deleted.

Binary file added results/coding_ber_curve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added results/equalization_eye_comparison.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added results/equalization_mse_curve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
137 changes: 127 additions & 10 deletions src/part1_channel_coding.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ def hamming74_encode(bits):
if not np.all((bits == 0) | (bits == 1)):
raise ValueError('bits 只能包含 0 或 1')

# TODO: 将 bits reshape 为 (-1, 4),再与 HAMMING_G 相乘并对 2 取模。
raise NotImplementedError('请实现 Hamming(7,4) 编码')
# 将 bits reshape 为 (-1, 4),再与 HAMMING_G 相乘并对 2 取模
blocks = bits.reshape(-1, 4)
encoded = (blocks @ HAMMING_G) % 2
return encoded.flatten()

# raise NotImplementedError('请实现 Hamming(7,4) 编码')


def hamming74_syndrome(codewords):
Expand All @@ -70,8 +74,10 @@ def hamming74_syndrome(codewords):
if codewords.shape[1] != 7:
raise ValueError('每个 Hamming(7,4) 码字长度必须为 7')

# TODO: 计算 s = r H^T mod 2。
raise NotImplementedError('请实现伴随式计算')
# 计算 s = r H^T mod 2
syndromes = (codewords @ HAMMING_H.T) % 2
return syndromes
# raise NotImplementedError('请实现伴随式计算')


def hamming74_decode(received):
Expand All @@ -94,8 +100,28 @@ def hamming74_decode(received):
if received.ndim != 1 or len(received) % 7 != 0:
raise ValueError('received 必须是一维数组,长度为 7 的倍数')

# TODO: 使用 hamming74_syndrome 完成单比特纠错,并返回前 4 个信息位。
raise NotImplementedError('请实现 Hamming(7,4) 译码')
# 将 received reshape 为 (-1, 7),复制一份避免直接修改输入
codewords = received.reshape(-1, 7).copy()

# 调用 hamming74_syndrome 计算每个码字的伴随式
syndromes = hamming74_syndrome(codewords)

# 对每个非零伴随式,与 HAMMING_H 的 7 列逐列比较,定位并翻转错误位
for i in range(codewords.shape[0]):
syndrome = syndromes[i]
# 若伴随式非零,说明有错误
if not np.all(syndrome == 0):
# 与 HAMMING_H 的每一列比较,找到匹配列
for col_idx in range(HAMMING_H.shape[1]):
if np.array_equal(HAMMING_H[:, col_idx], syndrome):
# 翻转对应码字位置
codewords[i, col_idx] ^= 1
break

# 系统码的信息位为前 4 位,取每个码字前 4 位并 flatten 返回
decoded_bits = codewords[:, :4].flatten()
return decoded_bits
# raise NotImplementedError('请实现 Hamming(7,4) 译码')


def convolutional_encode(bits):
Expand All @@ -108,8 +134,32 @@ def convolutional_encode(bits):
if not np.all((bits == 0) | (bits == 1)):
raise ValueError('bits 只能包含 0 或 1')

# TODO: 选做任务,可参考课件第6章卷积码部分。
raise NotImplementedError('选做:请实现卷积码编码')
# 生成多项式 g1=111, g2=101
# g1 对应输出1: 当前位 + 前一位 + 前两位
# g2 对应输出2: 当前位 + 前两位

# 在末尾添加 2 个尾比特 0
bits_padded = np.concatenate([bits, [0, 0]])

encoded = []
# 移位寄存器初始状态为 [0, 0](存储前两位)
state = [0, 0]

for bit in bits_padded:
# 当前输入为 bit,寄存器状态为 [state[0], state[1]] = [前一位, 前两位]
# 输出1 = bit XOR state[0] XOR state[1] (g1=111)
out1 = (bit ^ state[0] ^ state[1]) & 1
# 输出2 = bit XOR state[1] (g2=101)
out2 = (bit ^ state[1]) & 1

encoded.append(out1)
encoded.append(out2)

# 更新移位寄存器:新状态 = [当前bit, 前一位]
state = [bit, state[0]]

return np.array(encoded, dtype=int)
# raise NotImplementedError('选做:请实现卷积码编码')


def viterbi_decode_hard(received_bits):
Expand All @@ -120,8 +170,75 @@ def viterbi_decode_hard(received_bits):
if len(received_bits) % 2 != 0:
raise ValueError('卷积码接收序列长度必须是 2 的倍数')

# TODO: 选做任务,可使用汉明距离作为路径度量。
raise NotImplementedError('选做:请实现 Viterbi 硬判决译码')
# (2,1,3) 卷积码,约束长度 K=3,状态数为 2^(K-1) = 4
# 状态用 2 位表示:[s1, s0],s1 为较新的位,s0 为较旧的位
# 状态转移:输入 0 -> 新状态 = [0, s1],输入 1 -> 新状态 = [1, s1]

num_states = 4 # 2^(3-1) = 4
# 将接收序列按每 2 位分组
symbols = received_bits.reshape(-1, 2)
num_steps = len(symbols)

# 初始化路径度量,状态 0 为 0,其他为无穷大
INF = float('inf')
path_metrics = [INF] * num_states
path_metrics[0] = 0

# 保存每个状态在每个时刻的前驱状态和输入比特
# trellis[state][step] = (prev_state, input_bit)
trellis = [[None for _ in range(num_steps)] for _ in range(num_states)]

# 状态转移表和输出表
# 状态 s = (s1 << 1) | s0,即 s1 是高位,s0 是低位
# 对于状态 [s1, s0],输入 u 时:
# 输出1 = u XOR s1 XOR s0
# 输出2 = u XOR s0
# 下一状态 = [u, s1]

for step in range(num_steps):
new_metrics = [INF] * num_states
for state in range(num_states):
if path_metrics[state] == INF:
continue
s1 = (state >> 1) & 1 # 高位
s0 = state & 1 # 低位

# 尝试输入 0 和 1
for input_bit in [0, 1]:
# 计算输出
out1 = (input_bit ^ s1 ^ s0) & 1
out2 = (input_bit ^ s0) & 1
output = [out1, out2]

# 计算汉明距离
hamming_dist = np.sum(symbols[step] != output)

# 下一状态
next_state = (input_bit << 1) | s1

# 更新路径度量
new_metric = path_metrics[state] + hamming_dist
if new_metric < new_metrics[next_state]:
new_metrics[next_state] = new_metric
trellis[next_state][step] = (state, input_bit)

path_metrics = new_metrics

# 回溯,从最后的状态 0 开始(因为有尾比特保证回到全零)
decoded = []
current_state = 0 # 尾比特使最终状态为 0

for step in range(num_steps - 1, -1, -1):
prev_state, input_bit = trellis[current_state][step]
decoded.append(input_bit)
current_state = prev_state

# 反转得到正确顺序
decoded = decoded[::-1]

# 去掉尾比特对应的 2 个译码输出(最后 2 位是尾比特 0 产生的)
return np.array(decoded[:-2], dtype=int)
# raise NotImplementedError('选做:请实现 Viterbi 硬判决译码')


def run_coding_demo():
Expand Down
Loading
Loading