2025年11月4日 星期二

BPSK 調變

 BPSK 調變

頂部為參數輸入區,您可以設定載波頻率 (Fc)位元速率 (Rb)總位元數Fc 必須遠大於 Rb 才能清晰地看出調變效果(建議 Fc >=  10 * Rb)。

程式將在 GUI 中顯示三個子圖:

    • 基帶訊號: 顯示原始的 NRZ 數位電平。

    • 載波訊號: 顯示純粹的餘弦波載波。

    • BPSK 調變訊號: 顯示相位隨著輸入資料的 0 和 1 而發生 180°  變化的最終訊號。

    • 基帶訊號: 原始的二進位資料(1 或 0)被轉換為 NRZ 形式(非歸零碼),即將 1 映射為 A(例如 +1),將 0 映射為 -A(例如 -1)。

    • BPSK 調變: 簡單地將 NRZ 基帶訊號與載波訊號相乘: 

    •     S(t) = d(t) cos(2*pi*F_c t) 

    •      d(t)=+1 時,輸出 cos(2*pi *F_c *t) 。

    •      當 $d(t)=-1 時,輸出  -cos(2*pi*F_c*t) = cos(2 *pi* F_c*t + pi)  (相位偏移 $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:
    # 優先使用微軟正黑體,若無則使用SimHei,最後嘗試Arial Unicode MS
    plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei', 'SimHei', 'Arial Unicode MS'] 
    plt.rcParams['axes.unicode_minus'] = False # 解決負號顯示為方塊的問題
except:
    # 失敗時不作處理,依賴系統預設字體
    pass
# -----------------------------------

class BPSKSimulator(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("BPSK (二進位相位偏移調變) 模擬器")
        self.geometry("800x650")
        
        # 預設參數
        self.default_bit_rate = 10  # 位元速率 (Rb)
        self.default_carrier_freq = 100 # 載波頻率 (Fc)
        self.total_bits = 5 
        
        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)
        
        # 創建 3 個子圖的 Matplotlib 圖表
        self.fig, self.axes = plt.subplots(3, 1, figsize=(7, 5), sharex=True)
        plt.subplots_adjust(hspace=0.5)
        
        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):
        """從 GUI 獲取參數,執行 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 調變
        t, data_signal, carrier_signal, bpsk_signal = self.bpsk_modulation(data_bits, Rb, Fc)
        
        # 3. 繪製結果
        self.plot_signals(t, data_signal, carrier_signal, bpsk_signal, data_bits)

    def bpsk_modulation(self, data_bits, Rb, Fc):
        """執行 BPSK 調變的邏輯。"""
        
        # 採樣率 (必須遠大於 Fc 和 Rb)
        Fs = 20 * Fc  # 採樣頻率是載波頻率的 20 倍
        if Fs < 100 * Rb:
            Fs = 100 * Rb # 確保有足夠的採樣點

        T_bit = 1 / Rb  # 位元週期
        T_total = len(data_bits) * T_bit  # 總時間
        
        # 時間向量
        t = np.arange(0, T_total, 1/Fs)
        
        # 1. 產生 NRZ 數位基帶訊號 (將 0 映射為 -1,1 映射為 +1)
        data_nrz = 2 * data_bits - 1
        data_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)
            data_signal[start_sample:end_sample] = bit
            
        # 2. 產生載波訊號 (Carrier Signal)
        carrier_signal = np.cos(2 * np.pi * Fc * t)
        
        # 3. 產生 BPSK 訊號
        # BPSK = 基帶訊號 * 載波訊號
        bpsk_signal = data_signal * carrier_signal
        
        return t, data_signal, carrier_signal, bpsk_signal

    def plot_signals(self, t, data_signal, carrier_signal, bpsk_signal, data_bits):
        """繪製三個訊號波形。"""
        
        # 清除舊圖
        for ax in self.axes:
            ax.clear()
            
        # --- 繪製基帶訊號 ---
        self.axes[0].plot(t, data_signal, 'r-')
        self.axes[0].set_title(f"1. 基帶訊號 (NRZ, {len(data_bits)} bits)", fontsize=10)
        self.axes[0].set_ylabel("振幅", fontsize=8)
        self.axes[0].grid(True, linestyle='--')
        self.axes[0].set_yticks([-1, 0, 1])
        
        # --- 繪製載波訊號 ---
        self.axes[1].plot(t, carrier_signal, 'b-')
        self.axes[1].set_title(f"2. 載波訊號 (Fc={self.fc_entry.get()} Hz)", fontsize=10)
        self.axes[1].set_ylabel("振幅", fontsize=8)
        self.axes[1].grid(True, linestyle='--')

        # --- 繪製 BPSK 訊號 ---
        self.axes[2].plot(t, bpsk_signal, 'g-')
        self.axes[2].set_title("3. BPSK 調變訊號", fontsize=10)
        self.axes[2].set_xlabel("時間 (秒)", fontsize=8)
        self.axes[2].set_ylabel("振幅", fontsize=8)
        self.axes[2].grid(True, linestyle='--')
        
        # 重新繪製 canvas
        self.fig.tight_layout()
        self.canvas.draw()


if __name__ == "__main__":
    app = BPSKSimulator()
    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...