2025年11月4日 星期二

BPSK

 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()


沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...