כתיבת מטפלי פסיקה

על חומרה מתאימה, MicroPython מציעה את היכולת לכתוב מטפלי פסיקה ב-Python. מטפלי פסיקה - הידועים גם כשגרות שירות פסיקה (ISR’s) - מוגדרים כפונקציות callback. הם מבוצעים בתגובה לאירוע כגון הפעלת טיימר או שינוי מתח על פין. אירועים כאלה יכולים להתרחש בכל נקודה בביצוע קוד התוכנית. לכך השלכות משמעותיות, חלקן ייחודיות לשפת MicroPython. אחרות משותפות לכל המערכות המסוגלות להגיב לאירועי זמן אמת. מסמך זה מכסה תחילה את הנושאים הייחודיים לשפה, ולאחר מכן מבוא קצר לתכנות זמן אמת עבור מי שחדשים בו.

מבוא זה משתמש במונחים מעורפלים כמו ”איטי“ או ”מהר ככל האפשר“. זה נעשה במכוון, מכיוון שהמהירויות תלויות ביישום. משכי זמן מקובלים עבור ISR תלויים בקצב שבו מתרחשות הפסיקות, באופי התוכנית הראשית ובנוכחותם של אירועים מקבילים אחרים.

סוגיות MicroPython

חוצץ חריגות החירום

אם מתרחשת שגיאה ב-ISR, MicroPython אינה מסוגלת להפיק דוח שגיאה אלא אם נוצר חוצץ מיוחד למטרה זו. ניפוי הבאגים מפושט אם הקוד הבא נכלל בכל תוכנית המשתמשת בפסיקות.

import micropython

micropython.alloc_emergency_exception_buf(100)

חוצץ חריגות החירום יכול להחזיק רק עקבת מחסנית (stack trace) אחת של חריגה. משמעות הדבר היא שאם חריגה שנייה נזרקת במהלך הטיפול בחריגה בעוד הערימה (heap) נעולה, עקבת המחסנית של החריגה השנייה תחליף את המקורית - גם אם החריגה השנייה מטופלת כראוי. הדבר עלול להוביל להודעות חריגה מבלבלות אם החוצץ מודפס מאוחר יותר.

פשטות

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

תקשורת בין ISR לתוכנית הראשית

בדרך כלל ISR צריכה לתקשר עם התוכנית הראשית. האמצעי הפשוט ביותר לעשות זאת הוא באמצעות אובייקט נתונים משותף אחד או יותר, שמוצהר כגלובלי או משותף באמצעות מחלקה (ראו להלן). קיימות הגבלות וסכנות שונות סביב פעולה זו, המכוסות ביתר פירוט להלן. מספרים שלמים, אובייקטי bytes ו-bytearray נמצאים בשימוש נפוץ למטרה זו לצד מערכים (ממודול array) שיכולים לאחסן סוגי נתונים שונים.

השימוש במתודות אובייקט כ-callbacks

MicroPython תומכת בטכניקה רבת עוצמה זו המאפשרת ל-ISR לשתף משתני מופע עם הקוד הבסיסי. היא גם מאפשרת למחלקה המממשת מנהל התקן לתמוך במספר מופעי התקן. הדוגמה הבאה גורמת לשני נורות LED להבהב בקצבים שונים.

import machine
import micropython

micropython.alloc_emergency_exception_buf(100)


class Foo(object):
    def __init__(self, freq, led):
        self.led = led
        self.timer = machine.Timer(-1, freq=freq, callback=self.cb, hard=True)

    def cb(self, tim):
        self.led.toggle()


red = Foo(1, machine.LED("LED_RED"))
green = Foo(0.8, machine.LED("LED_GREEN"))

בדוגמה זו מופע ה-red מניע את נורת ה-LED האדומה מטיימר וירטואלי בקצב 1 Hz: בכל פעם שהטיימר נורה, red.cb() נקראת, ומחליפה את מצב נורת ה-LED האדומה. מופע ה-green פועל באופן דומה עם טיימר בקצב 0.8 Hz המחליף את מצב נורת ה-LED הירוקה. השימוש במתודות מופע מקנה שני יתרונות. ראשית, מחלקה יחידה מאפשרת שיתוף קוד בין מספר מופעי חומרה. שנית, כמתודה מאוגדת (bound method), הארגומנט הראשון של פונקציית ה-callback הוא self. הדבר מאפשר ל-callback לגשת לנתוני מופע ולשמור מצב בין קריאות עוקבות. לדוגמה, אם למחלקה שלעיל היה משתנה self.count המאופס לאפס בבנאי, cb() יכלה להגדיל את המונה. מופעי ה-red וה-green היו אז מתחזקים ספירות עצמאיות של מספר הפעמים שכל נורת LED שינתה את מצבה.

