Skip to content

Commit 0227860

Browse files
committed
pbio/drivebase: Implement distance and angle state offsets.
This moves the setting and getting of the user state to pbio, which gives us more control over resetting the control state when this is done. Also implements setting nonzero values for these states.
1 parent bcddbf6 commit 0227860

File tree

5 files changed

+163
-27
lines changed

5 files changed

+163
-27
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"request": "launch",
106106
"program": "${workspaceFolder}/bricks/virtualhub/build-debug/virtualhub-micropython",
107107
"args": [
108-
"${workspaceFolder}/tests/virtualhub/motor/car.py"
108+
"${workspaceFolder}/tests/virtualhub/motor/drivebase.py"
109109
],
110110
"stopAtEntry": false,
111111
"cwd": "${workspaceFolder}",

lib/pbio/include/pbio/drivebase.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,23 @@ typedef struct _pbio_drivebase_t {
2929
bool control_paused;
3030
pbio_servo_t *left;
3131
pbio_servo_t *right;
32+
/**
33+
* Offset of the reported heading angle with respect to the measured value.
34+
*
35+
* Not used if gyro control is active. Then the gyro module handles offset.
36+
*/
37+
pbio_angle_t heading_offset;
38+
/**
39+
* Heading controller.
40+
*/
3241
pbio_control_t control_heading;
42+
/**
43+
* Offset of the reported distance with respect to the measured value.
44+
*/
45+
pbio_angle_t distance_offset;
46+
/**
47+
* Distance controller.
48+
*/
3349
pbio_control_t control_distance;
3450
} pbio_drivebase_t;
3551

@@ -56,6 +72,7 @@ pbio_error_t pbio_drivebase_stop(pbio_drivebase_t *db, pbio_control_on_completio
5672
// Measuring and settings:
5773

5874
pbio_error_t pbio_drivebase_get_state_user(pbio_drivebase_t *db, int32_t *distance, int32_t *drive_speed, int32_t *angle, int32_t *turn_rate);
75+
pbio_error_t pbio_drivebase_reset(pbio_drivebase_t *db, int32_t distance, int32_t angle);
5976
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);
6077
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);
6178
pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, bool use_gyro);

