2026年1月19日 星期一

八木天線(Yagi-Uda Antenna)的計算工具

 八木天線(Yagi-Uda Antenna)的計算工具

設計一個八木天線(Yagi-Uda Antenna)的計算工具,首先需要理解其基本結構:

  1. 反射器 (Reflector):通常只有 1 節,長度最長。

  2. 驅動元件 (Driven Element / Folded Dipole):饋電點,長度約為 $0.5 \lambda$

  3. 引向器 (Directors):可有多節,長度較短,用於增強方向性。

它會根據輸入的頻率與節數,自動計算並顯示每一節的長度以及節與節之間的間距(基於標準 DL6WU 演算法模型)。


八木天線原理圖解

程式設計重點說明:

  1. 動態節數計算

    • 使用者輸入「總節數」後,程式會自動扣除反射器與驅動元件,剩下的計算為引向器(Directors)。

  2. 長度公式與縮減係數 (Velocity Factor)

    • 反射器 (Reflector):約 0.495λ。它是最長的,負責將電波反射回去。

    • 驅動元件 (Driven Element):約 0.473 λ。這是天線接收或發射信號的核心。

    • 引向器 (Directors):約 0.44λ 左右。在多節設計中,引向器通常會越往末端越短一點點,以增加寬頻響應。

  3. 間距 (Spacing)

    • 反射器到驅動元件的標準間距約為 0.125 λ0.2λ

    • 引向器之間的間距通常固定在 0.2 λ 左右。



如何使用:

  1. 執行程式後,輸入您想要的頻率(例如 433 代表 433 MHz 的無線電頻段)。

  2. 輸入節數,例如 10

  3. 點擊「開始計算」,表格會立即列出從第一節到第十節的精確長度(公釐)與累計距離。

import tkinter as tk

from tkinter import ttk, messagebox


class YagiCalculatorApp:

    def __init__(self, root):

        self.root = root

        self.root.title("八木天線 (Yagi-Uda) 設計工具")

        self.root.geometry("700x650")


        # 標題

        tk.Label(self.root, text="Yagi-Uda Antenna Designer", font=("Arial", 16, "bold")).pack(pady=10)


        # 輸入區域

        input_frame = tk.LabelFrame(self.root, text=" 輸入設計參數 ", padx=10, pady=10)

        input_frame.pack(fill="x", padx=20, pady=5)


        tk.Label(input_frame, text="中心頻率 (MHz):").grid(row=0, column=0, sticky="w")

        self.entry_freq = tk.Entry(input_frame)

        self.entry_freq.insert(0, "433.0")

        self.entry_freq.grid(row=0, column=1, padx=5, pady=5)


        tk.Label(input_frame, text="總節數 (至少 3 節):").grid(row=1, column=0, sticky="w")

        self.entry_elements = tk.Entry(input_frame)

        self.entry_elements.insert(0, "5")

        self.entry_elements.grid(row=1, column=1, padx=5, pady=5)


        # 按鈕

        self.btn_calc = tk.Button(input_frame, text="開始計算", command=self.calculate_yagi, bg="#4CAF50", fg="white", width=15)

        self.btn_calc.grid(row=2, column=0, columnspan=2, pady=10)


        # 結果顯示區域

        result_frame = tk.Frame(self.root)

        result_frame.pack(fill="both", expand=True, padx=20, pady=10)


        # 左側列表:顯示長度與間距

        columns = ("element", "length", "spacing", "total_dist")

        self.tree = ttk.Treeview(result_frame, columns=columns, show="headings")

        self.tree.heading("element", text="元件名稱")

        self.tree.heading("length", text="長度 (mm)")

        self.tree.heading("spacing", text="與前一節距離 (mm)")

        self.tree.heading("total_dist", text="累計距離 (mm)")

        

        # 設定欄寬

        self.tree.column("element", width=120, anchor="center")

        self.tree.column("length", width=120, anchor="center")

        self.tree.column("spacing", width=120, anchor="center")

        self.tree.column("total_dist", width=120, anchor="center")

        

        self.tree.pack(side="left", fill="both", expand=True)


        # 捲軸

        scrollbar = ttk.Scrollbar(result_frame, orient="vertical", command=self.tree.yview)

        self.tree.configure(yscroll=scrollbar.set)

        scrollbar.pack(side="right", fill="y")


    def calculate_yagi(self):

        try:

            freq = float(self.entry_freq.get())

            num_elements = int(self.entry_elements.get())

            

            if num_elements < 3:

                messagebox.showwarning("輸入錯誤", "八木天線至少需要 3 節(反射器、驅動元件、引向器)。")

                return


            # 清除舊數據

            for i in self.tree.get_children():

                self.tree.delete(i)


            # 波長 λ (mm)

            wavelength = 299792 / freq  # c / f

            

            # 1. 反射器 (Reflector)

            refl_len = wavelength * 0.495

            self.tree.insert("", "end", values=("反射器 (Refl)", f"{refl_len:.2f}", "0.00", "0.00"))


            # 2. 驅動元件 (Driven Element)

            driven_len = wavelength * 0.473

            driven_space = wavelength * 0.125

            self.tree.insert("", "end", values=("驅動元件 (DE)", f"{driven_len:.2f}", f"{driven_space:.2f}", f"{driven_space:.2f}"))


            # 3. 引向器 (Directors)

            total_dist = driven_space

            num_directors = num_elements - 2

            

            for i in range(1, num_directors + 1):

                # 簡單 DL6WU 模型估算:引向器長度約為 0.44λ,間距約為 0.2λ

                dir_len = wavelength * (0.450 - (i * 0.005)) # 越往後越短

                dir_space = wavelength * 0.2

                total_dist += dir_space

                self.tree.insert("", "end", values=(f"引向器 D{i}", f"{dir_len:.2f}", f"{dir_space:.2f}", f"{total_dist:.2f}"))


        except ValueError:

            messagebox.showerror("錯誤", "請輸入有效的數字格式。")


