פענוח ושחזור קוד של שלט למאוורר תקרה

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

שלט של מאוורר תקרה Hyundai. לחקות באמצעות ארדואינו
שלט של מאוורר תקרה Hyundai. לחקות באמצעות ארדואינו

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

השלט הרחוק המקורי משני צדיו (לחצו להגדלה)
השלט הרחוק המקורי משני צדיו (לחצו להגדלה)

הדבר הראשון שאנחנו יודעים הוא ששידורי IR כאלה הם מאופננים, כלומר המידע "רוכב" על הבהובי ON/OFF מהירים מאוד של נורות ה-LED הקטנות שבחזית השלט. כמה מהירים? כמעט תמיד 38,000 מחזורים בשנייה, כלומר הסטנדרט הנפוץ 38KHz (קילוהרץ), אבל לעתים יש גם 56KHz או הפתעות נדירות עוד יותר. אז בואו נמדוד.

לצורך המדידה לקחתי פוטוטרנזיסטור ל-IR, מדגם Everlight PT-534-6B. אם נפשט, זהו רכיב שנותן לזרם חשמלי לעבור דרכו כשהוא קולט IR (בלי קשר לאפנון), וחוסם את הזרם כשלא. כיוון שאנחנו לא יודעים מהו אורך הגל (ה"צבע") המדויק שיוצא מהשלט, היתרון של הפוטוטרנזיסטור הספציפי הזה הוא שהוא רגיש לשני אורכי גל נפרדים, 850 ו-940 ננומטר. תכל'ס זה לא ממש קריטי, כי כל חיישן רגיש לטווח מסוים – מקסימום נצטרך לשדר אליו ממרחק קצר יותר כדי להגביר את עוצמת האות הנקלט. בכל אופן, חיברתי את הפוטוטרנזיסטור למעגל בסיסי ובדקתי עם סקופ מה רואים כשמפעילים ישר מולו את השלט. ככה זה נראה:

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

ומה שיפה זה שהסקופ עצמו, ברגע שהוא קולט גל מחזורי, כבר יודע לומר לנו מה התדר של הגל הזה (הסתכלו על הטקסט הצהוב למטה בתמונה שצורפה). הדיוק לא מושלם אבל אנחנו רואים שכוונת היצרן הייתה ללא ספק גל נושא בתדר 38KHz. מעולה. כעת ניקח חיישן IR חכם יותר, שיודע לסנן את הגל הנושא ולהעביר לנו רק את המידע נטו. במקרה זה לקחתי חיישן מהדגם הפשוט והזול TL1838. הנה מה שמתקבל כשלוחצים על אחד הכפתורים בשלט מול החיישן הזה:

מעגל בדיקה עם TL1838, לא כולל אספקת חשמל
מעגל בדיקה עם TL1838, לא כולל אספקת חשמל
תחילת השידור הלא-מאופנן
תחילת השידור הלא-מאופנן

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

"חבילות" הביטים הראשונות בשידור
"חבילות" הביטים הראשונות בשידור (לחצו להגדלה)

זהו הקלט שהתקבל לאורך עשירית שנייה בערך, מתוך שנייה שלמה של לחיצה רצופה על כפתור אחד (ON/OFF של התאורה). שאר הקלט הוא למעשה חזרה שוב ושוב על החבילה השלישית משמאל, כך שאינטואיטיבית אפשר לשער כבר עכשיו שאורך השידור הקריטי הוא 3 חבילות, והשאר זה רק ליתירוּת. אגב, בשלט יש שמונה כפתורים, שכל אחד מהם שולח פקודה מיידית (כלומר לא מתבצע שום תכנות או שינוי הגדרות בשלט עצמו), כך שנצפה לראות בסך הכול 8 סוגי שידורים שונים, ואם כל אחד מהם הוא של 3 חבילות בלבד אפשר לפענח את זה אפילו ידנית בלי לבזבז יותר מדי זמן. אז לצורך העניין נניח שה"שקע" הארוך במתח הנמדד (1.26ms) מייצג את הביט 1 ואילו השקע הקצר (כ-0.43ms) מייצג 0. השידור שקיבלנו הוא לפיכך:

110000000000 110001111111 110000001000

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

OFF: 110000000000 110001111111 110000010000
LOW: 110000000000 110001111111 110001000011
MED: 110000000000 110001111111 110000000100
HI : 110000000000 110001111111 110000000001

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

1H: 0100000
3H: 1000110
6H: 0000010

אני עדיין לא מבחין באיזשהו הגיון מספרי, אז נניח שאלה קודים שרירותיים. כעת נותר רק לשחזר אותם בארדואינו, וראינו כבר את התזמונים (אם נעגל קצת את המספרים, אז יש 7.8ms בין חבילות, וכל ביט הוא שילוב של 1.2ms/0.4ms). החלק הפחות-אינטואיטיבי, לפחות למי שרוצה לעשות דברים לבד ולא להסתמך על ספריות קיימות, הוא ליצור בארדואינו את הגל הנושא – גל ריבועי בתדר 38KHz.