יצירת אובייקטים של Python

ISR’s אינן יכולות ליצור מופעים של אובייקטי Python. הסיבה לכך היא ש-MicroPython צריכה להקצות זיכרון עבור האובייקט ממאגר של בלוקי זיכרון פנויים הנקרא heap. הדבר אינו מותר במטפל פסיקה מכיוון שהקצאת ערימה אינה כשירה לכניסה חוזרת (re-entrant). במילים אחרות, הפסיקה עלולה להתרחש כאשר התוכנית הראשית נמצאת באמצע ביצוע הקצאה - כדי לשמור על שלמות הערימה, המפרש אוסר הקצאות זיכרון בקוד ISR.

תוצאה של כך היא ש-ISR’s אינן יכולות להשתמש בחשבון נקודה צפה; זאת מכיוון שמספרי נקודה צפה הם אובייקטי Python. באופן דומה, ISR אינה יכולה להוסיף פריט לרשימה. בפועל יכול להיות קשה לקבוע במדויק אילו מבני קוד ינסו לבצע הקצאת זיכרון ויעוררו הודעת שגיאה: סיבה נוספת לשמור על קוד ISR קצר ופשוט.

דרך אחת להימנע מסוגיה זו היא ש-ISR תשתמש בחוצצים שהוקצו מראש. לדוגמה, בנאי של מחלקה יוצר מופע bytearray ודגל בוליאני. מתודת ה-ISR מקצה נתונים למיקומים בחוצץ ומגדירה את הדגל. הקצאת הזיכרון מתרחשת בקוד התוכנית הראשית כאשר האובייקט נוצר, ולא ב-ISR.

מתודות הקלט/פלט של ספריית MicroPython מספקות בדרך כלל אפשרות להשתמש בחוצץ שהוקצה מראש. לדוגמה, machine.I2C.readfrom_into() קוראת לתוך חוצץ ניתן לשינוי שסופק על ידי הקורא: הדבר מאפשר את השימוש בה ב-ISR.

אמצעי ליצירת אובייקט מבלי להשתמש במחלקה או במשתנים גלובליים הוא כדלקמן:

def set_volume(t, buf=bytearray(3)):
    buf[0] = 0xa5
    buf[1] = t >> 4
    buf[2] = 0x5a
    return buf

המהדר יוצר מופע של ארגומנט ברירת המחדל buf כאשר הפונקציה נטענת בפעם הראשונה (בדרך כלל כאשר המודול שבו היא נמצאת מיובא).

מופע של יצירת אובייקט מתרחש כאשר נוצרת הפניה למתודה מאוגדת (bound method). משמעות הדבר היא ש-ISR אינה יכולה להעביר מתודה מאוגדת לפונקציה. פתרון אחד הוא ליצור הפניה למתודה המאוגדת בבנאי המחלקה ולהעביר את ההפניה הזו ב-ISR. לדוגמה:

class Foo():
    def __init__(self):
        self.bar_ref = self.bar  # Allocation occurs here
        self.x = 0.1
        self.tim = machine.Timer(-1, freq=2, callback=self.cb, hard=True)

    def bar(self, _):
        self.x *= 1.2
        print(self.x)

    def cb(self, t):
        # Passing self.bar would cause allocation.
        micropython.schedule(self.bar_ref, 0)

טכניקות אחרות הן להגדיר וליצור מופע של המתודה בבנאי, או להעביר את Foo.bar() עם הארגומנט self.

שימוש באובייקטים של Python

