• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricks-micropython / 6675885095

28 Oct 2023 08:38AM UTC coverage: 56.053% (+10.0%) from 46.074%
6675885095

push

github

laurensvalk
pybricks.hubs.MoveHub: Use standard hub init.

The Move Hub cannot have true vector axes, but we can still use this API to initialize the hub using numeric indices.

This ensures we can use the custom orientation not just in tilt, but also in acceleration like we do on other hubs.

3616 of 6451 relevant lines covered (56.05%)

20895680.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.23
/lib/pbio/src/drivebase.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2018-2023 The Pybricks Authors
3

4
// SPDX-License-Identifier: BSD-3-Clause
5
// Copyright (c) 2020-2023 LEGO System A/S
6

7
#include <stdlib.h>
8

9
#include <pbdrv/clock.h>
10
#include <pbio/error.h>
11
#include <pbio/drivebase.h>
12
#include <pbio/int_math.h>
13
#include <pbio/imu.h>
14
#include <pbio/servo.h>
15

16
#if PBIO_CONFIG_NUM_DRIVEBASES > 0
17

18
// Drivebase objects
19
static pbio_drivebase_t drivebases[PBIO_CONFIG_NUM_DRIVEBASES];
20

21
/**
22
 * Gets the state of the drivebase update loop.
23
 *
24
 * This becomes true after a successful call to pbio_drivebase_setup and
25
 * becomes false when there is an error. Such as when the cable is unplugged.
26
 *
27
 * @param [in]  db          The drivebase instance
28
 * @return                  True if up and running, false if not.
29
 */
30
bool pbio_drivebase_update_loop_is_running(pbio_drivebase_t *db) {
28,583✔
31

32
    // Drivebase must have servos.
33
    if (!db->left || !db->right) {
28,583✔
34
        return false;
12,058✔
35
    }
36

37
    // Drivebase must be the parent of its two servos.
38
    if (!pbio_parent_equals(&db->left->parent, db) || !pbio_parent_equals(&db->right->parent, db)) {
9,725✔
39
        return false;
21✔
40
    }
41

42
    // Both servo update loops must be running, since we want to read the servo observer state.
43
    return pbio_servo_update_loop_is_running(db->left) && pbio_servo_update_loop_is_running(db->right);
9,704✔
44
}
45

46
/**
47
 * Sets the drivebase settings based on the left and right motor settings.
48
 *
49
 * Sets all settings except ctl_steps_per_app_step. This must be set after
50
 * calling this function.
51
 *
52
 * @param [out] s_distance  Settings of the distance controller.
53
 * @param [out] s_heading   Settings of the heading controller.
54
 * @param [in]  s_left      Settings of the left motor controller.
55
 * @param [in]  s_right     Settings of the right motor controller.
56
 */
57
static void drivebase_adopt_settings(pbio_control_settings_t *s_distance, pbio_control_settings_t *s_heading, const pbio_control_settings_t *s_left, const pbio_control_settings_t *s_right) {
3✔
58

59
    // Use minimum PID of both motors, to avoid overly aggressive control if
60
    // one of the two motors has much higher PID values. Then scale it such
61
    // that we use the reduced kp value not just for low errors, but always.
62
    int32_t pid_kp = pbio_int_math_min(s_left->pid_kp, s_right->pid_kp) *
3✔
63
        pbio_int_math_min(s_left->pid_kp_low_pct, s_right->pid_kp_low_pct) / 100;
3✔
64

65
    // Cap maximum speed at the most constrained of the two motors.
66
    int32_t actuation_max = pbio_int_math_min(s_left->actuation_max, s_right->actuation_max);
3✔
67
    int32_t speed_max = pbio_int_math_min(s_left->speed_max, s_right->speed_max);
3✔
68

69
    // For all settings, take the value of the least powerful motor to ensure
70
    // that the drivebase can meet the given specs.
71
    *s_distance = (pbio_control_settings_t) {
5✔
72
        // Must be set immediately after calling the current function.
73
        .ctl_steps_per_app_step = 0,
74
        .stall_speed_limit = pbio_int_math_min(s_left->stall_speed_limit, s_right->stall_speed_limit),
3✔
75
        .stall_time = pbio_int_math_min(s_left->stall_time, s_right->stall_time),
3✔
76
        .speed_max = speed_max,
77
        // The default speed is 40% of the maximum speed.
78
        .speed_default = speed_max * 10 / 25,
3✔
79
        .speed_tolerance = pbio_int_math_min(s_left->speed_tolerance, s_right->speed_tolerance),
3✔
80
        // To account for reduced kp, adjust the position tolerance so we
81
        // always apply enough proportional torque to keep moving near the end.
82
        .position_tolerance = pbio_control_settings_div_by_gain(actuation_max, pid_kp),
3✔
83
        // Make acceleration, deceleration a bit slower for smoother driving.
84
        .acceleration = pbio_int_math_min(s_left->acceleration, s_right->acceleration) * 3 / 4,
3✔
85
        .deceleration = pbio_int_math_min(s_left->deceleration, s_right->deceleration) * 3 / 4,
3✔
86
        .actuation_max = actuation_max,
87
        .pid_kp = pid_kp,
88
        // Dynamic kp reduction is disabled for drivebases. Instead, it uses
89
        // reduced kp across the board.
90
        .pid_kp_low_pct = 0,
91
        .pid_kp_low_error_threshold = 0,
92
        .pid_kp_low_speed_threshold = 0,
93
        // Integral control is not necessary since there is no constant external
94
        // force to overcome that wouldn't be done by proportional control.
95
        .pid_ki = 0,
96
        .pid_kd = pbio_int_math_min(s_left->pid_kd, s_right->pid_kd),
3✔
97
        .integral_deadzone = pbio_int_math_max(s_left->integral_deadzone, s_right->integral_deadzone),
3✔
98
        .integral_change_max = pbio_int_math_min(s_left->integral_change_max, s_right->integral_change_max),
3✔
99
        .smart_passive_hold_time = pbio_int_math_max(s_left->smart_passive_hold_time, s_right->smart_passive_hold_time),
3✔
100
    };
101

102
    // By default, heading control is the nearly same as distance control.
103
    *s_heading = *s_distance;
3✔
104

105
    // We make the default turn speed a bit slower. Given the typical wheel
106
    // diameter, the wheels are often quite close together, so this
107
    // compensates by setting it at 33% instead of 40%.
108
    s_heading->speed_default = s_heading->speed_max / 3;
3✔
109

110
    // Most users intuitively expect heading control to take priority. When
111
    // heading controller is completely saturated, this ensures that it "wins"
112
    // against the distance controller.
113
    s_heading->actuation_max = s_distance->actuation_max * 2;
3✔
114
}
3✔
115

