טיימרים ב-MicroPython: פרויקט קודן עם Xiao RP2040

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

מערכת הקודן בפעולה, עם חשמל מחיבור USB
מערכת הקודן בפעולה, עם חשמל מחיבור USB

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

טיימרים וירטואליים הם בעצם אובייקטים של קוד מוכן-מראש, שמבצעים קריאות לפונקציות במרווחי זמן שאנחנו מגדירים. הרזולוציה והדיוק של טיימר וירטואלי נמוכים מאוד ביחס לטיימר חומרה ואין לו תכונות משוכללות, אבל התפעול שלו הרבה יותר קל, ומספר הטיימרים לא מוגבל לאלה שקיימים בחומרה של המיקרו-בקר הספציפי. כל טיימר וירטואלי הוא מופע של המחלקה Timer מהספרייה machine.Timer, והוא מקבל שלושה פרמטרים בלבד: mode (האם הוא ONE_SHOT, כלומר חד-פעמי, או PERIODIC שפועל שוב ושוב), callback – לאיזו פונקציה הוא צריך לקרוא, ו-period – פרק הזמן באלפיות השנייה מרגע ההפעלה עד הקריאה ל-callback, או בין קריאות.

נתחיל בדוגמה פשוטה. בפרויקט שלי השתמשתי בלוח מקשים שטוח ("ממברנה"), שלא מספק פידבק פיזי טוב ללחיצה. כדי שהמשתמש יידע שהלחיצה שלו אכן נקלטה במערכת, בכל פעם שזה קורה אני שולח אות PWM שגורם להשמעת צליל ב"באזר" קטן למשך 60 אלפיות השנייה. אם הפידבק הזה יתעכב באלפית שנייה או שתיים, או יימשך 59 או 62 אלפיות השנייה, לא יקרה כלום – תסריט שימוש קלאסי לטיימר בעל דיוק נמוך. ראשית, הנה הקוד שמגדיר את אובייקט ה-PWM על פין GPIO מספר 1, ואת אובייקט הטיימר:

קוד להגדרת אובייקט PWM ואובייקט טיימר וירטואלי
קוד להגדרת אובייקט PWM ואובייקט טיימר וירטואלי

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

הכללת הספריות הדרושות ל-GPIO, ל-PWM ול-Timer בקוד
הכללת הספריות הדרושות ל-GPIO, ל-PWM ול-Timer בקוד

חלק אחר בקוד, שנראה אותו בהמשך, הופך את המשתנה beep_flag ל-True בכל פעם שהוא מזהה לחיצה. ואז…

קוד להפעלת ותזמון הכיבוי של הביפ
קוד להפעלת ותזמון הכיבוי של הביפ (לחצו לתמונה גדולה)

שתי השורות הראשונות בתוך ה-if מעירות את אובייקט ה-PWM כדי להפעיל את הצליל בבאזר, והשורה השלישית מפעילה את הטיימר. הוא מופעל במצב ONE_SHOT, כלומר אחרי 60 אלפיות שנייה הוא יקרא לפונקציה שנשלחה ל-callback. כאן זוהי פונקציה אנונימית (lambda) פשוטה שמוגדרת לצורך העניין, וקוראת ל-deinit שראינו קודם. עולות כאן שתי שאלות: 1) למה לא לציין את deinit ישירות בשביל ה-callback? ו-2) בשביל מה הפרמטר caller אם הפונקציה האנונימית בכלל לא משתמשת בו? התשובה לשתיהן היא ש-callback מצפה לפונקציה שמקבלת פרמטר אחד ויחיד, וכשהטיימר קורא לה, הוא שולח דרך הפרמטר הזה רפרנס לעצמו. גם אם הייתי כותב פונקציה רגילה עם def בשביל לכבות את הבאזר, הייתי צריך לכתוב אותה כך שתקבל את הפרמטר הזה.

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

חיווט המערכת מקרוב, אחרי הקשת הקוד הסודי
חיווט המערכת מקרוב, אחרי הקשת הקוד הסודי (לחצו לתמונה גדולה)

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

הגבלת זמן ההארה של הלד הירוק באמצעות טיימר וירטואלי
הגבלת זמן ההארה של הלד הירוק באמצעות טיימר וירטואלי

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

הגדרת והפעלת הטיימר הווירטואלי לסריקת לוח המקשים
הגדרת והפעלת הטיימר הווירטואלי לסריקת לוח המקשים

את הפונקציה read_keypad כתבתי בעצמי, והיא מבצעת את העבודה השחורה: סריקה אחת של הפינים שמחוברים לכבל של לוח המקשים, המרת הממצאים הגולמיים, אם יש כאלה, לספרות ולסימנים שמופיעים על הלוח, הימנעות מחזרה לא רצויה על תווים במקרה של לחיצה ארוכה, איסוף התווים למחרוזת ועוד.

שאר הקוד של התוכנית מוקדש להגדרות משתנים ופינים, וטיפול במרווחי זמן גדולים מדי בין הקשות (את זה עשיתי בעזרת הפונקציות ticks_ms ו-ticks_diff מהספרייה time, באופן שמזכיר את העבודה עם millis בארדואינו). הקוד המלא נמצא כאן ב-Pastebin.

אופן המימוש הטכני של הטיימרים הווירטואליים במיקרו-פייתון לא מוסבר בתיעוד הרגיל. ממה שהצלחתי להבין בינתיים מקוד המקור (הנה חלק ממנו), הקריאה לפונקציה שביקשנו דרך callback אינה ישירה, אלא נעשית דרך פונקציה מתווכת בשם Schedule. זו פונקציה שאנחנו יכולים להשתמש בה גם בלי קשר לטיימרים, והיא אומרת למפענח של מיקרו-פייתון משהו כזה: "תקשיב אח שלי, יש לי פה פונקציה שאני רוצה שתריץ, אבל בלי לחץ, מתי שנוח לך, אם יש לך משהו אחר דחוף אז תשלים אותו קודם. סבבה?" החיסרון של זה הוא כמובן שהמפענח באמת יריץ את הפונקציה מתי שנוח לו, זאת אומרת שהיא יכולה להתעכב זמן לא-ידוע ולא נוכל לסמוך על התזמון אם יש למערכת דרישות Real-time. היתרון הוא שככה לא צריך לדאוג ממגבלות שמוטלות בדרך כלל על קוד שרץ מתוך פסיקות.

להרשמה
הודע לי על
0 תגובות
Inline Feedbacks
הראה את כל התגובות