פייתון במידה שהבקר צריך
זה אינו ספר על שפת פייתון. כאן לוקחים רק מה שהמשך הספר צריך, וכל דוגמה לקוחה מעולם המתח והחיישן. בסוף נדע איפה מיקרו-פייתון שונה מפייתון של המחשב, כי טעויות רבות באות משם.
- נשתמש במשתנים, בטיפוסים ובחשבון, ונדפיס מספרים בתבנית ברורה.
- נקרא טקסט מהמשתמש, נמיר אותו למספר, ונטפל בשגיאה אם אינו מספר.
- נבנה החלטה בתנאי וחזרה בלולאה, ונאסוף את הקריאות ברשימה.
- נארגן את הקוד בפונקציות, במחלקות ובספריות, ונתחשב בזיכרון ובדיוק העשרוני.
2.1 משתנים וטיפוסים (Variables and Types)
משתנה הוא שם שקושרים לערך. לא מצהירים על הטיפוס מראש; המפרש קובע אותו מהערך עצמו. הטיפוסים שאנחנו צריכים הם ארבעה:
| הטיפוס | דוגמה | משמש ל |
|---|---|---|
int מספר שלם | 20000 | קריאת הממיר, מונים, מספרי הדקים |
float מספר ממשי | 3.3 | מתח וטמפרטורה |
str מחרוזת | "pot" | שמות והודעות |
bool ערך בוליאני | True | תוצאה של כל השוואה |
type מגלה את הטיפוס של כל אחד.raw = 20000 vref = 3.3 name = "pot" active = raw > 1000 print(name, raw, active) print(type(vref), type(name))
pot 20000 True <class 'float'> <class 'str'>
2.2 חשבון (Arithmetic)
| הפעולה | משמעותה | דוגמה מעולמנו |
|---|---|---|
+ - * | חיבור, חיסור וכפל | כפל הקריאה במתח הייחוס |
/ | חילוק, והתוצאה תמיד float | המתח מהקריאה |
// | חילוק שלם | אחוזים בלי שברים |
% | שארית החילוק | החלפת מצב כל שני צעדים |
** | העלאה בחזקה | מספר המדרגות 2n |
raw = 20000
volts = raw * 3.3 / 65535
print("volts = %.3f" % volts)
print("7 / 2 = %.1f" % (7 / 2))
print("7 // 2 =", 7 // 2)
print("7 % 2 =", 7 % 2)
print("2 ** 12 =", 2 ** 12)volts = 1.007 7 / 2 = 3.5 7 // 2 = 3 7 % 2 = 1 2 ** 12 = 4096
raw * 3.3 / 65535 מבצע קודם את הכפל. ואילו raw // 65535 * 3.3 נותן אפס לכל קריאה הקטנה מ-65535, כי החילוק השלם משמיט את השבר.17 // 5, של 17 % 5 ושל 17 / 5?2.3 הדפסה ותבניות (Printing and Formatting)
קריאה גולמית אינה אומרת דבר למי שמתבונן במסך, ולכן מדפיסים בתבנית. יש לנו שתי דרכים, ושתיהן עובדות במיקרו-פייתון: תבנית בסימן % ומחרוזת מעוצבת f"...".
%.2f הוא מספר ממשי בשתי ספרות אחרי הנקודה, ו-%d הוא מספר שלם.volts = 1.0071
temp = 24.5
raw = 20000
print("V = %.2f" % volts)
print("T = %.1f C, raw = %d" % (temp, raw))
print(f"V = {volts:.2f}")
print(f"T = {temp:.1f} C, raw = {raw}")V = 1.01 T = 24.5 C, raw = 20000 V = 1.01 T = 24.5 C, raw = 20000
2.4 קלט והמרה (input, int, float, split)
הפקודה input מדפיסה שאלה וממתינה עד שהמשתמש מקליד שורה ולוחץ Enter בחלון Shell. והחשוב מכול: היא תמיד מחזירה מחרוזת, גם אם הקלדנו מספר. לכן ממירים ב-int או ב-float לפני כל חישוב. ואם הקלדנו שני מספרים בשורה אחת, הפקודה split חותכת את המחרוזת לרשימת חלקים. הקוד הבא לא הורץ בבניית הספר, כי אין לו מקלדת, ולכן לא מציגים לו מסך:
text = input("Voltage in volts: ")
volts = float(text)
print("raw =", int(volts * 65535 / 3.3))
line = input("R1 R2 in ohms: ")
parts = line.split()
r1 = float(parts[0])
r2 = float(parts[1])
print("Vout = %.2f V" % (3.3 * r2 / (r1 + r2)))בלוח מופיעה כל שאלה וממתינה, מקלידים את התשובה, והתוכנית מדפיסה את התוצאה. ההמרה עצמה אינה צריכה מקלדת, ולכן מנסים אותה באמת על מחרוזת קבועה:
input.line = "10000 4700"
parts = line.split()
print(parts)
r1 = float(parts[0])
r2 = float(parts[1])
print("Vout = %.2f V" % (3.3 * r2 / (r1 + r2)))['10000', '4700'] Vout = 1.06 V
ומה קורה אם המחרוזת אינה ניתנת להמרה? זו השגיאה הראשונה שאנחנו רואים, והיא אמיתית:
int אינו מקבל נקודה עשרונית.text = "3.3"
print("%.1f" % float(text))
n = int(text)
print(n)3.3 ValueError: invalid syntax for integer with base 10: '3.3'
השורה האחרונה נקראת חריגה: התוכנית נעצרה כאן, ושם השגיאה בראש השורה. תמיד קוראים את השורה הזאת לפני שמתקנים משהו. כדי שתוכנית לא תיעצר בגלל הקלדה שגויה, מכניסים את ההמרה לתוך try:
text = "abc"
try:
volts = float(text)
print("%.2f" % volts)
except ValueError:
print("not a number:", text)not a number: abc
12 ב-input. מהו הטיפוס של הערך שקיבלנו? ומה תוצאת n * 2 לפני ההמרה?2.5 תנאים (if / elif / else)
המיקרו-בקר קיים כדי להחליט: האם המתח גבוה? האם הדלת פתוחה? תנאי מריץ גוש שורות אם ההשוואה נכונה, וגוש השורות מזוהה בהזחה פנימה. משווים ב-==, ב-!=, ב-< וב->, וקושרים תנאים ב-and, ב-or וב-not.
volts = 2.7
if volts > 3.0:
print("HIGH")
elif volts > 1.0:
print("MID")
else:
print("LOW")
print("in range:", 1.0 <= volts <= 3.0)
print("not low:", not volts < 1.0)MID in range: True not low: True
= נותן ערך, והסימן == משווה. וההזחה היא חלק מהשפה: שורה שהוזחה בטעות משנה את משמעות התוכנית, או עוצרת אותה. תמיד משתמשים ב-4 רווחים.2.6 לולאות (for / while)
לולאה חוזרת על גוש שורות. הלולאה for עוברת על אוסף ידוע, ו-range(3) נותן 0, 1 ו-2, בלי 3. ואילו while חוזרת כל עוד התנאי נכון, ומתאימה למקרים שבהם איננו יודעים מראש כמה פעמים. כאן המתח יורד ב-20% בכל צעד, ושואלים: כמה צעדים עד שהוא קטן מוולט אחד?
for n in range(3):
raw = n * 20000
print(n, raw, "%.2f V" % (raw * 3.3 / 65535))
volts = 3.3
steps = 0
while volts > 1.0:
volts = volts * 0.8
steps += 1
print("steps:", steps, "volts: %.2f" % volts)0 0 0.00 V 1 20000 1.01 V 2 40000 2.01 V steps: 6 volts: 0.87
ובתוך כל לולאה, הפקודה continue מדלגת על הסיבוב הנוכחי, ו-break יוצאת מכל הלולאה.
הלולאה שאינה נגמרת:
כמעט כל תוכנית של בקר בנויה על לולאת while True שאינה נעצרת: קוראים, מחשבים, מחליטים, מבצעים, וחוזרים.
analog_in נסביר בפרק 6, ואת ההדק בפרק 3.import time
from machine import Pin
import board
pot = board.analog_in(board.POT)
led = Pin(board.LED, Pin.OUT)
while True:
volts = pot.read_u16() * 3.3 / 65535
if volts > 2.0:
led.on()
state = "ON"
else:
led.off()
state = "OFF"
print("%.2f V LED %s" % (volts, state))
time.sleep(1)0.50 V LED OFF 1.50 V LED OFF 2.50 V LED ON 3.00 V LED ON ...
הנקודות בסוף המסך אומרות שהמדמה עצר את התוכנית. הלוח האמיתי ממשיך עד שנלחץ Ctrl-C.
2.7 רשימות (Lists)
רשימה אוספת קריאות רבות תחת שם אחד, ולכל איבר בה יש אינדקס שמתחיל באפס. מהסוף סופרים בשלילי: temps[-1] הוא האיבר האחרון.
temps = [21.5, 22.0, 23.5, 22.5]
print("first %.1f, last %.1f" % (temps[0], temps[-1]))
temps.append(24.0)
print("count:", len(temps))
print("max %.1f, min %.1f" % (max(temps), min(temps)))
print("avg %.2f" % (sum(temps) / len(temps)))
for t in temps[1:3]:
print("%.1f" % t)first 21.5, last 22.5 count: 5 max 24.0, min 21.5 avg 22.70 22.0 23.5
IndexError. ורשימה שגדלה בלי גבול ממלאת בהכרח את הזיכרון הקטן, ולכן מגבילים את אורכה, כמו ב-2.10.2.8 פונקציות (Functions)
פונקציה נותנת שם לקבוצת שורות, וכך כותבים אותה פעם אחת ומשתמשים בה הרבה. היא מקבלת פרמטרים, ומחזירה תוצאה בפקודה return. לפרמטר אפשר לתת ערך ברירת מחדל, ופונקציה יכולה להחזיר יותר מערך אחד.
vref יש ערך ברירת מחדל, ו-limits מחזירה שני ערכים.def to_volts(raw, vref=3.3):
return raw * vref / 65535
def limits(values):
return min(values), max(values)
print("%.2f" % to_volts(20000))
print("%.2f" % to_volts(20000, 5.0))
low, high = limits([1.2, 0.9, 1.7])
print("low %.1f, high %.1f" % (low, high))1.01 1.53 low 0.9, high 1.7
משתנה שנותנים לו ערך בתוך פונקציה נשאר בתוכה. ואם רוצים לשנות משתנה מבחוץ, כותבים בתחילתה global count. נצטרך זאת בפסיקות בפרק 5.
2.9 מילונים בקצרה (Dictionaries)
מילון קושר שם לערך, וניגשים לערך לפי השם ולא לפי המיקום. פוגשים אותו בהגדרות ובנתוני JSON בפרק 12.
get אינו נעצר כשהמפתח חסר, אלא מחזיר ערך חלופי.limit = {"name": "battery", "low": 1.0, "high": 3.0}
print(limit["name"], "%.1f" % limit["high"])
limit["unit"] = "V"
print(limit.get("unit"), limit.get("max", "missing"))
print(sorted(limit))battery 3.0 V missing ['high', 'low', 'name', 'unit']
sorted כמו בתוכנית.2.10 מחלקה קטנה (A Small Class)
מחלקה מאגדת נתונים עם הפונקציות שפועלות עליהם, ואובייקט הוא עותק שלה עם הנתונים שלו. הפונקציה __init__ פועלת בעת יצירת כל אובייקט, ו-self הוא האובייקט עצמו. כך בדיוק אנחנו עושים כשכותבים Pin(2): יוצרים אובייקט ממחלקה מוכנה. הדוגמה שלנו היא ממוצע נע: מוסיפים את החדש וזורקים את הישן ביותר, כך שהרשימה אינה מתארכת לעולם, וההשפעה של קריאה חריגה כמו 400 קטנה. לכל חיישן אובייקט ורשימה משלו.
class Average:
def __init__(self, size):
self.size = size
self.values = []
def add(self, x):
self.values.append(x)
if len(self.values) > self.size:
self.values.pop(0)
return sum(self.values) / len(self.values)
pot = Average(3)
ldr = Average(2)
for raw in [100, 110, 90, 400, 105]:
print(raw, "%.1f" % pot.add(raw))
print("ldr %.1f" % ldr.add(500))100 100.0 110 105.0 90 100.0 400 200.0 105 198.3 ldr 500.0
2.11 ייבוא (import)
ספרייה היא קובץ שיש בו פונקציות מוכנות. מייבאים אותה כולה, או לוקחים ממנה שם אחד. וכל קובץ שכותבים ושומרים בתיקייה lib נעשה ספרייה. זו ספרייה קטנה שכתבנו:
# convert.py - small unit conversions, the same on every board.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
VREF = 3.3
def to_volts(raw, vref=VREF):
return raw * vref / 65535
def to_percent(raw):
return raw * 100 // 65535import math
from math import sqrt
import convert
print("%.3f %.3f" % (math.pi, sqrt(2)))
print("%.2f V" % convert.to_volts(20000))
print(convert.to_percent(32768), "percent")3.142 1.414 1.01 V 50 percent
time.py או math.py. המפרש לוקח קודם את הקובץ שלנו, וכל התוכניות שצריכות את המקורי נעצרות.2.12 במה היא שונה מפייתון של המחשב? (Differences from a PC)
הזיכרון מועט:
כל מה שיוצרים לוקח מהזיכרון, והפקודה gc.mem_free() נותנת את מה שנשאר. כאן לא מדפיסים מספר, כי המספר של המדמה אינו אומר דבר על הלוח שלכם; נסו זאת בו.
import gc
gc.collect()
before = gc.mem_free()
data = [0] * 1000
after = gc.mem_free()
print("the list took memory:", after < before)the list took memory: True
הדיוק העשרוני:
ב-ESP32 וב-Pico W הקושחה הרגילה שומרת מספר ממשי ב-32 סיביות, בדיוק של כ-7 ספרות. ואילו במדמה ובפייתון של המחשב זה 64 סיביות. לכן מדפיסים בתבנית, ואין משווים שני מספרים ממשיים ב-==:
total = 0.0
for i in range(10):
total += 0.1
print("exactly 1.0:", total == 1.0)
print("close to 1.0:", abs(total - 1.0) < 0.001)exactly 1.0: False close to 1.0: True
הקבוע const:
ערך שלם שאינו משתנה מצהירים עליו כקבוע בפקודה const מהספרייה micropython, והמפרש מציב את ערכו מראש. והדוגמה הזאת מחשבת גם היא במספרים שלמים בלבד, במיליוולט:
from micropython import const
_VREF_MV = const(3300)
_FULL = const(65535)
def to_mv(raw):
return raw * _VREF_MV // _FULL
print(to_mv(20000), "mV")1007 mV
לא כל הספריות קיימות:
ספריות הענק של פייתון אינן עובדות כאן, וייבוא שלהן עוצר את התוכנית בשגיאה ImportError. מה שזמין בלוח שלנו מגלים בפקודה help("modules") בשורה האינטראקטיבית, ואת חלק מהחסר אפשר להתקין, כמו בפרק 11. וההפך נכון גם כן: time.sleep_ms ו-machine אינם קיימים בפייתון של המחשב. מי שרוצה להעמיק בשפה עצמה יכול לפנות לספר הפייתון באתר fahed-lab.com כשיהיה זמין.
עיקרי הפרק:
inputמחזירה תמיד מחרוזת, וממירים אותה לפני החישוב.- השורה האחרונה של השגיאה מגלה את שמה: קוראים אותה לפני שמתקנים.
- כמעט כל תוכנית של בקר היא לולאה: קוראים, מחשבים, מחליטים, מבצעים.
- לרשימה יש תמיד אורך מוגבל, כי הזיכרון קטן.
- מספר ממשי מדפיסים בתבנית ואין משווים אותו ב-
==. והזמן נשמר כמספר שלם.
פקודות הפרק:
| פקודה | מה היא עושה |
|---|---|
| int() float() str() | המרה בין טיפוסים. |
| "%.2f" % x f"{x:.2f}" | הדפסה בשתי ספרות אחרי הנקודה. |
| input() line.split() | קריאת שורה וחיתוכה. |
| try / except ValueError | טיפול בשגיאה בלי עצירה. |
| a.append(x) a.pop(0) len(a) | הוספה, מחיקת הראשון, האורך. |
| gc.mem_free() const() | הזיכרון הפנוי; הצהרה על קבוע. |
מילון מונחים:
| עברית | English | ערבית |
|---|---|---|
| משתנה | Variable | المتغيّر |
| מספר שלם / ממשי | Integer / Float | عدد صحيح / عدد عشري |
| מחרוזת | String | النص |
| לולאה | Loop | الحلقة |
| הזחה | Indentation | الإزاحة |
| רשימה | List | القائمة |
| מחלקה | Class | الصنف |
| אובייקט | Object | الكائن |
| חריגה | Exception | الاستثناء |
| ממוצע נע | Moving Average | المتوسّط المتحرّك |
3, 2 ו-3.4."1212", כי כפל מחרוזת משכפל אותה.שאלות לתרגול:
3.3, "3.3", 33, 3 > 1?float, str, int, bool.range(2, 5)? ומהו האינדקס השלילי של האיבר הראשון ברשימה של 4 איברים?-4.def percent(raw):
return raw * 100 // 65535
for raw in (0, 32768, 65535):
print(raw, percent(raw))"12,30,18" הגיעה מחיישן בקו טורי. חברו את שלושת המספרים.line = "12,30,18"
total = 0
for part in line.split(","):
total += int(part)
print("total:", total)float, ואחרי כמה שעות הוא מפסיק לעלות בקביעות. למה? ומה התרופה?time.ticks_ms().