116
/**
117
 * Get the physical and estimated state of a drivebase in units of control.
118
 *
119
 * @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.
122
 * @return                      Error code.
123
 */
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) {
5,709✔
125

126
    // Get left servo state
127
    pbio_control_state_t state_left;
2,742✔
128
    pbio_error_t err = pbio_servo_get_state_control(db->left, &state_left);
5,709✔
129
    if (err != PBIO_SUCCESS) {
5,709✔
130
        return err;
131
    }
132

133
    // Get right servo state
134
    pbio_control_state_t state_right;
2,742✔
135
    err = pbio_servo_get_state_control(db->right, &state_right);
5,709✔
136
    if (err != PBIO_SUCCESS) {
5,709✔
137
        return err;
138
    }
139

140
    // Take average to get distance state
141
    pbio_angle_avg(&state_left.position, &state_right.position, &state_distance->position);
5,709✔
142
    pbio_angle_avg(&state_left.position_estimate, &state_right.position_estimate, &state_distance->position_estimate);
5,709✔
143
    state_distance->speed_estimate = (state_left.speed_estimate + state_right.speed_estimate) / 2;
5,709✔
144
    state_distance->speed = (state_left.speed + state_right.speed) / 2;
5,709✔
145

146
    // Take average difference to get heading state, which is implemented as:
147
    // (left - right) / 2 = (left + right) / 2 - right = avg - right.
148
    pbio_angle_diff(&state_distance->position, &state_right.position, &state_heading->position);
5,709✔
149
    pbio_angle_diff(&state_distance->position_estimate, &state_right.position_estimate, &state_heading->position_estimate);
5,709✔
150
    state_heading->speed_estimate = state_distance->speed_estimate - state_right.speed_estimate;
5,709✔
151
    state_heading->speed = state_distance->speed - state_right.speed;
5,709✔
152

153
    // Optionally use gyro to override the heading source for more accuracy.
154
    if (db->use_gyro) {
5,709✔
155
        pbio_imu_get_heading_scaled(&state_heading->position, &state_heading->speed, db->control_heading.settings.ctl_steps_per_app_step);
2,742✔
156
    }
157

158
    return PBIO_SUCCESS;
2,967✔
159
}
160

161
/**
162
 * Stop the drivebase from updating its controllers.
163
 *
164
 * This does not physically stop the motors if they are already moving.
165
 *
166
 * @param [in]  db              The drivebase instance
167
 */
168
static void pbio_drivebase_stop_drivebase_control(pbio_drivebase_t *db) {
9✔
169
    // Stop drivebase control so polling will stop
170
    pbio_control_stop(&db->control_distance);
9✔
171
    pbio_control_stop(&db->control_heading);
9✔
172
    db->control_paused = false;
9✔
173
}
9✔
174

175
/**
176
 * Stop the motors used by the drivebase from updating its controllers.
177
 *
178
 * This does not physically stop the motors if they are already moving.
179
 *
180
 * @param [in]  db              The drivebase instance
181
 */
182
static void pbio_drivebase_stop_servo_control(pbio_drivebase_t *db) {
18✔
183
    // Stop servo control so polling will stop
184
    pbio_control_stop(&db->left->control);
18✔
185
    pbio_control_stop(&db->right->control);
18✔
186
}
18✔
187

188
/**
189
 * Checks if both drive base controllers are active.
190
 *
191
 * @param [in]  drivebase       Pointer to this drivebase instance.
192
 * @return                      True if heading and distance control are active, else false.
193
 */
194
static bool pbio_drivebase_control_is_active(const pbio_drivebase_t *db) {
5,733✔
195
    return pbio_control_is_active(&db->control_distance) && pbio_control_is_active(&db->control_heading);
5,733✔
196
}
197

198
/**
199
 * Drivebase stop function that can be called from a servo.
200
 *
201
 * When a new command is issued to a servo, the servo calls this to stop the
202
 * drivebase controller and to stop the other motor physically.
203
 *
204
 * @param [in]  drivebase       Void pointer to this drivebase instance.
205
 * @param [in]  clear_parent    Unused. There is currently no higher
206
 *                              abstraction than a drivebase.
207
 * @return                      Error code.
208
 */
209
static pbio_error_t pbio_drivebase_stop_from_servo(void *drivebase, bool clear_parent) {
18✔
210

211
    // A drivebase has no parent, so clear_parent argument is not applicable.
212
    (void)clear_parent;
10✔
213

214
    // Specify pointer type.
215
    pbio_drivebase_t *db = drivebase;
18✔
216

217
    // If drive base control is not active, there is nothing we need to do.
218
    if (!pbio_drivebase_control_is_active(db)) {
18✔
219
        return PBIO_SUCCESS;
7✔
220
    }
221

222
    // Stop the drive base controller so the motors don't start moving again.
223
    pbio_drivebase_stop_drivebase_control(db);
1✔
224

225
    // Since we don't know which child called the parent to stop, we stop both
226
    // motors. We don't stop their parents to avoid escalating the stop calls
227
    // up the chain (and back here) once again.
228
    pbio_error_t err = pbio_dcmotor_coast(db->left->dcmotor);
1✔
229
    if (err != PBIO_SUCCESS) {
1✔
230
        return err;
231
    }
232
    return pbio_dcmotor_coast(db->right->dcmotor);
1✔
233
}
234

235
#define ROT_MDEG_OVER_PI (114592) // 360 000 / pi
236

