[C++] proposal needed: ramp up/down for smooth acceleration/deceleration?

for autonomous robots it would be extremely precious to have a way for ramp up/down for smooth acceleration/deceleration, e.g.

  • when starting to drive without spinning and slipping wheels,
  • or to stop smoothly in front of an obstacle without jerking and without locking or blocking wheels,
  • and especially when the ground is slippery
  • or for a robot arm which is transporting brittle or delicate things from 1 point to another (e.g., a glass filled with a liquid to avoid spilling).

So for that purpose one would need a limit to ramp-up = acceleration (increase rate of rotation speed, e.g. maybe 200 degrees/s² max)
and a limit to ramp-down = deceleration (decrease rate of rotation speed, e.g., max. 400 degrees/s²),
even for different motors to be controlled asynchroneously (e.g., for a 5-6 DOF robot arm).

What would be the best way to do this?
Is there a function already implemented on the BrickPi3 HATs locally like for the PID control?

I would be interested in hearing Matt response as this is obviously very important in making high performance robots but I would image set_DPS would have a PID control to smoothly take it to and hold it at the desired DPS. I would be interested to know how smooth and if you set_DPS = 0 would it smoothly slow down and then hold at 0??

That functionality is not integrated into the firmware or drivers, but can be added in user code. User code can set the PWM power to the motor, set the speed target in Degrees Per Second, and set the power and/or speed limit. A loop that slowly increases the value should do what you describe. That increasing (or decreasing) value can be used for any of the above three methods of controlling the power/speed.

If you set the DPS target to 0, the motor will stop abruptly, attempting to reach the new target ASAP.

yes, so far that is clear to me.
But the problem is:

  • I want full power for the medium part ( in case the run is long enough so that will be reached)
  • I want a ramp up/down for start and end.
  • In the moment I start the motor to the new position I do not know by design but just during runtime, how long to run the motor.

imagine I have to run 1000 degrees to the absolute target, then the ramp up/down control has to watch and calculate the ramp up time, the start of the intermediate part, the start of the the ramp down part, and finally the end; in the middle the full power probably will be reached.

but e.g., having to rotate just 10 degrees to the absolue target, the regulation must never reach full power but just start smoothly 5 degrees by max 250°/s² (no jerking) and then for the last 5° smoothly ramp down by 500°/s² (no blocking and slipping).
During ramp up and down the regulation has to face everything: the setpoint (target), the speed, the acceleration, and the max acceleration, not to overshoot during ramp up or during ramp down, and to leave enough room for all 3 parts (actually at least 2, i.e. ramp up+down).

that means:
when setting a speed limit (set_DPS) during ramp-up, that will have to be increased step by step in a loop from 0 perhaps up to max speed, monitoring the acceleration, nevertheless the target must not be overshooted during this part (also monitoring set_motor_position) and still letting enough time and space for ramp-down though, and I can’t see how to control both dps, dps/s, set_motor_position, and calculated TOA for starting then ramp_down in the correct moment,
and finally still reach the target without overshooting and without prematurely die a wretched death due to too low dps settings.

That’s why I actually think by user control it’s almost impossible to achieve that, better to integrate both ramp parts into the PID regulation.

I think your after a function similar to what was supplied in the EV3 basic API https://sites.google.com/site/ev3basic/ev3-basic-programming/ev3-basic-manual

Scroll down and read fuction Motor.SchedulePower (ports, power, degrees1, degrees2, degrees3, brake) and also function Motor.ScheduleSync (ports, speed1, speed2, degrees, brake)

It is a handy function My kids use it on their EV3 robots they build and program in EV3 basic

yes, Shane, indeed this EV3Basic function could be used for my purpose, and it would be extremely useful and helpful to have such a function for BrickPi3 C++ as well:

Motor.SchedulePower (ports, power, degrees1, degrees2, degrees3, brake)
Move one or more motors with the specified power.
The power can be adjusted along the total rotation to get a soft start and a soft stop if needed.
The angle to rotate the motor is degrees1+degrees2+degrees3.
Any negative signs for the angles will be ignored.
At the end of the movement, the motor stops automatically (with or without using the brake).
This function returns immediately, so if you want the movement to complete before the program continues you should follow this function with Motor.Wait(ports).

power Power level from -100 (full reverse) to 100 (full forward)
degrees1 The part of the rotation with acceleration
degrees2 The part of the rotation with uniform motion
degrees3 The part of the rotation with deceleration

