כניסות ויציאות ספרתיות
עד עכשיו התוכנית דיברה רק אל המסך. בפרק הזה היא יוצאת אל העולם: מדליקה נורית וקוראת לחצן. זה נראה פשוט, אבל ניתקל בשלוש הפתעות: לחצן לחוץ נקרא 0, לחיצה אחת נספרת כמה לחיצות, ואותה תוכנית לא תעבוד בלוח אחר אלא אם נפריד את החומרה מן הלוגיקה.
- נבחין בין כניסה ליציאה ובין שני סוגי היציאה, נכיר את גבולות המתח והזרם, ונחשב נגד לנורית.
- נכיר את הקובץ
board.pyונבין למה תוכנית שמייבאת אותו נשארת אחת בשני הלוחות. - נקרא לחצן בעזרת נגד המשיכה הפנימי, נטפל בקפיצות המגע, ונחבר הכול לרמזור.
3.1 הדק ספרתי: שני מצבים, מתח וזרם (Digital Pin)
הדק במצב ספרתי מכיר שני מצבים בלבד: 1, הרמה הגבוהה, ו-0, הרמה הנמוכה. בשני הלוחות שלנו 1 פירושו מתח קרוב ל-3.3 V, ו-0 פירושו מתח קרוב לאפס. ההדק פועל באחד משני אופנים:
- הדק כניסה: מודד את המתח שמגיע מבחוץ ואינו כופה מתח, וכמעט אינו מושך זרם. גיליון הנתונים קובע שני ספים: מעל הראשון נקרא 1, מתחת לשני נקרא 0, ובין השניים אין הבטחה.
- הדק יציאה: כופה מתח בעצמו, ונותן זרם לעומס או קולט ממנו.
לכל אחד משני האופנים יש גבולות. כך אומר התיעוד של מיקרו-פייתון על שני הלוחות שלנו:
| ESP32 | Pico W / Pico 2 W | |
|---|---|---|
| המתח הגבוה ביותר בהדק | 3.6 V כגבול מוחלט, והתקרבות אליו מסכנת את השבב | אינו מצוין: פונים לגיליון הנתונים |
| הזרם הבטוח בהדק | 5, 10, 20, 40 mA לפי עוצמת ההנעה drive, וברירת המחדל 20 mA | אינו מצוין: פונים לגיליון הנתונים |
3.2 שני סוגי יציאה (Output Types)
בתוך ההדק יש שני מתגים אלקטרוניים. הדרך שבה משתמשים בהם קובעת את סוג היציאה:
- יציאת דחיפה-משיכה,
Pin.OUT: ב-1 ההדק מחובר ל-3V3, וב-0 הוא מחובר ל-GND. זה המצב הרגיל, ובו נשתמש בכל התוכניות של הפרק. - יציאת ניקוז פתוח,
Pin.OPEN_DRAIN: ב-0 ההדק מחובר ל-GND, וב-1 הוא אינו מחובר לכלום, ולכן צריך נגד חיצוני שמעלה את המתח. כך כמה שבבים יכולים לחלוק חוט אחד בלי התנגשות, ונפגוש זאת באפיק I2C בפרק 9.
3.3 נורית ונגד (LED and Resistor)
נורית (דיודה פולטת אור) מעבירה זרם בכיוון אחד בלבד, מן האנודה אל הקתודה, והמתח עליה כמעט קבוע; אנו קוראים לו המתח הישר VF. לכן איננו מחברים אותה ישירות להדק: דבר אינו מגביל את הזרם, חוץ מנגד שנחבר איתה בטור.
LED_EXT נותן זרם ברמה 1: הנורית דולקת.| סמל | פירוש | יחידה |
|---|---|---|
| VOH | מתח ההדק ברמה 1 | V |
| VF | המתח הישר של הנורית, מגיליון הנתונים שלה | V |
| I | הזרם שאנו רוצים | A |
נבחר ערך זמין שגבוה מן המחושב, נניח 330 Ω, וכך הזרם יורד מעט ל-3.94 mA ואינו עולה.
3.4 התוכנית הראשונה: הבהוב (Blink)
בכל אחד משני הלוחות יש נורית מוכנה, ולכן אין צורך בשום חיבור. נתחיל ב-ESP32, שברוב לוחות DevKit הנורית שלו על הדק 2, ועלינו לוודא זאת בלוח שלנו:
from machine import Pin
import time
led = Pin(2, Pin.OUT)
while True:
led.value(not led.value())
print("LED:", led.value())
time.sleep(0.5)LED: 1 LED: 0 LED: 1 ...
Pin(2, Pin.OUT) יוצר אובייקט להדק 2 והופך אותו ליציאה, ו-led.value(not led.value()) הופך את מצבו: קורא את המצב וכותב את ההפך. בגרסאות חדשות של מיקרו-פייתון קיימת גם led.toggle(), אבל היא אינה עובדת בכל גרסה. כדי להציב בו 1 או 0 כותבים led.on() או led.off().
ב-Pico W הנורית אינה על הדק רגיל אלא מחוברת לשבב האלחוט, ולכן ניגשים אליה בשם "LED": כותבים Pin("LED", Pin.OUT). השורה הזאת עצמה נעצרת בשגיאה ב-ESP32, כי השם אינו מוכר שם.
Pin(2, Pin.OUT) ב-Pico W אינה מציגה שום הודעה, כי ההדק GP2 קיים באמת. התוכנית רצה, והנורית שעל הלוח אינה נדלקת לעולם.3.5 הקובץ board.py: הפרדת חומרה מלוגיקה (board.py)
אפשר להעתיק תוכנית ולשנות בה את המספרים כל עוד היא קצרה ואחת, אבל כשהתוכניות מתרבות זה נכשל. וההבדלים בין שני הלוחות אינם נעצרים בנורית:
| ESP32 | Pico W / Pico 2 W | |
|---|---|---|
| הנורית שעל הלוח | מספר הדק: 2 | שם: "LED" |
| ההדקים 34–39 | לכניסה בלבד, וללא נגד משיכה פנימי | אינם קיימים כלל |
| הממיר והטיימר | דורשים הגדרה ומספר (פרקים 6 ו-4) | אינם דורשים |
הפתרון הוא מה שנעשה בהמשך הספר: אוספים כל מה שנוגע ללוח בקובץ אחד בשם board.py. לכל לוח הקובץ שלו, והשמות בשניהם זהים והערכים שונים. זו הפרדת חומרה מלוגיקה: התוכנית אומרת מה היא רוצה, והקובץ יודע איפה היא נמצאת.
# board.py - ESP32 DevKit from machine import ADC, I2C, PWM, SPI, UART, Pin, Timer LED = 2 LED_EXT = 13 BTN = 14
# board.py - Raspberry Pi Pico W / Pico 2 W from machine import ADC, I2C, PWM, SPI, UART, Pin, Timer LED = 'LED' LED_EXT = 15 BTN = 14
על הלוח שומרים את הקובץ שלו, בשם board.py. התוכנית הבאה אינה מזכירה מספר ואינה מזכירה שם של לוח. הרצנו אותה בבניית הספר בשני הלוחות, וכל אחד מהם נתן את הערך שלו:
board.LED הוא ההבדל היחיד.from machine import Pin
import board
led = Pin(board.LED, Pin.OUT)
led.on()
print("board.LED is", repr(board.LED))
print("level:", led.value())board.LED is 2 level: 1
board.LED is 'LED' level: 1
ועכשיו נכתוב את ההבהוב פעם אחת לכל הלוחות. שורה אחת השתנתה מן התוכנית של ESP32, ושורה אחת הוספנו:
from machine import Pin
import time
import board
led = Pin(board.LED, Pin.OUT)
while True:
led.value(not led.value())
print("LED:", led.value())
time.sleep(0.5)LED: 1 LED: 0 LED: 1 ...
3.6 לחצן: הדק כניסה ונגד משיכה (Button and Pull-up)
לחצן מחבר שני קצוות כשלוחצים עליו ומנתק אותם כשעוזבים. אילו חיברנו את הלחצן רק בין ההדק ל-GND, ההדק היה צף כשעוזבים את הלחצן: אינו מחובר לכלום, קולט רעש, והקריאה שלו משתנה באקראי. לכן מוסיפים נגד משיכה, שנמצא בתוך השבב, ומפעילים אותו בעזרת Pin.PULL_UP.
from machine import Pin
import time
import board
btn = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
led = Pin(board.LED, Pin.OUT)
while True:
pressed = btn.value() == 0
led.value(1 if pressed else 0)
print("pressed" if pressed else "released")
time.sleep(0.5)released pressed pressed released ...
btn.value() == 0 בשורה אחת תחת שם ברור, ואיננו חוזרים עליו בתוכנית.3.7 קפיצות מגע (Contact Bounce)
אנו רוצים מונה לחיצות, שסופר רק במעבר מ-1 ל-0. אבל המגע שבתוך הלחצן הוא שתי חתיכות מתכת, וכשלוחצים הן קופצות כמה פעמים לפני שהן מתייצבות:
הפתרון בתוכנית פשוט: כשרואים את המעבר, ממתינים עד שהמגע מתייצב ואז קוראים שוב. אם ההדק נשאר ב-0, זו לחיצה אמיתית:
from machine import Pin
import time
import board
DEBOUNCE_MS = 20
btn = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
count = 0
last = 1
while True:
level = btn.value()
if last == 1 and level == 0:
time.sleep_ms(DEBOUNCE_MS)
level = btn.value()
if level == 0:
count += 1
print("count =", count)
last = level
time.sleep_ms(5)count = 1 ...
הרצנו את אותה תוכנית גם אחרי שמחקנו את ההמתנה ואת הקריאה השנייה, עם אותה כניסה, והשורה האחרונה שהודפסה הייתה count = 3: לחיצה אחת נספרה כ-3 לחיצות.
המתנה של 20 ms ארוכה ביחס לקפיצות וקצרה ביחס לאדם, והיא ערך להתחיל בו ולכוונן לפי הלחצן. לפתרון יש מחיר: בזמן ההמתנה התוכנית אינה עושה דבר אחר, ובפרק 4 נעקוף זאת.
3.8 פרויקט: רמזור (Traffic Light)
נחבר את כל מה שלמדנו: שלוש יציאות ולחצן אחד. הרמזור נשאר ירוק עד שהולך רגל לוחץ על הלחצן, ואז הוא משלים את מחזורו. את השלבים נתאר בטבלה, וכך התוכנית נשארת קצרה וקל לשנות בה את הזמנים.
from machine import Pin
import time
import board
red = Pin(board.LED_EXT, Pin.OUT)
yellow = Pin(board.SERVO, Pin.OUT)
green = Pin(board.BUZZER, Pin.OUT)
btn = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
PHASES = (("red", 1, 0, 0, 3),
("red+yellow", 1, 1, 0, 1),
("green", 0, 0, 1, 0),
("yellow", 0, 1, 0, 1))
while True:
for name, r, y, g, seconds in PHASES:
red.value(r)
yellow.value(y)
green.value(g)
print(name)
if seconds > 0:
time.sleep(seconds)
else:
while btn.value() == 1:
time.sleep_ms(20)red red+yellow green yellow red ...
board.SERVO ו-board.BUZZER כי בפרויקט הזה הם יציאות פנויות. השם בקובץ מתאר את השימוש בו בספר, וההדק עצמו אינו יודע מה מחובר אליו.עיקרי הפרק:
- הדק ספרתי הוא כניסה או יציאה, ויש לו גבולות מתח וזרם שאנו לוקחים מן התיעוד ומגיליון הנתונים, ולא מן הזיכרון.
board.pyאוסף את מה שנוגע ללוח:board.LEDהוא 2 ב-ESP32 ו-"LED"ב-Pico W, והתוכנית אינה משתנה.- עם
Pin.PULL_UPלחצן לחוץ נקרא 0. - לחיצה אחת עלולה להביא הרבה מעברים, ולכן ממתינים וקוראים שוב.
סיכום הכללים בפרק:
| הכלל | הנוסחה |
|---|---|
| הנגד של הנורית | R = VOH − VFI |
פקודות הפרק:
| פקודה | מה היא עושה |
|---|---|
| Pin(n, Pin.OUT) | הדק יציאה מסוג דחיפה-משיכה. |
| Pin(n, Pin.OPEN_DRAIN) | הדק יציאה מסוג ניקוז פתוח. |
| Pin(n, Pin.IN, Pin.PULL_UP) | הדק כניסה עם נגד משיכה פנימי. |
| p.on() , p.off() | מציבים בהדק 1, או 0. |
| p.value(not p.value()) | הופכים את מצב ההדק. |
| p.value() , p.value(x) | קוראים את ההדק, או כותבים אליו. |
מילון מונחים:
| עברית | English | ערבית |
|---|---|---|
| הדק | Pin | الطرف |
| מוצא דחיפה-משיכה | Push-Pull Output | مُخرَج الدفع والسحب |
| מוצא ניקוז פתוח | Open-Drain Output | مُخرَج المصرف المفتوح |
| נורית | Light-Emitting Diode | ثنائي باعث للضوء |
| הדק צף | Floating Pin | طرف عائم |
| נגד משיכה | Pull-up Resistor | مقاومة السحب |
| קפיצות מגע | Contact Bounce | ارتداد التلامس |
שאלות לתרגול:
Pin(36, Pin.OUT) נכשל ב-ESP32?from machine import Pin
import time
import board
led = Pin(board.LED, Pin.OUT)
for n in range(6):
led.value(not led.value())
time.sleep(0.2)
print("done:", led.value())board.py שם חדש LED_RED ב-ESP32. למה צריך להוסיף אותו גם לקובץ של Pico W?board.LED_RED תיעצר בשגיאה בלוח שאינו מכיר את השם.(א) מתי היא דולקת?
(ב) האם זה עובד עם
Pin.OPEN_DRAIN?(א) מה קורה אילו הקפיצות היו ארוכות מן ההמתנה?
(ב) ומה הנזק בהמתנה של 500 ms?