Czujniki ruchu

Platforma Android udostępnia kilka czujników, które umożliwiają monitorowanie ruchu urządzenia.

Możliwe architektury czujników różnią się w zależności od ich typu:

  • Czujniki grawitacji, przyspieszenia liniowego, wektora rotacji, znaczącego ruchu, licznika kroków i wykrywania kroków są oparte na sprzęcie lub oprogramowaniu.
  • Czujniki akcelerometru i żyroskopu są zawsze oparte na sprzęcie.

Większość urządzeń z Androidem ma akcelerometr, a wiele z nich ma też żyroskop. Dostępność czujników opartych na oprogramowaniu jest bardziej zmienna, ponieważ często korzystają one z co najmniej jednego czujnika sprzętowego do uzyskiwania danych. W zależności od urządzenia te czujniki oprogramowania mogą uzyskiwać dane z akcelerometru i magnetometru lub z żyroskopu.

Czujniki ruchu przydają się do monitorowania ruchu urządzenia, np. pochylenia, wstrząsów, obrotów czy kołysania. Ruch zwykle odzwierciedla bezpośrednie działanie użytkownika (np. sterowanie samochodem w grze lub piłką), ale może też odzwierciedlać środowisko fizyczne, w którym znajduje się urządzenie (np. poruszanie się wraz z Tobą podczas jazdy samochodem). W pierwszym przypadku monitorujesz ruch względem układu odniesienia urządzenia lub aplikacji, a w drugim – względem układu odniesienia świata. Same czujniki ruchu nie są zwykle używane do monitorowania pozycji urządzenia, ale mogą być używane z innymi czujnikami, np. czujnikiem pola geomagnetycznego, do określania pozycji urządzenia względem układu odniesienia świata (więcej informacji znajdziesz w sekcji Czujniki położenia).

Wszystkie czujniki ruchu zwracają wielowymiarowe tablice wartości czujników dla każdego SensorEvent. Na przykład podczas jednego zdarzenia czujnika akcelerometr zwraca dane o sile przyspieszenia dla 3 osi współrzędnych, a żyroskop – dane o szybkości obrotu dla 3 osi współrzędnych. Te wartości danych są zwracane w tablicy float (values) wraz z innymi parametrami SensorEvent. W tabeli 1 podsumowano czujniki ruchu dostępne na platformie Android.

Tabela 1. Czujniki ruchu obsługiwane na platformie Android.

