חיישני תנועה

פלטפורמת 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);

שלושת הרכיבים של וקטור הסיבוב מבוטאים באופן הבא:

x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)

הגודל של וקטור הסיבוב שווה ל-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.

שימוש בחיישן תנועה ניכרת

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

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) באמצעות הקשר הבא:

A_D=-(1/mass)∑F_S

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

A_D=-g-(1/mass)∑F_S

לכן, כשהמכשיר מונח על שולחן (ולא מאיץ), מד התאוצה קורא גודל של g = 9.81 m/s2. באופן דומה, כשהמכשיר נמצא בנפילה חופשית ולכן מאיץ במהירות לכיוון הקרקע במהירות של 9.81 m/s2‎, מד התאוצה שלו מראה גודל של g = 0 m/s2‎. לכן, כדי למדוד את התאוצה האמיתית של המכשיר, צריך להסיר את ההשפעה של כוח הכבידה מנתוני מד התאוצה. אפשר לעשות את זה באמצעות מסנן שמעביר תדרים גבוהים. לעומת זאת, אפשר להשתמש במסנן שמעביר תדרים נמוכים כדי לבודד את כוח הכבידה. בדוגמה הבאה אפשר לראות איך עושים את זה:

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‏ m/s2‎, ערך התאוצה בציר Z שווה ל-A + 9.81, שמתאים לתאוצה של המכשיר (‎+A‏ m/s2‎) פחות כוח הכבידה (‎-9.81‏ m/s2‎).
  • למכשיר הנייד במצב נייח יהיה ערך תאוצה של ‎+9.81, שמתאים לתאוצה של המכשיר (0 m/s2‎ פחות כוח הכבידה, שהוא ‎-9.81‏ m/s2‎).

באופן כללי, מד התאוצה הוא חיישן טוב לשימוש אם עוקבים אחרי תנועת המכשיר. כמעט לכל טלפון וטאבלט עם 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 שמוסברים בדף הזה.

כדאי לקרוא גם על