2026年5月18日 星期一

模擬 NFC Type A (106 kbit/s) 標準中的 ASK 主動負載調變 (Active Load Modulation, ALM)

模擬 NFC Type A (106 kbit/s) 標準中的 ASK 主動負載調變 (Active Load Modulation, ALM)



這是一個非常適合用 Python tkinter 結合 matplotlib 來製作的動態或靜態波形模擬。

在底下的程式碼中,我使用 matplotlib 來精準繪製這三個信號(載波、副載波、基頻信號)以及最終調變後的 主動負載調變(Active Load Modulation, ALM) 波形,並將圖表嵌入到 tkinter 的視窗介面中。

核心邏輯說明

  1. 基頻信號 (BS):使用曼徹斯特編碼(Manchester Code),1 個位元時間約為 (即 )。在程式中我們模擬一串示範資料(例如 [1, 0, 1])。

  2. 副載波 (SC):頻率為 (注意:圖片中標示的 848 MHz 應為 848 kHz 的筆誤,因為 )。

  3. 載波 (CF):頻率為

  4. 主動負載調變 (TX):當曼徹斯特編碼與副載波透過 AND 閘結合後,只有在特定區間內,PCD/PICC 才會主動發射 的載波突發包(Burst)。

import tkinter as tk

from tkinter import ttk

import numpy as np

import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


def generate_waveforms():

    # --- 參數設定 ---

    fc = 13.56e6      # 載波頻率 13.56 MHz

    fsc = 848e3       # 副載波頻率 848 kHz

    bit_rate = 106e3  # 106 kbit/s

    T_bit = 1 / bit_rate # ~9.43 us

    

    # 模擬 3 個位元的時間 [1, 0, 1]

    data = [1, 0, 1] 

    total_time = len(data) * T_bit

    

    # 建立高解析度時間軸

    fs = 100e6        # 取樣頻率 100 MHz

    t = np.linspace(0, total_time, int(total_time * fs))

    

    # 1. 基頻信號 (BS) - 曼徹斯特編碼

    bs = np.zeros_like(t)

    for i, bit in enumerate(data):

        bit_start = i * T_bit

        bit_mid = bit_start + (T_bit / 2)

        bit_end = (i + 1) * T_bit

        

        if bit == 1:

            bs[(t >= bit_start) & (t < bit_mid)] = 1

            bs[(t >= bit_mid) & (t < bit_end)] = 0

        else:

            bs[(t >= bit_start) & (t < bit_mid)] = 0

            bs[(t >= bit_mid) & (t < bit_end)] = 1


    # 2. 副載波 (SC) - 848 kHz 方波

    sc = (0.5 * (np.sign(np.sin(2 * np.pi * fsc * t)) + 1)).astype(int)

    

    # 3. 載波 (CF) - 13.56 MHz 正弦波

    cf = np.sin(2 * np.pi * fc * t)

    

    # 4. 主動負載調變 (TX) - AND 閘邏輯

    and_gate_output = bs * sc

    tx = and_gate_output * cf

    

    # 5. 傳統被動負載調變 (Passive) - 對比用

    passive_tx = (1 + 0.3 * and_gate_output) * np.sin(2 * np.pi * fc * t)


    return t * 1e6, cf, sc, bs, tx, passive_tx # 時間單位轉為微秒 (us)