Czujnik Dane zdarzeń z czujników Opis Jednostki miary
TYPE_ACCELEROMETER SensorEvent.values[0] Siła przyspieszenia wzdłuż osi X (z uwzględnieniem grawitacji). m/s2
SensorEvent.values[1] Siła przyspieszenia wzdłuż osi Y (z uwzględnieniem grawitacji).
SensorEvent.values[2] Siła przyspieszenia wzdłuż osi z (z uwzględnieniem grawitacji).
TYPE_ACCELEROMETER_UNCALIBRATED SensorEvent.values[0] Zmierzona wartość przyspieszenia wzdłuż osi X bez kompensacji odchyleń. m/s2
SensorEvent.values[1] Zmierzona wartość przyspieszenia wzdłuż osi Y bez kompensacji odchyleń.
SensorEvent.values[2] Zmierzona wartość przyspieszenia wzdłuż osi Z bez kompensacji odchyleń.
SensorEvent.values[3] Zmierzona wartość przyspieszenia wzdłuż osi X z szacunkową kompensacją odchylenia.
SensorEvent.values[4] Zmierzona wartość przyspieszenia wzdłuż osi Y z szacowaną kompensacją odchylenia.
SensorEvent.values[5] Zmierzona wartość przyspieszenia wzdłuż osi Z z szacowaną kompensacją odchylenia.
TYPE_GRAVITY SensorEvent.values[0] Siła grawitacji wzdłuż osi x. m/s2
SensorEvent.values[1] Siła grawitacji wzdłuż osi y.
SensorEvent.values[2] Siła grawitacji wzdłuż osi z.
TYPE_GYROSCOPE SensorEvent.values[0] Szybkość obrotu wokół osi x. rad/s
SensorEvent.values[1] Szybkość obrotu wokół osi Y.
SensorEvent.values[2] Szybkość obrotu wokół osi Z.
TYPE_GYROSCOPE_UNCALIBRATED SensorEvent.values[0] Szybkość obrotu (bez kompensacji dryfu) wokół osi X. rad/s
SensorEvent.values[1] Szybkość obrotu (bez kompensacji dryfu) wokół osi Y.
SensorEvent.values[2] Szybkość obrotu (bez kompensacji dryfu) wokół osi Z.
SensorEvent.values[3] Szacowany dryf wokół osi X.
SensorEvent.values[4] Szacowane odchylenie wokół osi Y.
SensorEvent.values[5] Szacowany dryf wokół osi z.
TYPE_LINEAR_ACCELERATION SensorEvent.values[0] Siła przyspieszenia wzdłuż osi X (z wyłączeniem grawitacji). m/s2
SensorEvent.values[1] Siła przyspieszenia wzdłuż osi y (z wyłączeniem grawitacji).
SensorEvent.values[2] Siła przyspieszenia wzdłuż osi z (z wyłączeniem grawitacji).
TYPE_ROTATION_VECTOR SensorEvent.values[0] Składowa wektora rotacji wzdłuż osi X (x * sin(θ/2)). Bez jednostek
SensorEvent.values[1] Składowa wektora rotacji wzdłuż osi Y (y * sin(θ/2)).
SensorEvent.values[2] Składowa wektora rotacji wzdłuż osi z (z * sin(θ/2)).
SensorEvent.values[3] Skalarny składnik wektora rotacji (cos(θ/2)).1
TYPE_SIGNIFICANT_MOTION Nie dotyczy Nie dotyczy Nie dotyczy
TYPE_STEP_COUNTER SensorEvent.values[0] Liczba kroków wykonanych przez użytkownika od ostatniego ponownego uruchomienia urządzenia, gdy czujnik był aktywny. Kroki
TYPE_STEP_DETECTOR Nie dotyczy Nie dotyczy Nie dotyczy

1 Składnik skalarny jest wartością opcjonalną.

Czujnik wektora rotacji i czujnik grawitacji są najczęściej używane do wykrywania i monitorowania ruchu. Czujnik wektora rotacji jest szczególnie wszechstronny i może być używany do wielu zadań związanych z ruchem, takich jak wykrywanie gestów, monitorowanie zmian kątowych i monitorowanie względnych zmian orientacji. Na przykład czujnik wektora rotacji jest idealny, jeśli tworzysz grę, aplikację rzeczywistości rozszerzonej, kompas 2D lub 3D albo aplikację do stabilizacji obrazu z kamery. W większości przypadków używanie tych czujników jest lepszym rozwiązaniem niż korzystanie z akcelerometru i czujnika pola geomagnetycznego lub czujnika orientacji.

Czujniki w projekcie Android Open Source

Projekt Android Open Source (AOSP) udostępnia 3 czujniki ruchu oparte na oprogramowaniu: czujnik grawitacji, czujnik przyspieszenia liniowego i czujnik wektora rotacji. Te czujniki zostały zaktualizowane w Androidzie 4.0 i teraz korzystają z żyroskopu urządzenia (oprócz innych czujników), aby poprawić stabilność i wydajność. Jeśli chcesz wypróbować te czujniki, możesz je zidentyfikować za pomocą metody getVendor() i metody getVersion() (dostawcą jest Google LLC, a numer wersji to 3). Identyfikacja tych czujników według dostawcy i numeru wersji jest konieczna, ponieważ system Android uznaje te 3 czujniki za czujniki dodatkowe. Jeśli na przykład producent urządzenia udostępnia własny czujnik grawitacji, czujnik grawitacji AOSP będzie wyświetlany jako dodatkowy czujnik grawitacji. Wszystkie 3 czujniki korzystają z żyroskopu. Jeśli urządzenie nie ma żyroskopu, te czujniki nie są widoczne i nie można ich używać.

Korzystanie z czujnika grawitacyjnego

Czujnik grawitacji dostarcza trójwymiarowy wektor wskazujący kierunek i wartość siły grawitacji. Zwykle ten czujnik służy do określania względnego położenia urządzenia w przestrzeni. Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika grawitacji:

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);

