מניסיוני, רוב ההחלפות שמסתבכות מתחילות מהנחה סבירה אך שגויה: אם מצאנו MCU עם אותה ליבה, אותו מספר פינים ואותם ממשקים, רוב העבודה מאחורינו. למעשה אלה רק תנאי הכניסה. המיקרו־בקר מחבר בין החשמל על הלוח, הקוד, תהליך הייצור, בדיקות התקינה והיכולת לקנות את המוצר לאורך שנים. לכן כדאי לפרק את ההחלפה לכמה חלקים, ולסיים כל חלק בשאלה פשוטה: איזו ראיה מוכיחה שהחלופה באמת שומרת על מה שהיה חשוב במוצר הישן?
החלק הראשון מתחיל דווקא במוצר הישן. לפני שפותחים מנוע חיפוש של יצרן שבבים, צריך לכתוב מה אסור להשתנות: מה המוצר מודד או מפעיל, כמה מהר הוא חייב להגיב, כמה זמן הוא פועל מסוללה, באיזו טמפרטורה הוא עובד ומה צריך לקרות כאשר חיישן, תקשורת או מתח אספקה נכשלים. אלה גבולות המשחק. בלי הגבולות האלה, קל לבחור רכיב שנראה חזק יותר אך חסר התנהגות קטנה שעליה המוצר נשען.
רק אחרי שההתנהגות ברורה יורדים למספר ההזמנה המדויק של הרכיב הקיים. לא מסתפקים בשם המשפחה: בודקים נפח Flash ו־RAM, מארז, דרגת טמפרטורה, גרסת סיליקון, ציוד היקפי פעיל, שימוש בזיכרון לא־נדיף וכל Errata שהקוד כבר עוקף. באותה הזדמנות אוספים את הנתונים שהפכו עם השנים לידע שבעל פה — זרם השיא בזמן שידור, זמן האתחול, דיוק המדידה או הסיבה שקבל מסוים נמצא ליד פין Reset. דווקא הפרטים האלה נעלמים כאשר המהנדס המקורי כבר אינו בחברה.
בסוף החלק הזה צריך להישאר דף אחד שמחלק את הדרישות לשתיים: דברים שחייבים להישאר זהים, ודברים שמותר לשנות אם מתכננים ובודקים אותם מחדש. הדף הזה חשוב יותר מרשימת מועמדים ארוכה, מפני שהוא מאפשר לפסול רכיב מוקדם ובזול. עכשיו, ורק עכשיו, אפשר לעבור לשאלה הבאה: האם המיקרו־בקר החדש מסוגל לשבת על הלוח ולהתנהג נכון מבחינה חשמלית?
החלק השני הוא החומרה, וכאן המראה החיצוני מטעה. בשיחה יומיומית אומרים לפעמים סוקט, אבל ברוב מוצרי הייצור ה־MCU מולחם ישירות ללוח. צריך להפריד בין צורת המארז, טביעת הרגל ומפת הפינים. שני רכיבים יכולים להגיע באותו מארז עם אותו מספר רגליים, ובכל זאת למקם מתח, אדמה, Reset או פין תכנות במקומות שונים. אם באמת משתמשים בסוקט לצורכי שירות, גם הוא חלק מהמפרט: התנגדות מגע, זרם, טמפרטורה, רעידות ומספר מחזורי החלפה.
אחרי שבודקים שהרכיב נכנס פיזית, בודקים מה כל פין עושה מרגע חיבור המתח ועד שהקוד משתלט עליו. יציאה שהייתה שקטה ברכיב הישן ועלתה לרגע ברכיב החדש יכולה לפתוח שסתום או להדליק טרנזיסטור. פין שסומן בעבר כלא מחובר יכול להפוך לפין שמור שאסור לקשור לאדמה. צריך לעבור באותה צורה על תחומי המתח, סבילות ל־5 וולט, נגדי המשיכה הפנימיים, יכולת הזרם ופיני ה־Boot. זו עבודה איטית, אבל היא זולה בהרבה מלגלות בזמן ההפעלה הראשונה שהלוח החדש מפעיל עומס לפני שהגיע ל־main.
מכאן מרחיבים את הבדיקה אל ספקי הכוח, השעון והעולם האנלוגי. בודקים שצריכת השיא מתאימה למייצב, שסף האיפוס בזמן נפילת מתח מתאים למוצר, ושהמתנד נשאר מדויק גם בחום ובקור. בממיר האנלוגי לא מסתפקים במספר הביטים: מקור בעל עכבה גבוהה עלול להזדקק לזמן דגימה ארוך יותר, ולכן אותו חיישן ואותו קוד יכולים להחזיר תוצאה שונה. בסוף חלק החומרה צריך להוכיח לא רק שהרכיב נדלק, אלא שהכניסות, היציאות, השעונים והמדידות משחזרים את התנהגות המוצר בכל תנאי העבודה.
החלק השלישי הוא התוכנה, וכאן צריך לתקן תפיסה נפוצה לגבי HAL. ה־HAL הוא בדרך כלל שכבה שהיצרן מספק כדי להפעיל רגיסטרים וציוד היקפי בלי לכתוב כל ביט ביד. הוא יכול לעזור לעבור בין דגמים של אותו יצרן, אבל הוא אינו מבטיח אינטגרציה בין התוכנה למוצר. אם לוגיקת המוצר קוראת ישירות ל־HAL של UART, לטיימר של הבקר ולפונקציית כתיבה מסוימת ל־Flash, החלפנו שמות של רגיסטרים אך לא באמת הפרדנו את התוכנה מן החומרה.
הארכיטקטורה הבריאה נראית כמו כמה קומות. בקומה התחתונה נמצאים ה־HAL והדרייברים שמכירים את ה־MCU. מעליהם יושב BSP — קיצור של Board Support Package — שמכיר את הלוח המסוים: איזה פין מדליק ממסר, איזה SPI מחובר לחיישן ואיך מאפסים את רכיב התקשורת. מעליו כדאי לבנות שכבת שירותי פלטפורמה עם פעולות בשפה של המוצר, למשל קריאת חיישן, שמירת הגדרה, שליחת חבילה וקבלת זמן. לוגיקת המוצר משתמשת בשירותים האלה ואינה אמורה לדעת אם מתחתיה יש STM32, NXP, מחשב אישי או בכלל סימולטור.
בעולם התוכנה קוראים לגישה הזאת בכמה שמות. Dependency Inversion אומר שהלוגיקה תלויה בממשק שאנחנו מגדירים ולא בדרייבר שהיצרן נתן. Ports and Adapters, או ארכיטקטורה משושת, מתארת את אותו רעיון בצורה ציורית: האפליקציה נמצאת באמצע, ומסביבה שקעים קבועים שאליהם מחברים מתאמים לחומרה, לרשת, לזיכרון ולמערכת ההפעלה. המונחים נשמעים כבדים, אבל הרעיון עממי מאוד — בונים תקע ושקע ברורים כדי שלא נצטרך לחתוך חוטים בכל פעם שמחליפים מכשיר.
ב־C אפשר לממש את השקע הזה בעזרת struct שמכיל מצביעים לפונקציות והקשר פרטי. הלוגיקה מקבלת, למשל, ממשק Clock עם פעולה שמחזירה זמן, ממשק Storage עם פעולות קריאה וכתיבה וממשק Network עם פעולות שליחה וקבלה. בייצור מחברים אליהם פונקציות שמגיעות מן הדרייברים האמיתיים; בבדיקה מחברים Fake קטן ששומר נתונים בזיכרון של המחשב ומאפשר לשלוט בזמן. אפשר לבחור את המימוש בזמן הקישור או להעביר אותו באתחול — שיטה שנקראת Dependency Injection. זה מעט יותר קוד בהתחלה, אבל הוא מונע מאלפי שורות לוגיקה להכיר את שם הבקר.
ב־C++ אפשר להשתמש במחלקות ממשק מופשטות ובבנאי שמקבל את התלויות, או ב־templates כאשר רוצים שהבחירה תיעשה בזמן הקומפילציה בלי מחיר של קריאה וירטואלית. RAII — קישור חיי המשאב לחיי האובייקט — יכול לעזור לנהל נעילות, buffers וחיבורי תקשורת בלי לשכוח לשחרר אותם במסלול שגיאה. במוצר קטן אפשר לכבות exceptions ו־RTTI ועדיין ליהנות מן המבנה הזה. הבחירה בין C ל־C++ פחות חשובה מן הגבול: הלוגיקה העסקית צריכה להתקמפל כספרייה רגילה ולהיות מסוגלת לרוץ במחשב ללא קובצי ה־HAL.
אבל גם שכבת ביניים יכולה להיות מסוכנת אם החוזה שלה מעורפל. כל ממשק צריך לומר אם הפעולה חוסמת, מי הבעלים של ה־buffer, עד מתי המידע חייב להישאר בזיכרון, מאיזה הקשר מגיע Callback ואיך מסמנים timeout או ניתוק. זה חשוב במיוחד ב־DMA אסינכרוני: ה־Fake במחשב יכול להעתיק מידע מיד, בזמן שהחומרה האמיתית ממשיכה להשתמש במצביע גם אחרי שהפונקציה חזרה. שכבת הפשטה טובה מסתירה את דרך המימוש, אבל אינה מסתירה התנהגות שהלוגיקה חייבת לכבד.
אפשר לעשות את זה גם בלי RTOS. ב־Superloop, הלולאה הראשית אוספת אירועים מן הדרייברים ומעבירה אותם למכונת מצבים — קוד שמחליט מה השלב הבא לפי המצב והאירוע. הפסיקה אינה מפעילה לוגיקה עסקית; היא רק מכניסה אירוע ל־Ring buffer או מסמנת דגל. פונקציה כמו app_step מקבלת אירוע וזמן ומחזירה פקודות לביצוע. בבדיקה אפשר להזין לה רצף מלאכותי של חיישן, timeout וניתוק תקשורת, ולראות בתוך אלפית שנייה כיצד המוצר היה מתנהג במשך שעה.
עם FreeRTOS החלוקה משתנה, אבל העיקרון נשאר. FreeRTOS נותן משימות, תורים, Mutex, התראות וטיימרים; הוא אינו מגדיר עבורכם את גבול החומרה. גישה טובה היא לתת למשימת שירות אחת בעלות על רכיב מורכב, למשל מודם או Flash, ולתת לשאר המערכת לדבר איתה בהודעות. מעל ה־RTOS אפשר להוסיף OSAL — שכבת הפשטה לשירותי מערכת ההפעלה — שמתרגמת יצירת משימה, המתנה, נעילה וזמן לממשק קטן של המוצר. כך אפשר להחליף FreeRTOS או להריץ חלקים מן הקוד במחשב, אבל עדיין צריך לבדוק עדיפויות, גדלי Stack, API שמותר לקרוא מתוך פסיקה ומדיניות הקצאת זיכרון.
Zephyr מציע מסלול מובנה יותר. יש בו Device Model עם ממשקים כלליים ל־GPIO, SPI, I²C, רשת ורכיבים אחרים, ו־Devicetree שמתאר מה מחובר ללוח בלי לקבור את המידע בקוד האפליקציה. Kconfig בוחר אילו יכולות ודרייברים נכנסים לבנייה. היתרון הוא שאפליקציה יכולה לדבר עם ממשק כללי במקום עם HAL של יצרן מסוים. בנוסף, native_sim מאפשר לבנות יישום Zephyr כתוכנית Linux רגילה, ו־Ztest ו־Twister מספקים מסגרת לבדיקות. אבל גם כאן אין קסם: צריך לוודא שהבקר המדויק, הדרייבר והאפשרויות שבהן משתמשים באמת נתמכים, ולא רק שקיים לוח דומה ברשימת Zephyr.
דוגמת Ethernet מראה למה המילה נתמך אינה מספיקה. ייתכן שלשני הבקרים יש MAC — החלק הדיגיטלי של Ethernet — אבל ה־PHY החיצוני שמחבר אותו לכבל דורש ממשק RMII או MII, שעון, קו ניהול MDIO ורצף Reset מסוים. אחר כך מגיעים תיאורי DMA, יישור buffers, Cache, אירועי Link ומחסנית TCP/IP. דוגמת קוד ל־PHY אחר או ללוח הערכה אחר יכולה להעלות Link במעבדה ועדיין להיכשל כאשר מוציאים ומחזירים כבל, מעבירים עומס גבוה או מפעילים Cache. לפני שבוחרים MCU חלופי צריך לחפש דוגמה מלאה לשילוב הקרוב לשלכם — אותו MAC, אותו PHY, אותה מחסנית ואותה שיטת DMA — ולברר מי מתחזק אותה.
ספריות IoT יוצרות אותה אשליה ברמה גבוהה יותר. coreMQTT של FreeRTOS, למשל, אינו תלוי דווקא ב־FreeRTOS Kernel, אבל הוא כן דורש מן הפלטפורמה פעולות שליחה וקבלה ושעון מדויק למדידת timeout. מוצר אמיתי צריך מסביב גם TCP, TLS, מקור מספרים אקראיים, אחסון תעודות ומפתחות, DNS, זיכרון ושיטת עדכון. ספריית קוד פתוח אחרת עלולה להניח גודל מילה מסוים, גישה לא־מיושרת, סדר בתים, הוראת הצפנה או התנהגות Cache של בקר מסוים. לכן לא שואלים רק אם הספרייה מתקמפלת; בודקים את כל נקודות החיבור שלה לפלטפורמה ואת הדוגמאות הקיימות עבור הרכיב וה־SDK המדויקים.
כאן נכנסת ההבחנה החשובה בין Integration, Verification ו־Validation. אינטגרציה אומרת שחיברנו את החלקים והם מדברים: מערכת ההפעלה עולה, ה־PHY מעלה Link והודעת MQTT מגיעה לענן. Verification בודק שהמימוש עומד בדרישות שכתבנו — זמני תגובה, טיפול בשגיאות, שימוש בזיכרון ואבטחה. Validation בודק שהמוצר השלם עדיין עושה אצל המשתמש את מה שהוא אמור לעשות, בתנאים האמיתיים שלו. אם המתכנתים שילבו מערכת הפעלה שהם אוהבים אבל יצרן השבב לא התחייב לגרסה, ללוח או לדרייברים האלה, האחריות על ה־Port, העדכונים, התקלות והוולידציה נשארת אצל החברה. זה לא הולך ברגל.
היתרון הגדול של שכבת הביניים מתגלה עכשיו בבדיקות. אותו ממשק שמחובר ל־Flash ולרשת בייצור יכול להתחבר במחשב ל־Mock שמוודא איזו פעולה נקראה, ל־Stub שמחזיר תשובה קבועה או ל־Fake שמתנהג כמו רכיב פשוט. כך מריצים אלפי בדיקות של לוגיקת המוצר בלי לוח ובלי לחכות לזמן אמיתי. אחר כך מוסיפים Contract tests — אותה סדרת בדיקות לכל מימוש של הממשק — כדי לוודא שהמתאם הישן, המתאם החדש והסימולטור מפרשים שגיאות וזמנים באותה צורה. רק מעליהם מגיעות בדיקות על לוח אמיתי, שבהן בודקים פסיקות, DMA, חשמל ותזמון שאי אפשר לדמות נאמנה.
בסוף חלק התוכנה צריכות להישאר ארבע ראיות. לוגיקת המוצר רצה בבדיקות Host בלי חומרה; המתאמים של הרכיב הישן והחדש עוברים את אותו חוזה בדיקות; קיימות דוגמאות או הוכחות אינטגרציה לרכיבים ולספריות הקריטיים; וגרסאות ה־RTOS, ה־SDK, הקומפיילר והספריות נעולות ומתועדות. אחרי זה עדיין נשארת וולידציה על המוצר האמיתי, אבל לפחות ברור מה בודקים ומי אחראי לכל שכבה. רק בנקודה הזאת אפשר לעבור מן התוכנה אל השאלה הבאה: האם אפשר לייצר את הפתרון החדש שוב ושוב, ולא רק לגרום לו לעבוד פעם אחת.
החלק הרביעי הוא המעבר מן המעבדה לייצור. רכיב שעבד על לוח פיתוח או אחרי הלחמה ידנית עדיין אינו חלופה מאושרת. קו הייצור צריך לקבל אותו באריזה המתאימה, למקם אותו, להלחים אותו, לבדוק אותו ולצרוב עליו קושחה בקצב הנדרש. כאן נכנסים שיקולים שבדרך כלל אינם מופיעים בדיון הראשון עם מתכנת הקושחה, אבל יכולים לעצור את המפעל לחלוטין.
מעבר ממארז LQFP, שבו רואים את הרגליים, אל QFN עם משטח תחתון או אל BGA משנה את טביעת הרגל, הסטנסיל, כמות משחת ההלחמה, פרופיל החימום ויכולת הבדיקה. צריך לבדוק גם MSL — הזמן שבו רכיב רגיש ללחות יכול להישאר מחוץ לאריזה המגינה עליו — מפני שלחות שנלכדה במארז עלולה לגרום לסדיקה בזמן החימום. לפעמים נדרש צילום X-ray כדי לראות את החיבורים, ולעיתים תיקון רכיב דורש ציוד שאין לקבלן ההרכבה. לכן שיטת ההלחמה אינה הערת ייצור; היא חלק מהחלטת הרכיב.
את החלק הזה סוגרים באצוות פיילוט, לא בלוח יחיד. מעבירים מספר לוחות בתהליך ההרכבה האמיתי, משתמשים במתכנת הייצור, בודקים את זמני הצריבה ואת נקודות המגע ומריצים את בדיקת סוף הקו. אחר כך מפעילים יחידות מן האצווה בחום, בקור ובשינויי מתח כדי לחפש תקלות שמגיעות משילוב המארז, הלוח והתהליך. אם אי אפשר לייצר, לבדוק ולתקן את הרכיב באופן עקבי, הוא אינו חלופה גם אם הוא עובד מצוין על שולחן המהנדס.
החלק החמישי מחזיר אותנו לסיבה שבגללה התחלנו: אספקה ותקינה. קל להשקיע חודשים בהסבה ואז לגלות שגם החלופה קרובה לסוף חייה, זמינה רק בכמות קטנה או תלויה באותו מפעל שבו מיוצר הרכיב הישן. לכן בודקים את מספר החלק המדויק, את תוכנית אורך החיים של היצרן, את מנגנון ההודעות על שינוי והפסקת ייצור ואת זמני האספקה בכמויות האמיתיות של המוצר. הבטחה לעשר או חמש־עשרה שנות חיים חשובה, אבל היא אינה הבטחה שהרכיב יגיע בחודש הבא.
גם הביטוי מקור שני דורש חשד בריא. שני יצרנים שונים יכולים להשתמש באותו מפעל פרוסות, באותו קבלן אריזה או באותו חומר גלם, ולכן להיפגע יחד. בזמן מחסור צריך להיזהר במיוחד מן השוק האפור: רכיב שנראה מקורי יכול להיות משופץ, מזויף או כזה שאוחסן בתנאי לחות לא ידועים. חלופה אמיתית כוללת ערוץ הפצה מורשה, עקיבות אצווה ותוכנית בדיקה לקבלת הסחורה, ולא רק מחיר ומועד משלוח באתר.
במוצר רפואי או בטיחותי יש שכבה נוספת. החלפת המיקרו־בקר יכולה לשנות את ניתוח הסיכונים, בדיקות התאימות האלקטרומגנטית או ההחלטה הרגולטורית על המוצר. ה־FDA אינו קובע שכל החלפת MCU מחייבת הגשה חדשה, אבל הוא כן מצפה מן היצרן להעריך אם השינוי עלול להשפיע על הבטיחות או היעילות, לנמק את ההחלטה ולשמור אותה בתהליך בקרת השינויים. כך נסגר גם החלק הזה: לא במספר חלק חדש ברשימת החומרים, אלא בשרשרת ראיות שמחברת בין הרכיב, המוצר והאישור לייצר אותו.
החלק האחרון הוא תוכנית ההוכחה. מתחילים במסמכים ובטבלת פערים, עוברים ללוח פיתוח כדי ללמוד את הכלים ואת הציוד ההיקפי, ואז בונים אבות־טיפוס של המוצר האמיתי. רצוי להשוות לוח עם הרכיב הישן ללוח עם החדש תחת אותו עומס ואותם תנאים. כך אפשר לראות אם שינוי בזמן אתחול, בזרם, ברעש המדידה או בתגובה לפסיקה הגיע מן ההחלפה ולא משינוי אחר שנכנס לפרויקט.
באמצע התהליך מפסיקים לבדוק רק את התרחיש היפה ומתחילים לייצר תקלות בכוונה. מורידים את המתח לאט, מנתקים שעון, מחזיקים קו תקשורת תקוע, מפסיקים עדכון באמצע ומפעילים בחום ובקור. בכל ניסוי מודדים את המשתנה שמתאים לטענה: זרם, זמן פסיקה, רמת Reset, איכות אות או תוכן הזיכרון אחרי האיפוס. LED ירוק אומר שהקוד הגיע לשורה מסוימת; הוא אינו מוכיח שהמוצר שרד את המצב שבגללו נכתבה הבדיקה.
החלק המעניין פה הוא שהחלפה מהירה אינה החלפה חפוזה. צוות שמפריד את לוגיקת המוצר מן הדרייברים, שומר בדיקות אוטומטיות, מוציא נקודות Boot ו־Debug נגישות ומנהל חלופות עוד לפני המחסור יכול להגיב בתוך שבועות במקום להתחיל לחקור בזמן שהקו עומד. אותו מספר פינים הוא רק הרגע שבו הרכיב נכנס ללוח. ההחלפה מסתיימת רק כאשר הוכחתם שהמוצר עדיין מתנהג נכון, מיוצר נכון, עומד בדרישות ואפשר לקנות אותו לאורך החיים שתכננתם.
הזווית ההנדסית
למה זה מעניין
מחסור ברכיב חושף מהר מאוד אם למוצר יש ארכיטקטורה או רק ערימת קוד שמחוברת ל־HAL מסוים. MCU חלופי יכול להתאים במארז ובכל זאת לשנות אתחול, תזמון, התנהגות אנלוגית, דרייברים, ספריות רשת, תהליך הלחמה או תיק תקינה. צוות שמפריד מראש את לוגיקת המוצר מן החומרה, בודק את המתאמים באותו חוזה ומוודא תמיכה אמיתית בשילובי RTOS, PHY ו־IoT יכול להגיב למחסור בלי להפוך את הלקוחות למעבדת הניסוי שלו.
השיחה ממשיכה
תגובות
שאלות, תיקונים ורעיונות שאפשר לבנות מהם משהו.