BPSK
BPSK 理論基礎:
BPSK 訊號可以表示為 S(t) = I(t) *cos(ωc*t) + Q(t) *sin(ωc*t) 。
對於 BPSK,只有 I (同相) 分量攜帶數據 (I(t) =±1),而 Q (正交) 分量始終為 0 (Q(t)=0)。
時域波形圖 (axes[0])
顯示了基帶訊號(即 I 分量 ±1)和調變後的 BPSK 訊號。
邏輯 1 (Φ=0°): BPSK 訊號與載波同相。
邏輯 0 (Φ=180°): BPSK 訊號與載波反相。
星座圖 (axes[1]):
軸線: X 軸代表 I (同相) 分量,Y 軸代表 Q (正交) 分量。
訊號點:
當輸入資料為 1 時,I=+1, Q=0,點位在 (1, 0) 處。
當輸入資料為 0 時,I=-1, Q=0,點位在 (-1, 0) 處。
BPSK 星座圖上的兩個點位完美地位於 I 軸上,彼此間隔 180 °的相位。
import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.font_manager as fm
# --- Matplotlib 中文字體設定 ---
try:
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei', 'SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
except:
pass
# -----------------------------------
class BPSKConstellationSimulator(tk.Tk):
def __init__(self):
super().__init__()
self.title("BPSK 時域波形與星座圖模擬器")
self.geometry("850x700")
# 預設參數
self.default_bit_rate = 10 # 10 bits/sec
self.default_carrier_freq = 100 # 100 Hz
self.total_bits = 10 # 增加位元數使星座圖點位更豐富
self.create_widgets()
def create_widgets(self):
# --- 參數輸入框架 ---
param_frame = ttk.LabelFrame(self, text="調變參數設置", padding="10")
param_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=5)
# 載波頻率
ttk.Label(param_frame, text="載波頻率 (Hz, Fc):").grid(row=0, column=0, padx=5, pady=5, sticky='w')
self.fc_entry = ttk.Entry(param_frame, width=10)
self.fc_entry.insert(0, str(self.default_carrier_freq))
self.fc_entry.grid(row=0, column=1, padx=5, pady=5, sticky='w')
# 位元速率
ttk.Label(param_frame, text="位元速率 (bits/s, Rb):").grid(row=0, column=2, padx=5, pady=5, sticky='w')
self.rb_entry = ttk.Entry(param_frame, width=10)
self.rb_entry.insert(0, str(self.default_bit_rate))
self.rb_entry.grid(row=0, column=3, padx=5, pady=5, sticky='w')
# 總位元數
ttk.Label(param_frame, text="總位元數:").grid(row=1, column=0, padx=5, pady=5, sticky='w')
self.bits_entry = ttk.Entry(param_frame, width=10)
self.bits_entry.insert(0, str(self.total_bits))
self.bits_entry.grid(row=1, column=1, padx=5, pady=5, sticky='w')
# 隨機資料按鈕
ttk.Button(param_frame, text="生成隨機資料並模擬", command=self.run_simulation).grid(row=2, column=0, columnspan=4, pady=10)
# 顯示當前資料序列
self.data_label = ttk.Label(param_frame, text="當前資料序列: N/A", font=('Arial', 10, 'italic'))
self.data_label.grid(row=3, column=0, columnspan=4, pady=5)
# --- 視覺化框架 (Matplotlib) ---
plot_frame = ttk.Frame(self)
plot_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=10, pady=5)
# 創建兩個子圖:一個用於波形,一個用於星座圖
self.fig, self.axes = plt.subplots(2, 1, figsize=(7, 6))
plt.subplots_adjust(hspace=0.4)
self.canvas = FigureCanvasTkAgg(self.fig, master=plot_frame)
self.canvas_widget = self.canvas.get_tk_widget()
self.canvas_widget.pack(fill=tk.BOTH, expand=True)
def run_simulation(self):
"""執行 BPSK 調變並繪製波形圖和星座圖。"""
try:
Rb = float(self.rb_entry.get())
Fc = float(self.fc_entry.get())
num_bits = int(self.bits_entry.get())
if Rb <= 0 or Fc <= 0 or num_bits <= 0:
raise ValueError("所有參數必須為正數。")
except ValueError as e:
messagebox.showerror("輸入錯誤", f"請輸入有效的數值:{e}")
return
# 1. 產生隨機二進位資料
data_bits = np.random.randint(0, 2, num_bits)
data_str = "".join(map(str, data_bits))
self.data_label.config(text=f"當前資料序列: {data_str}")
# 2. BPSK 調變與 I/Q 數據生成
t, baseband_signal, bpsk_signal, I_data, Q_data = self.bpsk_processing(data_bits, Rb, Fc)
# 3. 繪製結果
self.plot_signals(t, baseband_signal, bpsk_signal, I_data, Q_data)
def bpsk_processing(self, data_bits, Rb, Fc):
"""執行 BPSK 調變,並產生 I/Q 分量數據。"""
# 採樣率 Fs
Fs = 20 * Fc
if Fs < 100 * Rb:
Fs = 100 * Rb
T_bit = 1 / Rb # 位元週期
T_total = len(data_bits) * T_bit # 總時間
N = int(T_total * Fs)
t = np.linspace(0, T_total, N, endpoint=False)
# 1. 產生 NRZ 數位基帶訊號 (0 -> -1, 1 -> +1)
data_nrz = 2 * data_bits - 1
baseband_signal = np.zeros_like(t)
for i, bit in enumerate(data_nrz):
start_sample = int(i * T_bit * Fs)
end_sample = int((i + 1) * T_bit * Fs)
baseband_signal[start_sample:end_sample] = bit
# 2. BPSK 調變 (僅有 I 分量,Q 分量為零)
# BPSK 訊號 S(t) = I(t) * cos(2πFct) + Q(t) * sin(2πFct)
# 在 BPSK 中,I(t) = data_nrz, Q(t) = 0
bpsk_signal = baseband_signal * np.cos(2 * np.pi * Fc * t)
# 3. 星座圖數據 (I/Q 分量)
# 提取每個位元週期內的 I/Q 抽樣點
I_data = []
Q_data = []
for bit_value in data_nrz:
# 理想 BPSK: I = ±1, Q = 0
I_data.append(bit_value)
Q_data.append(0) # 理想 BPSK Q 分量永遠為 0
return t, baseband_signal, bpsk_signal, np.array(I_data), np.array(Q_data)
def plot_signals(self, t, baseband_signal, bpsk_signal, I_data, Q_data):
"""繪製波形圖和星座圖。"""
# 清除舊圖
for ax in self.axes:
ax.clear()
# --- 1. 時域波形圖 ---
# 繪製基帶訊號
self.axes[0].plot(t, baseband_signal, 'r-', linewidth=1, label='基帶訊號 (I分量)')
# 繪製調變訊號
self.axes[0].plot(t, bpsk_signal, 'g-', alpha=0.6, label='BPSK 訊號')
self.axes[0].set_title("時域波形: 基帶訊號與 BPSK 訊號", fontsize=12)
self.axes[0].set_xlabel("時間 (秒)", fontsize=10)
self.axes[0].set_ylabel("振幅", fontsize=10)
self.axes[0].grid(True, linestyle='--')
self.axes[0].set_yticks([-1, 0, 1])
self.axes[0].legend(fontsize=8)
# --- 2. 星座圖 ---
# 繪製星座點
# BPSK 點位在 I 軸上 (Q=0)
self.axes[1].scatter(I_data, Q_data, marker='o', color='blue', s=80, edgecolors='black', label='訊號點')
# 連接 I/Q 軸的原點,繪製參考線
self.axes[1].axhline(0, color='gray', linestyle='-')
self.axes[1].axvline(0, color='gray', linestyle='-')
self.axes[1].set_title("BPSK 星座圖 (I-Q 平面)", fontsize=12)
self.axes[1].set_xlabel("I (同相分量)", fontsize=10)
self.axes[1].set_ylabel("Q (正交分量)", fontsize=10)
# 確保 I/Q 軸比例一致,並居中
max_val = max(np.abs(I_data.max()), np.abs(I_data.min())) + 0.2
self.axes[1].set_xlim(-max_val, max_val)
self.axes[1].set_ylim(-max_val, max_val)
self.axes[1].set_aspect('equal', adjustable='box') # 確保I/Q軸等比例
self.axes[1].grid(True, linestyle='--')
self.axes[1].legend(fontsize=8)
# 重新繪製 canvas
self.fig.tight_layout()
self.canvas.draw()
if __name__ == "__main__":
app = BPSKConstellationSimulator()
app.mainloop()





沒有留言:
張貼留言