Jednostki są takie same jak w przypadku akcelerometru (m/s2), a układ współrzędnych jest taki sam jak w przypadku akcelerometru.

Uwaga: gdy urządzenie jest w spoczynku, dane wyjściowe czujnika grawitacji powinny być identyczne z danymi akcelerometru.

Korzystanie z akcelerometru liniowego

Czujnik przyspieszenia liniowego dostarcza trójwymiarowy wektor reprezentujący przyspieszenie wzdłuż każdej osi urządzenia z wyłączeniem grawitacji. Możesz użyć tej wartości do wykrywania gestów. Wartość ta może też służyć jako dane wejściowe dla inercyjnego systemu nawigacyjnego, który wykorzystuje nawigację zliczeniową. Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika przyspieszenia liniowego:

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);

Ten czujnik dostarcza dane o przyspieszeniu zgodnie z tą zależnością:

linear acceleration = acceleration - acceleration due to gravity

Zwykle używasz tego czujnika, gdy chcesz uzyskać dane o przyspieszeniu bez wpływu grawitacji. Możesz na przykład użyć tego czujnika, aby sprawdzić, jak szybko jedzie Twój samochód. Czujnik przyspieszenia liniowego zawsze ma przesunięcie, które musisz usunąć. Najprostszym sposobem jest wbudowanie w aplikację kroku kalibracji. Podczas kalibracji możesz poprosić użytkownika o położenie urządzenia na stole, a następnie odczytać wartości przesunięcia dla wszystkich trzech osi. Następnie możesz odjąć tę wartość od bezpośrednich odczytów czujnika przyspieszenia, aby uzyskać rzeczywiste przyspieszenie liniowe.

Układ współrzędnych czujnika jest taki sam jak w przypadku akcelerometru, podobnie jak jednostki miary (m/s2).

Korzystanie z czujnika wektora rotacji

Wektor rotacji reprezentuje orientację urządzenia jako kombinację kąta i osi, w której urządzenie zostało obrócone o kąt θ wokół osi (x, y lub z). Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika wektora rotacji:

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);

3 elementy wektora rotacji są wyrażone w ten sposób:

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

Wektor rotacji ma długość równą sin(θ/2), a jego kierunek jest zgodny z kierunkiem osi rotacji.

Rysunek 1. Układ współrzędnych używany przez czujnik wektora rotacji.

3 elementy wektora rotacji są równe 3 ostatnim komponentom kwaternionu jednostkowego (cos(θ/2), x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)). Elementy wektora rotacji są bezwymiarowe. Osie x, y i z są zdefiniowane w taki sam sposób jak w przypadku czujnika przyspieszenia. Referencyjny układ współrzędnych jest zdefiniowany jako bezpośrednia baza ortonormalna (patrz rysunek 1). Ten układ współrzędnych ma te cechy:

  • X jest zdefiniowany jako iloczyn wektorowy Y × Z. Jest styczna do powierzchni ziemi w bieżącej lokalizacji urządzenia i wskazuje w przybliżeniu wschód.
  • Oś Y jest styczna do powierzchni ziemi w bieżącej lokalizacji urządzenia i skierowana w stronę północnego bieguna geomagnetycznego.
  • Oś Z jest skierowana w stronę nieba i jest prostopadła do płaszczyzny gruntu.

Przykładową aplikację, która pokazuje, jak używać czujnika wektora rotacji, znajdziesz w pliku RotationVectorDemo.java.

Używanie czujnika wykrywającego znaczący ruch

Czujnik wykrywania znaczącego ruchu wywołuje zdarzenie za każdym razem, gdy wykryje znaczący ruch, a następnie wyłącza się. Znaczący ruch to ruch, który może prowadzić do zmiany lokalizacji użytkownika, np. chodzenie, jazda na rowerze lub siedzenie w poruszającym się samochodzie. Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika wykrywającego znaczące ruchy i jak zarejestrować odbiornik zdarzeń:

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);

Więcej informacji znajdziesz na stronie TriggerEventListener.

Korzystanie z czujnika krokomierza

Czujnik licznika kroków podaje liczbę kroków wykonanych przez użytkownika od ostatniego ponownego uruchomienia urządzenia, gdy czujnik był aktywny. Licznik kroków ma większe opóźnienie (do 10 sekund), ale jest dokładniejszy niż czujnik wykrywający kroki.

