זכרון

לארדואינו יש כמה סוגים של זכרונות שלכל אחד מאפיינים שונים ושימושים שונים:

  • זכרון הבזק – הוא נקרא גם זכרון FLASH ותפקידו להחזיק את התוכנה שהמשתמש כותב. הזכרון הזה הוא בלתי נדיף כלומר המידע נשאר עליו גם כשמכבים את הארדואינו, לארדואינו אונו יש 32KB של זכרון הבזק שמתוכם 1K שמורים לתוכנת BOOTLOADER . תוכנה זו רצה ברקע ומחכה לפקודת העלאה בכל זמן נתון ולכן אפשר למחוק ולעלות קוד חדש בכל רגע נתון אל הארדואינו. זכרון ההבזק של ארדואינו משמש מעין דיסק קשיח למרות שהוא עובד בטכנולוגיה שונה. אם ננסה להעלות קוד גדול מ-32KB הארדואינו יראה שגיאה ולא יעלה את הקוד.
  • זיכרון דינמי – זהו זכרון RAM ופה קורה כל האקשן. כל המשתנים של התוכנה נמצאים פה והם מתווספים ונמחקים באופן דינמי לפי מה שכתוב בתוכנה. בארדואינו אונו יש לנו 2KB של זכרון דינמי וצריך להיזהר לא לצרוך יותר מהכמות הזאת. הקומפיילר לא יכול לדעת מראש איזה משתנים יתווספו לזכרון לכן הוא נותן להעלות את הסקיצה בכל מקרה אבל אם נצרוך יותר מידי הוא יתנהג בצורה לא צפויה ויקרוס. הזכרון הדינמי הוא נדיף ונמחק ברגע שמכבים את הארדואינו. זכרון RAM הוא המהיר ביותר מבין כל הסוגים וגם היקר ביותר לכן הוא קטן יחסית.
  • זכרון EEPROM – עוד זכרון פנימי קטן שמשמש בעיקר לשמירת הגדרות תצורה שנשמרות בצורה בלתי נדיפה גם כשמכבים את הארדואינו. הטכנולוגיה שלו דומה מאוד לזכרון הבזק אבל השימוש בו שונה, אפשר לכתוב ולקרוא ממנו בכל עת מתוך התוכנה שכתבנו. בארדואינו אונו יש לנו 1KB מהזכרון הזה.

דבר ראשון שצריך לזכור הוא שהספריות שמכניסים בתחילת הקוד צורכות גם הן זכרון  FLASH וגם RAM.

ארדואינו זכרון

כשמכניסים קוד אל סביבת הפיתוח ולוחצים על כפתור בצע אימות מתבצעת קומפילציה וישנו חישוב שמראה בכמה זכרון אנחנו משתמשים. אפשר לראות בתחתית הקובץ שרק הספריה עצמה של GSM לוקחת 30% מזכרון FLASH ו-38% מזכרון RAM , וזה עוד לפני שכתבנו קוד להשתמש בספריה. הספריות שאנשים כותבים לארדואינו חלקן קלות משקל וחלקן כבדות ונראה בהמשך שהן לא חקוקות בסלע ואפשר לשנות את הספריות וגם לכתוב ספריות משלנו לפי הצורך.

עכשיו נראה איך הזכרון מתאכלס לפי סוגי המשתנים שאנחנו משתמשים בהם:

ארדואינו סוגי משתנים

 

כמה טיפוסי משתנים שצריך לשים לב אליהם הם float שצורך 4 בייט long ו- char. שברים תמיד היו קשים לייצוג על ידי המחשב וקשה עוד יותר לעשות עליהם חישובים. שבר הוא ערך בלתי סופי לכן מחשב לא יכול לייצג אותו במדויק והוא מעריך את התוצאה בקירוב, אז אם עושים הרבה חישובים הטעות יכולה לגדול לממדים גדולים.

char הוא תו וטיפוס שנלקח משפת תכנות C . מחרוזות (strings) הן מערך של תווים שנמצא בזכרון ובארדואינו יש לנו את האפשרות ליצור מחרוזות בשתי צורות:

char message[6] = "hello";

