کد کامل شبیه‌ساز DDS با خطای R-2R

منطق مدار R-2R، رابط کاربری گرافیکی، و تولید موج سینوسی و اضافه کردن آنالیز طیفی (FFT)، این کد یک شبیه‌ساز کامل DDS با قابلیت بررسی خطای مقاومت‌ها است.

این برنامه به شما نشان می‌دهد که خطای مقاومت‌ها چطور کیفیت خروجی سینوسی را از نظر هارمونیک (THD) تحت تاثیر قرار می‌دهد.

💻 کد کامل شبیه‌ساز DDS با خطای R-2R

import tkinter as tk
from tkinter import ttk
import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

# --- منطق محاسباتی مدار R-2R (کاملا" از قبل) ---
class Real_R2R_DAC:
    """کلاس شبیه‌سازی مدار R-2R با قابلیت خطای مقاومت‌ها"""
    def __init__(self, resolution_bits, v_ref, r_base, tolerance_percent):
        self.n = resolution_bits
        self.v_ref = v_ref
        
        # تولید مقاومت‌ها با خطای تصادفی
        def get_resistor(ideal_value):
            return ideal_value * (1 + random.uniform(-tolerance_percent/100, tolerance_percent/100))

        self.resistors_shunt = [get_resistor(2 * r_base) for _ in range(self.n)]
        self.resistors_series = [get_resistor(r_base) for _ in range(self.n - 1)]
        self.r_terminator = get_resistor(2 * r_base)

    def solve_circuit(self, digital_input):
        """حل مدار برای یک کد ورودی خاص با استفاده از روش تونن تکراری"""
        # تبدیل ورودی به لیست بیت‌ها (LSB در اندیس 0)
        bits = [(digital_input >> i) & 1 for i in range(self.n)]
        
        r_looking_down = self.r_terminator
        v_looking_down = 0.0

        # حل مدار از LSB به MSB
        for i in range(self.n):
            v_in_bit = self.v_ref if bits[i] == 1 else 0.0
            r_shunt = self.resistors_shunt[i]
            
            denom = r_looking_down + r_shunt
            v_node = (v_in_bit * r_looking_down + v_looking_down * r_shunt) / denom
            r_node = (r_looking_down * r_shunt) / denom
            
            if i == self.n - 1:
                return v_node
            
            r_series = self.resistors_series[i]
            v_looking_down = v_node
            r_looking_down = r_node + r_series
        return 0.0

# --- رابط کاربری گرافیکی (GUI) و منطق DDS ---
class DDS_Simulator_App:
    def __init__(self, root):
        self.root = root
        self.root.title("شبیه‌ساز کامل DDS و تحلیل هارمونیک")
        self.root.geometry("1200x850")
        
        self.r_base = 10000 # 10k Ohm ثابت

        # --- پنل کنترل (سمت چپ) ---
        control_frame = ttk.LabelFrame(root, text="تنظیمات مدار و سیگنال", padding="15")
        control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)

        # 1. تعداد بیت‌ها (Resolution)
        ttk.Label(control_frame, text="۱. رزولوشن (بیت):").pack(anchor="w", pady=5)
        self.var_bits = tk.IntVar(value=8)
        scale_bits = ttk.Scale(control_frame, from_=4, to=12, orient=tk.HORIZONTAL, variable=self.var_bits, command=self.update_plot)
        scale_bits.pack(fill=tk.X, pady=5)
        self.lbl_bits_val = ttk.Label(control_frame, text="8 bits")
        self.lbl_bits_val.pack(anchor="e")

        # 2. ولتاژ مرجع (Vref)
        ttk.Label(control_frame, text="۲. ولتاژ مرجع (V):").pack(anchor="w", pady=5)
        self.var_vref = tk.DoubleVar(value=5.0)
        scale_vref = ttk.Scale(control_frame, from_=1.0, to=10.0, orient=tk.HORIZONTAL, variable=self.var_vref, command=self.update_plot)
        scale_vref.pack(fill=tk.X, pady=5)
        self.lbl_vref_val = ttk.Label(control_frame, text="5.0 V")
        self.lbl_vref_val.pack(anchor="e")

        # 3. درصد خطا (Tolerance)
        ttk.Label(control_frame, text="۳. خطای مقاومت (%):").pack(anchor="w", pady=5)
        self.var_tol = tk.DoubleVar(value=0.0)
        scale_tol = ttk.Scale(control_frame, from_=0.0, to=10.0, orient=tk.HORIZONTAL, variable=self.var_tol, command=self.update_plot)
        scale_tol.pack(fill=tk.X, pady=5)
        self.lbl_tol_val = ttk.Label(control_frame, text="0.0 %")
        self.lbl_tol_val.pack(anchor="e")
        
        # 4. فرکانس سینوسی (Number of Cycles)
        ttk.Label(control_frame, text="۴. تعداد سیکل (در نمونه):").pack(anchor="w", pady=5)
        self.var_cycles = tk.IntVar(value=3)
        scale_cycles = ttk.Scale(control_frame, from_=1, to=8, orient=tk.HORIZONTAL, variable=self.var_cycles, command=self.update_plot)
        scale_cycles.pack(fill=tk.X, pady=5)
        self.lbl_cycles_val = ttk.Label(control_frame, text="3 Cycles")
        self.lbl_cycles_val.pack(anchor="e")

        # دکمه تولید مجدد (برای مشاهده تغییر مقاومت‌های تصادفی)
        ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=15)
        self.btn_regen = ttk.Button(control_frame, text="تولید مجدد مقاومت‌ها و شبیه‌سازی", command=self.force_update)
        self.btn_regen.pack(fill=tk.X, pady=5)

        # وضعیت و نتایج
        ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=15)
        self.lbl_status = tk.Label(control_frame, text="وضعییت: نرمال", font=("Arial", 12, "bold"), fg="green", wraplength=200)
        self.lbl_status.pack(pady=5)
        self.lbl_thd = tk.Label(control_frame, text="THD: N/A", font=("Arial", 12, "bold"), wraplength=200)
        self.lbl_thd.pack(pady=5)


        # --- ناحیه نمودار (سمت راست) ---
        plot_frame = ttk.Frame(root)
        plot_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        # ایجاد شکل Matplotlib با سه نمودار
        self.fig = Figure(figsize=(8, 7), dpi=100)
        self.ax1 = self.fig.add_subplot(311) # زمان
        self.ax2 = self.fig.add_subplot(312, sharex=self.ax1) # خطا
        self.ax3 = self.fig.add_subplot(313) # FFT
        self.fig.subplots_adjust(hspace=0.45, top=0.95, bottom=0.08)

        self.canvas = FigureCanvasTkAgg(self.fig, master=plot_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # رسم اولیه
        self.force_update()

    def force_update(self):
        """فراخوانی برای تولید مقادیر تصادفی جدید مقاومت و رسم"""
        self.update_plot(None)

    def calculate_thd(self, signal):
        """محاسبه THD (Total Harmonic Distortion) بر اساس FFT"""
        N = len(signal)
        fft_result = np.fft.fft(signal)
        magnitude = np.abs(fft_result[:N//2])
        
        # پیدا کردن فرکانس اصلی (بعد از DC)
        # DC (فرکانس صفر) را حذف می‌کنیم، چون موج ما از 0 تا Vref است و بایاس دارد
        fundamental_index = np.argmax(magnitude[1:]) + 1 
        V_f1 = magnitude[fundamental_index]
        
        # مجموع دامنه توان‌های هارمونیک (از هارمونیک دوم به بعد)
        # THD معمولاً بر اساس 10 هارمونیک اول محاسبه می‌شود
        harmonic_power_sum = 0
        for h in range(2, 11): # هارمونیک‌های 2 تا 10
            harmonic_index = h * fundamental_index
            if harmonic_index < N // 2:
                harmonic_power_sum += magnitude[harmonic_index]**2
        
        if V_f1 == 0: return float('inf')
        
        # THD = sqrt(مجموع توان هارمونیک‌ها) / توان فرکانس اصلی
        thd = np.sqrt(harmonic_power_sum) / V_f1
        return thd * 100 # بر حسب درصد

    def update_plot(self, event=None):
        # خواندن پارامترها
        bits = int(self.var_bits.get())
        v_ref = self.var_vref.get()
        tol = self.var_tol.get()
        cycles = self.var_cycles.get()
        r_base = self.r_base
        
        self.lbl_bits_val.config(text=f"{bits} bits")
        self.lbl_vref_val.config(text=f"{v_ref:.1f} V")
        self.lbl_tol_val.config(text=f"{tol:.1f} %")
        self.lbl_cycles_val.config(text=f"{cycles} Cycles")

        # --- ۱. تولید سیگنال DDS (ورودی سینوسی کوانتایز شده) ---
        num_samples = 2**bits
        # t = فاز سیگنال
        t = np.linspace(0, cycles * 2 * np.pi, num_samples, endpoint=False) 
        
        # موج سینوسی (بایاس شده برای DDS، از 0 تا 1)
        sine_wave_analog = (np.sin(t) + 1) / 2 
        
        # کوانتایز کردن (تبدیل به کد دیجیتال LUT)
        max_code = 2**bits - 1
        digital_inputs = np.round(sine_wave_analog * max_code).astype(int)

        # --- ۲. حل مدار DAC با ورودی سینوسی ---
        ideal_dac = Real_R2R_DAC(bits, v_ref, r_base, 0.0)
        real_dac = Real_R2R_DAC(bits, v_ref, r_base, tol)
        
        # حل DAC با ورودی LUT سینوسی
        y_ideal = np.array([ideal_dac.solve_circuit(i) for i in digital_inputs])
        y_real = np.array([real_dac.solve_circuit(i) for i in digital_inputs])
        
        # محاسبه خطا و یکنوایی
        y_real_diff = np.diff(y_real)
        is_monotonic = not np.any(y_real_diff < 0)

        # --- ۳. آنالیز طیفی (FFT) و THD ---
        # سیگنال را از بایاس DC خارج می‌کنیم
        signal_for_fft = y_real - np.mean(y_real)
        
        thd_percent = self.calculate_thd(signal_for_fft)
        
        N = len(signal_for_fft)
        fft_result = np.fft.fft(signal_for_fft)
        fft_magnitude = np.abs(fft_result[:N//2])
        fft_magnitude_db = 20 * np.log10(fft_magnitude / np.max(fft_magnitude)) # نرمالایز به dBc
        
        fft_freq_axis = np.fft.fftfreq(N, 1/N)[:N//2] # محور فرکانس نرمال شده

        # --- ۴. رسم نمودارها ---
        self.ax1.clear()
        self.ax2.clear()
        self.ax3.clear()

        # نمودار ۱: زمان (خروجی DDS)
        self.ax1.plot(t, y_ideal, linestyle='--', color='gray', label='Ideal', alpha=0.7)
        self.ax1.plot(t, y_real, color='blue', label='Real', linewidth=1.5)
        self.ax1.set_ylabel('Voltage (V)')
        self.ax1.set_title(f'Time Domain Output (DDS Signal)')
        self.ax1.legend()
        self.ax1.grid(True, alpha=0.3)

        # نمودار ۲: خطا (INL/DNL - به دلیل غیرخطی بودن)
        error_val = y_real - y_ideal
        self.ax2.plot(t, error_val, color='red')
        self.ax2.axhline(0, color='black', linewidth=0.8)
        self.ax2.set_ylabel('Error (V)')
        self.ax2.set_title('Error/Distortion in Time Domain')
        self.ax2.grid(True, alpha=0.3)

        # نمودار ۳: طیف فرکانسی (FFT)
        self.ax3.plot(fft_freq_axis, fft_magnitude_db, color='purple')
        self.ax3.set_xlabel('Normalized Frequency Index')
        self.ax3.set_ylabel('Magnitude (dBc)')
        self.ax3.set_title('Frequency Spectrum (FFT)')
        self.ax3.set_ylim(-100, 0) # نمایش از 0 تا -100 dB
        self.ax3.grid(True, alpha=0.3)
        
        # --- ۵. بروزرسانی وضعیت ---
        if is_monotonic:
            self.lbl_status.config(text="وضعیت: یکنوا (Monotonic)", fg="green")
        else:
            self.lbl_status.config(text="هشدار: غیریکنوا (Non-Monotonic)!", fg="red")
        
        self.lbl_thd.config(text=f"THD: {thd_percent:.3f} %")

        self.canvas.draw()

# --- اجرای برنامه ---
if __name__ == "__main__":
    root = tk.Tk()
    try:
        default_font = ('Tahoma', 10)
        root.option_add("*Font", default_font)
    except:
        pass
    app = DDS_Simulator_App(root)
    root.mainloop()

راهنمای استفاده و تحلیل نتایج:

  1. خطای صفر (Tolerance 0%):
    • THD بسیار پایین است (کمتر از ۰.۱٪) و فقط به دلیل کوانتایزیشن (Quantization) است، نه خطای مقاومت.
    • در نمودار FFT، قله اصلی سیگنال بسیار غالب است.
  2. خطای بالا (Tolerance 5% یا بیشتر):
    • THD به شدت افزایش می‌یابد (ممکن است به بالای ۱٪ برسد).
    • در نمودار FFT، چندین قله کوچک (هارمونیک) در کنار قله اصلی ظاهر می‌شوند که نشان‌دهنده آلودگی سیگنال است.
    • این دقیقاً نشان می‌دهد که چرا سازندگان DDS از شبکه‌های مقاومتی بسیار دقیق استفاده می‌کنند.

همچنین بررسی کنید

ساخت یک پروب تفاضلی مجازی (Pseudo-Differential)

ساخت یک «پروب تفاضلی مجازی» با استفاده از دو پروب معمولی، یک ترفند بسیار کاربردی …

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *