גישה ל-FLASH ו-EEPROM ב-AVR החדשים

בניגוד לדגמי ה-AVR ה"קלאסיים" שכל המייקרים מכירים, בדגמים המודרניים כל הזיכרון הבלתי-נדיף (NVM) של המיקרו-בקר, מכל הסוגים, מנוהל באופן אחיד על ידי אותם רגיסטרים. מה זה בעצם אומר, איך כותבים לבד קוד שעובד עם זה, והאם קיבלנו קצת זיכרון SRAM במתנה?

כמעט לכל המיקרו-בקרים בעולם יש זיכרון SRAM לשמירת נתונים שוטפת, וזיכרון FLASH (או משהו דומה) נפרד לשמירה של קוד התוכנה עצמו. בדגמים רבים יש גם זיכרון EEPROM לנתונים. אלה שלושה סוגי זיכרון שונים מאוד זה מזה, כבר מרמת הסיליקון, ובדרך כלל גם הגישה אל כל אחד מהם נעשית באופנים ובכלים שונים. בשימוש שגרתי בארדואינו, למשל, ה-FLASH נסתר מעינינו לגמרי (אלא אם אנחנו משתמשים במאקרו "F" עבור מחרוזות קבועות, כך שלא יתפסו מקום ב-SRAM). ב-SRAM נמצאים המשתנים וה-stack של הקוד ה"רגיל", ואילו אל ה-EEPROM אנחנו ניגשים דרך ספרייה מיוחדת. כשמוותרים על הפונקציות המוכנות של ארדואינו וכותבים קוד קרוב יותר לברזלים, העסק הזה נעשה עוד יותר מורכב.

…מורכב עד כדי כך שמישהו בצוות הפיתוח של AVR החליט לעשות לנו חיים קצת יותר קלים, ולמיטב ידיעתי, בכל שבבי AVR 8-Bit מהדור החדש (סדרות 0 ו-1 ומשפחת Dx) ניהול הזיכרונות מתנהל בצורה שונה. ההבדל המהותי הראשון הוא שעכשיו כל הזיכרון, מכל הסוגים, ממופה על אותו מרחב כתובות (רק מבחינה לוגית כמובן, לא פיזית בסיליקון). הסתכלו למשל על מיפוי הזיכרון בתמונה הבאה, מתוך ה-Datasheet של ATtiny202:

מפת הזיכרון של ATtiny202 וחבריו
מפת הזיכרון של ATtiny202 וחבריו (לחצו לתמונה גדולה)

בין הכתובת 0x0000 לכתובת 0x87FF, או 34815 בבסיס עשר, נמצאים כל הזיכרונות: גם EEPROM, גם ה-SRAM וגם ה-FLASH, כולל תת-האזורים של כל אחד מהם (שחלקם אפילו לא מוזכרים כאן בשמם). היתרון של המיפוי האחיד הזה הוא שככה, אם אני רוצה לקרוא בייט "גולמי" מכל מקום שהוא, אני צריך רק קוד פשוט בסגנון הזה:

uint8_t v;
uint16_t address;
// ...

v = *(uint8_t *)address;

כלומר, עושים casting של הכתובת המספרית (במשתנה address) למצביע לבייט, ושמים ב-v את הערך שהמצביע הזה מצביע אליו. לא צריך רגיסטרים או ספריות כדי להגיע לתוכן ה-EEPROM, למשל. המגבלה היחידה בקריאה כזו היא על אזורים ב-FLASH שהוגדרו בכוונה תחילה כ"נעולים" באמצעות פיוז הגנה. כהערה צדדית, השתמשתי למעלה בכתובת עם 16 ביטים, ויש מספר מיקרו-בקרים שבהם זה לא יספיק כדי "לכסות" את כל מרחב הכתובות. היזהרו עם דברים כאלה.

מה קורה אם מנסים לקרוא מידע מאזור שמור ("Reserved") או לא מוגדר (למשל הכתובת 0x3F00 בדגם שיש לו רק 128 בייט SRAM, לפי האיור למעלה)? ניסיתי ולמרבה הצער, לא מתגלה ככה מאגר סודי של זיכרון שימושי. משהו במיקרו-בקר פשוט מקצץ את הכתובות הגדולות-מדי ומחזיר לנו את המידע מהאזורים הלגיטימיים.

הנושא של כתיבה לזיכרון מורכב יותר, כי בכל זאת, יש הבדלים עצומים בין סוגי הזיכרון השונים. כתיבה ל-FLASH, לדוגמה, חייבת להתבצע ב"דפים" (pages). אז ל-SRAM הגולמי אנחנו יכולים לגשת ישירות, אבל בשביל הזיכרונות הבלתי-נדיפים יש לנו עכשיו מודול חומרה פנימי שנקרא NVMCTRL (קיצור של Nonvolatile Memory Controller). בגדול, ה-NVMCTRL אומר לנו כזה דבר: "אתם תכתבו ערכים ישירות לכתובות הרצויות כאילו זה SRAM, אבל בשלב ראשון הם לא באמת יישמרו שם. אני אקח ואשמור אותם בצד בבאפר משלי, וכשתגמרו סבב של כתיבה, תנו לי פקודה מתאימה ואני אדאג להעתיק את הבאפר לזיכרון האמיתי."