כאן אנחנו מקצים מראש 6 תווים למשתנה שנקרא message המקום מוקצה בזכרון וזוהי השיטה הנפוצה בשפת תכנות C עליה מבוססת שפת תכנות ארדואינו. הסיבה שאנחנו מקצים שישה תווים ולא חמישה היא שמחרוזת חייבת להסתיים בסימן 0\ שאומר למחשב שהיא הסתיימה דבר שקורא אוטומטית ואנחנו חייבים לשמר לתו הזה זכרון.

בשפת ארדואינו אפשר להשתמש בטיפוס String בצורה הבאה:

String x = "Hello world";

במקרה הזה הזכרון מוקצה בצורה דינמית וצריך להיות עירניים לכמות שהוא צורך, שימוש ב – String נותן לארדואינו לדאוג להקצאת זכרון באופן אוטומטי וגם נותן לנו לעשות מניפולציות על טקסט שאי אפשר לעשות בשפת C. שימוש כזה הוא מומלץ כל עוד שמים לב לכמות הזכרון החופשי בבקר.

 

הגדרת ערכים קבועים במקום משתנים היא דרך לחסוך בזכרון ולכתוב קוד יותר יעיל. במקרה הבא אנחנו יודעים שכפתור יהיה מחובר לרגל מספר 2 בארדואינו וזה לא הולך להשתנות במהלך התוכנה. אנחנו יוצרים קבוע וקוראים לו button וכך חוסכים בזכרון RAM, כי הקבוע נשמר בזכרון הבזק. כמו שאמרנו RAM הוא יקר תרתי משמע גם במחיר וגם בנדירות (זה בעצם אותו דבר לא?) אז נעדיף בדרך כלל להחזיק מידע בזכרון הבזק.

const int button = 2;

אנחנו מודיעים מראש לקומפיילר שהערך button אינו משתנה והוא עושה לו אופטימיזציה בהתאם וחוסך RAM.

 

הדבר החשוב לגבי זכרון זה להיות עירניים אליו לדוגמה השורה:

Serial.begin(9600);

צורכת 1KB זכרון הבזק  ו- 1.8KB זכרון RAM אז ברור שמשתמשים בה רק שיש צורך בזה. כשהזכרון בארדואינו אונו הולך להגמר אפשר להשתמש בארדואינו גדול יותר כמו ארדואינו מגה , או שאפשר לעבור על הקוד ולבדוק איפה אפשר לקצץ במיוחד בספריות.

שמירת מידע בזכרון FLASH

כמו שאפשר לראות זכרון דינמי (RAM) קטן בהרבה מזכרון הבזק ואם יש לנו חוסר במקום אפשר לשמור מידע גם בזכרון הבזק, המידע הזה יהיה קבוע ולא יהיה ניתן לשינוי אחרי שנעלה את הקוד לארדואינו והגישה אליו איטית יותר לכן מומלץ לשמור בו נתונים רק כשאין ברירה.

לדוגמה כשאנחנו רוצים לשמור מערך גדול עם טקסט להציג על מסך LCD או מערך גדול עם נתונים מספריים אפשר להשתמש בטכניקה הזו. זאת הצורה הפשוטה ביותר לשמור נתונים ולקרוא אותם מהזכרון:

#include <avr/pgmspace.h>

const byte x[] PROGMEM = {34,78,12,89};

void setup() {
  Serial.begin(9600);
  
  Serial.println(pgm_read_byte(x));
  Serial.println(pgm_read_byte(x+1));
  Serial.println(pgm_read_byte(x+2));
  Serial.println(pgm_read_byte(x+3));
}

void loop() {

}

arduino_progmem

 

 

כדי לשמור טקסט ארוך בתוך זכרון FLASH נשתמש באותה טכניקה. פה אנחנו שומרים סיפור ילדים קצר בגודל 500 בייט שזה רבע מגודל הזכרון RAM של ארדואינו אונו בתוך FLASH וקוראים אותו בהדרגה אל המוניטור.

#include <avr/pgmspace.h>

const char text[] PROGMEM = "Once upon a time, a man and his wife had the good fortune to have a goose which laid a golden egg every day.
Lucky though they were, they soon began to think they were not getting rich fast enough.They imagined that if the bird is able to lay
golden eggs, its insides must be made of gold. And they thought that if they could get all that precious metal at once,
they would get mighty rich very soon. So the man and his wife decided to kill the bird.";

void setup() {
  Serial.begin(9600);
  
  for(int i=0;i<450;i++){
    char c = pgm_read_byte(text+i);
    Serial.print(c);
    delay(200);
  }

}

void loop() {}

כדי לבדוק כמה זכרון דינמי (RAM) נשאר פנוי בכל זמן נתון בתוכנה ישנה פונקציה שנקראית freeram. אנחנו קוראים לה במהלך התוכנה כדי לדבג בעיות ולראות איפה אפשר לחסוך בזכרון. זאת הפונקציה:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

 

קריאה וכתיבה אל EEPROM

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

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

בדוגמה הבאה אנחנו צורבים את המספר הסידורי ואת הגרסה:

#include <EEPROM.h> // include eeprom library
char serialNumber[22] = {'I','D',':','1','5','4','3','7','1','4',' ','V','E','R','S','I','O','N',':','1','.','3'}; // create char array
void setup() {
 for (int i = 0; i < 22; i++){ 
 EEPROM.write(i, serialNumber[i]); // write each char to eeprom memory
 }
}

void loop() {}

הזכרון EEPROM מתחיל מ-0 ונגמר ב- 499 בארדואינו אונו, כלומר יש לנו 500 בייט שאפשר לצרוב עליו. תו הוא בייט אחד אז אנחנו צורבים 22 בייט, כאמור int הוא 2 בייט, float הוא 4 בייט אז צריך לשים לב איפה אנחנו נמצאים בזכרון.  במקרה הזה צורבים לזכרון פעם אחת ואז אפשר להכניס לסקיצה אחרת אפשרות לקרוא את המספר הסידורי והגרסה:

#include <EEPROM.h>
char x[22]; // create char array x
void setup() {
 Serial.begin(9600);
 for (int i = 0; i < 22; i++){
    x[i] = EEPROM.read(i); // put in the array eeprom content
 }

 for (int i = 0; i < 22; i++){
   Serial.print (x[i]); // print to screen to verify
 }
}

void loop() {}

עוד דבר מעניין שאפשר לבצע זה לשמור נתונים של תוכנה או משחק בהתאם לפעולת המשתמש. נניח שיצרנו משחק עם תוצאה של נקודות ואנחנו רוצים לשמור את התוצאה הגבוהה ביותר, כשמדליקים את המשחק היא אמורה להופיע בראש המשחק, אם מישהו עבר אותה אז אנחנו מעדכנים אותה בזכרון ה- EEPROM :

#include <EEPROM.h> // include eeprom library
int currentScore = 0; // current score of player
int highestScore;
void setup() {
 byte high = EEPROM.read(0); // read the first byte
 byte low = EEPROM.read(1); // read the second byte
 highestScore = word(high,low); // create word type
 }

void loop() { // during the game 
 if (currentScore > highestScore){ // if player passes highest score
 EEPROM.write(0,highByte(currentScore)); // write first byte to eeprom
 EEPROM.write(1,lowByte(currentScore)); // write second byte to eeprom
 }
 }

int הוא טיפוס שמצריך שני בייט כדי לשמור אותו בזכרון אז אנחנו צריכים לעשות פה כמה פעולות כשקוראים וכותבים לזכרון. כדי לקרוא מהזכרון אנחנו קוראים את הבייט הראשון והשני ואז מצרפים אותם ביחד באמצעות פונקציית word שהיא למעשה unsigned int שזהו מספר מ-0 עד 65535 . כשאנחנו רוצים לכתוב מספר לזכרון אנחנו כותבים את שני הבייטים שמייצגים אותו אחד אחרי השני לשני המקומות הראשונים בזכרון.

כשנמאס לנו מכל הערכים ששמורים בזכרון ה- eeprom ואנחנו רוצים למחוק את כולם נשתמש בקוד הבא כדי למלות אותו באפסים. שימו לב שבזכרון של מחשב כשמוחקים משהו למעשה ממלאים אותו באפסים(פירמוט):

void setup(){
for (int i = 0; i < 500; i++) {
     EEPROM.write(i, 0);
   }
}
void loop(){}

 

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *