Skip to content

Commit 6bfc545

Browse files
HilariousCowslouken
authored andcommitted
Accelerometer Tolerance is now calibrated before Gyro Drift.
1 parent 07ef532 commit 6bfc545

File tree

3 files changed

+185
-69
lines changed

3 files changed

+185
-69
lines changed

test/gamepadutils.c

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,8 +1031,10 @@ struct GyroDisplay
10311031
int estimated_sensor_rate_hz; /*hz - our estimation of the actual polling rate by observing packets received*/
10321032
float euler_displacement_angles[3]; /* pitch, yaw, roll */
10331033
Quaternion gyro_quaternion; /* Rotation since startup/reset, comprised of each gyro speed packet times sensor delta time. */
1034-
float drift_calibration_progress_frac; /* [0..1] */
1034+
EGyroCalibrationPhase current_calibration_phase;
1035+
float calibration_phase_progress_fraction; /* [0..1] */
10351036
float accelerometer_noise_sq; /* Distance between last noise and new noise. Used to indicate motion.*/
1037+
float accelerometer_noise_tolerance_sq; /* Maximum amount of noise detected during the Noise Profiling Phase */
10361038

10371039
GamepadButton *reset_gyro_button;
10381040
GamepadButton *calibrate_gyro_button;
@@ -1049,6 +1051,10 @@ GyroDisplay *CreateGyroDisplay(SDL_Renderer *renderer)
10491051
ctx->gyro_quaternion = quat_identity;
10501052
ctx->reported_sensor_rate_hz = 0;
10511053
ctx->next_reported_sensor_time = 0;
1054+
ctx->current_calibration_phase = GYRO_CALIBRATION_PHASE_OFF;
1055+
ctx->calibration_phase_progress_fraction = 0.0f; /* [0..1] */
1056+
ctx->accelerometer_noise_sq = 0.0f;
1057+
ctx->accelerometer_noise_tolerance_sq = ACCELEROMETER_NOISE_THRESHOLD; /* Will be overwritten but this avoids divide by zero. */
10521058
ctx->reset_gyro_button = CreateGamepadButton(renderer, "Reset View");
10531059
ctx->calibrate_gyro_button = CreateGamepadButton(renderer, "Recalibrate Drift");
10541060
}
@@ -1362,17 +1368,7 @@ static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, cons
13621368
}
13631369
}
13641370

