2025年11月4日 星期二

副載波負載調變、曼徹斯特編碼

 (副載波負載調變、曼徹斯特編碼、副載波頻率 $13.56 MHz/16)是 ISO/IEC 14443 Type A 非接觸式通訊協定中,卡片(PICC)回傳資料給讀寫器(PCD,例如 MFRC522)的關鍵技術細節



import tkinter as tk

from tkinter import ttk

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

# -----------------------------------


# 常數

F_CARRIER = 13.56e6  # 13.56 MHz

DIVIDER = 16

F_SUB = F_CARRIER / DIVIDER  # 副載波頻率: 847.5 kHz

R_B = 106e3          # 位元速率: 106 kBd


class ManchesterSubcarrierSimulator(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("ISO 14443A 曼徹斯特副載波調變模擬")

        self.geometry("800x550")

        

        # 參數設置

        self.num_bits = 4 

        

        self.create_widgets()


    def create_widgets(self):

        # --- 頂部控制區 ---

        control_frame = ttk.Frame(self, padding="10")

        control_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=5)

        

        ttk.Label(control_frame, text=f"副載波頻率 (Fsub): {F_SUB/1e3:.1f} KHz").pack(side=tk.LEFT, padx=10)

        ttk.Label(control_frame, text=f"位元速率 (Rb): {R_B/1e3:.0f} kBd").pack(side=tk.LEFT, padx=10)

        

        ttk.Button(control_frame, text="模擬並繪製訊號", command=self.run_simulation).pack(side=tk.RIGHT, padx=10)


        self.data_label = ttk.Label(control_frame, text="當前資料序列: N/A", font=('Arial', 10, 'italic'))

        self.data_label.pack(side=tk.LEFT, padx=10)


        # --- 繪圖區 (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(3, 1, figsize=(7, 4), sharex=True)

        plt.subplots_adjust(hspace=0.6)

        

        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)

        

        # 初始繪圖

        self.run_simulation() 


    def manchester_encoding(self, data_bits, Rb, Fsub):

        """

        模擬曼徹斯特編碼下的副載波訊號。

        訊號 S(t) = m(t) * cos(2*pi*Fsub*t)

        其中 m(t) 為曼徹斯特編碼後的 NRZ 訊號 (±1)。

        """

        

        # 採樣率 Fs 需遠高於 Fsub

        Fs = 20 * Fsub 

        

        T_bit = 1 / Rb       # 位元週期

        T_total = len(data_bits) * T_bit  # 總時間

        

        # 時間向量

        t = np.arange(0, T_total, 1/Fs)

        

        # 1. 產生曼徹斯特基帶訊號 m(t)

        manchester_baseband = np.zeros_like(t)

        

        for i, bit in enumerate(data_bits):

            t_start = i * T_bit

            t_mid = t_start + T_bit / 2

            t_end = (i + 1) * T_bit

            

            # 找到對應時間區間的索引

            idx_start = int(i * T_bit * Fs)

            idx_mid = int((i * T_bit + T_bit / 2) * Fs)

            idx_end = int((i + 1) * T_bit * Fs)


            # 曼徹斯特編碼規則:

            if bit == 1:

                # 邏輯 1: Low -> High (+1) (訊號從低到高轉換)

                manchester_baseband[idx_start:idx_mid] = -1 

                manchester_baseband[idx_mid:idx_end] = 1

            else:

                # 邏輯 0: High -> Low (-1) (訊號從高到低轉換)

                manchester_baseband[idx_start:idx_mid] = 1

                manchester_baseband[idx_mid:idx_end] = -1

                

        # 2. 產生副載波訊號

        subcarrier = np.cos(2 * np.pi * Fsub * t)

        

        # 3. 負載調變訊號 (模擬卡片訊號)

        # 這是副載波訊號與曼徹斯特基帶訊號的乘積 (等同於 BPSK)

        load_modulation_signal = manchester_baseband * subcarrier

        

        return t, manchester_baseband, subcarrier, load_modulation_signal, T_bit


    def run_simulation(self):

        """執行模擬並繪圖。"""

        

        # 產生隨機二進位資料

        data_bits = np.random.randint(0, 2, self.num_bits)

        data_str = "".join(map(str, data_bits))

        self.data_label.config(text=f"當前資料序列 (Manchester): {data_str}")

        

        t, baseband, subcarrier, modulated_signal, T_bit = self.manchester_encoding(data_bits, R_B, F_SUB)

        

        # --- 繪圖 ---

        for ax in self.axes:

            ax.clear()

            

        # 1. 原始位元流

        # 繪製原始位元流 (僅為示意,非實際發射訊號)

        digital_data = np.zeros_like(t)

        for i, bit in enumerate(data_bits):

            idx_start = int(i * T_bit * (F_SUB * 20))

            idx_end = int((i + 1) * T_bit * (F_SUB * 20))

            digital_data[idx_start:idx_end] = bit

            

        self.axes[0].plot(t, digital_data, 'k-', linewidth=2)

        self.axes[0].set_title(f"1. 原始數位資料 ({self.num_bits} bits)", fontsize=10)

        self.axes[0].set_yticks([0, 1])

        self.axes[0].grid(True, linestyle='--')

        

        # 2. 曼徹斯特基帶訊號

        self.axes[1].plot(t, baseband, 'r-', linewidth=1.5)

        self.axes[1].set_title("2. 曼徹斯特基帶編碼訊號", fontsize=10)

        self.axes[1].set_ylabel("電平 (±1)", fontsize=8)

        self.axes[1].set_yticks([-1, 0, 1])

        self.axes[1].grid(True, linestyle='--')


        # 3. 調變後的副載波訊號

        self.axes[2].plot(t, modulated_signal, 'g-')

        self.axes[2].set_title("3. 調變後的副載波訊號 (卡片發射訊號)", fontsize=10)

        self.axes[2].set_xlabel("時間 (秒)", fontsize=8)

        self.axes[2].set_ylabel("振幅", fontsize=8)

        self.axes[2].set_ylim(-1.5, 1.5)

        self.axes[2].grid(True, linestyle='--')

        

        # 標記位元週期

        for i in range(self.num_bits + 1):

            time_mark = i * T_bit

            for ax in self.axes:

                 ax.axvline(time_mark, color='gray', linestyle=':', linewidth=0.5)


        # 重新繪製 canvas

        self.fig.tight_layout()

        self.canvas.draw()



