אבל עוד לפני שהגעתי למצלמה, למודל או אפילו ל־LED הראשון, STM32CubeMX נעצר בזמן הורדת קובץ בשם crdb.zip.

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

יש משהו מדכא ברגע הזה. על השולחן נמצא אחד השבבים המתקדמים של החברה, אבל הדלת אליו תקועה בגלל קובץ ZIP שלא ירד.

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

בעבר עבדתי במשך כמה חודשים עם GUI Guider של NXP, כלי שמאפשר לבנות ממשק גרפי למערכות Embedded. בשלב מסוים יצאה גרסה חדשה. עדכנתי, פתחתי את התוכנה, ופתאום הופיע מסך כניסה.

בסדר. היה לי משתמש. התחברתי.

אחרי ההתחברות קיבלתי מסך לבן.

פניתי לתמיכה של NXP, והתשובה הייתה שמדובר בבעיה מוכרת ושבינתיים כדאי להשתמש בגרסה הקודמת.

וואלה יופי.

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

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

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

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

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

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

כך אפשר לשרוף יום על הגדרת Clock שמעולם לא הייתה הבעיה, או לפרק קוד תקין מפני שמסד נתונים של CubeMX לא ירד כמו שצריך.

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

קל לחשוב שהפרויקט שלנו הוא אוסף קובצי C ו־C++. בפועל, פרויקט עובד נשען גם על קוד האתחול, קובץ הקישור, אפשרויות הקומפיילר, ספריות היצרן, דרייברים, קוד שנוצר אוטומטית, הגדרות הדיבאגר וקובצי הפרויקט של סביבת הפיתוח.

לכל אלה יש גרסאות.

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

כאן מתחיל הסיוט של מעבר ל־SDK או ל־IDE חדשים.

תרשים שכבות התלות של פרויקט Embedded מקוד האפליקציה ועד חבילות ושירותים מרוחקים
קוד המקור הוא רק השכבה העליונה. קוד שנוצר, SDK, מערכת הפעלה, קומפיילר, קובץ קישור, IDE, דיבאגר ושירותי הורדה משתתפים כולם ביצירת הבינארי. תקלה או שינוי גרסה בכל אחת מהשכבות עלולים לעצור את הפרויקט.קרדיט: תרשים מקורי ל־NatiLab, מבוסס על תיאורי שרשרת הכלים הרשמיים של ST, NXP, TI ו־Arduino

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

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

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

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

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

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

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

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

סקיצה שנבנית היום עדיין תלויה בגרסת ליבת Arduino של הלוח, בחבילת ה־Board, בספריות, בקומפיילר ובהגדרות הבנייה. כאשר אחת השכבות האלה משתנה, אפשר להגיע לאותה בעיית עדכון — רק בלי היכרות טובה עם מה שמתרחש מתחת לכפתור.

Arduino הוא נקודת התחלה נהדרת. הוא אינו פוטר אותנו מתיעוד גרסאות ומשחזור סביבת העבודה.

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

במקרים רבים זו גישה נכונה, אבל חשוב להבין מה היא פותרת ומה לא.

VS Code הוא עורך. GCC הוא קומפיילר. הם אינם מגדירים לבדם את השעונים, אינם מאתחלים זיכרון חיצוני ואינם יודעים כיצד לחבר את הדיבאגר למיקרו־בקר. עדיין צריך מערכת בנייה, קובצי קישור, קוד אתחול, דרייברים, הגדרות Debug ולעיתים גם חלקים מתוך ה־SDK של היצרן.

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

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

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

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

אבל כדאי להשתמש בדוגמה כנקודת שיגור, לא ככלא.

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

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

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

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

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

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

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

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

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

כאשר LED לא נדלק, אני רוצה לבדוק את הקוד, את החיבור ואת החומרה. אני לא רוצה לשאול אם שרת מרוחק החזיר את קובץ מסד הנתונים הנכון, אם מנגנון ההתחברות תקוע או אם עדכון שינה קובץ שלא ידעתי שקיים.

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

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

אבל הלקח הוא לא לבנות מוצר שהדרך היחידה לבנות אותו עוברת דרך התקנה אחת של CubeMX, גרסה אחת של IAR או חשבון משתמש אחד של NXP.

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

הזווית ההנדסית

למה זה מעניין

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