8-QAM Signal 4 Phases 2 Amplitudes + 8PSK
import tkinter as tk
from tkinter import messagebox
import math
import cmath
# --- 8-QAM 參數設定 ---
# 振幅大小定義
AMP_SMALL = 0.5
AMP_LARGE = 1.0
# 8-QAM 符號定義 (I, Q 座標對)
QAM_SYMBOLS = {
# 小振幅 (內環)
'000': (AMP_SMALL, AMP_SMALL), # 45度
'001': (-AMP_SMALL, AMP_SMALL), # 135度
'010': (-AMP_SMALL, -AMP_SMALL), # 225度
'011': (AMP_SMALL, -AMP_SMALL), # 315度
# 大振幅 (外環)
'100': (AMP_LARGE, AMP_LARGE), # 45度
'101': (-AMP_LARGE, AMP_LARGE), # 135度
'110': (-AMP_LARGE, -AMP_LARGE), # 225度
'111': (AMP_LARGE, -AMP_LARGE) # 315度
}
# 按照 000 -> 001 -> ... -> 111 排序的符號列表,用於自動模式
SYMBOL_SEQUENCE = sorted(QAM_SYMBOLS.keys())
# --- 模式與自動控制變數 ---
AUTO_MODE_RUNNING = False
AUTO_DELAY_MS = 3000
current_symbol_index = 0
after_id = None
# --- 星座圖參數 (左側) ---
CONST_W = 400
CONST_H = 400
CONST_CX = CONST_W // 2
CONST_CY = CONST_H // 2
SCALE_FACTOR = 150
POINT_R = 10
I_OFFSET_Y = 25
Q_OFFSET_X = 25
# --- 靜態波形圖參數 (右側 8 個) ---
ALL_WAVE_W = 350
ALL_WAVE_H = 400
SMALL_WAVE_H_PER_SYMBOL = 45
SMALL_FREQUENCY = 2
SMALL_SYMBOL_DURATION = 200
STATIC_TEXT_OFFSET = 100
SMALL_WAVE_AMPLITUDE = 20
# --- 動態波形圖參數 (底部) ---
DYNAMIC_WAVE_W = 800
DYNAMIC_WAVE_H = 300
DYNAMIC_WAVE_CY = DYNAMIC_WAVE_H // 2
WAVE_SCALING = 100
FREQUENCY = 2
SYMBOL_DURATION = 200
DYNAMIC_WAVE_START_X = (DYNAMIC_WAVE_W - SYMBOL_DURATION) // 2
# *** 修正點:定義紅字標註的右側偏移量 ***
RED_TEXT_OFFSET_RIGHT = 80
# --- 輔助函數 ---
def calculate_polar(i_coord, q_coord):
"""計算給定 I/Q 座標的振幅和相位 (度)。"""
magnitude = math.sqrt(i_coord**2 + q_coord**2)
phase_rad = cmath.phase(complex(i_coord, q_coord))
phase_deg = math.degrees(phase_rad)
if phase_deg < 0:
phase_deg += 360
return magnitude, phase_deg
# --- 函數定義 ---
def draw_8qam_constellation(canvas):
"""繪製 8-QAM 星座圖 (4 Phases, 2 Amplitudes 佈局)。"""
canvas.delete("all")
# 1. 繪製標題
canvas.create_text(CONST_CX, 20, text="8-QAM 星座圖 (4 Phases, 2 Amplitudes)", font=('Arial', 12, 'bold'))
# 2. 繪製座標軸 (I軸 和 Q軸)
axis_end = SCALE_FACTOR * AMP_LARGE * 1.5
canvas.create_line(CONST_CX - axis_end, CONST_CY, CONST_CX + axis_end, CONST_CY, arrow=tk.LAST, fill='black')
canvas.create_text(CONST_CX + axis_end, CONST_CY + I_OFFSET_Y, text='I', fill='black', font=('Arial', 12, 'bold'))
canvas.create_line(CONST_CX, CONST_CY + axis_end, CONST_CX, CONST_CY - axis_end, arrow=tk.LAST, fill='black')
canvas.create_text(CONST_CX + Q_OFFSET_X, CONST_CY - axis_end, text='Q', fill='black', font=('Arial', 12, 'bold'))
# 繪製代表振幅的虛線方格/圓 (這裡繪製方格)
square_size_small = AMP_SMALL * SCALE_FACTOR
square_size_large = AMP_LARGE * SCALE_FACTOR
# 小振幅方格
x1_s, y1_s = CONST_CX - square_size_small, CONST_CY - square_size_small
x2_s, y2_s = CONST_CX + square_size_small, CONST_CY + square_size_small
canvas.create_rectangle(x1_s, y1_s, x2_s, y2_s, outline='gray', dash=(5, 3))
# 大振幅方格
x1_l, y1_l = CONST_CX - square_size_large, CONST_CY - square_size_large
x2_l, y2_l = CONST_CX + square_size_large, CONST_CY + square_size_large
canvas.create_rectangle(x1_l, y1_l, x2_l, y2_l, outline='black', dash=(5, 3))
# 3. 繪製 8 個符號點和標註
for bits, (i_norm, q_norm) in QAM_SYMBOLS.items():
i_coord = CONST_CX + i_norm * SCALE_FACTOR
q_coord = CONST_CY - q_norm * SCALE_FACTOR
canvas.create_oval(i_coord - POINT_R, q_coord - POINT_R,
i_coord + POINT_R, q_coord + POINT_R,
fill='pink' if abs(i_norm)==AMP_SMALL else 'red', outline='black')
offset = 20
angle_rad = cmath.phase(complex(i_norm, q_norm))
text_i_coord = i_coord + offset * math.cos(angle_rad)
text_q_coord = q_coord - offset * math.sin(angle_rad)
canvas.create_text(text_i_coord, text_q_coord, text=bits, font=('Arial', 10, 'bold'), fill='darkblue')
def draw_all_waveforms(canvas):
"""繪製右側 8 個靜態 8-QAM 波形圖。"""
canvas.delete("all")
canvas.create_text(ALL_WAVE_W // 2, 10, text="8-QAM 符號波形對照", font=('Arial', 12, 'bold'), fill='red')
y_offset = 30
sorted_symbols = SYMBOL_SEQUENCE
for bits in sorted_symbols:
i_norm, q_norm = QAM_SYMBOLS[bits]
magnitude, phase_deg = calculate_polar(i_norm, q_norm)
phase_rad = math.radians(phase_deg)
# 標註位元組和相位 (靠右對齊)
label_text = f"{bits} ({phase_deg:.0f}°)"
canvas.create_text(STATIC_TEXT_OFFSET, y_offset + SMALL_WAVE_H_PER_SYMBOL // 2,
text=label_text, anchor=tk.E,
font=('Arial', 9), fill='darkgreen')
start_x = STATIC_TEXT_OFFSET + 10
wave_cy = y_offset + SMALL_WAVE_H_PER_SYMBOL // 2
# 繪製時間軸
canvas.create_line(start_x, wave_cy, start_x + SMALL_SYMBOL_DURATION, wave_cy, fill='gray', width=1)
waveform_points = []
for t in range(SMALL_SYMBOL_DURATION):
t_norm = t / SMALL_SYMBOL_DURATION
amplitude = magnitude * SMALL_WAVE_AMPLITUDE * math.cos(2 * math.pi * SMALL_FREQUENCY * t_norm + phase_rad)
x = start_x + t
y = wave_cy - amplitude
waveform_points.extend([x, y])
canvas.create_line(waveform_points, fill='blue', smooth=True, width=1)
y_offset += SMALL_WAVE_H_PER_SYMBOL
def draw_single_waveform(canvas, bits_input):
"""根據輸入的位元組繪製單個符號的 8-QAM 波形。"""
canvas.delete("all")
i_norm, q_norm = QAM_SYMBOLS[bits_input]
magnitude, phase_deg = calculate_polar(i_norm, q_norm)
phase_rad = math.radians(phase_deg)
# 1. 繪製座標軸 (置中對齊)
x_axis_end = DYNAMIC_WAVE_START_X + SYMBOL_DURATION + 20
canvas.create_line(DYNAMIC_WAVE_START_X - 20, DYNAMIC_WAVE_CY, x_axis_end, DYNAMIC_WAVE_CY, fill='black', width=2)
canvas.create_text(DYNAMIC_WAVE_START_X - 30, DYNAMIC_WAVE_CY, text='0', fill='black', anchor=tk.E)
canvas.create_text(20, 20, text='Amplitude (A)', anchor=tk.W)
canvas.create_text(DYNAMIC_WAVE_W - 10, DYNAMIC_WAVE_CY + 10, text='Time (t)', anchor=tk.E)
# 2. 繪製符號週期邊界
canvas.create_line(DYNAMIC_WAVE_START_X, 20, DYNAMIC_WAVE_START_X, DYNAMIC_WAVE_H - 20, dash=(4, 2), fill='gray')
canvas.create_line(DYNAMIC_WAVE_START_X + SYMBOL_DURATION, 20, DYNAMIC_WAVE_START_X + SYMBOL_DURATION, DYNAMIC_WAVE_H - 20, dash=(4, 2), fill='gray')
# 3. 標註符號 (將紅字更靠右)
# 這裡將 x 座標設置為接近畫布右側邊緣 RED_TEXT_OFFSET_RIGHT 的位置
text_x_coord = DYNAMIC_WAVE_W - RED_TEXT_OFFSET_RIGHT
label_text = f"Symbol: {bits_input} | Mag: {magnitude:.2f} | Phase: {phase_deg:.0f}° | Freq: {FREQUENCY} cycles/symbol"
canvas.create_text(text_x_coord, 40,
text=label_text,
font=('Arial', 12, 'bold'), fill='red', anchor=tk.E) # anchor=tk.E 確保文字右側對齊於此座標
# 4. 繪製波形 (置中對齊)
current_x = DYNAMIC_WAVE_START_X
waveform_points = []
for t in range(SYMBOL_DURATION):
t_norm = t / SYMBOL_DURATION
amplitude = magnitude * WAVE_SCALING * math.cos(2 * math.pi * FREQUENCY * t_norm + phase_rad)
x = current_x + t
y = DYNAMIC_WAVE_CY - amplitude
waveform_points.extend([x, y])
canvas.create_line(waveform_points, fill='blue', smooth=True, width=2)
# --- 自動模式控制函數 ---
def start_auto_mode():
global AUTO_MODE_RUNNING, current_symbol_index, after_id
if AUTO_MODE_RUNNING:
return
current_symbol_index = 0
AUTO_MODE_RUNNING = True
input_entry.config(state=tk.DISABLED)
manual_button.config(state=tk.DISABLED)
auto_start_button.config(text="運行中...", state=tk.DISABLED, bg='red')
auto_stop_button.config(state=tk.NORMAL)
run_auto_cycle()
def run_auto_cycle():
global current_symbol_index, after_id
if not AUTO_MODE_RUNNING:
return
bits = SYMBOL_SEQUENCE[current_symbol_index]
draw_single_waveform(dynamic_wave_canvas, bits)
input_entry.config(state=tk.NORMAL)
input_entry.delete(0, tk.END)
input_entry.insert(0, bits)
input_entry.config(state=tk.DISABLED)
current_symbol_index = (current_symbol_index + 1) % len(SYMBOL_SEQUENCE)
after_id = root.after(AUTO_DELAY_MS, run_auto_cycle)
def stop_auto_mode():
global AUTO_MODE_RUNNING, after_id
if not AUTO_MODE_RUNNING:
return
AUTO_MODE_RUNNING = False
if after_id:
root.after_cancel(after_id)
after_id = None
input_entry.config(state=tk.NORMAL)
manual_button.config(state=tk.NORMAL)
auto_start_button.config(text="啟動自動模式", state=tk.NORMAL, bg='lightgreen')
auto_stop_button.config(state=tk.DISABLED)
def update_waveform_manual():
if AUTO_MODE_RUNNING:
messagebox.showinfo("模式提示", "請先點擊 '停止自動模式' 才能手動輸入。")
return
input_bits = input_entry.get().strip()
if input_bits not in QAM_SYMBOLS:
messagebox.showerror("輸入錯誤", "請輸入一個有效的 3-bit 位元組 (000~111)。")
return
draw_single_waveform(dynamic_wave_canvas, input_bits)
# --- 主程式 ---
root = tk.Tk()
root.title("8-QAM 調變分析工具 (4 Phases, 2 Amplitudes)")
# 1. 頂部主框架 (包含星座圖和靜態波形)
top_frame = tk.Frame(root)
top_frame.pack(pady=10)
# 1A. 星座圖框架 (左側)
const_frame = tk.Frame(top_frame)
const_frame.pack(side=tk.LEFT, padx=10)
const_canvas = tk.Canvas(const_frame, width=CONST_W, height=CONST_H, bg='white')
const_canvas.pack()
draw_8qam_constellation(const_canvas)
tk.Label(const_frame, text="8-QAM 星座圖", font=('Arial', 14, 'bold')).pack()
# 1B. 靜態波形框架 (右側)
all_wave_frame = tk.Frame(top_frame)
all_wave_frame.pack(side=tk.LEFT, padx=10)
all_wave_canvas = tk.Canvas(all_wave_frame, width=ALL_WAVE_W, height=ALL_WAVE_H, bg='lightyellow')
all_wave_canvas.pack()
draw_all_waveforms(all_wave_canvas)
# 2. 中間控制與輸入部分 (增加模式選擇)
control_frame = tk.Frame(root)
control_frame.pack(pady=10)
# 2A. 手動控制組
manual_control_frame = tk.Frame(control_frame)
manual_control_frame.pack(side=tk.LEFT, padx=20)
tk.Label(manual_control_frame, text="【手動模式】輸入位元組 (000~111):", font=('Arial', 12)).pack(side=tk.LEFT, padx=5)
input_entry = tk.Entry(manual_control_frame, width=5, font=('Arial', 12))
input_entry.pack(side=tk.LEFT, padx=5)
manual_button = tk.Button(manual_control_frame, text="手動顯示波形", command=update_waveform_manual, font=('Arial', 12, 'bold'), bg='lightblue')
manual_button.pack(side=tk.LEFT, padx=10)
# 2B. 自動控制組
auto_control_frame = tk.Frame(control_frame)
auto_control_frame.pack(side=tk.LEFT, padx=20)
auto_start_button = tk.Button(auto_control_frame, text="啟動自動模式", command=start_auto_mode, font=('Arial', 12, 'bold'), bg='lightgreen')
auto_start_button.pack(side=tk.LEFT, padx=10)
auto_stop_button = tk.Button(auto_control_frame, text="停止自動模式", command=stop_auto_mode, font=('Arial', 12, 'bold'), bg='red', state=tk.DISABLED)
auto_stop_button.pack(side=tk.LEFT, padx=10)
# 3. 底部動態波形圖部分
dynamic_wave_frame = tk.Frame(root)
dynamic_wave_frame.pack(pady=10)
dynamic_wave_canvas = tk.Canvas(dynamic_wave_frame, width=DYNAMIC_WAVE_W, height=DYNAMIC_WAVE_H, bg='white')
dynamic_wave_canvas.pack()
# 初始顯示 '000' 的波形
draw_single_waveform(dynamic_wave_canvas, '000')
tk.Label(dynamic_wave_frame, text="單一符號動態 QAM 波形圖 (2個週期)", font=('Arial', 14, 'bold')).pack()
# 執行 Tkinter 事件迴圈
root.mainloop()
import tkinter as tk
from tkinter import messagebox
import math
import itertools
# --- 全域參數設定 ---
# 8-PSK 符號定義 (角度,單位:度)
PSK_PHASES = {
'111': 0,
'110': 45,
'010': 90,
'011': 135,
'001': 180,
'000': 225,
'100': 270,
'101': 315
}
# 按照 000 -> 001 -> ... -> 111 排序的符號列表,用於自動模式
SYMBOL_SEQUENCE = sorted(PSK_PHASES.keys())
# --- 模式與自動控制變數 ---
AUTO_MODE_RUNNING = False # 追蹤自動模式是否正在運行
AUTO_DELAY_MS = 3000 # 自動模式延遲時間 (3000ms = 3秒)
current_symbol_index = 0 # 追蹤當前自動模式的符號索引
after_id = None # 用於儲存 tk.after 的 ID,以便取消
# --- 星座圖參數 (左側) ---
CONST_W = 400
CONST_H = 400
CONST_CX = CONST_W // 2
CONST_CY = CONST_H // 2
RADIUS = 150
POINT_R = 10
I_OFFSET_Y = 25
Q_OFFSET_X = 25
# --- 靜態波形圖參數 (右側 8 個) ---
ALL_WAVE_W = 350
ALL_WAVE_H = 400
SMALL_WAVE_H_PER_SYMBOL = 45
SMALL_WAVE_AMPLITUDE = 15
SMALL_FREQUENCY = 3
SMALL_SYMBOL_DURATION = 200
STATIC_TEXT_OFFSET = 100
# --- 動態波形圖參數 (底部) ---
DYNAMIC_WAVE_W = 800
DYNAMIC_WAVE_H = 300
DYNAMIC_WAVE_CY = DYNAMIC_WAVE_H // 2
WAVE_AMPLITUDE = 90
FREQUENCY = 2
SYMBOL_DURATION = 200
DYNAMIC_WAVE_START_X = (DYNAMIC_WAVE_W - SYMBOL_DURATION) // 2
# --- 函數定義 ---
def draw_8psk_constellation(canvas):
"""繪製 8-PSK 星座圖。"""
# 1. 繪製座標軸 (I軸 和 Q軸)
canvas.create_line(CONST_CX - RADIUS - 20, CONST_CY, CONST_CX + RADIUS + 20, CONST_CY, arrow=tk.LAST, fill='black')
canvas.create_text(CONST_CX + RADIUS + 30, CONST_CY + I_OFFSET_Y, text='I', fill='black', font=('Arial', 12, 'bold'))
canvas.create_line(CONST_CX, CONST_CY + RADIUS + 20, CONST_CX, CONST_CY - RADIUS - 20, arrow=tk.LAST, fill='black')
canvas.create_text(CONST_CX + Q_OFFSET_X, CONST_CY - RADIUS - 30, text='Q', fill='black', font=('Arial', 12, 'bold'))
# 2. 繪製圓形
x1, y1 = CONST_CX - RADIUS, CONST_CY - RADIUS
x2, y2 = CONST_CX + RADIUS, CONST_CY + RADIUS
canvas.create_oval(x1, y1, x2, y2, outline='black')
# 3. 繪製 8 個符號點和標註
for bits, angle_deg in PSK_PHASES.items():
angle_rad = math.radians(angle_deg)
i_coord = CONST_CX + RADIUS * math.cos(angle_rad)
q_coord = CONST_CY - RADIUS * math.sin(angle_rad)
canvas.create_oval(i_coord - POINT_R, q_coord - POINT_R,
i_coord + POINT_R, q_coord + POINT_R,
fill='lightblue', outline='black')
offset_dist = RADIUS + 25
text_i_coord = CONST_CX + offset_dist * math.cos(angle_rad)
text_q_coord = CONST_CY - offset_dist * math.sin(angle_rad)
canvas.create_text(text_i_coord, text_q_coord, text=bits, font=('Arial', 10, 'bold'), fill='darkblue')
def draw_all_waveforms(canvas):
"""繪製右側 8 個靜態波形圖。"""
canvas.delete("all")
canvas.create_text(ALL_WAVE_W // 2, 10, text="8-PSK 符號相位對照 (全部 8 個)", font=('Arial', 12, 'bold'), fill='red')
y_offset = 30
sorted_symbols = SYMBOL_SEQUENCE # 使用排序後的列表
for bits in sorted_symbols:
phase_deg = PSK_PHASES[bits]
phase_rad = math.radians(phase_deg)
# 標註位元組和相位 (靠右對齊)
canvas.create_text(STATIC_TEXT_OFFSET, y_offset + SMALL_WAVE_H_PER_SYMBOL // 2,
text=f"{bits} ({phase_deg}°)", anchor=tk.E,
font=('Arial', 9), fill='darkgreen')
start_x = STATIC_TEXT_OFFSET + 10
wave_cy = y_offset + SMALL_WAVE_H_PER_SYMBOL // 2
# 繪製時間軸
canvas.create_line(start_x, wave_cy, start_x + SMALL_SYMBOL_DURATION, wave_cy, fill='gray', width=1)
waveform_points = []
for t in range(SMALL_SYMBOL_DURATION):
t_norm = t / SMALL_SYMBOL_DURATION
amplitude = SMALL_WAVE_AMPLITUDE * math.cos(2 * math.pi * SMALL_FREQUENCY * t_norm + phase_rad)
x = start_x + t
y = wave_cy - amplitude
waveform_points.extend([x, y])
canvas.create_line(waveform_points, fill='blue', smooth=True, width=1)
y_offset += SMALL_WAVE_H_PER_SYMBOL
def draw_single_waveform(canvas, bits_input):
"""根據輸入的位元組繪製單個符號的 8-PSK 波形。"""
canvas.delete("all")
# 1. 繪製座標軸 (置中對齊)
canvas.create_line(DYNAMIC_WAVE_START_X - 20, DYNAMIC_WAVE_CY, DYNAMIC_WAVE_START_X + SYMBOL_DURATION + 20, DYNAMIC_WAVE_CY, fill='black', width=2)
canvas.create_text(DYNAMIC_WAVE_START_X - 30, DYNAMIC_WAVE_CY, text='0', fill='black', anchor=tk.E)
canvas.create_text(20, 20, text='Amplitude (A)', anchor=tk.W)
canvas.create_text(DYNAMIC_WAVE_W - 10, DYNAMIC_WAVE_CY + 10, text='Time (t)', anchor=tk.E)
# 2. 繪製符號週期邊界
canvas.create_line(DYNAMIC_WAVE_START_X, 20, DYNAMIC_WAVE_START_X, DYNAMIC_WAVE_H - 20, dash=(4, 2), fill='gray')
canvas.create_line(DYNAMIC_WAVE_START_X + SYMBOL_DURATION, 20, DYNAMIC_WAVE_START_X + SYMBOL_DURATION, DYNAMIC_WAVE_H - 20, dash=(4, 2), fill='gray')
# 3. 標註符號 (靠右對齊)
phase_deg = PSK_PHASES[bits_input]
text_x_coord = DYNAMIC_WAVE_START_X + SYMBOL_DURATION + 30
canvas.create_text(text_x_coord, 40,
text=f"Symbol: {bits_input} | Phase: {phase_deg}° | Freq: {FREQUENCY} cycles/symbol",
font=('Arial', 12, 'bold'), fill='red', anchor=tk.E)
phase_rad = math.radians(phase_deg)
# 4. 繪製波形 (置中對齊)
current_x = DYNAMIC_WAVE_START_X
waveform_points = []
for t in range(SYMBOL_DURATION):
t_norm = t / SYMBOL_DURATION
amplitude = WAVE_AMPLITUDE * math.cos(2 * math.pi * FREQUENCY * t_norm + phase_rad)
x = current_x + t
y = DYNAMIC_WAVE_CY - amplitude
waveform_points.extend([x, y])
canvas.create_line(waveform_points, fill='blue', smooth=True, width=2)
# --- 自動模式控制函數 ---
def start_auto_mode():
"""啟動自動循環模式。"""
global AUTO_MODE_RUNNING, current_symbol_index, after_id
if AUTO_MODE_RUNNING:
return
# 確保從 000 開始
current_symbol_index = 0
AUTO_MODE_RUNNING = True
# 禁用手動控制
input_entry.config(state=tk.DISABLED)
manual_button.config(state=tk.DISABLED)
auto_start_button.config(text="運行中...", state=tk.DISABLED, bg='red')
auto_stop_button.config(state=tk.NORMAL)
# 立即開始循環
run_auto_cycle()
def run_auto_cycle():
"""自動切換符號並繪製波形。"""
global current_symbol_index, after_id
if not AUTO_MODE_RUNNING:
return
bits = SYMBOL_SEQUENCE[current_symbol_index]
draw_single_waveform(dynamic_wave_canvas, bits)
# 更新控制面板上的顯示
input_entry.config(state=tk.NORMAL)
input_entry.delete(0, tk.END)
input_entry.insert(0, bits)
input_entry.config(state=tk.DISABLED) # 重新禁用
# 移動到下一個索引,循環
current_symbol_index = (current_symbol_index + 1) % len(SYMBOL_SEQUENCE)
# 設置下一次循環
after_id = root.after(AUTO_DELAY_MS, run_auto_cycle)
def stop_auto_mode():
"""停止自動循環模式。"""
global AUTO_MODE_RUNNING, after_id
if not AUTO_MODE_RUNNING:
return
AUTO_MODE_RUNNING = False
# 取消排隊中的 after 任務
if after_id:
root.after_cancel(after_id)
after_id = None
# 啟用手動控制
input_entry.config(state=tk.NORMAL)
manual_button.config(state=tk.NORMAL)
auto_start_button.config(text="啟動自動模式", state=tk.NORMAL, bg='lightgreen')
auto_stop_button.config(state=tk.DISABLED)
def update_waveform_manual():
"""手動模式下,更新波形。"""
if AUTO_MODE_RUNNING:
messagebox.showinfo("模式提示", "請先點擊 '停止自動模式' 才能手動輸入。")
return
input_bits = input_entry.get().strip()
if input_bits not in PSK_PHASES:
messagebox.showerror("輸入錯誤", "請輸入一個有效的 3-bit 位元組 (000~111)。")
return
draw_single_waveform(dynamic_wave_canvas, input_bits)
# --- 主程式 ---
root = tk.Tk()
root.title("8-PSK 調變分析工具 (自動/手動模式)")
# 1. 頂部主框架 (包含星座圖和靜態波形)
top_frame = tk.Frame(root)
top_frame.pack(pady=10)
# 1A. 星座圖框架 (左側)
const_frame = tk.Frame(top_frame)
const_frame.pack(side=tk.LEFT, padx=10)
const_canvas = tk.Canvas(const_frame, width=CONST_W, height=CONST_H, bg='white')
const_canvas.pack()
draw_8psk_constellation(const_canvas)
tk.Label(const_frame, text="8-PSK 星座圖", font=('Arial', 14, 'bold')).pack()
# 1B. 靜態波形框架 (右側)
all_wave_frame = tk.Frame(top_frame)
all_wave_frame.pack(side=tk.LEFT, padx=10)
all_wave_canvas = tk.Canvas(all_wave_frame, width=ALL_WAVE_W, height=ALL_WAVE_H, bg='lightyellow')
all_wave_canvas.pack()
draw_all_waveforms(all_wave_canvas)
# 2. 中間控制與輸入部分 (增加模式選擇)
control_frame = tk.Frame(root)
control_frame.pack(pady=10)
# 2A. 手動控制組
manual_control_frame = tk.Frame(control_frame)
manual_control_frame.pack(side=tk.LEFT, padx=20)
tk.Label(manual_control_frame, text="【手動模式】輸入位元組 (000~111):", font=('Arial', 12)).pack(side=tk.LEFT, padx=5)
input_entry = tk.Entry(manual_control_frame, width=5, font=('Arial', 12))
input_entry.pack(side=tk.LEFT, padx=5)
manual_button = tk.Button(manual_control_frame, text="手動顯示波形", command=update_waveform_manual, font=('Arial', 12, 'bold'), bg='lightblue')
manual_button.pack(side=tk.LEFT, padx=10)
# 2B. 自動控制組
auto_control_frame = tk.Frame(control_frame)
auto_control_frame.pack(side=tk.LEFT, padx=20)
auto_start_button = tk.Button(auto_control_frame, text="啟動自動模式", command=start_auto_mode, font=('Arial', 12, 'bold'), bg='lightgreen')
auto_start_button.pack(side=tk.LEFT, padx=10)
auto_stop_button = tk.Button(auto_control_frame, text="停止自動模式", command=stop_auto_mode, font=('Arial', 12, 'bold'), bg='red', state=tk.DISABLED)
auto_stop_button.pack(side=tk.LEFT, padx=10)
# 3. 底部動態波形圖部分
dynamic_wave_frame = tk.Frame(root)
dynamic_wave_frame.pack(pady=10)
dynamic_wave_canvas = tk.Canvas(dynamic_wave_frame, width=DYNAMIC_WAVE_W, height=DYNAMIC_WAVE_H, bg='white')
dynamic_wave_canvas.pack()
# 初始顯示 '111' 的波形
draw_single_waveform(dynamic_wave_canvas, '111')
tk.Label(dynamic_wave_frame, text="單一符號動態波形圖 (2個週期)", font=('Arial', 14, 'bold')).pack()
# 執行 Tkinter 事件迴圈
root.mainloop()
import tkinter as tk
from tkinter import messagebox
import math
# --- 全域參數設定 ---
# 8-PSK 符號定義 (角度,單位:度)
# 注意: 確保所有 8 個符號都在這裡
PSK_PHASES = {
'111': 0, # 修正:靜態圖中缺少的 111 (0度)
'110': 45,
'010': 90,
'011': 135,
'001': 180,
'000': 225,
'100': 270,
'101': 315
}
# --- 星座圖參數 (左側) ---
CONST_W = 400
CONST_H = 400
CONST_CX = CONST_W // 2
CONST_CY = CONST_H // 2
RADIUS = 150
POINT_R = 10
I_OFFSET_Y = 25
Q_OFFSET_X = 25
# --- 靜態波形圖參數 (右側 8 個) ---
ALL_WAVE_W = 350
ALL_WAVE_H = 400
SMALL_WAVE_H_PER_SYMBOL = 45 # 每個小波形的高度稍微縮小,確保 8 個都能放下
SMALL_WAVE_AMPLITUDE = 15
SMALL_FREQUENCY = 3
SMALL_SYMBOL_DURATION = 200 # 修正:將長度從 250 縮短到 200
STATIC_TEXT_OFFSET = 100 # 新增:靜態波形文字 X 軸偏移量
# --- 動態波形圖參數 (底部) ---
DYNAMIC_WAVE_W = 800
DYNAMIC_WAVE_H = 300
DYNAMIC_WAVE_CY = DYNAMIC_WAVE_H // 2
WAVE_AMPLITUDE = 90
FREQUENCY = 2
SYMBOL_DURATION = 200
DYNAMIC_WAVE_START_X = (DYNAMIC_WAVE_W - SYMBOL_DURATION) // 2 # 修正:波形繪製的起始X座標 (置中)
# --- 函數定義 ---
def draw_8psk_constellation(canvas):
"""繪製 8-PSK 星座圖。 (無變化)"""
# 1. 繪製座標軸 (I軸 和 Q軸)
canvas.create_line(CONST_CX - RADIUS - 20, CONST_CY, CONST_CX + RADIUS + 20, CONST_CY, arrow=tk.LAST, fill='black')
canvas.create_text(CONST_CX + RADIUS + 30, CONST_CY + I_OFFSET_Y, text='I', fill='black', font=('Arial', 12, 'bold'))
canvas.create_line(CONST_CX, CONST_CY + RADIUS + 20, CONST_CX, CONST_CY - RADIUS - 20, arrow=tk.LAST, fill='black')
canvas.create_text(CONST_CX + Q_OFFSET_X, CONST_CY - RADIUS - 30, text='Q', fill='black', font=('Arial', 12, 'bold'))
# 2. 繪製圓形
x1, y1 = CONST_CX - RADIUS, CONST_CY - RADIUS
x2, y2 = CONST_CX + RADIUS, CONST_CY + RADIUS
canvas.create_oval(x1, y1, x2, y2, outline='black')
# 3. 繪製 8 個符號點和標註
for bits, angle_deg in PSK_PHASES.items():
angle_rad = math.radians(angle_deg)
i_coord = CONST_CX + RADIUS * math.cos(angle_rad)
q_coord = CONST_CY - RADIUS * math.sin(angle_rad)
canvas.create_oval(i_coord - POINT_R, q_coord - POINT_R,
i_coord + POINT_R, q_coord + POINT_R,
fill='lightblue', outline='black')
offset_dist = RADIUS + 25
text_i_coord = CONST_CX + offset_dist * math.cos(angle_rad)
text_q_coord = CONST_CY - offset_dist * math.sin(angle_rad)
canvas.create_text(text_i_coord, text_q_coord, text=bits, font=('Arial', 10, 'bold'), fill='darkblue')
def draw_all_waveforms(canvas):
"""繪製右側 8 個靜態波形圖 (修正:補齊 111 符號並修正標註位置)。"""
# 清空畫布以防萬一
canvas.delete("all")
# 標題
canvas.create_text(ALL_WAVE_W // 2, 10, text="8-PSK 符號相位對照 (全部 8 個)", font=('Arial', 12, 'bold'), fill='red')
y_offset = 30
# 根據位元組排序,確保 111 在列
sorted_symbols = sorted(PSK_PHASES.items(), key=lambda item: item[0])
for bits, phase_deg in sorted_symbols:
phase_rad = math.radians(phase_deg)
# 修正 2: 標註位元組和相位,並使用 STATIC_TEXT_OFFSET 靠右
canvas.create_text(STATIC_TEXT_OFFSET, y_offset + SMALL_WAVE_H_PER_SYMBOL // 2,
text=f"{bits} ({phase_deg}°)", anchor=tk.E, # anchor=tk.E 確保文字右側對齊
font=('Arial', 9), fill='darkgreen')
# 波形繪製起始 X 座標 (在標註右側)
start_x = STATIC_TEXT_OFFSET + 10
wave_cy = y_offset + SMALL_WAVE_H_PER_SYMBOL // 2
# 繪製時間軸
canvas.create_line(start_x, wave_cy, start_x + SMALL_SYMBOL_DURATION, wave_cy, fill='gray', width=1)
waveform_points = []
for t in range(SMALL_SYMBOL_DURATION):
t_norm = t / SMALL_SYMBOL_DURATION
# S(t) = A * cos(2*pi*f*t + phi)
amplitude = SMALL_WAVE_AMPLITUDE * math.cos(2 * math.pi * SMALL_FREQUENCY * t_norm + phase_rad)
x = start_x + t
y = wave_cy - amplitude
waveform_points.extend([x, y])
canvas.create_line(waveform_points, fill='blue', smooth=True, width=1)
# 移動到下一個波形的位置
y_offset += SMALL_WAVE_H_PER_SYMBOL
def draw_single_waveform(canvas, bits_input):
"""根據輸入的位元組繪製單個符號的 8-PSK 波形 (修正:波形置中並調整標註位置)。"""
canvas.delete("all")
# 1. 繪製座標軸 (使用置中的座標)
canvas.create_line(DYNAMIC_WAVE_START_X - 20, DYNAMIC_WAVE_CY, DYNAMIC_WAVE_START_X + SYMBOL_DURATION + 20, DYNAMIC_WAVE_CY, fill='black', width=2)
canvas.create_text(DYNAMIC_WAVE_START_X - 30, DYNAMIC_WAVE_CY, text='0', fill='black', anchor=tk.E) # 原點標註
canvas.create_text(20, 20, text='Amplitude (A)', anchor=tk.W) # 振幅標註
canvas.create_text(DYNAMIC_WAVE_W - 10, DYNAMIC_WAVE_CY + 10, text='Time (t)', anchor=tk.E) # 時間標註
# 2. 繪製符號週期邊界
canvas.create_line(DYNAMIC_WAVE_START_X, 20, DYNAMIC_WAVE_START_X, DYNAMIC_WAVE_H - 20, dash=(4, 2), fill='gray')
canvas.create_line(DYNAMIC_WAVE_START_X + SYMBOL_DURATION, 20, DYNAMIC_WAVE_START_X + SYMBOL_DURATION, DYNAMIC_WAVE_H - 20, dash=(4, 2), fill='gray')
# 3. 標註符號 (修正 3: 將紅色的文字進一步向右移動)
phase_deg = PSK_PHASES[bits_input]
# 計算靠右邊界附近的 x 座標,例如在 90% 的位置,並使用 anchor=tk.E
text_x_coord = DYNAMIC_WAVE_START_X + SYMBOL_DURATION + 30
canvas.create_text(text_x_coord, 40,
text=f"Symbol: {bits_input} | Phase: {phase_deg}° | Freq: {FREQUENCY} cycles/symbol",
font=('Arial', 12, 'bold'), fill='red', anchor=tk.E)
phase_rad = math.radians(phase_deg)
# 4. 繪製波形 (修正 4: 使用置中的起始座標)
current_x = DYNAMIC_WAVE_START_X
waveform_points = []
for t in range(SYMBOL_DURATION):
t_norm = t / SYMBOL_DURATION
amplitude = WAVE_AMPLITUDE * math.cos(2 * math.pi * FREQUENCY * t_norm + phase_rad)
x = current_x + t
y = DYNAMIC_WAVE_CY - amplitude
waveform_points.extend([x, y])
canvas.create_line(waveform_points, fill='blue', smooth=True, width=2)
def update_waveform():
"""從輸入框讀取位元組,驗證後更新波形圖。"""
input_bits = input_entry.get().strip()
if input_bits not in PSK_PHASES:
messagebox.showerror("輸入錯誤", "請輸入一個有效的 3-bit 位元組 (000~111)。")
return
draw_single_waveform(dynamic_wave_canvas, input_bits)
# --- 主程式 ---
root = tk.Tk()
root.title("8-PSK 調變分析工具 (美化修正版)")
# 1. 頂部主框架 (包含星座圖和靜態波形)
top_frame = tk.Frame(root)
top_frame.pack(pady=10)
# 1A. 星座圖框架 (左側)
const_frame = tk.Frame(top_frame)
const_frame.pack(side=tk.LEFT, padx=10)
const_canvas = tk.Canvas(const_frame, width=CONST_W, height=CONST_H, bg='white')
const_canvas.pack()
draw_8psk_constellation(const_canvas)
tk.Label(const_frame, text="8-PSK 星座圖", font=('Arial', 14, 'bold')).pack()
# 1B. 靜態波形框架 (右側)
all_wave_frame = tk.Frame(top_frame)
all_wave_frame.pack(side=tk.LEFT, padx=10)
all_wave_canvas = tk.Canvas(all_wave_frame, width=ALL_WAVE_W, height=ALL_WAVE_H, bg='lightyellow')
all_wave_canvas.pack()
draw_all_waveforms(all_wave_canvas)
# 2. 中間控制與輸入部分
control_frame = tk.Frame(root)
control_frame.pack(pady=10)
tk.Label(control_frame, text="輸入 3-bit 位元組 (000~111):", font=('Arial', 12)).pack(side=tk.LEFT, padx=5)
input_entry = tk.Entry(control_frame, width=5, font=('Arial', 12))
input_entry.pack(side=tk.LEFT, padx=5)
update_button = tk.Button(control_frame, text="顯示動態波形", command=update_waveform, font=('Arial', 12, 'bold'), bg='lightgreen')
update_button.pack(side=tk.LEFT, padx=10)
# 3. 底部動態波形圖部分
dynamic_wave_frame = tk.Frame(root)
dynamic_wave_frame.pack(pady=10)
dynamic_wave_canvas = tk.Canvas(dynamic_wave_frame, width=DYNAMIC_WAVE_W, height=DYNAMIC_WAVE_H, bg='white')
dynamic_wave_canvas.pack()
# 初始顯示 '111' 的波形
draw_single_waveform(dynamic_wave_canvas, '111')
tk.Label(dynamic_wave_frame, text="單一符號動態波形圖 (2個週期)", font=('Arial', 14, 'bold')).pack()
# 執行 Tkinter 事件迴圈
root.mainloop()






沒有留言:
張貼留言