הגבלה נוספת על אובייקטים נובעת מאופן הפעולה של Python. כאשר מבוצעת הוראת import, קוד ה-Python מהודר ל-bytecode, כאשר שורת קוד אחת ממופה בדרך כלל למספר bytecodes. כאשר הקוד רץ, המפרש קורא כל bytecode ומבצע אותו כסדרה של הוראות קוד מכונה. בהינתן שפסיקה יכולה להתרחש בכל זמן בין הוראות קוד מכונה, שורת קוד ה-Python המקורית עשויה להתבצע רק חלקית. כתוצאה מכך, אובייקט Python כגון קבוצה (set), רשימה או מילון שעבר שינוי בלולאה הראשית עלול לחסר עקביות פנימית ברגע שהפסיקה מתרחשת.

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

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

הסכנה מתעוררת כאשר מבנה האובייקט משתנה, בעיקר במקרה של מילונים. הוספה או מחיקה של מפתחות יכולה להפעיל ערבול מחדש (rehash). אם ISR קשיחה רצה בעוד ערבול מחדש בתהליך ומנסה לגשת לפריט, עלולה להתרחש קריסה. באופן פנימי, משתנים גלובליים ממומשים כמילון. כתוצאה מכך, התוכנית הראשית צריכה ליצור את כל המשתנים הגלובליים הנחוצים לפני תחילת תהליך המייצר פסיקות קשיחות. קוד היישום צריך גם להימנע ממחיקת משתנים גלובליים.

MicroPython תומכת במספרים שלמים בדיוק שרירותי. ערכים בין 230 -1 לבין -230 יאוחסנו במילת מכונה יחידה. ערכים גדולים יותר מאוחסנים כאובייקטי Python. כתוצאה מכך, שינויים במספרים שלמים ארוכים אינם יכולים להיחשב אטומיים. השימוש במספרים שלמים ארוכים ב-ISR’s אינו בטוח מכיוון שעלול להתבצע ניסיון להקצאת זיכרון כאשר ערך המשתנה משתנה.

התגברות על מגבלת הנקודה הצפה

באופן כללי, מוטב להימנע משימוש במספרי נקודה צפה בקוד ISR: התקני חומרה בדרך כלל מטפלים במספרים שלמים, וההמרה למספרי נקודה צפה נעשית בדרך כלל בלולאה הראשית. עם זאת, ישנם כמה אלגוריתמי DSP הדורשים נקודה צפה. בפלטפורמות עם נקודה צפה חומרתית (כגון מצלמות OpenMV Cam מבוססות STM32), ניתן להשתמש באסמבלר ARM Thumb המוטמע (inline) כדי לעקוף מגבלה זו. זאת מכיוון שהמעבד מאחסן ערכי נקודה צפה במילת מכונה; ניתן לשתף ערכים בין ה-ISR לקוד התוכנית הראשית באמצעות מערך של מספרי נקודה צפה.

שימוש ב-micropython.schedule

פונקציה זו מאפשרת ל-ISR לתזמן callback לביצוע ”בקרוב מאוד“. ה-callback מתוייק לביצוע שיתרחש בזמן שבו הערימה (heap) אינה נעולה. לפיכך הוא יכול ליצור אובייקטי Python ולהשתמש במספרי נקודה צפה. ה-callback גם מובטח שירוץ בזמן שבו התוכנית הראשית השלימה כל עדכון של אובייקטי Python, כך שה-callback לא ייתקל באובייקטים מעודכנים חלקית.

שימוש טיפוסי הוא לטיפול בחומרת חיישנים. ה-ISR רוכשת נתונים מהחומרה ומאפשרת לה להנפיק פסיקה נוספת. לאחר מכן היא מתזמנת callback לעיבוד הנתונים.

callbacks מתוזמנים צריכים לציית לעקרונות תכנון מטפל הפסיקה המתוארים להלן. זאת כדי להימנע מבעיות הנובעות מפעילות קלט/פלט ומשינוי נתונים משותפים, שיכולות להתעורר בכל קוד שמקדים (preempts) את לולאת התוכנית הראשית.

יש לשקול את זמן הביצוע ביחס לתדירות שבה יכולות להתרחש פסיקות. אם פסיקה מתרחשת בעוד ה-callback הקודם מתבצע, מופע נוסף של ה-callback יתוייק לביצוע; זה ירוץ לאחר שהמופע הנוכחי הושלם. קצב חזרת פסיקות גבוה ומתמשך נושא לפיכך סיכון לצמיחה בלתי מוגבלת של התור וכשל סופי עם RuntimeError.

