برمجة المتحكّمات بلغة مايكروبايثون · الفصل 7 — الخرج التماثلي: PWM وDACמוצא אנלוגי: PWM ו-DAC · תכנות מיקרו-בקרים בשפת MicroPython⌂📚🏠
الفصل 7

الخرج التماثلي: PWM وDAC

מוצא אנלוגי: PWM ו-DAC – Analog Output
3 ساعات تعليمية

في الفصل السابق قرأنا جهدا متغيّرا. والآن الاتجاه المعاكس: كيف نُخرج من طرف لا يعرف إلا 0 و3.3 V ضوءا خافتا، أو زاوية محرّك، أو نغمة؟ الجواب في أغلب الحالات خدعة زمنية اسمها PWM. وفي حالة واحدة فقط نجد مخرجا تماثليا حقيقيا، وعلى لوحة واحدة.

في هذا الفصل:
  • نحسب الجهد المتوسّط وقيمة duty_u16 من دورة العمل.
  • نتحكّم في إضاءة ثنائي باعث للضوء بمقياس الجهد، ونحرّك محرّكا مؤازرا بمكتبة servo.py.
  • نولّد نغمة بتغيير التردد، ونفرّق بين PWM وDAC.
  • نحسب مرشّح RC الذي يعوّض غياب DAC في Pico W.
  • نشغّل محرّك تيار مستمر عبر ترانزستور أو جسر H، دون تيار من طرف المتحكّم.

7.1 مبدأ تعديل عرض النبضة – עיקרון PWM (Pulse Width Modulation)

لا يستطيع الطرف الرقمي إعطاء 1.65 V. لكنه يستطيع التبديل بين 0 و3.3 V بسرعة كبيرة. إذا كان الحمل أبطأ من التبديل، كالعين أو المحرّك، فهو يشعر بالمتوسّط فقط.

تعديل عرض النبضة (אפנון רוחב פולס, PWM): موجة مربّعة ترددها ثابت، ونتحكّم في الزمن الذي تبقى فيه عالية خلال كل دورة.
D = 25%0.825 VD = 50%1.65 VD = 75%2.475 VT
ثلاث دورات عمل بالتردد نفسه. الخط المتقطّع هو الجهد المتوسّط.
دورة العمل (מחזור עבודה) والجهد المتوسّط (מתח ממוצע)
D = tonT
Vavg = D · VH
الرمزالمعنىوحدة القياس
tonزمن النبضة العالية، وT = 1/f زمن الدورة s
VHالجهد العالي للطرف V
مثال 7.1
دورة العمل 25% على طرف جهده 3.3 V. ما الجهد المتوسّط؟
الحل:
Vavg = D · VH = 0.25 · 3.3 = 0.825 V
Vavg = 0.825 V
سؤال 7.1: نريد جهدا متوسّطا قدره 2.2 V من طرف جهده 3.3 V. ما دورة العمل؟

7.2 المخرج board.pwm والأمر duty_u16 – פקודות PWM

ننشئ المخرج بالدالة board.pwm(pin, freq). وكما في read_u16، يأخذ duty_u16 عددا بين 0 و65535 في كل اللوحات، فيكون N = D · 65535. فدورة عمل 30% تعني N = 19661.

ESP32Pico W
مجال التردد حسب التوثيق1 Hz – 40 MHz8 Hz – 62.5 MHz عند ساعة 125 MHz
عدد القنوات16 قناة، و8 ترددات مختلفة8 شرائح في كل منها قناتان
القيدالتردد الأعلى يقلّل دقة دورة العملقناتا الشريحة تشتركان في التردد
تحذير: الثنائي الموجود على لوحة Pico W موصول بشريحة اللاسلكي، وليس بطرف PWM عادي، فلا يصلح لهذا الفصل. لذلك نستعمل LED_EXT. وعلى ESP32 يذكر التوثيق أن دورة العمل الافتراضية 50%، فنحدّدها مباشرة بعد إنشاء المخرج.
الطباعة تحسب المتوسّط من القانون. جرّبوه في المختبر: prog=pwm_duty.
import board
import time

led = board.pwm(board.LED_EXT, 1000)

for percent in (0, 25, 50, 100):
    duty = percent * 65535 // 100
    led.duty_u16(duty)
    v = duty * 3.3 / 65535
    print("%3d%%  duty_u16 = %5d  Vavg = %.3f V" % (percent, duty, v))
    time.sleep(1)
