פלטפורמת Android מספקת כמה חיישנים שמאפשרים לעקוב אחרי התנועה של מכשיר.
הארכיטקטורות האפשריות של החיישנים משתנות בהתאם לסוג החיישן:
- החיישנים של כוח המשיכה, התאוצה הליניארית, וקטור הסיבוב, תנועה משמעותית, מד הצעדים וגלאי הצעדים הם מבוססי חומרה או מבוססי תוכנה.
- חיישני מד התאוצה והג'ירוסקופ תמיד מבוססים על חומרה.
לרוב המכשירים עם Android יש מד תאוצה, ורבים מהם כוללים עכשיו גם ג'ירוסקופ. הזמינות של חיישנים מבוססי תוכנה משתנה יותר, כי הם מסתמכים לעיתים קרובות על חיישן חומרה אחד או יותר כדי להפיק את הנתונים שלהם. בהתאם למכשיר, חיישנים מבוססי-תוכנה יכולים להפיק את הנתונים שלהם ממד התאוצה וממגנטומטר או מהג'ירוסקופ.
חיישני תנועה שימושיים למעקב אחר תנועת המכשיר, כמו הטיה, ניעור, סיבוב או תנועה מצד לצד. התנועה בדרך כלל משקפת קלט ישיר של המשתמש (לדוגמה, משתמש שמכוון מכונית במשחק או משתמש ששולט בכדור במשחק), אבל היא יכולה גם לשקף את הסביבה הפיזית שבה המכשיר נמצא (לדוגמה, תנועה עם המשתמש בזמן שהוא נוהג במכונית). במקרה הראשון, אתם עוקבים אחרי תנועה ביחס למערכת הייחוס של המכשיר או של האפליקציה. במקרה השני, אתם עוקבים אחרי תנועה ביחס למערכת הייחוס של העולם. חיישני תנועה לבדם לא משמשים בדרך כלל למעקב אחרי מיקום המכשיר, אבל אפשר להשתמש בהם עם חיישנים אחרים, כמו חיישן השדה הגיאומגנטי, כדי לקבוע את מיקום המכשיר ביחס למערכת הייחוס העולמית (מידע נוסף זמין במאמר בנושא חיישני מיקום).
כל חיישני התנועה מחזירים מערכים רב-ממדיים של ערכי חיישנים לכל SensorEvent
. לדוגמה, במהלך אירוע חיישן יחיד, מד התאוצה מחזיר נתוני כוח תאוצה עבור שלושת צירי הקואורדינטות, והג'ירוסקופ מחזיר נתוני קצב סיבוב עבור שלושת צירי הקואורדינטות. ערכי הנתונים האלה מוחזרים במערך float
(values
) יחד עם פרמטרים אחרים של SensorEvent
. בטבלה 1 מפורטים חיישני התנועה שזמינים בפלטפורמת Android.
טבלה 1. חיישני תנועה שנתמכים בפלטפורמת Android.
חיישן | נתוני אירועים מחיישנים | תיאור | יחידות מידה |
---|---|---|---|
TYPE_ACCELEROMETER |
SensorEvent.values[0] |
כוח התאוצה לאורך ציר x (כולל כוח המשיכה). | m/s2 |
SensorEvent.values[1] |
כוח התאוצה לאורך ציר ה-Y (כולל כוח המשיכה). | ||
SensorEvent.values[2] |
כוח ההאצה לאורך ציר z (כולל כוח המשיכה). | ||
TYPE_ACCELEROMETER_UNCALIBRATED |
SensorEvent.values[0] |
תאוצה שנמדדה לאורך ציר X ללא פיצוי על הטיה. | m/s2 |
SensorEvent.values[1] |
תאוצה שנמדדה לאורך ציר Y ללא פיצוי על הטיה. | ||
SensorEvent.values[2] |
האצה שנמדדה לאורך ציר Z ללא פיצוי על הטיה. | ||
SensorEvent.values[3] |
תאוצה שנמדדה לאורך ציר ה-X עם פיצוי מוטה משוער. | ||
SensorEvent.values[4] |
תאוצה שנמדדה לאורך ציר Y עם פיצוי משוער על הטיה. | ||
SensorEvent.values[5] |
תאוצה שנמדדה לאורך ציר Z עם פיצוי מוטה משוער. | ||
TYPE_GRAVITY |
SensorEvent.values[0] |
כוח המשיכה לאורך ציר x. | m/s2 |
SensorEvent.values[1] |
כוח המשיכה לאורך ציר ה-y. | ||
SensorEvent.values[2] |
כוח המשיכה לאורך ציר z. | ||
TYPE_GYROSCOPE |
SensorEvent.values[0] |
שיעור הסיבוב סביב ציר ה-x. | rad/s |
SensorEvent.values[1] |
מהירות הסיבוב סביב ציר ה-y. | ||
SensorEvent.values[2] |
שיעור הסיבוב סביב ציר ה-z. | ||
TYPE_GYROSCOPE_UNCALIBRATED |
SensorEvent.values[0] |
קצב הסיבוב (ללא פיצוי על סחיפה) סביב ציר ה-X. | rad/s |
SensorEvent.values[1] |
קצב הסיבוב (ללא פיצוי על סחיפה) סביב ציר ה-y. | ||
SensorEvent.values[2] |
קצב הסיבוב (ללא פיצוי על סחיפה) סביב ציר ה-z. | ||
SensorEvent.values[3] |
הסחף המשוער סביב ציר ה-x. | ||
SensorEvent.values[4] |
הסחף המשוער סביב ציר ה-y. | ||
SensorEvent.values[5] |
הסחף המשוער סביב ציר ה-Z. | ||
TYPE_LINEAR_ACCELERATION |
SensorEvent.values[0] |
כוח התאוצה לאורך ציר X (לא כולל כוח המשיכה). | m/s2 |
SensorEvent.values[1] |
כוח ההאצה לאורך ציר ה-y (לא כולל כוח המשיכה). | ||
SensorEvent.values[2] |
כוח ההאצה לאורך ציר z (לא כולל כוח המשיכה). | ||
TYPE_ROTATION_VECTOR |
SensorEvent.values[0] |
רכיב וקטור הסיבוב לאורך ציר ה-x (x * sin(θ/2)). | ללא יחידה |
SensorEvent.values[1] |
רכיב וקטור הסיבוב לאורך ציר ה-y (y * sin(θ/2)). | ||
SensorEvent.values[2] |
רכיב וקטור הסיבוב לאורך ציר z (z * sin(θ/2)). | ||
SensorEvent.values[3] |
הרכיב הסקלרי של וקטור הסיבוב (cos(θ/2)).1 | ||
TYPE_SIGNIFICANT_MOTION |
לא רלוונטי | לא רלוונטי | לא רלוונטי |
TYPE_STEP_COUNTER |
SensorEvent.values[0] |
מספר הצעדים שהמשתמש עשה מאז ההפעלה מחדש האחרונה בזמן שהחיישן היה פעיל. | צעדים |
TYPE_STEP_DETECTOR |
לא רלוונטי | לא רלוונטי | לא רלוונטי |
1 הרכיב הסקלרי הוא ערך אופציונלי.
חיישן וקטור הסיבוב וחיישן הכבידה הם החיישנים הנפוצים ביותר לזיהוי תנועה ולמעקב אחריה. חיישן וקטור הסיבוב הוא רב-תכליתי במיוחד, ואפשר להשתמש בו למגוון רחב של משימות שקשורות לתנועה, כמו זיהוי תנועות, מעקב אחרי שינוי זוויתי ומעקב אחרי שינויים בהתמצאות היחסית. לדוגמה, חיישן וקטור הסיבוב הוא אידיאלי אם אתם מפתחים משחק, אפליקציית מציאות רבודה, מצפן דו-ממדי או תלת-ממדי או אפליקציה לייצוב מצלמה. ברוב המקרים, השימוש בחיישנים האלה הוא בחירה טובה יותר מאשר שימוש במד תאוצה ובחיישן השדה הגיאומגנטי או בחיישן הכיוון.
חיישנים בפרויקט קוד פתוח של Android
פרויקט הקוד הפתוח של Android (AOSP) מספק שלושה חיישני תנועה מבוססי תוכנה: חיישן כוח משיכה, חיישן תאוצה לינארית וחיישן וקטור סיבוב. החיישנים האלה עודכנו ב-Android 4.0, ועכשיו הם משתמשים בג'ירוסקופ של המכשיר (בנוסף לחיישנים אחרים) כדי לשפר את היציבות והביצועים. אם רוצים לנסות את החיישנים האלה, אפשר לזהות אותם באמצעות השיטה getVendor()
והשיטה getVersion()
(הספק הוא Google LLC ומספר הגרסה הוא 3). צריך לזהות את החיישנים האלה לפי הספק ומספר הגרסה, כי מערכת Android מחשיבה את שלושת החיישנים האלה כחיישנים משניים. לדוגמה, אם יצרן מכשיר מספק חיישן כוח משיכה משלו, חיישן כוח המשיכה של AOSP יופיע כחיישן כוח משיכה משני. כל שלושת החיישנים האלה מסתמכים על גירוסקופ: אם במכשיר אין גירוסקופ, החיישנים האלה לא מוצגים ולא זמינים לשימוש.
שימוש בחיישן הכבידה
חיישן הכבידה מספק וקטור תלת-ממדי שמציין את הכיוון והעוצמה של הכבידה. בדרך כלל, החיישן הזה משמש לקביעת הכיוון היחסי של המכשיר במרחב. הקוד הבא מראה איך מקבלים מופע של חיישן הכבידה שמוגדר כברירת מחדל:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
היחידות זהות ליחידות שבהן משתמש חיישן התאוצה (m/s2), ומערכת הקואורדינטות זהה למערכת שבה משתמש חיישן התאוצה.
הערה: כשהמכשיר במנוחה, הפלט של חיישן הכבידה צריך להיות זהה לזה של מד התאוצה.
שימוש במד התאוצה הליניארי
חיישן התאוצה הליניארית מספק וקטור תלת-ממדי שמייצג את התאוצה לאורך כל ציר של המכשיר, לא כולל כוח המשיכה. אפשר להשתמש בערך הזה כדי לזהות תנועות. הערך יכול לשמש גם כקלט למערכת ניווט אינרציאלית, שמשתמשת בחישוב מיקום משוער. הקוד הבא מראה איך מקבלים מופע של חיישן תאוצה ליניארית שמוגדר כברירת מחדל:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
מבחינה מושגית, החיישן הזה מספק נתוני תאוצה בהתאם לקשר הבא:
linear acceleration = acceleration - acceleration due to gravity
בדרך כלל משתמשים בחיישן הזה כשרוצים לקבל נתוני תאוצה בלי ההשפעה של כוח המשיכה. לדוגמה, אפשר להשתמש בחיישן הזה כדי לראות את מהירות הנסיעה של הרכב. לחיישן התאוצה הליניארית יש תמיד היסט, שצריך להסיר. הדרך הכי פשוטה לעשות את זה היא להוסיף שלב כיול לאפליקציה. במהלך הכיול, אפשר לבקש מהמשתמש להניח את המכשיר על שולחן, ואז לקרוא את ההיסטים של כל שלושת הצירים. אחר כך אפשר להפחית את ההיסט הזה מהקריאות הישירות של חיישן התאוצה כדי לקבל את התאוצה הלינארית בפועל.
מערכת הקואורדינטות של החיישן זהה לזו שבה משתמש חיישן התאוצה, וכך גם יחידות המידה (m/s2).
שימוש בחיישן וקטור הסיבוב
וקטור הסיבוב מייצג את הכיוון של המכשיר כשילוב של זווית וציר, שבו המכשיר הסתובב בזווית θ סביב ציר (x, y או z). הקוד הבא מראה איך מקבלים מופע של חיישן וקטור הסיבוב שמוגדר כברירת מחדל:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
שלושת הרכיבים של וקטור הסיבוב מבוטאים באופן הבא:

הגודל של וקטור הסיבוב שווה ל-sin(θ/2), והכיוון של וקטור הסיבוב שווה לכיוון של ציר הסיבוב.

איור 1. מערכת הקואורדינטות שבה משתמש חיישן וקטור הסיבוב.
שלושת הרכיבים של וקטור הסיבוב שווים לשלושת הרכיבים האחרונים של קווטרניון יחידה (cos(θ/2), x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)). האלמנטים של וקטור הסיבוב הם חסרי יחידות. ציר ה-x, ציר ה-y וציר ה-z מוגדרים באותו אופן כמו חיישן התאוצה. מערכת הקואורדינטות של ההפניה מוגדרת כבסיס אורתונורמלי ישיר (ראו איור 1). למערכת הקואורדינטות הזו יש את המאפיינים הבאים:
- X מוגדר כמכפלה וקטורית Y x Z. הוא משיק לקרקע במיקום הנוכחי של המכשיר ומצביע בערך לכיוון מזרח.
- ציר Y משיק לקרקע במיקום הנוכחי של המכשיר ומצביע לכיוון הקוטב הצפוני הגיאומגנטי.
- הציר Z מצביע כלפי השמיים ומאונך למישור הקרקע.
אפליקציה לדוגמה שמראה איך להשתמש בחיישן וקטור הסיבוב זמינה ב- RotationVectorDemo.java.
שימוש בחיישן תנועה משמעותית
חיישן התנועה המשמעותית מפעיל אירוע בכל פעם שמזוהה תנועה משמעותית, ואז הוא מושבת. תנועה משמעותית היא תנועה שעשויה להוביל לשינוי במיקום של המשתמש, למשל הליכה, רכיבה על אופניים או ישיבה במכונית נוסעת. בדוגמה הבאה אפשר לראות איך מקבלים מופע של חיישן ברירת המחדל לזיהוי תנועה משמעותית ואיך רושמים מאזין לאירועים:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val mSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION) val triggerEventListener = object : TriggerEventListener() { override fun onTrigger(event: TriggerEvent?) { // Do work } } mSensor?.also { sensor -> sensorManager.requestTriggerSensor(triggerEventListener, sensor) }
Java
private SensorManager sensorManager; private Sensor sensor; private TriggerEventListener triggerEventListener; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); triggerEventListener = new TriggerEventListener() { @Override public void onTrigger(TriggerEvent event) { // Do work } }; sensorManager.requestTriggerSensor(triggerEventListener, mSensor);
מידע נוסף זמין TriggerEventListener
.
שימוש בחיישן מד הצעדים
חיישן מד הצעדים מספק את מספר הצעדים שהמשתמש עשה מאז ההפעלה מחדש האחרונה בזמן שהחיישן היה פעיל. למד הצעדים יש זמן אחזור ארוך יותר (עד 10 שניות), אבל הוא מדויק יותר מחיישן גלאי הצעדים.
הערה: כדי שהאפליקציה תוכל להשתמש בחיישן הזה במכשירים עם Android 10 (רמת API 29) ומעלה, צריך להצהיר על ההרשאה ACTIVITY_RECOGNITION
.
הקוד הבא מראה איך מקבלים מופע של חיישן ברירת המחדל למדידת צעדים:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
כדי לחסוך בסוללה במכשירים שבהם האפליקציה שלכם פועלת, כדאי להשתמש במחלקה JobScheduler
כדי לאחזר את הערך הנוכחי מחיישן מד הצעדים במרווח זמן ספציפי. אפליקציות מסוגים שונים דורשות מרווחי קריאה שונים מהחיישן, אבל כדאי להגדיר את המרווח הזה לארוך ככל האפשר, אלא אם האפליקציה דורשת נתונים בזמן אמת מהחיישן.
שימוש בחיישן לזיהוי צעדים
חיישן זיהוי הצעדים מפעיל אירוע בכל פעם שהמשתמש עושה צעד. זמן האחזור צפוי להיות מתחת ל-2 שניות.
הערה: כדי שהאפליקציה תוכל להשתמש בחיישן הזה במכשירים עם Android 10 (רמת API 29) ומעלה, צריך להצהיר על ההרשאה ACTIVITY_RECOGNITION
.
הקוד הבא מראה איך מקבלים מופע של חיישן ברירת המחדל לזיהוי צעדים:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
עבודה עם נתונים גולמיים
החיישנים הבאים מספקים לאפליקציה נתונים גולמיים על הכוחות הליניאריים והסיבוביים שמופעלים על המכשיר. כדי להשתמש ביעילות בערכים מהחיישנים האלה, צריך לסנן גורמים מהסביבה, כמו כוח המשיכה. יכול להיות שיהיה צורך גם להחיל אלגוריתם החלקה על מגמת הערכים כדי לצמצם את הרעש.
שימוש במד התאוצה
חיישן תאוצה מודד את התאוצה שמופעלת על המכשיר, כולל כוח המשיכה. הקוד הבא מראה איך מקבלים מופע של חיישן התאוצה שמוגדר כברירת מחדל:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
הערה: אם האפליקציה מטרגטת את Android 12 (רמת API 31) או גרסה מתקדמת יותר, קצב השימוש בחיישן הזה מוגבל.
מבחינה מושגית, חיישן תאוצה קובע את התאוצה שמופעלת על מכשיר (Ad) על ידי מדידת הכוחות שמופעלים על החיישן עצמו (Fs) באמצעות הקשר הבא:

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

לכן, כשהמכשיר מונח על שולחן (ולא מאיץ), מד התאוצה קורא גודל של g = 9.81 m/s2. באופן דומה, כשהמכשיר נמצא בנפילה חופשית ולכן מאיץ במהירות לכיוון הקרקע במהירות של 9.81 מ'/שנייה2, מד התאוצה שלו מראה גודל של g = 0 מ'/שנייה2. לכן, כדי למדוד את התאוצה האמיתית של המכשיר, צריך להסיר את ההשפעה של כוח המשיכה מנתוני מד התאוצה. אפשר לעשות זאת באמצעות מסנן מעביר גבוה. לעומת זאת, אפשר להשתמש במסנן מעביר נמוכים כדי לבודד את כוח המשיכה. בדוגמה הבאה אפשר לראות איך עושים את זה:
Kotlin
override fun onSensorChanged(event: SensorEvent) { // In this example, alpha is calculated as t / (t + dT), // where t is the low-pass filter's time-constant and // dT is the event delivery rate. val alpha: Float = 0.8f // Isolate the force of gravity with the low-pass filter. gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0] gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1] gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2] // Remove the gravity contribution with the high-pass filter. linear_acceleration[0] = event.values[0] - gravity[0] linear_acceleration[1] = event.values[1] - gravity[1] linear_acceleration[2] = event.values[2] - gravity[2] }
Java
public void onSensorChanged(SensorEvent event){ // In this example, alpha is calculated as t / (t + dT), // where t is the low-pass filter's time-constant and // dT is the event delivery rate. final float alpha = 0.8; // Isolate the force of gravity with the low-pass filter. gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]; gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]; gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]; // Remove the gravity contribution with the high-pass filter. linear_acceleration[0] = event.values[0] - gravity[0]; linear_acceleration[1] = event.values[1] - gravity[1]; linear_acceleration[2] = event.values[2] - gravity[2]; }
הערה: אפשר להשתמש בטכניקות שונות כדי לסנן את נתוני החיישנים. בדוגמת הקוד שלמעלה נעשה שימוש בקבוע מסנן פשוט (אלפא) כדי ליצור מסנן מעביר נמוכים. המסנן הזה הקבוע נגזר מקבוע זמן (t), שהוא ייצוג גס של זמן האחזור שהמסנן מוסיף לאירועי החיישן, וקצב העברת האירועים של החיישן (dt). בדוגמת הקוד נעשה שימוש בערך אלפא של 0.8 לצורכי הדגמה. אם משתמשים בשיטת הסינון הזו, יכול להיות שיהיה צורך לבחור ערך אלפא אחר.
מדי תאוצה משתמשים במערכת קואורדינטות סטנדרטית של חיישנים. בפועל, המשמעות היא שהתנאים הבאים חלים כשהמכשיר מונח שטוח על שולחן באוריינטציה הטבעית שלו:
- אם דוחפים את המכשיר מצד שמאל (כך שהוא זז ימינה), ערך התאוצה בציר x הוא חיובי.
- אם דוחפים את המכשיר מלמטה (כך שהוא מתרחק מכם), ערך התאוצה בציר Y הוא חיובי.
- אם דוחפים את המכשיר לכיוון השמיים בתאוצה של A מטרים לשנייה2, ערך התאוצה בציר z שווה ל-A + 9.81, שמתאים לתאוצה של המכשיר (+A מטרים לשנייה2) פחות כוח המשיכה (-9.81 מטרים לשנייה2).
- למכשיר הנייח יהיה ערך תאוצה של +9.81, שמתאים לתאוצה של המכשיר (0 מטרים לשנייה2 פחות כוח המשיכה, שהוא -9.81 מטרים לשנייה2).
באופן כללי, מד התאוצה הוא חיישן טוב לשימוש אם עוקבים אחרי תנועת המכשיר. כמעט לכל טלפון וטאבלט עם Android יש מד תאוצה, והוא צורך בערך פי 10 פחות חשמל מחיישני התנועה האחרים. חיסרון אחד הוא שאולי תצטרכו להטמיע מסנני מעבר נמוך ומסנני מעבר גבוה כדי לבטל כוחות כבידה ולהפחית רעשים.
שימוש בג'ירוסקופ
הג'ירוסקופ מודד את קצב הסיבוב ברדיאנים לשנייה (rad/s) סביב הצירים x, y ו-z של המכשיר. בדוגמה הבאה של קוד אפשר לראות איך מקבלים מופע של הג'ירוסקופ שמוגדר כברירת מחדל:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
הערה: אם האפליקציה מטרגטת את Android 12 (רמת API 31) או גרסה מתקדמת יותר, קצב השימוש בחיישן הזה מוגבל.
מערכת הקואורדינטות של החיישן זהה לזו שמשמשת את חיישן התאוצה. הסיבוב חיובי בכיוון נגד השעון. כלומר, אם צופה שמסתכל ממיקום חיובי כלשהו על ציר ה-x, ציר ה-y או ציר ה-z על מכשיר שממוקם בנקודת האפס, ידווח על סיבוב חיובי אם נראה שהמכשיר מסתובב נגד השעון. זוהי ההגדרה המתמטית הסטנדרטית של סיבוב חיובי, והיא לא זהה להגדרה של הטיה שמשמשת את חיישן הכיוון.
בדרך כלל, הפלט של הג'ירוסקופ משולב לאורך זמן כדי לחשב סיבוב שמתאר את השינוי בזוויות לאורך מרווח הזמן. לדוגמה:
Kotlin
// Create a constant to convert nanoseconds to seconds. private val NS2S = 1.0f / 1000000000.0f private val deltaRotationVector = FloatArray(4) { 0f } private var timestamp: Float = 0f override fun onSensorChanged(event: SensorEvent?) { // This timestep's delta rotation to be multiplied by the current rotation // after computing it from the gyro sample data. if (timestamp != 0f && event != null) { val dT = (event.timestamp - timestamp) * NS2S // Axis of the rotation sample, not normalized yet. var axisX: Float = event.values[0] var axisY: Float = event.values[1] var axisZ: Float = event.values[2] // Calculate the angular speed of the sample val omegaMagnitude: Float = sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ) // Normalize the rotation vector if it's big enough to get the axis // (that is, EPSILON should represent your maximum allowable margin of error) if (omegaMagnitude > EPSILON) { axisX /= omegaMagnitude axisY /= omegaMagnitude axisZ /= omegaMagnitude } // Integrate around this axis with the angular speed by the timestep // in order to get a delta rotation from this sample over the timestep // We will convert this axis-angle representation of the delta rotation // into a quaternion before turning it into the rotation matrix. val thetaOverTwo: Float = omegaMagnitude * dT / 2.0f val sinThetaOverTwo: Float = sin(thetaOverTwo) val cosThetaOverTwo: Float = cos(thetaOverTwo) deltaRotationVector[0] = sinThetaOverTwo * axisX deltaRotationVector[1] = sinThetaOverTwo * axisY deltaRotationVector[2] = sinThetaOverTwo * axisZ deltaRotationVector[3] = cosThetaOverTwo } timestamp = event?.timestamp?.toFloat() ?: 0f val deltaRotationMatrix = FloatArray(9) { 0f } SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector); // User code should concatenate the delta rotation we computed with the current rotation // in order to get the updated rotation. // rotationCurrent = rotationCurrent * deltaRotationMatrix; }
Java
// Create a constant to convert nanoseconds to seconds. private static final float NS2S = 1.0f / 1000000000.0f; private final float[] deltaRotationVector = new float[4](); private float timestamp; public void onSensorChanged(SensorEvent event) { // This timestep's delta rotation to be multiplied by the current rotation // after computing it from the gyro sample data. if (timestamp != 0) { final float dT = (event.timestamp - timestamp) * NS2S; // Axis of the rotation sample, not normalized yet. float axisX = event.values[0]; float axisY = event.values[1]; float axisZ = event.values[2]; // Calculate the angular speed of the sample float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ); // Normalize the rotation vector if it's big enough to get the axis // (that is, EPSILON should represent your maximum allowable margin of error) if (omegaMagnitude > EPSILON) { axisX /= omegaMagnitude; axisY /= omegaMagnitude; axisZ /= omegaMagnitude; } // Integrate around this axis with the angular speed by the timestep // in order to get a delta rotation from this sample over the timestep // We will convert this axis-angle representation of the delta rotation // into a quaternion before turning it into the rotation matrix. float thetaOverTwo = omegaMagnitude * dT / 2.0f; float sinThetaOverTwo = sin(thetaOverTwo); float cosThetaOverTwo = cos(thetaOverTwo); deltaRotationVector[0] = sinThetaOverTwo * axisX; deltaRotationVector[1] = sinThetaOverTwo * axisY; deltaRotationVector[2] = sinThetaOverTwo * axisZ; deltaRotationVector[3] = cosThetaOverTwo; } timestamp = event.timestamp; float[] deltaRotationMatrix = new float[9]; SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector); // User code should concatenate the delta rotation we computed with the current rotation // in order to get the updated rotation. // rotationCurrent = rotationCurrent * deltaRotationMatrix; }
ג'ירוסקופים רגילים מספקים נתוני סיבוב גולמיים ללא סינון או תיקון של רעשים וסחיפה (הטיה). בפועל, רעשים וסחיפה של גירוסקופ יובילו לשגיאות שצריך לפצות עליהן. בדרך כלל קובעים את הסחיפה (הטיה) והרעש על ידי מעקב אחרי חיישנים אחרים, כמו חיישן כוח המשיכה או מד התאוצה.
שימוש בג'ירוסקופ לא מכויל
הג'ירוסקופ הלא מכויל דומה לג'ירוסקופ, אבל לא מופעלת בו פיצוי על סחיפת הג'ירוסקופ בקצב הסיבוב. הכיול של היצרן
והפיצוי על הטמפרטורה עדיין חלים על קצב הסיבוב. הג'ירוסקופ הלא מכויל שימושי לעיבוד שלאחר מכן ולמיזוג נתוני אוריינטציה. באופן כללי, הערך של gyroscope_event.values[0]
יהיה קרוב לערך של uncalibrated_gyroscope_event.values[0] - uncalibrated_gyroscope_event.values[3]
.
כלומר,
calibrated_x ~= uncalibrated_x - bias_estimate_x
הערה: חיישנים לא מכוילים מספקים יותר תוצאות גולמיות ועשויים לכלול הטיה מסוימת, אבל המדידות שלהם מכילות פחות קפיצות מתיקונים שמוחלים באמצעות כיול. יכול להיות שחלק מהאפליקציות יעדיפו את התוצאות האלה שלא עברו כיול, כי הן חלקות ואמינות יותר. לדוגמה, אם אפליקציה מנסה לבצע מיזוג נתונים מהחיישנים בעצמה, הוספת כיולים עלולה לעוות את התוצאות.
בנוסף לשיעורי הסיבוב, הג'ירוסקופ הלא מכויל מספק גם את הסחף המשוער סביב כל ציר. הקוד הבא מראה איך מקבלים מופע של גירוסקופ ברירת המחדל שלא עבר כיול:
Kotlin
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED)
Java
private SensorManager sensorManager; private Sensor sensor; ... sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
דוגמאות קוד נוספות
בדוגמה BatchStepSensor מוצג שימוש נוסף בממשקי ה-API שמוסברים בדף הזה.