模擬 NFC Type A (106 kbit/s) 標準中的 ASK 主動負載調變 (Active Load Modulation, ALM)
這是一個非常適合用 Python tkinter 結合 matplotlib 來製作的動態或靜態波形模擬。
在底下的程式碼中,我使用 matplotlib 來精準繪製這三個信號(載波、副載波、基頻信號)以及最終調變後的 主動負載調變(Active Load Modulation, ALM) 波形,並將圖表嵌入到 tkinter 的視窗介面中。
核心邏輯說明
基頻信號 (BS):使用曼徹斯特編碼(Manchester Code),1 個位元時間約為 (即 )。在程式中我們模擬一串示範資料(例如
[1, 0, 1])。副載波 (SC):頻率為 (注意:圖片中標示的 848 MHz 應為 848 kHz 的筆誤,因為 )。
載波 (CF):頻率為 。
主動負載調變 (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)
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)
這個函式負責計算出所有訊號的數學矩陣。
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)使用曼徹斯特編碼。
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) 產生
# 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 閘邏輯)
這是硬體電路圖中最核心的部分。
# 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 個不同的子圖上。
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)
這段負責撐起整個軟體視窗與按鈕介面。
# --- 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()
沒有留言:
張貼留言