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 typu czujnika:

  • Czujniki przyspieszenia liniowego, wektora obrotu, grawitacji, ruchu znaczącego, krokomierza i czujnika kroków są oparte na sprzęcie lub oprogramowaniu.
  • Akcelerometr i żyroskop 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 oprogramowania jest bardziej zmienna, ponieważ często korzystają one z co najmniej jednego czujnika sprzętowego. W zależności od urządzenia te oparte na oprogramowaniu czujniki mogą pobierać dane z akcelerometru i magnetometru lub z żyroskopu.

Czujniki ruchu służą do monitorowania ruchu urządzenia, np. pochylania, potrząsania, obracania lub kołysania. Ruch jest zwykle odzwierciedleniem bezpośrednich działań użytkownika (np. kierowania samochodem w grze lub sterowania piłką w grze), ale może też być odzwierciedleniem środowiska fizycznego, w którym znajduje się urządzenie (np. poruszanie się razem z użytkownikiem podczas jazdy samochodem). W pierwszym przypadku monitorujesz ruch w układzie odniesienia urządzenia lub aplikacji, a w drugim – w układzie odniesienia świata. Czujniki ruchu zwykle nie służą do monitorowania pozycji urządzenia, ale można ich używać z innymi czujnikami, takimi jak czujnik pola geomagnetycznego, aby określić pozycję urządzenia względem układu odniesienia świata (więcej informacji znajdziesz w sekcji Czujniki pozycji).

Wszystkie czujniki ruchu zwracają wielowymiarowe tablice wartości czujnika dla każdego SensorEvent. Podczas pojedynczego zdarzenia związanego z czujnikiem akcelerometr zwraca dane o sile przyspieszenia dla 3 osi, a żyroskop – dane o prędkości obrotu dla 3 osi. Te wartości danych są zwracane w tablicy float (values) wraz z innymi parametrami SensorEvent. Tabela 1 zawiera podsumowanie czujników ruchu dostępnych na platformie Android.

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

