מוצא אנלוגי: PWM ו-DAC
בפרק הקודם קראנו מתח משתנה. עכשיו הכיוון ההפוך: איך מוציאים מהדק שמכיר רק 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 במהירות גבוהה. אם העומס איטי מן המיתוג, כמו העין או מנוע, הוא מרגיש רק את הממוצע.
| סמל | פירוש | יחידה |
|---|---|---|
| ton | זמן הפולס הגבוה, ו-T = 1/f הוא זמן המחזור | s |
| VH | המתח הגבוה של ההדק | V |
7.2 המוצא board.pwm והפקודה duty_u16
יוצרים את המוצא בפונקציה board.pwm(pin, freq). וכמו ב-read_u16, duty_u16 מקבל מספר בין 0 ל-65535 בכל הלוחות, ולכן N = D · 65535. מחזור עבודה של 30% פירושו N = 19661.
| ESP32 | Pico W | |
|---|---|---|
| תחום התדר לפי התיעוד | 1 Hz – 40 MHz | 8 Hz – 62.5 MHz בשעון 125 MHz |
| מספר הערוצים | 16 ערוצים, ו-8 תדרים שונים | 8 פרוסות, ובכל אחת שני ערוצים |
| מגבלה | תדר גבוה מקטין את דיוק מחזור העבודה | שני ערוצי הפרוסה חולקים את התדר |
LED_EXT. וב-ESP32 התיעוד אומר שמחזור העבודה ברירת המחדל הוא 50%, ולכן קובעים אותו מיד אחרי יצירת המוצא.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)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, ערך קטן ובטוח להדק.
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)duty_u16 = 0 ( 0%) duty_u16 = 32776 ( 50%) duty_u16 = 65535 (100%) ...
7.4 סרבו (Servo)
סרבו אינו קורא את המתח הממוצע אלא את רוחב הפולס. שולחים פולס כל 20 ms, כלומר בתדר 50 Hz. הרוחב האופייני, בין 0.5 ms ל-2.5 ms, מתאים לזווית בין 0° ל-180°. אלה ערכים אופייניים שמשתנים בין דגמים, ולכן בודקים בדף הנתונים של המנוע.
ומכאן הערך של duty_u16: N = tpT · 65535. לאמצע התחום: N = 1.520 · 65535 = 4915.
את החישוב הזה שמים בספרייה שאפשר להשתמש בה שוב, ונותנים לה אובייקט PWM מוכן, וכך היא נשארת בלתי תלויה בלוח:
# 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)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)angle 0 duty_u16 = 1638 angle 90 duty_u16 = 4915 angle 180 duty_u16 = 8192
min_us ו-max_us.7.5 זמזם ותדר (Buzzer)
עד עכשיו שינינו את מחזור העבודה והשארנו את התדר קבוע. בזמזם פסיבי עושים להפך: התדר הוא הצליל, ומחזור עבודה של 50% מספיק. צלילי הסולם המוזיקלי מוכפלים כל 12 חצאי טון, ולכן f = 440 · 2n/12 נותן את הצליל שמרוחק n חצאי טון מ-A4. לזמזם אקטיבי, לעומת זאת, יש מתנד פנימי, והוא משמיע צליל אחד כשמזינים אותו, ולכן שינוי התדר לא עוזר.
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)A4 440 Hz C5 523 Hz E5 659 Hz
7.6 ממיר ספרתי לאנלוגי, רק ב-ESP32 (DAC)
לפי התיעוד, ב-ESP32 הוא קיים בהדקים 25 ו-26, ו-write מקבל מספר של 8 סיביות, בין 0 ל-255. אין אותו ב-Pico W ולא ב-Raspberry Pi 4/5. גרסאות אחרות של שבבי ESP32 שונות בזה, ולכן בודקים בתיעוד שלהן.
מתח המוצא הוא V = N255 · Vref, כאשר N הוא המספר שנכתב ו-Vref הוא כ-3.3 V.
המדרגה כאן היא 12.9 mV, גדולה בהרבה ממדרגת הממיר האנלוגי לספרתי בפרק 6.
כדי לוודא, מחברים בחוט את הדק 25 להדק 34, וקוראים את מה שכתבנו בממיר שאנחנו כבר מכירים:
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))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: PWM עם מסנן RC
מאחר שב-Pico W אין ממיר ספרתי לאנלוגי, מעבירים את גל ה-PWM דרך מסנן של נגד וקבל. הקבל נטען ומתפרק מעט בכל מחזור, ולכן המתח עליו נשאר קרוב לממוצע, עם אדווה קטנה.
| סמל | פירוש | יחידה |
|---|---|---|
| ΔV | האדווה בתדר f, והיא הגדולה ביותר כש-D = 0.5. רוצים ש-fc יהיה קטן בהרבה מ-f | V |
המחיר הוא האיטיות: זמן ההתייצבות הוא כ-5RC, כלומר 50 ms.
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()))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 | DAC | |
|---|---|---|
| על ההדק | גל ריבועי: 0 או 3.3 V | מתח קבוע אמיתי |
| דיוק | 16 סיביות בפקודה, והדיוק בפועל לפי התדר | 8 סיביות |
| לוחות | ESP32, Pico W ו-Pi 4/5 | ESP32 בלבד |
| מתאים ל | אור, מנוע, סרבו, צליל | מתח ייחוס, אות שמע פשוט |
7.8 מנוע DC (DC Motor)
מנוע צריך זרם גדול בהרבה ממה שהדק של הבקר נותן, ולעולם לא מחברים אותו להדק. ההדק שולט רק במתג, שהוא טרנזיסטור, וזרם המנוע בא מהזנה נפרדת. וכשהמתג נפתח, סליל המנוע מנסה לשמור על הזרם שלו, ומפיק מתח גבוה שעלול להרוס את הטרנזיסטור. לכן מחברים במקביל למנוע דיודת חופש, והזרם מוצא דרך שבה הוא מתפוגג.
בוחרים את הערך הסטנדרטי 680 Ω, ואז IB = 3.82 mA. ובודקים את הגבול של ההדק בדף הנתונים של הלוח.
הטרנזיסטור מפעיל את המנוע בכיוון אחד. כדי להפוך כיוון צריך גשר H: ארבעה מתגים. סגירת S1 ו-S4 מעבירה זרם בכיוון אחד, וסגירת S3 ו-S2 הופכת אותו. אסור לסגור יחד שני מתגים בצד אחד, כי זה קצר ישיר בהזנה. בשבבים מוכנים שולחים PWM לאחת משתי כניסות השבב ו-0 לאחרת.
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)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. קטע הקוד הזה לא הורץ בעת בניית הספר.
from gpiozero import PWMLED from signal import pause led = PWMLED(18) led.value = 0.25 pause()
עיקרי הפרק:
- הדק ספרתי נותן רק שני מצבים, ו-PWM נשען על עומס איטי מן המיתוג.
duty_u16מקבל 0–65535 כמוread_u16, ולכן הפוטנציומטר מתחבר לנורית ישירות.- הסרבו קורא את רוחב הפולס, והזמזם קורא את התדר.
- DAC הוא מתח אמיתי של 8 סיביות ב-ESP32 בלבד, וב-Pico W משתמשים ב-PWM עם מסנן RC.
- אין זרם עומס מהדק הבקר: טרנזיסטור או גשר H, עם דיודת חופש.
סיכום הכללים בפרק:
| הכלל | הנוסחה |
|---|---|
| המתח הממוצע | Vavg = D · VH |
| ערך duty_u16 | N = D · 65535 |
| מוצא DAC | V = 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 | H-Bridge | جسر H |
שאלות לתרגול:
duty_u16 לפולס של סרבו ברוחב 2 ms?raw * raw // 65535. מהי התוצאה ב-50% מן התחום? ומהו מחזור העבודה המתקבל?duty_u16 יש בין 0° ל-180° ב-servo.py?(ב) כמה צעדים יש למעלה אחת?