STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
IMUCalibration.cpp
Go to the documentation of this file.
1#include "../include/calibration/IMUCalibration.hpp"
2
3#include <algorithm>
4#include <cmath>
5#include <iostream>
6
7namespace fsw {
8namespace calibration {
9
11 : sensor_type_(type), status_(CalibrationStatus::NOT_CALIBRATED) {
13 params_.scale_matrix = Eigen::Matrix3d::Identity();
14 params_.misalignment = Eigen::Matrix3d::Identity();
15}
16
18
19void IMUCalibration::initialize(const CalibrationParams& initial_params) {
20 std::lock_guard<std::mutex> lock(mutex_);
21 params_ = initial_params;
23 calibration_data_.clear();
24 recent_readings_.clear();
25}
26
28 std::lock_guard<std::mutex> lock(mutex_);
29 calibration_data_.push_back(point);
30}
31
33 std::lock_guard<std::mutex> lock(mutex_);
34
35 if (calibration_data_.empty()) {
37 return false;
38 }
39
41
42 // Extract raw readings
43 std::vector<RawReading> readings;
44 for (const auto& point : calibration_data_) {
45 readings.push_back(point.raw);
46 }
47
48 // Perform calibration based on sensor type
49 bool success = false;
50 switch (sensor_type_) {
53 break;
55 success = calibrateGyroscope(readings);
56 break;
58 // For magnetometer, need reference field
59 if (calibration_data_.size() > 0) {
60 Eigen::Vector3d ref_field = calibration_data_[0].reference;
61 success = calibrateMagnetometer(readings, ref_field);
62 }
63 break;
64 }
65
66 if (success) {
67 params_.calibration_quality = computeCalibrationQuality();
69 } else {
71 }
72
73 return success;
74}
75
76bool IMUCalibration::calibrateAccelerometer(const std::vector<RawReading>& readings) {
77 if (readings.size() < 6) {
78 std::cerr << "[IMUCalibration] Need at least 6 positions for accelerometer calibration"
79 << std::endl;
80 return false;
81 }
82
83 // Estimate bias as mean of readings (assuming gravity cancels out)
84 estimateBias(readings);
85
86 // For accelerometer, use static calibration
87 return calibrateAccelStatic(readings);
88}
89
90bool IMUCalibration::calibrateGyroscope(const std::vector<RawReading>& readings,
91 const std::vector<double>& reference_rates) {
92 if (readings.empty()) {
93 return false;
94 }
95
96 // Estimate bias from zero-velocity periods or known rotations
97 if (reference_rates.empty()) {
98 // Zero-velocity calibration
99 return calibrateGyroZeroVelocity(readings);
100 } else {
101 // Rotation-based calibration
102 return calibrateGyroRotation(readings, reference_rates);
103 }
104}
105
106bool IMUCalibration::calibrateMagnetometer(const std::vector<RawReading>& readings,
107 const Eigen::Vector3d& reference_field) {
108 if (readings.size() < 6) {
109 std::cerr << "[IMUCalibration] Need at least 6 orientations for magnetometer calibration"
110 << std::endl;
111 return false;
112 }
113
114 // Use ellipsoid fitting for magnetometer
115 return calibrateMagEllipsoidFit(readings);
116}
117
119 std::lock_guard<std::mutex> lock(mutex_);
120
122
124 calibrated.valid = false;
125 return calibrated;
126 }
127
128 // Apply temperature compensation
129 double temp_scale = 1.0;
130 if (std::abs(params_.temperature_coeff) > 1e-6) {
131 double temp_diff = raw.temperature - params_.reference_temperature;
132 temp_scale = 1.0 + params_.temperature_coeff * temp_diff;
133 }
134
135 // Apply calibration: calibrated = scale_matrix * (raw - bias) * temp_scale
136 Eigen::Vector3d corrected = raw.value - params_.bias;
137 corrected *= temp_scale;
138 calibrated.value = params_.scale_matrix * corrected;
139
140 // Compute uncertainty (simplified)
141 calibrated.covariance =
142 Eigen::Matrix3d::Identity() * (params_.bias_uncertainty * params_.bias_uncertainty +
143 params_.scale_uncertainty * params_.scale_uncertainty);
144
145 calibrated.valid = true;
146 return calibrated;
147}
148
150 std::lock_guard<std::mutex> lock(mutex_);
151 return params_;
152}
153
155 std::lock_guard<std::mutex> lock(mutex_);
156 return status_;
157}
158
162
164 std::lock_guard<std::mutex> lock(mutex_);
166 calibration_data_.clear();
167 recent_readings_.clear();
169 params_.scale_matrix = Eigen::Matrix3d::Identity();
170 params_.misalignment = Eigen::Matrix3d::Identity();
171}
172
173void IMUCalibration::estimateBias(const std::vector<RawReading>& readings) {
174 if (readings.empty()) {
175 return;
176 }
177
178 Eigen::Vector3d bias_sum = Eigen::Vector3d::Zero();
179 for (const auto& reading : readings) {
180 bias_sum += reading.value;
181 }
182 params_.bias = bias_sum / static_cast<double>(readings.size());
183}
184
185bool IMUCalibration::calibrateAccelStatic(const std::vector<RawReading>& readings) {
186 // For accelerometer static calibration:
187 // We know gravity magnitude (9.81 m/s²)
188 // At each orientation, |accel| should equal gravity
189
190 const double g = 9.81;
191 std::vector<CalibrationPoint> points;
192
193 for (const auto& reading : readings) {
194 CalibrationPoint point;
195 point.raw = reading;
196 // Reference is gravity vector in sensor frame
197 // Magnitude should be g, direction depends on orientation
198 double magnitude = reading.value.norm();
199 if (magnitude > 0.1) {
200 point.reference = reading.value.normalized() * g;
201 point.reference_uncertainty = 0.1; // 0.1 m/s² uncertainty
202 points.push_back(point);
203 }
204 }
205
206 if (points.size() < 6) {
207 return false;
208 }
209
211 return true;
212}
213
214bool IMUCalibration::calibrateGyroZeroVelocity(const std::vector<RawReading>& readings) {
215 // For zero-velocity periods, gyro should read zero
216 estimateBias(readings);
217
218 // Estimate scale factors from variance
219 Eigen::Vector3d variance = Eigen::Vector3d::Zero();
220 for (const auto& reading : readings) {
221 Eigen::Vector3d diff = reading.value - params_.bias;
222 variance += diff.cwiseProduct(diff);
223 }
224 variance /= static_cast<double>(readings.size());
225
226 // Scale matrix is identity with scale factors from variance
227 params_.scale_matrix = Eigen::Matrix3d::Identity();
228 // Scale factors inversely related to variance (higher variance = lower confidence)
229
230 return true;
231}
232
233bool IMUCalibration::calibrateGyroRotation(const std::vector<RawReading>& readings,
234 const std::vector<double>& reference_rates) {
235 if (readings.size() != reference_rates.size()) {
236 return false;
237 }
238
239 // Estimate bias and scale from known rotation rates
240 estimateBias(readings);
241
242 // Estimate scale factors
243 Eigen::Vector3d scale = Eigen::Vector3d::Ones();
244 for (size_t i = 0; i < readings.size(); ++i) {
245 Eigen::Vector3d corrected = readings[i].value - params_.bias;
246 double magnitude = corrected.norm();
247 if (magnitude > 0.01 && std::abs(reference_rates[i]) > 0.01) {
248 scale = scale.cwiseProduct(Eigen::Vector3d::Constant(reference_rates[i] / magnitude));
249 }
250 }
251
252 params_.scale_matrix = scale.asDiagonal();
253 return true;
254}
255
256bool IMUCalibration::calibrateMagEllipsoidFit(const std::vector<RawReading>& readings) {
257 // Ellipsoid fitting for magnetometer calibration
258 // Fit to: (x - center)^T * A * (x - center) = 1
259
260 if (readings.size() < 6) {
261 return false;
262 }
263
264 // Simple ellipsoid fit (hard iron and soft iron correction)
265 // Hard iron: bias correction
266 estimateBias(readings);
267
268 // Soft iron: scale and misalignment
269 // For now, use identity scale matrix
270 // Full ellipsoid fitting would require solving for 9 parameters
271
272 params_.scale_matrix = Eigen::Matrix3d::Identity();
273 return true;
274}
275
276void IMUCalibration::estimateScaleAndMisalignment(const std::vector<CalibrationPoint>& points) {
277 if (points.size() < 6) {
278 return;
279 }
280
281 // Least squares estimation of scale matrix
282 // Solve: calibrated = scale_matrix * (raw - bias)
283 // Or: reference = scale_matrix * (raw - bias)
284
285 Eigen::MatrixXd A(points.size() * 3, 9);
286 Eigen::VectorXd b(points.size() * 3);
287
288 for (size_t i = 0; i < points.size(); ++i) {
289 const auto& point = points[i];
290 Eigen::Vector3d corrected = point.raw.value - params_.bias;
291
292 // Build linear system for scale matrix elements
293 // For each component: ref_i = sum_j(scale_ij * corrected_j)
294 for (int row = 0; row < 3; ++row) {
295 int idx = i * 3 + row;
296 b(idx) = point.reference(row);
297
298 for (int col = 0; col < 3; ++col) {
299 A(idx, row * 3 + col) = corrected(col);
300 }
301 }
302 }
303
304 // Solve least squares
305 Eigen::VectorXd scale_vec = A.colPivHouseholderQr().solve(b);
306
307 // Reshape to matrix
308 for (int i = 0; i < 3; ++i) {
309 for (int j = 0; j < 3; ++j) {
310 params_.scale_matrix(i, j) = scale_vec(i * 3 + j);
311 }
312 }
313}
314
316 if (calibration_data_.empty()) {
317 return 0.0;
318 }
319
320 // Compute quality based on residuals
321 double total_error = 0.0;
322 int count = 0;
323
324 for (const auto& point : calibration_data_) {
325 auto calibrated = calibrateReading(point.raw);
326 if (calibrated.valid) {
327 Eigen::Vector3d error = calibrated.value - point.reference;
328 total_error += error.norm();
329 count++;
330 }
331 }
332
333 if (count == 0) {
334 return 0.0;
335 }
336
337 double avg_error = total_error / count;
338 // Quality is inverse of error (normalized)
339 double quality = 1.0 / (1.0 + avg_error);
340 return std::clamp(quality, 0.0, 1.0);
341}
342
343bool IMUCalibration::detectDrift(const RawReading& reading) const {
344 // Simple drift detection: check if reading is far from expected
345 // This is a placeholder - real implementation would use statistical tests
346
347 if (recent_readings_.size() < 10) {
348 return false;
349 }
350
351 // Compute mean and std of recent readings
352 Eigen::Vector3d mean = Eigen::Vector3d::Zero();
353 for (const auto& r : recent_readings_) {
354 mean += r.value;
355 }
356 mean /= static_cast<double>(recent_readings_.size());
357
358 Eigen::Vector3d diff = reading.value - mean;
359 double distance = diff.norm();
360
361 // Threshold based on sensor type
362 double threshold = (sensor_type_ == SensorType::MAGNETOMETER) ? 0.1 : 1.0;
363
364 return distance > threshold;
365}
366
367// IMUCalibrationSystem implementation
368
374
376
377std::shared_ptr<IMUCalibration> IMUCalibrationSystem::getCalibrator(SensorType type) {
378 switch (type) {
379 case SensorType::ACCELEROMETER:
380 return accel_calibrator_;
381 case SensorType::GYROSCOPE:
382 return gyro_calibrator_;
383 case SensorType::MAGNETOMETER:
384 return mag_calibrator_;
385 }
386 return nullptr;
387}
388
390 const std::vector<IMUCalibration::RawReading>& accel_readings,
391 const std::vector<IMUCalibration::RawReading>& gyro_readings,
392 const std::vector<IMUCalibration::RawReading>& mag_readings,
393 const Eigen::Vector3d& reference_magnetic_field) {
394 bool success = true;
395
396 // Calibrate accelerometer
397 if (!accel_readings.empty()) {
398 for (const auto& reading : accel_readings) {
400 point.raw = reading;
401 // Reference will be computed in calibrateAccelerometer
402 accel_calibrator_->addCalibrationPoint(point);
403 }
404 success &= accel_calibrator_->calibrate();
405 }
406
407 // Calibrate gyroscope
408 if (!gyro_readings.empty()) {
409 for (const auto& reading : gyro_readings) {
411 point.raw = reading;
412 gyro_calibrator_->addCalibrationPoint(point);
413 }
414 success &= gyro_calibrator_->calibrate();
415 }
416
417 // Calibrate magnetometer
418 if (!mag_readings.empty()) {
419 for (const auto& reading : mag_readings) {
421 point.raw = reading;
422 point.reference = reference_magnetic_field;
423 point.reference_uncertainty = 0.001; // 1 mT uncertainty
424 mag_calibrator_->addCalibrationPoint(point);
425 }
426 success &= mag_calibrator_->calibrate();
427 }
428
429 return success;
430}
431
434 const IMUCalibration::RawReading& mag) const {
436
437 calibrated.accelerometer = accel_calibrator_->calibrateReading(accel);
438 calibrated.gyroscope = gyro_calibrator_->calibrateReading(gyro);
439 calibrated.magnetometer = mag_calibrator_->calibrateReading(mag);
440
441 return calibrated;
442}
443
445 CalibrationStatus status;
446 status.accel = accel_calibrator_->getStatus();
447 status.gyro = gyro_calibrator_->getStatus();
448 status.mag = mag_calibrator_->getStatus();
449 return status;
450}
451
452} // namespace calibration
453} // namespace fsw
static bool calibrated
Definition board_adc.c:17
std::shared_ptr< IMUCalibration > gyro_calibrator_
std::shared_ptr< IMUCalibration > mag_calibrator_
std::shared_ptr< IMUCalibration > getCalibrator(SensorType type)
Get calibrator for specific sensor.
std::shared_ptr< IMUCalibration > accel_calibrator_
bool calibrateAll(const std::vector< IMUCalibration::RawReading > &accel_readings, const std::vector< IMUCalibration::RawReading > &gyro_readings, const std::vector< IMUCalibration::RawReading > &mag_readings, const Eigen::Vector3d &reference_magnetic_field)
Calibrate all sensors.
CalibratedIMU calibrateIMU(const IMUCalibration::RawReading &accel, const IMUCalibration::RawReading &gyro, const IMUCalibration::RawReading &mag) const
bool calibrate()
Perform calibration.
void reset()
Reset calibration.
bool calibrateMagEllipsoidFit(const std::vector< RawReading > &readings)
void estimateBias(const std::vector< RawReading > &readings)
void estimateScaleAndMisalignment(const std::vector< CalibrationPoint > &points)
std::vector< CalibrationPoint > calibration_data_
bool calibrateGyroZeroVelocity(const std::vector< RawReading > &readings)
bool calibrateMagnetometer(const std::vector< RawReading > &readings, const Eigen::Vector3d &reference_field)
Calibrate magnetometer using known field.
std::vector< RawReading > recent_readings_
bool calibrateGyroscope(const std::vector< RawReading > &readings, const std::vector< double > &reference_rates={})
Calibrate gyroscope using rotation rates.
CalibratedReading calibrateReading(const RawReading &raw) const
Apply calibration to raw reading.
bool detectDrift(const RawReading &reading) const
CalibrationParams getCalibrationParams() const
Get calibration parameters.
CalibrationStatus getStatus() const
Get calibration status.
bool calibrateAccelStatic(const std::vector< RawReading > &readings)
void initialize(const CalibrationParams &initial_params=CalibrationParams{})
Initialize calibration.
bool calibrateGyroRotation(const std::vector< RawReading > &readings, const std::vector< double > &reference_rates)
bool calibrateAccelerometer(const std::vector< RawReading > &readings)
Calibrate accelerometer using static positions.
void addCalibrationPoint(const CalibrationPoint &point)
Add calibration data point.
bool isValid() const
Check if calibration is valid.
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
size_type count(const key_type &key) const
Definition json.hpp:19264
static osMutexId_t lock
Definition mode.c:11
Output smoothing for calibrated PT pressure, per sensor uid (ESP32 and LabJack T7 alike).
Motorized ball valve position: raw ADC counts -> % open.
bool success
static ArmGate g(kMe)
static boot_report_t r
static int32_t diff(uint32_t a, uint32_t b)