if __name__ == "__main__":

    app = ManchesterSubcarrierSimulator()

    app.mainloop()


  • 曼徹斯特基帶訊號 (m(t)):

    • 顯示了資料位元 0 和 1 如何被轉換為高低電平的波形。

    • 邏輯 1 造成訊號從 -1 轉換到 +1

    • 邏輯 0 造成訊號從 +1 轉換到 -1

    • 訊號電平在位元週期的中點處發生跳變。

  • 副載波調變訊號:

    • 這是卡片實際發射的訊號(曼徹斯特基帶 $\times$ 副載波)。

    • 這個波形的頻率F_sub (847.5  KHz)。

    • 相位轉換點:每當曼徹斯特基帶訊號 m(t) 從+1 變成 -1 或反之時,調變訊號的相位就會反轉 180°。這是一種特殊的 BPSK (二進位相位偏移調變),但訊號是發生在副載波上。

  • 曼徹斯特編碼對相位調變的影響 (放大):

    • 將時間軸放大到前兩個位元,更清楚地展示在位元週期中點處,副載波訊號如何因為曼徹斯特編碼的電平跳變而導致相位發生突變。



  • import numpy as np

    import matplotlib.pyplot as plt

    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

    # -----------------------------------


    # 系統參數 (ISO/IEC 14443 A 106 kBd)

    F_CARRIER = 13.56e6  # 讀寫器載波頻率 (13.56 MHz)

    DIVIDER = 16

    F_SUB = F_CARRIER / DIVIDER  # 副載波頻率: 847.5 KHz (847500 Hz)

    R_B = 106e3          # 位元速率: 106 kBd


    # 模擬參數

    NUM_BITS = 5         # 模擬的位元數

    Fs = 20 * F_SUB      # 採樣率 (必須遠高於副載波頻率)

    T_bit = 1 / R_B      # 位元週期


    def manchester_encoding_and_modulation(data_bits, Rb, Fsub, Fs):

        """模擬曼徹斯特編碼和副載波調變"""

        

        T_total = len(data_bits) * T_bit 

        t = np.arange(0, T_total, 1/Fs)

        

        # 1. 曼徹斯特基帶訊號 (m(t))

        manchester_baseband = np.zeros_like(t)

        for i, bit in enumerate(data_bits):

            # 找到位元週期內的採樣索引

            idx_start = int(i * T_bit * Fs)

            idx_mid = int((i * T_bit + T_bit / 2) * Fs)

            idx_end = int((i + 1) * T_bit * Fs)


            if bit == 1:

                # 邏輯 1: Low (-1) -> High (+1)

                manchester_baseband[idx_start:idx_mid] = -1 

                manchester_baseband[idx_mid:idx_end] = 1

            else:

                # 邏輯 0: High (+1) -> Low (-1)

                manchester_baseband[idx_start:idx_mid] = 1

                manchester_baseband[idx_mid:idx_end] = -1

                

        # 2. 副載波訊號

        subcarrier = np.cos(2 * np.pi * Fsub * t)

        

        # 3. 負載調變訊號 (等效於曼徹斯特基帶與副載波的相乘)

        load_modulation_signal = manchester_baseband * subcarrier

        

        return t, manchester_baseband, load_modulation_signal


    # --- 執行模擬 ---

    # 隨機產生資料:例如 [1, 0, 1, 1, 0]

    data_bits = np.random.randint(0, 2, NUM_BITS)

    t, baseband_signal, modulated_signal = manchester_encoding_and_modulation(data_bits, R_B, F_SUB, Fs)


    # --- Matplotlib 視覺化說明 ---

    fig, axes = plt.subplots(3, 1, figsize=(10, 6), sharex=True)

    plt.subplots_adjust(hspace=0.6)


    # 1. 曼徹斯特基帶編碼 (說明編碼規則)

    axes[0].plot(t, baseband_signal, 'r-', linewidth=1.5)

    axes[0].set_title(f"1. 曼徹斯特基帶訊號 (資料: {''.join(map(str, data_bits))})", fontsize=12)

    axes[0].set_ylabel("電平 (±1)", fontsize=10)

    axes[0].set_yticks([-1, 0, 1])

    axes[0].grid(True, linestyle='--')

    axes[0].axhline(0, color='gray', linewidth=0.5)


    # 2. 調變後的副載波訊號 (說明調變結果)

    axes[1].plot(t, modulated_signal, 'g-', linewidth=1)

    axes[1].set_title(f"2. 副載波調變訊號 (Fsub={F_SUB/1e3:.1f} KHz)", fontsize=12)

    axes[1].set_ylabel("振幅", fontsize=10)

    axes[1].set_ylim(-1.5, 1.5)

    axes[1].grid(True, linestyle='--')


    # 3. 關鍵機制說明:相位反轉

    axes[2].plot(t, modulated_signal, 'g-', linewidth=1)

    axes[2].set_title("3. 曼徹斯特編碼對相位調變的影響 (放大)", fontsize=12)

    axes[2].set_xlabel("時間 (秒)", fontsize=10)

    axes[2].set_ylabel("振幅", fontsize=10)

    axes[2].set_ylim(-1.5, 1.5)

    axes[2].grid(True, linestyle='--')


    # 標記位元週期和相位轉換點

    for i in range(NUM_BITS + 1):

        time_mark = i * T_bit

        for ax in axes:

            ax.axvline(time_mark, color='k', linestyle=':', linewidth=0.8) # 位元邊界

        

        # 在第一個位元週期的中間標記 (例如在 0.05 秒處)

        if i < NUM_BITS:

            axes[1].text(time_mark + T_bit / 2, 1.3, f"bit {i}", ha='center', fontsize=8, color='purple')


    # 縮放第三個圖表以聚焦於第一個位元的轉換

    axes[2].set_xlim(0, T_bit * 2) 

    axes[2].axvline(T_bit / 2, color='orange', linestyle='--', linewidth=1)

    axes[2].text(T_bit / 2, -1.3, "相位反轉 (由曼徹斯特電平轉換引起)", ha='center', fontsize=9, color='orange')



    plt.suptitle("ISO 14443A 106 kBd 副載波負載調變與曼徹斯特編碼", fontsize=14, y=1.02)

    plt.show()




    頻域分析



    import numpy as np

    import matplotlib.pyplot as plt

    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

    # -----------------------------------


    # 系統參數 (ISO/IEC 14443 A 106 kBd)

    F_CARRIER = 13.56e6  # 讀寫器載波頻率 (13.56 MHz)

    DIVIDER = 16

    F_SUB = F_CARRIER / DIVIDER  # 副載波頻率: 847.5 KHz (847500 Hz)

    R_B = 106e3          # 位元速率: 106 kBd

    T_bit = 1 / R_B      # 位元週期


    # 模擬參數

    NUM_BITS = 10        # 增加位元數以獲得更平滑的頻譜

    Fs_ratio = 20        # 採樣頻率與副載波頻率的比值

    Fs = Fs_ratio * F_SUB # 採樣率 


    def manchester_encoding_and_modulation(data_bits, Rb, Fsub, Fs):

        """模擬曼徹斯特編碼和副載波調變"""

        

        T_total = len(data_bits) * T_bit 

        # 確保總採樣點是 2 的冪次方,以便 FFT 更有效率,雖然 Numpy FFT 不強制要求

        N = int(T_total * Fs)

        # 重新計算時間向量,確保採樣點數正確

        t = np.linspace(0, T_total, N, endpoint=False)

        

        # 1. 曼徹斯特基帶訊號 (m(t))

        manchester_baseband = np.zeros_like(t)

        for i, bit in enumerate(data_bits):

            idx_start = int(i * T_bit * Fs)

            idx_mid = int((i * T_bit + T_bit / 2) * Fs)

            idx_end = int((i + 1) * T_bit * Fs)


            if bit == 1:

                manchester_baseband[idx_start:idx_mid] = -1 

                manchester_baseband[idx_mid:idx_end] = 1

            else:

                manchester_baseband[idx_start:idx_mid] = 1

                manchester_baseband[idx_mid:idx_end] = -1

                

        # 2. 副載波訊號

        subcarrier = np.cos(2 * np.pi * Fsub * t)

        

        # 3. 負載調變訊號

        load_modulation_signal = manchester_baseband * subcarrier

        

        return t, manchester_baseband, load_modulation_signal, N


    def plot_frequency_domain(signal, N, Fs, ax):

        """計算並繪製訊號的單邊頻譜。"""

        

        # 執行 FFT

        Y = np.fft.fft(signal)

        

        # 計算頻率軸

        f = np.fft.fftfreq(N, 1/Fs)

        

        # 轉換為單邊頻譜 (僅取正頻率部分)

        Y_single_sided = 2.0 / N * np.abs(Y[:N//2])

        f_single_sided = f[:N//2]

        

        # 繪製頻譜 (以 KHz 為單位)

        ax.plot(f_single_sided / 1e3, Y_single_sided, 'b-')

        

        # 標記副載波頻率

        ax.axvline(F_SUB / 1e3, color='r', linestyle='--', label=f'副載波 Fsub={F_SUB/1e3:.1f} KHz')

        

        # 標記邊帶位置 (曼徹斯特編碼的第一個零點在 Rb 處, 頻譜在 Fsub ± Rb 附近)

        ax.axvline((F_SUB + R_B) / 1e3, color='g', linestyle=':', label=f'邊帶上限 (+Rb)')

        ax.axvline((F_SUB - R_B) / 1e3, color='g', linestyle=':', label=f'邊帶下限 (-Rb)')


        ax.set_title("負載調變訊號的頻域頻譜", fontsize=12)

        ax.set_xlabel("頻率 (KHz)", fontsize=10)

        ax.set_ylabel("歸一化振幅 (dB 刻度更常用)", fontsize=10)

        ax.set_xlim(0, F_SUB / 1e3 * 2) # 顯示到副載波的兩倍頻率

        ax.legend(fontsize=8)

        ax.grid(True, linestyle='--')



    # --- 執行模擬和繪圖 ---

    # 隨機產生資料

    data_bits = np.random.randint(0, 2, NUM_BITS)

    t, baseband_signal, modulated_signal, N = manchester_encoding_and_modulation(data_bits, R_B, F_SUB, Fs)


    fig, axes = plt.subplots(2, 1, figsize=(10, 8))

    plt.subplots_adjust(hspace=0.4)


    # --- 1. 時域圖 ---

    axes[0].plot(t * 1e6, modulated_signal, 'g-', linewidth=1)

    axes[0].set_title(f"時域波形: 曼徹斯特負載調變訊號 ({NUM_BITS} bits)", fontsize=12)

    axes[0].set_xlabel("時間 (微秒 μs)", fontsize=10)

    axes[0].set_ylabel("振幅", fontsize=10)

    axes[0].grid(True, linestyle='--')


    # 標記位元邊界 (用微秒標記更清晰)

    for i in range(NUM_BITS + 1):

        axes[0].axvline(i * T_bit * 1e6, color='k', linestyle=':', linewidth=0.8)


    # --- 2. 頻域圖 ---

    plot_frequency_domain(modulated_signal, N, Fs, axes[1])


    plt.suptitle(f"ISO 14443A 106 kBd 頻域分析 (Fsub={F_SUB/1e3:.1f} KHz)", fontsize=14, y=0.95)

    plt.show()


    沒有留言:

    張貼留言

    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...