Search code examples
javaandroidaccelerometer

Integrating Android Accelerometer to Find Velocity and Position


I am attempting to calculate displacement using the accelerometer on an Android device (Sensor.TYPE_LINEAR_ACCELERATION). Here is my OnSensorChanged() method:

public void onSensorChanged(SensorEvent event) {
    accelX = event.values[0];
    accelY = event.values[1];
    accelZ = event.values[2];

    long currentTime = System.currentTimeMillis() / 1000;

    if(prevTime == 0) prevTime = currentTime;

    long interval = currentTime - prevTime;
    prevTime = currentTime;     

    velX += accelX * interval;
    velY += accelY * interval;
    velZ += accelZ * interval;

    distX += prevVelX + velX * interval;
    distY += prevVelY + velY * interval;
    distZ += prevVelZ + velZ * interval;

    prevAccelX = accelX;        
    prevAccelY = accelY;        
    prevAccelZ = accelZ;

    prevVelX = velX;
    prevVelY = velY;
    prevVelZ = velZ;
}

Velocity, however, doesn't return to 0 after any sort of movement. Deceleration has little effect unless it is very sudden. This leads to some very inaccurate distance values.

The accelerometer is also very noisy, so I added this:

accelX = (accelX * accelKFactor) + prevAccelX * (1 - accelKFactor);
accelY = (accelY * accelKFactor) + prevAccelY * (1 - accelKFactor);
accelY = (accelX * accelKFactor) + prevAccelY * (1 - accelKFactor);

Where accelKFactor = .01

Am I missing something obvious?

My end goal is simply to be able to tell if the device has moved more than ~10 ft or not, but right now, my data is pretty much useless.

EDIT:

I found part of the problem. currentTime was a long, but I was dividing the system time in ms by 1000 to get seconds. So velocity could only really update every 1s. Changing currentTime to a double helps, but does not entirely solve the problem.


Solution

  • SensorEvent has a timestamp field, which is the time in nanoseconds. Use this instead of System.currentTimeMillis(). Here's an example from the documentation:

    private static final float NS2S = 1.0f / 1000000000.0f;
     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;
    

    Tracking displacement this way is extremely sensitive to initial conditions, so this should help.

    Also, you may have better luck with Location.getSpeed(). Consider using the accelerometer only as a backup if you can't get a signal.