טיימרים בארדואינו: מבוא

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

בסדרת פוסטים קצרה זו אנסה להסביר בצורה מובנת – קל זה לא יהיה, תאמינו לי – את נושא הטיימרים בארדואינו (ליתר דיוק, במיקרו-בקרים מסוג ATmega328) וכיצד להתחיל להשתמש בהם. לכן, השאלה הראשונה אליה אתייחס, ואולי החשובה מכולן, היא: מי צריך את זה?

מי צריך טיימרים?

בואו נדבר רגע על הפלט הבסיסי של הארדואינו, דרך הפינים. יש שתי דרכים "להפעיל" פין פלט של ארדואינו: הראשונה היא להשתמש בפונקציות המובנות והמוכרות pinMode ו-digitalWrite, והשניה היא לשנות ישירות רגיסטרים (Registers) של המיקרו-בקר. מבחינת קוד התוכנה, הרגיסטרים מתנהגים כמעט בדיוק כמו משתנים פשוטים שהוגדרו מראש, אך בפועל הם לא סתם שוכנים בזיכרון המיקרו-בקר כמו משתנים רגילים, אלא מחוברים ישירות לחומרה, משפיעים עליה ומושפעים על-ידה.

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

העיקרון הכללי

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

במקביל לפעולות החישוב ששייכות לקוד שלנו, המיקרו-בקר מוסיף, עם כל תנודה של השעון, 1 לרגיסטר (אחד או יותר) שנקרא טיימר. אנחנו יכולים להגדיר ערך עליון לרגיסטר הזה (עד לגבול המקסימלי שמוכתב על ידי מספר הביטים ברגיסטר), ובכל פעם שהרגיסטר יגיע לערך העליון שהגדרנו, הוא יתאפס ויתחיל את הספירה מחדש. כלומר, נניח שיש לנו טיימר בן 8 ביטים, אנחנו יכולים בתיאוריה להגדיר את הערך העליון שלו לכל מספר בין 0 ל-255. אפשר להגדיר גם פסיקה (interrupt) לטיימר, כך שבכל פעם שהטיימר יתאפס, הפסיקה תופעל והקוד שבה ירוץ אוטומטית.

לדוגמה, נניח שהגדרתי בקוד שלי משתנה בשם A שמאותחל ל-0, אני יוצר פסיקה לטיימר שהקוד שלה מגדיל את A ב-1, ואני קובע שהגבול העליון של הטיימר הוא 255. לכן, הטיימר יתאפס אחת לכל 256 תנודות של השעון, ובהנחה שתדר השעון הוא בדיוק 16MHz, כעבור שניה אחת הערך של A יגיע ל-62,500.

כעת, נניח שאני רוצה שהפסיקה תופעל לעתים רחוקות יותר – למשל, במקום כל 256 תנודות של השעון, אני מעוניין שהפסיקה תרוץ אחת לכל 2048 תנודות, מספר שהוא כמובן גדול מדי עבור מגבלת 8 הביטים של הטיימר. אפשר לכתוב קוד בסיסי שיספור 8 פסיקות ורק אז יריץ את הדבר האמיתי, אבל זהו בזבוז של משאבים ויש פתרון טוב יותר, שנקרא prescaler. ה-prescaler מבצע מעין חלוקה של תדר השעון המקורי, והטיימר בעצם מתבסס על החלוקה הזו במקום ישירות על השעון. אם ה-prescaler הוא 1, אז כמובן אין שום הבדל, כי חלוקה באחד לא משנה את התדר. לעומת זאת, אם נגדיר את ה-prescaler כ-8, אז בעיני הטיימר, התדר של השעון יתחלק ב-8 ויהפוך ל-2 מגהרץ. הטיימר יתאפס 7812.5 פעמים בשניה (שזה 62,500 חלקי 8) – בדיוק אחת ל-2048 תנודות של השעון המקורי.

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

הטיימרים שבארדואינו

במיקרו-בקרים מסוג ATmega328, אלה שנמצאים ברוב דגמי Uno, Nano, Pro Mini ו-Duemilanove הישן, יש שלושה טיימרים (ממוספרים 0-2), ועוד אחד (Watch dog) שעובד בשיטה טיפה שונה ולא נדבר עליו כרגע. הטיימרים 0 ו-2 הם למעשה משתנים בגודל 8 ביט כל אחד, כמו בדוגמה למעלה. טיימר 1, לעומת זאת, הוא משתנה בגודל 16 ביט, מה שמאפשר לקבוע את הגבול העליון שלו עד ערך של 65,535.

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

הספריות הסטנדרטיות של הארדואינו עושות שימוש נרחב בשלושת הטיימרים האלה. פונקציות כמו delay, millis, tone, analogWrite או פונקציות להפעלת סרבו מסתמכות על טיימרים שונים, כך שברגע שמתחילים לעבוד ישירות עם הטיימרים, כבר אי אפשר לסמוך על הפונקציות המוכרות. זו עוד סיבה לכאב ראש עבור מתחילים, אבל כאמור, כשהדרישות של הפרויקט מכתיבות דיוק, זהו קורבן קטן יחסית לעומת היתרונות.

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

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

הי עידו, האם יש לך מדריך/קוד מוכן אפילו כיצד לממש בעזרת prescaler חלוקת תדר השעון 16 מגה פי 2 בלוח אונו והוצאת גל ריבועי בGPIO בתדר של 8 מגה?
אודה לעזרתך!

איפה הפוסט הבא בבקשה ?

שלום,
רציתי לדעת באיזה פינים בארדואינו מגה 2560 יש טיימרים.
אשמח אם תחזור אלי למייל.

תודה רבה.