237
/**
238
 * Gets drivebase instance from two servo instances.
239
 *
240
 * @param [out] db_address       Drivebase instance if available.
241
 * @param [in]  left             Left servo instance.
242
 * @param [in]  right            Right servo instance.
243
 * @param [in]  wheel_diameter   Wheel diameter in um.
244
 * @param [in]  axle_track       Distance between wheel-ground contact points in um.
245
 * @return                       Error code.
246
 */
247
pbio_error_t pbio_drivebase_get_drivebase(pbio_drivebase_t **db_address, pbio_servo_t *left, pbio_servo_t *right, int32_t wheel_diameter, int32_t axle_track) {
3✔
248

249
    // Can't build a drive base with just one motor.
250
    if (left == right) {
3✔
251
        return PBIO_ERROR_INVALID_ARG;
252
    }
253

254
    // Assert that both motors have the same gearing
255
    if (left->control.settings.ctl_steps_per_app_step != right->control.settings.ctl_steps_per_app_step) {
3✔
256
        return PBIO_ERROR_INVALID_ARG;
257
    }
258

259
    // Check if the servos already have parents.
260
    if (pbio_parent_exists(&left->parent) || pbio_parent_exists(&right->parent)) {
3✔
261
        // If a servo is already in use by a higher level
262
        // abstraction like a drivebase, we can't re-use it.
263
        return PBIO_ERROR_BUSY;
×
264
    }
265

266
    // Now we know that the servos are free, there must be an available
267
    // drivebase. We can just use the first one that isn't running.
268
    uint8_t index;
269
    for (index = 0; index < PBIO_CONFIG_NUM_DRIVEBASES; index++) {
3✔
270
        if (!pbio_drivebase_update_loop_is_running(&drivebases[index])) {
3✔
271
            break;
1✔
272
        }
273
    }
274
    // Verify result is in range.
275
    if (index == PBIO_CONFIG_NUM_DRIVEBASES) {
3✔
276
        return PBIO_ERROR_FAILED;
277
    }
278

279
    // So, this is the drivebase we'll use.
280
    pbio_drivebase_t *db = &drivebases[index];
3✔
281

282
    // Set return value.
283
    *db_address = db;
3✔
284

285
    // Attach servos
286
    db->left = left;
3✔
287
    db->right = right;
3✔
288

289
    // Set parents of both servos, so they can stop this drivebase.
290
    pbio_parent_set(&left->parent, db, pbio_drivebase_stop_from_servo);
3✔
291
    pbio_parent_set(&right->parent, db, pbio_drivebase_stop_from_servo);
3✔
292

293
    // Stop any existing drivebase controls
294
    pbio_control_reset(&db->control_distance);
3✔
295
    pbio_control_reset(&db->control_heading);
3✔
296
    db->control_paused = false;
3✔
297

298
    // Reset both motors to a passive state
299
    pbio_drivebase_stop_servo_control(db);
3✔
300
    pbio_error_t err = pbio_drivebase_stop(db, PBIO_CONTROL_ON_COMPLETION_COAST);
3✔
301
    if (err != PBIO_SUCCESS) {
3✔
302
        return err;
303
    }
304

305
    // Adopt settings as the average or sum of both servos, except scaling
306
    drivebase_adopt_settings(&db->control_distance.settings, &db->control_heading.settings, &left->control.settings, &right->control.settings);
3✔
307

308
    // Verify that the given dimensions are not too small or large to compute
309
    // a correct result for heading and distance control scale below.
310
    if (wheel_diameter < 1000 || axle_track < 1000 ||
3✔
311
        left->control.settings.ctl_steps_per_app_step > INT32_MAX / ROT_MDEG_OVER_PI ||
3✔
312
        left->control.settings.ctl_steps_per_app_step > INT32_MAX / axle_track
3✔
313
        ) {
314
        return PBIO_ERROR_INVALID_ARG;
315
    }
316

317
    // Average rotation of the motors for every 1 degree drivebase rotation.
318
    db->control_heading.settings.ctl_steps_per_app_step =
3✔
319
        left->control.settings.ctl_steps_per_app_step * axle_track / wheel_diameter;
3✔
320

321
    // Average rotation of the motors for every 1 mm forward.
322
    db->control_distance.settings.ctl_steps_per_app_step =
3✔
323
        left->control.settings.ctl_steps_per_app_step * ROT_MDEG_OVER_PI / wheel_diameter;
3✔
324

325

326
    // Verify that wheel diameter was not so large that scale is now zero.
327
    if (db->control_distance.settings.ctl_steps_per_app_step < 1 ||
3✔
328
        db->control_heading.settings.ctl_steps_per_app_step < 1) {
1✔
329
        return PBIO_ERROR_INVALID_ARG;
330
    }
331

332
    // Finish setup. By default, don't use gyro.
333
    return pbio_drivebase_set_use_gyro(db, false);
3✔
334
}
335

336
/**
337
 * Makes the drivebase use gyro or motor rotation sensors for heading control.
338
 *
339
 * This function will stop the drivebase if it is running.
340
 *
341
 * @param [in]  db               Drivebase instance.
342
 * @param [in]  use_gyro         Whether to use the gyro for heading control.
343
 * @return                       Error code.
344
 */
345
pbio_error_t pbio_drivebase_set_use_gyro(pbio_drivebase_t *db, bool use_gyro) {
3✔
346

347
    // We stop so that new commands will reinitialize the state using the
348
    // newly selected input for heading control.
349
    pbio_error_t err = pbio_drivebase_stop(db, PBIO_CONTROL_ON_COMPLETION_COAST);
3✔
350
    if (err != PBIO_SUCCESS) {
3✔
351
        return err;
352
    }
353

354
    db->use_gyro = use_gyro;
3✔
355
    return PBIO_SUCCESS;
3✔
356
}
357

358
/**
359
 * Stops a drivebase.
360
 *
361
 * @param [in]  db               Drivebase instance.
362
 * @param [in]  on_completion    Which stop type to use.
363
 * @return                       Error code.
364
 */
365
pbio_error_t pbio_drivebase_stop(pbio_drivebase_t *db, pbio_control_on_completion_t on_completion) {
9✔
366

367
    // Don't allow new user command if update loop not registered.
368
    if (!pbio_drivebase_update_loop_is_running(db)) {
9✔
369
        return PBIO_ERROR_INVALID_OP;
370
    }
371

372
    // We're asked to stop, so continuing makes no sense.
373
    if (on_completion == PBIO_CONTROL_ON_COMPLETION_CONTINUE) {
9✔
374
        return PBIO_ERROR_INVALID_ARG;
375
    }
376

377
    // Holding is the same as traveling by 0 degrees.
378
    if (on_completion == PBIO_CONTROL_ON_COMPLETION_HOLD) {
9✔
379
        return pbio_drivebase_drive_straight(db, 0, on_completion);
1✔
380
    }
381

382
    // Stop control.
383
    pbio_drivebase_stop_drivebase_control(db);
8✔
384

385
    // Stop the servos and pass on requested stop type.
386
    pbio_error_t err = pbio_servo_stop(db->left, on_completion);
8✔
387
    if (err != PBIO_SUCCESS) {
8✔
388
        return err;
389
    }
390
    return pbio_servo_stop(db->right, on_completion);
8✔
391
}
392

393
/**
394
 * Checks if a drivebase has completed its maneuver.
395
 *
396
 * @param [in]  db          The drivebase instance
397
 * @return                  True if still moving to target, false if not.
398
 */
399
bool pbio_drivebase_is_done(const pbio_drivebase_t *db) {
16,685✔
400
    return pbio_control_is_done(&db->control_distance) && pbio_control_is_done(&db->control_heading);
16,685✔
401
}
402

403
/**
404
 * Updates one drivebase in the control loop.
405
 *
406
 * This reads the physical and estimated state, and updates the controller if
407
 * it is active.
408
 *
409
 * @param [in]  db          The drivebase instance
410
 * @return                  Error code.
411
 */
412
static pbio_error_t pbio_drivebase_update(pbio_drivebase_t *db) {
5,713✔
413

414
    // If passive, no need to update.
415
    if (!pbio_drivebase_control_is_active(db)) {
5,713✔
416
        return PBIO_SUCCESS;
28✔
417
    }
418

419
    // Get current time
420
    uint32_t time_now = pbio_control_get_time_ticks();
5,685✔
421

422
    // Get drive base state
423
    pbio_control_state_t state_distance;
2,730✔
424
    pbio_control_state_t state_heading;
2,730✔
425
    pbio_error_t err = pbio_drivebase_get_state_control(db, &state_distance, &state_heading);
5,685✔
426
    if (err != PBIO_SUCCESS) {
5,685✔
427
        return err;
428
    }
429

430
    // Get reference and torque signals for distance control.
431
    pbio_trajectory_reference_t ref_distance;
2,730✔
432
    int32_t distance_torque;
2,730✔
433
    pbio_dcmotor_actuation_t distance_actuation;
2,730✔
434
    bool distance_external_pause = db->control_paused;
5,685✔
435
    pbio_control_update(&db->control_distance, time_now, &state_distance, &ref_distance, &distance_actuation, &distance_torque, &distance_external_pause);
5,685✔
436

437
    // Get reference and torque signals for heading control.
438
    pbio_trajectory_reference_t ref_heading;
2,730✔
439
    int32_t heading_torque;
2,730✔
440
    pbio_dcmotor_actuation_t heading_actuation;
2,730✔
441
    bool heading_external_pause = db->control_paused;
5,685✔
442
    pbio_control_update(&db->control_heading, time_now, &state_heading, &ref_heading, &heading_actuation, &heading_torque, &heading_external_pause);
5,685✔
443

444
    // If either controller is paused, pause both.
445
    db->control_paused = distance_external_pause || heading_external_pause;
5,685✔
446

447
    // If either controller coasts, coast both, thereby also stopping control.
448
    if (distance_actuation == PBIO_DCMOTOR_ACTUATION_COAST ||
5,685✔
449
        heading_actuation == PBIO_DCMOTOR_ACTUATION_COAST) {
5,684✔
450
        return pbio_drivebase_stop(db, PBIO_CONTROL_ON_COMPLETION_COAST);
1✔
451
    }
452
    // If either controller brakes, brake both, thereby also stopping control.
453
    if (distance_actuation == PBIO_DCMOTOR_ACTUATION_BRAKE ||
5,684✔
454
        heading_actuation == PBIO_DCMOTOR_ACTUATION_BRAKE) {
2,954✔
455
        return pbio_drivebase_stop(db, PBIO_CONTROL_ON_COMPLETION_BRAKE);
×
456
    }
457

458
    // The only other expected actuation type is torque, so make sure it is.
459
    if (distance_actuation != PBIO_DCMOTOR_ACTUATION_TORQUE ||
5,684✔
460
        heading_actuation != PBIO_DCMOTOR_ACTUATION_TORQUE) {
2,954✔
461
        return PBIO_ERROR_FAILED;
462
    }
463

464
    // The left servo drives at a torque and speed of (average) + (difference).
465
    int32_t feed_forward_left = pbio_observer_get_feedforward_torque(
8,414✔
466
        db->left->observer.model,
5,684✔
467
        ref_distance.speed + ref_heading.speed, // left speed
5,684✔
468
        ref_distance.acceleration + ref_heading.acceleration); // left acceleration
5,684✔
469
    err = pbio_servo_actuate(db->left, PBIO_DCMOTOR_ACTUATION_TORQUE,
8,414✔
470
        distance_torque + heading_torque + feed_forward_left);
5,684✔
471
    if (err != PBIO_SUCCESS) {
5,684✔
472
        return err;
473
    }
474

475
    // The right servo drives at a torque and speed of (average) - (difference).
476
    int32_t feed_forward_right = pbio_observer_get_feedforward_torque(
8,414✔
477
        db->right->observer.model,
5,684✔
478
        ref_distance.speed - ref_heading.speed, // right speed
5,684✔
479
        ref_distance.acceleration - ref_heading.acceleration); // right acceleration
5,684✔
480
    return pbio_servo_actuate(db->right, PBIO_DCMOTOR_ACTUATION_TORQUE,
5,684✔
481
        distance_torque - heading_torque + feed_forward_right);
5,684✔
482
}
483

484
/**
485
 * Updates all currently active (previously set up) drivebases.
486
 */
487
void pbio_drivebase_update_all(void) {
8,196✔
488
    // Go through all drive base candidates
489
    for (uint8_t i = 0; i < PBIO_CONFIG_NUM_DRIVEBASES; i++) {
32,784✔
490

491
        pbio_drivebase_t *db = &drivebases[i];
24,588✔
492

493
        // If it's registered for updates, run its update loop
494
        if (pbio_drivebase_update_loop_is_running(db)) {
24,588✔
495
            pbio_drivebase_update(db);
5,713✔
496
        }
497
    }
498
}
8,196✔
499

500
/**
501
 * Starts the drivebase controllers to run by a given distance and angle.
502
 *
503
 * @param [in]  db              The drivebase instance.
504
 * @param [in]  distance        The distance to run by in mm.
505
 * @param [in]  drive_speed     The drive speed in mm/s.
506
 * @param [in]  angle           The angle to turn in deg.
507
 * @param [in]  turn_speed      The turn speed in deg/s.
508
 * @param [in]  on_completion   What to do when reaching the target.
509
 * @return                      Error code.
510
 */
511
static pbio_error_t pbio_drivebase_drive_relative(pbio_drivebase_t *db, int32_t distance, int32_t drive_speed, int32_t angle, int32_t turn_speed, pbio_control_on_completion_t on_completion) {
14✔
512

513
    // Don't allow new user command if update loop not registered.
514
    if (!pbio_drivebase_update_loop_is_running(db)) {
14✔
515
        return PBIO_ERROR_INVALID_OP;
516
    }
517

518
    // Stop servo control in case it was running.
519
    pbio_drivebase_stop_servo_control(db);
14✔
520

521
    // Get current time
522
    uint32_t time_now = pbio_control_get_time_ticks();
14✔
523

524
    // Get drive base state
525
    pbio_control_state_t state_distance;
10✔
526
    pbio_control_state_t state_heading;
10✔
527
    pbio_error_t err = pbio_drivebase_get_state_control(db, &state_distance, &state_heading);
14✔
528
    if (err != PBIO_SUCCESS) {
14✔
529
        return err;
530
    }
531

532
    // Start controller that controls the average angle of both motors.
533
    err = pbio_control_start_position_control_relative(&db->control_distance, time_now, &state_distance, distance, drive_speed, on_completion, false);
14✔
534
    if (err != PBIO_SUCCESS) {
14✔
535
        return err;
536
    }
537

538
    // Start controller that controls half the difference between both angles.
539
    err = pbio_control_start_position_control_relative(&db->control_heading, time_now, &state_heading, angle, turn_speed, on_completion, false);
14✔
540
    if (err != PBIO_SUCCESS) {
14✔
541
        return err;
542
    }
543

544
    // At this point, the two trajectories may have different durations, so they won't complete at the same time
545
    // To account for this, we re-compute the shortest trajectory to have the same duration as the longest.
546

547
    // First, find out which controller takes the lead
548
    const pbio_control_t *control_leader;
10✔
549
    pbio_control_t *control_follower;
10✔
550

551
    if (pbio_trajectory_get_duration(&db->control_distance.trajectory) >
28✔
552
        pbio_trajectory_get_duration(&db->control_heading.trajectory)) {
14✔
553
        // Distance control takes the longest, so it will take the lead
554
        control_leader = &db->control_distance;
2✔
555
        control_follower = &db->control_heading;
2✔
556
    } else {
557
        // Heading control takes the longest, so it will take the lead
558
        control_leader = &db->control_heading;
6✔
559
        control_follower = &db->control_distance;
6✔
560
    }
561

562
    // Revise follower trajectory so it takes as long as the leader, achieved
563
    // by picking a lower speed and accelerations that makes the times match.
564
    pbio_trajectory_stretch(&control_follower->trajectory, &control_leader->trajectory);
14✔
565

566
    return PBIO_SUCCESS;
14✔
567
}
568

569
/**
570
 * Starts the drivebase controllers to run by a given distance.
571
 *
572
 * This will use the default speed.
573
 *
574
 * @param [in]  db              The drivebase instance.
575
 * @param [in]  distance        The distance to run by in mm.
576
 * @param [in]  on_completion   What to do when reaching the target.
577
 * @return                      Error code.
578
 */
579
pbio_error_t pbio_drivebase_drive_straight(pbio_drivebase_t *db, int32_t distance, pbio_control_on_completion_t on_completion) {
9✔
580
    // Execute the common drive command at default speed (by passing 0 speed).
581
    return pbio_drivebase_drive_relative(db, distance, 0, 0, 0, on_completion);
9✔
582
}
583

584
/**
585
 * Starts the drivebase controllers to run by an arc of given radius and angle.
586
 *
587
 * This will use the default speed.
588
 *
589
 * @param [in]  db              The drivebase instance.
590
 * @param [in]  radius          Radius of the arc in mm.
591
 * @param [in]  angle           Angle in degrees.
592
 * @param [in]  on_completion   What to do when reaching the target.
593
 * @return                      Error code.
594
 */
595
pbio_error_t pbio_drivebase_drive_curve(pbio_drivebase_t *db, int32_t radius, int32_t angle, pbio_control_on_completion_t on_completion) {
5✔
596

597
    // The angle is signed by the radius so we can go both ways.
598
    int32_t arc_angle = radius < 0 ? -angle : angle;
5✔
599

600
    // Arc length is computed accordingly.
601
    int32_t arc_length = (10 * pbio_int_math_abs(angle) * radius) / 573;
5✔
602

603
    // Execute the common drive command at default speed (by passing 0 speed).
604
    return pbio_drivebase_drive_relative(db, arc_length, 0, arc_angle, 0, on_completion);
5✔
605
}
606

