در دنیای واقعی، هیچ مقاومتی دقیقاً $R$ یا $2R$ نیست. مقاومتها دارای تلورانس (Tolerance) هستند (مثلاً ۱٪ یا ۵٪ خطا). این خطا باعث میشود که تقسیم ولتاژ دقیقاً بر ۲ انجام نشود و در نتیجه خروجی DAC دچار غیرخطیگری (Non-linearity) شود.
بدترین حالت معمولاً در گذر از 011...1 به 100...0 (مثلاً ۱۲۷ به ۱۲۸ در ۸ بیت) رخ میدهد. اگر مقاومت MSB (بیت باارزش) خطای زیادی داشته باشد، ممکن است ولتاژ خروجی به جای افزایش، کاهش یابد! به این پدیده عدم یکنوایی (Non-monotonicity) میگویند که برای سیستمهای کنترلی فاجعه است.
کد پایتون شبیهسازی R-2R با خطای مقاومت
این برنامه یک مدار واقعی را با استفاده از مدل مدار معادل تونن (Thevenin) حل میکند تا ولتاژ واقعی هر گره را محاسبه کند، نه اینکه صرفاً از فرمول ایدهآل استفاده کند.
Python
import numpy as np
import matplotlib.pyplot as plt
import random
class Real_R2R_DAC:
def __init__(self, resolution_bits, v_ref, r_base, tolerance_percent):
"""
:param resolution_bits: تعداد بیتها
:param v_ref: ولتاژ مرجع
:param r_base: مقدار پایه مقاومت R (مثلاً 10 کیلو اهم)
:param tolerance_percent: درصد خطای مقاومتها (مثلاً 1 یا 5)
"""
self.n = resolution_bits
self.v_ref = v_ref
# تولید مقاومتهای واقعی با اضافه کردن نویز تصادفی
# ساختار نردبان:
# - N عدد مقاومت Shunt (عمودی، 2R) متصل به پینهای ورودی
# - N-1 عدد مقاومت Series (افقی، R) بین گرهها
# - 1 عدد مقاومت Terminating (عمودی، 2R) در انتهای LSB به زمین
self.resistors_shunt = []
self.resistors_series = []
# تابع کمکی برای تولید مقاومت با خطا
def get_resistor(ideal_value):
error_factor = 1 + random.uniform(-tolerance_percent/100, tolerance_percent/100)
return ideal_value * error_factor
# ایجاد مقاومتهای Shunt (2R) برای هر بیت
for _ in range(self.n):
self.resistors_shunt.append(get_resistor(2 * r_base))
# ایجاد مقاومتهای Series (R) بین بیتها
for _ in range(self.n - 1):
self.resistors_series.append(get_resistor(r_base))
# مقاومت پایاندهنده (Terminator) در سمت LSB که همیشه به زمین وصل است (2R)
self.r_terminator = get_resistor(2 * r_base)
def solve_circuit(self, digital_input):
"""
حل مدار نردبانی با استفاده از تکرار مدار معادل تونن از LSB به سمت MSB
"""
# تبدیل ورودی به لیست بیتها (LSB در اندیس 0)
bits = [(digital_input >> i) & 1 for i in range(self.n)]
# شروع از انتهای LSB:
# مقاومت معادل "نگاه به پایین" (به سمت زمین) ابتدا همان مقاومت پایاندهنده است
r_looking_down = self.r_terminator
v_looking_down = 0.0 # ولتاژ منبع معادل سمت پایین (زمین)
# حرکت از LSB (بیت 0) به سمت MSB (بیت N-1)
for i in range(self.n):
# ولتاژ ورودی این بیت (اگر 1 باشد Vref، اگر 0 باشد GND)
v_in_bit = self.v_ref if bits[i] == 1 else 0.0
r_shunt = self.resistors_shunt[i]
# محاسبه ولتاژ گره (Node Voltage) با استفاده از قضیه میلمن (Millman) یا تقسیم ولتاژ
# ما دو شاخه داریم:
# 1. شاخه ورودی بیت (V_in_bit, R_shunt)
# 2. شاخه پایین نردبان (V_looking_down, R_looking_down)
# ولتاژ معادل در گره فعلی:
v_node = (v_in_bit * r_looking_down + v_looking_down * r_shunt) / (r_looking_down + r_shunt)
# مقاومت معادل دیده شده در این گره (موازی دو شاخه):
r_node = (r_looking_down * r_shunt) / (r_looking_down + r_shunt)
# اگر آخرین بیت (MSB) هستیم، این ولتاژ خروجی است
if i == self.n - 1:
return v_node
# اگر هنوز به MSB نرسیدیم، باید از مقاومت سری عبور کنیم برای مرحله بعد
r_series = self.resistors_series[i]
# آپدیت مقادیر برای حلقه بعدی
v_looking_down = v_node
r_looking_down = r_node + r_series
return 0.0
# --- تنظیمات شبیهسازی ---
BITS = 6 # تعداد بیت (کمتر گرفتم تا پلهها واضح باشند)
V_REF = 5.0
R_BASE = 10000 # 10k Ohm
TOLERANCE = 20 # خطای 20% (خیلی زیاد گرفتم تا اثرش در نمودار کاملا دیده شود!)
# ساخت DAC ایدهآل (خطای 0) و واقعی (خطای 20)
ideal_dac = Real_R2R_DAC(BITS, V_REF, R_BASE, 0.0)
real_dac = Real_R2R_DAC(BITS, V_REF, R_BASE, TOLERANCE)
# تولید دادهها
x_values = range(2**BITS)
y_ideal = [ideal_dac.solve_circuit(i) for i in x_values]
y_real = [real_dac.solve_circuit(i) for i in x_values]
errors = [y_real[i] - y_ideal[i] for i in x_values]
# --- رسم نمودار ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10), sharex=True)
# نمودار 1: مقایسه خروجی ایدهآل و واقعی
ax1.step(x_values, y_ideal, where='post', label='Ideal DAC', linestyle='--', alpha=0.7)
ax1.step(x_values, y_real, where='post', label=f'Real DAC ({TOLERANCE}% Tol)', color='red')
ax1.set_ylabel('Output Voltage (V)')
ax1.set_title(f'R-2R DAC Simulation: Ideal vs Real Resistors')
ax1.legend()
ax1.grid(True)
# نمودار 2: نمایش خطا (Non-Linearity)
ax2.plot(x_values, errors, color='purple')
ax2.set_ylabel('Error Voltage (V)')
ax2.set_xlabel('Digital Input Code')
ax2.set_title('Error (Real - Ideal)')
ax2.grid(True)
ax2.axhline(0, color='black', linewidth=0.8)
# بررسی Monotonicity (یکنوایی)
# اگر جایی ولتاژ با افزایش کد، کاهش یابد، آنجا غیریکنوا است
is_monotonic = True
for i in range(1, len(y_real)):
if y_real[i] < y_real[i-1]:
ax1.plot(i, y_real[i], 'ro', markersize=10, mfc='none') # علامت گذاری روی نمودار
is_monotonic = False
print(f"Simulated Resistance Tolerance: {TOLERANCE}%")
print(f"Is the DAC Monotonic? {'YES' if is_monotonic else 'NO (See circles on plot)'}")
plt.tight_layout()
plt.show()
نکات کلیدی برای تحلیل خروجی:
- خطا را زیاد گرفتم: من عمداً
TOLERANCE = 20(بیست درصد) قرار دادم. در واقعیت مقاومتها ۱٪ یا ۵٪ هستند، اما با ۲۰٪ شما به راحتی میتوانید اعوجاج و خرابی موج را ببینید. - دایرههای قرمز: اگر برنامه تشخیص دهد که در جایی با افزایش کد ورودی، ولتاژ خروجی کاهش یافته است (که نباید بشود)، دور آن نقطه در نمودار بالا یک دایره قرمز میکشد. این یعنی DAC شما غیر یکنوا (Non-Monotonic) شده است.
- نمودار پایین (Error): این نمودار تفاوت بین حالت ایدهآل و واقعی را نشان میدهد. دقت کنید که بیشترین خطاها معمولاً در وسط نمودار (کد ۳۱ به ۳۲ در سیستم ۶ بیتی) رخ میدهد، جایی که تمام بیتهای پایین خاموش و بیت بالا روشن میشود.
پیشنهاد بعدی
حالا که سختافزار (DAC) را شبیهسازی کردیم، نظرتان چیست که یک قدم فراتر برویم و تولید موج سینوسی با استفاده از جدول (Look-Up Table) را روی این DAC شبیهسازی شده اجرا کنیم؟ این دقیقاً همان کاری است که در DDS انجام میشود، اما حالا میتوانیم ببینیم خطای مقاومتها چه بلایی سر شکل موج سینوسی میآورد.
سایت آموزشی الکترونیک و کامپیوتر اوپن مقاله های آموزشی الکترونیک و کامپیوتر و فن آوری