lib/pbio/src/drivebase.c

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static pbio_drivebase_t drivebases[PBIO_CONFIG_NUM_DRIVEBASES];
2121
/**
2222
* Gets the state of the drivebase update loop.
2323
*
24-
* This becomes true after a successful call to pbio_drivebase_setup and
24+
* This becomes true after a successful call to pbio_drivebase_get_drivebase and
2525
* becomes false when there is an error. Such as when the cable is unplugged.
2626
*
2727
* @param [in] db The drivebase instance
@@ -117,11 +117,11 @@ static void drivebase_adopt_settings(pbio_control_settings_t *s_distance, pbio_c
117117
* Get the physical and estimated state of a drivebase in units of control.
118118
*
119119
* @param [in] db The drivebase instance
120-
* @param [out] state_distance Physical and estimated state of the distance.
121-
* @param [out] state_heading Physical and estimated state of the heading.
120+
* @param [out] state_distance Physical and estimated state of the distance based on motor angles.
121+
* @param [out] state_heading Physical and estimated state of the heading based on motor angles.
122122
* @return Error code.
123123
*/
124-
static pbio_error_t pbio_drivebase_get_state_control(pbio_drivebase_t *db, pbio_control_state_t *state_distance, pbio_control_state_t *state_heading) {
124+
static pbio_error_t pbio_drivebase_get_state_via_motors(pbio_drivebase_t *db, pbio_control_state_t *state_distance, pbio_control_state_t *state_heading) {
125125

126126
// Get left servo state
127127
pbio_control_state_t state_left;
@@ -150,7 +150,35 @@ static pbio_error_t pbio_drivebase_get_state_control(pbio_drivebase_t *db, pbio_
150150
state_heading->speed_estimate = state_distance->speed_estimate - state_right.speed_estimate;
151151
state_heading->speed = state_distance->speed - state_right.speed;
152152

153+
return PBIO_SUCCESS;
154+
}
155+
156+
/**
157+
* Get the physical and estimated state of a drivebase in units of control.
158+
*
159+
* @param [in] db The drivebase instance
160+
* @param [out] state_distance Physical and estimated state of the distance.
161+
* @param [out] state_heading Physical and estimated state of the heading.
162+
* @return Error code.
163+
*/
164+
static pbio_error_t pbio_drivebase_get_state_control(pbio_drivebase_t *db, pbio_control_state_t *state_distance, pbio_control_state_t *state_heading) {
165+
166+
// Gets the "measured" state according to the driver motors.
167+
pbio_error_t err = pbio_drivebase_get_state_via_motors(db, state_distance, state_heading);
168+
if (err != PBIO_SUCCESS) {
169+
return err;
170+
}
171+
172+
// Subtract distance offset.
173+
pbio_angle_diff(&state_distance->position, &db->distance_offset, &state_distance->position);
174+
pbio_angle_diff(&state_distance->position_estimate, &db->distance_offset, &state_distance->position_estimate);
175+
176+
// Subtract heading offset
177+
pbio_angle_diff(&state_heading->position, &db->heading_offset, &state_heading->position);
178+
pbio_angle_diff(&state_heading->position_estimate, &db->heading_offset, &state_heading->position_estimate);
179+
153180
// Optionally use gyro to override the heading source for more accuracy.
181+
// The gyro manages its own offset, so we don't need to subtract it here.
154182
if (db->use_gyro) {
155183
pbio_imu_get_heading_scaled(&state_heading->position, &state_heading->speed, db->control_heading.settings.ctl_steps_per_app_step);
156184
}
@@ -235,7 +263,7 @@ static pbio_error_t pbio_drivebase_stop_from_servo(void *drivebase, bool clear_p
235263
#define ROT_MDEG_OVER_PI (114592) // 360 000 / pi
236264

237265
/**
238-
* Gets drivebase instance from two servo instances.
266+
* Gets and sets up drivebase instance from two servo instances.
239267
*
240268
* @param [out] db_address Drivebase instance if available.
241269
* @param [in] left Left servo instance.
@@ -329,6 +357,10 @@ pbio_error_t pbio_drivebase_get_drivebase(pbio_drivebase_t **db_address, pbio_se
329357
return PBIO_ERROR_INVALID_ARG;
330358
}
331359

360+
// Reset offsets so current distance and angle are 0. This relies on the
361+
// geometry, so it is done after the scaling is set.
362+
pbio_drivebase_reset(db, 0, 0);
363+
332364
// Finish setup. By default, don't use gyro.
333365
return pbio_drivebase_set_use_gyro(db, false);
334366
}
@@ -687,6 +719,50 @@ pbio_error_t pbio_drivebase_get_state_user(pbio_drivebase_t *db, int32_t *distan
687719
return PBIO_SUCCESS;
688720
}
689721

722+
/**
723+
* Stops the drivebase and resets the accumulated drivebase state in user units.
724+
*
725+
* If the gyro is being used for control, it will be reset to the same angle.
726+
*
727+
* @param [in] db The drivebase instance.
728+
* @param [out] distance Distance traveled in mm.
729+
* @param [out] angle Angle turned in degrees.
730+
* @return Error code.
731+
*/
732+
pbio_error_t pbio_drivebase_reset(pbio_drivebase_t *db, int32_t distance, int32_t angle) {
733+
734+
// Physically stops motors and stops the ongoing controllers, simplifying
735+
// the state reset since we won't need to restart ongoing motion.
736+
pbio_error_t err = pbio_drivebase_stop(db, PBIO_CONTROL_ON_COMPLETION_COAST);
737+
if (err != PBIO_SUCCESS) {
738+
return err;
739+
}
740+
741+
// Get measured state according to motor encoders.
742+
pbio_control_state_t measured_distance;
743+
pbio_control_state_t measured_heading;
744+
err = pbio_drivebase_get_state_via_motors(db, &measured_distance, &measured_heading);
745+
if (err != PBIO_SUCCESS) {
746+
return err;
747+
}
748+
749+
// We want to get: reported_new = measured - offset_new
750+
// So we can do: offset_new = measured - reported_new
751+
pbio_angle_t reported_new;
752+
753+
pbio_angle_from_low_res(&reported_new, distance, db->control_distance.settings.ctl_steps_per_app_step);
754+
pbio_angle_diff(&measured_distance.position, &reported_new, &db->distance_offset);
755+
756+
pbio_angle_from_low_res(&reported_new, angle, db->control_heading.settings.ctl_steps_per_app_step);
757+
pbio_angle_diff(&measured_heading.position, &reported_new, &db->heading_offset);
758+
759+
// Whether or not the gyro is being used, synchronize heading and drivebase
760+
// angle state.
761+
pbio_imu_set_heading(angle);
762+
763+
return PBIO_SUCCESS;
764+
}
765+
690766

691767
/**
692768
* Gets the drivebase settings in user units.

pybricks/robotics/pb_type_drivebase.c

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ typedef struct _pb_type_DriveBase_obj_t pb_type_DriveBase_obj_t;
2929
struct _pb_type_DriveBase_obj_t {
3030
mp_obj_base_t base;
3131
pbio_drivebase_t *db;
32-
int32_t initial_distance;
33-
int32_t initial_heading;
3432
#if PYBRICKS_PY_COMMON_CONTROL
3533
mp_obj_t heading_control;
3634
mp_obj_t distance_control;
@@ -39,18 +37,18 @@ struct _pb_type_DriveBase_obj_t {
3937
};
4038

4139
// pybricks.robotics.DriveBase.reset
42-
static mp_obj_t pb_type_DriveBase_reset(mp_obj_t self_in) {
43-
pb_type_DriveBase_obj_t *self = MP_OBJ_TO_PTR(self_in);
40+
static mp_obj_t pb_type_DriveBase_reset(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
4441

45-
int32_t distance, drive_speed, angle, turn_rate;
46-
pb_assert(pbio_drivebase_get_state_user(self->db, &distance, &drive_speed, &angle, &turn_rate));
42+
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
43+
pb_type_DriveBase_obj_t, self,
44+
PB_ARG_DEFAULT_INT(distance, 0),
45+
PB_ARG_DEFAULT_INT(angle, 0));
4746

48-
self->initial_distance = distance;
49-
self->initial_heading = angle;
47+
pb_assert(pbio_drivebase_reset(self->db, pb_obj_get_int(distance_in), pb_obj_get_int(angle_in)));
5048

5149
return mp_const_none;
5250
}
53-
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_DriveBase_reset_obj, pb_type_DriveBase_reset);
51+
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_DriveBase_reset_obj, 1, pb_type_DriveBase_reset);
5452

5553
// pybricks.robotics.DriveBase.__init__
5654
static mp_obj_t pb_type_DriveBase_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
@@ -80,9 +78,6 @@ static mp_obj_t pb_type_DriveBase_make_new(const mp_obj_type_t *type, size_t n_a
8078
self->distance_control = pb_type_Control_obj_make_new(&self->db->control_distance);
8179
#endif
8280

83-
// Reset drivebase state
84-
pb_type_DriveBase_reset(MP_OBJ_FROM_PTR(self));
85-
8681
// List of awaitables associated with this drivebase. By keeping track,
8782
// we can cancel them as needed when a new movement is started.
8883
self->awaitables = mp_obj_new_list(0, NULL);
@@ -243,7 +238,7 @@ static mp_obj_t pb_type_DriveBase_distance(mp_obj_t self_in) {
243238
int32_t distance, _;
244239
pb_assert(pbio_drivebase_get_state_user(self->db, &distance, &_, &_, &_));
245240

246-
return mp_obj_new_int(distance - self->initial_distance);
241+
return mp_obj_new_int(distance);
247242
}
248243
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_DriveBase_distance_obj, pb_type_DriveBase_distance);
249244

@@ -254,7 +249,7 @@ static mp_obj_t pb_type_DriveBase_angle(mp_obj_t self_in) {
254249
int32_t heading, _;
255250
pb_assert(pbio_drivebase_get_state_user(self->db, &_, &_, &heading, &_));
256251

257-
return mp_obj_new_int(heading - self->initial_heading);
252+
return mp_obj_new_int(heading);
258253
}
259254
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_DriveBase_angle_obj, pb_type_DriveBase_angle);
260255

@@ -266,9 +261,9 @@ static mp_obj_t pb_type_DriveBase_state(mp_obj_t self_in) {
266261
pb_assert(pbio_drivebase_get_state_user(self->db, &distance, &drive_speed, &heading, &turn_rate));
267262

268263
mp_obj_t ret[4];
269-
ret[0] = mp_obj_new_int(distance - self->initial_distance);
264+
ret[0] = mp_obj_new_int(distance);
270265
ret[1] = mp_obj_new_int(drive_speed);
271-
ret[2] = mp_obj_new_int(heading - self->initial_heading);
266+
ret[2] = mp_obj_new_int(heading);
272267
ret[3] = mp_obj_new_int(turn_rate);
273268

274269
return mp_obj_new_tuple(4, ret);

tests/virtualhub/motor/drivebase.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,67 @@
22
from pybricks.tools import wait
33
from pybricks.parameters import Port, Direction
44
from pybricks.robotics import DriveBase
5+
from umath import pi
56

67
# Initialize default "Driving Base" with medium motors and wheels.
78
left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE)
89
right_motor = Motor(Port.B)
910
drive_base = DriveBase(left_motor, right_motor, wheel_diameter=56, axle_track=112)
1011

11-
drive_base.settings(
12-
straight_speed=500, straight_acceleration=1000, turn_rate=500, turn_acceleration=2000
13-
)
12+
13+
def expect_state(
14+
expected_distance, expected_drive_speed, expected_angle, expected_angular_velocity
15+
):
16+
expected_state = (
17+
expected_distance,
18+
expected_drive_speed,
19+
expected_angle,
20+
expected_angular_velocity,
21+
)
22+
distance, drive_speed, angle, angular_velocity = drive_base.state()
23+
if (
24+
abs(expected_distance - distance) > 10
25+
or abs(expected_drive_speed - drive_speed) > 30
26+
or abs(expected_angle - angle) > 10
27+
or abs(expected_angular_velocity - angular_velocity) > 30
28+
):
29+
raise ValueError("Expected {0} but got {1}".format(expected_state, drive_base.state()))
30+
31+
32+
# Expect zeroed state on startup.
33+
expect_state(0, 0, 0, 0)
1434

1535
# Drive straight forward and back again.
1636
drive_base.straight(500)
37+
expect_state(500, 0, 0, 0)
1738
drive_base.straight(-500)
39+
expect_state(0, 0, 0, 0)
40+
41+
# Curve and back again.
42+
drive_base.curve(100, 90)
43+
expect_state(pi / 2 * 100, 0, 90, 0)
44+
drive_base.curve(-100, 90)
45+
expect_state(0, 0, 0, 0)
1846

19-
wait(100)
20-
drive_base.stop()
47+
# Test setting of distance and angle state.
48+
drive_base.reset(1000, 360)
49+
expect_state(1000, 0, 360, 0)
50+
drive_base.straight(-1000)
51+
drive_base.turn(-360)
52+
expect_state(0, 0, 0, 0)
53+
54+
# Expect to reach target speed.
55+
drive_base.drive(200, 25)
56+
wait(500)
57+
expect_state(drive_base.distance(), 200, drive_base.angle(), 25)
58+
59+
# Expect reset to stop the robot
60+
drive_base.reset()
61+
wait(500)
62+
expect_state(drive_base.distance(), 0, drive_base.angle(), 0)
63+
64+
# Drive fast.
65+
drive_base.settings(
66+
straight_speed=500, straight_acceleration=1000, turn_rate=500, turn_acceleration=2000
67+
)
68+
drive_base.straight(500)

0 commit comments

Comments
 (0)