אם ה-callback שיש להעביר ל-schedule() הוא מתודה מאוגדת (bound method), שקלו את ההערה ב“יצירת אובייקטים של Python“.

חריגות

אם ISR מעוררת חריגה, היא לא תתפשט ללולאה הראשית. הפסיקה תושבת אלא אם החריגה מטופלת על ידי קוד ה-ISR.

ממשק ל-asyncio

כאשר ISR רצה, היא יכולה להקדים (preempt) את מתזמן ה-asyncio. אם ה-ISR מבצעת פעולת asyncio, פעולת המתזמן עלולה להשתבש. הדבר חל בין אם הפסיקה קשיחה או רכה, וגם חל אם ה-ISR העבירה ביצוע לפונקציה אחרת באמצעות micropython.schedule. בפרט, יצירה או ביטול של משימות אינם תקינים בהקשר ISR. הדרך הבטוחה לתקשר עם asyncio היא לממש קורוטינה עם סנכרון המבוצע על ידי asyncio.ThreadSafeFlag. הקטע הבא ממחיש את יצירת המשימה בתגובה לפסיקה:

tsf = asyncio.ThreadSafeFlag()


def isr(_):  # Interrupt handler
    tsf.set()


async def foo():
    while True:
        await tsf.wait()
        asyncio.create_task(bar())

בדוגמה זו תהיה כמות משתנה של השהיה בין ביצוע ה-ISR לבין ביצוע foo(). הדבר טבוע בתזמון השיתופי (cooperative scheduling). ההשהיה המרבית תלויה ביישום ובפלטפורמה אך בדרך כלל ניתנת למדידה בעשרות מילישניות.

סוגיות כלליות

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

תכנון מטפל פסיקה

כאמור לעיל, ISR’s צריכות להיות מתוכננות להיות פשוטות ככל האפשר. עליהן תמיד לחזור בפרק זמן קצר וצפוי. הדבר חשוב מכיוון שכאשר ה-ISR רצה, הלולאה הראשית אינה רצה: בהכרח הלולאה הראשית חווה השהיות בביצועה בנקודות אקראיות בקוד. השהיות כאלה יכולות להיות מקור לבאגים שקשה לאבחן, במיוחד אם משכן ארוך או משתנה. כדי להבין את ההשלכות של זמן ריצת ה-ISR, נדרשת הבנה בסיסית של עדיפויות פסיקה.

פסיקות מאורגנות על פי תוכנית עדיפויות. קוד ISR עשוי בעצמו להיות מופרע על ידי פסיקה בעדיפות גבוהה יותר. לכך השלכות אם שתי הפסיקות משתפות נתונים (ראו קטעים קריטיים להלן). אם פסיקה כזו מתרחשת, היא משבצת השהיה לתוך קוד ה-ISR. אם פסיקה בעדיפות נמוכה יותר מתרחשת בעוד ה-ISR רצה, היא תתעכב עד שה-ISR תסתיים: אם ההשהיה ארוכה מדי, הפסיקה בעדיפות הנמוכה יותר עלולה להיכשל. סוגיה נוספת עם ISR’s איטיות היא המקרה שבו פסיקה שנייה מאותו סוג מתרחשת במהלך ביצועה. הפסיקה השנייה תטופל עם סיום הראשונה. עם זאת, אם קצב הפסיקות הנכנסות עולה בעקביות על יכולת ה-ISR לשרת אותן, התוצאה לא תהיה משמחת.

כתוצאה מכך יש להימנע ממבני לולאה או למזער אותם. יש להימנע בדרך כלל מקלט/פלט להתקנים מלבד ההתקן המפסיק: קלט/פלט כגון גישה לדיסק, הוראות print וגישה ל-UART הוא איטי יחסית, ומשכו עשוי להשתנות. סוגיה נוספת כאן היא שפונקציות מערכת הקבצים אינן כשירות לכניסה חוזרת (reentrant): שימוש בקלט/פלט של מערכת הקבצים ב-ISR ובתוכנית הראשית יהיה מסוכן. באופן מכריע, קוד ISR אינו צריך להמתין לאירוע. קלט/פלט מקובל אם ניתן להבטיח שהקוד יחזור בפרק זמן צפוי, לדוגמה החלפת מצב של פין או נורת LED. גישה להתקן המפסיק באמצעות I2C או SPI עשויה להיות נחוצה, אך יש לחשב או למדוד את הזמן הנדרש לגישות כאלה ולהעריך את השפעתו על היישום.

