כדי להישאר רלוונטיים בעולם האמבדד התחרותי, דגמי מיקרו-בקרים 8-ביט חדשים מקבלים עוד ועוד יכולות חומרה ש"עוקפות" את המעבד הפנימי המוגבל, מעשירות את ארגז הכלים שלנו כמפתחים וחוסכות רכיבים חיצוניים. בואו נראה כיצד עובדים עם מודול EVSYS שקיים בדור החדש של משפחת AVR, ושמקשר בין מודולים פריפריאליים שונים בלי צורך בתיווך של קוד רגיל או פסיקות.
לצורך הפוסט הזה נתחיל ביישום מינימלי מאוד לדוגמה: יש סיגנל דיגיטלי חיצוני שמגיע לפין X של מיקרו-בקר, ואני רוצה "לשכפל" אותו, כלומר להוציא סיגנל זהה, בפין Y. לא חשוב כרגע למה, רק תזכרו שזה חייב להתבצע בתוך המיקרו-בקר – אסור להעביר חוט חיצוני בין הפינים.
בשיטות של פעם
בקוד רגיל לגמרי, נניח בארדואינו, היינו כותבים בלולאה הראשית משהו כזה:
זה היה עובד, כל עוד הסיגנל איטי יחסית לזמן העיבוד שהפקודות האלה דורשות, וכל עוד הקוד הראשי אינו עסוק במשהו אחר.
כדי לצמצם עיכובים ופספוסים, אפשר להצמיד לפסיקה חיצונית (בארדואינו: attachInterrupt) פונקציה שמעתיקה את הקלט לפלט. שימו לב שבהתאם לדגם המיקרו-בקר, אנחנו עשויים להיות מוגבלים לפיני קלט מסוימים. למשל, בלוח ארדואינו קלאסי שמבוסס על ATmega328P, רק הפינים 2 ו-3 יכולים לעורר פסיקה חיצונית. בשיטה הזו, גם אם הקוד הראשי נמצא באמצע חישוב מסובך וארוך, הפסיקה תקבל קדימות והסיגנל יועתק. מצד שני, אם הסיגנל משתנה לעתים קרובות מדי, כגון גל ריבועי בתדר של כמה מאות קילוהרץ, פונקציית הפסיקה "תשתה" את כל זמן המעבד והקוד הראשי לא יספיק לעשות שום דבר כמעט.
הרעיון של EVSYS
במיקרו-בקרי AVR חדשים יותר, כגון ATmega4809 שהזכרתי בבלוג פעמים רבות, יש מודול פריפריאלי בשם EVSYS, קיצור של Event System, שיכול ליצור חיבור ישיר של סיגנלים בסיסיים בין מודול פנימי אחד לאחר. לא מדובר בהעברה מתוחכמת של נתונים, כפי שעושה DMA במיקרו-בקרים משוכללים יותר. ה-EVSYS דומה יותר להעברת חוט מפה לשם – בדיוק מה שאנחנו צריכים בשביל היישום-לדוגמה שלנו.
בצד אחד של ה-EVSYS יש "Event generators": גנרטורים או מקורות של סיגנלים שה-EVSYS יכול לקלוט: בין השאר, אירועי Overflow של טיימרים, הפלט הפנימי של מודול ה-AC, אירוע השלמת המרה במודול ADC, וגם שינוי בפין קלט. בצד השני יש "Users", מודולים שיכולים לקבל אותות מ-EVSYS: בין השאר טיימרים, מודול ה-ADC, ופיני פלט. אנחנו מגדירים עד 8 ערוצי אירועים ("Channels"), שכל אחד מהם מתחבר לגנרטור, ובוחרים עבור כל User שמעניין אותנו את הערוץ שאליו הוא יתחבר. מרגע שגנרטור שולח אות, הערוץ מעביר אותו אל ה-User, בלי שום פסיקה ושום הפרעה אחרת לקוד הרגיל.
מספר ערוצים יכולים להתחבר לגנרטור אחד, ומספר Users יכולים להתחבר לערוץ אחד. חלק מה-Users (בעיקר טיימרים) הם סינכרוניים, מה שאומר שהסיגנל עשוי להתעכב מחזור שעון אחד עד שיגיע אליהם ויפעיל אותם. אחרים הם אסינכרוניים, וזמן התגובה שלהם אמור להיות מיידי. ב-EVSYS יש גם רגיסטר בשם STROBE, שכל ביט בו מקביל לאחד מה-Channels. אם נכתוב "1" לביט הזה, אז למשך מחזור שעון אחד אותו ערוץ ידמה סיגנל מהגנרטור.
קוד עובד להעתקת סיגנל
לצורך ההדגמה הזו השתמשתי בלוח ATmega4809 Curiosity Nano. פין הקלט שבחרתי הוא PA1 (כלומר, פין 1 של PORT A), ופין הפלט הוא PA2. הבחירה הזו לא לגמרי שרירותית: יש רק שישה פינים שהם Users של ה-EVSYS, והם נקראים EVOUTA עד EVOUTF – אחד לכל פורט (למשל EVOUTC הוא פין מסוים ב-PORT C). אפשר לבדוק מיהם בטבלת ה-Multiplexed Signals בתחילת ה-Datasheet.
בעיקרון, כל פין קלט דיגיטלי יכול לשמש כגנרטור, אבל לא כל ערוץ יכול לגשת לכל פין. למשל, רק ערוצים 0 ו-1 יכולים לגשת לפינים של PORTA. עוד התחכמות שצריך לזכור היא שכאשר מקצים ל-User ערוץ, הערך "0" פירושו "שום ערוץ" – אז חייבים להוסיף אחד למספר הערוץ הרצוי. למשל, כדי להקצות את ערוץ 0, צריך לתת ל-User דווקא את המספר 1.
בהדגמה שלי, כל זה מסתכם בשלוש שורות קוד בתחילת התוכנית, לפני הלולאה הראשית. השורה הראשונה הופכת את פין PA2 לפין פלט, השורה השנייה מקצה ל-User שנקרא EVOUTA (=פין PA2) את ערוץ 0, והשלישית מחברת את ערוץ 0 לגנרטור של פין הקלט PA1. הערך 0x41 מגיע מטבלת "Channel n Generator Selection", עמ' 136 ב-Datasheet.
זהו. מעתה ואילך, המתח (הדיגיטלי) על PA1 ישתקף אוטומטית ב-PA2.
משהו קצת יותר שימושי
הסיבה לפוסט הזה היא צורך שעלה בפרויקט מסוים, לבנות שרשרת של כרטיסים שגם יאזינו ויגיבו לשידור UART, וגם ישמשו "תחנות ממסר" לאותו שידור, כדי שיוכל לעבור מרחקים ארוכים. אם הייתי משתמש בחוט TX יחיד ורציף שכל הכרטיסים מתחברים אליו, האות היה נחלש וסופג יותר מדי הפרעות בדרך. למה לא RS-485? יש הסבר אבל הוא לא חשוב כרגע.
האופציה הראשונה שחשבתי עליה היא רכיב Buffer חיצוני, כגון SN74LVC1G17DBVR . השידור היה נכנס במקביל גם אליו, כדי לעבור "ריענון" וחיזוק לקראת הכרטיס הבא בתור, וגם למיקרו-בקר בשביל עיבוד מקומי. זה היה עובד, אבל אז נזכרתי ב-EVSYS והחלטתי לבדוק אם הוא יכול לעמוד במשימה, וכך לחסוך קצת כסף, קצת הלחמות וקצת מקום על ה-PCB.
הניסוי למעלה הראה שה-EVSYS יכול להעביר סיגנל בין פינים, אבל האם זה יעבוד גם כשפין הקלט מוגדר כ-RX של מודול UART פנימי? חיברתי אותו ואת פין הפלט למתאם USB-to-UART מהמחשב והפעלתי את מודול ה-UART הפנימי המתאים (שימו לב שפין ה-TX של המודול הוא לא פין הפלט שבחרתי ב-EVSYS!) בצורת החיבור הזו, פעולת ה-EVSYS לבדה אמורה לתת לי echo, שיקוף של השידור בחזרה למחשב. זה עבד היטב בטרמינל גם בקצב שידור מהיר של 115,200 באוד. ליתר ביטחון חיברתי את החוטים ללוג'יק אנלייזר כדי למדוד את העיכוב בפועל של הסיגנל דרך המיקרו-בקר:
16 מיליארדיות השנייה בלבד, לא מיידי כמו חוט פיזי אבל הרבה יותר מהיר ממהירות השעון של המיקרו-בקר, ולא נורא בכלל גם בחישוב מצטבר על פני עשרות תחנות ממסר.
כשראיתי שה-echo עובד, הוספתי קוד ללולאה הראשית של המיקרו-בקר, שהאזין לקלט מה-UART ושינה את המצב של הלד המובנה על הלוח בכל פעם שנתקל בתו "0" בשידור. גם זה עבד מצוין, מה שהוכיח ש-EVSYS יכול לעבוד במקביל ל-UART על אותם פינים, ושהרעיון שלי לכרטיסים יעבוד.