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 字樣會變成紅色


  • MQTT BOX





    MQTTGO.io


    Python+TKinter


    ESP32 結合 MQTT 範例主要是實現物聯網數據的發布 (Publish) 和訂閱 (Subscribe),例如透過 Arduino IDE 寫程式讓 ESP32 連上 WiFi,再連接 EMQX 提供的公開測試 MQTT broker, 範例包括讀取溫濕度感測器 (如 DHT11/DHT22) 將數據發布到特定主題 (Topic),或接收控制指令並控制 LED RELAY 等,關鍵在於使用 PubSubClient 函式庫與 ESP-IDF 的 ESP-MQTT 範例來處理 TCP/SSL/WebSocket 連線和消息收發。 
    核心概念
    • MQTT Broker: 訊息的中介站 (例如 test.mosquitto.org)。
    • Topic: 訊息的分類主題 (例如 home/livingroom/temperature)。
    • Publish (發布者): 將感測器數據發送到 Topic。
    • Subscribe (訂閱者): 訂閱 Topic 接收消息。 






    Python  程式  與  Wokwi Arduino IDE 程式

    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()



    WOKWI程式

    #include <WiFi.h>
    #include <PubSubClient.h>
    #include <DHTesp.h>

    // --- 配置參數 ---
    const char* ssid = "Wokwi-GUEST";     // Wokwi 專用 WiFi
    const char* password = "";
    const char* mqtt_server = "mqttgo.io";

    // 定義接腳
    const int DHT_PIN = 15;
    const int RELAY_PINS[] = {23, 22, 21, 19, 18, 5};
    const char* RELAY_TOPICS[] = {
      "alex9ufo/relay1", "alex9ufo/relay2", "alex9ufo/relay3",
      "alex9ufo/relay4", "alex9ufo/relay5", "alex9ufo/relay6"
    };

    DHTesp dhtSensor;
    WiFiClient espClient;
    PubSubClient client(espClient);

    unsigned long lastMsg = 0;

    void setup_wifi() {
      delay(10);
      Serial.println();
      Serial.print("Connecting to ");
      Serial.println(ssid);

      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, password);

      while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
      }
      Serial.println("\nWiFi connected");
    }

    // 接收來自 Python (MQTT) 的指令
    void callback(char* topic, byte* payload, unsigned int length) {
      String message;
      for (int i = 0; i < length; i++) {
        message += (char)payload[i];
      }
     
      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      Serial.println(message);

      // 比對 Topic 並切換對應的繼電器接腳
      for (int i = 0; i < 6; i++) {
        if (String(topic) == RELAY_TOPICS[i]) {
          if (message == "ON") {
            digitalWrite(RELAY_PINS[i], HIGH);
          } else {
            digitalWrite(RELAY_PINS[i], LOW);
          }
        }
      }
    }

    void reconnect() {
      while (!client.connected()) {
        Serial.print("Attempting MQTT connection...");
        String clientId = "ESP32Client-" + String(random(0xffff), HEX);
        if (client.connect(clientId.c_str())) {
          Serial.println("connected");
          // 訂閱所有繼電器 Topic
          for (int i = 0; i < 6; i++) {
            client.subscribe(RELAY_TOPICS[i]);
          }
        } else {
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
          delay(5000);
        }
      }
    }

    void setup() {
      Serial.begin(115200);
     
      // 初始化繼電器接腳為輸出
      for (int i = 0; i < 6; i++) {
        pinMode(RELAY_PINS[i], OUTPUT);
        digitalWrite(RELAY_PINS[i], LOW); // 預設關閉
      }

      dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
      setup_wifi();
      client.setServer(mqtt_server, 1883);
      client.setCallback(callback);
    }

    void loop() {
      if (!client.connected()) {
        reconnect();
      }
      client.loop();

      // 每 2 秒讀取一次溫濕度並上傳
      unsigned long now = millis();
      if (now - lastMsg > 2000) {
        lastMsg = now;
        TempAndHumidity data = dhtSensor.getTempAndHumidity();

        if (isnan(data.temperature) || isnan(data.humidity)) {
          Serial.println("Failed to read from DHT sensor!");
          return;
        }

        // 發布溫度
        client.publish("alex9ufo/dht22/temp", String(data.temperature, 2).c_str());
        // 發布濕度
        client.publish("alex9ufo/dht22/humi", String(data.humidity, 2).c_str());

        Serial.print("Temp: "); Serial.print(data.temperature);
        Serial.print("C, Humi: "); Serial.print(data.humidity); Serial.println("%");
      }
    }

    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%...