14 Commits

  1. 9
      .gitignore
  2. 9
      .gitmodules
  3. 17
      Makefile
  4. 99
      _config.yml
  5. 22
      _layouts/base.html
  6. 6
      _layouts/page.html
  7. 424
      _sass/_normalize.scss
  8. 1
      assets/fonts/league-spartan
  9. 1
      assets/fonts/sorts-mill-goudy
  10. 87
      assets/style.sass
  11. 3319
      package-lock.json
  12. 20
      package.json
  13. 4
      scaffolds/draft.md
  14. 4
      scaffolds/page.md
  15. 5
      scaffolds/post.md
  16. 232
      source/_posts/magnetometer_calibration.md
  17. BIN
      source/_posts/magnetometer_calibration/img/L-12_ground_calibration.jpg
  18. BIN
      source/_posts/magnetometer_calibration/img/L-12_liftoff.jpg
  19. BIN
      source/_posts/magnetometer_calibration/img/L-12_overview.png
  20. BIN
      source/_posts/magnetometer_calibration/post_files/post_11_0.png
  21. BIN
      source/_posts/magnetometer_calibration/post_files/post_24_0.png
  22. BIN
      source/_posts/magnetometer_calibration/post_files/post_25_0.png
  23. BIN
      source/_posts/magnetometer_calibration/post_files/post_26_0.png
  24. BIN
      source/_posts/magnetometer_calibration/post_files/post_28_0.png
  25. BIN
      source/_posts/magnetometer_calibration/post_files/post_29_0.png
  26. BIN
      source/_posts/magnetometer_calibration/post_files/post_31_0.png
  27. BIN
      source/_posts/magnetometer_calibration/post_files/post_32_0.png
  28. BIN
      source/_posts/magnetometer_calibration/post_files/post_5_0.png
  29. BIN
      source/_posts/magnetometer_calibration/post_files/post_6_0.png
  30. BIN
      source/_posts/magnetometer_calibration/post_files/post_7_0.png
  31. BIN
      source/_posts/magnetometer_calibration/post_files/post_8_0.png
  32. BIN
      source/_posts/magnetometer_calibration/post_files/post_9_0.png
  33. 1
      themes/tufte

9
.gitignore

@ -1,3 +1,12 @@
.DS_Store
Thumbs.db
_site/
.sass-cache/
favicon.ico
db.json
*.log
node_modules/
public/
.deploy*/

9
.gitmodules

@ -1,6 +1,3 @@
[submodule "assets/fonts/league-spartan"]
path = assets/fonts/league-spartan
url = https://github.com/theleagueof/league-spartan.git
[submodule "assets/fonts/sorts-mill-goudy"]
path = assets/fonts/sorts-mill-goudy
url = https://github.com/theleagueof/sorts-mill-goudy.git
[submodule "themes/tufte"]
path = themes/tufte
url = https://git.natronics.org/natronics/hexo-theme-tufte.git

17
Makefile

