(副載波負載調變、曼徹斯特編碼、副載波頻率 $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()



沒有留言:
張貼留言