線上編程-Wokwi
2025年12月25日 星期四
2025年12月24日 星期三
WOKWI ESP32 DHT22 溫度濕度 上下限設定 (MQTT 工具)
WOKWI ESP32 DHT22 溫度濕度 上下限設定
溫度下限值:現在與「繼電器 1」處於 row 2。
溫度上限值:現在與「繼電器 3」處於 row 4。
濕度下限值:現在與「繼電器 4」處於 row 6。
濕度上限值:現在與「繼電器 6」處於 row 8。
字體顏色:當繼電器狀態切換為 ON 時,對應的 OFF/ON 字樣會變成紅色。
ESP-MQTT 範例來處理 TCP/SSL/WebSocket 連線和消息收發。 - MQTT Broker: 訊息的中介站 (例如
test.mosquitto.org)。 - Topic: 訊息的分類主題 (例如
home/livingroom/temperature)。 - Publish (發布者): 將感測器數據發送到 Topic。
- Subscribe (訂閱者): 訂閱 Topic 接收消息。
import tkinter as tk
from tkinter import font
import paho.mqtt.client as mqtt
# --- MQTT 配置 ---
BROKER = "mqttgo.io"
PORT = 1883
TOPICS = {
"temp": "alex9ufo/dht22/temp",
"humi": "alex9ufo/dht22/humi",
}
RELAY_TOPICS = [
"alex9ufo/relay1", "alex9ufo/relay2", "alex9ufo/relay3",
"alex9ufo/relay4", "alex9ufo/relay5", "alex9ufo/relay6"
]
class MQTTApp:
def __init__(self, root):
self.root = root
self.root.title("ESP32 監控系統")
self.root.geometry("700x500")
self.label_font = font.Font(size=12)
self.val_font = font.Font(size=12, weight="bold")
# 變數初始化
self.current_temp = tk.StringVar(value="--")
self.current_humi = tk.StringVar(value="--")
self.mqtt_status = tk.StringVar(value="斷線")
self.relay_status = [tk.StringVar(value="OFF") for _ in range(6)]
self.relay_labels = []
self.setup_ui()
self.client = mqtt.Client()
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
try:
self.client.connect(BROKER, PORT, 60)
self.client.loop_start()
except:
self.mqtt_status.set("連線失敗")
def setup_ui(self):
# 設定整體網格權重
for i in range(12): self.root.grid_rowconfigure(i, weight=1)
for i in range(4): self.root.grid_columnconfigure(i, weight=1)
# --- 第一行:目前溫濕度 ---
tk.Label(self.root, text="目前溫度:", font=self.label_font).grid(row=0, column=0, sticky="e")
tk.Label(self.root, textvariable=self.current_temp, font=self.val_font, fg="blue", relief="sunken", width=12).grid(row=0, column=1, padx=5)
tk.Label(self.root, text="目前濕度:", font=self.label_font).grid(row=0, column=2, sticky="e")
tk.Label(self.root, textvariable=self.current_humi, font=self.val_font, fg="blue", relief="sunken", width=12).grid(row=0, column=3, padx=5)
# --- 繼電器與設定值佈局 (依圖片位置) ---
pins = ["23", "22", "21", "19", "18", "5"]
# 1. 繼電器1 (23) 與 溫度下限值 (Row 2)
tk.Label(self.root, text=f"繼電器1 ({pins[0]})狀態:", font=self.label_font).grid(row=2, column=0, sticky="e")
lbl1 = tk.Label(self.root, textvariable=self.relay_status[0], font=self.val_font, relief="sunken", width=12)
lbl1.grid(row=2, column=1, padx=5); self.relay_labels.append(lbl1)
tk.Label(self.root, text="溫度下限值:", font=self.label_font).grid(row=2, column=2, sticky="e")
self.ent_t_low = tk.Entry(self.root, width=15); self.ent_t_low.insert(0, "20"); self.ent_t_low.grid(row=2, column=3)
# 2. 繼電器2 (22) (Row 3)
tk.Label(self.root, text=f"繼電器2 ({pins[1]})狀態:", font=self.label_font).grid(row=3, column=0, sticky="e")
lbl2 = tk.Label(self.root, textvariable=self.relay_status[1], font=self.val_font, relief="sunken", width=12)
lbl2.grid(row=3, column=1, padx=5); self.relay_labels.append(lbl2)
# 3. 繼電器3 (21) 與 溫度上限值 (Row 4)
tk.Label(self.root, text=f"繼電器3 ({pins[2]})狀態:", font=self.label_font).grid(row=4, column=0, sticky="e")
lbl3 = tk.Label(self.root, textvariable=self.relay_status[2], font=self.val_font, relief="sunken", width=12)
lbl3.grid(row=4, column=1, padx=5); self.relay_labels.append(lbl3)
tk.Label(self.root, text="溫度上限值:", font=self.label_font).grid(row=4, column=2, sticky="e")
self.ent_t_high = tk.Entry(self.root, width=15); self.ent_t_high.insert(0, "30"); self.ent_t_high.grid(row=4, column=3)
# --- 中間空行 (Row 5) ---
# 4. 繼電器4 (19) 與 濕度下限值 (Row 6)
tk.Label(self.root, text=f"繼電器4 ({pins[3]})狀態:", font=self.label_font).grid(row=6, column=0, sticky="e")
lbl4 = tk.Label(self.root, textvariable=self.relay_status[3], font=self.val_font, relief="sunken", width=12)
lbl4.grid(row=6, column=1, padx=5); self.relay_labels.append(lbl4)
tk.Label(self.root, text="濕度下限值:", font=self.label_font).grid(row=6, column=2, sticky="e")
self.ent_h_low = tk.Entry(self.root, width=15); self.ent_h_low.insert(0, "40"); self.ent_h_low.grid(row=6, column=3)
# 5. 繼電器5 (18) (Row 7)
tk.Label(self.root, text=f"繼電器5 ({pins[4]})狀態:", font=self.label_font).grid(row=7, column=0, sticky="e")
lbl5 = tk.Label(self.root, textvariable=self.relay_status[4], font=self.val_font, relief="sunken", width=12)
lbl5.grid(row=7, column=1, padx=5); self.relay_labels.append(lbl5)
# 6. 繼電器6 (5) 與 濕度上限值 (Row 8)
tk.Label(self.root, text=f"繼電器6 ({pins[5]})狀態:", font=self.label_font).grid(row=8, column=0, sticky="e")
lbl6 = tk.Label(self.root, textvariable=self.relay_status[5], font=self.val_font, relief="sunken", width=12)
lbl6.grid(row=8, column=1, padx=5); self.relay_labels.append(lbl6)
tk.Label(self.root, text="濕度上限值:", font=self.label_font).grid(row=8, column=2, sticky="e")
self.ent_h_high = tk.Entry(self.root, width=15); self.ent_h_high.insert(0, "70"); self.ent_h_high.grid(row=8, column=3)
# --- 底部:MQTT 連線狀態 ---
tk.Label(self.root, text="MQTT連線狀態:", font=self.label_font).grid(row=10, column=0, sticky="e")
tk.Label(self.root, textvariable=self.mqtt_status, font=self.val_font, fg="green", relief="sunken", width=12).grid(row=10, column=1, padx=5)
def on_connect(self, client, userdata, flags, rc):
if rc == 0:
self.mqtt_status.set("已連線")
client.subscribe(TOPICS["temp"])
client.subscribe(TOPICS["humi"])
def on_message(self, client, userdata, msg):
try:
val = float(msg.payload.decode())
if msg.topic == TOPICS["temp"]:
self.current_temp.set(f"{val} °C")
self.check_logic(val, "temp")
elif msg.topic == TOPICS["humi"]:
self.current_humi.set(f"{val} %")
self.check_logic(val, "humi")
except: pass
def check_logic(self, current_val, mode):
try:
if mode == "temp":
low, high = float(self.ent_t_low.get()), float(self.ent_t_high.get())
indices = [0, 1, 2]
else:
low, high = float(self.ent_h_low.get()), float(self.ent_h_high.get())
indices = [3, 4, 5]
states = ["OFF", "OFF", "OFF"]
if current_val < low: states[0] = "ON"
elif current_val > high: states[2] = "ON"
else: states[1] = "ON"
for i, idx in enumerate(indices):
self.relay_status[idx].set(states[i])
# 狀態為 ON 時顯示紅色
self.relay_labels[idx].config(fg="red" if states[i] == "ON" else "black")
self.client.publish(RELAY_TOPICS[idx], states[i])
except: pass
if __name__ == "__main__":
root = tk.Tk()
app = MQTTApp(root)
root.mainloop()
2025年12月22日 星期一
LFSR (Linear Feedback Shift Registers)
LFSR (Linear Feedback Shift Registers)
Galois LFSR 在硬體實作中更受歡迎,主要是因為它的異或門(XOR gate)是並聯位在暫存器之間,電路延遲(Propagation Delay)較低,時鐘頻率可以跑得更高;而 Fibonacci 的 XOR 則是串聯在回授路徑上。
Galois vs Fibonacci LFSR 實作模擬
這個程式會顯示兩個 4-bit 的移位暫存器(多項式採用 x^4 + x^3 + 1),點擊按鈕後可以同步觀察兩者的位移與 XOR 運算的差異。
Galois LFSR (左側):
原理:當輸出位元為
1時,特定的 "Tap" 位元會在移位過程中與輸出值進行 XOR。視覺觀察:你會發現它的 XOR 運算是「由外往內」注入的。
Fibonacci LFSR (右側):
原理:選定多個位元(Taps)進行 XOR 運算後,將結果回傳到第一個位元。
視覺觀察:這是一般教科書最常教的方式,所有運算都在「回授路徑」上完成。
import tkinter as tk
from tkinter import ttk
class LFSRVisualizer:
def __init__(self, root):
self.root = root
self.root.title("LFSR 實作對比: Galois vs Fibonacci (x^4 + x^3 + 1)")
# 初始狀態 (4-bit, 不能為全 0)
self.galois_state = [1, 0, 0, 0]
self.fibonacci_state = [1, 0, 0, 0]
self.setup_ui()
def setup_ui(self):
# 使用主要容器
main_frame = ttk.Frame(self.root, padding="20")
main_frame.grid(row=0, column=0)
# 標題與多項式說明
title_label = ttk.Label(main_frame, text="多項式: x⁴ + x³ + 1", font=("Arial", 14, "bold"))
title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20))
# --- 左側: Galois ---
galois_frame = ttk.LabelFrame(main_frame, text=" Galois LFSR (內部異或) ", padding="10")
galois_frame.grid(row=1, column=0, padx=15)
# Galois 電路示意圖 (簡化文字版)
self.g_diag = tk.Label(galois_frame, text="[IN] → [b0] → [b1] → [b2] → (XOR) → [b3] → [OUT]\n"
" ↑______________|",
font=("Courier", 10), justify=tk.LEFT, bg="#f0f0f0")
self.g_diag.pack(pady=5)
self.galois_canvas = tk.Canvas(galois_frame, width=320, height=100, bg="white")
self.galois_canvas.pack()
# --- 右側: Fibonacci ---
fib_frame = ttk.LabelFrame(main_frame, text=" Fibonacci LFSR (外部回授) ", padding="10")
fib_frame.grid(row=1, column=1, padx=15)
# Fibonacci 電路示意圖 (簡化文字版)
self.f_diag = tk.Label(fib_frame, text=" ____________(XOR) <--- [b2] <--- [b3] [OUT]\n"
" ↓ |___________________|\n"
"[IN] → [b0] → [b1] → [b2] → [b3]",
font=("Courier", 10), justify=tk.LEFT, bg="#f0f0f0")
self.f_diag.pack(pady=5)
self.fib_canvas = tk.Canvas(fib_frame, width=320, height=100, bg="white")
self.fib_canvas.pack()
# --- 控制區 ---
btn_frame = ttk.Frame(main_frame)
btn_frame.grid(row=2, column=0, columnspan=2, pady=20)
self.step_btn = ttk.Button(btn_frame, text=" 執行一個移位 (Step) ", command=self.step)
self.step_btn.pack(side=tk.LEFT, padx=5)
self.reset_btn = ttk.Button(btn_frame, text=" 重設 (Reset) ", command=self.reset)
self.reset_btn.pack(side=tk.LEFT, padx=5)
self.draw_registers()
def draw_single_lfsr(self, canvas, state, highlight_idx=None):
canvas.delete("all")
for i in range(4):
x0, y0 = 40 + (i * 60), 30
x1, y1 = x0 + 45, 75
# 繪製暫存器方框
color = "#e1f5fe" if i != highlight_idx else "#fff9c4"
canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="black", width=2)
# 數值
canvas.create_text(x0 + 22, y0 + 22, text=str(state[i]), font=("Arial", 18, "bold"))
# 索引
canvas.create_text(x0 + 22, y0 - 10, text=f"b{i}", font=("Arial", 10))
# 箭頭
if i < 3:
canvas.create_line(x1, (y0+y1)/2, x1+15, (y0+y1)/2, arrow=tk.LAST)
def draw_registers(self):
self.draw_single_lfsr(self.galois_canvas, self.galois_state)
self.draw_single_lfsr(self.fib_canvas, self.fib_state_visual())
def fib_state_visual(self):
return self.fibonacci_state
def step(self):
# 1. Galois Logic (x^4 + x^3 + 1)
# 輸出位元是最後一格 (b3)
out = self.galois_state[3]
new_g = [0] * 4
new_g[0] = out # b3 回授到 b0
new_g[1] = self.galois_state[0] # 正常移位
new_g[2] = self.galois_state[1] # 正常移位
new_g[3] = self.galois_state[2] ^ out # Tap 在此:b2 XOR b3(out)
self.galois_state = new_g
# 2. Fibonacci Logic (x^4 + x^3 + 1)
# Tap 在 b2(x^3) 與 b3(x^4)
feedback = self.fibonacci_state[2] ^ self.fibonacci_state[3]
new_f = [feedback] + self.fibonacci_state[:-1]
self.fibonacci_state = new_f
self.draw_registers()
def reset(self):
self.galois_state = [1, 0, 0, 0]
self.fibonacci_state = [1, 0, 0, 0]
self.draw_registers()
if __name__ == "__main__":
root = tk.Tk()
app = LFSRVisualizer(root)
root.mainloop()
2025年12月6日 星期六
8-QAM Signal 4 Phases 2 Amplitudes + 8PSK
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()
線上編程-Wokwi
線上編程-Wokwi https://sites.google.com/ism.edu.mo/f4dat/topic1-arduino%E5%9F%BA%E6%9C%AC%E6%87%89%E7%94%A8/1-0-%E7%B7%9A%E4%B8%8A%E7%B7%A8%E7%...
-
python pip 不是内部或外部命令 -- 解決方法 要安裝 Pyqt5 1. 首先,開啟命令提示字元。 2. 輸入 pip3 install pyqt5 好像不能執行 ! ! 錯誤顯示 : ‘ pip3 ’ 不是內部或外部命令、可執行的程式或批...
-
課程講義 下載 11/20 1) PPT 下載 + 程式下載 http://www.mediafire.com/file/cru4py7e8pptfda/106%E5%8B%A4%E7%9B%8A2-1.rar 11/27 2) PPT 下載...
-
• 認 識 PreFix、InFix、PostFix PreFix(前序式):* + 1 2 + 3 4 InFix(中序式): (1+2)*(3+4) PostFix(後序式):1 2 + 3 4 + * 後 序式的運算 例如: 運算時由 後序式的...

