ESP32 + Pico W · MicroPython 1.29 · simulated board
  0%  duty_u16 =     0  Vavg = 0.000 V
 25%  duty_u16 = 16383  Vavg = 0.825 V
 50%  duty_u16 = 32767  Vavg = 1.650 V
100%  duty_u16 = 65535  Vavg = 3.300 V

7.3 تعتيم الضوء بمقياس الجهد – עמעום נורית בפוטנציומטר

نجمع الآن الفصلين: board.analog_in من الفصل 6 يعطي 0–65535، وduty_u16 يأخذ 0–65535، فلا حاجة إلى أي تحويل. ومقاومة التوالي تحدّد التيار. بجهد أمامي نموذجي 2 V تعطي 330 Ω تيارا قدره 3.9 mA، وهي قيمة صغيرة وآمنة للطرف.

3V3POTGNDLED_EXT330 Ω10 kΩ
مقياس الجهد على المدخل التماثلي، والثنائي الباعث للضوء على مخرج PWM.
الجهد على مقياس الجهد يتغيّر كل ثانية.
import board
import time

pot = board.analog_in(board.POT)
led = board.pwm(board.LED_EXT, 1000)

while True:
    raw = pot.read_u16()
    led.duty_u16(raw)
    print("duty_u16 = %5d  (%3.0f%%)" % (raw, raw * 100 / 65535))
    time.sleep(1)
ESP32 + Pico W · MicroPython 1.29 · simulated board
duty_u16 =     0  (  0%)
duty_u16 = 32776  ( 50%)
duty_u16 = 65535  (100%)
...
▶تجربة حية — تعتيم الضوء بمقياس الجهدحرّكوا مقياس الجهد، وراقبوا الإضاءة.
⚠
المختبر غير متاح الآن. المادة في هذا الفصل كاملة من دونه — نواصل القراءة، ونعود إلى التجربة لاحقا أو نفتحها مباشرة من الرابط الذي تحت الإطار.
https://fahed-lab.com/books/micropython/lab/mpy_lab?prog=pwm_dim
نفتح المختبر في صفحة مستقلة ↗ fahed-lab.com/books/micropython/lab/mpy_lab?prog=pwm_dim

7.4 المحرّك المؤازر – סרבו (Servo)

المحرّك المؤازر (סרבו) لا يقرأ الجهد المتوسّط، بل عرض النبضة (רוחב פולס). نرسل نبضة كل 20 ms، أي بتردد 50 Hz. والعرض النموذجي بين 0.5 ms و2.5 ms يقابل زاوية بين 0° و180°. هذه قيم نموذجية تختلف بين الطرازات، فنراجع ورقة بيانات المحرّك.

0203.3tV[ms]1.5 ms
نبضة 1.5 ms كل 20 ms.
SERVOGNDServoPWM+5 V
تغذية خارجية بأرضي مشترك.

ومنها قيمة duty_u16: N = tpT · 65535. فلمنتصف المجال: N = 1.520 · 65535 = 4915.

نضع هذا الحساب في مكتبة نعيد استعمالها، ونعطيها كائن PWM جاهزا، فتبقى مستقلة عن اللوحة:

الملف servo.py، على كل اللوحات
# servo.py - a hobby servo on any PWM output, the same on every board.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com

PERIOD_US = 20000


class Servo:
    def __init__(self, pwm, min_us=500, max_us=2500):
        self.pwm = pwm
        self.min_us = min_us
        self.max_us = max_us
        pwm.freq(50)

    def pulse(self, us):
        us = max(self.min_us, min(self.max_us, us))
        self.pwm.duty_u16(int(us * 65535 / PERIOD_US + 0.5))

    def angle(self, deg):
        deg = max(0, min(180, deg))
        span = self.max_us - self.min_us
        self.pulse(self.min_us + span * deg / 180)

    def off(self):
        self.pwm.duty_u16(0)
أرقام duty_u16 نفسها في المثال.
import board
import time
from servo import Servo

arm = Servo(board.pwm(board.SERVO, 50))

for deg in (0, 90, 180):
    arm.angle(deg)
    print("angle %3d  duty_u16 = %d" % (deg, arm.pwm.duty_u16()))
    time.sleep_ms(500)
