תכנות מיקרו-בקרים בשפת MicroPython · פרק 4 — זמן וטיימרים⌂📚🏠
פרק 4

זמן וטיימרים

Time and Timers
2 שעות לימוד

כל התוכניות עד עכשיו ממתינות בעזרת הפקודה sleep, ובזמן ההמתנה התוכנית עיוורת: אינה רואה לחצן ואינה משרתת נורית אחרת. בפרק הזה נמדוד את הזמן במקום להמתין לו, ונשאיר לטיימר את מה שצריך לקרות בזמנו.

בפרק הזה:
  • נמדוד זמן בעזרת הפקודה ticks_diff, ונפעיל שתי משימות בשני קצבים שונים בלי חסימה.
  • נשתמש בטיימר מחזורי ובטיימר חד-פעמי, ונדע מה ההבדל בין שני הלוחות.
  • נמדוד רוחב פולס ותדר, ונחשב מרחק בעזרת החיישן HC-SR04.

4.1 השהיות ושעון הבקר (Delays and Ticks)

להמתנה יש time.sleep(s) בשניות, time.sleep_ms(ms) ו-time.sleep_us(us), כי לפי התיעוד בחלק מהלוחות sleep אינה מקבלת שבר. לקריאת השעון יש time.ticks_ms() ו-time.ticks_us(), שני מונים עולים שנקודת ההתחלה שלהם אינה מוגדרת, ו-time.ticks_diff(b, a) נותן את הזמן מ-a עד b.

ההמתנה היא רף תחתון: התיעוד מציין ש-sleep_ms עלולה להתארך, ולכן כדי לדעת כמה זמן עבר קוראים את השעון. המונה מוגבל, ולכן כשהוא מגיע לערך הגבוה ביותר הוא גולש וחוזר לאפס. אורך המחזור שלו הוא חזקה של 2 לפי התיעוד. בסימולטור הוא 230, ולכן מונה אלפיות השנייה גולש בערך כל 12.4 ימים:

שעון הסימולטור מתחיל מאפס: הקריאה הראשונה לפני הגלישה, והשנייה אחריה.
import time

t1 = time.ticks_add(time.ticks_ms(), -100)
t2 = time.ticks_add(t1, 250)
print("t1 =", t1)
print("t2 =", t2)
print("ticks_diff =", time.ticks_diff(t2, t1))
ESP32 + Pico W · MicroPython 1.29 · simulated board
t1 = 1073741724
t2 = 150
ticks_diff = 250
טעות נפוצה: חיסור ישיר כאן נותן -1073741574. ticks_diff נותן 250, בתנאי ששתי הנקודות אינן מרוחקות יותר ממחצית המחזור. תוכנית שמחסרת ישירות מצליחה ימים ואז טועה.

4.2 למה sleep חוסמת? שתי משימות יחד

אנו רוצים שתי נוריות: הראשונה משתנה כל 500 ms, והשנייה כל 300 ms. אילו כתבנו sleep_ms(500) ואחריה sleep_ms(300), כל אחת מהן הייתה משתנה כל 800 ms: כל פקודת המתנה חוסמת את כל השאר. והפתרון: מסתובבים בלולאה מהר, ובכל סיבוב שואלים אם הגיע מועדה של הנורית הזאת.

הזמן באלפיות שנייה בכל שינוי. כל נורית בקצב שלה.
from machine import Pin
import time
import board

led_a = Pin(board.LED, Pin.OUT)
led_b = Pin(board.LED_EXT, Pin.OUT)
start = time.ticks_ms()
last_a = start
last_b = start

while True:
    now = time.ticks_ms()
    if time.ticks_diff(now, last_a) >= 500:
        last_a = time.ticks_add(last_a, 500)
        led_a.value(not led_a.value())
        print(time.ticks_diff(now, start), "A")
    if time.ticks_diff(now, last_b) >= 300:
        last_b = time.ticks_add(last_b, 300)
        led_b.value(not led_b.value())
        print(time.ticks_diff(now, start), "B")
    time.sleep_ms(1)
ESP32 + Pico W · MicroPython 1.29 · simulated board
300 B
500 A
600 B
900 B
...
הערה: את המועד הבא מחשבים מ-last_a בעזרת ticks_add, ולא מ-now, וכך האיחור אינו מצטבר.
▶ניסוי חי — מעבדת הבהוב בלי חסימההנורית מהבהבת בקצב קבוע וכל לחיצה נספרת מיד. אחר כך נשנה את הקוד ל-sleep_ms(2000) ונראה מה הולך לאיבוד.
⚠
המעבדה אינה זמינה כעת. החומר בפרק שלם גם בלעדיה: ממשיכים לקרוא, וחוזרים לניסוי מאוחר יותר, או פותחים אותו ישירות מן הקישור שמתחת למסגרת.
https://fahed-lab.com/books/micropython/lab/mpy_lab?prog=ch4_no_block
פותחים את המעבדה בדף נפרד ↗ fahed-lab.com/books/micropython/lab/mpy_lab?prog=ch4_no_block