def draw_plots():

    ax1.clear()

    ax2.clear()

    ax3.clear()

    ax4.clear()

    ax5.clear()

    

    t_us, cf, sc, bs, tx, passive_tx = generate_waveforms()

    

    # 限制繪圖顯示範圍(只看前 15 微秒,解析度才夠看清載波脈衝)

    display_limit = (t_us >= 0) & (t_us <= 15)

    

    # Subplot 1: Carrier Frequency (CF)

    ax1.plot(t_us[display_limit], cf[display_limit], color='black', linewidth=0.5)

    ax1.set_ylabel("CF\n13.56 MHz", fontsize=9, rotation=0, labelpad=30, va='center')

    ax1.grid(True, linestyle=':', alpha=0.6)  # 已修正

    ax1.set_xticklabels([])

    

    # Subplot 2: Sub-Carrier (SC)

    ax2.plot(t_us[display_limit], sc[display_limit], color='gray', drawstyle='steps-pre')

    ax2.set_ylabel("SC\n848 kHz", fontsize=9, rotation=0, labelpad=30, va='center')

    ax2.set_ylim(-0.2, 1.2)

    ax2.grid(True, linestyle=':', alpha=0.6)  # 已修正

    ax2.set_xticklabels([])

    

    # Subplot 3: Baseband Signal (BS)

    ax3.plot(t_us[display_limit], bs[display_limit], color='blue', drawstyle='steps-pre')

    ax3.set_ylabel("BS (Data)\nManchester", fontsize=9, rotation=0, labelpad=30, va='center')

    ax3.set_ylim(-0.2, 1.2)

    ax3.grid(True, linestyle=':', alpha=0.6)  # 已修正

    ax3.set_xticklabels([])

    

    # Subplot 4: Active Load Modulation (TX)

    ax4.plot(t_us[display_limit], tx[display_limit], color='darkred', linewidth=0.8)

    ax4.set_ylabel("TX (ALM)\nActive", fontsize=9, rotation=0, labelpad=30, va='center')

    ax4.grid(True, linestyle=':', alpha=0.6)  # 已修正

    ax4.set_xticklabels([])

    

    # Subplot 5: Passive Load Modulation

    ax5.plot(t_us[display_limit], passive_tx[display_limit], color='purple', linewidth=0.5)

    ax5.set_ylabel("Passive\nModulation", fontsize=9, rotation=0, labelpad=30, va='center')

    ax5.set_xlabel("Time ($\mu$s)", fontsize=10)

    ax5.grid(True, linestyle=':', alpha=0.6)  # 已修正

    

    fig.tight_layout()

    canvas.draw()


# --- Tkinter 介面建立 ---

root = tk.Tk()

root.title("NFC Type A 106 kBit/s - ASK Active Load Modulation 模擬器")

root.geometry("1000x700")


# 頂部控制面板

frame_control = ttk.Frame(root)

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


btn_generate = ttk.Button(frame_control, text="產生波形 (Generate Waveforms)", command=draw_plots)

btn_generate.pack(side=tk.LEFT, pady=5)


label_info = ttk.Label(frame_control, text=" 模擬數據: [1, 0, 1] 曼徹斯特編碼 | 顯示前 15 微秒範圍以看清載波脈衝", foreground="green")

label_info.pack(side=tk.LEFT, padx=20)


# 建立 Matplotlib 畫布

fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(5, 1, figsize=(10, 6), sharex=True)

canvas = FigureCanvasTkAgg(fig, master=root)

canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)


# 執行 Tkinter 主迴圈

root.mainloop()



這份程式碼結合了 Tkinter(圖形介面)Matplotlib(繪圖庫),用來模擬 NFC Type A (106 kbit/s) 標準中的 ASK 主動負載調變 (Active Load Modulation, ALM)

為了讓你徹底理解,我們將程式碼拆解為四個主要部分來逐行說明:


一、 匯入模組與庫 (Imports)

Python
import tkinter as tk                  # Tkinter 是 Python 內建的圖形介面 (GUI) 庫
from tkinter import ttk               # ttk 提供更現代化、美觀的進階元件 (如按鈕、標籤)
import numpy as np                    # NumPy 用於高效的數學運算與陣列 (時間軸、正弦波等)
import matplotlib.pyplot as plt       # Matplotlib 的繪圖核心,負責繪製波形
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg  # 關鍵:將 Matplotlib 的圖表嵌入到 Tkinter 視窗中的橋樑

二、 核心演算法:波形產生邏輯 (generate_waveforms)

這個函式負責計算出所有訊號的數學矩陣。

Python
def generate_waveforms():
    # --- 參數設定 ---
    fc = 13.56e6      # 載波頻率 (Carrier Frequency) = 13.56 MHz (NFC 標準射頻頻率)
    fsc = 848e3       # 副載波頻率 (Sub-carrier Frequency) = 848 kHz (13.56 MHz / 16)
    bit_rate = 106e3  # 資料傳輸率 = 106 kbit/s
    T_bit = 1 / bit_rate # 計算一個位元(Bit) 所需的時間,約為 9.43 微秒 (µs)
    
    # 模擬 3 個位元的資料,這裡設定為 [1, 0, 1] 作為示範
    data = [1, 0, 1]  
    total_time = len(data) * T_bit # 總模擬時間 = 3 * 9.43 µs ≈ 28.3 µs
    
    # 建立高解析度的時間軸
    fs = 100e6        # 取樣率設定為 100 MHz (每秒取樣一億次),這樣才能精準捕捉 13.56 MHz 的超高頻波形
    t = np.linspace(0, total_time, int(total_time * fs)) # 產生從 0 到總時間、包含數千個點的均勻時間陣列

1. 基頻信號 (BS) - 曼徹斯特編碼

根據 NFC Type A 規範,卡片回傳資料(PICC to PCD)使用曼徹斯特編碼。