1365-
bool BHasCachedGyroDriftSolution(GyroDisplay *ctx)
1366-
{
1367-
if (!ctx) {
1368-
return false;
1369-
}
1370-
return (ctx->gyro_drift_solution[0] != 0.0f ||
1371-
ctx->gyro_drift_solution[1] != 0.0f ||
1372-
ctx->gyro_drift_solution[2] != 0.0f);
1373-
}
1374-
1375-
void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, float drift_calibration_progress_frac, float accelerometer_noise_sq)
1371+
void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, EGyroCalibrationPhase calibration_phase, float drift_calibration_progress_frac, float accelerometer_noise_sq, float accelerometer_noise_tolerance_sq)
13761372
{
13771373
if (!ctx) {
13781374
return;
@@ -1391,8 +1387,10 @@ void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, fl
13911387
SDL_memcpy(ctx->gyro_drift_solution, gyro_drift_solution, sizeof(ctx->gyro_drift_solution));
13921388
SDL_memcpy(ctx->euler_displacement_angles, euler_displacement_angles, sizeof(ctx->euler_displacement_angles));
13931389
ctx->gyro_quaternion = *gyro_quaternion;
1394-
ctx->drift_calibration_progress_frac = drift_calibration_progress_frac;
1390+
ctx->current_calibration_phase = calibration_phase;
1391+
ctx->calibration_phase_progress_fraction = drift_calibration_progress_frac;
13951392
ctx->accelerometer_noise_sq = accelerometer_noise_sq;
1393+
ctx->accelerometer_noise_tolerance_sq = accelerometer_noise_tolerance_sq;
13961394
}
13971395

13981396
extern GamepadButton *GetGyroResetButton(GyroDisplay *ctx)
@@ -1713,7 +1711,7 @@ void RenderSensorTimingInfo(GyroDisplay *ctx, GamepadDisplay *gamepad_display)
17131711
/* Sensor timing section */
17141712
char text[128];
17151713
const float new_line_height = gamepad_display->button_height + 2.0f;
1716-
const float text_offset_x = ctx->area.x + ctx->area.w / 4.0f + 40.0f;
1714+
const float text_offset_x = ctx->area.x + ctx->area.w / 4.0f + 35.0f;
17171715
/* Anchor to bottom left of principle rect. */
17181716
float text_y_pos = ctx->area.y + ctx->area.h - new_line_height * 2;
17191717
/*
@@ -1759,7 +1757,7 @@ void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_
17591757
float log_y = ctx->area.y + BUTTON_PADDING;
17601758
const float new_line_height = gamepad_display->button_height + 2.0f;
17611759
GamepadButton *start_calibration_button = GetGyroCalibrateButton(ctx);
1762-
bool bHasCachedDriftSolution = BHasCachedGyroDriftSolution(ctx);
1760+
17631761

17641762
/* Show the recalibration progress bar. */
17651763
float recalibrate_button_width = GetGamepadButtonLabelWidth(start_calibration_button) + 2 * BUTTON_PADDING;
@@ -1769,24 +1767,46 @@ void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_
17691767
recalibrate_button_area.w = GetGamepadButtonLabelWidth(start_calibration_button) + 2.0f * BUTTON_PADDING;
17701768
recalibrate_button_area.h = gamepad_display->button_height + BUTTON_PADDING * 2.0f;
17711769

1772-
if (!bHasCachedDriftSolution) {
1773-
SDL_snprintf(label_text, sizeof(label_text), "Progress: %3.0f%% ", ctx->drift_calibration_progress_frac * 100.0f);
1774-
} else {
1775-
SDL_strlcpy(label_text, "Calibrate Drift", sizeof(label_text));
1770+
/* Above button */
1771+
SDL_strlcpy(label_text, "Gyro Orientation:", sizeof(label_text));
1772+
SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y - new_line_height, label_text);
1773+
1774+
/* Button label vs state */
1775+
if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_OFF) {
1776+
SDL_strlcpy(label_text, "Start Gyro Calibration", sizeof(label_text));
1777+
} else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) {
1778+
SDL_snprintf(label_text, sizeof(label_text), "Noise Progress: %3.0f%% ", ctx->calibration_phase_progress_fraction * 100.0f);
1779+
} else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) {
1780+
SDL_snprintf(label_text, sizeof(label_text), "Drift Progress: %3.0f%% ", ctx->calibration_phase_progress_fraction * 100.0f);
1781+
} else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_COMPLETE) {
1782+
SDL_strlcpy(label_text, "Recalibrate Gyro", sizeof(label_text));
17761783
}
17771784

17781785
SetGamepadButtonLabel(start_calibration_button, label_text);
17791786
SetGamepadButtonArea(start_calibration_button, &recalibrate_button_area);
1780-
RenderGamepadButton(start_calibration_button);
1787+
RenderGamepadButton(start_calibration_button);
17811788

1782-
/* Above button */
1783-
SDL_strlcpy(label_text, "Gyro Orientation:", sizeof(label_text));
1784-
SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y - new_line_height, label_text);
1785-
1786-
if (!bHasCachedDriftSolution) {
1789+
const float flAbsoluteMaxAccelerationG = 0.125f;
1790+
bool bExtremeNoise = ctx->accelerometer_noise_sq > (flAbsoluteMaxAccelerationG * flAbsoluteMaxAccelerationG);
1791+
/* Explicit warning message if we detect too much movement */
1792+
if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_OFF) {
1793+
1794+
if (bExtremeNoise)
1795+
{
1796+
SDL_strlcpy(label_text, "GamePad Must Be Still", sizeof(label_text));
1797+
SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height, label_text);
1798+
SDL_strlcpy(label_text, "Place GamePad On Table", sizeof(label_text));
1799+
SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height * 2, label_text);
1800+
}
1801+
}
17871802