4.3 טיימר (Timer)

טיימר: קורא לפונקציה שנתנו לו, במועד שקבענו, בלי שהתוכנית הראשית עוצרת. הוא מחזורי אם הוא חוזר על עצמו, או חד-פעמי.

כאן שני הלוחות שונים. ב-ESP32 יש ארבעה טיימרי חומרה במספרים 0–3, וכותבים Timer(0). ב-Pico W יש טיימרי תוכנה שאינם צריכים מספר, והוא מקבל Timer(-1), ש-ESP32 אינו מקבל. את ההבדל הזה מחביאים ב-board.py: הפונקציה board.timer() מחזירה את הטיימר הנכון, והתוכנית אינה מזכירה מספר.

התוכנית הראשית ממתינה והטיימר עובד. אחר כך משתמשים בו שוב כחד-פעמי, והוא פועל פעם אחת ואינו חוזר.
from machine import Pin, Timer
import time
import board

led = Pin(board.LED, Pin.OUT)
tim = board.timer()

def tick(t):
    led.value(not led.value())
    print("tick", led.value())

tim.init(period=500, mode=Timer.PERIODIC, callback=tick)
time.sleep_ms(1200)
tim.deinit()
print("main: stopped")

tim.init(period=1000, mode=Timer.ONE_SHOT, callback=tick)
time.sleep_ms(1500)
ESP32 + Pico W · MicroPython 1.29 · simulated board
tick 1
tick 0
main: stopped
tick 1
אזהרה: ב-ESP32 הפונקציה board.timer() מחזירה בכל פעם את טיימר 0, ולכן אין להפעיל בו שתי משימות יחד. הפונקציה שהטיימר קורא לה קוטעת את התוכנית, ולכן שומרים אותה קצרה. הסיבות בפרק 5.

4.4 מדידת רוחב פולס ותדר

רוחב פולס הוא הזמן שההדק נשאר ברמה מסוימת. הפקודה המוכנה למדידתו היא time_pulse_us(pin, level, timeout_us): היא ממתינה לתחילת הפולס ואז מחזירה את רוחבו במיקרו-שניות. היא מחזירה -2 אם הזמן שהוקצב נגמר לפני שהפולס התחיל, ו--1 אם נגמר באמצע המדידה. התוכנית עומדת כל הזמן הזה, ולכן בוחרים את הזמן המוקצב בקפידה. הדרך הפשוטה ביותר למדוד את התדר היא לספור עליות בשנייה שלמה:

על ההדק אות ריבועי של 50 Hz, בסימולטור.
from machine import Pin
import time
import board

sig = Pin(board.ECHO, Pin.IN)
count = 0
last = sig.value()
start = time.ticks_ms()

while time.ticks_diff(time.ticks_ms(), start) < 1000:
    level = sig.value()
    if last == 0 and level == 1:
        count += 1
    last = level
    time.sleep_ms(1)

print("f =", count, "Hz")
ESP32 + Pico W · MicroPython 1.29 · simulated board
f = 50 Hz
אזהרה: קוראים את ההדק לכל היותר פעם באלפית שנייה, ולכן זה מתאים רק לתדרים נמוכים: מתחת ל-500 הרץ במקרה הטוב. לתדרים גבוהים יותר סופרים בעזרת פסיקות, בפרק 5.
▶ניסוי חי — מעבדת זמן התגובהתוכנית שמדליקה את הנורית וסופרת בעזרת ticks_diff את הזמן עד שאנו לוחצים על הלחצן בעכבר.
⚠
המעבדה אינה זמינה כעת. החומר בפרק שלם גם בלעדיה: ממשיכים לקרוא, וחוזרים לניסוי מאוחר יותר, או פותחים אותו ישירות מן הקישור שמתחת למסגרת.
https://fahed-lab.com/books/micropython/lab/mpy_lab?prog=ch4_reaction
פותחים את המעבדה בדף נפרד ↗ fahed-lab.com/books/micropython/lab/mpy_lab?prog=ch4_reaction

4.5 פרויקט: מדידת מרחק (HC-SR04)

החיישן HC-SR04 שולח פולס קול שאינו נשמע, ואחר כך נותן פולס על ECHO שרוחבו הוא זמן ההלוך והשוב של הקול. מפעילים אותו בפולס קצר על TRIG, באורך 10 µs לפי גיליון הנתונים של היחידה הנפוצה.

