חיישני תנועה

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

הערה: צריך להצהיר על ההרשאה ACTIVITY_RECOGNITION כדי שהאפליקציה תוכל להשתמש בחיישן הזה במכשירים עם Android 10 (רמת API 29) ואילך.

הקוד הבא מסביר איך למצוא מכונה של חיישן ברירת המחדל למונה:

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

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