1788-
float flNoiseFraction = SDL_clamp(SDL_sqrtf(ctx->accelerometer_noise_sq) / ACCELEROMETER_NOISE_THRESHOLD, 0.0f, 1.0f);
1789-
bool bTooMuchNoise = (flNoiseFraction == 1.0f);
1803+
if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING
1804+
|| ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING)
1805+
{
1806+
float flAbsoluteNoiseFraction = SDL_clamp(ctx->accelerometer_noise_sq / (flAbsoluteMaxAccelerationG * flAbsoluteMaxAccelerationG), 0.0f, 1.0f);
1807+
float flAbsoluteToleranceFraction = SDL_clamp(ctx->accelerometer_noise_tolerance_sq / (flAbsoluteMaxAccelerationG * flAbsoluteMaxAccelerationG), 0.0f, 1.0f);
1808+
float flRelativeNoiseFraction = SDL_clamp(ctx->accelerometer_noise_sq / ctx->accelerometer_noise_tolerance_sq, 0.0f, 1.0f);
1809+
bool bTooMuchNoise = (flAbsoluteNoiseFraction == 1.0f);
17901810

17911811
float noise_bar_height = gamepad_display->button_height;
17921812
SDL_FRect noise_bar_rect;
@@ -1795,21 +1815,35 @@ void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_
17951815
noise_bar_rect.w = recalibrate_button_area.w;
17961816
noise_bar_rect.h = noise_bar_height;
17971817

1818+
//SDL_strlcpy(label_text, "Place GamePad On Table", sizeof(label_text));
1819+
SDL_snprintf(label_text, sizeof(label_text), "Noise Tolerance: %3.3fG ", SDL_sqrtf(ctx->accelerometer_noise_tolerance_sq) );
1820+
SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height * 2, label_text);
1821+
17981822
/* Adjust the noise bar rectangle based on the accelerometer noise value */
17991823

1800-
float noise_bar_fill_width = flNoiseFraction * noise_bar_rect.w; /* Scale the width based on the noise value */
1824+
float noise_bar_fill_width = flAbsoluteNoiseFraction * noise_bar_rect.w; /* Scale the width based on the noise value */
18011825
SDL_FRect noise_bar_fill_rect;
18021826
noise_bar_fill_rect.x = noise_bar_rect.x + (noise_bar_rect.w - noise_bar_fill_width) * 0.5f;
18031827
noise_bar_fill_rect.y = noise_bar_rect.y;
18041828
noise_bar_fill_rect.w = noise_bar_fill_width;
18051829
noise_bar_fill_rect.h = noise_bar_height;
18061830

1807-
/* Set the color based on the noise value */
1808-
Uint8 red = (Uint8)(flNoiseFraction * 255.0f);
1809-
Uint8 green = (Uint8)((1.0f - flNoiseFraction) * 255.0f);
1831+
/* Set the color based on the noise value vs the tolerance */
1832+
Uint8 red = (Uint8)(flRelativeNoiseFraction * 255.0f);
1833+
Uint8 green = (Uint8)((1.0f - flRelativeNoiseFraction) * 255.0f);
18101834
SDL_SetRenderDrawColor(ctx->renderer, red, green, 0, 255); /* red when high noise, green when low noise */
18111835
SDL_RenderFillRect(ctx->renderer, &noise_bar_fill_rect); /* draw the filled rectangle */
18121836

1837+
float tolerance_bar_fill_width = flAbsoluteToleranceFraction * noise_bar_rect.w; /* Scale the width based on the noise value */
1838+
SDL_FRect tolerance_bar_rect;
1839+
tolerance_bar_rect.x = noise_bar_rect.x + (noise_bar_rect.w - tolerance_bar_fill_width) * 0.5f;
1840+
tolerance_bar_rect.y = noise_bar_rect.y;
1841+
tolerance_bar_rect.w = tolerance_bar_fill_width;
1842+
tolerance_bar_rect.h = noise_bar_height;
1843+
1844+
SDL_SetRenderDrawColor(ctx->renderer, 128, 128, 0, 255);
1845+
SDL_RenderRect(ctx->renderer, &tolerance_bar_rect); /* draw the tolerance rectangle */
1846+
18131847
SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255); /* gray box */
18141848
SDL_RenderRect(ctx->renderer, &noise_bar_rect); /* draw the outline rectangle */
18151849

