תכנות ב-Python וספריית gpiozero
עכשיו עוזבים את הבקר ועוברים למחשב שלם בגודל כרטיס. ב-Raspberry Pi 4/5 אין מיקרו-פייתון ואין machine, אלא לינוקס ופייתון רגיל. הרעיונות שלמדנו נשארים כמו שהם: הדק, יציאה, כניסה ואירוע. אבל דרך ההתקנה, ההפעלה והתזמון משתנה, והפרק הזה מראה מה משתנה ומה נשאר.
- נכין את מערכת ההפעלה, נתחבר ל-Pi דרך SSH, ונדע מה שונה בין Pi 4 ל-Pi 5.
- נעבוד בתוך סביבה וירטואלית, ונבין למה
pipמסרב לעבוד מחוצה לה. - נבחין בין מספור BCM למספור הפיזי, ונקפיד על מתח של 3.3 V.
- נשתמש ב-
LED, ב-Button, ב-PWMLED, ב-Servo, ב-DistanceSensorובאירועים. - נפעיל את התוכנית שלנו אוטומטית באתחול בעזרת שירות systemd.
22.1 התקנת המערכת וחיבור מרחוק (Setup and SSH)
בסעיף 1.2 השווינו בין הלוחות וראינו ש-Pi הוא מחשב ולא בקר. מותקנת עליו מערכת הפעלה, Raspberry Pi OS, והתוכנית שלנו היא אחת מעשרות תוכנות שרצות בו יחד. ההתקנה נעשית פעם אחת:
- מתקינים במחשב שלנו את התוכנה Raspberry Pi Imager מן האתר הרשמי, ובוחרים בה את דגם הלוח, את מערכת ההפעלה ואת כרטיס ה-microSD.
- לפני הכתיבה ממלאים את הגדרות ההתאמה האישית: שם המכשיר, משתמש וסיסמה, רשת WiFi והפעלת SSH. שמות המסכים עשויים להשתנות בין גרסאות.
- מכניסים את הכרטיס ל-Pi ומדליקים אותו, ממתינים לסיום האתחול הראשון, ואז מתחברים מן המחשב שלנו לשורת הפקודה:
ssh student@mypi.local sudo apt update sudo apt full-upgrade python --version sudo shutdown -h now
את פקודות המערכת שבפרק הזה איננו מריצים בזמן בניית הספר, כי הן דורשות Pi אמיתי. אבל התוכניות של gpiozero שיש מתחתיהן מסך פלט אכן הורצו.
| Pi 4 | Pi 5 | |
|---|---|---|
| מקור ההזנה שהחברה ממליצה עליו | 5 V, 3 A | 5 V, 5 A |
| ההדקים | 40 הדקים, באותו סדר | 40 הדקים, באותו סדר |
| מי שולט בהדקים | השבב הראשי | שבב נפרד: RP1 |
הספרייה gpiozero | עובדת | עובדת |
הספרייה RPi.GPIO | עובדת | לא עובדת |
sudo shutdown -h now, וממתינים עד שהנורית הירוקה מפסיקה להבהב.22.2 סביבה וירטואלית (Virtual Environment)
בבקר היינו מעתיקים את הספרייה ללוח וזהו. ב-Pi המערכת עצמה נשענת על פייתון, ואם נשנה ספרייה שהיא משתמשת בה, דבר אחר עלול להתקלקל. לכן Raspberry Pi OS אוסרת, מאז הגרסה Bookworm, להתקין ספריות בעזרת pip ישירות בפייתון של המערכת:
$ pip install smbus2 error: externally-managed-environment
sudo apt install python3-gpiozero mkdir ~/project cd ~/project python -m venv --system-site-packages env source env/bin/activate pip install smbus2 python blink.py deactivate
האפשרות --system-site-packages גורמת לסביבה לראות גם את ספריות המערכת, ובהן gpiozero שהתקנו בעזרת apt. אחרי ההפעלה מופיע (env) בתחילת השורה, ומאותו רגע pip כותב רק בתוך התיקייה env. בקובץ pi_i2c.py מפרק 6 השתמשנו ב-smbus2, וזה המקום הנכון להתקין אותה.
python app.py, ולמרות שהתקנו אתמול את smbus2, הופיעה שגיאה שהספרייה אינה קיימת. מה הסיבה?22.3 מספור הדקים ומתח (BCM Numbering)
לכל הדק ב-Pi יש שני מספרים. המספר הפיזי הוא מקומו על המחבר, מ-1 עד 40, ומספר BCM הוא מספרו בשבב. הספרייה gpiozero משתמשת במספר BCM, ולכן הפקודה LED(17) פירושה GPIO17, שהוא ההדק הפיזי 11.
pinout מדפיסה את המפה המלאה על ה-Pi.בוחרים את הערך הסטנדרטי הקרוב 330 Ω, והזרם יהיה כ-3.94 mA.
22.4 נורית ולחצן (LED, Button)
הספרייה gpiozero אינה מתחילה מההדק כמו machine, אלא מההתקן: אומרים "נורית על 17" והיא דואגת לשאר. כל פלט במסכים הבאים בא מהרצת הספרייה האמיתית על הדקים מדומים, ולכן הוא אידיאלי ואינו מתיימר להיות מדידה בלוח.
on() ו-off(), והמאפיין is_lit מדווח לנו על המצב.from gpiozero import LED
import time
led = LED(17)
for i in range(3):
led.on()
print("on, is_lit =", led.is_lit)
time.sleep(0.5)
led.off()
print("off, is_lit =", led.is_lit)
time.sleep(0.5)on, is_lit = True off, is_lit = False on, is_lit = True off, is_lit = False on, is_lit = True off, is_lit = False
עכשיו ללחצן. המחלקה Button מפעילה בעצמה את נגד המשיכה הפנימי, ואחר כך מתרגמת בשבילנו את המשמעות: is_pressed נותנת True בלחיצה, אף על פי שההדק יורד אז ל-0. אין צורך להפוך את הערך כפי שעשינו בפרק 3.
from gpiozero import LED, Button
import time
led = LED(17)
button = Button(2)
for step in range(5):
pressed = button.is_pressed
led.value = pressed
print("t=%.1f pressed=%s lit=%s" % (step * 0.5, pressed, led.is_lit))
time.sleep(0.5)t=0.0 pressed=False lit=False t=0.5 pressed=True lit=True t=1.0 pressed=True lit=True t=1.5 pressed=False lit=False t=2.0 pressed=False lit=False
RPi.GPIO, שמופיעה בדוגמאות רבות ברשת? כי היא פונה ישירות לאוגרי ההדקים בשבב הראשי, וב-Pi 5 ההדקים עברו לשבב RP1, ולכן הספרייה נעצרת בהודעת שגיאה. הספרייה gpiozero בוחרת בעצמה את השכבה המתאימה ללוח, וב-Pi 5 זו lgpio. לכן אותה תוכנית עובדת בשני הלוחות.22.5 אירועים והמתנה (Events, pause)
לולאת בדיקה עובדת, אבל לחיצה קצרה עלולה לחמוק בין שתי קריאות. החלופה היא אירוע: נותנים לספרייה פונקציה, והיא קוראת לה בעת הלחיצה.
pressed נקראת מעצמה בכל אחת מהן.from gpiozero import LED, Button
import time
led = LED(17)
button = Button(2)
count = 0
def pressed():
global count
count += 1
led.toggle()
print("press %d, lit=%s" % (count, led.is_lit))
button.when_pressed = pressed
for second in range(4):
time.sleep(1)
print("done, %d presses" % count)press 1, lit=True press 2, lit=False done, 2 presses
הרעיון דומה לפסיקה בפרק 5, אבל ההבדל גדול: הפונקציה כאן אינה פסיקה אמיתית, אלא רצה בתהליכון (thread) רקע של פייתון. לכן מותר בה מה שנמנענו ממנו בפסיקה, כמו הדפסה ויצירת רשימות. המחיר הוא שזמן התגובה פחות יציב.
לולאת for בתוכנית שלמעלה קיימת לצורך ההצגה בלבד: היא מחזיקה את התוכנית בחיים כמה שניות ואז התוכנית מסתיימת, וכך אנחנו רואים את הפלט. בפרויקט אמיתי שמים במקומה pause(), שממתינה לנצח. לכן את התוכנית הבאה לא הרצנו בבניית הספר, כי היא אינה מסתיימת:
bounce_time מטפל בקפיצות המגע, ו-pause() משאירה את התוכנית ממתינה לאירועים.from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(2, bounce_time=0.05) button.when_pressed = led.toggle pause()
button.when_pressed = pressed בלי סוגריים. עם סוגריים הפונקציה נקראת פעם אחת עכשיו, ותוצאתה None מושמת, ולכן לא קורה דבר בלחיצה.pause() מן התוכנית האחרונה. מה קורה כשמריצים אותה?22.6 בהירות, סרוו ומרחק (PWMLED, Servo, DistanceSensor)
תוכנית הבהירות רצה כאן על הדקים מדומים ששומרים רק את ערך ה-PWM, ולכן אין אור ואין פולסים. DistanceSensor, לעומת זאת, דורש מדידת זמן אמיתית, ולכן תוכנית החניה לא הורצה בבניית הספר: היא עברה הידור בלבד, ואין מתחתיה מסך.
value בין 0 ל-1, במקום duty_u16 בין 0 ל-65535. יש גם led.pulse(), שפועמת ברקע.from gpiozero import PWMLED
import time
led = PWMLED(18, frequency=1000)
for percent in (0, 25, 50, 75, 100):
led.value = percent / 100
print("duty %3d%% value %.2f" % (percent, led.value))
time.sleep(1)duty 0% value 0.00 duty 25% value 0.25 duty 50% value 0.50 duty 75% value 0.75 duty 100% value 1.00
from gpiozero import Servo, DistanceSensor
import time
servo = Servo(18)
sensor = DistanceSensor(echo=24, trigger=23, max_distance=1)
while True:
metres = sensor.distance
print("%.2f m" % metres)
if metres < 0.2:
servo.max()
else:
servo.min()
time.sleep(0.5)מחברים את הזנת הסרוו למקור 5 V נפרד, עם אדמה משותפת, ומן ההדק לוקחים את האות בלבד. Servo מייצר את הפולסים בתוכנה, ולכן הסרוו עלול לרעוד מעט, כי לינוקס עשויה לעכב את התוכנית.
אין ב-Pi ממיר אנלוגי לספרתי. כדי לקרוא פוטנציומטר או נגד תלוי אור משתמשים בממיר החיצוני ADS1115 דרך I2C, כפי שעשינו בפרק 6.
22.7 ההבדלים ממיקרו-פייתון
| מיקרו-פייתון בבקר | פייתון ב-Pi | |
|---|---|---|
| הפעלה | תוכנית אחת שמתחילה מ-main.py | תוכנית אחת מבין רבות, מתחילים אותה בעצמנו או בעזרת שירות |
| גישה להדקים | machine.Pin | gpiozero |
| מספר ההדק | מספר השבב, דרך board.py | BCM |
| נגד משיכה ללחצן | מבקשים אותו: Pin.PULL_UP | אוטומטי ב-Button |
| אירוע בלחיצה | Pin.irq, פסיקה קצרה | when_pressed, תהליכון רקע |
| קריאה אנלוגית | ADC פנימי | ממיר חיצוני בלבד |
| התקנת ספריות | העתקה או mip | pip בסביבה וירטואלית, או apt |
| דיוק התזמון | גבוה יותר, כי אין מערכת שמפריעה לתוכנית | נמוך יותר: לינוקס מחלקת את הזמן בין התוכניות |
| כיבוי | מנתקים את החשמל | מכבים קודם את המערכת |
השורה שלפני האחרונה מסבירה את חלוקת התפקידים בפרק הבא: התזמון המדויק והקרבה לחיישן שייכים לבקר, והאחסון, הרשת ודפי האינטרנט שייכים ל-Pi.
22.8 הרצה אוטומטית (systemd)
בבקר main.py מתחיל מעצמו כשמזינים חשמל. ב-Pi שום דבר מזה לא קורה: אם נסגור חלון SSH, התוכנית תיעצר, ואחרי אתחול מחדש היא לא תרוץ. הפתרון הוא שירות: קובץ שמתאר למנהל המערכת systemd איך להריץ את התוכנית שלנו. נשמור את תוכנית pause() מסעיף 22.5 בשם button_led.py ונכתוב:
[Unit] Description=Button and LED After=multi-user.target [Service] User=student WorkingDirectory=/home/student/project ExecStart=/home/student/project/env/bin/python -u button_led.py Restart=on-failure [Install] WantedBy=multi-user.target
sudo nano /etc/systemd/system/button-led.service sudo systemctl daemon-reload sudo systemctl enable --now button-led systemctl status button-led journalctl -u button-led -f sudo systemctl stop button-led
ExecStart: מצביע ישירות על הפייתון שבתוך הסביבה, ולכן אין צורך ב-activate.User: שם המשתמש שבחרנו בהתאמה האישית. השםpiכבר אינו מובטח.Restart=on-failure: מפעיל מחדש אם התוכנית קורסת.-u: מונע השהיה שלprint, כדי שנראה את הפלט מיד ב-journalctl.
עיקרי הפרק:
- ה-Pi הוא מחשב עם לינוקס: מכבים אותו בפקודה, ולא מנתקים את החשמל שלו.
- כל פרויקט בסביבה וירטואלית, והאפשרות
--system-site-packagesנותנת לו לראות אתgpiozero. gpiozeroמשתמשת במספור BCM, ועובדת ב-Pi 4 וב-Pi 5. הספרייהRPi.GPIOאינה עובדת ב-Pi 5.- כל הדק עובד על 3.3 V בלבד, ואין ב-Pi ממיר אנלוגי.
- אירועים רצים בתהליכון רקע, ו-
pause()משאירה את התוכנית בחיים. - שירות systemd הוא המקבילה של
main.pyב-Pi.
| פקודה | מה היא עושה |
|---|---|
| LED(17) | נורית על GPIO17: on, off, toggle, is_lit, value. |
| Button(2, bounce_time=0.05) | לחצן עם נגד משיכה פנימי, וטיפול בקפיצות המגע. |
| button.is_pressed | True בלחיצה. |
| button.when_pressed = f | f נקראת בלחיצה. המקבילה שלה היא when_released. |
| from signal import pause | pause() ממתינה לאירועים לנצח. |
| PWMLED(18).value = 0.5 | בהירות בין 0 ל-1. |
| Servo(18) | סרוו: min, mid, max, value. |
| DistanceSensor(echo=24, trigger=23) | distance במטרים. על ECHO יש מחלק מתח. |
| python -m venv --system-site-packages env | סביבה וירטואלית שרואה את ספריות המערכת. |
| systemctl enable --now NAME | הפעלת השירות עכשיו ובכל אתחול. |
| journalctl -u NAME -f | מעקב אחר הפלט של השירות. |
מילון מונחים:
| עברית | English | ערבית |
|---|---|---|
| מערכת הפעלה | Operating System | نظام التشغيل |
| שורת פקודה | Command Line | سطر الأوامر |
| סביבה וירטואלית | Virtual Environment | البيئة الافتراضية |
| מספור פיזי | Physical Numbering | الترقيم الفيزيائي |
| אירוע | Event | الحدث |
| תהליכון | Thread | الخيط |
| שירות | Service | الخدمة |
source env/bin/activate ומריצים שוב.LED(18).journalctl -u button-led מראה את הסיבה. את systemctl enable שכחנו.שאלות לתרגול:
--system-site-packages? ולמה אנחנו זקוקים לה כאן?gpiozero שהותקנה בעזרת apt בלי להתקין אותה שוב.from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(2) button.when_pressed = led.on button.when_released = led.off pause()
import RPi.GPIO as GPIO ועובדת ב-Pi 4, אבל נכשלת ב-Pi 5. למה? ואיזה פתרון אנחנו מעדיפים?RPi.GPIO אינה מגיעה אליו. כותבים אותה מחדש בעזרת gpiozero.