Skip to content

Commit 0ccb111

Browse files
committed
Huge restructurisation of folders
1 parent 5a81653 commit 0ccb111

File tree

10 files changed

+1678
-5
lines changed

10 files changed

+1678
-5
lines changed

firmware/app/config.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747

4848
// Complementary Filter
4949
#define CF_ALPHA 0.98f // 98% gyro, 2% accel
50+
#define ACC_MAG_MIN 0.75f // Minimum accel magnitude to trust (g)
51+
#define ACC_MAG_MAX 1.25f // Maximum accel magnitude to trust (g)
5052

5153
// Arming
5254
#define ARM_RAMP_RATE 2.0f // Arm ramp rate (1/s)

firmware/core/control/attitude.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,21 @@ void attitude_update(float rollRate_dps, float pitchRate_dps,
5050
// Gyro integration prediction
5151
float roll_pred = currentAngles.roll_deg + rollRate_dps * dt_s;
5252
float pitch_pred = currentAngles.pitch_deg + pitchRate_dps * dt_s;
53-
54-
// IMU: Keep CF_ALPHA = 0.98f
55-
currentAngles.roll_deg = CF_ALPHA * roll_pred + (1.0f - CF_ALPHA) * roll_acc_lpf;
56-
currentAngles.pitch_deg = CF_ALPHA * pitch_pred + (1.0f - CF_ALPHA) * pitch_acc_lpf;
57-
53+
54+
// Magnitude gating: only apply accel correction if magnitude is close to 1g
55+
// This prevents drift during free-fall, high-G maneuvers, or vibrations
56+
float acc_magnitude = sqrtf(ax_g*ax_g + ay_g*ay_g + az_g*az_g);
57+
58+
if (acc_magnitude >= ACC_MAG_MIN && acc_magnitude <= ACC_MAG_MAX) {
59+
// Normal complementary filter with accel correction
60+
currentAngles.roll_deg = CF_ALPHA * roll_pred + (1.0f - CF_ALPHA) * roll_acc_lpf;
61+
currentAngles.pitch_deg = CF_ALPHA * pitch_pred + (1.0f - CF_ALPHA) * pitch_acc_lpf;
62+
} else {
63+
// Pure gyro integration (accel is unreliable)
64+
currentAngles.roll_deg = roll_pred;
65+
currentAngles.pitch_deg = pitch_pred;
66+
}
67+
5868
// Normalize angles to [-180, +180] range
5969
currentAngles.roll_deg = math::normalize_angle_deg(currentAngles.roll_deg);
6070
currentAngles.pitch_deg = math::normalize_angle_deg(currentAngles.pitch_deg);

platformio.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ build_flags = ${common.build_flags}
8787
test_build_src = yes
8888
build_src_filter =
8989
+<core/*> ; Include core logic (logger_stub.cpp is here)
90+
+<stubs/*> ; Include core logic (logger_stub.cpp is here)
9091
-<platform/> ; Exclude all platform-specific code
9192
-<app/> ; Exclude application code
93+
-<logger.cpp/> ; Exclude real logger implementation
9294
lib_deps =
9395
doctest/doctest@^2.4.11
9496
FabioBatSilva/ArduinoFake@^0.3.1

tests/stubs/config_cal_stub.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @file config_cal_stub.cpp
3+
* @brief Stub implementation of calibration configuration for native testing
4+
*/
5+
6+
#include "config_cal.h"
7+
8+
// Default calibration configuration
9+
CalConfig g_cal_cfg = {
10+
.samples = 300,
11+
.sample_rate_hz = 200.0f,
12+
.max_rate_rms_dps = 2.0f,
13+
.acc_mag_min_g = 0.95f,
14+
.acc_mag_max_g = 1.05f,
15+
.max_age_days = 30,
16+
.cal_timeout_ms = 10000
17+
};

tests/stubs/imu_mapping_stub.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @file imu_mapping_stub.cpp
3+
* @brief Stub implementation of IMU mapping for native testing
4+
*/
5+
6+
#include "imu_mapping.h"
7+
#include <cstdio>
8+
9+
// Predefined IMU orientation mappings
10+
const AxisMapping IMU_ORIENT_STANDARD = {
11+
.axis = {0, 1, 2},
12+
.sign = {1, -1, 1},
13+
.name = "Standard (0°)"
14+
};
15+
16+
const AxisMapping IMU_ORIENT_ROTATED_90 = {
17+
.axis = {1, 0, 2},
18+
.sign = {-1, 1, 1},
19+
.name = "Rotated 90° CW"
20+
};
21+
22+
const AxisMapping IMU_ORIENT_ROTATED_180 = {
23+
.axis = {0, 1, 2},
24+
.sign = {-1, -1, 1},
25+
.name = "Rotated 180°"
26+
};
27+
28+
const AxisMapping IMU_ORIENT_ROTATED_270 = {
29+
.axis = {1, 0, 2},
30+
.sign = {1, -1, 1},
31+
.name = "Rotated 270° CW"
32+
};
33+
34+
const AxisMapping IMU_ORIENT_INVERTED = {
35+
.axis = {0, 1, 2},
36+
.sign = {-1, 1, -1},
37+
.name = "Inverted (upside down)"
38+
};
39+
40+
// Current active mapping
41+
static AxisMapping g_current_mapping = IMU_ORIENT_STANDARD;
42+
static bool g_mapping_initialized = false;
43+
44+
void imu_map_vector(const float sensor[3], float body[3]) {
45+
if (!g_mapping_initialized) {
46+
fprintf(stderr, "ERROR: IMU mapping not initialized\n");
47+
return;
48+
}
49+
50+
for (int i = 0; i < 3; i++) {
51+
int axis_idx = g_current_mapping.axis[i];
52+
body[i] = g_current_mapping.sign[i] * sensor[axis_idx];
53+
}
54+
}
55+
56+
bool imu_validate_mapping(const AxisMapping& mapping) {
57+
// Validate axis indices
58+
for (int i = 0; i < 3; i++) {
59+
if (mapping.axis[i] < 0 || mapping.axis[i] > 2) {
60+
return false;
61+
}
62+
}
63+
64+
// Validate sign multipliers
65+
for (int i = 0; i < 3; i++) {
66+
if (mapping.sign[i] != 1 && mapping.sign[i] != -1) {
67+
return false;
68+
}
69+
}
70+
71+
// Validate bijective mapping (each axis used once)
72+
bool axis_used[3] = {false, false, false};
73+
for (int i = 0; i < 3; i++) {
74+
int axis = mapping.axis[i];
75+
if (axis_used[axis]) {
76+
return false;
77+
}
78+
axis_used[axis] = true;
79+
}
80+
81+
// Validate name
82+
if (mapping.name == nullptr) {
83+
return false;
84+
}
85+
86+
return true;
87+
}
88+
89+
bool imu_set_orientation(const AxisMapping& orientation) {
90+
if (!imu_validate_mapping(orientation)) {
91+
return false;
92+
}
93+
94+
g_current_mapping = orientation;
95+
g_mapping_initialized = true;
96+
return true;
97+
}
98+
99+
const AxisMapping& imu_get_orientation() {
100+
return g_current_mapping;
101+
}

tests/stubs/nvs_cal_stub.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @file nvs_cal_stub.cpp
3+
* @brief Stub implementation of NVS calibration for native testing
4+
*/
5+
6+
#include "nvs_cal.h"
7+
#include <cmath>
8+
9+
// In-memory calibration storage for testing
10+
static AccelCalData g_stored_cal = {
11+
.ax_off = 0.0f,
12+
.ay_off = 0.0f,
13+
.az_off = 0.0f,
14+
.timestamp = 0,
15+
.version = 1
16+
};
17+
static bool g_cal_exists = false;
18+
19+
// Maximum reasonable offset magnitude (in g)
20+
static const float MAX_OFFSET_G = 0.2f;
21+
22+
bool nvs_cal_init() {
23+
return true;
24+
}
25+
26+
bool nvs_load_accel_cal(AccelCalData& out_data) {
27+
if (!g_cal_exists) {
28+
return false;
29+
}
30+
out_data = g_stored_cal;
31+
return true;
32+
}
33+
34+
bool nvs_save_accel_cal(const AccelCalData& cal_data) {
35+
g_stored_cal = cal_data;
36+
g_cal_exists = true;
37+
return true;
38+
}
39+
40+
bool nvs_has_fresh_accel_cal(uint32_t max_age_days) {
41+
if (!g_cal_exists) {
42+
return false;
43+
}
44+
return nvs_validate_accel_cal(g_stored_cal);
45+
}
46+
47+
bool nvs_validate_accel_cal(const AccelCalData& cal_data) {
48+
// Validate offset bounds
49+
if (std::abs(cal_data.ax_off) > MAX_OFFSET_G) return false;
50+
if (std::abs(cal_data.ay_off) > MAX_OFFSET_G) return false;
51+
if (std::abs(cal_data.az_off) > MAX_OFFSET_G) return false;
52+
53+
// Check for NaN/Infinity
54+
if (!std::isfinite(cal_data.ax_off)) return false;
55+
if (!std::isfinite(cal_data.ay_off)) return false;
56+
if (!std::isfinite(cal_data.az_off)) return false;
57+
58+
return true;
59+
}

0 commit comments

Comments
 (0)