@ -1,7 +1,7 @@
all: _site
_site: index.markdown assets/style.sass favicon.ico
JEKYLL_ENV=production jekyll build
public: favicon.ico source
npx hexo generate
cp -r node_modules/mathjax/es5 public/mathjax
cp favicon.ico public/
favicon.ico: favicon.svg
rsvg-convert -w 16 -h 16 --background-color=none favicon.svg -o favicon-16.png
@ -10,9 +10,10 @@ favicon.ico: favicon.svg
convert favicon-16.png favicon-32.png favicon-64.png favicon.ico
rm favicon*.png
clean:
rm -rf _site
deploy: public
scp -r public/* natronics.org:/var/www
deploy: all
scp -r _site/* natronics.org:/var/www
clean:
rm -rf public
.PHONY: deploy clean

99
_config.yml

@ -1,18 +1,81 @@
exclude:
- README.md
- Makefile
- LICENSE
- favicon.svg
- assets/fonts/league-spartan/*markdown
- assets/fonts/league-spartan/*.otf
- assets/fonts/sorts-mill-goudy/*.otf
- assets/fonts/sorts-mill-goudy/*.markdown
- assets/fonts/sorts-mill-goudy/OFLGoudyStMTT-Italic.ttf
- assets/fonts/sorts-mill-goudy/OFLGoudyStMTT.ttf
- assets/fonts/sorts-mill-goudy/images
- assets/fonts/sorts-mill-goudy/source
include:
- _webfonts
markdown: kramdown
sass:
style: compressed
# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site
title: natronics.org
subtitle:
description:
keywords:
author: Nathan
language: en
timezone: America/New_York
# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: https://natronics.org
root: /
permalink: :year/:title/
permalink_defaults:
# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:
# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: true
relative_link: false
future: true
highlight:
enable: true
line_number: false
auto_detect: false
tab_replace:
# Home page setting
# path: Root path for your blogs index page. (default = '')
# per_page: Posts displayed per page. (0 = disable pagination)
# order_by: Posts order. (Order by date descending by default)
index_generator:
path: ''
per_page: 10
order_by: -date
# Category & Tag
default_category: uncategorized
category_map:
tag_map:
# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: tufte
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type:

22
_layouts/base.html

@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="natronics.org">
{% capture caching %}{% if jekyll.environment == "production" %}.{{ site.time | date: '%s' }}{% else %}{% endif %}{% endcapture %}
<link rel="shortcut icon" href="/favicon{{ caching }}.ico">
<link rel="stylesheet" href="/assets/style{{ caching }}.css">
<title>{{ page.title }}</title>
</head>
<body>
<header>
<span class="title">natronics.org</span>
</header>
{{ content }}
<footer>
made in the boiling hot cement cubes of Philadelphia PA.
</footer>
</body>
</html>

6
_layouts/page.html

@ -1,6 +0,0 @@
---
layout: base
---
<main id="page">
{{ content }}
</main>

424
_sass/_normalize.scss

@ -1,424 +0,0 @@
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS and IE text size adjust after device orientation change,
* without disabling user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability of focused elements when they are also in an
* active/hover state.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
box-sizing: content-box; /* 2 */
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}

1
assets/fonts/league-spartan

@ -1 +0,0 @@
Subproject commit c350408b07ca284b6c097b7194f2f14f428013f5

1
assets/fonts/sorts-mill-goudy

@ -1 +0,0 @@
Subproject commit 06072890c7b05f274215a24f17449655ccb2c8af

87
assets/style.sass

@ -1,87 +0,0 @@
---
---
{% capture caching %}{% if jekyll.environment == "production" %}.{{ site.time | date: '%s' }}{% else %}{% endif %}{% endcapture %}
$linkcolor: #3765D4
$primary: #F18BD1
@import _normalize
@font-face
font-family: 'League Spartan'
src: url('/assets/fonts/league-spartan/_webfonts/leaguespartan-bold{{ caching }}.eot')
src: url('/assets/fonts/league-spartan/_webfonts/leaguespartan-bold{{ caching }}.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/league-spartan/_webfonts/leaguespartan-bold{{ caching }}.woff2') format('woff2'), url('/assets/fonts/league-spartan/_webfonts/leaguespartan-bold{{ caching }}.woff') format('woff'), url('/assets/fonts/league-spartan/_webfonts/leaguespartan-bold{{ caching }}.ttf') format('truetype'), url('/assets/fonts/league-spartan/_webfonts/leaguespartan-bold{{ caching }}.svg#league_spartanbold') format('svg')
font-weight: bold
font-style: normal
@font-face
font-family: 'Sorts Mill Goudy'
src: url('/assets/fonts/sorts-mill-goudy/webfonts/GoudyStM-webfont{{ caching }}.eot')
src: url('/assets/fonts/sorts-mill-goudy/webfonts/GoudyStM-webfont{{ caching }}.woff') format('woff'), url('/assets/fonts/sorts-mill-goudy/webfonts/GoudyStM-webfont{{ caching }}.ttf') format('truetype'), url('/assets/fonts/sorts-mill-goudy/webfontsGoudyStM-webfont{{ caching }}.svg') format('svg')
font-weight: 400
font-style: normal
@font-face
font-family: 'Sorts Mill Goudy'
src: url('/assets/fonts/sorts-mill-goudy/webfonts/GoudyStM-Italic-webfont{{ caching }}.eot')
src: url('/assets/fonts/sorts-mill-goudy/webfonts/GoudyStM-Italic-webfont{{ caching }}.woff') format('woff'), url('/assets/fonts/sorts-mill-goudy/webfonts/GoudyStM-Italic-webfont{{ caching }}.ttf') format('truetype'), url('/assets/fonts/sorts-mill-goudy/webfontsGoudyStM-Italic-webfont{{ caching }}.svg') format('svg')
font-weight: 400
font-style: italic
header
background: $primary
border-top: 3px solid darken($primary, 15%)
color: #fff
header .title
display: block
padding: 2% 10% 2% 10%
font-family: 'League Spartan'
font-size: 1.6em
#page
padding: 5% 10% 10% 10%
font-family: 'Sorts Mill Goudy'
font-size: 1.4em
line-height: 1.33em
footer
padding: 3% 10% 1% 10%
font-family: 'Sorts Mill Goudy'
font-size: 0.9em
color: #999
footer a, footer a:visited
color: #666
a
color: $linkcolor
text-decoration: none
a:hover
text-decoration: underline
a:visited
color: darken($linkcolor, 14%)
#page img
width: 100%
h1, h2, h3, h4, h5, h6
font-family: 'League Spartan'
h1
border-bottom: 2px solid #ddd
padding-bottom: 0.2em
margin-bottom: 1.2em
@media (max-width: 500px)
header .title
padding: 1% 5% 1% 5%
#page
padding: 5%
text-align: justify
font-size: 1.2em
footer
padding: 3% 5% 1% 5%