Python
    bs = np.zeros_like(t) # 建立一個與時間軸 t 長度相同、初始值全為 0 的矩陣
    for i, bit in enumerate(data):
        bit_start = i * T_bit          # 當前位元的開始時間點
        bit_mid = bit_start + (T_bit / 2) # 當前位元的正中間時間點
        bit_end = (i + 1) * T_bit      # 當前位元的結束時間點
        
        if bit == 1:
            # 邏輯 1:前半週期為高電平 (1),後半週期為低電平 (0)
            bs[(t >= bit_start) & (t < bit_mid)] = 1
            bs[(t >= bit_mid) & (t < bit_end)] = 0
        else:
            # 邏輯 0:前半週期為低電平 (0),後半週期為高電平 (1)
            bs[(t >= bit_start) & (t < bit_mid)] = 0
            bs[(t >= bit_mid) & (t < bit_end)] = 1

2. 副載波 (SC) 與 載波 (CF) 產生

Python
    # 2. 副載波 (SC) - 848 kHz 方波
    # np.sin 產生正弦波,np.sign 取正負號(變成 -1 和 1),最後透過 +1 再乘以 0.5 將訊號轉換成 0 與 1 的方波
    sc = (0.5 * (np.sign(np.sin(2 * np.pi * fsc * t)) + 1)).astype(int)
    
    # 3. 載波 (CF) - 13.56 MHz 乾淨的正弦波
    cf = np.sin(2 * np.pi * fc * t)

3. 邏輯運算與調變輸出 (AND 閘邏輯)

這是硬體電路圖中最核心的部分。

Python
    # 4. 主動負載調變 (TX) - 對應硬體中的 AND 閘
    # 將基頻訊號 (BS) 與 副載波 (SC) 進行相乘(相當於數位邏輯的 AND 運算)
    and_gate_output = bs * sc
    # 只有當 AND 閘輸出為 1 時,主動發射器才會把 13.56 MHz 載波發射出去 (突發包 Burst)
    tx = and_gate_output * cf
    
    # 5. 傳統被動負載調變 (Passive) - 作為圖表對比用
    # 被動調變是持續擁有載波,透過改變天線阻抗使振幅產生輕微變化 (例如 10%~30% ASK 變化)
    passive_tx = (1 + 0.3 * and_gate_output) * np.sin(2 * np.pi * fc * t)

    return t * 1e6, cf, sc, bs, tx, passive_tx # 回傳所有數據,並將時間軸乘以 1e6 轉換為微秒 (µs) 單位

三、 繪圖邏輯與圖表更新 (draw_plots)

當使用者點擊按鈕時,這個函式會被觸發,負責將數據畫到 5 個不同的子圖上。