@@ -1828,7 +1862,7 @@ void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_
18281862
progress_bar_rect.h = BUTTON_PADDING * 0.5f;
18291863

18301864
/* Adjust the drift bar rectangle based on the drift calibration progress fraction */
1831-
float drift_bar_fill_width = bTooMuchNoise ? 1.0f : ctx->drift_calibration_progress_frac * progress_bar_rect.w;
1865+
float drift_bar_fill_width = bTooMuchNoise ? 1.0f : ctx->calibration_phase_progress_fraction * progress_bar_rect.w;
18321866
SDL_FRect progress_bar_fill;
18331867
progress_bar_fill.x = progress_bar_rect.x;
18341868
progress_bar_fill.y = progress_bar_rect.y;
@@ -1947,14 +1981,14 @@ void RenderGyroDisplay(GyroDisplay *ctx, GamepadDisplay *gamepadElements, SDL_Ga
19471981
SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
19481982

19491983
RenderSensorTimingInfo(ctx, gamepadElements);
1950-
19511984
RenderGyroDriftCalibrationButton(ctx, gamepadElements);
19521985

1953-
bool bHasCachedDriftSolution = BHasCachedGyroDriftSolution(ctx);
1954-
if (bHasCachedDriftSolution) {
1986+
/* Render Gyro calibration phases */
1987+
if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_COMPLETE) {
19551988
float bottom = RenderEulerReadout(ctx, gamepadElements);
19561989
RenderGyroGizmo(ctx, gamepad, bottom);
19571990
}
1991+
19581992
SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
19591993
}
19601994

test/gamepadutils.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,26 @@ extern void RenderGamepadButton(GamepadButton *ctx);
142142
extern void DestroyGamepadButton(GamepadButton *ctx);
143143

144144
/* Gyro element Display */
145-
/* If you want to calbirate against a known rotation (i.e. a turn table test) Increase ACCELEROMETER_NOISE_THRESHOLD to about 5, or drift correction will be constantly reset.*/
146-
#define ACCELEROMETER_NOISE_THRESHOLD 0.5f
145+
146+
/* This is used as the initial noise tolernace threshold. It's set very close to zero to avoid divide by zero while we're evaluating the noise profile. Each controller may have a very different noise profile.*/
147+
#define ACCELEROMETER_NOISE_THRESHOLD 1e-6f
148+
149+
/* Gyro Calibration Phases */
150+
typedef enum
151+
{
152+
GYRO_CALIBRATION_PHASE_OFF, /* Calibration has not yet been evaluated - signal to the user to put the controller on a flat surface before beginning the calibration process */
153+
GYRO_CALIBRATION_PHASE_NOISE_PROFILING, /* Find the max accelerometer noise for a fixed period */
154+
GYRO_CALIBRATION_PHASE_DRIFT_PROFILING, /* Find the drift while the accelerometer is below the accelerometer noise tolerance */
155+
GYRO_CALIBRATION_PHASE_COMPLETE, /* Calibration has finished */
156+
} EGyroCalibrationPhase;
157+
147158
typedef struct Quaternion Quaternion;
148159
typedef struct GyroDisplay GyroDisplay;
149160

150161
extern void InitCirclePoints3D();
151162
extern GyroDisplay *CreateGyroDisplay(SDL_Renderer *renderer);
152163
extern void SetGyroDisplayArea(GyroDisplay *ctx, const SDL_FRect *area);
153-
extern bool BHasCachedGyroDriftSolution(GyroDisplay *ctx);
154-
extern void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, float drift_calibration_progress_frac, float accelerometer_noise_sq);
164+
extern void SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, EGyroCalibrationPhase calibration_phase, float drift_calibration_progress_frac, float accelerometer_noise_sq, float accelerometer_noise_tolerance_sq);
155165
extern GamepadButton *GetGyroResetButton(GyroDisplay *ctx);
156166
extern GamepadButton *GetGyroCalibrateButton(GyroDisplay *ctx);
157167
extern void RenderGyroDisplay(GyroDisplay *ctx, GamepadDisplay *gamepadElements, SDL_Gamepad *gamepad);

0 commit comments

Comments
 (0)