3319
package-lock.json
File diff suppressed because it is too large
View File

20
package.json

@ -0,0 +1,20 @@
{
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"hexo": {
"version": "3.9.0"
},
"dependencies": {
"hexo": "^3.9.0",
"hexo-generator-archive": "^0.1.5",
"hexo-generator-category": "^0.1.3",
"hexo-generator-index": "^0.2.1",
"hexo-generator-tag": "^0.2.0",
"hexo-renderer-ejs": "^0.3.1",
"hexo-renderer-marked": "^1.0.1",
"hexo-renderer-stylus": "^0.3.3",
"hexo-server": "^0.3.3",
"mathjax": "^3.0.0"
}
}

4
scaffolds/draft.md

@ -0,0 +1,4 @@
---
title: {{ title }}
tags:
---

4
scaffolds/page.md

@ -0,0 +1,4 @@
---
title: {{ title }}
date: {{ date }}
---

5
scaffolds/post.md

@ -0,0 +1,5 @@
---
title: {{ title }}
date: {{ date }}
tags:
---

232
source/_posts/magnetometer_calibration.md

@ -0,0 +1,232 @@
---
title: PSAS Magnetometer Calibration
author: Nathan
date: 2019/11/05
---
For a number of years I was involved with a university rocketry club called PSAS{% sidenote %}[Portland State Aerospace Society](http://psas.pdx.edu), a student aerospace engineering project at Portland State University. They build ultra-low-cost, open source rockets that feature very sophisticated amateur rocket avionics systems.{% endsidenote %}. One of the things I really liked to do was play with the data from the launches and learn how rockets and flight electronics work.
Our rockets carry an instrument on them called an **IMU** (Inertial Measument Unit). An IMU typically measures both acceleration and rotation-rate of an object in all directions so with some clever math you can recreate the exact position, velocity, and orientation of the rocket over time. This is the only way to know where something is in space, and very important for rockets. IMUs have a problem though: they're not very precise.
Since our IMU is fixed to the rocket, {% marginnote %}![diagram of the rocket on it's side showing the layout of the internal components](img/L-12_overview.png) Overview of the rocket "LV2.3". The IMU is near the primary flight computer.{% endmarginnote %} which direction is "up" or "left", etc. relative to the Earth changes constantly as the rocket flies about. In order for the data to be useful we need to know which way we are pointed, which is why IMUs alway have some kind of gryoscope to account for rotation. Our particular IMU has rate-gyroscopes that can sense rotation rate, and so we integrate that once to get orientation. Since any integration will give an estimate that drifts from the true value over time, our IMU also includes a 3-axis _magnetometer_ as well.
## 9DOF IMU
This makes what is often what is refered to as a "9DOF" IMU, because it has "nine degrees of freedom". That would be _x, y, z_ accleration, _x, y, z_ rotation-rate, and _x, y, z_ magnetic field. The reason to have a magnetometer is so you can use Earth's own magnetic field as a kind of guide to the orientation of the rocket. This doesn't instantly solve all problems in life, sadly. But it provides a good reference for the rough orientation of the rocket that can be used to produce a real-time estimate of rate-gyroscape drift, or 'bias', as we fly.
The magnetic field sensor in the rocket is sensitive, but because the Earth's field is so weak it's easily overwhelmed by local effects (metal screws, magnetic fields from nearby wires, etc.). In order to get good orientation data we need to undo{% marginnote %}![photo of two men awkwardly holding a large rocket body and an angle](img/L-12_ground_calibration.jpg) Members of the PSAS ground crew lifting and aranging the rocket around as many different orientations as possible before the flight.{% endmarginnote %} these local effects.
So a little before the flight we took the nearly complete rocket, powered the electronics up, picked it up and tried to move it around in every direction.
## Magnetometer Calibration
What do we expect good magnetometer data to look like? The Earth's magnetic field shouldn't change much, so it should look like a single vector going through the IMU. If we rotate the rocket one way or another, the angel that the vector goes through will change, but it should stay the same strength. That means that the magnitude of the local magnetic filed should be constant, and it should measure it to be exactly the same as Earth's magnetic field.
## Earth's Field Strength
But what is the strength of Earth's magnetic field? It varies over time and over the surface of the Earth. We know where we launched from{% sidenote %}
Latitude: `43.79613280°` N
Longitude: `120.65175340°` W
Elevation: `1390.0` m Mean Sea Level
{% endsidenote %} and the date, so we can look up{% sidenote %}[NOAA's magnetic field calculator](https://www.ngdc.noaa.gov/geomag/magfield.shtml)
Model Used: `WMM2015`
{% endsidenote %} what the expected magnetic field should be:
Its direction:
| Declination (+E/-W) | Inclination (+D/-U) | Horizontal Intensity |
| ------------------: | ------------------: | -------------------: |
| 14.7990° ±0.36° | 66.5386° ±0.22 | 20,754.1 nT ±133 nT|
And as a vector:
| North Comp (+N/-S) | East Comp (+E/-W) | Vertical Comp (+D/-U) |
| -------------------: | ----------------: | --------------------: |
| 20,065.7 nT ±138 nT | 5,301.2 nT ±89 nT | 47,819.4 nT ±165 nT |
And finally, the total strength:
| Total Field |
| ------------------: |
| 52,129.0 nT ±152 nT |
## Calibration Data
That's what we _expect_ to see. What do we actually get?
In the 22.1 minutes that we had the flight computer collecting data in our calibration run we recoreded 1,079,342 data points from the IMU.
Looking over time at the _x, y, z_ values of the magnetometer and the mangitue compared to the NOAA predicted field we see it vary a lot.
<figure class="fullwidth">
<img src="post_files/post_9_0.png">
</figure>
This is because we have a couple of problems. One is that the effective _center_ of our magnetometer values are pushed off to one side. And the other is that the values are skewed (or "stretched") off to one side as well. This is somewhat easier to see in 3D:
<figure class="">
<img src="post_files/post_11_0.png">
</figure>
## Correction
The two parts of the correction are called "Hard Iron" (fixed center offset) and "Soft Iron" (streched sphere) corrections.
### Hard Iron
This is the simpler of the two, one can essentially find the midrange value of across the entire calibration dataset and subtract that offset to move the '0' point back to center.
### Soft Iron
Finding the soft iron correction is a bit trickier because we want to fit an matching elongated ellipsoid to the data, and then once we have an approximation for that ellipsoid apply stretch to the data to undo the elongation and get it back to a sphere. Luckily an algorithm for this has been worked out. For a detailed breakdown see
https://teslabs.com/articles/magnetometer-calibration/
After doing fitting we end up with both a correction matrix and an offset vector. This is both the soft iron and hard iron correction.
To invert the stretch we multiply a vector representing each magnetometer reading (a 'sample', $\vec s$) by the correction matrix (after subtracting the center offset offset).
$$
\vec s_\textrm{corrected} = \mathbf{A} \cdot (\vec s - \vec b)
$$
Where
- $\vec s_\textrm{corrected}$ is a fully corrected sample at time t
- $\mathbf{A}$ is our soft iron correction matrix
- $\vec s$ is a raw sample from the IMU at time t
- $\vec b$ our hard iron offset vector.
When we solve for $\mathbf{A}$ on the calibration data we get the matrix:
$$
\textbf{A} = \left[\begin{array}{ccc}
0.870368 & -0.128543 & -0.283684 \\\\
-0.128543 & 1.510386 & -0.046543 \\\\
-0.283684 & -0.046543 & 1.440805
\end{array}\right]
$$
And a hard iron offset vector:
$$
\vec b = \left[ \begin{array}{ccc}
-12.019415 & -3.209783 & -1.939041
\end{array}\right]
$$
## Python
If we want to apply this correction we can make a convienient function to call on all our samples:
``` python
import numpy as np
MAG_CORRECTION_A = np.array((
( 0.870367858077, -0.128543320363, -0.283683583608),
(-0.128543320363, 1.510386103995, -0.046543028701),
(-0.283683583608, -0.046543028701, 1.440804950101),
))
MAG_CORRECTION_b = np.array((-12.019414737824, -3.209782771540, -1.939040882716))
def apply_mag_correction(sample):
"""Take a sample and correct for hard and soft iron effects
based on calibration run before Launch 12.
:param sample: Vector (np.array) (x,y,z) instantaintous magnetometer reading
:returns: vector (x,y,z) corrected magnetometer reading
"""
return np.dot(MAG_CORRECTION_A, sample + MAG_CORRECTION_b)
```
## Apply Calibration
After we apply the calibration fix above, do we get a better result?
<figure class="fullwidth">
<img src="post_files/post_24_0.png">
</figure>
Yes! Quite a bit better. Notice how the magnitue of the vector now stays very close to constant and is very close to the NOAA estimate!
Again in 3D we can see a much closer to spherical data:
<figure class="">
<img src="post_files/post_26_0.png">
</figure>
## Applying to Flight Data
We can now take our calibration matrix and apply it to real flight data! Here is a 3D look at the [Launch-12](https://github.com/psas/Launch-12) **raw** (uncalibrated) magnetometer data from liftoff to apogee:
<figure class="">
<img src="post_files/post_29_0.png">
</figure>
The rocket spins during flight, and we see the magnetic field measurement spiral around the plots. We also see the familiar stretch and offset that we saw in the calibration data.
### Calibrated Flight Data
So now lets calibrate the flight data!
<figure class="fullwidth">
<img src="post_files/post_32_0.png">
</figure>
And it looks like the calibration did a reasonable job. The values now come very close to landing on the nominal Earth field sphere. The XY view is still off a little bit but it might just be that we had some bias in the calibration run. It's still a huge improvment to the original dataset and it now usable in IMU reconstructions of the flight of the rocket.
----------------------------------------------
This post is written as a [jupyter notebook](https://jupyter.org/) and all its code and data can be viewed as a stand-alone own project here:
https://git.natronics.org/natronics/psas-magnetometer-calibration

BIN
source/_posts/magnetometer_calibration/img/L-12_ground_calibration.jpg

After

Width: 1836  |  Height: 1836  |  Size: 702 KiB

BIN
source/_posts/magnetometer_calibration/img/L-12_liftoff.jpg

After

Width: 992  |  Height: 992  |  Size: 147 KiB

BIN
source/_posts/magnetometer_calibration/img/L-12_overview.png

After

Width: 1169  |  Height: 433  |  Size: 55 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_11_0.png

After

Width: 1408  |  Height: 1377  |  Size: 359 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_24_0.png

After

Width: 1408  |  Height: 503  |  Size: 85 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_25_0.png

After

Width: 1408  |  Height: 1377  |  Size: 389 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_26_0.png

After

Width: 1408  |  Height: 1377  |  Size: 391 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_28_0.png

After

Width: 1408  |  Height: 1377  |  Size: 290 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_29_0.png

After

Width: 1408  |  Height: 1377  |  Size: 290 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_31_0.png

After

Width: 1408  |  Height: 1377  |  Size: 351 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_32_0.png

After

Width: 1408  |  Height: 1377  |  Size: 354 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_5_0.png

After

Width: 1408  |  Height: 500  |  Size: 83 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_6_0.png

After

Width: 1408  |  Height: 500  |  Size: 83 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_7_0.png

After

Width: 1408  |  Height: 500  |  Size: 83 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_8_0.png

After

Width: 1408  |  Height: 500  |  Size: 83 KiB

BIN
source/_posts/magnetometer_calibration/post_files/post_9_0.png

After

Width: 1408  |  Height: 503  |  Size: 85 KiB

1
themes/tufte

@ -0,0 +1 @@
Subproject commit 2a7bcf18e0aae97390b3b444dbe799571382c56d
Loading…
Cancel
Save