ESP32 + Pico W · MicroPython 1.29 · simulated board
angle   0  duty_u16 = 1638
angle  90  duty_u16 = 4915
angle 180  duty_u16 = 8192
تحذير: لا نغذّي المحرّك المؤازر من طرف 3V3: تيار تشغيله يفوق ما تعطيه اللوحة، وقد يُعيد تشغيلها. نستعمل تغذية خارجية ونوصل الأرضيين معا. وإذا اهتزّ المحرّك عند الطرفين، نضيّق المجال بالوسيطين min_us وmax_us.

7.5 الطنّان والتردد – זמזם ותדר (Buzzer)

حتى الآن غيّرنا دورة العمل وثبّتنا التردد. في الطنّان (זמזם) السلبي نفعل العكس: التردد هو النغمة، ودورة العمل 50% تكفي. ونغمات السلّم الموسيقي تتضاعف كل 12 درجة، فيعطينا f = 440 · 2n/12 النغمة التي تبعد n درجة عن A4. أما الطنّان الفعّال فله مذبذب داخلي، ويطلق نغمة واحدة بمجرد تغذيته، فلا يفيده تغيير التردد.

BUZZERGND
طنّان سلبي بين الطرف وGND. وطنّان التيار الكبير يُشغَّل عبر ترانزستور كما في 7.8.
نغيّر التردد بالأمر freq، ونُسكت الطنّان بدورة عمل 0.
import board
import time

buzzer = board.pwm(board.BUZZER, 440)

for n, name in ((0, "A4"), (3, "C5"), (7, "E5")):
    f = int(440 * 2 ** (n / 12) + 0.5)
    buzzer.freq(f)
    buzzer.duty_u16(32768)
    print(name, f, "Hz")
    time.sleep_ms(300)

buzzer.duty_u16(0)
ESP32 + Pico W · MicroPython 1.29 · simulated board
A4 440 Hz
C5 523 Hz
E5 659 Hz

7.6 المحوّل الرقمي التماثلي – ממיר D/A, רק ב-ESP32 (DAC)

المحوّل الرقمي التماثلي (ממיר ספרתי לאנלוגי, DAC): دائرة تحوّل عددا إلى جهد ثابت حقيقي على الطرف، دون تبديل. واسمه بالإنجليزية DAC.

حسب التوثيق، يوجد في ESP32 على الطرفين 25 و26، ويأخذ write عددا من 8 بتّات، بين 0 و255. ولا يوجد في Pico W، ولا في Raspberry Pi 4/5. ونسخ أخرى من شرائح ESP32 تختلف في ذلك، فنراجع توثيقها.

وجهد المخرج V = N255 · Vref، حيث N العدد المكتوب وVref نحو 3.3 V.

مثال 7.2
نريد 1.5 V. ما العدد الذي نكتبه، وما الجهد الذي نحصل عليه؟
الحل:
N = 1.53.3 · 255 ≈ 116
V = 116255 · 3.3 = 1.501 V

الخطوة الواحدة هنا 12.9 mV، أي أكبر بكثير من خطوة المحوّل التماثلي الرقمي في الفصل 6.

V = 1.501 V

لنتأكد، نوصل الطرف 25 بالطرف 34 بسلك، ونقرأ ما كتبناه بالمحوّل الذي نعرفه:

على ESP32 فقط. والأرقام هنا مثالية: اللوحة الحقيقية تضيف خطأ المحوّلين.
import board
import time
from machine import DAC, Pin

dac = DAC(Pin(board.DAC_OUT))
adc = board.analog_in(board.POT)

for n in (0, 128, 255):
    dac.write(n)
    time.sleep_ms(10)
    v = adc.read_u16() * 3.3 / 65535
    print("write %3d  read back %.3f V" % (n, v))
ESP32 · MicroPython 1.29 · simulated board
write   0  read back 0.000 V
write 128  read back 1.657 V
write 255  read back 3.300 V

7.7 على Pico W: مرشّح RC – PWM עם מסנן RC

ما دام Pico W بلا محوّل رقمي تماثلي، نمرّر موجة PWM في مرشّح (מסנן) من مقاومة ومكثّف. المكثّف يشحن ويفرغ قليلا في كل دورة، فيبقى الجهد عليه قرب المتوسّط مع تموّج (אדווה) صغير.

PWMVout10 kΩ1 µF
مرشّح RC على مخرج PWM.
1.653.3tV
بدورة أطول من الواقع، ليظهر التموّج: الجهد يرتفع ويستقر حول المتوسّط.
تصميم المرشّح
fc = 12π R C
ΔV ≈ VH · D · (1 − D)f · R · C
الرمزالمعنىوحدة القياس
ΔVالتموّج عند تردد f، وهو أكبر عند D = 0.5. ونريد fc أقل بكثير من f V
مثال 7.3
R = 10 kΩ، وC = 1 µF، وD = 0.5. ما تردد القطع؟ وما التموّج عند 20 kHz؟
الحل:
fc = 12π · 104 · 10−6 = 15.9 Hz
ΔV20k = 3.3 · 0.2520000 · 10−2 = 4.1 mV

