أنظمة التحكّم
حتى الآن كان البرنامج يقرأ ثم يقرّر. في هذا الفصل نغلق الدائرة: قرار البرنامج يغيّر العالم، والحسّاس يقرأ هذا التغيّر، والقراءة الجديدة تغيّر القرار. بهذا يصمد الجهاز أمام ما لم نتوقّعه، مثل باب فُتح أو حمل ازداد.
- نميّز بين الحلقة المفتوحة والحلقة المغلقة، ونرسم المخطّط الصندوقي بكل أجزائه.
- نبني تحكّما بحالتين ونحسب زمن دورته، وتحكّما تناسبيا بـPWM ونحسب خطأه الدائم.
- نقيس سرعة محرّك بمرمّز، ونفهم ماذا يتحكّم في المحرّك المؤازر من الداخل.
- نكتب منظّم PID في مكتبة، ونضبطه نوعيا، ونختار زمن العيّنة.
20.1 الحلقة المفتوحة والحلقة المغلقة – חוג פתוח וחוג סגור (Open and Closed Loop)
آلة تحميص تعمل دقيقتين ثم تتوقف لا تعرف شيئا عن لون الخبز. ومكيّف يقيس حرارة الغرفة ويغيّر عمله حسبها يعرف النتيجة. هذا هو الفرق كله.
- القيمة المطلوبة (אות רצוי) r: ما نريده، مثل 45 درجة.
- القيمة المقيسة (אות מצוי) y: ما يقوله الحسّاس.
- الخطأ (שגיאה) e: الفرق بينهما، وهو كل ما يراه المنظّم.
- المنظّم (בקר): الجزء من البرنامج الذي يحسب الأمر u من الخطأ. نسمّيه هكذا كي لا يختلط بالمتحكّم، وهو اللوحة كلها.
- المشغّل والعملية (תהליך מבוקר): المشغّل ينفّذ الأمر، والعملية هي ما نريد التحكّم فيه.
- الاضطراب (הפרעה): كل ما يؤثّر في العملية من خارج البرنامج.
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| e | الخطأ: موجب يعني أننا دون المطلوب | — |
| r | القيمة المطلوبة | — |
| y | القيمة المقيسة | — |
الخطأ موجب، فنحن دون المطلوب، ويجب أن نزيد التسخين.
كي نرى هذا بالأرقام نحتاج إلى عملية تستجيب لأوامرنا. لذلك نحاكي داخل البرنامج نفسه عملية من الرتبة الأولى (תהליך מסדר ראשון): صندوق يسخن ويبرد تدريجيا نحو قيمة نهائية. لا يوجد هنا صندوق حقيقي، فالدالة process هي العالم كله، والأرقام ناتجة عن هذا النموذج وليست قياسا. وعلى لوحة حقيقية نحذف الدالة ونقرأ الحسّاس.
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| τ | ثابت الزمن (קבוע זמן)، عنده نقطع 63% من الطريق | s |
| K | كم يرفع الأمر الكامل الحرارة | °C |
| uk | الأمر، من 0 إلى 1 | — |
| Ta | حرارة المحيط | °C |
| Ts | زمن العيّنة | s |
import math
GAIN = 40.0
TAU = 20.0
DT = 1.0
A = 1 - math.exp(-DT / TAU)
def process(temp, room, u):
return temp + A * (room + GAIN * u - temp)
u = 0.625
temp = 20.0
room = 20.0
for t in range(1, 161):
if t == 80:
room = 12.0
temp = process(temp, room, u)
if t % 20 == 0:
print("t=%3d room=%.0f u=%.3f T=%.1f" % (t, room, u, temp))t= 20 room=20 u=0.625 T=35.8 t= 40 room=20 u=0.625 T=41.6 t= 60 room=20 u=0.625 T=43.8 t= 80 room=12 u=0.625 T=44.2 t=100 room=12 u=0.625 T=39.6 t=120 room=12 u=0.625 T=38.0 t=140 room=12 u=0.625 T=37.4 t=160 room=12 u=0.625 T=37.1
الحساب صحيح تماما حتى يأتي الاضطراب. بعده تهبط الحرارة نحو 37 درجة، والبرنامج لا يدري.
20.2 التحكّم بحالتين مع التخلّف – בקרה דו-מצבית עם היסטרזיס (On/Off Control)
أبسط منظّم يعرف أمرين فقط: شغّل أو أطفئ. ورأينا في القسم 6.8 لماذا لا تكفي عتبة واحدة، وكيف تمنع عتبتان متباعدتان الارتجاف. هذا هو التخلّف، ولن نشرحه هنا مرة أخرى. الجديد أن العملية الآن تردّ على أمرنا، فتنشأ دورة ذاتية حول القيمة المطلوبة.
board.LED، والعملية مُحاكاة. الاضطراب نفسه في t = 80.import math
from machine import Pin
import board
GAIN = 40.0
TAU = 20.0
DT = 1.0
A = 1 - math.exp(-DT / TAU)
LOW = 44.0
HIGH = 46.0
def process(temp, room, u):
return temp + A * (room + GAIN * u - temp)
heater = Pin(board.LED, Pin.OUT)
heater.off()
temp = 20.0
room = 20.0
flips = 0
for t in range(1, 161):
if t == 80:
room = 12.0
if temp < LOW and heater.value() == 0:
heater.on()
flips += 1
elif temp > HIGH and heater.value() == 1:
heater.off()
flips += 1
temp = process(temp, room, heater.value())
if t % 20 == 0:
print("t=%3d T=%.1f heater=%d flips=%d"
% (t, temp, heater.value(), flips))t= 20 T=45.3 heater=1 flips=1 t= 40 T=44.1 heater=0 flips=8 t= 60 T=45.3 heater=0 flips=14 t= 80 T=46.2 heater=1 flips=19 t=100 T=45.7 heater=1 flips=23 t=120 T=45.0 heater=1 flips=27 t=140 T=44.3 heater=1 flips=31 t=160 T=43.4 heater=1 flips=35
الحلقة المغلقة نجحت: الحرارة بقيت حول النطاق رغم برودة المحيط، وتخرج عنه قليلا لأننا نقرأها مرة في الثانية. لكن ثمن ذلك أن عدّاد التبديل flips لا يتوقف. ولكل مرحّل عمر محدود بعدد مرات التبديل، فنحسب كم مرة سيعمل.
سرعة التغيّر قرب 45 درجة، والسخّان يعمل (u = 1):
والسخّان مطفأ (u = 0):
نقطع درجتين صعودا ودرجتين نزولا، فزمن الدورة tc هو:
20.3 التحكّم التناسبي: ضوء ثابت بـPWM – בקרה פרופורציונית באור (Proportional Control)
مع PWM لم نعد مضطرين إلى قفزة بين الحدّين: نعطي قدرا من الأمر يتناسب مع الخطأ. خطأ كبير يعني أمرا كبيرا، وخطأ صغير يعني تصحيحا لطيفا. مثالنا مصباح ومقاومة ضوئية متقابلان: نريد إنارة ثابتة على الطاولة، ليلا ونهارا.
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| Kp | الربح التناسبي (הגבר) | — |
| ess | الخطأ الدائم (שגיאה מתמדת) بعد أن تستقر العملية | — |
| d | ما يصل إلى الحسّاس دون أمرنا، هنا ضوء النهار | — |
| K | ربح العملية | — |
لماذا يبقى خطأ؟ لأن u = Kp · e: لو صار الخطأ صفرا لصار الأمر صفرا، فانطفأ المصباح. المنظّم التناسبي يحتاج إلى خطأ كي يعطي أمرا.
فتستقر القراءة عند 1.811 V. ومع نهار أقوى، d = 0.9 V، يصغر الخطأ إلى 0.122 V وتصبح القراءة 1.878 V.
import math
import time
import board
GAIN = 2.0
TAU = 0.5
DT = 0.05
A = 1 - math.exp(-DT / TAU)
KP = 4.0
TARGET = 2.0
led = board.pwm(board.LED_EXT, 1000)
day = 0.3
v = day
for k in range(1, 61):
if k == 31:
day = 0.9
u = KP * (TARGET - v)
u = max(0.0, min(1.0, u))
led.duty_u16(int(u * 65535))
v += A * (day + GAIN * u - v)
if k % 10 == 0:
print("t=%.1f day=%.1f V=%.3f duty=%2.0f%%"
% (k * DT, day, v, u * 100))
time.sleep(DT)t=0.5 day=0.3 V=1.564 duty=100% t=1.0 day=0.3 V=1.811 duty=76% t=1.5 day=0.3 V=1.811 duty=76% t=2.0 day=0.9 V=1.878 duty=49% t=2.5 day=0.9 V=1.878 duty=49% t=3.0 day=0.9 V=1.878 duty=49%
القراءة تطابق الحساب. وعلى لوحة حقيقية نحذف النموذج ونقرأ المقاومة الضوئية. هذا البرنامج جاهز لذلك، وفيه يحدّد مقياس الجهد القيمة المطلوبة. في العالم المُحاكى هنا قراءة الضوء لا تتبع المصباح، لذلك نعطيها قيما متزايدة كي نرى كيف يتراجع الأمر:
import time
import board
KP = 1.0
pot = board.analog_in(board.POT)
ldr = board.analog_in(board.LDR)
led = board.pwm(board.LED_EXT, 1000)
while True:
target = pot.read_u16() * 3.3 / 65535
light = ldr.read_u16() * 3.3 / 65535
u = KP * (target - light)
u = max(0.0, min(1.0, u))
led.duty_u16(int(u * 65535))
print("want %.2f have %.2f duty %3.0f%%"
% (target, light, u * 100))
time.sleep(1)want 2.00 have 0.50 duty 100% want 2.00 have 1.00 duty 100% want 2.00 have 1.50 duty 50% want 2.00 have 2.00 duty 0% want 2.00 have 2.50 duty 0% ...
KP في البرنامج.20.4 سرعة المحرّك: PWM مع مرمّز – מהירות מנוע עם אנקודר (Speed Control with an Encoder)
شغّلنا المحرّك بـPWM في القسم 7.8، لكن تلك كانت حلقة مفتوحة: عند زيادة الحمل تهبط السرعة ولا نعلم. لإغلاق الحلقة نحتاج إلى قياس السرعة، وهذا عمل المرمّز (אנקודר): قرص مركّب على المحور يعطي عددا ثابتا من النبضات في كل لفّة.
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| n | السرعة | rpm |
| N | النبضات التي عددناها خلال النافذة | — |
| P | عدد النبضات في اللفّة | — |
| T | طول نافذة العدّ | s |
| Δn | أصغر فرق في السرعة نراه | rpm |
board.BTN. قطار النبضات هنا من العالم المُحاكى، وسرعته تهبط إلى النصف بعد ثانية.import time
import machine
from machine import Pin
import board
PPR = 40
WINDOW = 0.5
pulses = 0
def on_edge(pin):
global pulses
pulses += 1
enc = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
enc.irq(trigger=Pin.IRQ_FALLING, handler=on_edge)
for _ in range(4):
time.sleep(WINDOW)
state = machine.disable_irq()
n = pulses
pulses = 0
machine.enable_irq(state)
rpm = n * 60 / (PPR * WINDOW)
print("pulses=%3d rpm=%.0f" % (n, rpm))pulses= 50 rpm=150 pulses= 50 rpm=150 pulses= 25 rpm=75 pulses= 25 rpm=75
لذلك تقفز القراءة بخطوات من 15 rpm. نافذة أطول تعطي قراءة أدق، لكن الحلقة تصبح أبطأ.
نغلق الحلقة بمنظّم تناسبي. المحرّك هنا مُحاكى أيضا، بثابت زمن 1 s وسرعة نهائية 300 rpm عند الأمر الكامل، ونحسب نبضاته من سرعته:
import math
import time
import board
PPR = 40
DT = 0.1
GAIN = 300.0
TAU = 1.0
A = 1 - math.exp(-DT / TAU)
KP = 0.01
TARGET = 150.0
motor = board.pwm(board.LED_EXT, 1000)
speed = 0.0
turned = 0.0
load = 0.0
for k in range(1, 81):
if k == 41:
load = 60.0
turned += speed * PPR * DT / 60
pulses = int(turned)
turned -= pulses
rpm = pulses * 60 / (PPR * DT)
u = max(0.0, min(1.0, KP * (TARGET - rpm)))
motor.duty_u16(int(u * 65535))
speed += A * (GAIN * u - load - speed)
if k % 10 == 0:
print("t=%.0f load=%2.0f speed=%3.0f duty=%2.0f%%"
% (k * DT, load, speed, u * 100))
time.sleep(DT)t=1 load= 0 speed=112 duty=45% t=2 load= 0 speed=112 duty=30% t=3 load= 0 speed=112 duty=30% t=4 load= 0 speed=112 duty=45% t=5 load=60 speed=100 duty=60% t=6 load=60 speed=100 duty=60% t=7 load=60 speed=100 duty=60% t=8 load=60 speed= 96 duty=45%
نرى عيب المنظّم التناسبي بوضوح: السرعة لا تصل إلى المطلوب، وتبتعد أكثر مع الحمل. وحسب صيغة ess يبقى خطأ نحو 37.5 rpm دون حمل، ونحو 52.5 rpm مع الحمل. ودورة العمل تقفز بين قيمتين، لأن كل خطوة من خطوات المرمّز تساوي 15% من الأمر. وسيعالج الحد التكاملي الخطأ الدائم.
20.5 زاوية الذراع: المحرّك المؤازر – זווית בסרוו (Angle Control)
المحرّك المؤازر (סרוו) حلقة مغلقة جاهزة: في داخله محرّك صغير، ومقياس جهد على المحور يقرأ الزاوية، ودائرة تقارنها بعرض النبضة. لذلك نحن لا نتحكّم في المحرّك، بل نرسل القيمة المطلوبة فقط، والمحرّك المؤازر يغلق الحلقة وحده.
فما الذي يبقى لنا؟ نختار كيف تتغيّر القيمة المطلوبة. فقفزة كبيرة تجعل الذراع تضرب بأقصى سرعة، وتسحب تيارا عاليا. لذلك نقيّد التغيّر في كل خطوة:
servo.py من الفصل 7، ومقياس الجهد عند أعلى موضع. الذراع تتقدّم 25 درجة في كل خطوة حتى تصل.import time
import board
from servo import Servo
MAX_STEP = 25
pot = board.analog_in(board.POT)
arm = Servo(board.pwm(board.SERVO, 50))
now = 90
arm.angle(now)
for _ in range(6):
target = pot.read_u16() * 180 / 65535
step = max(-MAX_STEP, min(MAX_STEP, target - now))
now += step
arm.angle(now)
print("target=%3.0f now=%3.0f" % (target, now))
time.sleep(0.5)target=180 now=115 target=180 now=140 target=180 now=165 target=180 now=180 target=180 now=180 target=180 now=180
20.6 المنظّم PID – בקר PID (PID Controller)
يجمع هذا المنظّم ثلاثة حدود، وكل حد ينظر إلى الخطأ في زمن مختلف: التناسبي (פרופורציונלי) ينظر إلى الحاضر، والتكاملي (אינטגרלי) إلى الماضي، والتفاضلي (דיפרנציאלי) إلى الاتجاه.
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| Ki | الربح التكاملي: يجمع الخطأ مع الزمن | — |
| Kd | الربح التفاضلي: يتفاعل مع سرعة التغيّر | — |
| Ik | مجموع الحد التكاملي حتى الخطوة k | — |
| Ts | زمن العيّنة | s |
التكامل يحلّ مشكلة الخطأ الدائم: ما دام هناك خطأ، ولو صغير، يستمر المجموع بالتغيّر، فلا تستقر الحلقة إلا عند خطأ صفر. وفي الصيغة المتقطّعة (בדיד) نكتب التفاضل على القراءة y وليس على الخطأ، فلا تنتج قفزة في الأمر عندما نغيّر r.
dt علينا أن نحافظ عليه.# pid.py - a discrete PID loop with output limits, the same on every board.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
class PID:
def __init__(self, kp, ki, kd, dt, lo=0.0, hi=1.0):
self.kp = kp
self.ki = ki
self.kd = kd
self.dt = dt
self.lo = lo
self.hi = hi
self.reset()
def reset(self):
self.total = 0.0
self.last = None
def update(self, target, measured):
error = target - measured
slope = 0.0
if self.last is not None:
slope = (measured - self.last) / self.dt
self.last = measured
total = self.total + self.ki * error * self.dt
out = self.kp * error + total - self.kd * slope
if out > self.hi:
return self.hi
if out < self.lo:
return self.lo
self.total = total
return outif out > self.hi: عند بلوغ حد الخرج لا نحفظ المجموع الجديد. لو حفظناه لواصل النمو بينما السخّان بكامل طاقته، ولا يفرغ إلا بعد أن يتجاوز الهدف. هذا هو الإشباع التكاملي (התרוממות אינטגרל). في نموذجنا: مع الحماية تبلغ الحرارة 45.5 درجة، ودونها 54.9 درجة.import math
from pid import PID
GAIN = 40.0
TAU = 20.0
DT = 1.0
A = 1 - math.exp(-DT / TAU)
TARGET = 45.0
def run(kp, ki, kd, noise):
loop = PID(kp, ki, kd, DT)
temp = 20.0
peak = temp
last_u = 0.0
moved = 0.0
for k in range(180):
n = noise if k % 2 else -noise
u = loop.update(TARGET, temp + n)
temp += A * (20.0 + GAIN * u - temp)
peak = max(peak, temp)
if k >= 150:
moved += abs(u - last_u)
last_u = u
return peak, temp, moved / 30
print("kind peak final output move")
for name, kp, ki, kd in (("P ", 0.1, 0.0, 0.0),
("PI ", 0.1, 0.02, 0.0),
("PID", 0.1, 0.02, 0.2)):
peak, final, _ = run(kp, ki, kd, 0.0)
_, _, move = run(kp, ki, kd, 0.1)
print("%s %.1f %.1f %.3f" % (name, peak, final, move))kind peak final output move P 40.0 40.0 0.022 PI 45.5 45.0 0.025 PID 46.2 45.0 0.208
الجدول يلخّص الأمر: PI يقضي على الخطأ الدائم. وعمليتنا من الرتبة الأولى، فلا يفيد فيها الحد التفاضلي: زاد التجاوز قليلا لأنه يخرج الأمر من حده أبكر، فيبدأ المجموع أبكر، وكبرت حركة الأمر كثيرا بسبب الضجيج. لذلك نبدأ عادة بـPI، ولا نضيف Kd إلا إذا كانت العملية بطيئة ومتأخرة وكان الحسّاس نظيفا.
الضبط النوعي – כיוונון איכותני:
- نضع Ki = 0 وKd = 0، ونرفع Kp حتى تصبح الاستجابة سريعة قبل أن يبدأ التذبذب، ثم نخفضه قليلا.
- نرفع Ki تدريجيا حتى يزول الخطأ الدائم في زمن مقبول. إذا زاد التجاوز (חריגה) نخفضه.
- نضيف Kd قليلا فقط إذا بقي تجاوز كبير، ونراقب الأمر: إذا ارتعش نخفضه أو نمرّر القراءة في مرشّح.
| عندما نرفع | السرعة | التجاوز | الخطأ الدائم | أثر الضجيج |
|---|---|---|---|---|
| Kp | تزيد | يزيد | يقل | يزيد |
| Ki | تزيد | يزيد | يزول | قليل |
| Kd | قليل | يقل في حدود، وليس دائما | لا يتغيّر | يزيد كثيرا |
20.7 اختيار زمن العيّنة – בחירת קצב דגימה (Sampling Time)
البرنامج يرى العملية على فترات، وبينها لا يعرف شيئا. فإذا تأخرنا كثيرا صار التصحيح قديما عندما يصل، وقد يدفع في الاتجاه الخاطئ. نجرّب المنظّم التناسبي نفسه (Kp = 0.1) على الصندوق نفسه، ولا نغيّر سوى زمن العيّنة:
import math
GAIN = 40.0
TAU = 20.0
KP = 0.1
def run(dt):
a = 1 - math.exp(-dt / TAU)
temp = 20.0
peak = temp
low = 99.0
high = 0.0
steps = int(240 / dt)
for k in range(steps):
u = max(0.0, min(1.0, KP * (45.0 - temp)))
temp += a * (20.0 + GAIN * u - temp)
peak = max(peak, temp)
if k >= steps // 2:
low = min(low, temp)
high = max(high, temp)
return peak, high - low
for dt in (1, 5, 10, 20, 30):
peak, swing = run(dt)
print("Ts=%2d s peak=%.1f swing=%.1f" % (dt, peak, swing))Ts= 1 s peak=40.0 swing=0.0 Ts= 5 s peak=40.5 swing=0.0 Ts=10 s peak=44.1 swing=5.6 Ts=20 s peak=49.2 swing=18.5 Ts=30 s peak=52.7 swing=25.4
الشرط في هذا المثال هو a · (1 + Kp · K) < 2. ومع Kp · K = 4 نحصل على a < 0.4، ومنه Ts < 10.2 s. وهذا ما نراه في الأرقام: عند 10 s نحن على حافة الحد، والحركة تخمد ببطء شديد، وعند 20 s لا تخمد أبدا.
من الناحية العملية لا نقترب من الحد. نأخذ القاعدة الشائعة: عشر عيّنات على الأقل في ثابت الزمن، Ts ≤ τ/10. ولا فائدة من المبالغة في الاتجاه الآخر: الحرارة في صندوقنا لا تتغيّر في جزء من الثانية، وعند عيّنات كثيرة جدا يصبح فرق القراءتين في الحد التفاضلي ضجيجا فقط، وتصغر نافذة المرمّز فتسوء دقته، كما حسبنا في 20.4.
| العملية | ثابت زمن تقريبي للمثال | زمن عيّنة مناسب |
|---|---|---|
| حرارة صندوقنا | 20 s | 2 s فأقل |
| مصباح ومقاومة ضوئية | 0.5 s | 50 ms فأقل |
| محرّكنا | 1 s | 100 ms فأقل |
time.sleep(0.1) وحدها لا تعطيه، لأن زمن الحساب والطباعة يضاف إليها. لذلك نحسب موعد كل خطوة من الموعد السابق بـticks_add، كما في البرنامج التالي. وقد نستعمل مؤقّتا أو مهمة asyncio من الفصل 19.نجمع الآن كل ما سبق: محرّك مُحاكى، ونبضات مرمّز، ومنظّم PI من مكتبتنا، وخطوة ثابتة:
import math
import time
import board
from pid import PID
PPR = 40
PERIOD_MS = 100
DT = PERIOD_MS / 1000
GAIN = 300.0
TAU = 1.0
A = 1 - math.exp(-DT / TAU)
TARGET = 150.0
motor = board.pwm(board.LED_EXT, 1000)
loop = PID(0.004, 0.008, 0.0, DT)
speed = 0.0
turned = 0.0
load = 0.0
deadline = time.ticks_ms()
for k in range(1, 121):
if k == 61:
load = 60.0
turned += speed * PPR * DT / 60
pulses = int(turned)
turned -= pulses
u = loop.update(TARGET, pulses * 60 / (PPR * DT))
motor.duty_u16(int(u * 65535))
speed += A * (GAIN * u - load - speed)
if k % 15 == 0:
print("t=%4.1f load=%2.0f speed=%3.0f duty=%2.0f%%"
% (k * DT, load, speed, u * 100))
deadline = time.ticks_add(deadline, PERIOD_MS)
wait = time.ticks_diff(deadline, time.ticks_ms())
if wait > 0:
time.sleep_ms(wait)t= 1.5 load= 0 speed=157 duty=55% t= 3.0 load= 0 speed=155 duty=52% t= 4.5 load= 0 speed=149 duty=43% t= 6.0 load= 0 speed=151 duty=50% t= 7.5 load=60 speed=140 duty=72% t= 9.0 load=60 speed=151 duty=71% t=10.5 load=60 speed=151 duty=77% t=12.0 load=60 speed=151 duty=77%
الآن تصل السرعة إلى 150 rpm تقريبا قبل الحمل وبعده. الخطأ الدائم زال، ولا تبقى إلا حركة صغيرة بحجم خطوة المرمّز. وارتفعت دورة العمل وحدها لتعويض الحمل، وهذا هو عمل الحلقة المغلقة.
أهمّ أفكار الفصل:
- الحلقة المفتوحة لا تعرف النتيجة، والحلقة المغلقة تقيس وتصحّح حسب الخطأ e = r − y.
- التحكّم بحالتين بسيط وله دورة ذاتية، ونطاق التخلّف يحدّد التموّج وعدد مرات التبديل.
- المنظّم التناسبي يترك خطأ دائما، لأنه يحتاج إلى خطأ كي يعطي أمرا.
- المرمّز يحوّل السرعة إلى نبضات، ونافذة العدّ توازن بين الدقة والبطء.
- الحد التكاملي يزيل الخطأ الدائم ويحتاج إلى حماية من الإشباع، والحد التفاضلي يكبّر الضجيج.
- زمن العيّنة ثابت، وعشر عيّنات على الأقل في ثابت الزمن.
ملخّص قوانين الفصل:
| القانون | الصيغة |
|---|---|
| الخطأ | e = r − y |
| عملية من الرتبة الأولى | Tk+1 = Tk + a · (Ta + K · uk − Tk) |
| الخطأ الدائم للمنظّم التناسبي | ess = r − d1 + Kp · K |
| السرعة من المرمّز | n = 60 · NP · T |
| المنظّم PID | u = Kp · e + Ki · ∫e dt + Kd · dedt |
أوامر الفصل:
| الأمر | عمله |
|---|---|
| PID(kp, ki, kd, dt, lo, hi) | منظّم من المكتبة، بحدّين للخرج. |
| loop.update(target, measured) | خطوة واحدة: تعطي الأمر الجديد. |
| loop.reset() | تصفير المجموع، عند إعادة تشغيل الحلقة. |
| machine.disable_irq() | إيقاف المقاطعات لحظة، حول متغيّر مشترك. |
| time.ticks_add(t, ms) | موعد الخطوة التالية بزمن ثابت. |
قاموس المصطلحات – מילון מונחים:
| بالعربية | بالعبرية | English |
|---|---|---|
| الحلقة المفتوحة | חוג פתוח | Open Loop |
| الحلقة المغلقة | חוג סגור | Closed Loop |
| التغذية الراجعة | משוב | Feedback |
| المخطّط الصندوقي | תרשים מלבנים | Block Diagram |
| القيمة المطلوبة | אות רצוי | Set Point |
| القيمة المقيسة | אות מצוי | Measured Value |
| الخطأ | שגיאה | Error |
| الاضطراب | הפרעה | Disturbance |
| المنظّم | בקר | Controller |
| العملية | תהליך מבוקר | Process |
| ثابت الزمن | קבוע זמן | Time Constant |
| الخطأ الدائم | שגיאה מתמדת | Steady-State Error |
| المرمّز | אנקודר | Encoder |
| الإشباع التكاملي | התרוממות אינטגרל | Integrator Windup |
| التجاوز | חריגה | Overshoot |
| زمن العيّنة | זמן דגימה | Sampling Time |
duty_u16 هي 4915 من أصل 65535. وهذان الحدّان قيمتان شائعتان تختلفان من طراز إلى آخر.أسئلة للتمرين:
kd * slope على القراءة وليس على الخطأ؟(ب) ماذا يحدث في الاستدعاء الأول؟
self.last فارغ، فنعتبر الميل صفرا.reset()؟(ب) ماذا يحدث إذا أطفأنا السخّان بزر، وتركنا الحلقة تجمع الخطأ، ثم عدنا؟
update ونستدعي reset().