Skip to content

Commit ecc62a1

Browse files
Merge pull request #188 from ISSUIUC/feature/magnetometer-calib
Magnetometer calibration
2 parents d17c7b5 + 9ba55f4 commit ecc62a1

File tree

10 files changed

+184
-10
lines changed

10 files changed

+184
-10
lines changed

MIDAS/extra_script.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
file_eeprom = f"""\
2727
// autogenerated on build by applying crc32 on esp_eeprom_format.h
28-
#define EEPROM_CHECKSUM (0x{checksum:08x})
28+
#define EEPROM_CHECKSUM (0x{checksum_eeprom:08x})
2929
"""
3030

3131
with Path("src/log_checksum.h").open("w") as checksum_file:

MIDAS/src/buzzer.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct BuzzerController {
4545
#define LAND_TONE_LENGTH 11
4646

4747
#define C_XL_LENGTH 3
48+
#define C_MG_LENGTH 5
4849

4950
extern Sound free_bird[FREE_BIRD_LENGTH];
5051
extern Sound warn_tone[WARN_TONE_LENGTH];
@@ -53,4 +54,9 @@ extern Sound land_tone[LAND_TONE_LENGTH];
5354
extern Sound xl_calib_rdy[C_XL_LENGTH];
5455
extern Sound xl_calib_next_axis[C_XL_LENGTH];
5556
extern Sound xl_calib_done[C_XL_LENGTH];
56-
extern Sound xl_calib_abort[C_XL_LENGTH];
57+
extern Sound xl_calib_abort[C_XL_LENGTH];
58+
59+
extern Sound mg_calib_rdy[C_MG_LENGTH];
60+
extern Sound mg_calib_done[C_MG_LENGTH];
61+
extern Sound mg_calib_inp[C_MG_LENGTH];
62+
extern Sound mg_calib_bad[C_MG_LENGTH];

MIDAS/src/esp_eeprom.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <esp_eeprom.h>
22

33
constexpr size_t EEPROM_SIZE = sizeof(MIDASEEPROM);
4+
constexpr size_t EEPROM_MAX_SIZE = 512; // https://randomnerdtutorials.com/esp32-flash-memory/
45

56
bool EEPROMController::read() {
67
MIDASEEPROM _read;
@@ -38,7 +39,7 @@ bool EEPROMController::commit() {
3839
}
3940

4041
ErrorCode EEPROMController::init() {
41-
42+
static_assert(EEPROM_SIZE <= EEPROM_MAX_SIZE);
4243
EEPROM.begin((size_t)EEPROM_SIZE);
4344

4445
if (!read()) {

MIDAS/src/esp_eeprom_format.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
struct MIDASEEPROM {
66
uint32_t checksum;
77

8-
Acceleration lsm6dsv320x_hg_xl_bias;
8+
Acceleration lsm6dsv320x_hg_xl_bias = {0.0f, 0.0f, 0.0f};
9+
Magnetometer mmc5983ma_softiron_bias = {1.0f, 1.0f, 1.0f};
10+
Magnetometer mmc5983ma_hardiron_bias = {0.0f, 0.0f, 0.0f};
911
};

MIDAS/src/hardware/Magnetometer.cpp

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55

66
SFE_MMC5983MA MMC5983; // global static instance of the sensor
77

8+
#define MGC_TONE_PITCH_H Sound{3000, 50}
9+
#define MGC_TONE_PITCH_L Sound{2200, 50}
10+
#define MGC_TONE_PITCH_LONG Sound{3000, 250}
11+
#define MGC_TONE_PITCH_LONG_L Sound{2000, 250}
12+
#define MGC_TONE_WAIT Sound{0, 50}
13+
#define MGC_TONE_NOOP Sound{0, 1}
14+
15+
Sound mg_calib_rdy[C_MG_LENGTH] = {MGC_TONE_PITCH_H, MGC_TONE_WAIT, MGC_TONE_PITCH_H, MGC_TONE_WAIT, MGC_TONE_PITCH_H};
16+
Sound mg_calib_done[C_MG_LENGTH] = {MGC_TONE_PITCH_LONG, MGC_TONE_WAIT, MGC_TONE_PITCH_H, MGC_TONE_WAIT, MGC_TONE_PITCH_H};
17+
Sound mg_calib_inp[C_MG_LENGTH] = {MGC_TONE_PITCH_H, MGC_TONE_NOOP, MGC_TONE_NOOP, MGC_TONE_NOOP,MGC_TONE_NOOP};
18+
Sound mg_calib_bad[C_MG_LENGTH] = {MGC_TONE_PITCH_LONG_L, MGC_TONE_WAIT, MGC_TONE_PITCH_LONG_L, MGC_TONE_NOOP, MGC_TONE_NOOP};
19+
820
ErrorCode MagnetometerSensor::init() {
921
// Checks if sensor is connected
1022
if (!MMC5983.begin(MMC5983_CS)) {
@@ -28,6 +40,110 @@ Magnetometer MagnetometerSensor::read() {
2840
Y = ((double)cy - sf)/sf;
2941
Z = ((double)cz - sf)/sf;
3042

31-
Magnetometer reading{X, Y, Z};
43+
// We multiply by 8, which is the full scale of the mag.
44+
// https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/blob/main/examples/Example4-SPI_Simple_measurement/Example4-SPI_Simple_measurement.ino
45+
Magnetometer reading{X*8, Y*8, Z*8};
3246
return reading;
47+
}
48+
49+
void MagnetometerSensor::begin_calibration(BuzzerController& buzzer) {
50+
Serial.println("[MAG] Calibration begin");
51+
buzzer.play_tune(mg_calib_rdy, C_MG_LENGTH);
52+
in_calibration_mode = true;
53+
_calib_begin_timestamp = millis();
54+
_calib_beeping = 1;
55+
_calib_max_axis = {-INFINITY, -INFINITY, -INFINITY};
56+
_calib_min_axis = {INFINITY, INFINITY, INFINITY};
57+
_calib_magnitude_sum = 0.0;
58+
_calib_num_datapoints = 0;
59+
}
60+
61+
void MagnetometerSensor::calib_reading(Magnetometer& reading, EEPROMController& eeprom, BuzzerController& buzzer) {
62+
// X reading
63+
if(reading.mx > _calib_max_axis.mx) { _calib_max_axis.mx = reading.mx; };
64+
if(reading.mx < _calib_min_axis.mx) { _calib_min_axis.mx = reading.mx; };
65+
// Y
66+
if(reading.my > _calib_max_axis.my) { _calib_max_axis.my = reading.my; };
67+
if(reading.my < _calib_min_axis.my) { _calib_min_axis.my = reading.my; };
68+
// Z
69+
if(reading.mz > _calib_max_axis.mz) { _calib_max_axis.mz = reading.mz; };
70+
if(reading.mz < _calib_min_axis.mz) { _calib_min_axis.mz = reading.mz; };
71+
72+
// Magnitude
73+
double mag = sqrtf(reading.mx*reading.mx + reading.my*reading.my + reading.mz*reading.mz);
74+
_calib_magnitude_sum += mag;
75+
_calib_num_datapoints++;
76+
77+
// Beeping
78+
if(get_time_since_calibration_start() > 15000 * _calib_beeping && _calib_beeping < 4) {
79+
_calib_beeping++;
80+
buzzer.play_tune(mg_calib_inp, C_MG_LENGTH);
81+
}
82+
83+
if(get_time_since_calibration_start() > _calib_time) {
84+
Serial.println("[MAG] Calibration done.");
85+
commit_calibration(eeprom, buzzer);
86+
}
87+
}
88+
89+
bool MagnetometerSensor::calibration_valid(const Magnetometer& b, const Magnetometer& s) {
90+
constexpr float kSensorEpsilon = 1e-6f;
91+
constexpr float kSensorMaxShear = 5.0f; // probably should tweak this
92+
93+
if(s.mx < kSensorEpsilon || s.my < kSensorEpsilon || s.mz < kSensorEpsilon) {
94+
// Scale data is too small, so we probably didn't get enough data
95+
return false;
96+
}
97+
98+
float s_min = fminf(s.mx, fminf(s.my, s.mz)); // Get minimum value of s
99+
float s_max = fmaxf(s.mx, fmaxf(s.my, s.mz)); // Get minimum value of s
100+
101+
if(s_max > kSensorMaxShear * s_min) {
102+
// this is iffy but could indicate strong sensor reference ellipsoid shear
103+
// return false;
104+
}
105+
106+
return true;
107+
}
108+
109+
void MagnetometerSensor::restore_calibration(EEPROMController& eeprom) {
110+
in_calibration_mode = false;
111+
112+
// Read back the calibration data from EEPROM:
113+
calibration_bias_softiron = eeprom.data.mmc5983ma_softiron_bias;
114+
calibration_bias_hardiron = eeprom.data.mmc5983ma_hardiron_bias;
115+
}
116+
117+
void MagnetometerSensor::commit_calibration(EEPROMController& eeprom, BuzzerController& buzzer) {
118+
in_calibration_mode = false;
119+
120+
Magnetometer b, s;
121+
double magnitude_tgt = _calib_magnitude_sum / _calib_num_datapoints;
122+
123+
// b: Origin offset
124+
b.mx = (_calib_min_axis.mx + _calib_max_axis.mx) / 2;
125+
b.my = (_calib_min_axis.my + _calib_max_axis.my) / 2;
126+
b.mz = (_calib_min_axis.mz + _calib_max_axis.mz) / 2;
127+
128+
// s: Scale offset
129+
s.mx = (_calib_max_axis.mx - _calib_min_axis.mx) / (2*magnitude_tgt);
130+
s.my = (_calib_max_axis.my - _calib_min_axis.my) / (2*magnitude_tgt);
131+
s.mz = (_calib_max_axis.mz - _calib_min_axis.mz) / (2*magnitude_tgt);
132+
133+
Serial.print("MAG: ");
134+
Serial.println(magnitude_tgt);
135+
136+
if(!calibration_valid(b, s)) {
137+
Serial.println("[MAG] Calibration is bad.");
138+
buzzer.play_tune(mg_calib_bad, C_MG_LENGTH);
139+
return; // The calibration is garbage... this probably shouldn't fail silently but idc rn.
140+
}
141+
142+
calibration_bias_hardiron = b;
143+
calibration_bias_softiron = s;
144+
buzzer.play_tune(mg_calib_done, C_MG_LENGTH);
145+
146+
eeprom.data.mmc5983ma_softiron_bias = calibration_bias_softiron;
147+
eeprom.data.mmc5983ma_hardiron_bias = calibration_bias_hardiron;
148+
eeprom.commit();
33149
}

MIDAS/src/hardware/sensors.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ struct IMUSensor {
5454
struct MagnetometerSensor {
5555
ErrorCode init();
5656
Magnetometer read();
57+
58+
// Calibration functions
59+
bool in_calibration_mode = false;
60+
void begin_calibration(BuzzerController& buzzer);
61+
void calib_reading(Magnetometer& reading, EEPROMController& eeprom, BuzzerController& buzzer);
62+
void restore_calibration(EEPROMController& eeprom);
63+
64+
Magnetometer calibration_bias_hardiron = {0.0, 0.0, 0.0}; // hard iron offset -- "recenters" data on origin (0,0,0).
65+
Magnetometer calibration_bias_softiron = {1.0, 1.0, 1.0}; // soft iron offset -- scales per-axis data (Should be 3x3, but we'll try 1x3 for now.)
66+
67+
unsigned long get_time_since_calibration_start() { return millis() - _calib_begin_timestamp; }
68+
69+
private:
70+
void commit_calibration(EEPROMController& eeprom, BuzzerController& buzzer); // Calculate and commit the calibration to memory
71+
bool calibration_valid(const Magnetometer& b, const Magnetometer& s); // Calculate and sanity check calibration data
72+
/* Maximum value per-axis during calibration */
73+
Magnetometer _calib_max_axis;
74+
/* Minimum value per-axis during calibration */
75+
Magnetometer _calib_min_axis;
76+
unsigned long _calib_begin_timestamp;
77+
double _calib_magnitude_sum = 0.0;
78+
int _calib_num_datapoints = 0;
79+
int _calib_beeping = 0;
80+
/* Magnetometer calibration isn't based on a per-axis calibration, but on getting as many datapoints as possible.
81+
For now let's try 60 sec */
82+
const unsigned long _calib_time = 60000;
83+
5784
};
5885

5986
/**

MIDAS/src/systems.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,28 @@ DECLARE_THREAD(imuthread, RocketSystems *arg)
108108
}
109109

110110
DECLARE_THREAD(magnetometer, RocketSystems* arg) {
111+
arg->sensors.magnetometer.restore_calibration(arg->eeprom);
112+
int i = 0;
111113
while (true) {
112114
// xSemaphoreTake(spi_mutex, portMAX_DELAY);
113115
Magnetometer reading = arg->sensors.magnetometer.read();
114116
// xSemaphoreGive(spi_mutex);
117+
118+
// Sensor calibration
119+
if(arg->sensors.magnetometer.in_calibration_mode) {
120+
arg->sensors.magnetometer.calib_reading(reading, arg->eeprom, arg->buzzer);
121+
// Mag calibration handles its own calibration timing.
122+
}
123+
124+
// Sensor biases
125+
Magnetometer b = arg->sensors.magnetometer.calibration_bias_hardiron; // "Hard iron" / origin offset.
126+
Magnetometer s = arg->sensors.magnetometer.calibration_bias_softiron; // "Soft iron" / scale offset.
127+
reading.mx = (reading.mx - b.mx) / s.mx;
128+
reading.my = (reading.my - b.my) / s.my;
129+
reading.mz = (reading.mz - b.mz) / s.mz;
130+
115131
arg->rocket_data.magnetometer.update(reading);
132+
116133
THREAD_SLEEP(50);
117134
}
118135
}
@@ -330,9 +347,9 @@ DECLARE_THREAD(kalman, RocketSystems *arg)
330347

331348
arg->rocket_data.kalman.update(current_state);
332349

333-
Serial.print("MQ tilt: ");
334-
Serial.println(current_angular_kalman.mq_tilt);
335-
Serial.println();
350+
// Serial.print("MQ tilt: ");
351+
// Serial.println(current_angular_kalman.mq_tilt);
352+
// Serial.println();
336353

337354
last = xTaskGetTickCount();
338355
// Serial.println("Kalman");
@@ -398,6 +415,9 @@ void handle_tlm_command(TelemetryCommand &command, RocketSystems *arg, FSMState
398415
case CommandType::CALIB_ACCEL:
399416
arg->sensors.imu.begin_calibration(arg->buzzer);
400417
break;
418+
case CommandType::CALIB_MAG:
419+
arg->sensors.magnetometer.begin_calibration(arg->buzzer);
420+
break;
401421
default:
402422
break; // how
403423
}

MIDAS/src/telemetry_packet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct TelemetryPacket {
5050

5151

5252
// Commands transmitted from ground station to rocket
53-
enum class CommandType: uint8_t { RESET_KF, SWITCH_TO_SAFE, SWITCH_TO_PYRO_TEST, SWITCH_TO_IDLE, FIRE_PYRO_A, FIRE_PYRO_B, FIRE_PYRO_C, FIRE_PYRO_D, CAM_ON, CAM_OFF, TOGGLE_CAM_VMUX, CALIB_ACCEL };
53+
enum class CommandType: uint8_t { RESET_KF, SWITCH_TO_SAFE, SWITCH_TO_PYRO_TEST, SWITCH_TO_IDLE, FIRE_PYRO_A, FIRE_PYRO_B, FIRE_PYRO_C, FIRE_PYRO_D, CAM_ON, CAM_OFF, TOGGLE_CAM_VMUX, CALIB_ACCEL, CALIB_MAG };
5454

5555
/**
5656
* @struct TelemetryCommand

ground/src/feather_duo/Command.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include<stdint.h>
33
#include<array>
44

5-
enum class CommandType: uint8_t { RESET_KF, SWITCH_TO_SAFE, SWITCH_TO_PYRO_TEST, SWITCH_TO_IDLE, FIRE_PYRO_A, FIRE_PYRO_B, FIRE_PYRO_C, FIRE_PYRO_D, CAM_ON, CAM_OFF, TOGGLE_CAM_VMUX, CALIB_ACCEL, EMPTY };
5+
enum class CommandType: uint8_t { RESET_KF, SWITCH_TO_SAFE, SWITCH_TO_PYRO_TEST, SWITCH_TO_IDLE, FIRE_PYRO_A, FIRE_PYRO_B, FIRE_PYRO_C, FIRE_PYRO_D, CAM_ON, CAM_OFF, TOGGLE_CAM_VMUX, CALIB_ACCEL, CALIB_MAG, EMPTY };
66
// Commands transmitted from ground station to rocket
77
struct TelemetryCommand {
88
CommandType command;

ground/src/feather_duo/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ void handle_serial(const String& key) {
146146
command.command = CommandType::TOGGLE_CAM_VMUX;
147147
} else if (cmd_name == "CXL") {
148148
command.command = CommandType::CALIB_ACCEL;
149+
} else if (cmd_name == "CMG") {
150+
command.command = CommandType::CALIB_MAG;
149151
} else {
150152
Serial.println(json_command_bad);
151153
return;

0 commit comments

Comments
 (0)