בפלטפורמת 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 (לא כולל כוח הכבידה). | מ"ש2 |
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);
מבחינה רעיונית, החיישן הזה מספק נתוני תאוצה לפי related:
linear acceleration = acceleration - acceleration due to gravity
בדרך כלל עליך להשתמש בחיישן הזה כשרוצים לקבל נתוני תאוצה ללא השפעה של כוח הכבידה. לדוגמה, אפשר להשתמש בחיישן הזה כדי לראות את מהירות הנסיעה של הרכב. את הערכים הלינאריים בחיישן התאוצה תמיד יש היסט, שאותו צריך להסיר. הדרך הפשוטה ביותר לעשות זאת היא כדי לבנות שלב כיול לתוך האפליקציה. במהלך הכיול, אפשר לבקש מהמשתמש להגדיר את המכשיר על שולחן, ואז קורא את ההיסטים של כל שלושת הצירים. ואז אפשר להפחית לקזז מהקריאות הישירות של חיישן התאוצה כדי לקבל את הערך הלינארי בפועל האצה.
קואורדינטה של החיישן המערכת זהה לזו שמשמשת את חיישן התאוצה, וכך גם יחידות המידה (מ"ש2).
שימוש בחיישן הווקטור של הסיבוב
וקטור הסיבוב מייצג את כיוון המכשיר כשילוב של זווית ציר שבו המכשיר הסתובב דרך זווית צדפות סביב ציר (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)](https://developer.android.com/static/images/guide/topics/sensors/rotational_vec.png?authuser=9&hl=he)
כאשר גודל הווקטור הסיבוב שווה ל-sin(המותר לפרסום/2), והכיוון של הווקטור של הסיבוב שווה לכיוון של ציר הסיבוב.
![](https://developer.android.com/static/images/axis_globe.png?authuser=9&hl=he)
איור 1. תיאום המערכת שמשמשת את חיישן הווקטור של הסיבוב.
שלושת הרכיבים של וקטור הסיבוב שווים לשלושת הרכיבים האחרונים ביחידה קוואטרניון (cos(סיכום/2), x*sin(לפעמים, x, y*sin(השהיה/2), z*sin(לפעמים, )). הרכיבים של וקטור הסיבוב ללא יחידות. הצירים 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 שניות), אבל יותר מדויק יותר מחיישן גלאי הצעדים.
הערה: יש להצהיר על
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 שניות.
הערה: יש להצהיר על
ACTIVITY_RECOGNITION
הרשאה כדי שהאפליקציה שלך תוכל להשתמש בחיישן הזה במכשירים פועלים
Android בגרסה 10 (רמת API 29) ואילך.
הקוד הבא מסביר איך לאחזר מופע של שלב ברירת המחדל חיישן גלאי:
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) או החיישן הזה גבוה יותר rate-limited
באופן רעיוני, חיישן תאוצה קובע את התאוצה שמופעלת למכשיר (Ad) על ידי מדידת הכוחות שמופעלים על החיישן עצמו (Fs) באמצעות קשר הגומלין הבא:
![A_D=-(1/mass)∑F_S](https://developer.android.com/static/images/guide/topics/sensors/acceleration.png?authuser=9&hl=he)
אבל כוח הכבידה תמיד משפיע על התאוצה שנמדדת קשר הגומלין הבא:
![A_D=-g-(1/mass)∑F_S](https://developer.android.com/static/images/guide/topics/sensors/acceleration_with_grav.png?authuser=9&hl=he)
מסיבה זו, כשהמכשיר מונח על שולחן (ולא מאיץ), מד התאוצה קורא שהגודל הוא 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 מטר לשנייה פחות כוח הכבידה, כלומר -9.81). מ"ש2).
באופן כללי, מד התאוצה הוא חיישן שמומלץ להשתמש בו אם אתה עוקב אחר התנועה של המכשיר. כמעט לכל טלפון וטאבלט מבוססי Android יש מד תאוצה, והוא צורך בערך 10 פעמים פחות עוצמה מחיישני התנועה האחרים. אחד החסרונות לשיטה הזו הוא שייתכן שתצטרכו להטמיע פילטרים של מעבר נמוך ונתיב גבוה כדי לבטל את כוחות הכבידה ולהפחית את הרעש.
שימוש בג'ירוסקופ
הג'ירוסקופ מודד את קצב הסיבוב ב-RD/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) או החיישן הזה גבוה יותר rate-limited
מערכת הקואורדינטות של החיישן זהה לזו שמשמשת את חיישן התאוצה. הסבב הוא חיובי בכיוון נגד כיוון השעון; כלומר, צופה שצופה ממיקום חיובי כלשהו בציר ה-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 שמפורטים בדף הזה.