עברנו דרך ארוכה בפוסט הקודם כדי ליצור גל ריבועי בתדר 38KHz על פין 5 של ה-ATtiny85. אם נחבר לפין הזה נורית IR LED, כל חיישן IR קרוב שמכוון לתדר המתאים יקלוט את הבהובי האור המהירים ויזהה אותם כסיגנל שידור. אלא שכדי שנוכל להעביר נתונים של ממש מצד לצד, אנחנו צריכים יותר מאשר זיהוי בלבד, וזה יהיה הנושא שלנו הפעם.
מגבלה עקרונית מס' 1
לפני שניגש לקוד, יש עניין חשוב שטואטא עד כה מתחת לשטיח והגיע הזמן לחשוף אותו: לאיזו מהירות שידור עלינו לצפות ולכוון את עצמנו?
אנחנו יודעים שהמקלטים מחפשים גל בתדר 38KHz, וברור שמפסגה יחידה של הגל הזה הם לא יכולים להסיק כלום על התדר. שתי פסגות, גם אם הרווח ביניהן נכון, עדיין עלולות להיות צירוף מקרים. צריכות להגיע לפחות שלוש פסגות ברצף כדי שנוכל לדעת ברמת ביטחון גבוהה שמדובר בשידור מכוון. בפועל, בין אם מטעמי זהירות או שיקולים פיזיקליים, במפרטי חיישנים שראיתי מבקשים מינימום של עשר פסגות, ומשך זמן דומה לצורך "התאוששות". זה מגביל אותנו לקצב שידור נתונים מרבי של כ-3800 ביטים לשניה, או 475 בייטים לשניה. כלומר, אם בניתם על IR ב-38KHz לשידור של וידאו או אודיו בזמן אמת… שכחו מזה.
מגבלה עקרונית מס' 2
עיון זהיר במפרט שבקישור למעלה, בטבלה בדף 5, מגלה אילוץ חשוב נוסף: זמן התאוששות דיפרנציאלי. אחרי שידור שנמשך 10-70 מחזורים/פסגות (או 10-35 בחלק מהדגמים), צריך כאמור לתת לחיישן לפחות 10 מחזורים של "שקט" – אבל אחרי שידור של יותר מ-70 (או 35) מחזורים, החיישן זקוק להפסקה שאורכה פי ארבעה (או עשרה) מאורך השידור!
המשמעות היא שאם אנסה לשדר ביטים בצורה הכי בסיסית, כלומר 1=שידור ו-0=אין שידור, ואפילו באורך מינימלי של 10 פסגות (או זמן שווה-ערך) לכל ביט, רצף תמים של ארבעה ביטים שערכם 1 עלול לשבש לחלוטין את קליטת הנתונים הבאים. כדי למנוע שיבושים כאלה, המשדר חייב כך או אחרת להחליף כל הזמן ובמהירות בין מצב שידור למצב אי-שידור. בדף הראשון של מסמך זה מוצגות שלוש דרכים להעברת ביטים תוך מילוי הדרישה הזו.
מעניין לציין ששיקולים דומים קיימים גם בשידורי RF דרך ה"קיטים" הבסיסיים והזולים (315/433MHz) שנמכרים בזוגות משדר-מקלט באיביי ובאתרים סיניים שונים. בקיטים אלה המקלט מצפה לשינויים מהירים, וכאשר השידור אחיד לאורך זמן רב מדי לטעמו, הוא מתחיל לשנות באופן אקטיבי את הרגישות שלו עד לרמה שרעש סביבתי רגיל נתפס כשידור.
בהמשך אציג פרוטוקול משלי להתמודדות עם הדרישות האלה, אך בינתיים נחזור ל-ATtiny85 ונראה איך מנהלים הפעלה וכיבוי של השידור.
לפני הספירה, והספירה
כפי שהזכרתי בפוסט הקודם, התפעול של פין 5 על ידי מודול A של טיימר 0 נעשה באופן שלחלוטין אינו תלוי במה שהקוד ה"רגיל" עושה, ואף אינו זקוק לפסיקות (Interrupts). שני הביטים COM0A0/1 ברגיסטר TCCR0A קובעים מה יקרה לפין ברגע שתהיה התאמה בין ערך הטיימר לערך A שהוגדר, וזאת לפי הטבלה הבאה:
ליצירת הגל הריבועי קבענו את ערכי הביטים לפי השורה השניה, שגורמת ל-Toggle, החלפת ערך הפלט בפין לחילופין. כדי להפסיק את השידור הזה, כל שעלינו לעשות זה להחליף את הערך של COM0A0 מ-1 ל-0 (כפי שמוצג בשורה הראשונה).
אם נרצה להגביל את השידור ל-10 פסגות של הגל, אנחנו צריכים בקרה נוספת. זו יכולה להסתמך על תזמון (למשל באמצעות הטיימר השני) או על ספירה, ואת זו אפשר לבצע עם טיימר 0 עצמו – הפעם בעזרת פסיקה. אחת הפסיקות של הטיימר מופעלת (אם נבחר להפעיל אותה) בכל פעם שהמונה שלו מגיע לערך שהגדרנו עבור A. זהו כמובן גם הרגע שבו הגל עובר ממצב למצב, ולכן אם נשתמש בפסיקה כדי לספור עשרים מעברים ולהפסיק את השידור לאחר מכן, מובטח לנו שנקבל שידור בן עשר פסגות.
כדי להפעיל את הפסיקה, עלינו לכתוב 1 לביט OCIE0A (קיצור של Output Compare Interrupt Enable) שברגיסטר TIMSK. כמובן, צריך גם לכתוב את הקוד של מה שהפסיקה הזו תעשה. לשם כך צריך לכלול בקוד את הספריה avr/interrupt.h, ולכתוב פונקציה בעלת מבנה מיוחד:
ISR(TIM0_COMPA_vect) { }
זו לא פונקציה שגרתית של שפת התכנות, אלא מבנה ייחודי לקומפיילר של AVR, שיוצר "רוטינת שירות לפסיקה" (Interrupt Service Routine) ומניח מצביע אליה במקום שהמיקרו-בקר פונה אליו כאשר מתרחש האירוע המתאים – במקרה זה אירוע TIM0_COMPA, כלומר התאמה של טיימר 0 למונה A. בתוך ה-ISR הזו נשים קוד שסופר לאחור שוב ושוב מעשרים לאפס ומחליף את המצב של הביט COM0A0. לא לשכוח גם הגדרה מפורשת של הפלט כ-LOW בסוף כל רצף שידור, כדי למנוע טעויות ותקלות פוטנציאליות בעתיד, וקריאה לפונקציה sei בסיום ההגדרות כדי לתת אור ירוק לפסיקות בתוכנית. הנה התוכנית המלאה:
#include <avr/io.h> #include <avr/interrupt.h> #define MAX_TOGGLE_COUNT 20 volatile uint8_t toggleCount = MAX_TOGGLE_COUNT; int main(void) { DDRB = 1; // Pin PB0 output TCCR0A = (1 << WGM01) + (1 << COM0A0); OCR0A = 218; TCCR0B = 1 << CS00; TIMSK |= (1 << OCIE0A); sei(); // Enable interrupts while(1) {} } ISR(TIM0_COMPA_vect) { toggleCount--; if (!toggleCount) { PORTB &= 254; // Output off if (TCCR0A & (1 << COM0A0)) TCCR0A &= ~(1 << COM0A0); else TCCR0A |= (1 << COM0A0); toggleCount = MAX_TOGGLE_COUNT; } }
ובואו נראה מה מתקבל בפלט:
למראית עין, לפחות, זה מושלם. בפעם הבאה נבדוק אם גם חיישן ה-IR חושב שזה מושלם, ואיך לשלוח על בסיס המנגנון הזה נתונים כרצוננו.