הגדרות רגיסטרים ופסיקות למימוש של תקשורת UART בסיסית בלוח ה-MSP430G2 Launchpad הקלאסי.
פרוטוקולי התקשורת הטוריים הנפוצים בעולם האמבדד, כגון UART, SPI, I2C וכל מיני וריאנטים שלהם, דומים זה לזה במובנים רבים. לכן, כדי לפשט את התכנון ולחסוך בשטח הסיליקון, במיקרו-בקרים רבים יש מודולי תקשורת גנריים (או אם להשתמש במונח השיווקי יותר, "אוניברסליים") שמסוגלים לעבוד בשני פרוטוקולים או יותר – אם כי לא בו-זמנית כמובן.
במיקרו-בקר MSP430G2553 שאיתו אנחנו עובדים כאן יש מודול תקשורת טורית אחד ויחיד שנקרא USCI – Universal Serial Communication Interface, וכל תקשורת טורית שמתבצעת בחומרה חייבת לעבור דרכו. המשמעות היא שאי אפשר להפעיל בחומרה תקשורת UART ו-SPI, למשל, בבת אחת. אם פרויקט מחייב שני ערוצי תקשורת כלשהם, נצטרך לממש לפחות אחד מהם בתוכנה, או לבחור מיקרו-בקר משוכלל יותר.
ה-Datasheet לא מנוסח באופן הכי ברור בעולם, אך בסופו של דבר, אם מתחילים מערכי ברירת המחדל של הרגיסטרים השונים, ההפעלה של ה-USCI במצב UART דורשת מעט מאוד פעולות.
הכנת ה-USCI לתקשורת UART
ראשית, אנחנו צריכים להגיד לפיני הקלט/פלט הרלוונטיים (פין P1.2 ל-TX ופין 1.1 ל-RX) שהם עוברים לשליטת ה-USCI. את זה נעשה על ידי כתיבת הערך "1" לביטים 1 ו-2 גם ברגיסטר P1SEL וגם ברגיסטר P1SEL2 (אלה הרגיסטרים שקובעים, במשותף, את השיוך של כל פין בפורט 1).
// Associate P1.1 and P1.2 to USCI
P1SEL |= 6;
P1SEL2 |= 6;
אחרי זה, נאמר ל-USCI מה מקור השעון שלו. בדרך כלל, השעון שנקרא SMCLK (שיכול להיות זהה לשעון הראשי) יתאים לנו. כדי להשתמש בו, צריך לכתוב "10" או "11" לביטים 6:7 ברגיסטר UCA0CTL1. נשתמש באופרטור של השמה עם פעולת or ולא ב"=" בלבד, כדי לא לשנות בטעות ביטים אחרים.
UCA0CTL1 |= 0x80;
עכשיו צריך לקבוע את ה-baudrate, קצב השידור. כדי לעשות את זה צריך לדעת קודם כל מה תדר השעון. נניח שקבענו, כמו בפוסט הזה, שתדר שעון המערכת יהיה 16MHz. נחלק את המספר הזה ב-baudrate הרצוי לנו, למשל 9600, נעגל ונקבל 1667. את הערך הזה "נפרק" לשני בייטים נפרדים שנכתוב לרגיסטרים UCA0BR0 ו-UCA0BR1. זה, אני חייב לציין, קצת מוזר, כי הרי הארכיטקטורה היא 16-ביט ולכאורה הפעולה הכי טבעית היא לכתוב את המספר המלא בבת אחת לרגיסטר בגודל 16-ביט. אבל לא.
UCA0BR0 = 131; // 1667 % 256
UCA0BR1 = 6; // 1667 / 256
ולסיום, צריך לכתוב "0" לביט מס' 0 ברגיסטר UCA0CTL1 (כן, זה שראינו קודם) כדי "להתניע" את המודול:
UCA0CTL1 &= ~1;
מרגע זה והלאה, אם ה-USCI אינו עסוק בשידור קודם, ונכתוב בייט כלשהו לבאפר השליחה UCA0TXBUF, הבייט הזה יישלח החוצה בשידור UART. איך יודעים מתי ה-USCI עסוק? יש שתי אפשרויות: כאשר ביט 0 (שנקרא גם UCBUSY) ברגיסטר הסטטוס UCA0STAT הוא "1", סימן שהמודול עושה משהו אחר כרגע. אבל המשהו האחר הזה יכול להיות גם קליטה, לא רק שידור. בנוסף, הביט UCA0TXIFG (ביט 1) ברגיסטר הפסיקות IFG2 הופך ל-"1" ברגע שבאפר השידור מתרוקן ואפשר לכתוב אליו ערך חדש. זהו הביט שמשויך גם לפסיקה של השידור.
באופן דומה, הביט UCA0RXIFG (ביט 0) ב-IFG2 יהפוך ל-"1" ברגע שיהיה בייט זמין שהגיע דרך RX, והרגיסטר UCA0RXBUF יכיל את הערך של הבייט הזה.
לפני שנכתוב קוד שמשתמש בכל זה, כמה מילים על הטרמינל המובנה בסביבת הפיתוח Code Composer Studio. נכון שלא חסרים לנו טרמינלים, אבל אם אנחנו כבר שם, בואו נפתח אותו. נבחר בתפריט View->Terminal, ונקבל חלונית קטנה שתצטרף לסביבת העבודה. זה עוד לא החיבור הספציפי ללוח שלנו. בשביל זה נלחץ על האייקון של "Open New Terminal" (או Ctrl+Alt+Shift+T),
ובחלון שייפתח נבחר את כל הפרמטרים הרלוונטיים, למשל:
אם לא יהיו תקלות, תופיע בחלונית הטרמינל לשונית חדשה עם החיבור שלנו, שמציגה את הקלט מהלוח, ובלחיצה על אייקון "Toggle Command Input Field" תופיע למטה בנוסף תיבת טקסט שדרכה נוכל לשלוח תווים אל הלוח.
על לוח ה-Launchpad עצמו יש שני ג'אמפרים שגם רלוונטיים לתקשורת UART בין הלוח למחשב, אבל אם הגעתם עד אליהם, אתם כנראה כבר יודעים מה אתם עושים.
ועכשיו לקוד. כדי להיות מקצועיים ניעזר בפסיקות, גם של הקליטה וגם של השידור. בכל פעם שיתקבל בייט, נשדר בחזרה את המחרוזת "Got x" (כש-x הוא הבייט שהתקבל), בלי להפריע לריצה של הקוד הראשי שסתם יהבהב בלד. כדי לא להסתבך יותר מדי, נניח שהבייטים המתקבלים לא סמוכים מדי אחד לשני ויש מספיק זמן לשלוח את כל התשובה בלי שנתונים חדשים יידרסו.
תוך כדי בניית הקוד מצאתי מלכודת. ה"דגל" UCA0TXIFG שהזכרתי קודם, שאומר שבאפר השידור ריק, הוא "1" כברירת מחדל (כי אכן באפר השידור ריק בתחילת התוכנית). אבל זה גורם לפסיקה להתעורר מיד, ואם לא נכתוב משהו לבאפר, היא תופעל שוב ושוב ותתקע את התוכנית. הדבר הנכון לעשות הוא לא לאפשר בכלל את הפסיקה של השידור אלא רק כשאנחנו מתחילים בפועל, ובסיום השידור להחזיר את המצב לקדמותו.
דבר דומה קורה עם הדגל UCA0RXIFG, של הקליטה. כל עוד לא קראנו את המידע מהבאפר, הדגל הזה נשאר "מורם", אז אם פונקציית הפסיקה שנכתוב ל-RX לא קוראת בעצמה את המידע ומשאירה את הפעולה הזו לקוד הראשי, היא חייבת להוריד בעצמה את הדגל כדי שהוא לא יפעיל אותה שוב בלולאה אינסופית.
ולסיום, התברר שהטרמינל של CCS די טיפש, ושולח אוטומטית תו או תווי שורה חדשה בסיום המחרוזת, כך שהקוד שכתבתי (שלא ציפה לתווים אלה) לא עבד בו כמו שצריך. בתוכנת טרמינל אחרת לא הייתה בעיה.