Uwaga: aby aplikacja mogła korzystać z tego czujnika na urządzeniach z Androidem 10 (API na poziomie 29) lub nowszym, musisz zadeklarować uprawnienie ACTIVITY_RECOGNITION.

Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika licznika kroków:

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);

Aby oszczędzać baterię na urządzeniach, na których działa Twoja aplikacja, używaj klasy JobScheduler do pobierania bieżącej wartości z czujnika krokomierza w określonych odstępach czasu. Różne typy aplikacji wymagają różnych interwałów odczytu danych z czujników, ale jeśli aplikacja nie wymaga danych z czujników w czasie rzeczywistym, interwał ten powinien być jak najdłuższy.

Korzystanie z czujnika wykrywania kroków

Czujnik wykrywania kroków wywołuje zdarzenie za każdym razem, gdy użytkownik zrobi krok. Opóźnienie powinno być mniejsze niż 2 sekundy.

Uwaga: aby aplikacja mogła korzystać z tego czujnika na urządzeniach z Androidem 10 (API na poziomie 29) lub nowszym, musisz zadeklarować uprawnienie ACTIVITY_RECOGNITION.

Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika wykrywania kroków:

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);

Praca z nieprzetworzonymi danymi

Te czujniki dostarczają aplikacji surowe dane o siłach liniowych i obrotowych działających na urządzenie. Aby skutecznie korzystać z wartości z tych czujników, musisz odfiltrować czynniki środowiskowe, takie jak grawitacja. Może też być konieczne zastosowanie algorytmu wygładzania do trendu wartości, aby zredukować szum.

Korzystanie z akcelerometru

Czujnik przyspieszenia mierzy przyspieszenie działające na urządzenie, w tym siłę grawitacji. Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika przyspieszenia:

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);

Uwaga: jeśli Twoja aplikacja jest kierowana na Androida 12 (poziom API 31) lub nowszego, ten czujnik jest ograniczony.

Czujnik przyspieszenia określa przyspieszenie, które jest stosowane do urządzenia (Ad), mierząc siły działające na sam czujnik (Fs) zgodnie z tą zależnością:

A_D=-(1/mass)∑F_S

Siła grawitacji zawsze wpływa jednak na mierzone przyspieszenie zgodnie z tą zależnością:

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

Dlatego, gdy urządzenie leży na stole (i nie przyspiesza), akcelerometr odczytuje wartość g = 9,81 m/s2. Podobnie, gdy urządzenie spada swobodnie, a więc szybko przyspiesza w kierunku ziemi z prędkością 9,81 m/s2, akcelerometr odczytuje wartość g = 0 m/s2. Dlatego, aby zmierzyć rzeczywiste przyspieszenie urządzenia, należy usunąć z danych akcelerometru wpływ siły grawitacji. Można to osiągnąć, stosując filtr górnoprzepustowy. Z kolei filtr dolnoprzepustowy może służyć do wyodrębniania siły grawitacji. Poniższy przykład pokazuje, jak to zrobić:

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];
}

Uwaga: do filtrowania danych z czujników możesz używać wielu różnych technik. Powyższy przykładowy kod używa prostej stałej filtra (alfa) do utworzenia filtra dolnoprzepustowego. Ta stała filtra pochodzi od stałej czasowej (t), która jest przybliżonym odzwierciedleniem opóźnienia, jakie filtr dodaje do zdarzeń czujnika, oraz szybkości dostarczania zdarzeń przez czujnik (dt). W przykładzie kodu użyto wartości alfa 0,8. Jeśli używasz tej metody filtrowania, może być konieczne wybranie innej wartości alfa.

Akcelerometry korzystają ze standardowego układu współrzędnych czujnika. W praktyce oznacza to, że gdy urządzenie leży płasko na stole w naturalnej orientacji, obowiązują te warunki:

  • Jeśli przesuniesz urządzenie w lewo (tak, aby poruszało się w prawo), wartość przyspieszenia w osi X będzie dodatnia.
  • Jeśli popchniesz urządzenie w dół (tak, aby oddaliło się od Ciebie), wartość przyspieszenia w osi Y będzie dodatnia.
  • Jeśli urządzenie jest pchane w górę z przyspieszeniem A m/s2, wartość przyspieszenia w osi Z jest równa A + 9,81, co odpowiada przyspieszeniu urządzenia (+A m/s2) pomniejszonemu o siłę grawitacji (–9,81 m/s2).
  • Urządzenie stacjonarne będzie miało wartość przyspieszenia +9,81, co odpowiada przyspieszeniu urządzenia (0 m/s2 minus siła grawitacji, która wynosi -9,81 m/s2).

Akcelerometr jest dobrym czujnikiem do monitorowania ruchu urządzenia. Akcelerometr jest dostępny na niemal każdym telefonie i tablecie z Androidem. Zużywa on około 10 razy mniej energii niż inne czujniki ruchu. Jedną z wad jest to, że może być konieczne zastosowanie filtrów dolnoprzepustowych i górnoprzepustowych, aby wyeliminować siły grawitacji i zmniejszyć szum.

Korzystanie z żyroskopu

Żyroskop mierzy szybkość obrotu w rad/s wokół osi x, y i z urządzenia. Poniższy kod pokazuje, jak uzyskać instancję domyślnego żyroskopu:

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);

Uwaga: jeśli Twoja aplikacja jest kierowana na Androida 12 (poziom API 31) lub nowszego, ten czujnik jest ograniczony.

Układ współrzędnych czujnika jest taki sam jak w przypadku czujnika przyspieszenia. Obrót w kierunku przeciwnym do ruchu wskazówek zegara jest dodatni. Oznacza to, że obserwator patrzący z dowolnego dodatniego punktu na osi X, Y lub Z na urządzenie umieszczone w punkcie początkowym zgłosi dodatni obrót, jeśli urządzenie będzie obracać się w kierunku przeciwnym do ruchu wskazówek zegara. Jest to standardowa definicja matematyczna obrotu w kierunku dodatnim, która różni się od definicji przechyłu używanej przez czujnik orientacji.

Zwykle dane wyjściowe żyroskopu są integrowane w czasie, aby obliczyć rotację opisującą zmianę kątów w danym przedziale czasu. Na przykład:

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;
}

Standardowe żyroskopy dostarczają surowe dane o rotacji bez filtrowania ani korekty szumu i dryfu (odchylenia). W praktyce szum i dryf żyroskopu powodują błędy, które należy skompensować. Odchylenie (bias) i szum zwykle określa się, monitorując inne czujniki, takie jak czujnik grawitacji lub akcelerometr.

Korzystanie z niekalibrowanego żyroskopu

Nie skalibrowany żyroskop jest podobny do żyroskopu, z tą różnicą, że do szybkości obrotu nie jest stosowana kompensacja dryfu żyroskopu. Kalibracja fabryczna i kompensacja temperatury są nadal stosowane do szybkości obrotu. Nie skalibrowany żyroskop jest przydatny do przetwarzania końcowego i łączenia danych o orientacji. Ogólnie rzecz biorąc, wartość gyroscope_event.values[0] będzie zbliżona do wartości uncalibrated_gyroscope_event.values[0] - uncalibrated_gyroscope_event.values[3]. Oznacza to, że

calibrated_x ~= uncalibrated_x - bias_estimate_x

Uwaga: niekalibrowane czujniki dostarczają więcej surowych wyników i mogą zawierać pewne odchylenia, ale ich pomiary zawierają mniej skoków wynikających z korekt wprowadzanych podczas kalibracji. Niektóre aplikacje mogą preferować te niekalibrowane wyniki, ponieważ są one bardziej płynne i wiarygodne. Jeśli na przykład aplikacja próbuje przeprowadzić własną fuzję czujników, wprowadzenie kalibracji może zniekształcić wyniki.

Oprócz szybkości obrotu niekalibrowany żyroskop podaje też szacowany dryf wokół każdej osi. Poniższy kod pokazuje, jak uzyskać instancję domyślnego niekalibrowanego żyroskopu:

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);

Dodatkowe przykłady kodu

Przykład BatchStepSensor pokazuje, jak używać interfejsów API opisanych na tej stronie.

Warto też przeczytać