החלק היפה כאן הוא שהממשק ל-EEPROM ול-FLASH זהה לחלוטין. בשני המקרים אנחנו פשוט כותבים את המידע לכתובות הרצויות, ואז אומרים ל-NVMCTRL להשלים את הכתיבה. אותו הדבר נכון אגב גם ל-User Row (אזור קטן נוסף של EEPROM, מעבר לזה ה"רשמי" שמוצהר ב-Datasheet). החלק הפחות-יפה הוא שה-NVMCTRL הוא בכל זאת די טיפש, והבאפר שלו גם כן קצת מוזר, אז אנחנו צריכים לדעת בדיוק מה אנחנו עושים ולנהל את הכתיבה בהתאם.

פקודות הכתיבה הסופית ניתנות על ידי כתיבת ערך מתאים לרגיסטר NVMCTRL.CTRLA. בתור רגיסטר עם פוטנציאל נזק גדול, הוא מוגן ב-Configuration Change Protection, נעילה שאנחנו צריכים לשחרר כל פעם מחדש כי היא נפתחת רק למשך ארבעה מחזורי שעון. יש שני סוגי נעילות: נגד תכנות-עצמי (SPM) ונגד שינוי רגיסטרים רגישים (IOREG). במקרה של NVMCTRL.CTRLA, לא הרגיסטר עצמו רגיש אלא מה שהוא עושה, שזה כתיבה לזיכרון הבלתי-נדיף. אז כדי לשחרר אותו צריך לכתוב את הערך הייעודי לביטול נעילת SPM, שהוא 0x9D, לרגיסטר CCP – ואז מהר-מהר לתת את הפקודה ל-NVMCTRL. הפקודה הנפוצה ביותר היא המספר 0x3, שגורם למחיקה של ה-page הרלוונטי ומיד אחריה כתיבה של המידע מהבאפר הפנימי אל ה-page הזה. אתם זוכרים, כמובן, שזיכרונות כמו FLASH חייבים למחוק לפני שכותבים להם ערכים חדשים…

אם כך, אנחנו יכולים לכתוב בכל פעם רק page יחיד. זה אומר שאם אכתוב בייט אחד לכתובת ששייכת ל-FLASH ואחד לכתובת ב-EEPROM, או אפילו לשני בייטים באותו סוג זיכרון רק ב-pages שונים, ה-NVMCTRL לא יקבל את זה באהבה. אנחנו צריכים לעבוד בצורה מסודרת ולהקפיד על הגבולות (שימו לב שגודל ה-page שונה בכל סוג זיכרון, וגם בין דגמי AVR. המידע הזה מופיע ב-Datasheet). אבל כידוע, ל-EEPROM הרי אין באמת page, אפשר לכתוב אליו ב"רזולוציה" של בייט יחיד. פה ה-NVMCTRL עובד בשבילנו: אנחנו כותבים בייטים ל-EEPROM איך שבא לנו, אחד או יותר (במסגרת ה-page המדומה) וכשנגיד ל-NVMCTRL להשלים את הפעולה, הוא לא יכתוב את כל הבאפר אלא רק את הבייטים הספציפיים שנגענו בהם!

השאלה שהכי עניינה אותי היא איפה בדיוק מסתתר הבאפר הזמני הזה, כי הוא לא חלק מה-SRAM הרגיל. במקרה של ה-ATtiny202, מדובר בלא פחות מ-64 בייטים, בזמן שה-SRAM הרגיל כולו הוא 128 בייטים בלבד. בבדיקות בסיסיות שהרצתי, לא הצלחתי למצוא אותו במרחב הכתובות הרגיל, אם כי יכול להיות שלא חיפשתי מספיק טוב. הנושא עדיין בבדיקה. בכל מקרה הוא לא מתנהג בדיוק כמו SRAM: אם כותבים שני ערכים שונים לאותה כתובת, הערך השני לא מחליף את הראשון, אלא מתבצעת פעולת AND שלהם. כדי לכתוב ערכים חדשים באמת, צריך לנקות את הבאפר (הפקודה 0x4).

הזיכרון הנסתר של הבאפר יכול להיות שימושי מאוד אם יש לנו, לדוגמה, bootloader שמקבל בלוקים של מספרים דרך איזושהי תקשורת וצורב אותם ל-FLASH. בזכות הבאפר, כל ה-SRAM פנוי לפעולות של הקוד שלנו, במקום שנצטרך לשריין חצי ממנו רק לאחסון של הנתונים לקראת הצריבה. האם יש עוד משהו שאפשר לעשות עם הזיכרון הזה, שבמובנים מסוימים הוא Write-only? תסריט אחד שחשבתי עליו הוא יצירת dump לדיבוג. בהנחה שאנחנו לא צריכים את הבאפר לכתיבה רגילה ל-NVM, אפשר לשמור בו מידע שוטף על פעילות הקוד, ורק במקרה הנדיר (בשאיפה) של זיהוי תקלה חמורה, לומר ל-MVNCTRL לכתוב אותו בפועל. אחר כך נוכל לגשת למידע הזה אפילו מבחוץ, בעזרת הצורב וסביבת הפיתוח, לקרוא אותו ולפענח בעזרתו מה קרה. האם יש לכם רעיונות נוספים?

להרשמה
הודע לי על
2 תגובות
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

תובנה די מגניבה – בתכלס microchip מיישרת כאן קו עם כל בקרי ה-ARM למיניהם ושאר הארכיטקטורות היותר מתקדמות. מרחב זיכרון יחיד חוסך הרבה כאב ראש – כל פעולות הכתיבה לפלאש היו מאוד מוזרות לפני כן.
לפני שעושים שימוש מפוקפק ב-SRAM הנוסף – לכמה מחזורי שעון מובטח שהוא ישמור את ערכו? אילו פעולות עלולות לגרום לו להמחק\להתרפרש?