IIRC, there also was a similar Extended Rotate Motor function in NXC (perhaps using a motor module structure? Don’t remember quite exactly FTM), and also something similar in leJOS.

(hell, what an editor… :-/ )

You can govern just how soft a start or stop you want by changing over how many degrees it does the ramp up and ramp down over i.e how big you make degree1 and degrees3

I understand how the EV3Basic function works, the main and crucial thing is that all over the duty cycle the targeted point will always be reached and not overshooted unexpectedly because always the SAMD-based PID control takes care of all that.
So now please let us not discuss about EV3Basic and lms2012 but see if such a ramp up/down functionality can be provided by BrickPi3 API as well - as already stated, it’s most probably impossible by a user function in a Pi C++ program, because the PID control is always affected in either runtime stadium, and for this purpose the internal PID constants will have to be changed, adjusted, and applied accordingly for the ramp parts.

Especially if your trying to do it all in parallel with other functions.

1 Like

Especially if your trying to do it all in parallel with other functions.

yes, that would make it even harder,
but as already stated:
not even if it was the only task when having arbitrary angles to rotate, one by one:
even this single task can not be solved with the brickPi3 C API currently, because it is not possible at all to have both a ramp up/down and a PID control simultaneously.

I think any motor control functionality that could be implemented in the firmware can alternatively be implemented in C or Python using the existing drivers. The user program has access to the encoder value and PWM power control, so you can implement into your user program whatever motor control algorithms you need. At some point I might implement speed/position control speed ramping in the firmware, but it’s a low priority, so in the meantime you can implement it in your user code.

In the case where you are using position control, to make the FW still do most of the work, you can still use set_motor_position. The user program can slowly (whatever rate you choose) ramp up the speed limit using set_motor_limits. To ramp down, you can monitor the encoder value using get_motor_encoder, and again apply speed limits using set_motor_limits when the motor is getting close to the target.

but I do not know at what time I have to set dps, when to start set_motor_position, and again have to set dps, because all that depends on how close I am to the target, and I do not know that in my program, that will change on runtime dependencies.

if I start to set dps from 1 to 100 in a loop by 1°/ms, I will have reached an angle of 100° degrees after 100ms. But what if the targeted angle is arbitrarily just 50° or 10° to go, so when start set_motor_position in my C program, and when to start to ramp down - and even when ramping down, not overshooting the target and not dying prematurely before reaching it, but holding close to it (like set_motor_position does) ?
by a generalized function that is expected to work for either angle (1, 10, 50, 100, 500, 1000, 5000 degrees) ?

Use set_motor_position for the FW to do all the targeting (to control the motor). Use set_motor_limits to limit the speed for ramping. Don’t use set_motor_dps to control the speed.

I think you don’t understand what I mean:

how to write a generalized function that is expected to work for either angle (1, 10, 50, 100, 500, 1000, 5000 degrees),
first ramp up by limited accelaration, then approach the target by normal (max) speed (if enough time is left), then ramp down by limited decelaration when coming close enough to the target, then hold close to the target ?

I think what you want is something like this, which can be implemented in C or Python:

get initial encoder position
set speed limit to 1 (just so the motor does not take off immediately)
set motor target position
while target is not reached:
    if encoder is within x degrees of starting position OR encoder is within y degrees of target position:
        set motor speed limit to e.g. 1 + (1 DPS per degree from starting/target position (whichever the motor is closest to))
        set motor speed limit to 0 (no limit).

yes, indeed, sth like this… :thinking:
as the other tasks must not be forced to wait for that motor to do his thing, one probably will need to start a detached pthread thread for each single motor control… :thinking:

Yes, that could work. Alternatively if you have a loop running, you could implement this motor control as a state machine that gets serviced in the loop. Actually, it would hardly need to be a state machine.

but now imagine to have to do this state machine thing for 10 motors simultaneously… :-/

I think I’ll wait until the firmware has been enhanced eventually … :wink:

Based on my rough estimate, the above mentioned algorithm could be implemented to control 10 motors, updating the limit at 50Hz, and worst case, still only be using about a quarter of the available SPI bandwidth (getting encoder values and setting limits). Further, I think 10-25Hz would probably be adequate. If you need the functionality before it’s implemented in FW, I think it seems very feasible to implement in user code.