חיישני תנועה

פלטפורמת 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 הרכיב הסקלרי הוא ערך אופציונלי.

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

חיישנים של פרויקט קוד פתוח של Android

פרויקט Android Open Source Project‏ (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), ומערכת הקואורדינטות היא אותה מערכת שבה נעשה שימוש בחיישן התאוצה.

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

שימוש ב-accelerometer לינאריים

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

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.

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

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

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

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

שימוש בג'ירוסקופ

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

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

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 שמפורטים בדף הזה.

כדאי גם לקרוא את המאמרים הבאים: