# Unity 3D recreation of Pitch/Roll from accelerometer

I have an accelerometer that is communicating serially to a PC. As I read in the data, I filter it to clean it up and calculate the angle of the pitch and roll. This all works great. Then I want to apply these angles to an object in Unity3D.

I created an object an try to rotate it using

transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(new Vector3(pitchAngle, 0f, rollAngle)), Time.time);


I use this method because I read that if you try and apply Euler angles, there is potential to run into gimbal lock, so I attempted to use Quaternion to fix that. It seems I might still be running into the problem.

If I try something like

transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(new Vector3(pitchAngle, 0f, 0f)), Time.time);


or

transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(new Vector3(0, 0f, rollAngle)), Time.time);


The rotation is perfect, there is no 180 degree flip.

I also tried one more thing that can be ruled out that did not work, I tried,

transform.localRotation =
Quaternion.AngleAxis(rollAngle, Vector3.forward) *
Quaternion.AngleAxis(0, Vector3.up) *
Quaternion.AngleAxis(pitchAngle, Vector3.right);


To describe the numbers on the next three images, the very top line is the calculated angle of pitch and roll (respectively). The line below that is the m/sec^2 (g's) recorded from the accelerometer. The very last line is obtained by transform.eulerAngles.ToString().

The picture above is before pitch crosses 90 degrees

The picture above is after pitch crosses 90 degrees, notice the 180 degree turn

This picture is removing roll from the equation. The angle of the pitch works perfectly fine. transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(new Vector3(pitchAngle, 0f, 0f)), Time.time); The 'roll' also works perfect if pitch is removed. They just don't work well together.
The one thing I noticed is when only rotating one axis, the other two both change from 0 to 180. But I can't simply subtract the 180 from one axis, as I lose my full range of motion.

• Question:
How to I fix this (gimbal lock) problem? After trying Quaternion and failing to use it properly, I am very confused with finding a solution. The math doesn't seem to be clicking in my head to manipulate the angles properly.
• Presumably you're getting an acceletation vector from your accelerometer, yes? Can you skip converting it to an angular representation entirely and work directly with the 3-vector, using eg. Quaternion.LookRotation with an appropriate adjustment? Commented Apr 21, 2017 at 0:29
• @DMGregory I'm receiving my accelerometer data from the serial port as a comma delimited list and using string.Split(','). I glanced at the Quaternion.LookRotation function, I don't fully understand your comment and how that function would work. I'm going to dig into it further and see what I can do. Commented Apr 21, 2017 at 0:40
• The list you're getting, is it a list of angles or a list of acceleration vector components? I'm trying to sort out whether you're computing those roll and pitch angles or just consuming them. Commented Apr 21, 2017 at 0:59
• @DMGregory Oh yea, I'm getting them in as (g) values. So I use Math.Atan2(-x, z) * 180 / Math.PI; for Pitch and Math.Atan2(y, z) * 180 / Math.PI; for Roll. These return my full range of rotation. Which individually they work perfect, but together they cause the unwanted 'flip'. Commented Apr 21, 2017 at 1:07

Whenever I can, I like to avoid explicitly working with angles.

Let's say we have an acceleration vector measured in gs, after you've applied any smoothing & filtering you're using. When the device is at rest this will be roughly a unit vector pointing in the direction of world up. Let's arrange the coordinates into Unity's coordinate system, so that...

• an x+ acceleration means the object is sitting on its left side
• a y+ acceleration means the object is sitting level and not upside-down
• a z+ acceleration means the object has its nose in the air

Quaternion.LookRotation(acceleration, control) gives us a rotation that points the z+ axis in the direction of this locally sensed acceleration, and the y+ axis as close as possible to some control vector.

We can apply a correcting pitch to get an orientation that maps y+ to this sensed direction, and z+ as close as possible to forward (since we don't get yaw information from the accelerometer, we'll just hint it toward zero yaw this way)

var worldToLocal = Quaternion.LookRotation(acceleration, Vector3.back)
* Quaternion.Euler(90f, 0f, 0f);


Or, alternatively, we can choose to not normalize the yaw component, and just choose whichever rotation gives us the least difference from our current orientation:

var worldToLocal = Quaternion.LookRotation(
acceleration,
transform.InverseTransformDirection(Vector3.back)
) * Quaternion.Euler(90f, 0f, 0f);


Because we don't standardize a front/back axis, this will allow the object's yaw to drift over time, but can give less distracting results when rotating end over end (where an object's effective yaw jumps 180 degrees as it turns upside down).

You can also apply a hybrid approach, nudging the yaw back toward zero when the device appears to be at rest, to keep is somewhat leashed-in.

Either way, now we have an orientation describing how we think the world is oriented relative to our sensor. To apply this as a transformation to our object, we just need to invert this to get the orientation of our object relative to the world:

transform.rotation = Quaternion.Inverse(worldToLocal);


Instead of setting the rotation directly, you can of course apply a smoothing here with Quaternion.Lerp or RotateTowards to damp out remaining jitter or spikes in the sensor signal.

• Thanks, this actually makes alot of sense. I seem to be running into a problem though. I set my code up as var worldToLocal = Quaternion.LookRotation(new Vector3(LPFPrevX, LPFPrevY, LPFPrevZ), Vector3.back) * Quaternion.Euler(90f, 0f, 0f); Where the LPF values are just the smoothed X,Y,Z values. It seems when ever either the X or the Y value cross from a positive to negative value or vice versa, the object like to swing a 180. I believe I set your code up correctly though. Please correct if I'm wrong on how this works. Commented Apr 21, 2017 at 2:03
• That sounds like what would happen if the acceleration vector was very close to the z axis. Does it happen when you're pitching the object almost nose-up? If you see this when the object is sitting close to level, then the device's coordinate system might be different from Unity's, and you might need to swap the y and z axes (and possibly negate the x) Commented Apr 21, 2017 at 3:39
• @Timmy you'll have to interpolate between the 2 angles Commented Apr 21, 2017 at 5:59
• @Bálint Isn't that what lerp does? Commented Apr 22, 2017 at 17:45
• @DMGregory Using var worldToLocal = Quaternion.LookRotation(new Vector3(-LPFPrevX, LPFPrevZ, LPFPrevY), Vector3.back) * Quaternion.Euler(90f, 0f, 0f); Keeps the correct orientation of the object in unity, the strange thing is now, the pitch and roll seemed to be swapped with each other, (which can be ok for what I'm doing). So the pitch(which is shown as roll on the screen) works fine. But when rolling the other direction it still does the 180 degree flip. I tried playing around with Quaternion.Euler(90f, 0f, 0f); but the 180 flip still happens on the one axis. I can add pics if needed. Commented Apr 22, 2017 at 17:59