607
/**
608
 * Starts the drivebase controllers to run for a given duration.
609
 *
610
 * @param [in]  db              The drivebase instance.
611
 * @param [in]  drive_speed     The drive speed in mm/s.
612
 * @param [in]  turn_speed      The turn speed in deg/s.
613
 * @param [in]  duration        The duration in ms.
614
 * @param [in]  on_completion   What to do when reaching the target.
615
 * @return                      Error code.
616
 */
617
static pbio_error_t pbio_drivebase_drive_time_common(pbio_drivebase_t *db, int32_t drive_speed, int32_t turn_speed, uint32_t duration, pbio_control_on_completion_t on_completion) {
1✔
618

619
    // Don't allow new user command if update loop not registered.
620
    if (!pbio_drivebase_update_loop_is_running(db)) {
1✔
621
        return PBIO_ERROR_INVALID_OP;
622
    }
623

624
    // Stop servo control in case it was running.
625
    pbio_drivebase_stop_servo_control(db);
1✔
626

627
    // Get current time
628
    uint32_t time_now = pbio_control_get_time_ticks();
1✔
629

630
    // Get drive base state
631
    pbio_control_state_t state_distance;
632
    pbio_control_state_t state_heading;
633
    pbio_error_t err = pbio_drivebase_get_state_control(db, &state_distance, &state_heading);
1✔
634
    if (err != PBIO_SUCCESS) {
1✔
635
        return err;
636
    }
637

638
    // Initialize both controllers
639
    err = pbio_control_start_timed_control(&db->control_distance, time_now, &state_distance, duration, drive_speed, on_completion);
1✔
640
    if (err != PBIO_SUCCESS) {
1✔
641
        return err;
642
    }
643

644
    err = pbio_control_start_timed_control(&db->control_heading, time_now, &state_heading, duration, turn_speed, on_completion);
1✔
645
    if (err != PBIO_SUCCESS) {
1✔
646
        return err;
647
    }
648

649
    return PBIO_SUCCESS;
1✔
650
}
651

652
/**
653
 * Starts the drivebase controllers to run forever.
654
 *
655
 * @param [in]  db              The drivebase instance.
656
 * @param [in]  speed           The drive speed in mm/s.
657
 * @param [in]  turn_rate       The turn rate in deg/s.
658
 * @return                      Error code.
659
 */
660
pbio_error_t pbio_drivebase_drive_forever(pbio_drivebase_t *db, int32_t speed, int32_t turn_rate) {
1✔
661
    return pbio_drivebase_drive_time_common(db, speed, turn_rate, PBIO_TRAJECTORY_DURATION_FOREVER_MS, PBIO_CONTROL_ON_COMPLETION_CONTINUE);
1✔
662
}
663

664
/**
665
 * Gets the drivebase state in user units.
666
 *
667
 * @param [in]  db          The drivebase instance.
668
 * @param [out] distance    Distance traveled in mm.
669
 * @param [out] drive_speed Current speed in mm/s.
670
 * @param [out] angle       Angle turned in degrees.
671
 * @param [out] turn_rate   Current turn rate in deg/s.
672
 * @return                  Error code.
673
 */
674
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) {
9✔
675

676
    // Get drive base state
677
    pbio_control_state_t state_distance;
2✔
678
    pbio_control_state_t state_heading;
2✔
679
    pbio_error_t err = pbio_drivebase_get_state_control(db, &state_distance, &state_heading);
9✔
680
    if (err != PBIO_SUCCESS) {
9✔
681
        return err;
682
    }
683
    *distance = pbio_control_settings_ctl_to_app_long(&db->control_distance.settings, &state_distance.position);
9✔
684
    *drive_speed = pbio_control_settings_ctl_to_app(&db->control_distance.settings, state_distance.speed);
9✔
685
    *angle = pbio_control_settings_ctl_to_app_long(&db->control_heading.settings, &state_heading.position);
9✔
686
    *turn_rate = pbio_control_settings_ctl_to_app(&db->control_heading.settings, state_heading.speed);
9✔
687
    return PBIO_SUCCESS;
9✔
688
}
689

690

691
/**
692
 * Gets the drivebase settings in user units.
693
 *
694
 * @param [in]  db                  Drivebase instance.
695
 * @param [out] drive_speed         Default linear speed in mm/s.
696
 * @param [out] drive_acceleration  Linear acceleration in mm/s^2.
697
 * @param [out] drive_deceleration  Linear deceleration in mm/s^2.
698
 * @param [out] turn_rate           Default turn rate in deg/s.
699
 * @param [out] turn_acceleration   Angular acceleration in deg/s^2.
700
 * @param [out] turn_deceleration   Angular deceleration in deg/s^2.
701
 */
702
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) {
2✔
703

704
    const pbio_control_settings_t *sd = &db->control_distance.settings;
2✔
705
    const pbio_control_settings_t *sh = &db->control_heading.settings;
2✔
706

707
    *drive_speed = pbio_control_settings_ctl_to_app(sd, sd->speed_default);
2✔
708
    *drive_acceleration = pbio_control_settings_ctl_to_app(sd, sd->acceleration);
2✔
709
    *drive_deceleration = pbio_control_settings_ctl_to_app(sd, sd->deceleration);
2✔
710
    *turn_rate = pbio_control_settings_ctl_to_app(sh, sh->speed_default);
2✔
711
    *turn_acceleration = pbio_control_settings_ctl_to_app(sh, sh->acceleration);
2✔
712
    *turn_deceleration = pbio_control_settings_ctl_to_app(sh, sh->deceleration);
2✔
713

714
    return PBIO_SUCCESS;
2✔
715
}
716

717
/**
718
 * Sets the drivebase settings in user units.
719
 *
720
 * @param [in]  db                  Drivebase instance.
721
 * @param [in] drive_speed          Default linear speed in mm/s.
722
 * @param [in] drive_acceleration   Linear acceleration in mm/s^2.
723
 * @param [in] drive_deceleration   Linear deceleration in mm/s^2.
724
 * @param [in] turn_rate            Default turn rate in deg/s.
725
 * @param [in] turn_acceleration    Angular acceleration in deg/s^2.
726
 * @param [in] turn_deceleration    Angular deceleration in deg/s^2.
727
 */