الثمن هو البطء: زمن الاستقرار نحو 5RC، أي 50 ms.

fc = 15.9 Hz
ما نكتبه لنحصل على الجهد المطلوب بعد المرشّح. والمُحاكاة لا تحتوي المرشّح، فلا تقيس الناتج.
import board

out = board.pwm(board.LED_EXT, 20000)

for v in (0.5, 1.0, 1.65, 2.5):
    out.duty_u16(int(v / 3.3 * 65535 + 0.5))
    print("%.2f V  duty_u16 = %5d" % (v, out.duty_u16()))
ESP32 + Pico W · MicroPython 1.29 · simulated board
0.50 V  duty_u16 =  9930
1.00 V  duty_u16 = 19859
1.65 V  duty_u16 = 32768
2.50 V  duty_u16 = 49648
خطأ شائع: PWM ليس محوّلا رقميا تماثليا. على الطرف نفسه لا يوجد إلا 0 و3.3 V. جهاز القياس البطيء يعرض المتوسّط، أما المحوّل في الفصل 6 فقد يقرأ 0 أو 3.3 V حسب لحظة القراءة. ولا نسحب تيارا من خرج المرشّح، لأن مقاومته في طريق التيار فيهبط الجهد.
PWMDAC
على الطرفموجة مربّعة: 0 أو 3.3 Vجهد ثابت حقيقي
الدقة16 بتّا في الأمر، والفعلية حسب التردد8 بتّات
اللوحاتESP32 وPico W وPi 4/5ESP32 فقط
يناسبالضوء، المحرّك، المحرّك المؤازر، النغمةجهد مرجع، إشارة صوتية بسيطة

7.8 محرّك التيار المستمر – מנוע DC (DC Motor)

المحرّك يحتاج إلى تيار أكبر بكثير مما يعطيه طرف المتحكّم، ولا نوصله بالطرف أبدا. الطرف يتحكّم في مفتاح فقط، وهو ترانزستور (טרנזיסטור)، وتيار المحرّك يأتي من تغذية منفصلة. وعندما ينفتح المفتاح يحاول ملف المحرّك إبقاء تياره، فيولّد جهدا عاليا قد يتلف الترانزستور. لذلك نضع بالتوازي مع المحرّك ثنائي الدوران الحرّ (דיודת חופש)، فيجد التيار طريقا يتلاشى فيه.

مثال 7.4
محرّك تياره 200 mA، ونفترض تكبيرا مضمونا β = 50 لهذا المثال. ما مقاومة القاعدة؟ (جهد القاعدة نحو 0.7 V.)
الحل:
IB = IMβ = 20050 = 4 mA
RB = 3.3 − 0.74 mA = 650 Ω

نختار القيمة القياسية 680 Ω، فيكون IB = 3.82 mA. ونتحقق من حد الطرف في ورقة بيانات اللوحة.

RB = 680 Ω
ملاحظة: هذا افتراض للتدريب. في الإشباع الموثوق تنخفض β كثيرا، وأوراق البيانات تذكر الإشباع غالبا عند β = 10. ولمحرّك كهذا يعني ذلك 20 mA من الطرف، وهذا كثير. لذلك نستخدم للتيارات الأكبر ترانزستور MOSFET بمستوى منطقي أو شريحة مشغّل.

الترانزستور يدير المحرّك في اتجاه واحد. ولعكس الاتجاه نحتاج إلى جسر H (גשר H): أربعة مفاتيح، إغلاق S1 وS4 يمرّر التيار في اتجاه، وإغلاق S3 وS2 يعكسه. ولا يجوز إغلاق مفتاحين في جهة واحدة معا، لأن ذلك قصر مباشر على التغذية. في الشرائح الجاهزة نرسل PWM إلى أحد مدخلي الشريحة و0 إلى الآخر.

+VMMPWMRB
مفتاح NPN في جهة الأرضي، والأرضيان موصولان معا.
+VGNDS1S2S3S4M
جسر H: المفتاحان S1 وS4 مغلقان.
طرفان من board لمدخلي الشريحة. نمرّ بالصفر قبل العكس ليتباطأ المحرّك ولا يكبر التيار.
import board
import time

