Frage

My app needs to show the current bearing of the device using its compass. The code I'm using (below) works perfectly fine on my Galaxy Nexus and Galaxy One, but the compass is spinning around wildly on a Samsung Galaxy S III. I've tried doing a figure-8 to recalibrate the device, but that doesn't change anything. The weird thing is that other compass apps downloaded from Google Play work just fine on the SIII. What could be the issue here?

float[] mGravity;
float[] mGeomagnetic;

public void onSensorChanged( SensorEvent event ) {
    float azimuth = 0f;
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        mGravity = event.values;
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
        mGeomagnetic = event.values;
    if (mGravity != null && mGeomagnetic != null) {
        float R[] = new float[9];
        float I[] = new float[9];
        boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
        if (success) {
            float orientation[] = new float[3];
            SensorManager.getOrientation(R, orientation);
            azimuth = orientation[0]; // orientation contains: azimut, pitch and roll
        }
     }

    //Discard 0.0-values
    if(azimuth == 0.0) { return; }

    //Convert the sensor value to degrees
    azimuth = (float) Math.toDegrees(azimuth); //same as azimuth = -azimuth*360/(2*3.14159f);


    //Smooth the sensor-output
    azimuth = smoothValues(azimuth);
}

//From http://stackoverflow.com/questions/4699417/android-compass-orientation-on-unreliable-low-pass-filter
//SmoothFactorCompass: The easing float that defines how smooth the movement will be (1 is no smoothing and 0 is never updating, my default is 0.5).
//SmoothThresholdCompass: The threshold in which the distance is big enough to turn immediately (0 is jump always, 360 is never jumping, my default is 30).
static final float SmoothFactorCompass = 0.5f;
static final float SmoothThresholdCompass = 30.0f;
float oldCompass = 0.0f;
private float smoothValues (float newCompass){
    if (Math.abs(newCompass - oldCompass) < 180) {
        if (Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
            oldCompass = newCompass;
        }
        else {
            oldCompass = oldCompass + SmoothFactorCompass * (newCompass - oldCompass);
        }
    }
    else {
        if (360.0 - Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
            oldCompass = newCompass;
        }
        else {
            if (oldCompass > newCompass) {
                oldCompass = (oldCompass + SmoothFactorCompass * ((360 + newCompass - oldCompass) % 360) + 360) % 360;
            } 
            else {
                oldCompass = (oldCompass - SmoothFactorCompass * ((360 - newCompass + oldCompass) % 360) + 360) % 360;
            }
        }
    }
    return oldCompass;
}
War es hilfreich?

Lösung

Currently I am investigating compass mechanism on Android and I would recommend to start with low-pass filter in your case. What you need to do - is to apply low-pass filter to both ACCELEROMETER and MAGNETIC_FIELD sensors data. Here is how I implemented that:

private float[] accel;
private float[] geomagnetic;
float R[] = new float[9];
float I[] = new float[9];
float orientation[] = new float[3];

@Override
public void onSensorChanged(SensorEvent event)
{
    synchronized (this)
    {
        float azimuth = -1f;

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            accel = lowPass( event.values.clone(), accel );

        if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
            geomagnetic = lowPass(event.values.clone(), geomagnetic);

        if (accel != null && geomagnetic != null)
        {

            boolean success = SensorManager.getRotationMatrix(R, I,
                    accel, geomagnetic);

            SensorManager.remapCoordinateSystem(R,
                    SensorManager.AXIS_X, SensorManager.AXIS_Z, R);

            if (success)
            {
                SensorManager.getOrientation(R, orientation);
                azimuth = orientation[0]; // orientation contains:
                                         // azimuth, pitch
                                         // and roll
                float newHeading = azimuth * 360 / (2 * 3.14159f);

                //do what you need to do with new heading
            } 
        }
    }
}


/*
 * time smoothing constant for low-pass filter 0 ≤ alpha ≤ 1 ; a smaller
 * value basically means more smoothing See:
 * http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization
 */
static final float ALPHA = 0.15f;

/**
 * @see http
 *      ://en.wikipedia.org/wiki/Low-pass_filter#Algorithmic_implementation
 * @see http
 *      ://developer.android.com/reference/android/hardware/SensorEvent.html
 *      #values
 */

protected float[] lowPass(float[] input, float[] output)
{
    if (output == null)
        return input;

    for (int i = 0; i < input.length; i++)
    {
        output[i] = output[i] + ALPHA * (input[i] - output[i]);
    }
    return output;
}

Andere Tipps

may be you should clone the value from the sensor reading first and then lowpass the filter if this does not work , I know SONY XPeria phone is very sensitive unlike samsung which is quite stable .

In my view , S3 reading is quite ok under android OS 4.3

event.values.clone;

My recent experience couple data feedback force me to apply rotation vector logic on SONY phone loaded with Invesense sensor and it seemed to be working , although I still do not like the choppy response but I have live with that kind of response on SONY android 4.3 OS . It is also appear to me that any phone loaded invensense sensor need to apply the rotation vector for it to work properly

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top