אחרי שראינו איך בוחרים את תדר המתנד הפנימי של המיקרו-בקר הוותיק MSP430G2553 ומהבהבים בלד על לוח ההערכה שלו, בואו נלמד איך עובדים עם מודול כלב השמירה (Watchdog Timer), כקו הגנה אחרון לקוד שלנו אבל בעיקר כמקור זול ובסיסי לפסיקות חוזרות.
למי ששכח: Watchdog Timer (או בקיצור WDT) הוא טיימר עצמאי ולא ממש מדויק שקיים בכל מיקרו-בקר, ושתפקידו לבצע אתחול של המיקרו-בקר כעבור פרק זמן מסוים אם הוא לא מקבל עדכונים שוטפים מהקוד שהכול בסדר. זהו מעין ביטוח למקרה של כשל קטסטרופלי בקוד, אם כי הוא לא מהווה פתרון מושלם וצריך להשתמש בו בזהירות ובחוכמה. בהרבה מערכות, בגלל היבריס של המתכנתים או מכל סיבה אחרת, לא מפעילים את ה-WDT בכלל בתפקיד זה. אז למה לא לנצל את החומרה שלו בדרכים אחרות? ואכן, כמעט כל WDT בעולם מסוגל גם לעורר פסיקה רגילה במקום אתחול, או להעיר את המיקרו-בקר ממצב שינה.
בקוד ברירת המחדל שלנו בסביבת הפיתוח Code Composer Studio, נתקלנו כזכור בשורה המוזרה הזו:
WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer
ההערה מסבירה מה הפקודה עושה, וזה בסדר גמור כי זה התפקיד של הערות, אבל לא ברור מהקוד איך זה קורה. ובכן, הרגיסטר WDTCTL הוא הרגיסטר ששולט בפעולת ה-WDT. הקבוע WDTHOLD מתייחס לביט השביעי ברגיסטר, שאומר אם ה-WDT פעיל (0) או מושהה (1), והקבוע WDTPW הוא ה"סיסמה" – הערך 23040, או בהקסדצימלי 0x5A00 – שחייבים לכתוב לרגיסטר בכל פעם שרוצים לשנות בו משהו. שימו לב שזה מספר שחורג מבייט יחיד, ואכן ארכיטקטורת ה-MSP430 היא של 16 ביט אז זה די טבעי.
הסיסמה הזו נחוצה כדי למנוע מצב שאיזה פוינטר תועה בקוד ישבש את פעולת ה-WDT עצמו. למעשה, אם רק ננסה לכתוב משהו לרגיסטר בלי הסיסמה הנכונה, כפי שפוינטר תועה עלול לעשות, המיקרו-בקר יעבור אתחול מיידי.
ה-WDT כמנגנון בטיחות
אם כיבינו את ה-WDT עם הפקודה שלמעלה, לא צריך לעשות עוד כלום. אבל אם השארנו אותו פעיל, אז ביט 3 ברגיסטר, שנקרא WDTCNTCL, הוא הביט שנכתוב אליו "1" כדי להרגיע את כלב השמירה שהכול בסדר בינתיים, ושיתחיל לספור מהתחלה. ביחד עם הסיסמה, הפקודה תיראה פשוט כך:
WDTCTL = WDTPW | WDTCNTCL;
וזו הפקודה שצריך להספיק ולבצע בקוד כל פעם מחדש, לפני שה-WDT מסיים את הספירה שלו. כמה זמן זה? קודם כל נבחר מקור שעון עבור ה-WDT, באמצעות הביט השני ברגיסטר, ששמו WDTSSEL: הערך "0" יחבר אותנו ל-SMCLK שפגשנו בפוסט הקודם, ואילו "1" לשעון ACLK שפועל בתדרים נמוכים (עם גביש חיצוני או מתנד פנימי). אחרי שבחרנו מקור שעון, אנחנו מחלקים את התדר שלו באמצעות צמד הביטים [1:0] ברגיסטר, שנקראים WDTISx: ערך "00" (ברירת המחדל) יחלק את השעון ב-32768, ערך "01" ב-8192, "10" ב-512 ו-"11" ב-64.
לדוגמה, אם שעון המערכת שלנו הוא 16MHz, ואנחנו לא נוגעים בברירות המחדל, ה-WDT ייכנס לפעולה בתדר שלו חלקי 32768, שזה כ-488Hz, כלומר טיפה יותר מ-2 אלפיות השנייה. זה זמן די קצר מבחינת הדברים שהמיקרו-בקר יספיק בכלל לעשות, ולכן בדרך כלל עדיף להתבסס על השעון ACLK, שאיתו נוכל להגיע אפילו לשניות ספורות. כברירת מחדל, ACLK ניזון מגביש חיצוני, אז אם אין לנו כזה במערכת נצטרך לשנות את ביט 5 ברגיסטר BCSCTL3 ל-1. אז השעון ACLK יוזן ממתנד פנימי בתדר 12KHz (אפשר לחלק גם אותו בפקטור מסוים, למי שרוצה).
ה-WDT כמקור לפסיקות חוזרות
עד כאן הכול היה ברור ומובן, נכון? אין ספק. אז בואו נגיד ל-WDT לעורר פסיקה רגילה במקום אתחול, וניעזר בה כדי להבהב לאט בלד . התהליך במלואו יהיה כזה:
- נעצור את ה-WDT
- נכתוב את ההגדרות לפין הפלט של הלד
- נגדיר את ACLK כך שיסתמך על המתנד הפנימי האיטי (12KHz)
- נכוון את ה-WDT כך שיסתמך על ACLK
- נחלק את כניסת השעון של ה-WDT ב-8192
5.1 לשם כך, חייבים לאפס באותה פעולה גם את המונה של WDT! - נכתוב את פונקציית הפסיקה שמשנה את מצב הלד
- נוסיף לקוד הראשי לולאה שתהבהב בלד אחר, כדי לוודא שהכול באמת תקין
- נאפשר את פסיקת ה-WDT (ביט 0 ברגיסטר IE1)
- נאפשר פסיקות באופן כללי
- נפעיל מחדש את ה-WDT
כמו ב-AVR (ובניגוד ל-PIC הפשוטים), לכל מקור פסיקה ב-MSP430 יש וקטור (מצביע לפונקציה) נפרד משלו. כלומר, בסעיף 6 למעלה אנחנו צריכים לכתוב פונקציית פסיקה ספציפית שתשרת את אירוע ה-WDT. בסביבת הפיתוח CCS היא תיראה כך:
#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR(void) {
P1OUT ^= 1; // Toggle P1.0, Red LED on launchpad
}
את רשימת הווקטורים שהקומפיילר מכיר, כמו WDT_VECTOR בקוד למעלה, מוצאים בקובץ ההגדרות של המיקרו-בקר הספציפי, שנקרא במקרה שלנו msp430g2553.h.
סעיף 9, הפעלת פסיקות באופן כללי, מתייחס לביט 3 (שנקרא GIE) ברגיסטר SR. הבעיה היא שהרגיסטר הזה אינו נגיש כמו האחרים. הדרך הכי נוחה שמצאתי בינתיים להפעיל את הפסיקות היא להשתמש בכאילו-פונקציה מוכנה מראש, שמתחבאת בקובץ הסמוי intrinsics.h:
__enable_interrupt();
ועוד דבר קטן: אפשר ורצוי לאחד פעולות שנעשות על הרגיסטר WDTCTL, כמו הפעולות בסעיפים 4-5. לא צריך לכתוב פקודה נפרדת לכל אחד.
את הקוד המלא שעושה את כל זה, והוא הרבה יותר קצר מהפוסט, שמתי כאן ב-Pastebin. בפוסט הבא, כדי שנוכל להתחיל לדבר עם המיקרו-בקר בצורה יותר משמעותית, ננסה להפעיל תקשורת UART.