Skip to content

Commit 967a4d7

Browse files
committed
pbio/imu: Make 3D heading optional.
This way we can introduce the feature gradually, defaulting to the current 1D behavior.
1 parent accb117 commit 967a4d7

File tree

7 files changed

+120
-35
lines changed

7 files changed

+120
-35
lines changed

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
makes it properly equivalent to `hub.imu.heading`.
2020
- Re-implemented tilt using the gyro data by default. Pure accelerometer tilt
2121
can still be obtained with `hub.imu.tilt(use_gyro=False)`.
22-
- Re-implemented `hub.imu.heading()` to use projection of 3D orientation to
23-
improve performance when the hub is lifted off the ground. If necessary,
24-
pure 1D rotation can still be obtained from `hub.imu.rotation()`.
22+
- Re-implemented `hub.imu.heading()` to use optionally use the projection of 3D
23+
orientation to improve performance when the hub is lifted off the ground.
24+
The 1D-based heading remains the default for now.
2525

2626
### Fixed
2727
- Fixed `DriveBase.angle()` getting an incorrectly rounded gyro value, which

lib/pbio/include/pbio/drivebase.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
#include <pbio/servo.h>
1818

19+
#include <pbio/imu.h>
20+
1921
#if PBIO_CONFIG_NUM_DRIVEBASES > 0
2022

2123
typedef struct _pbio_drivebase_t {
2224
/**
23-
* True if a gyro or compass is used for heading control, else false.
25+
* Whether to use the gyro for heading control, and if so which type.
2426
*/
25-
bool use_gyro;
27+
pbio_imu_heading_type_t gyro_heading_type;
2628
/**
2729
* Synchronization state to indicate that one or more controllers are paused.
2830
*/
@@ -79,7 +81,7 @@ pbio_error_t pbio_drivebase_get_state_user_angle(pbio_drivebase_t *db, float *an
7981
pbio_error_t pbio_drivebase_reset(pbio_drivebase_t *db, int32_t distance, int32_t angle);
8082
pbio_error_t pbio_drivebase_get_drive_settings(const pbio_drivebase_t *db, int32_t *drive_speed, int32_t *drive_acceleration, int32_t *drive_deceleration, int32_t *turn_rate, int32_t *turn_acceleration, int32_t *turn_deceleration);
8183
pbio_error_t pbio_drivebase_set_drive_settings(pbio_drivebase_t *db, int32_t drive_speed, int32_t drive_acceleration, int32_t drive_deceleration, int32_t turn_rate, int32_t turn_acceleration, int32_t turn_deceleration);
82-
pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, bool use_gyro);
84+
pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, pbio_imu_heading_type_t heading_type);
8385

8486
#if PBIO_CONFIG_DRIVEBASE_SPIKE
8587

lib/pbio/include/pbio/imu.h

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@
1818
#include <pbio/error.h>
1919
#include <pbio/geometry.h>
2020

21+
/**
22+
* Heading type to use, set, or get.
23+
*/
24+
typedef enum {
25+
/**
26+
* Heading should not be used.
27+
*/
28+
PBIO_IMU_HEADING_TYPE_NONE,
29+
/**
30+
* The heading is the integrated gyro rate along one fixed axis.
31+
*/
32+
PBIO_IMU_HEADING_TYPE_1D,
33+
/**
34+
* The heading is angle between the projection of the line coming out of
35+
* the front of the hub onto the horizontal plane and the x-axis.
36+
*/
37+
PBIO_IMU_HEADING_TYPE_3D,
38+
} pbio_imu_heading_type_t;
39+
2140
/**
2241
* IMU settings flags.
2342
*
@@ -44,6 +63,10 @@ typedef enum {
4463
* The accelerometer offsets and scale has been calibrated.
4564
*/
4665
PBIO_IMU_SETTINGS_FLAGS_ACCEL_CALIBRATED = (1 << 5),
66+
/**
67+
* The heading correction for 1D rotation has been set.
68+
*/
69+
PBIO_IMU_SETTINGS_FLAGS_HEADING_CORRECTION_1D_SET = (1 << 6),
4770
} pbio_imu_persistent_settings_flags_t;
4871