TRIGECHO10 µst
פולס ההפעלה, ואחריו פולס ההד ורוחבו t. הסרטוט אינו בקנה מידה.
המרחק מזמן ההד
d = c · t2
סמלפירושיחידה
cמהירות הקול: כ-0.0343 באוויר בכ-20 מעלות צלזיוס cm/µs
tרוחב פולס ההד µs

לדוגמה: פולס הד של 1200 µs פירושו d = 0.0343 · 12002 = 20.58 cm.

היחידה הנפוצה פועלת ב-5 V, ולכן פולס ECHO גבוה ממה שההדק עומד בו. לכן מציבים מחלק מתח, והמתח על ההדק הוא V = 5 · 20001000 + 2000 = 3.33 V.

VCCTRIGECHOGNDHC-SR045 VTRIGECHOGND1 kΩ2 kΩ
ב-ESP32: TRIG הוא 27 ו-ECHO הוא 32. וב-Pico W: GP2 ו-GP3. והדק ה-5 V הוא VIN או VBUS לפי הלוח.

את כל זה כותבים פעם אחת בספרייה. פסק הזמן 30000 µs שווה לכ-514 cm, יותר מן הטווח של היחידה הנפוצה, שהוא 4 m לפי גיליון הנתונים שלה.

הקובץ hcsr04.py, בכל הלוחות
# hcsr04.py - HC-SR04 ultrasonic distance sensor, the same on every board.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
from machine import Pin, time_pulse_us
import time

SOUND_CM_PER_US = 0.0343


class HCSR04:
    def __init__(self, trig, echo, timeout_us=30000):
        self.trig = Pin(trig, Pin.OUT, value=0)
        self.echo = Pin(echo, Pin.IN)
        self.timeout_us = timeout_us

    def distance_cm(self):
        self.trig.value(1)
        time.sleep_us(10)
        self.trig.value(0)
        us = time_pulse_us(self.echo, 1, self.timeout_us)
        if us < 0:
            return None
        return us * SOUND_CM_PER_US / 2
בסימולטור: עצם קרוב, אחר כך אין הד, ואחר כך עצם רחוק.
import time
import board
from hcsr04 import HCSR04

sensor = HCSR04(board.TRIG, board.ECHO)

while True:
    d = sensor.distance_cm()
    if d is None:
        print("no echo")
    else:
        print("%.1f cm" % d)
    time.sleep(1)
ESP32 + Pico W · MicroPython 1.29 · simulated board
20.0 cm
no echo
100.0 cm
...

עיקרי הפרק:

סיכום הכללים בפרק:

הכללהנוסחה
המרחק מזמן ההדd = c · t2

פקודות הפרק:

פקודהמה היא עושה
time.ticks_add(t, d)מועד שמרחק d אחרי t.
tim.init(period=, mode=, callback=)הפעלת הטיימר, ו-tim.deinit() עוצרת אותו.
time_pulse_us(pin, level, timeout_us)רוחב פולס במיקרו-שניות.

מילון מונחים:

עבריתEnglishערבית
טיימרTimerالمؤقّت
גלישהWrap-aroundالالتفاف
רוחב פולסPulse Widthعرض النبضة
תדרFrequencyالتردد

שאלות לתרגול:

רמה ראשונה — בסיס
1.
רוחב פולס ההד 2000 µs. מהו המרחק?
תשובות: d = 34.3 cm.
2.
למה כותבים board.timer() ולא Timer(0)?
תשובות: כי Timer(0) אינו מתאים ל-Pico W.
רמה שנייה — יישום
3.
כתבו תוכנית שמהבהבת בלי חסימה וסופרת בו בזמן לחיצות על לחצן.
תשובות: מחברים את לולאת ההבהוב עם המונה של פרק 3. הפתרון מוכן במעבדת ההבהוב בלי חסימה, ועם לחיצה אחת הוא הדפיס בבניית הספר: presses: 1.
רמה שלישית — העמקה
4.
אנו רוצים למדוד אות של 2 kHz בתוכנית שסופרת עליות פעם באלפית שנייה. האם זה יצליח?
תשובות: לא. בכל אלפית שנייה יש שני מחזורים שלמים, ולכן נפספס את רוב העליות. התוכנית מתאימה לפחות מ-500 הרץ.
5.
מהירות הקול בקירוב היא c = 331.3 + 0.606 T ביחידות m/s. פולס הד של 6000 µs ב-35 מעלות צלזיוס: מהי השגיאה אם נשתמש ב-0.0343 cm/µs?
תשובות: c = 352.5 m/s, ולכן המרחק 105.8 cm. ובערך הקבוע 102.9 cm, כלומר שגיאה של כ-2.9 cm.
נוסחאון הספר: כל נוסחאות הפרק הזה מרוכזות, עם סרטוטיהן וטבלאות הסמלים שלהן, בפרק 25 — נוסחאון הספר.