728
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) {
3✔
729

730
    pbio_control_settings_t *sd = &db->control_distance.settings;
3✔
731
    pbio_control_settings_t *sh = &db->control_heading.settings;
3✔
732

733
    pbio_error_t err = pbio_trajectory_validate_speed_limit(sd->ctl_steps_per_app_step, drive_speed);
3✔
734
    if (err != PBIO_SUCCESS) {
3✔
735
        return err;
736
    }
737
    err = pbio_trajectory_validate_acceleration_limit(sd->ctl_steps_per_app_step, drive_acceleration);
3✔
738
    if (err != PBIO_SUCCESS) {
3✔
739
        return err;
1✔
740
    }
741
    err = pbio_trajectory_validate_acceleration_limit(sd->ctl_steps_per_app_step, drive_deceleration);
2✔
742
    if (err != PBIO_SUCCESS) {
2✔
743
        return err;
744
    }
745
    err = pbio_trajectory_validate_speed_limit(sh->ctl_steps_per_app_step, turn_rate);
2✔
746
    if (err != PBIO_SUCCESS) {
2✔
747
        return err;
748
    }
749
    err = pbio_trajectory_validate_acceleration_limit(sh->ctl_steps_per_app_step, turn_acceleration);
2✔
750
    if (err != PBIO_SUCCESS) {
2✔
751
        return err;
752
    }
753
    err = pbio_trajectory_validate_acceleration_limit(sh->ctl_steps_per_app_step, turn_deceleration);
2✔
754
    if (err != PBIO_SUCCESS) {
2✔
755
        return err;
756
    }
757

758
    sd->speed_default = pbio_int_math_clamp(pbio_control_settings_app_to_ctl(sd, drive_speed), sd->speed_max);
2✔
759
    sd->acceleration = pbio_control_settings_app_to_ctl(sd, drive_acceleration);
2✔
760
    sd->deceleration = pbio_control_settings_app_to_ctl(sd, drive_deceleration);
2✔
761
    sh->speed_default = pbio_int_math_clamp(pbio_control_settings_app_to_ctl(sh, turn_rate), sh->speed_max);
2✔
762
    sh->acceleration = pbio_control_settings_app_to_ctl(sh, turn_acceleration);
2✔
763
    sh->deceleration = pbio_control_settings_app_to_ctl(sh, turn_deceleration);
2✔
764

765
    return PBIO_SUCCESS;
2✔
766
}
767

768
/**
769
 * Checks whether drivebase is stalled. If the drivebase is actively
770
 * controlled, it is stalled when the controller(s) cannot maintain the
771
 * target speed or position while using maximum allowed torque. If control
772
 * is not active, it uses the individual servos to check for stall.
773
 *
774
 * @param [in]  db              The servo instance.
775
 * @param [out] stalled         True if stalled, false if not.
776
 * @param [out] stall_duration  For how long it has been stalled (ms).
777
 * @return                      Error code. ::PBIO_ERROR_INVALID_OP if update
778
 *                              loop not running, else ::PBIO_SUCCESS
779
 */
780
pbio_error_t pbio_drivebase_is_stalled(pbio_drivebase_t *db, bool *stalled, uint32_t *stall_duration) {
3✔
781

782
    // Don't allow access if update loop not registered.
783
    if (!pbio_drivebase_update_loop_is_running(db)) {
3✔
784
        *stalled = false;
1✔
785
        *stall_duration = 0;
1✔
786
        return PBIO_ERROR_INVALID_OP;
1✔
787
    }
788

789
    pbio_error_t err;
790

791
    // If drive base control is active, look at controller state.
792
    if (pbio_drivebase_control_is_active(db)) {
2✔
793
        uint32_t stall_duration_distance; // ticks, 0 on false
794
        uint32_t stall_duration_heading; // ticks, 0 on false
795
        bool stalled_heading = pbio_control_is_stalled(&db->control_heading, &stall_duration_heading);
1✔
796
        bool stalled_distance = pbio_control_is_stalled(&db->control_distance, &stall_duration_distance);
1✔
797

798
        // We are stalled if any controller is stalled.
799
        *stalled = stalled_heading || stalled_distance;
1✔
800
        *stall_duration = pbio_control_time_ticks_to_ms(pbio_int_math_max(stall_duration_distance, stall_duration_heading));
1✔
801
        return PBIO_SUCCESS;
1✔
802
    }
803

804
    // Otherwise look at individual servos.
805
    bool stalled_left;
806
    bool stalled_right;
807
    uint32_t stall_duration_left; // ms, 0 on false.
808
    uint32_t stall_duration_right; // ms, 0 on false.
809
    err = pbio_servo_is_stalled(db->left, &stalled_left, &stall_duration_left);
1✔
810
    if (err != PBIO_SUCCESS) {
1✔
811
        return err;
812
    }
813
    err = pbio_servo_is_stalled(db->left, &stalled_right, &stall_duration_right);
1✔
814
    if (err != PBIO_SUCCESS) {
1✔
815
        return err;
816
    }
817
    // We are stalled if at least one motor is stalled.
818
    *stalled = stalled_left || stalled_right;
1✔
819
    *stall_duration = pbio_int_math_max(stall_duration_left, stall_duration_right);
1✔
820
    return PBIO_SUCCESS;
1✔
821
}
822

823
#if PBIO_CONFIG_DRIVEBASE_SPIKE
824

825
/**
826
 * Gets spike drivebase instance from two servo instances.
827
 *
828
 * This and the following functions provide spike-like "tank-drive" controls.
829
 * These will not use the pbio functionality for gearing or reversed
830
 * orientation. Any scaling and flipping happens within the functions below.
831
 *
832
 * It is a drivebase, but without wheel size and axle track parameters.
833
 * Instead, the internal conversions are such that the "distance" is expressed
834
 * in motor degrees. Likewise, turning in place by N degrees means that both
835
 * motors turn by that amount.
836
 *
837
 * @param [out] db_address       Drivebase instance if available.
838
 * @param [in]  left             Left servo instance.
839
 * @param [in]  right            Right servo instance.
840
 * @return                       Error code.
841
 */
