点击查看代码
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib.gridspec as gridspec
from matplotlib.patches import Rectangle, Circle
import random
matplotlib.use('TkAgg')
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用于显示中文
plt.rcParams['axes.unicode_minus'] = False # 用于显示负号
# 创建图形和布局
fig = plt.figure(figsize=(14, 10), facecolor='#0f0f1a')
gs = gridspec.GridSpec(3, 2, height_ratios=[1, 1, 1])
# 创建子图
ax_qpsk = fig.add_subplot(gs[0, 0])
ax_16qam = fig.add_subplot(gs[0, 1])
ax_bits = fig.add_subplot(gs[1, :])
ax_info = fig.add_subplot(gs[2, :])
# 设置整体背景色
for ax in [ax_qpsk, ax_16qam, ax_bits, ax_info]:
ax.set_facecolor('#0f0f1a')
ax.tick_params(colors='white')
for spine in ax.spines.values():
spine.set_color('white')
# 设置QPSK星座图
ax_qpsk.set_title('QPSK 调制星座图', color='cyan', fontsize=14, pad=15)
ax_qpsk.set_xlim(-1.5, 1.5)
ax_qpsk.set_ylim(-1.5, 1.5)
ax_qpsk.grid(True, alpha=0.3, color='gray')
ax_qpsk.axhline(y=0, color='white', alpha=0.5)
ax_qpsk.axvline(x=0, color='white', alpha=0.5)
ax_qpsk.set_xlabel('同相分量 (I)', color='white')
ax_qpsk.set_ylabel('正交分量 (Q)', color='white')
ax_qpsk.set_aspect('equal', adjustable='datalim')
# 绘制QPSK星座点
qpsk_points = [(-1, -1), (-1, 1), (1, -1), (1, 1)]
for i, point in enumerate(qpsk_points):
color = ['#FF6666', '#66FF66', '#6666FF', '#FF66FF'][i]
ax_qpsk.scatter(*point, s=150, color=color, edgecolor='white', zorder=10)
ax_qpsk.text(point[0] + 0.1, point[1] - 0.1, f"{i:02b}", color='white', fontsize=10)
# 设置16QAM星座图
ax_16qam.set_title('16QAM 调制星座图', color='yellow', fontsize=14, pad=15)
ax_16qam.set_xlim(-4, 4)
ax_16qam.set_ylim(-4, 4)
ax_16qam.grid(True, alpha=0.3, color='gray')
ax_16qam.axhline(y=0, color='white', alpha=0.5)
ax_16qam.axvline(x=0, color='white', alpha=0.5)
ax_16qam.set_xlabel('同相分量 (I)', color='white')
ax_16qam.set_ylabel('正交分量 (Q)', color='white')
ax_16qam.set_aspect('equal', adjustable='datalim')
# 绘制16QAM星座点
qam_points = [(-3, -3), (-3, -1), (-3, 1), (-3, 3),
(-1, -3), (-1, -1), (-1, 1), (-1, 3),
(1, -3), (1, -1), (1, 1), (1, 3),
(3, -3), (3, -1), (3, 1), (3, 3)]
colors = ['#FF6666', '#FF9966', '#FFCC66', '#FFFF66',
'#99FF66', '#66FF66', '#66FF99', '#66FFCC',
'#66CCFF', '#6699FF', '#6666FF', '#9966FF',
'#CC66FF', '#FF66FF', '#FF66CC', '#FF6699']
for i, point in enumerate(qam_points):
ax_16qam.scatter(*point, s=100, color=colors[i], edgecolor='white', zorder=10)
ax_16qam.text(point[0] - 0.25, point[1] - 0.35, f"{i:04b}", color='white', fontsize=8)
# 设置比特流显示
ax_bits.set_title('二进制比特流', color='#FF8C00', fontsize=14, pad=15)
ax_bits.set_xlim(0, 20)
ax_bits.set_ylim(0, 6)
ax_bits.set_xticks([])
ax_bits.set_yticks([])
ax_bits.text(0.5, 5.5, "当前传输比特:", color='cyan', fontsize=12, ha='left')
# 设置信息显示区域
ax_info.set_axis_off()
info_text = ax_info.text(0.02, 0.8, "", color='white', fontsize=12, ha='left', va='top',
transform=ax_info.transAxes)
# 调制信息
mod_info = [
"调制技术说明:",
"• QPSK (Quadrature Phase Shift Keying): 四相相移键控",
" - 每个符号传输 2 比特",
" - 4 个星座点分布在单位圆的四相上",
" - 抗噪声性能中等,频谱效率较高",
"",
"• 16QAM (16-Quadrature Amplitude Modulation): 正交振幅调制",
" - 每个符号传输 4 比特",
" - 16 个星座点分布在网格上",
" - 高频谱效率,但抗噪声性能较弱"
]
# 添加调制原理说明
for i, text in enumerate(mod_info):
color = 'white' if len(text) < 2 or text[1] == ' ' else '#FF9999' if 'QPSK' in text else '#99FF99'
ax_info.text(0.02, 0.65 - i * 0.075, text, color=color, fontsize=11, ha='left')
# 添加动画控制按钮
button_ax = fig.add_axes([0.75, 0.05, 0.15, 0.05])
button = Rectangle((0, 0), 1, 1, facecolor='#3366CC', edgecolor='white')
button_ax.add_patch(button)
button_ax.text(0.5, 0.5, "暂停/继续", color='white', ha='center', va='center', fontsize=11)
button_ax.set_axis_off()
# 初始化变量
current_symbol = 0
symbol_history = []
qpsk_lines = []
qam_lines = []
bit_texts = []
paused = False
# 初始比特流显示
bit_positions = []
for i in range(16):
x = 0.5 + i
y = 3.5
rect = Rectangle((x, y), 0.8, 0.8, facecolor='#222233', edgecolor='#555555')
ax_bits.add_patch(rect)
bit_text = ax_bits.text(x + 0.4, y + 0.4, "", color='white', ha='center', va='center', fontsize=11)
bit_texts.append(bit_text)
bit_positions.append((x + 0.4, y + 0.4))
# 动画更新函数
def update(frame):
global current_symbol, symbol_history, paused
if paused:
return
# 生成随机比特
bit_value = random.randint(0, 15)
symbol_history.append(bit_value)
# 更新比特流显示
for i in range(16):
if i < len(symbol_history):
bit_str = f"{symbol_history[i]:04b}"
bit_texts[i].set_text(bit_str)
# 高亮当前比特
if i == current_symbol:
bit_texts[i].set_color('cyan')
bit_texts[i].set_fontweight('bold')
else:
bit_texts[i].set_color('white')
bit_texts[i].set_fontweight('normal')
else:
bit_texts[i].set_text("")
# 解码比特
bits = f"{bit_value:04b}"
# QPSK映射 (使用第一个2比特)
qpsk_bit = bits[:2]
qpsk_index = int(qpsk_bit, 2)
qx, qy = qpsk_points[qpsk_index]
# 16QAM映射 (使用全部4比特)
qam_index = bit_value
qamx, qamy = qam_points[qam_index]
# 清除旧线条
for line in qpsk_lines:
line.remove()
qpsk_lines.clear()
for line in qam_lines:
line.remove()
qam_lines.clear()
# 绘制新线条
line_qpsk, = ax_qpsk.plot([0, qx], [0, qy], 'w-', linewidth=1.5, alpha=0.8)
line_qam, = ax_16qam.plot([0, qamx], [0, qamy], 'w-', linewidth=1.5, alpha=0.8)
qpsk_lines.append(line_qpsk)
qam_lines.append(line_qpsk)
# 绘制点
point_qpsk = ax_qpsk.scatter(qx, qy, s=100, color=colors[qam_index], edgecolor='white', zorder=10)
point_qam = ax_16qam.scatter(qamx, qamy, s=100, color=colors[qam_index], edgecolor='white', zorder=10)
qpsk_lines.append(point_qpsk)
qam_lines.append(point_qam)
# 更新信息
info = f"当前符号: {current_symbol}\n"
info += f"二进制比特: {bits}\n"
info += f"QPSK映射: {qpsk_bit} → 星座点 ({qx}, {qy})\n"
info += f"16QAM映射: {bits} → 星座点 ({qamx}, {qamy})\n"
info += f"传输比特总数: {4 * len(symbol_history)}"
info_text.set_text(info)
current_symbol = (current_symbol + 1) % 16
# 点击事件处理
def on_click(event):
global paused
if event.inaxes == button_ax:
paused = not paused
fig.canvas.mpl_connect('button_press_event', on_click)
# 创建动画
ani = FuncAnimation(fig, update, interval=1000, cache_frame_data=False)
# 添加标题
fig.suptitle('QPSK vs 16QAM 星座点跃迁过程', color='white', fontsize=18, y=0.97)
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
plt.subplots_adjust(hspace=0.25)
plt.show()