בדרך כלל קיים צורך לשתף נתונים בין ה-ISR לבין הלולאה הראשית. ניתן לעשות זאת באמצעות משתנים גלובליים או באמצעות משתני מחלקה או מופע. המשתנים הם בדרך כלל מסוג מספר שלם או בוליאני, או מערכי מספרים שלמים או בייטים (מערך מספרים שלמים שהוקצה מראש מציע גישה מהירה יותר מרשימה). כאשר מספר ערכים משתנים על ידי ה-ISR, יש לשקול את המקרה שבו הפסיקה מתרחשת בזמן שהתוכנית הראשית ניגשה לחלק מהערכים, אך לא לכולם. הדבר עלול להוביל לחוסר עקביות.

שקלו את התכנון הבא. ISR מאחסנת נתונים נכנסים ב-bytearray, ולאחר מכן מוסיפה את מספר הבייטים שהתקבלו למספר שלם המייצג את סך הבייטים המוכנים לעיבוד. התוכנית הראשית קוראת את מספר הבייטים, מעבדת את הבייטים, ולאחר מכן מאפסת את מספר הבייטים המוכנים. זה יעבוד עד שתתרחש פסיקה מיד לאחר שהתוכנית הראשית קראה את מספר הבייטים. ה-ISR מכניסה את הנתונים שנוספו לחוצץ ומעדכנת את המספר שהתקבל, אך התוכנית הראשית כבר קראה את המספר, ולכן מעבדת את הנתונים שהתקבלו במקור. הבייטים שזה עתה הגיעו אובדים.

ישנן דרכים שונות להימנע מסכנה זו, הפשוטה ביותר היא שימוש בחוצץ מעגלי (circular buffer). אם לא ניתן להשתמש במבנה עם בטיחות חוטים (thread safety) מובנית, דרכים אחרות מתוארות להלן.

כניסה חוזרת (Reentrancy)

סכנה אפשרית עלולה להתרחש אם פונקציה או מתודה משותפת בין התוכנית הראשית לבין אחת או יותר ISR’s, או בין מספר ISR’s. הסוגיה כאן היא שהפונקציה עצמה עשויה להיות מופרעת ומופע נוסף של אותה פונקציה ירוץ. אם הדבר עתיד להתרחש, הפונקציה חייבת להיות מתוכננת להיות כשירה לכניסה חוזרת (reentrant). כיצד עושים זאת הוא נושא מתקדם החורג מהיקף מדריך זה.

קטעים קריטיים

דוגמה לקטע קריטי של קוד היא קטע הניגש ליותר ממשתנה אחד שיכול להיות מושפע מ-ISR. אם הפסיקה במקרה מתרחשת בין הגישות למשתנים הבודדים, ערכיהם יהיו לא עקביים. זוהי דוגמה לסכנה הידועה כמצב מרוץ (race condition): ה-ISR ולולאת התוכנית הראשית מתחרות על שינוי המשתנים. כדי להימנע מחוסר עקביות, יש להשתמש באמצעי כדי להבטיח שה-ISR לא תשנה את הערכים למשך הקטע הקריטי. דרך אחת להשיג זאת היא להנפיק machine.disable_irq() לפני תחילת הקטע, ו-machine.enable_irq() בסופו. הנה דוגמה לגישה זו:

import machine
import micropython
import array
import random
import time

micropython.alloc_emergency_exception_buf(100)


class BoundsException(Exception):
    pass


ARRAYSIZE = const(20)
index = 0
data = array.array('i', [0] * ARRAYSIZE)


def callback1(t):
    global data, index
    for x in range(5):
        data[index] = random.getrandbits(30)  # simulate input
        index += 1
        if index >= ARRAYSIZE:
            raise BoundsException('Array bounds exceeded')


tim = machine.Timer(-1, freq=100, callback=callback1, hard=True)