אם ממש מתעקשים אפשר אולי לממש את זה בתוכנה נטו, אבל זה דחוק ולא מומלץ. הדרך הנכונה היא באמצעות טיימר, למעשה להתאים לצרכינו את אחד מערוצי ה-PWM (או בשפת המתחילים analogWrite). אנחנו יודעים שתדר השעון של ארדואינו Uno סטנדרטי הוא 16MHz, וכדי "לנדנד" פין פלט בתדר 38KHz אנחנו צריכים בעצם לשנות את המצב שלו 76,000 פעמים בשנייה (כי כל "גל" מורכב מחצי HIGH וחצי LOW), כלומר כ-210.5 מחזורי שעון. לשם כך יספיק לנו Timer2 בן 8 הביטים. נגדיר אותו למצב שבו הוא סופר מ-0 עד 209 שוב ושוב, ומחליף את הפלט OC2A (פין PB3, או 11 במספור ארדואינו) בכל פעם שהספירה מתחילה מחדש. הנה קוד שיכול להתאים:

// Set up Timer2 to 38KHz CTC mode, OFF
void T2_38KHzSetup() {
  TCCR2A = (1 << COM2A0) | (1 << WGM21);
  TCCR2B = 0;
  OCR2A  = 209;  
}

// Turn Timer2 ON/OFF
void T2_SetActivity(const bool isON) {
  if (isON) {
    TCCR2B |= (1 << CS20);
  } else {
      TCCR2B &= ~(1 << CS20);
    }
}

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

void remoteSendBit(const uint8_t b) {

  const uint8_t US_DELAY[2][2] = {
    {400, 1200}, {1200, 400}
  };

  T2_SetActivity(true);
  delayMicroseconds(US_DELAY[b][0]);
  T2_SetActivity(false);
  delayMicroseconds(US_DELAY[b][1]);
  
}
והפונקציה הזו, בתורה, תשמש אותנו כדי לשלוח חבילה, ומשם נמשיך לשידור שלם – הנה קוד שכתבתי "מהשרוול" לפני הבדיקות:
void remoteSendPacket(const uint8_t command) {

  const uint16_t PACKET_MASK     = 2048U;
  // 0b110000000000
  const uint16_t PACKET_SKELETON = 3072U; 
  
  uint16_t packet = PACKET_SKELETON + command;
  uint8_t bitsLeft = 12;

  while (bitsLeft--) {
    remoteSendBit(PACKET_MASK == 
                  (PACKET_MASK & packet));
    packet <<= 1;
  }
  
}

void remoteSendCommand(const uint8_t command, 
                       uint8_t repeats = 1) {

  const uint16_t PACKET_DELAY = 7800U;

  remoteSendPacket(0b0000000);
  delayMicroseconds(PACKET_DELAY);
  remoteSendPacket(0b1111111);
  while (repeats--) {
    delayMicroseconds(PACKET_DELAY);
    remoteSendPacket(command);    
  }  
  
}

כשבדקתי את הפלט של הקוד הזה עם לוג'יק אנלייזר גיליתי דבר אחד חשוב שחסר. אם אני עוצר את Timer2 והפלט באותו רגע הוא HIGH, הוא יישאר HIGH, וזה לא טוב. אחרי כל קטע שידור פעיל צריך לתת פקודה מפורשת שתבטיח שהפין יהיה ב-LOW. מה שנשאר כעת זה לחבר לפין 11 בארדואינו נורת LED אינפרה-אדומה עם נגד מתאים, לקוות שאורך הגל שלה יספיק בשביל המקלט שבמאוורר, ולכתוב קוד הדגמה שידליק ויכבה את האור ואת המאוורר בקצב איטי. זה לא עבד.

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

למי שרוצה לראות את הקוד הסופי והמעודכן לארדואינו, הוא נמצא כאן: https://pastebin.com/yt60Y5Lx . את סרטון ההדגמה אשאיר לשלב הבא בפרויקט – הכנה של שלט חלופי עם מעגל עצמאי ומארז מודפס בתלת ממד. במקביל, לפני שאכנס לפרויקט כבד של שלט של מזגן, אנסה לפצח שלט של מאוורר תקרה אחר שיש לי במקרה בבית. המשך יבוא…

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

מגניב 🙂
ניסית לתת למאוורר גם פקודות שהשלט לא תמך בהן?
אולי תגלה הפתעות :O

> עם זאת, בינתיים אני לא רואה דפוס בחבילה האחרונה – הקשר בין המספר למשמעות נראה די אקראי.

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

https://en.wikipedia.org/wiki/Hamming_distance

אכן נראה שכל הפקודות נותנות מרחק של 2 או 4.
לא בטוח שהפרמוט יעבוד טוב:

1 2 3 4 5 6 7 8
1 0 2 4 2 2 2 4 2
2 2 0 4 2 2 2 4 2
3 4 4 0 4 2 4 2 2
4 2 2 4 0 2 2 2 2
5 2 2 2 2 0 2 4 2
6 2 2 4 2 2 0 4 2
7 4 4 2 2 4 4 0 2
8 2 2 2 2 2 2 2 0

מגניב. המימוש הכי אהוב עלי ללוחות פיתוח הוא פענוח ושחזור שלטי IR. בהצלחה!

בזמנו התעניינתי בהפיכת מזגן עם שלט IR לחכם וראיתי פוסט של אלכס שו (לא זוכר אם באתר מייקרס זל או בבלוג שלו, שניהם נמחקו מאז) וגם סרטון יוטיוב שלו בנושא, זה היה מעניין https://www.youtube.com/watch?v=67IEmopqE9A