in1 = board.pwm(board.LED_EXT, 1000)
in2 = board.pwm(board.BUZZER, 1000)

def drive(percent):
    duty = abs(percent) * 65535 // 100
    a = duty if percent > 0 else 0
    b = duty if percent < 0 else 0
    in1.duty_u16(a)
    in2.duty_u16(b)
    print("speed %4d%%  in1 = %5d  in2 = %5d" % (percent, a, b))

for speed in (60, 100, 0, -50, 0):
    drive(speed)
    time.sleep_ms(500)
ESP32 + Pico W · MicroPython 1.29 · simulated board
speed   60%  in1 = 39321  in2 =     0
speed  100%  in1 = 65535  in2 =     0
speed    0%  in1 =     0  in2 =     0
speed  -50%  in1 =     0  in2 = 32767
speed    0%  in1 =     0  in2 =     0

7.9 على Raspberry Pi 4/5

لا توجد machine، ولا مخرج تماثلي. المكتبة gpiozero تعطي الصنف PWMLED، وخاصيته value بين 0 و1، وتردده الافتراضي 100 Hz حسب توثيقها. وللقراءة من مقياس جهد نعود إلى ADS1115 في الفصل 6. لم يُشغَّل هذا القطع عند بناء الكتاب.

على Raspberry Pi 4/5
from gpiozero import PWMLED
from signal import pause

led = PWMLED(18)
led.value = 0.25
pause()

أهمّ أفكار الفصل:

ملخّص قوانين الفصل:

القانونالصيغة
الجهد المتوسّطVavg = D · VH
قيمة duty_u16N = D · 65535
مخرج DACV = N255 · Vref
تردد القطعfc = 12π R C
التموّجΔV ≈ VH · D · (1 − D)f · R · C

أوامر الفصل:

الأمرعمله
board.pwm(pin, freq)مخرج PWM على طرف.
p.duty_u16(n)دورة العمل، من 0 إلى 65535.
p.freq(f)تغيير التردد.
Servo(pwm).angle(deg)زاوية المحرّك المؤازر، من مكتبة servo.py.
DAC(Pin(25)).write(n)جهد تماثلي من 0 إلى 255، على ESP32 فقط.

قاموس المصطلحات – מילון מונחים:

بالعربيةبالعبريةEnglish
تعديل عرض النبضةאפנון רוחב פולסPulse Width Modulation
دورة العملמחזור עבודהDuty Cycle
عرض النبضةרוחב פולסPulse Width
المحرّك المؤازرסרבוServo Motor
المحوّل الرقمي التماثليממיר ספרתי לאנלוגיDigital-to-Analog Converter
المرشّحמסנןFilter
التموّجאדווהRipple
ثنائي الدوران الحرّדיודת חופשFlyback Diode
جسر Hגשר HH-Bridge
الإجابات النهائية لأسئلة الفصل
7.1 D = 66.7%.

أسئلة للتمرين:

المستوى الأول — أساسي
1.
موجة PWM ترددها 500 Hz ودورة عملها 40%. ما زمن الدورة، وما ton؟
الإجابات: T = 2 ms، وton = 0.8 ms.
المستوى الثاني — تطبيق
2.
ما قيمة duty_u16 لنبضة محرّك مؤازر عرضها 2 ms؟
الإجابات: N = 6554.
3.
العين لا تحسّ بالضوء خطّيا، فنستعمل مربّع القيمة: raw * raw // 65535. ما ناتجه عند 50% من المجال؟ وما دورة العمل الناتجة؟
الإجابات: N = 16383، أي دورة عمل نحو 25%.
المستوى الثالث — تعمّق
4.
(أ) كم خطوة من duty_u16 بين 0° و180° في servo.py؟
(ب) كم خطوة للدرجة الواحدة؟
الإجابات: (أ) 8192 − 1638 = 6554. (ب) نحو 36.4 خطوة.
5.
مرشّح R = 10 kΩ وتردد PWM 5 kHz. ما أصغر C يعطي تموّجا لا يزيد عن 10 mV عند D = 0.5؟
الإجابات: C ≥ 1.65 µF، فنختار القيمة القياسية التالية.
لوحة القوانين: كل قوانين هذا الفصل مجموعة مع رسومها وجداول رموزها في الفصل 25 — لوحة القوانين الشاملة. (נוסחאון הספר)