חיישני תנועה

בפלטפורמת Android יש מספר חיישנים שמאפשרים לעקוב אחרי התנועה של מכשיר.

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

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

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

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

כל חיישני התנועה מחזירים מערכים רב-ממדיים של ערכי חיישנים לכל SensorEvent. לדוגמה, במהלך אירוע חיישן יחיד, מד התאוצה חוזר נתוני כוח התאוצה של שלושת צירי הקואורדינטות, והג'ירוסקופ מחזיר את קצב הסיבוב עבור שלושת צירי הקואורדינטות. ערכי הנתונים האלה מוחזרים במערך float (values) יחד עם עוד SensorEvent . טבלה 1 מסכמת את חיישני התנועה שזמינים בפלטפורמת Android.

טבלה 1. חיישני תנועה שתומכים בפלטפורמת Android.

חיישן נתוני אירועים מחיישנים תיאור יחידות מידה
TYPE_ACCELEROMETER SensorEvent.values[0] כוח התאוצה לאורך ציר x (כולל כוח המשיכה). מ"ש2
SensorEvent.values[1] כוח התאוצה לאורך ציר ה-y (כולל כוח הכבידה).
SensorEvent.values[2] כוח התאוצה לאורך ציר z (כולל כוח המשיכה).
TYPE_ACCELEROMETER_UNCALIBRATED SensorEvent.values[0] תאוצה שנמדדת לאורך ציר X ללא תיקון הטיה. מ"ש2
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), ומערכת הקואורדינטות זהה לזו שמשמשת את חיישן תאוצה.

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

שימוש במד התאוצה הלינארי

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

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(called/2), y*sin(לפעמים, 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.

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

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

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

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

כדאי גם לקרוא