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

pybricks / pybricks-micropython / 5486450973

pending completion
5486450973

push

github

laurensvalk
pybricks.common.IMU: Relax default stationary thresholds.

This makes sure that the defaults work better in noisier conditions.

Fixes https://github.com/pybricks/support/issues/1105

3416 of 6215 relevant lines covered (54.96%)

31856010.62 hits per line

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

98.71
/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,574✔
31

32
    // Drivebase must have servos.
33
    if (!db->left || !db->right) {
28,574✔
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,704✔
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,683✔
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,715✔
125

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

133
    // Get right servo state
134
    pbio_control_state_t state_right;
2,748✔
135
    err = pbio_servo_get_state_control(db->right, &state_right);
5,715✔
136
    if (err != PBIO_SUCCESS) {
5,715✔
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,715✔
142
    pbio_angle_avg(&state_left.position_estimate, &state_right.position_estimate, &state_distance->position_estimate);
5,715✔
143
    state_distance->speed_estimate = (state_left.speed_estimate + state_right.speed_estimate) / 2;
5,715✔
144
    state_distance->speed = (state_left.speed + state_right.speed) / 2;
5,715✔
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,715✔
149
    pbio_angle_diff(&state_distance->position_estimate, &state_right.position_estimate, &state_heading->position_estimate);
5,715✔
150
    state_heading->speed_estimate = state_distance->speed_estimate - state_right.speed_estimate;
5,715✔
151
    state_heading->speed = state_distance->speed - state_right.speed;
5,715✔
152

153
    // Optionally use gyro to override the heading source for more accuracy.
154
    if (db->use_gyro) {
5,715✔
155
        pbio_imu_get_heading_scaled(&state_heading->position, &state_heading->speed, db->control_heading.settings.ctl_steps_per_app_step);
2,748✔
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) {
8✔
169
    // Stop drivebase control so polling will stop
170
    pbio_control_stop(&db->control_distance);
8✔
171
    pbio_control_stop(&db->control_heading);
8✔
172
    db->control_paused = false;
8✔
173
}
8✔
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,737✔
195
    return pbio_control_is_active(&db->control_distance) && pbio_control_is_active(&db->control_heading);
5,737✔
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) {
16✔
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;
16✔
216

217
    // If drive base control is not active, there is nothing we need to do.
218
    if (!pbio_drivebase_control_is_active(db)) {
16✔
219
        return PBIO_SUCCESS;
5✔
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
    // By default, don't use gyro.
290
    db->use_gyro = false;
3✔
291

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

296
    // Stop any existing drivebase controls
297
    pbio_control_reset(&db->control_distance);
3✔
298
    pbio_control_reset(&db->control_heading);
3✔
299
    db->control_paused = false;
3✔
300

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

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

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

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

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

328

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

335
    return PBIO_SUCCESS;
1✔
336
}
337

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

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

356
    db->use_gyro = use_gyro;
2✔
357
    return PBIO_SUCCESS;
2✔
358
}
359

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

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

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

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

384
    // Stop control.
385
    pbio_drivebase_stop_drivebase_control(db);
7✔
386

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

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

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

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

421
    // Get current time
422
    uint32_t time_now = pbio_control_get_time_ticks();
5,691✔
423

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

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

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

446
    // If either controller is paused, pause both.
447
    db->control_paused = distance_external_pause || heading_external_pause;
5,691✔
448

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

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

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

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

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

493
        pbio_drivebase_t *db = &drivebases[i];
24,606✔
494

495
        // If it's registered for updates, run its update loop
496
        if (pbio_drivebase_update_loop_is_running(db)) {
24,606✔
497
            pbio_drivebase_update(db);
5,719✔
498
        }
499
    }
500
}
8,202✔
501

502
/**
503
 * Starts the drivebase controllers to run by a given distance and angle.
504
 *
505
 * @param [in]  db              The drivebase instance.
506
 * @param [in]  distance        The distance to run by in mm.
507
 * @param [in]  drive_speed     The drive speed in mm/s.
508
 * @param [in]  angle           The angle to turn in deg.
509
 * @param [in]  turn_speed      The turn speed in deg/s.
510
 * @param [in]  on_completion   What to do when reaching the target.
511
 * @return                      Error code.
512
 */
513
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✔
514

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

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

523
    // Get current time
524
    uint32_t time_now = pbio_control_get_time_ticks();
14✔
525

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

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

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

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

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

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

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

568
    return PBIO_SUCCESS;
14✔
569
}
570

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

586
/**
587
 * Starts the drivebase controllers to run by an arc of given radius and angle.
588
 *
589
 * This will use the default speed.
590
 *
591
 * @param [in]  db              The drivebase instance.
592
 * @param [in]  radius          Radius of the arc in mm.
593
 * @param [in]  angle           Angle in degrees.
594
 * @param [in]  on_completion   What to do when reaching the target.
595
 * @return                      Error code.
596
 */
597
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✔
598

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

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

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

609
/**
610
 * Starts the drivebase controllers to run for a given duration.
611
 *
612
 * @param [in]  db              The drivebase instance.
613
 * @param [in]  drive_speed     The drive speed in mm/s.
614
 * @param [in]  turn_speed      The turn speed in deg/s.
615
 * @param [in]  duration        The duration in ms.
616
 * @param [in]  on_completion   What to do when reaching the target.
617
 * @return                      Error code.
618
 */
619
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✔
620

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

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

629
    // Get current time
630
    uint32_t time_now = pbio_control_get_time_ticks();
1✔
631

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

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

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

651
    return PBIO_SUCCESS;
1✔
652
}
653

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

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

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

692

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

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

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

716
    return PBIO_SUCCESS;
2✔
717
}
718

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

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

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

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

767
    return PBIO_SUCCESS;
2✔
768
}
769

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

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

791
    pbio_error_t err;
792

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

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

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

825
#if PBIO_CONFIG_DRIVEBASE_SPIKE
826

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

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

854
/**
855
 * Starts driving for a given duration, at the provided motor speeds.
856
 *
857
 * @param [in]  db              The drivebase instance.
858
 * @param [in]  speed_left      Left motor speed in deg/s.
859
 * @param [in]  speed_right     Right motor speed in deg/s.
860
 * @param [in]  duration        The duration in ms.
861
 * @param [in]  on_completion   What to do when reaching the target.
862
 * @return                      Error code.
863
 */
864
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) {
865
    // Flip left tank motor orientation.
866
    speed_left = -speed_left;
867

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

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

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

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

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

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

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

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

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

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

944
    // Initialize both at the given speed.
945
    *speed_left = speed;
946
    *speed_right = speed;
947

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

953
#endif // PBIO_CONFIG_DRIVEBASE_SPIKE
954

955
#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