איך קומפיילרים יכולים לקצר לכם את החיים

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

רקע

בימים אלה אני עובד על הפרויקט ה"רציני" הראשון שלי עם מיקרו-בקר ממשפחת PIC של Microchip, ולא סתם אלא דגם שמעולם לא השתמשתי בו קודם. מטבע הדברים אני עובד באופן הדרגתי וזהיר: בניתי את המערכת קודם כל על בסיס ארדואינו שאני מכיר, כדי לוודא שהלוגיקה פועלת כמו שצריך, ובצורה כזו שהצד הלוגי של התוכנית מופרד ומבודד ככל האפשר מהחומרה הספציפית. הרעיון היה שברגע שזה יעבוד, אוכל להעתיק את הקוד לסביבת הפיתוח של Microchip, ורק לשנות את מעט הנקודות שבהן צריך להתייחס ישירות לחומרה (כגון טיימרים, פורטים של פלט/קלט וכו').

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

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

מה קרה שם

הסתכלו בכל datasheet של מיקרו-בקר מצוי ותראו שהרבה רגיסטרים מחולקים ל"דגלים" נפרדים, ולכל אחד מהדגלים האלה יש שם משלו. הנה דוגמאות אקראיות מה-datasheet של מיקרו-בקרים ממשפחת AVR וממשפחת PIC (לחצו להגדלה):

דוגמה לשמות של ביטים ברגיסטרים (מסומנים בחץ: BODSE בעליון ו-CCP5MD בתחתון)
דוגמה לשמות של ביטים ברגיסטרים (מסומנים בחץ: BODSE בעליון ו-CCP5MD בתחתון)

הקומפיילר שמיועד לאותו מיקרו-בקר מכיר, וליתר דיוק מגדיר מראש בספריות הליבה שלו, את כל השמות האלה כדי שהמתכנתים יוכלו להשתמש בהם בקלות. מה שלא ידעתי זה שהמשמעות של השמות שונה לגמרי בקומפיילרים השונים! בקומפיילר של AVR, כל שם כזה הוא ערך מספרי קבוע שמציין את המיקום של הביט ברגיסטר. למשל, הדגל BODSE בדוגמה למעלה הוא קבוע שערכו 2 (הביט השלישי מימין, מתחילים לספור מ-0). בקומפיילר XC8 של Microchip, לעומת זאת, כל שם כזה הוא משתנה ש"מחובר" ישירות לביט ומה שכותבים אליו או קוראים ממנו הוא הערך של הביט הספציפי!

ב-AVR, כדי לתת ערך 1 לדגל BODSE ברגיסטר MCUCR, צריך לעשות תרגיל קטן שנראה בערך ככה:

MCUCR |= 1 << BODSE;

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

MCUCR |= 4;

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

ב-XC8, לעומת זאת, כדי לתת ערך 1 לדגל CCP5MD ברגיסטר PMD1, כותבים פשוט ככה:

CCP5MD = 1;

זאת אומרת, כשניסיתי לשנות דגל ברגיסטר של PIC בשיטה של AVR, נניח ככה:

PMD1 |= 1 << CCP5MD;

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

ראו הוזהרתם

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

למי שמעוניין, הנה המדריך למשתמש בקומפיילר XC8, ועכשיו אדע לבדוק טוב מראש גם את הקומפיילר ל-STM8

תוספת: זמן לא רב אחרי שכתבתי את זה, נתקלתי בעוד עניין שעלול להכשיל משתמשים תמימים. כשמגדירים משתנה מטיפוס הנתונים char, ברוב הקומפיילרים ההנחה המובלעת היא שמדובר ב-signed char, כלומר בעל סימן, שיכול לקבל גם ערכים שליליים. לא ב-XC8 – הוא מניח שזהו unsigned char. זהו עניין בעייתי במיוחד, כי בהרבה מקרים של שימוש סביר ההבדל לא יורגש בכלל, וכשהבאג יצוץ בסופו של דבר יהיה קשה להבין מאיפה הוא בא. ליתר ביטחון, במיוחד אם עובדים עם סביבת פיתוח שפחות מכירים, כדאי – שוב – לא להניח הנחות ולכתוב הכל במפורש.

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