אי-שם בשטח, רחוק מכל מחשב, יושב התקן אלקטרוני ומוציא פלט דרך UART לדיבוג. אנחנו רוצים לשמור את הפלט הזה, ולהעביר אותו בהמשך במרוכז לניתוח במחשב. הנה דרך נוחה לעשות זאת – אם שמים לב לפרטים הקטנים.
כשקיבלתי משימה להכין datalogger לפי התיאור הנ"ל (אגב, הכנתי פעם רשימה של דרישות ושיקולים לאוגרי נתונים למיניהם), חשבתי מיד על לוח ה-Xiao RP2040: לא רק כי יש לי אותו, אלא כי הוא מתנהג קצת כמו כונן נייד כשמחברים אותו ב-USB למחשב. אם הקוד שלי יוכל לכתוב את המחרוזות מה-UART לתוך קובץ טקסט שיופיע ב"כונן" הזה, רוב העבודה בעצם כבר תושלם.
בחירת השפה
הנה הפרט הקטן והחשוב הראשון: זה שהלוח מופיע ככונן, לא אומר שקוד MicroPython (להלן MP) שפועל על הלוח יכול ליצור בכונן הזה קבצים. ל-MP אמנם יש "מערכת קבצים" אך היא פנימית, לא זו שה-USB רואה. אלה שני דברים נפרדים. שפת CircuitPython (להלן CP) של Adafruit, שמבוססת על MP, דווקא כן יכולה לקשר בין שתי המערכות – בתנאי שהן לא פועלות במקביל. הטריק מוסבר במפורט במדריך הזה, אז הנה רק התקציר: חוץ מהקוד הראשי, שנמצא בקובץ code.py, מוסיפים ללוח קובץ קוד boot.py. הוא רץ לפני הראשי, מחליט באיזה מצב המערכת אמורה לעבוד (למשל באמצעות קריאה של מצב מפסק), ומבצע remount למערכת הקבצים בהתאם – כך שהקוד הראשי יוכל לכתוב אליה, או ה-USB.
כמו במקרה של נגן ה-mp3, הטריק השימושי הזה של CP מחייב כמובן שגם כל שאר הקוד יהיה ב-CP, במקום ב-MP כמו ברוב הפוסטים שכתבתי על ה-Xiao. הדבר גזל ממני הרבה זמן של קריאת דוגמאות והסברים, לא תמיד הכי ברורים או מפורטים. לפרויקט מבוסס פייתון שאינו זקוק לספריות מיוחדות של Adafruit, עדיין הייתי הולך על MP בלי לחשוב פעמיים.
שיקולים חשמליים
אחרי התקנת CP על הלוח נשארים ב"דיסק" שלו בערך 900KB פנויים, וזה מספיק לתסריט השימוש של הלקוח כך שאין צורך ברכיב זיכרון חיצוני. ל-Xiao יש כמובן קווי UART בחומרה, אך חשוב לזכור שהוא עובד ב-3V3, ואם ה-UART הנכנס הוא 5V, חייבים להוריד את המתח שמגיע לרגל RX של ה-Xiao. במקרה שלי עשיתי זאת בעזרת מחלק מתח פשוט. הנגדים הנוספים בתמונה למעלה אינם חיוניים והם רק "ליתר ביטחון".
ללוגר הזה לא נדרשת סוללה: את החשמל להפעלת ה-Xiao עצמו, בסביבות 30mA בתנאים רגילים, הוא יקבל דרך ה-USB בזמן חיבור למחשב, או מהמעגל שממנו מגיעים הנתונים בזמן האיסוף שלהם.
משוב למפעיל
אוגר הנתונים שלנו כבר פועל, ובכל זאת כדאי להוסיף אינדיקציות חזותיות (ובעברית: לדים 🙂 ) כדי שמי שמחבר אותו בשטח יוכל לזהות בקלות פעילות תקינה או בעיה. גם לזה לא נדרשים רכיבים נוספים – על ה-Xiao כבר יש לד RGB אחד מסוג "Neopixel", ולד RGB קטנטן רגיל שכל צבע בו נשלט על ידי פין GPIO נפרד. יש גם לד אדום נפרד שדולק אוטומטית כשהלוח מקבל חשמל.
בחרתי להציג על ה-Neopixel, שהוא הלד הכי חזק על הלוח, את הסטטוס לפי מפתח הצבעים הבא:
- לבן = אתחול הושלם, מוכן לאיסוף נתונים
- ירוק = איסוף נתונים בעיצומו, הגיעה ונרשמה לפחות שורת נתונים אחת
- אדום = נגמר המקום בקובץ
- כתום = בעיה אחרת / מצב USB
בנוסף, הלד RGB הקטן יהבהב בכחול בכל פעם שמגיעה שורת נתונים חדשה.
עוד מלכודות קטנות
אם הקוד רץ, כמו ברוב מערכות האמבדד, כל הזמן ברציפות עד שמנתקים אותו מהחשמל, מתי הוא יספיק לסגור בצורה מסודרת את קובץ הנתונים? פספוס סגירה של קבצים הוא סיוט שמלווה אותי מאז שהתחלתי לתכנת, אך מסתבר שב-CP זה לא כל כך נורא: חייבים רק לבצע flush לקובץ אחרי כל פעולת כתיבה.
הקלט שמוחזר מהפונקציה UART.readline אינו מחרוזת אלא מערך בייטים, ובפייתון זה לא אותו הדבר. כדי לשמור אותו בקובץ יש להמיר אותו לפני כן למחרוזת, למשל:
s = "".join([chr(b) for b in raw_bytes])
הלוגר שלי מוסיף, לפני כל מחרוזת בקובץ, את הזמן שבו נקלטה – באלפיות שנייה מאז האתחול. את המידע הזה אני מקבל מהפונקציה supervisor.ticks_ms, שמסיבות שונות ומשונות לא מתחילה לספור מאפס, ושחוזרת לאפס דווקא אחרי 2 בחזקת 29, כך שחישוב הפרשי זמנים אינו "אוטומטי" כמו עם millis של ארדואינו.
סיכום
שפת CircuitPython היא ארדואינו על סטרואידים, לטוב ולרע: יש בה ספריות שמאפשרות לנו לבצע בקלילות מטלות מורכבות מאוד, כמו ניגון קובץ mp3 או הנגשה של קובץ פנימי למחשב, ומצד שני היא מכריחה אותנו ללמוד ולעבוד בדיוק לפי ההגדרות, המגבלות (הרבות!) והשיגיונות הספציפיים שלה. לטעמי, ברוב המקרים החסרונות גוברים על היתרונות, ולא הייתי מציע לאף אחד להתמקצע בה. גם לי אין שום כוונה לעשות זאת. אבל למי שכבר מכיר קצת את צורת העבודה, ובתנאים הנכונים, כפי שהתקיימו בפרויקט הזה, היא מביאה אותנו לפתרון בדרך הקלה והפשוטה ביותר.