לבד בצמרת: ההטיה של map

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

התפלגות לדוגמה של פלט הפונקציה map
סוד אפל ומסוכן. מה, אתם לא רואים?

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

הפונקציה map, למי שעוד לא מכיר, מקבלת חמישה פרמטרים: ערך x כלשהו, מינימום ומקסימום של סקאלת מקור, ומינימום ומקסימום של סקאלת יעד. בתוך הפונקציה מתבצע חישוב קצר שממיר את הערך מהסקאלה הראשונה לשניה, ואת התוצאה אנחנו מקבלים בחזרה. אחד השימושים הנפוצים ל-map, בפרויקט הדגמה קלאסי למתחילים, הוא המרה של קלט אנלוגי מפוטנציומטר (טווח ערכים 0-1023) לפלט שמתאים לפקודה Servo.write (בטווח 0-180):

// From Arduino's "Knob" example sketch

val = analogRead(potpin);           
val = map(val, 0, 1023, 0, 180);
myservo.write(val);

הפרמטרים והערך החוזר של map הם כולם מספרים שלמים, מה שאומר שאם ממירים מסקאלה רחבה לסקאלה צרה יותר, איפשהו בדרך משהו חייב ללכת לאיבוד. להמחשה קיצונית מעט, אם נמיר סקאלה של 0-1023 לסקאלה של 0-1, כל הניואנסים שבאמצע ייעלמו. זה סביר. מה שפחות סביר הוא הכיוון אליו הם נעלמים. חישבו על זה רגע. בהמרת סקאלות מ-0-1023 ל-0-1, עבור כמה מהערכים בין 0 ל-1023 (כולל) נקבל את התשובה 0, ועבור כמה את התשובה 1? חצי חצי?

למעשה, בגלל צורת החישוב שנבחרה עבור map, הערך היחיד שייתן את התשובה 1 הוא הערך הקיצוני 1023. כל שאר אלף עשרים ושניים הערכים ייתנו את התשובה 0! כשאנחנו רק משחקים עם פוטנציומטר וסרבו, סביר להניח שלא נרגיש בזיוף הזה כי גודלו רק 1/180 מטווח התנועה של המנוע – אולי אף פחות מחוסר הדיוק המובנה של הסרבואים הסיניים הזולים. אבל בסקאלות קטנות יותר, או כשיש משמעות לאחידות של התפלגות התוצאות, מדובר בסכנה אמתית.

בגרף בתחילת הפוסט מוצגים הערכים החוזרים ממיפוי של 0-1023 (בציר x) ל-0-10 בעזרת הפונקציה map. אם נתעלם מהקו הדקיק והזניח בקצה הימני שמגיע עד 10, אפשר לומר שקיבלנו בעצם המרה לסקאלה 0-9 בלבד, ולא כפי שביקשנו. לדבר כזה יכולות להיות השלכות הרסניות על תזמונים של פעולות במיקרו-בקר, על תגובות לערכי חיישנים ועוד.

למזלנו, יש כמה וכמה דרכים למנוע קיצוץ כזה. אפשרות אחת היא להוסיף 1 (אחד) גם ל-in_max וגם ל-out_max שנשלחים כפרמטרים ל-map. כל עוד הערך שנשלח להמרה לא חורג מהגבולות, מקרה הקיצון לא יתרחש לעולם – ושאר ההתפלגות, כמו שראינו, תהיה פחות או יותר בסדר.

אפשרות נוספת היא לעבוד, אם ניתן, בחזקות של 2. לדוגמה, את הסקאלה של 0-1023 אפשר למפות בקלות לסקאלה של 0-63 פשוט על ידי הזזה של הביטים בערך המקורי ארבעה מקומות ימינה (חלוקה ב-16). זה גם יעבוד הרבה יותר מהר.

לא במפתיע, שום דבר מכל זה לא מוזכר במפורש בתיעוד הרשמי של map, וגם לא בהערות בקוד המקור שלה.

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

אבל…
זו השפה.
0-1023 = 1024 ערכים.
0-10 = 11 ערכים.
אם התכוונת לקבל רק 10 ערכים אז למעשה 0-9.
אתה גם תמיד יכול להגדיר
x=MAP(…)+1 , אם אתה רוצה את הערכים "אמיתיים".

כלומר, לקחו רק את החלק השלם בחלוקה בלי התחשבות בשארית.
מה עם לכתוב פונקצית MAP2 של עצמך?

יש דרך להשתמש בmap ולא לחלק את זה לחלקים שווים אלא לחלק לפי מה שאני בחרתי לדוגמה לחלק 1023 ל 3 אבל במקום שיתחלק לשלושה חלקים שווים הוא יתחלק ל 0=0-308 1=309-616 617-1023 =2?

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

כמו כותב מיומן אמרת הנושא נטחן:)