Picking up from where I got to before, I’m going to follow the path where the accelerometer raw data ellipsoidal space is near enough to spherical that I can forget the rx*_gain values.

The errors this will introduce should be relatively inconsequential: first the gains are only a per-cent or two; secondly, the critical factor here is that the sensors read zero when horizontal (i.e. corrected by offsets), but any minor gain errors will lead to angle and speed errors, but critically, these will still read zero when they should, and the errors will be handled by the PIDs.

On that basis, let’s see where that leads. Here’s what we have to work with so far

qx = (rx + rx_{off}) * rx_{gain}
qy = (ry + ry_{off}) * ry_{gain}
qz = (rz + rz_{off}) * rz_{gain}
qx^{2} + qy^{2} + qz^{2} = 1
ex = 0
ey = 0
ez = 1
|ex| |cos(pitch), 0, -sin(pitch)||qx|
|ey| = | 0, cos(roll), -sin(roll)||qy|
|ez| |sin(pitch), sin(roll), cos(pitch).cos(roll)||qz|
pitch = atan(qx / qz)
roll = atan(qy / qz)

So without the gain excluded, we have the following – and we have 3 variables fewer to calculate.

qx = (rx' + rx'_{off})
qy = (ry' + ry'_{off})
qz = (rz' + rz'_{off})

Sat on the ground, the only force acting on Phoebe is vertical gravity, so the earth axis values should read (0, 0, 1) g, as should the offseted sensors once matrixed.

ex = 0 = (rx' + rx'_{off}) * cos(pitch) - (rz' + rz'_{off}) * sin(pitch)
ey = 0 = (ry' + ry'_{off}) * cos(roll) - (rz' + rz'_{off}) * sin(roll)
ez = 1 = (rz' + rz'_{off}) * cos(pitch) * cos(roll) + (rx' + rx'_{off}) * sin(pitch) + (ry' + ry'_{off}) * sin(roll)

Replacing pitch and roll with the raw data gives

pitch = atan((rx' + rx'_{off}) / (rz' + rz'_{off}))
roll = atan((ry' + ry'_{off}) / (rz' + rz'_{off}))

That leaves us with only the 3 offset variables, r*_{off} to calculate based upon the raw sensor data, r*.

(rx' + rx'_{off}) * cos(pitch) = (rz' + rz'_{off}) * sin(pitch)
(ry' + ry'_{off}) * cos(roll) = (rz' + rz'_{off}) * sin(roll)
(rz' + rz'_{off}) * cos(pitch) * cos(roll) + (rx' + rx'_{off}) * sin(pitch) + (ry' + ry'_{off}) * sin(roll) = 1

Not the easiest of equations to solve as I don’t think I can simplify the atan’s sine’s and cosine’s, but I’ll have a go.

(rx' + rx'_{off}) * cos(pitch) = (rz' + rz'_{off}) * sin(pitch)

Dividing both sides by cos(pitch) leads to

(rx' + rx'_{off}) = (rz' + rz'_{off}) * tan(pitch)

Substituting for tan(pitch) leads to

(rx' + rx'_{off}) = (rz' + rz'_{off}) * (rx' + rx'_{off}) / (rz' + rz'_{off})

Thus successfully proving that

1 = 1

^{10}/_{10} for effort, ^{0}/_{10} for results!