842
pbio_error_t pbio_drivebase_get_drivebase_spike(pbio_drivebase_t **db_address, pbio_servo_t *left, pbio_servo_t *right) {
843
    pbio_error_t err = pbio_drivebase_get_drivebase(db_address, left, right, 1000, 1000);
844

845
    // The application input for spike bases is degrees per second average
846
    // between both wheels, so in millidegrees this is x1000.
847
    (*db_address)->control_heading.settings.ctl_steps_per_app_step = 1000;
848
    (*db_address)->control_distance.settings.ctl_steps_per_app_step = 1000;
849
    return err;
850
}
851

852
/**
853
 * Starts driving for a given duration, at the provided motor speeds.
854
 *
855
 * @param [in]  db              The drivebase instance.
856
 * @param [in]  speed_left      Left motor speed in deg/s.
857
 * @param [in]  speed_right     Right motor speed in deg/s.
858
 * @param [in]  duration        The duration in ms.
859
 * @param [in]  on_completion   What to do when reaching the target.
860
 * @return                      Error code.
861
 */
862
pbio_error_t pbio_drivebase_spike_drive_time(pbio_drivebase_t *db, int32_t speed_left, int32_t speed_right, uint32_t duration, pbio_control_on_completion_t on_completion) {
863
    // Flip left tank motor orientation.
864
    speed_left = -speed_left;
865

866
    // Start driving forever with the given sum and dif rates.
867
    int32_t drive_speed = (speed_left + speed_right) / 2;
868
    int32_t turn_speed = (speed_left - speed_right) / 2;
869
    return pbio_drivebase_drive_time_common(db, drive_speed, turn_speed, duration, on_completion);
870
}
871

872
/**
873
 * Starts driving indefinitely, at the provided motor speeds.
874
 *
875
 * @param [in]  db              The drivebase instance.
876
 * @param [in]  speed_left      Left motor speed in deg/s.
877
 * @param [in]  speed_right     Right motor speed in deg/s.
878
 * @return                      Error code.
879
 */
880
pbio_error_t pbio_drivebase_spike_drive_forever(pbio_drivebase_t *db, int32_t speed_left, int32_t speed_right) {
881
    // Same as driving for time, just without an endpoint.
882
    return pbio_drivebase_spike_drive_time(db, speed_left, speed_right, PBIO_TRAJECTORY_DURATION_FOREVER_MS, PBIO_CONTROL_ON_COMPLETION_CONTINUE);
883
}
884

885
/**
886
 * Drive the motors by a given angle, at the provided motor speeds.
887
 *
888
 * Only the faster motor will travel by the given angle. The slower motor
889
 * travels less, such that they still stop at the same time.
890
 *
891
 * @param [in]  db              The drivebase instance.
892
 * @param [in]  speed_left      Left motor speed in deg/s.
893
 * @param [in]  speed_right     Right motor speed in deg/s.
894
 * @param [in]  angle           Angle (deg) that the fast motor should travel.
895
 * @param [in]  on_completion   What to do when reaching the target.
896
 * @return                      Error code.
897
 */
898
pbio_error_t pbio_drivebase_spike_drive_angle(pbio_drivebase_t *db, int32_t speed_left, int32_t speed_right, int32_t angle, pbio_control_on_completion_t on_completion) {
899

900
    // In the classic tank drive, we flip the left motor here instead of at the low level.
901
    speed_left *= -1;
902

903
    // If both speeds are zero, we can't do anything meaningful, but most users
904
    // find it confusing if we return an error. To make sure it won't block
905
    // forever, we set the angle to zero instead, so we're "done" right away.
906
    if (speed_left == 0 && speed_right == 0) {
907
        return pbio_drivebase_drive_relative(db, 0, 0, 0, 0, on_completion);
908
    }
909

910
    // Work out angles for each motor.
911
    int32_t max_speed = pbio_int_math_max(pbio_int_math_abs(speed_left), pbio_int_math_abs(speed_right));
912
    int32_t angle_left = max_speed == 0 ? 0 : angle * speed_left / max_speed;
913
    int32_t angle_right = max_speed == 0 ? 0 : angle * speed_right / max_speed;
914

915
    // Work out the required total and difference angles to achieve this.
916
    int32_t distance = (angle_left + angle_right) / 2;
917
    int32_t turn_angle = (angle_left - angle_right) / 2;
918
    int32_t speed = (pbio_int_math_abs(speed_left) + pbio_int_math_abs(speed_right)) / 2;
919

920
    // Execute the maneuver.
921
    return pbio_drivebase_drive_relative(db, distance, speed, turn_angle, speed, on_completion);
922
}
923

924
/**
925
 * Converts a speed and a steering ratio into a separate left and right speed.
926
 *
927
 * The steering value must be in the range [-100, 100].
928
 *
929
 * @param [in]  speed         Overall speed (deg/s).
930
 * @param [in]  steering      Steering ratio.
931
 * @param [out] speed_left    Speed of the left motor (deg/s).
932
 * @param [out] speed_right   Speed of the right motor (deg/s).
933
 * @return                    Error code.
934
 */
935
pbio_error_t pbio_drivebase_spike_steering_to_tank(int32_t speed, int32_t steering, int32_t *speed_left, int32_t *speed_right) {
936

937
    // Steering must be bounded.
938
    if (steering < -100 || steering > 100) {
939
        return PBIO_ERROR_INVALID_ARG;
940
    }
941

942
    // Initialize both at the given speed.
943
    *speed_left = speed;
944
    *speed_right = speed;
945

946
    // Depending on steering direction, one wheel moves slower.
947
    *(steering > 0 ? speed_right : speed_left) = speed * (100 - 2 * pbio_int_math_abs(steering)) / 100;
948
    return PBIO_SUCCESS;
949
}
950

951
#endif // PBIO_CONFIG_DRIVEBASE_SPIKE
952

953
#endif // PBIO_CONFIG_NUM_DRIVEBASES > 0
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc