منطق مدار 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()
راهنمای استفاده و تحلیل نتایج:
- خطای صفر (Tolerance 0%):
THDبسیار پایین است (کمتر از ۰.۱٪) و فقط به دلیل کوانتایزیشن (Quantization) است، نه خطای مقاومت.- در نمودار FFT، قله اصلی سیگنال بسیار غالب است.
- خطای بالا (Tolerance 5% یا بیشتر):
THDبه شدت افزایش مییابد (ممکن است به بالای ۱٪ برسد).- در نمودار FFT، چندین قله کوچک (هارمونیک) در کنار قله اصلی ظاهر میشوند که نشاندهنده آلودگی سیگنال است.
- این دقیقاً نشان میدهد که چرا سازندگان DDS از شبکههای مقاومتی بسیار دقیق استفاده میکنند.
سایت آموزشی الکترونیک و کامپیوتر اوپن مقاله های آموزشی الکترونیک و کامپیوتر و فن آوری