Czujnik Dane o zdarzeniach czujnika Opis Jednostki miary
TYPE_ACCELEROMETER SensorEvent.values[0] Siła przyspieszenia wzdłuż osi X (w tym grawitacja). m/s2
SensorEvent.values[1] Siła przyspieszenia wzdłuż osi y (w tym grawitacja).
SensorEvent.values[2] Siła przyspieszenia wzdłuż osi z (w tym grawitacja).
TYPE_ACCELEROMETER_UNCALIBRATED SensorEvent.values[0] Zmierzone przyspieszenie wzdłuż osi X bez żadnej kompensacji błędów. m/s2
SensorEvent.values[1] Zmierzone przyspieszenie wzdłuż osi Y bez żadnej kompensacji błędów.
SensorEvent.values[2] Zmierzone przyspieszenie wzdłuż osi Z bez żadnej kompensacji błędów.
SensorEvent.values[3] Zmierzone przyspieszenie wzdłuż osi X z szacowaną kompensacją stronniczości.
SensorEvent.values[4] Zmierzone przyspieszenie wzdłuż osi Y z szacowaną kompensacją przesunięcia.
SensorEvent.values[5] Zmierzone przyspieszenie wzdłuż osi Z z szacowaną kompensacją przesunięcia.
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 na osi z.
TYPE_GYROSCOPE SensorEvent.values[0] Prędkość obrotu wokół osi X. rad/s
SensorEvent.values[1] Prędkość 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] Prędkość obrotu (bez kompensacji dryfu) wokół osi Y.
SensorEvent.values[2] Szybkość obrotu (bez kompensacji dryfu) wokół osi z.
SensorEvent.values[3] Szacowana zmiana wokół osi x.
SensorEvent.values[4] Szacowany dryft wzdłuż osi Y.
SensorEvent.values[5] Szacowany dryft 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ładnik wektora obrotu wzdłuż osi X (x * sin(θ/2)). bez jednostek
SensorEvent.values[1] Składnik wektora obrotu wzdłuż osi y (y * sin(θ/2)).
SensorEvent.values[2] Składnik wektora obrotu wzdłuż osi z (z * sin(θ/2)).
SensorEvent.values[3] Składnik skalarny wektora obrotu ((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, gdy czujnik był włączony. Kroki
TYPE_STEP_DETECTOR Nie dotyczy Nie dotyczy Nie dotyczy

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

Czujnik wektora obrotu i czujnik przyspieszenia są najczęściej używanymi czujnikami do wykrywania i monitorowania ruchu. Czujnik wektorowy obrotu jest szczególnie wszechstronny i może być używany do wykonywania wielu zadań związanych z ruchy, takich jak wykrywanie gestów, monitorowanie zmiany kąta i monitorowanie względnych zmian orientacji. Na przykład czujnik wektorowy obrotu jest idealny, jeśli tworzysz grę, aplikację rozszerzonej rzeczywistości, dwu- lub trójwymiarowy kompas albo aplikację do stabilizacji obrazu. W większości przypadków użycie tych czujników jest lepszym wyborem niż użycie akcelerometru, czujnika pola geomagnetycznego lub czujnika orientacji.

Czujniki projektu Android Open Source

Projekt Android Open Source (AOSP) udostępnia 3 oparte na oprogramowaniu czujniki ruchu: czujnik przyspieszenia grawitacyjnego, czujnik przyspieszenia liniowego i czujnik wektora obrotu. Te czujniki zostały zaktualizowane w Androidzie 4.0 i teraz korzystają z żyroskopu urządzenia (oprócz innych czujników) w celu poprawy stabilności i wydajności. Jeśli chcesz wypróbować te czujniki, możesz je zidentyfikować, używając metody getVendor() i metody getVersion() (dostawcą jest Google LLC; numer wersji to 3). Identyfikacja tych czujników według dostawcy i numeru wersji jest konieczna, ponieważ system Android traktuje te 3 czujniki jako czujniki dodatkowe. Jeśli na przykład producent urządzenia udostępnia własny czujnik przyspieszenia, czujnik przyspieszenia AOSP będzie widoczny jako dodatkowy czujnik przyspieszenia. Wszystkie te czujniki działają na podstawie ż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 wektora trójwymiarowego wskazującego kierunek i wielkość przyspieszenia grawitacyjnego. Zwykle czujnik ten służy do określania orientacji względnej urządzenia w przestrzeni. Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika przyspieszenia ziemskiego:

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 czujnika przyspieszenia (m/s2), a układ współrzędnych jest taki sam jak w przypadku czujnika przyspieszenia.

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

Korzystanie z akcelerometru liniowego

Czujnik przyspieszenia liniowego dostarcza 3-wymiarowego wektora przyspieszenia wzdłuż każdej osi urządzenia, z wyjątkiem grawitacji. Możesz użyć tej wartości do wykrywania gestów. Wartość może też służyć jako dane wejściowe dla systemu nawigacji bezwładnościowej, który wykorzystuje metodę szacowania. 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 danych przyspieszenia zgodnie z tym wzorem:

linear acceleration = acceleration - acceleration due to gravity

Z tego czujnika zwykle korzysta się, gdy chce się uzyskać dane przyspieszenia 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 ma zawsze przesunięcie, które należy usunąć. Najprostszym sposobem jest wbudowanie w aplikację etapu kalibracji. Podczas kalibracji możesz poprosić użytkownika o ustawienie urządzenia na stole, a następnie odczytać przesunięcia dla wszystkich 3 osi. Następnie możesz odjąć to przesunięcie od bezpośrednich odczytów czujnika przyspieszenia, aby uzyskać rzeczywiste przyspieszenie liniowe.

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

Korzystanie z czujnika wektora obrotu

Wektor obrotu 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ć wystąpienie domyślnego czujnika wektorowego obrotu:

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 obrotu wyraża się w ten sposób:

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

gdzie moduł wektora obrotu jest równy sin(θ/2), a kierunek wektora obrotu jest równy kierunkowi osi obrotu.

Rysunek 1. System współrzędnych używany przez czujnik wektora obrotu.

3 elementy wektora obrotu są równe 3 ostatnim elementom jednostkowego kwaterniony (cos(θ/2), x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)). Elementy wektora obrotu są bezwymiarowe. Osie x, y i z są definiowane tak samo jak w przypadku czujnika przyspieszenia. Współrzędnościowy system odniesienia jest zdefiniowany jako bezpośrednia baza ortonormalna (patrz rysunek 1). Ten układ współrzędnych ma te cechy:

  • X jest zdefiniowane jako iloczyn wektorowy Y x Z. Jest ona styczna do gruntu w bieżącej lokalizacji urządzenia i wskazuje w przybliżeniu na wschód.
  • Y jest styczna do powierzchni w bieżącej lokalizacji urządzenia i wskazuje w kierunku geomagnetycznego bieguna północnego.
  • Z wskazuje niebo i jest prostopadły do płaszczyzny horyzontu.

Przykładowa aplikacja, która pokazuje, jak używać czujnika wektorowego obrotu, znajduje się w pliku RotationVectorDemo.java.

Korzystanie z czujnika ruchu znaczącego

Czujnik ruchu wyzwala 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 spowodować zmianę lokalizacji użytkownika, np. chodzenie, jazda na rowerze lub siedzenie w ruchu. Poniższy kod pokazuje, jak uzyskać instancję domyślnego czujnika ruchu i jak zarejestrować listenera 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 w artykule TriggerEventListener.

Korzystanie z czujnika krokomierza

Czujnik krokomierza podaje liczbę kroków wykonanych przez użytkownika od ostatniego ponownego uruchomienia, gdy czujnik był włączony. Licznik kroków ma większą latencję (do 10 sekund), ale jest dokładniejszy niż czujnik krokomierza.

Uwaga: aby aplikacja mogła korzystać z tego czujnika na urządzeniach z Androidem 10 (poziom interfejsu API 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 z Twoją aplikacją, użyj klasy JobScheduler, aby pobierać bieżącą wartość z czujnika krokomierza w określonym odstępie czasu. Chociaż różne typy aplikacji wymagają różnych interwałów odczytu czujnika, należy je robić tak długie, jak to możliwe, chyba że aplikacja wymaga danych z czujnika w czasie rzeczywistym.

Korzystanie z czujnika krokomierza

Detektor kroków wyzwala zdarzenie za każdym razem, gdy użytkownik wykona krok. Opóźnienie powinno wynosić mniej niż 2 sekundy.

Uwaga: aby aplikacja mogła korzystać z tego czujnika na urządzeniach z Androidem 10 (poziom interfejsu API 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 danymi nieprzetworzonymi

Te czujniki dostarczają aplikacji surowych danych o siłach liniowych i siłach obrotowych działających na urządzenie. Aby efektywnie korzystać z wartości z tych czujników, musisz odfiltrowywać czynniki środowiskowe, takie jak grawitacja. Aby zmniejszyć szum, konieczne może być też zastosowanie algorytmu wygładzania do trendu wartości.

Korzystanie z akcelerometru

Czujnik przyspieszenia mierzy przyspieszenie zastosowane do urządzenia, 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 szybkością.

W ujęciu teoretycznym czujnik przyspieszenia określa przyspieszenie zastosowane do urządzenia (Ad) przez pomiar sił działających na sam czujnik (Fs) przy użyciu następującego związku:

A_D=-(1/mass)∑F_S

Siła grawitacji zawsze wpływa jednak na zmierzone przyspieszenie zgodnie z tym wzorem:

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

Dlatego gdy urządzenie znajduje się na stole (i nie przyspiesza), akcelerometr odczytuje wartość g = 9,81 m/s2. Podobnie, gdy urządzenie jest w wolnym spadku i szybko przyspiesza w kierunku ziemi z przyspieszeniem 9,81 m/s2, jego akcelerometr rejestruje wartość g = 0 m/s2. Dlatego, aby zmierzyć rzeczywiste przyspieszenie urządzenia, z danych akcelerometru należy usunąć wpływ siły grawitacji. Można to osiągnąć, stosując filtr górnoprzepustowy. Z drugiej strony, filtr dolnoprzepustowy może być użyty do wyizolowania siły grawitacji. Ten 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 jest obliczana na podstawie stałej czasowej (t), która jest przybliżoną wartością opóźnienia dodanego przez filtr do zdarzeń czujnika, oraz współczynnika dostarczania zdarzeń przez czujnik (dt). W tym przykładzie kodu na potrzeby demonstracji użyto wartości alfa 0,8. Jeśli używasz tej metody filtrowania, może być konieczne wybranie innej wartości alfa.

Akcelerometry używają standardowego systemu współrzędnych czujnika. W praktyce oznacza to, że gdy urządzenie leży na stole w naturalnej orientacji, obowiązują te warunki:

  • Jeśli naciśniesz urządzenie po lewej stronie (czyli przesuniesz je w prawo), wartość przyspieszenia x będzie dodatnia.
  • Jeśli naciśniesz urządzenie od dołu (czyli odsuniesz je od siebie), wartość przyspieszenia w osi Y będzie dodatnia.
  • Jeśli popchniesz urządzenie w kierunku nieba w przyspieszeniu A m/s2, wartość przyspieszenia z będzie równa A + 9,81, co odpowiada przyspieszeniu urządzenia (+A m/s2) pomniejszonemu o siły grawitacyjne (-9,81 m/s2).
  • Wartość przyspieszenia urządzenia nieruchomego to +9,81, co odpowiada przyspieszeniu urządzenia (0 m/s2 minus siła grawitacji, która wynosi -9,81 m/s2).

Jeśli chcesz monitorować ruch urządzenia, użyj akcelerometru. Prawie każdy telefon i tablet z Androidem ma akcelerometr, który zużywa około 10 razy mniej energii niż inne czujniki ruchu. Jednym z mankamentów jest to, że może być konieczne wdrożenie filtrów dolnoprzepustowych i górnoprzepustowych, aby wyeliminować siły grawitacyjne i zredukować 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ć wystąpienie 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 szybkością.

Układ współrzędnych czujnika jest taki sam jak w przypadku czujnika przyspieszenia. Obrót jest dodatni w kierunku przeciwnym do ruchu wskazówek zegara, czyli obserwator patrzący z jakiejś dodatniej lokalizacji na osi x, y lub z urządzenia umieszczonego na początku raportuje dodatni obrót, jeśli urządzenie obraca się w kierunku przeciwnym do ruchu wskazówek zegara. Jest to standardowa definicja matematyczna pozytywnego obrotu i nie jest tożsama z definicją przewijania używaną przez czujnik orientacji.

Zazwyczaj dane z żyroskopu są integrowane w czasie, aby obliczyć rotację opisującą zmianę kątów w interwale czasowym. Może to obejmować np. te funkcje:

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ą danych bez filtrowania ani korygowania szumów i błędów (błędu systematycznego). W praktyce szum i drift żyroskopu powodują błędy, które trzeba skompensować. Zwykle określa się dryft (błąd systematyczny) i szum, monitorując inne czujniki, takie jak czujnik grawitacji lub akcelerometr.

Korzystanie z nieskalibrowanego żyroskopu

Nieskalibrowany żyroskop działa podobnie do żyroskopu, z tym że nie stosuje się do niego kompensacji dryftu. kalibracja fabryczna i kompensacja temperatury nadal mają zastosowanie do szybkości obrotów; Nieskalibrowany żyroskop jest przydatny do postprocessingu i zszywania danych orientacji. Ogólnie rzecz biorąc, gyroscope_event.values[0] będzie zbliżone do uncalibrated_gyroscope_event.values[0] - uncalibrated_gyroscope_event.values[3].

calibrated_x ~= uncalibrated_x - bias_estimate_x

Uwaga: nieskalibrowane czujniki zapewniają bardziej surowe wyniki i mogą zawierać pewne odchylenia, ale ich pomiary zawierają mniej skoków wynikających z poprawek wprowadzonych podczas kalibracji. Niektóre aplikacje mogą preferować te nieskalibrowane wyniki, ponieważ są one płynniejsze i bardziej niezawodne. Jeśli na przykład aplikacja próbuje przeprowadzić własną fuzje czujników, wprowadzenie kalibracji może zniekształcić wyniki.

Oprócz prędkości obrotu nieskalibrowany żyroskop podaje też szacowany dryf wokół każdej osi. Poniższy kod pokazuje, jak uzyskać instancję domyślnego nieskalibrowanego ż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 dodatkowe zastosowania interfejsów API opisanych na tej stronie.

Przeczytaj też