if __name__ == "__main__":

    root = tk.Tk()

    app = YagiCalculatorApp(root)

    root.mainloop()







import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib
import platform

# --- 設定 Matplotlib 中文字體,解決 Glyph 錯誤 ---
def set_mpl_chinese_font():
    system = platform.system()
    try:
        if system == "Windows":
            matplotlib.rc('font', family='Microsoft JhengHei')
        elif system == "Darwin":
            matplotlib.rc('font', family='Arial Unicode MS')
        else:
            matplotlib.rc('font', family='Noto Sans CJK JP')
        matplotlib.rcParams['axes.unicode_minus'] = False
    except:
        pass # 如果找不到字體,則使用預設值避免程式崩潰

set_mpl_chinese_font()

class YagiCalculatorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("八木天線 (Yagi-Uda) 專業設計工具")
        self.root.geometry("1100x700")

        # 主容器
        main_frame = tk.Frame(self.root, padx=10, pady=10)
        main_frame.pack(fill="both", expand=True)

        # --- 左側控制區 ---
        control_frame = tk.Frame(main_frame, width=400)
        control_frame.pack(side="left", fill="y", padx=(0, 10))

        tk.Label(control_frame, text="設計參數輸入", font=("Arial", 14, "bold")).pack(pady=10)

        input_frame = tk.LabelFrame(control_frame, text=" 物理規格 ", padx=10, pady=10)
        input_frame.pack(fill="x", pady=5)

        tk.Label(input_frame, text="中心頻率 (MHz):").grid(row=0, column=0, sticky="w")
        self.entry_freq = tk.Entry(input_frame)
        self.entry_freq.insert(0, "433.0")
        self.entry_freq.grid(row=0, column=1, padx=5, pady=5)

        tk.Label(input_frame, text="總節數 (Elements):").grid(row=1, column=0, sticky="w")
        self.entry_elements = tk.Entry(input_frame)
        self.entry_elements.insert(0, "5")
        self.entry_elements.grid(row=1, column=1, padx=5, pady=5)

        self.btn_calc = tk.Button(input_frame, text="計算並更新圖表", command=self.calculate_yagi, 
                                 bg="#28a745", fg="white", font=("Arial", 10, "bold"), pady=5)
        self.btn_calc.grid(row=2, column=0, columnspan=2, sticky="ew", pady=10)

        # 表格顯示
        self.tree = ttk.Treeview(control_frame, columns=("element", "length", "total_dist"), show="headings", height=15)
        self.tree.heading("element", text="元件")
        self.tree.heading("length", text="長度(mm)")
        self.tree.heading("total_dist", text="位置(mm)")
        self.tree.column("element", width=100, anchor="center")
        self.tree.column("length", width=100, anchor="center")
        self.tree.column("total_dist", width=100, anchor="center")
        self.tree.pack(fill="both", expand=True)

        # --- 右側繪圖區 ---
        self.plot_frame = tk.LabelFrame(main_frame, text=" 天線佈局預覽 ")
        self.plot_frame.pack(side="right", fill="both", expand=True)

        self.fig, self.ax = plt.subplots(figsize=(6, 8))
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
        self.canvas.get_tk_widget().pack(fill="both", expand=True)

        # 初始執行一次
        self.calculate_yagi()

    def calculate_yagi(self):
        try:
            freq = float(self.entry_freq.get())
            num = int(self.entry_elements.get())
            if num < 3:
                messagebox.showwarning("提醒", "八木天線至少需要 3 節元件")
                return

            for i in self.tree.get_children(): self.tree.delete(i)
            
            wavelength = 299792 / freq
            elements_data = []

            # 1. 反射器
            refl_len = wavelength * 0.495
            pos = 0.0
            elements_data.append(("反射器", refl_len, pos))
            self.tree.insert("", "end", values=("反射器", f"{refl_len:.1f}", f"{pos:.1f}"))

            # 2. 驅動元件
            de_len = wavelength * 0.473
            pos += wavelength * 0.125
            elements_data.append(("驅動元件", de_len, pos))
            self.tree.insert("", "end", values=("驅動元件", f"{de_len:.1f}", f"{pos:.1f}"))

            # 3. 引向器
            for i in range(1, num - 1):
                d_len = wavelength * (0.440 - (i * 0.005))
                pos += wavelength * 0.2
                elements_data.append((f"引向器 D{i}", d_len, pos))
                self.tree.insert("", "end", values=(f"引向器 D{i}", f"{d_len:.1f}", f"{pos:.1f}"))

            self.update_plot(elements_data)

        except Exception as e:
            messagebox.showerror("錯誤", f"輸入數據無效: {e}")

    def update_plot(self, data):
        self.ax.clear()
        
        # 繪製支撐桿 (Boom)
        max_pos = data[-1][2]
        self.ax.plot([0, max_pos], [0, 0], color='gray', linestyle='--', linewidth=2, alpha=0.5)

        for name, length, pos in data:
            half = length / 2
            # 判斷顏色
            color = 'red' if "驅動" in name else ('blue' if "反射" in name else 'green')
            
            # 繪製天線元件(垂直線)
            self.ax.plot([pos, pos], [-half, half], color=color, linewidth=4, solid_capstyle='round')
            
            # 標註文字
            self.ax.text(pos, half + (length*0.05), name, rotation=45, fontsize=9, ha='left')
            self.ax.text(pos, -half - (length*0.1), f"{length:.0f}mm", fontsize=8, ha='center', color='gray')

        self.ax.set_title(f"八木天線設計圖 ({self.entry_freq.get()} MHz)", pad=20)
        self.ax.set_xlabel("支撐桿位置 (mm)")
        self.ax.set_ylabel("元件長度方向 (mm)")
        self.ax.set_aspect('equal')
        self.ax.grid(True, alpha=0.3)
        self.fig.tight_layout()
        self.canvas.draw()

if __name__ == "__main__":
    root = tk.Tk()
    app = YagiCalculatorApp(root)
    root.mainloop()

沒有留言:

張貼留言

Dynamic Framed Slotted ALOHA (DFSA) 防撞方法

 Dynamic Framed Slotted ALOHA (DFSA)防撞方法 DFSA 的核心邏輯: 動態框長 (Dynamic Frame Size) :閱讀器會根據上一輪的結果(碰撞次數、空閒次數)來 動態調整 下一輪的時槽數(框長 $L$ )。 效率優化 :當標籤很多時...