Python
def draw_plots():
    # 清空 5 個子圖(ax1 到 ax5)上一次留下的舊畫面
    ax1.clear()
    ax2.clear()
    ax3.clear()
    ax4.clear()
    ax5.clear()
    
    # 呼叫前述函式,獲取最新的波形數據
    t_us, cf, sc, bs, tx, passive_tx = generate_waveforms()
    
    # 關鍵:限制顯示範圍!
    # 因為 13.56 MHz 頻率太高,如果把 3 個位元 (約 28µs) 全部畫出來,載波會擠成一團黑線。
    # 這裡我們只篩選時間在 0 到 15 微秒 (µs) 之間的數據,這樣才能清晰看見正弦波形。
    display_limit = (t_us >= 0) & (t_us <= 15)
    
    # 繪製第一個子圖:載波 (CF) 13.56 MHz
    ax1.plot(t_us[display_limit], cf[display_limit], color='black', linewidth=0.5)
    ax1.set_ylabel("CF\n13.56 MHz", fontsize=9, rotation=0, labelpad=30, va='center') # 設定 Y 軸標籤
    ax1.grid(True, linestyle=':', alpha=0.6) # 開啟點狀網格線
    ax1.set_xticklabels([])                  # 隱藏 X 軸刻度字體(因為與下方共用)
    
    # 繪製第二個子圖:副載波 (SC) 848 kHz 方波
    # drawstyle='steps-pre' 確保數位方波垂直邊緣不會變成斜線
    ax2.plot(t_us[display_limit], sc[display_limit], color='gray', drawstyle='steps-pre')
    ax2.set_ylabel("SC\n848 kHz", fontsize=9, rotation=0, labelpad=30, va='center')
    ax2.set_ylim(-0.2, 1.2)                  # 固定 Y 軸上下限,讓方波更美觀
    ax2.grid(True, linestyle=':', alpha=0.6)
    ax2.set_xticklabels([])
    
    # 繪製第三個子圖:基頻信號 (BS) 曼徹斯特編碼
    ax3.plot(t_us[display_limit], bs[display_limit], color='blue', drawstyle='steps-pre')
    ax3.set_ylabel("BS (Data)\nManchester", fontsize=9, rotation=0, labelpad=30, va='center')
    ax3.set_ylim(-0.2, 1.2)
    ax3.grid(True, linestyle=':', alpha=0.6)
    ax3.set_xticklabels([])
    
    # 繪製第四個子圖:主動負載調變 (TX ALM)
    # 這就是你在圖中看到的「有訊號時才有波形,沒訊號時是一條零電平直線」的 ALM 特有突發波形!
    ax4.plot(t_us[display_limit], tx[display_limit], color='darkred', linewidth=0.8)
    ax4.set_ylabel("TX (ALM)\nActive", fontsize=9, rotation=0, labelpad=30, va='center')
    ax4.grid(True, linestyle=':', alpha=0.6)
    ax4.set_xticklabels([])
    
    # 繪製第五個子圖:被動負載調變 (Passive)
    # 這是傳統卡片的波形,用來與主動調變做對比(全程都有載波,只是中間有些地方振幅變大或變小)
    ax5.plot(t_us[display_limit], passive_tx[display_limit], color='purple', linewidth=0.5)
    ax5.set_ylabel("Passive\nModulation", fontsize=9, rotation=0, labelpad=30, va='center')
    ax5.set_xlabel("Time ($\mu$s)", fontsize=10) # 只有最後一圖需要顯示 X 軸時間標籤
    ax5.grid(True, linestyle=':', alpha=0.6)
    
    # 自動優化子圖之間的間距,避免文字重疊
    fig.tight_layout()
    # 關鍵:通知 Tkinter 的畫布元件,Matplotlib 圖表內容已經更新,請重新刷新畫面
    canvas.draw()

四、 介面佈局與主迴圈 (Tkinter GUI Setup)

這段負責撐起整個軟體視窗與按鈕介面。

Python
# --- Tkinter 介面建立 ---
root = tk.Tk() # 建立主要的 GUI 視窗物件
root.title("NFC Type A 106 kBit/s - ASK Active Load Modulation 模擬器") # 設定視窗標題
root.geometry("1000x700") # 設定視窗預設大小為寬 1000 像素、高 700 像素

# 1. 建立頂部的控制面板區塊 (Frame)
frame_control = ttk.Frame(root)
frame_control.pack(side=tk.TOP, fill=tk.X, padx=10, pady=5) # 放置在視窗最上方,水平填滿

# 2. 在控制面板區塊中放置一個按鈕
# command=draw_plots 意思是:當使用者點擊這個按鈕,就去執行 draw_plots 函式開始繪圖
btn_generate = ttk.Button(frame_control, text="產生波形 (Generate Waveforms)", command=draw_plots)
btn_generate.pack(side=tk.LEFT, pady=5) # 靠左對齊放置

# 3. 在按鈕旁放一個綠色的文字標籤做說明
label_info = ttk.Label(frame_control, text=" 模擬數據: [1, 0, 1] 曼徹斯特編碼 | 顯示前 15 微秒範圍以看清載波脈衝", foreground="green")
label_info.pack(side=tk.LEFT, padx=20) # 靠左對齊,並留 20 像素的水平間距

# 4. 建立 Matplotlib 的畫布圖表物件
# subplots(5, 1, ...) 代表建立 5 行 1 列 的垂直疊加子圖。sharex=True 代表讓這 5 個子圖共用同一個 X 軸時間刻度
fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(5, 1, figsize=(10, 6), sharex=True)

# 5. 將 Matplotlib 轉換為 Tkinter 認得的 Widget (畫布元件)
canvas = FigureCanvasTkAgg(fig, master=root)
# 將畫布放置到視窗中,設定為隨視窗大小縮放 (expand=True, fill=tk.BOTH)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

# 進入 Tkinter 的事件監聽主迴圈,保持視窗開啟,等待使用者的點擊操作
root.mainloop()

沒有留言:

張貼留言

ISO/IEC DIS9789-2 標準中的「三步驟雙向加密認證(Three-Step Mutual Authentication)」

ISO/IEC DIS9789-2 標準中的「三步驟雙向加密認證(Three-Step Mutual Authentication)」 這是一個涉及到 資訊安全、射頻識別(RFID/NFC)高階安全認證 以及 圖形化使用者介面(GUI) 的整合實作。 在 ISO/IEC 的安...