for loop in range(1000):
    if index > 0:
        irq_state = machine.disable_irq()  # Start of critical section
        for x in range(index):
            print(data[x])
        index = 0
        machine.enable_irq(irq_state)  # End of critical section
        print('loop {}'.format(loop))
    time.sleep_ms(1)

tim.deinit()

קטע קריטי יכול להכיל שורת קוד יחידה ומשתנה יחיד. שקלו את קטע הקוד הבא.

count = 0


def cb(): # An interrupt callback
    count += 1


def main():
    # Code to set up the interrupt callback omitted
    while True:
        count += 1

דוגמה זו ממחישה מקור עדין של באגים. השורה count += 1 בלולאה הראשית נושאת סכנת מצב מרוץ ספציפית הידועה כקריאה-שינוי-כתיבה (read-modify-write). זהו גורם קלאסי לבאגים במערכות זמן אמת. בלולאה הראשית MicroPython קוראת את ערך count, מוסיפה לו 1, וכותבת אותו בחזרה. במקרים נדירים הפסיקה מתרחשת לאחר הקריאה ולפני הכתיבה. הפסיקה משנה את count אך השינוי שלה נדרס על ידי הלולאה הראשית כאשר ה-ISR חוזרת. במערכת אמיתית הדבר עלול להוביל לכשלים נדירים ובלתי צפויים.

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

יש לנקוט זהירות מיוחדת אם מערך נתונים משותף בין ISR’s שונות. הסכנה כאן היא שהפסיקה בעדיפות הגבוהה יותר עלולה להתרחש כאשר זו בעדיפות הנמוכה יותר עדכנה חלקית את הנתונים המשותפים. ההתמודדות עם מצב זה היא נושא מתקדם החורג מהיקף מבוא זה, מלבד לציין שאובייקטי mutex המתוארים להלן יכולים לעיתים לשמש.

השבתת פסיקות למשך הקטע הקריטי היא הדרך הרגילה והפשוטה ביותר להמשיך, אך היא משביתה את כל הפסיקות ולא רק את זו בעלת הפוטנציאל לגרום בעיות. בדרך כלל לא רצוי להשבית פסיקה לזמן ארוך. במקרה של פסיקות טיימר הדבר מכניס שונות לזמן שבו מתרחש callback. במקרה של פסיקות התקן, הדבר עלול להוביל לכך שההתקן ישורת מאוחר מדי, עם אובדן נתונים אפשרי או שגיאות גלישה (overrun) בחומרת ההתקן. כמו ISR’s, קטע קריטי בקוד הראשי צריך להיות בעל משך קצר וצפוי.

גישה להתמודדות עם קטעים קריטיים המצמצמת באופן רדיקלי את הזמן שבו הפסיקות מושבתות היא שימוש באובייקט הנקרא mutex (שם הנגזר מהמושג הדרה הדדית, mutual exclusion). התוכנית הראשית נועלת את ה-mutex לפני הרצת הקטע הקריטי ומשחררת אותו בסופו. ה-ISR בודקת אם ה-mutex נעול. אם כן, היא נמנעת מהקטע הקריטי וחוזרת. אתגר התכנון הוא הגדרת מה ה-ISR צריכה לעשות במקרה שבו הגישה למשתנים הקריטיים נדחית. דוגמה פשוטה ל-mutex ניתן למצוא כאן. שימו לב שקוד ה-mutex אכן משבית פסיקות, אך רק למשך שמונה הוראות מכונה: היתרון בגישה זו הוא שפסיקות אחרות כמעט אינן מושפעות.

פסיקות וה-REPL

מטפלי פסיקה, כגון אלה המשויכים לטיימרים, יכולים להמשיך לרוץ לאחר שתוכנית מסתיימת. הדבר עלול להפיק תוצאות בלתי צפויות במקרים שבהם ייתכן שציפיתם שהאובייקט המעורר את ה-callback יצא מתחום ההגדרה (scope). לדוגמה, על OpenMV Cam:

def bar():
    foo = machine.Timer(-1, freq=4, callback=lambda t: print('.', end=''), hard=True)

bar()

זה ממשיך לרוץ עד שהטיימר מושבת במפורש או שהלוח מאופס באמצעות Ctrl-D.