איך מגדירים רכיב חדש ב-Device Tree

איך מגדירים רכיב חדש ב-Device Tree

בעולם המערכות המשובצות מבוססות Linux של היום, הוספת רכיב חדש ללוח היא משימה שחוזרת בכל פרויקט. חיישן חדש על אפיק I2C, שבב זיכרון על SPI, מרחיב GPIO, או רכיב תקשורת – בכל אחד מהמקרים, המערכת לא תדע על קיומו עד שמישהו יגיד לה. המקום שבו אומרים לה הוא ה-Device Tree, וזו נקודה שמפתחים חדשים בתחום נוטים להיתקע בה: הקוד קיים, הרכיב מחובר, ובכל זאת שום דבר לא קורה. הסיבה כמעט תמיד היא שההגדרה חסרה, שגויה, או נמצאת במקום הלא נכון. בחברת TandemG אנו עוסקים בהתאמת מערכות Embedded Linux לחומרה, וההגדרה הנכונה של רכיבים בעץ היא חלק שגרתי מהעבודה.

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

מה ה-Device Tree עושה בכלל

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

ה-Device Tree הוא הפתרון לכך: מסמך שמתאר ל-Kernel את מבנה החומרה של הלוח. מה מחובר, לאיזה אפיק, באיזו כתובת, ובאילו פרמטרים. ה-Kernel קורא אותו בזמן האתחול ויוצר לפיו את מפת ההתקנים.

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

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

היכן ההגדרה נמצאת

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

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

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

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

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

ההגדרה הבסיסית: מה חייב להיות בה

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

&i2c1 {

    status = "okay";

    clock-frequency = <100000>;

    my_sensor: sensor@48 {

        compatible = "vendor,part-number";

        reg = <0x48>;

        status = "okay";

    };

};

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

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

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

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

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

המאפיינים שרכיבים בדרך כלל דורשים

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

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

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

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

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

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

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

הנה דוגמה מורחבת שמשלבת כמה מהם:

&spi0 {

    status = "okay";

    my_device: device@0 {

        compatible = "vendor,part-number";

        reg = <0>;

        spi-max-frequency = <1000000>;

        interrupt-parent = <&gpio2>;

        interrupts = <5 IRQ_TYPE_EDGE_FALLING>;

        reset-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;

        vdd-supply = <&reg_3v3>;

        status = "okay";

    };

};

התהליך המעשי, צעד אחר צעד

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

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

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

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

צעד רביעי: לכתוב את ההגדרה. בקובץ הלוח או בשכבת-על, ולא בקבצי היצרן.

צעד חמישי: לבנות ולצרוב. במערכות שמבוססות Yocto, זה כולל בנייה מחדש של ה-Device Tree ועדכון התמונה. חשוב לוודא שהקובץ שנצרב הוא זה שהתעדכן, מפני שטעות נפוצה היא לערוך קובץ אחד ולצרוב אחר.

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

איך מוודאים שההגדרה עבדה

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

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

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

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

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

הטעויות הנפוצות

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

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

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

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

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

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

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

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

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

מתי ההגדרה אינה מספיקה

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

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

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

כשהרכיב מוגדר נכון והתזמון נכשל. במערכות שיש בהן דרישות תגובה, הגדרה תקינה אינה מבטיחה שהתזמון יעמוד בדרישה. במערכות Real-Time Embedded יש לאמת זאת במדידה בנפרד.

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

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

הקשר לניהול הפרויקט לאורך זמן

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

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

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

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

שאלות נפוצות

מה בעצם ה-Device Tree עושה?

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

היכן כותבים את ההגדרה של רכיב חדש?

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

מהו המאפיין החשוב ביותר בהגדרה?

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

איך יודעים אילו מאפיינים הרכיב דורש?

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

מהי הטעות הנפוצה ביותר?

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

איך מאמתים שההגדרה נקלטה?

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

מה עושים כשאין דרייבר לרכיב?

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

האם הגדרה תקינה מבטיחה שהרכיב יעבוד כנדרש?

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

כיצד שומרים על סדר לאורך חיי המוצר?

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

היתרון של TandemG: התאמת המערכת לחומרה מתוך היכרות עם שני הצדדים

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

בחברת TandemG אנו עוסקים גם בפיתוח חומרה וגם בהתאמת מערכות Embedded Linux לחומרה, ולכן הגדרת הרכיבים נעשית מתוך ידיעה ולא מתוך הנחה. אנו מלווים את התהליך מהבדיקה שדרייבר בכלל קיים לרכיב, דרך ההגדרה בעץ והתאמת תצורת ה-Kernel, ועד האימות על החומרה במדידה. במערכות שכוללות רכיבי Real-Time Embedded לצד Linux, אנו מוודאים שהתזמון עומד בדרישות ולא רק שהרכיב נזהה, ובמוצרים מחוברים אנו משלבים זאת בתכנון ה-IoT מקצה לקצה הכולל.

צוותי המהנדסים שלנו פועלים כ-AI-powered developers ומשתמשים בכלי AI מתקדמים כמו Claude ו-GitHub Copilot כדי לקצר את תהליכי הפיתוח, לשפר את איכות הקוד, ולהאיץ את סקירות הארכיטקטורה, תוך אימות של כל הגדרה מול תיעוד הדרייבר ומול סכמת הלוח.

הפרויקט הבא שלכם מתחיל בשיחה

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

בחברת TandemG אנו מלווים חברות בהתאמת מערכות Embedded Linux לחומרה, מבדיקת זמינות דרייברים והגדרת הרכיבים בעץ, דרך התאמת תצורת ה-Kernel, ועד אימות על החומרה ותחזוקה לאורך חיי המוצר. צרו קשר לייעוץ ראשוני.

הקמה ושיווק