4972
/**
@@ -68,6 +91,13 @@ typedef struct {
6891
pbio_geometry_xyz_t angular_velocity_bias_start;
6992
/** Angular velocity scale (unadjusted measured degrees per whole rotation) */
7093
pbio_geometry_xyz_t angular_velocity_scale;
94+
/**
95+
* Additional correction for 1D rotation in the user frame. Works on top
96+
* of other calibration settings and values. Only used for 1D heading.
97+
*
98+
* This setting may be removed in the future when removing 1D support.
99+
*/
100+
float heading_correction_1d;
71101
} pbio_imu_persistent_settings_t;
72102

73103
#if PBIO_CONFIG_IMU
@@ -98,11 +128,11 @@ pbio_error_t pbio_imu_get_single_axis_rotation(pbio_geometry_xyz_t *axis, float
98128

99129
pbio_geometry_side_t pbio_imu_get_up_side(bool calibrated);
100130

101-
float pbio_imu_get_heading(void);
131+
float pbio_imu_get_heading(pbio_imu_heading_type_t type);
102132

103133
void pbio_imu_set_heading(float desired_heading);
104134

105-
void pbio_imu_get_heading_scaled(pbio_angle_t *heading, int32_t *heading_rate, int32_t ctl_steps_per_degree);
135+
void pbio_imu_get_heading_scaled(pbio_imu_heading_type_t type, pbio_angle_t *heading, int32_t *heading_rate, int32_t ctl_steps_per_degree);
106136

107137
void pbio_orientation_imu_get_rotation(pbio_geometry_matrix_3x3_t *rotation);
108138

@@ -143,14 +173,14 @@ static inline pbio_geometry_side_t pbio_imu_get_up_side(bool calibrated) {
143173
return PBIO_GEOMETRY_SIDE_TOP;
144174
}
145175

146-
static inline float pbio_imu_get_heading(void) {
176+
static inline float pbio_imu_get_heading(pbio_imu_heading_type_t type) {
147177
return 0.0f;
148178
}
149179

150180
static inline void pbio_imu_set_heading(float desired_heading) {
151181
}
152182

153-
static inline void pbio_imu_get_heading_scaled(pbio_angle_t *heading, int32_t *heading_rate, int32_t ctl_steps_per_degree) {
183+
static inline void pbio_imu_get_heading_scaled(pbio_imu_heading_type_t type, pbio_angle_t *heading, int32_t *heading_rate, int32_t ctl_steps_per_degree) {
154184
}
155185

156186
static inline void pbio_orientation_imu_get_rotation(pbio_geometry_matrix_3x3_t *rotation) {

lib/pbio/src/drivebase.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ static pbio_error_t pbio_drivebase_get_state_control(pbio_drivebase_t *db, pbio_
182182
// Note that the heading speed estimate (used for derivative control still
183183
// uses the motor estimate rather than the gyro speed, to guarantee the
184184
// same stability properties to stabilize the motors.)
185-
if (db->use_gyro) {
186-
pbio_imu_get_heading_scaled(&state_heading->position, &state_heading->speed, db->control_heading.settings.ctl_steps_per_app_step);
185+
if (db->gyro_heading_type != PBIO_IMU_HEADING_TYPE_NONE) {
186+
pbio_imu_get_heading_scaled(db->gyro_heading_type, &state_heading->position, &state_heading->speed, db->control_heading.settings.ctl_steps_per_app_step);
187187
}
188188

189189
return PBIO_SUCCESS;
@@ -365,7 +365,7 @@ pbio_error_t pbio_drivebase_get_drivebase(pbio_drivebase_t **db_address, pbio_se
365365
pbio_drivebase_reset(db, 0, 0);
366366

367367
// By default, don't use gyro for steering control.
368-
db->use_gyro = false;
368+
db->gyro_heading_type = PBIO_IMU_HEADING_TYPE_NONE;
369369

370370
return PBIO_SUCCESS;
371371
}
@@ -376,13 +376,13 @@ pbio_error_t pbio_drivebase_get_drivebase(pbio_drivebase_t **db_address, pbio_se
376376
* This function will stop the drivebase if it is running.
377377
*
378378
* @param [in] db Drivebase instance.
379-
* @param [in] use_gyro Whether to use the gyro for heading control.
379+
* @param [in] heading_type Whether to use the gyro for heading control, and if so which type.
380380
* @return Error code.
381381
*/
382-
pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, bool use_gyro) {
382+
pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, pbio_imu_heading_type_t heading_type) {
383383

384384
// No need to reset controls if already in correct mode.
385-
if (db->use_gyro == use_gyro) {
385+
if (db->gyro_heading_type == heading_type) {
386386
return PBIO_SUCCESS;
387387
}
388388

@@ -393,7 +393,7 @@ pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, bool use_gyro) {
393393
return err;
394394
}
395395

396-
db->use_gyro = use_gyro;
396+
db->gyro_heading_type = heading_type;
397397
return PBIO_SUCCESS;
398398
}
399399

@@ -860,7 +860,7 @@ pbio_error_t pbio_drivebase_reset(pbio_drivebase_t *db, int32_t distance, int32_
860860
pbio_angle_diff(&measured_heading.position, &reported_new, &db->heading_offset);
861861

862862
// Synchronize heading and drivebase angle state if gyro in use.
863-
if (db->use_gyro) {
863+
if (db->gyro_heading_type != PBIO_IMU_HEADING_TYPE_NONE) {
864864
pbio_imu_set_heading(angle);
865865
}
866866

@@ -877,7 +877,7 @@ bool pbio_drivebase_any_uses_gyro(void) {
877877
pbio_drivebase_t *db = &drivebases[i];
878878

879879
// Only consider activated drive bases that use the gyro.
880-
if (!pbio_drivebase_update_loop_is_running(db) || !db->use_gyro) {
880+
if (!pbio_drivebase_update_loop_is_running(db) || db->gyro_heading_type == PBIO_IMU_HEADING_TYPE_NONE) {
881881
continue;
882882
}
883883

lib/pbio/src/imu.c

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static pbio_geometry_xyz_t acceleration_calibrated; // mm/s^2, in hub frame
8282
* 1D integrated angular velocity for each body axis.
8383
*
8484
* This is based on integrating the calibrated angular velocity over time, so
85-
* including its bias and adjustments to achhieve 360 degrees per rotation.
85+
* including its bias and adjustments to achieve 360 degrees per rotation.
8686
*
8787
* This is not used for 3D attitude estimation, but serves as a useful way to
8888
* estimate 1D rotations without being effected by accelerometer fusion which
@@ -180,9 +180,8 @@ const float standard_gravity = 9806.65f;
180180
*/
181181
static void pbio_imu_apply_pbdrv_settings(pbio_imu_persistent_settings_t *settings) {
182182

183-
// IMU config is loaded set pbio, while this first occurence of applying
184-
// settings is only called by pbsys (after pbio init), so this should
185-
// never happen.
183+
// First occurence of this being called is when pbsys loads or resets
184+
// the settings, so this should never happen.
186185
if (!imu_config) {
187186
return;
188187
}
@@ -206,6 +205,7 @@ void pbio_imu_set_default_settings(pbio_imu_persistent_settings_t *settings) {
206205
settings->gravity_neg.x = settings->gravity_neg.y = settings->gravity_neg.z = -standard_gravity;
207206
settings->angular_velocity_bias_start.x = settings->angular_velocity_bias_start.y = settings->angular_velocity_bias_start.z = 0.0f;
208207
settings->angular_velocity_scale.x = settings->angular_velocity_scale.y = settings->angular_velocity_scale.z = 360.0f;
208+
settings->heading_correction_1d = 360.0f;
209209
pbio_imu_apply_pbdrv_settings(settings);
210210
}
211211

@@ -521,6 +521,10 @@ pbio_error_t pbio_imu_set_settings(pbio_imu_persistent_settings_t *new_settings)
521521
persistent_settings->gravity_neg = new_settings->gravity_neg;
522522
}
523523

524+
if (new_settings->flags & PBIO_IMU_SETTINGS_FLAGS_HEADING_CORRECTION_1D_SET) {
525+
persistent_settings->heading_correction_1d = new_settings->heading_correction_1d;
526+
}
527+
524528
// If any settings were changed, request saving.
525529
if (new_settings->flags) {
526530
persistent_settings->flags |= new_settings->flags;
@@ -621,18 +625,38 @@ pbio_geometry_side_t pbio_imu_get_up_side(bool calibrated) {
621625
return pbio_geometry_side_from_vector(acceleration);
622626
}
623627

624-
static float heading_offset = 0;
628+
static float heading_offset_1d = 0;
629+
static float heading_offset_3d = 0;
625630

626631
/**
627632
* Reads the estimated IMU heading in degrees, accounting for user offset and
628633
* user-specified heading correction scaling constant.
629634
*
630635
* Heading is defined as clockwise positive.
631636
*
637+
* @param [in] type The type of heading to get.
638+
*
632639
* @return Heading angle in the base frame.
633640
*/
634-
float pbio_imu_get_heading(void) {
635-
return heading_rotations * 360.0f + heading_projection - heading_offset;
641+
float pbio_imu_get_heading(pbio_imu_heading_type_t type) {
642+
643+
// 3D. Mapping into user frame is already accounted for in the projection.
644+
if (type == PBIO_IMU_HEADING_TYPE_3D) {
645+
return heading_rotations * 360.0f + heading_projection - heading_offset_3d;
646+
}
647+
648+
// 1D. Map the per-axis integrated rotation to the user frame, then take
649+
// the negative z component as the heading for positive-clockwise convention.
650+
pbio_geometry_xyz_t heading_mapped;
651+
pbio_geometry_vector_map(&pbio_imu_base_orientation, &single_axis_rotation, &heading_mapped);
652+
653+
float correction = (persistent_settings && (persistent_settings->flags & PBIO_IMU_SETTINGS_FLAGS_HEADING_CORRECTION_1D_SET)) ?
654+
// If set, adjust by the user-specified scaling constant.
655+
(360.0f / persistent_settings->heading_correction_1d):
656+
// No (additional) correction.
657+
1.0f;
658+
659+
return -heading_mapped.z * correction - heading_offset_1d;
636660
}
637661

638662
/**
@@ -645,7 +669,8 @@ float pbio_imu_get_heading(void) {
645669
*/
646670
void pbio_imu_set_heading(float desired_heading) {
647671
heading_rotations = 0;
648-
heading_offset = pbio_imu_get_heading() + heading_offset - desired_heading;
672+
heading_offset_3d = pbio_imu_get_heading(PBIO_IMU_HEADING_TYPE_3D) + heading_offset_3d - desired_heading;
673+
heading_offset_1d = pbio_imu_get_heading(PBIO_IMU_HEADING_TYPE_1D) + heading_offset_1d - desired_heading;
649674
}
650675

651676
/**
@@ -657,14 +682,15 @@ void pbio_imu_set_heading(float desired_heading) {
657682
*
658683
* Heading is defined as clockwise positive.
659684
*
685+
* @param [in] type Heading type to get.
660686
* @param [out] heading The heading angle in control units.
661687
* @param [out] heading_rate The heading rate in control units.
662688
* @param [in] ctl_steps_per_degree The number of control steps per heading degree.
663689
*/
664-
void pbio_imu_get_heading_scaled(pbio_angle_t *heading, int32_t *heading_rate, int32_t ctl_steps_per_degree) {
690+
void pbio_imu_get_heading_scaled(pbio_imu_heading_type_t type, pbio_angle_t *heading, int32_t *heading_rate, int32_t ctl_steps_per_degree) {
665691

666692
// Heading in degrees of the robot.
667-
float heading_degrees = pbio_imu_get_heading();
693+
float heading_degrees = pbio_imu_get_heading(type);
668694

669695
// Number of whole rotations in control units (in terms of wheels, not robot).
670696
heading->rotations = (int32_t)(heading_degrees / (360000.0f / ctl_steps_per_degree));

pybricks/common/pb_type_imu.c

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ static mp_obj_t pb_type_imu_settings(size_t n_args, const mp_obj_t *pos_args, mp
188188
PB_ARG_DEFAULT_NONE(acceleration_threshold),
189189
PB_ARG_DEFAULT_NONE(angular_velocity_bias),
190190
PB_ARG_DEFAULT_NONE(angular_velocity_scale),
191-
PB_ARG_DEFAULT_NONE(acceleration_correction));
191+
PB_ARG_DEFAULT_NONE(acceleration_correction),
192+
PB_ARG_DEFAULT_NONE(heading_correction));
192193

193194
(void)self;
194195

@@ -225,6 +226,7 @@ static mp_obj_t pb_type_imu_settings(size_t n_args, const mp_obj_t *pos_args, mp
225226
mp_obj_new_tuple(MP_ARRAY_SIZE(angular_velocity_bias), angular_velocity_bias),
226227
mp_obj_new_tuple(MP_ARRAY_SIZE(angular_velocity_scale), angular_velocity_scale),
227228
mp_obj_new_tuple(MP_ARRAY_SIZE(acceleration_corrections), acceleration_corrections),
229+
mp_obj_new_float_from_f(get_settings->heading_correction_1d),
228230
};
229231
return mp_obj_new_tuple(MP_ARRAY_SIZE(ret), ret);
230232
}
@@ -283,18 +285,31 @@ static mp_obj_t pb_type_imu_settings(size_t n_args, const mp_obj_t *pos_args, mp
283285
set_settings.gravity_neg.z = mp_obj_get_float(gravity[5]);
284286
}
285287

288+
if (heading_correction_in != mp_const_none) {
289+
set_settings.flags |= PBIO_IMU_SETTINGS_FLAGS_HEADING_CORRECTION_1D_SET;
290+
set_settings.heading_correction_1d = mp_obj_get_float(heading_correction_in);
291+
}
292+
286293
pb_assert(pbio_imu_set_settings(&set_settings));
287294

288295
return mp_const_none;
289296
}
290297
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_imu_settings_obj, 1, pb_type_imu_settings);
291298

292299
// pybricks._common.IMU.heading
293-
static mp_obj_t pb_type_imu_heading(mp_obj_t self_in) {
294-
(void)self_in;
295-
return mp_obj_new_float(pbio_imu_get_heading());
300+
static mp_obj_t pb_type_imu_heading(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
301+
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
302+
pb_type_imu_obj_t, self,
303+
PB_ARG_DEFAULT_NONE(heading_type));
304+
305+
(void)self;
306+
pbio_imu_heading_type_t type = heading_type_in == MP_OBJ_NEW_QSTR(MP_QSTR_3D) ?
307+
PBIO_IMU_HEADING_TYPE_3D : PBIO_IMU_HEADING_TYPE_1D;
308+
309+
return mp_obj_new_float(pbio_imu_get_heading(type));
310+
;
296311
}
297-
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_imu_heading_obj, pb_type_imu_heading);
312+
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_imu_heading_obj, 1, pb_type_imu_heading);
298313

299314
// pybricks._common.IMU.orientation
300315
STATIC mp_obj_t common_IMU_orientation(mp_obj_t self_in) {

pybricks/robotics/pb_type_drivebase.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,19 @@ static mp_obj_t pb_type_DriveBase_use_gyro(size_t n_args, const mp_obj_t *pos_ar
377377
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
378378
pb_type_DriveBase_obj_t, self,
379379
PB_ARG_REQUIRED(use_gyro));
380-
pb_assert(pbio_drivebase_set_use_gyro(self->db, mp_obj_is_true(use_gyro_in)));
380+
381+
pbio_imu_heading_type_t type = PBIO_IMU_HEADING_TYPE_NONE;
382+
if (mp_obj_is_true(use_gyro_in)) {
383+
type = PBIO_IMU_HEADING_TYPE_1D;
384+
}
385+
386+
// Allows testing of 3D heading calculation before it becomes the default
387+
// in a future release.
388+
if (use_gyro_in == MP_OBJ_NEW_QSTR(MP_QSTR_3D)) {
389+
type = PBIO_IMU_HEADING_TYPE_3D;
390+
}
391+
392+
pb_assert(pbio_drivebase_set_use_gyro(self->db, type));
381393
return mp_const_none;
382394
}
383395
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_DriveBase_use_gyro_obj, 1, pb_type_DriveBase_use_gyro);

0 commit comments

Comments
 (0)