This is the code that you can use in the R stats package to work out the magnetometer calibration. You need to import two libraries into R, the rgl graphics library and minpack.lm for the Levenberg-Marquardt optimisation library.
The input is a csv file of the output from the AHRS software in CALIBRATE_MAG mode, as previously described. The output is all on screen – graphs in their own windows and data at the R console.
library("rgl")
library("minpack.lm")
# Load data - rows are vectors
magData <- read.csv("magnetometer_data20.csv")
# Split data into two matrices, columns are vectors
magM <- t(magData[,1:3])
rownames(magM) <-c('mx', 'my', 'mz')
R <- t(magData[,4:12]) / 1e5
rownames(R) <- c('xx', 'xy', 'xz', 'yx', 'yy', 'yz', 'zx', 'zy', 'zz')
# Plot on screen
plot3d(t(magM), aspect = c(1,1,1), box = FALSE, col="violet")
# use arithmetic mean to get starting estimate for centre of ellipsoid
pStart <- list(a = 1, b = 0, c = 0, d = 0, e = 1, f = 0, g = 0, h = 0, i = 1,
B_x = mean(magM['mx',]), B_y = mean(magM['my',]), B_z = mean(magM['mz',]),
eb_x = 1, eb_y = 0, eb_z = 0)
# Plot starting estimate for visual check:
rgl.spheres(pStart$B_x, pStart$B_y, pStart$B_z, 20, alpha = 1, col = "green", box = FALSE, add = TRUE)
# Optimize over a matrix M, offset B and and external field eb:
residFun<-function(p, m, r) {
Rieb_x = p$eb_x * r['xx',] + p$eb_y * r['yx',] + p$eb_z * r['zx',]
Rieb_y = p$eb_x * r['xy',] + p$eb_y * r['yy',] + p$eb_z * r['zy',]
Rieb_z = p$eb_x * r['xz',] + p$eb_y * r['yz',] + p$eb_z * r['zz',]
M = matrix(c(p$a,p$b,p$c,p$d,p$e,p$f,p$g,p$h,p$i) ,nrow = 3, byrow = TRUE)
B = c(p$B_x, p$B_y, p$B_z)
Rieb = matrix(c(Rieb_x, Rieb_y, Rieb_z), nrow = 3, byrow = TRUE)
residV <- (M %*% Rieb + B)
return (colSums((residV - m)^2))
}
# Perform optimisation, increase maxiter for more iterations if necessary
nls.out <- nls.lm(par = pStart, fn = residFun, m = magM, r = R, control = nls.lm.control(nprint = 1, maxiter = 50))
summary(nls.out)
M <- matrix(coef(nls.out)[1:9], ncol = 3, byrow = TRUE)
B <- c(coef(nls.out)[10:12])
eb <- c(coef(nls.out)[13:15])
# Subtract B from every column then premultiply by inverse of M
magRSR <- solve(M) %*% (magM - B)
# and plot
rgl.open()
bg3d("white")
plot3d(t(magRSR), col = "blue", scale = FALSE, box = FALSE, xlab = 'x', ylab = 'y', zlab = 'z')
# best spherical fit
sphereRadius = mean(sqrt(colSums(magRSR^2)))
rgl.spheres(0, 0, 0, sphereRadius, alpha = 0.2, col = "green", box = FALSE, add = TRUE)
# Plot Earth field estmates in Earth frame
# note: R times a vector measured in body frame gives you earth-frame representation
N = matrix(c( magRSR[1,] * R['xx',] + magRSR[2,] * R['xy',] + magRSR[3,] * R['xz',] ,
magRSR[1,] * R['yx',] + magRSR[2,] * R['yy',] + magRSR[3,] * R['yz',] ,
magRSR[1,] * R['zx',] + magRSR[2,] * R['zy',] + magRSR[3,] * R['zz',] ), nrow = 3, byrow = TRUE )
plot3d(t(N), col = "red", box = TRUE, add= TRUE, alpha = 1.0)
# Yellow blob for mean Earth field vector estimate
plot3d(t(eb), col = "yellow", type = "s", radius = sphereRadius * 0.0174, alpha = 0.7, add = TRUE)
# Green blob at "south pole"
rgl.spheres(0, 0, -sphereRadius, radius = sphereRadius / 25, alpha = 1, col = "green", add = TRUE)
# Examine angular deviation from b0 in z-plane
Dev = matrix(c( 57 * asin((eb[1] * N[2,] - eb[2] * N[1,]) / (sqrt(eb[1]^2 + eb[2]^2 ) * sqrt(N[1,]^2 + N[2,]^2)))))
plot(Dev)
# RESULTS:
# This is the mag offset:
B
# This is the rotation matrix from magRaw to body-frame calibrated B-field measurement:
solve(M) / solve(M)[1,1]
# And this is the magnetic field vector at the calibration site,
# relative to the startup datum for the calibration run:
eb <- eb / sqrt(sum(eb^2))
eb
# the magnetic declination (angle of dip) at your calibration site was measured to be (degrees)
180 * asin(eb[3]) / 3.14159