first
This commit is contained in:
722
extras/doc/FastAccelStepper_API.md
Normal file
722
extras/doc/FastAccelStepper_API.md
Normal file
@@ -0,0 +1,722 @@
|
||||
# FastAccelStepper
|
||||
|
||||
FastAccelStepper is a high speed alternative for the
|
||||
[AccelStepper library](http:www.airspayce.com/mikem/arduino/AccelStepper/).
|
||||
Supported are avr (ATmega 168/328/P, ATmega2560), esp32 and atmelsam due.
|
||||
|
||||
Here is a basic example to run a stepper from position 0 to 1000 and back
|
||||
again to 0.
|
||||
```
|
||||
|
||||
FastAccelStepperEngine engine = FastAccelStepperEngine();
|
||||
FastAccelStepper *stepper = NULL;
|
||||
|
||||
#define dirPinStepper 5
|
||||
#define enablePinStepper 6
|
||||
#define stepPinStepper 9
|
||||
void setup() {
|
||||
engine.init();
|
||||
stepper = engine.stepperConnectToPin(stepPinStepper);
|
||||
if (stepper) {
|
||||
stepper->setDirectionPin(dirPinStepper);
|
||||
stepper->setEnablePin(enablePinStepper);
|
||||
stepper->setAutoEnable(true);
|
||||
|
||||
stepper->setSpeedInHz(500);
|
||||
stepper->setAcceleration(100);
|
||||
stepper->moveTo(1000, true);
|
||||
stepper->moveTo(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
```
|
||||
|
||||
## FastAccelStepperEngine
|
||||
|
||||
This engine - actually a factory - provides you with instances of steppers.
|
||||
### Initialization
|
||||
|
||||
The FastAccelStepperEngine is declared with FastAccelStepperEngine().
|
||||
This is to occupy the needed memory.
|
||||
```cpp
|
||||
FastAccelStepperEngine engine = FastAccelStepperEngine();
|
||||
```
|
||||
But it still needs to be initialized.
|
||||
For this init shall be used:
|
||||
```cpp
|
||||
void setup() {
|
||||
engine.init();
|
||||
}
|
||||
```
|
||||
In a multitasking and multicore system like ESP32, the steppers are
|
||||
controlled by a continuously running task. This task can be fixed to one
|
||||
CPU core with this modified init()-call. ESP32 implementation detail: For
|
||||
values 0 and 1, xTaskCreatePinnedToCore() is used, or else xTaskCreate()
|
||||
```cpp
|
||||
void init(uint8_t cpu_core);
|
||||
#endif
|
||||
```
|
||||
### Creation of FastAccelStepper
|
||||
|
||||
Using a call to `stepperConnectToPin()` a FastAccelStepper instance is
|
||||
created. This call tells the stepper, which step pin to use. As the
|
||||
hardware may have limitations - e.g. no stepper resources anymore, or the
|
||||
step pin cannot be used, then NULL is returned. So it is advised to check
|
||||
the return value of this call.
|
||||
```cpp
|
||||
#if !defined(SUPPORT_SELECT_DRIVER_TYPE)
|
||||
FastAccelStepper* stepperConnectToPin(uint8_t step_pin);
|
||||
#endif
|
||||
```
|
||||
For e.g. esp32, there are two types of driver.
|
||||
One using mcpwm and pcnt module. And another using rmt module.
|
||||
This call allows to select the respective driver
|
||||
```cpp
|
||||
#if defined(SUPPORT_SELECT_DRIVER_TYPE)
|
||||
#define DRIVER_MCPWM_PCNT 0
|
||||
#define DRIVER_RMT 1
|
||||
#define DRIVER_DONT_CARE 2
|
||||
FastAccelStepper* stepperConnectToPin(uint8_t step_pin,
|
||||
uint8_t driver_type = DRIVER_DONT_CARE);
|
||||
#endif
|
||||
```
|
||||
Comments to valid pins:
|
||||
|
||||
| Device | Comment |
|
||||
|:----------------|:--------------------------------------------------------------------------------------------------|
|
||||
| ESP32 | Every output capable GPIO can be used |
|
||||
| ESP32S2 | Every output capable GPIO can be used |
|
||||
| Atmega168/328/p | Only the pins connected to OC1A and OC1B are allowed |
|
||||
| Atmega2560 | Only the pins connected to OC4A, OC4B and OC4C are allowed. |
|
||||
| Atmega32u4 | Only the pins connected to OC1A, OC1B and OC1C are allowed |
|
||||
| Atmel SAM | This can be one of each group of pins: 34/67/74/35, 17/36/72/37/42, 40/64/69/41, 9, 8/44, 7/45, 6 |
|
||||
## External Pins
|
||||
|
||||
If the direction/enable pins are e.g. connected via external HW (shift
|
||||
registers), then an external callback function can be supplied. The
|
||||
supplied value is either LOW or HIGH. The return value shall be the status
|
||||
of the pin (false for LOW or true for HIGH). If returned value and supplied
|
||||
value do not match, the stepper does not continue, but calls this function
|
||||
again.
|
||||
|
||||
This function is called from cyclic task/interrupt with 4ms rate, which
|
||||
creates the commands to put into the command queue. Thus the supplied
|
||||
function should take much less time than 4ms. Otherwise there is risk, that
|
||||
other running steppers are running out of commands in the queue. If this
|
||||
takes longer, then the function should be offloaded and return the new
|
||||
status, after the pin change has been successfully completed.
|
||||
|
||||
The callback has to be called on the FastAccelStepperEngine.
|
||||
See examples/ExternalCall
|
||||
|
||||
Stepperpins (enable or direction), which should use this external callback,
|
||||
need to be or'ed with PIN_EXTERNAL_FLAG ! FastAccelStepper uses this flag
|
||||
to determine, if a pin is external or internal.
|
||||
```cpp
|
||||
void setExternalCallForPin(bool (*func)(uint8_t pin, uint8_t value));
|
||||
```
|
||||
### Debug LED
|
||||
|
||||
If blinking of a LED is required to indicate, the stepper controller is
|
||||
still running, then the port. to which the LED is connected, can be told to
|
||||
the engine. The periodic task will let the associated LED blink with 1 Hz
|
||||
```cpp
|
||||
void setDebugLed(uint8_t ledPin);
|
||||
```
|
||||
### Return codes of calls to `move()` and `moveTo()`
|
||||
|
||||
The defined preprocessor macros are MOVE_xxx:
|
||||
MOVE_OK: All is OK:
|
||||
MOVE_ERR_NO_DIRECTION_PIN: Negative direction requested, but no direction pin
|
||||
MOVE_ERR_SPEED_IS_UNDEFINED: The maximum speed has not been set yet
|
||||
MOVE_ERR_ACCELERATION_IS_UNDEFINED: The acceleration to use has not been set
|
||||
yet
|
||||
### Return codes of `rampState()`
|
||||
|
||||
The return value is an uint8_t, which consist of two fields:
|
||||
|
||||
| Bit 7 | Bits 6-5 | Bits 4-0 |
|
||||
|:--------|:----------|:---------|
|
||||
|Always 0 | Direction | State |
|
||||
|
||||
The bit mask for direction and state:
|
||||
```cpp
|
||||
#define RAMP_DIRECTION_MASK (32 + 64)
|
||||
#define RAMP_STATE_MASK (1 + 2 + 4 + 8 + 16)
|
||||
```
|
||||
The defined ramp states are:
|
||||
```cpp
|
||||
#define RAMP_STATE_IDLE 0
|
||||
#define RAMP_STATE_COAST 1
|
||||
#define RAMP_STATE_ACCELERATE 2
|
||||
#define RAMP_STATE_DECELERATE 4
|
||||
#define RAMP_STATE_REVERSE (4 + 8)
|
||||
#define RAMP_STATE_ACCELERATING_FLAG 2
|
||||
#define RAMP_STATE_DECELERATING_FLAG 4
|
||||
```
|
||||
And the two directions of a move
|
||||
```cpp
|
||||
#define RAMP_DIRECTION_COUNT_UP 32
|
||||
#define RAMP_DIRECTION_COUNT_DOWN 64
|
||||
```
|
||||
A ramp state value of 2 is set after any move call on a stopped motor
|
||||
and until the stepper task is serviced. The stepper task will then
|
||||
control the direction flags
|
||||
|
||||
## Timing values - Architecture dependent
|
||||
|
||||
### AVR
|
||||
|VARIABLE | Value | Unit |
|
||||
|:----------------|------------:|:------------------------|
|
||||
|TICKS_PER_S | 16_000_000 | [ticks/s] |
|
||||
|MIN_CMD_TICKS | 640 | [1/TICKS_PER_S seconds] |
|
||||
|MIN_DIR_DELAY_US | 40 | [µs] |
|
||||
|MAX_DIR_DELAY_US | 4095 | [µs] |
|
||||
|
||||
### ESP32
|
||||
|VARIABLE | Value | Unit |
|
||||
|:----------------|------------:|:------------------------|
|
||||
|TICKS_PER_S | 16_000_000 | [ticks/s] |
|
||||
|MIN_CMD_TICKS | 3200 | [1/TICKS_PER_S seconds] |
|
||||
|MIN_DIR_DELAY_US | 200 | [µs] |
|
||||
|MAX_DIR_DELAY_US | 4095 | [µs] |
|
||||
|
||||
### SAM DUE
|
||||
|VARIABLE | Value | Unit |
|
||||
|:----------------|------------:|:------------------------|
|
||||
|TICKS_PER_S | 21_000_000 | [ticks/s] |
|
||||
|MIN_CMD_TICKS | 4200 | [1/TICKS_PER_S seconds] |
|
||||
|MIN_DIR_DELAY_US | 200 | [µs] |
|
||||
|MAX_DIR_DELAY_US | 3120 | [µs] |
|
||||
|
||||
# FastAccelStepper
|
||||
## Step Pin
|
||||
step pin is defined at creation. Here can retrieve the pin
|
||||
```cpp
|
||||
uint8_t getStepPin();
|
||||
```
|
||||
## Direction Pin
|
||||
if direction pin is connected, call this function.
|
||||
|
||||
If the pin number is >= 128, then the direction pin is assumed to be
|
||||
external and the external callback function (set by
|
||||
`setExternalCallForPin()`) is used to set the pin. For direction pin, this
|
||||
is implemented for esp32 and its supported derivates, and avr and its
|
||||
derivates except atmega32u4
|
||||
|
||||
For slow driver hardware the first step after any polarity change of the
|
||||
direction pin can be delayed by the value dir_change_delay_us. The allowed
|
||||
range is MIN_DIR_DELAY_US and MAX_DIR_DELAY_US. The special value of 0
|
||||
means, that no delay is added. Values 1 up to MIN_DIR_DELAY_US will be
|
||||
clamped to MIN_DIR_DELAY_US. Values above MAX_DIR_DELAY_US will be clamped
|
||||
to MAX_DIR_DELAY_US. For external pins, dir_change_delay_us is ignored,
|
||||
because the mechanism applied for external pins provides already pause
|
||||
in the range of ms or more.
|
||||
```cpp
|
||||
void setDirectionPin(uint8_t dirPin, bool dirHighCountsUp = true,
|
||||
uint16_t dir_change_delay_us = 0);
|
||||
uint8_t getDirectionPin() { return _dirPin; }
|
||||
bool directionPinHighCountsUp() { return _dirHighCountsUp; }
|
||||
```
|
||||
## Enable Pin
|
||||
if enable pin is connected, then use this function.
|
||||
|
||||
If the pin number is >= 128, then the enable pin is assumed to be
|
||||
external and the external callback function (set by
|
||||
`setExternalCallForPin()`) is used to set the pin.
|
||||
|
||||
In case there are two enable pins: one low and one high active, then
|
||||
these calls are valid and both pins will be operated:
|
||||
setEnablePin(pin1, true);
|
||||
setEnablePin(pin2, false);
|
||||
If pin1 and pin2 are same, then the last call will be used.
|
||||
```cpp
|
||||
void setEnablePin(uint8_t enablePin, bool low_active_enables_stepper = true);
|
||||
uint8_t getEnablePinHighActive() { return _enablePinHighActive; }
|
||||
uint8_t getEnablePinLowActive() { return _enablePinLowActive; }
|
||||
```
|
||||
using enableOutputs/disableOutputs the stepper can be enabled and disabled
|
||||
For a running motor with autoEnable set, disableOutputs() will return false
|
||||
bool enableOutputs();returns true, if enabled
|
||||
bool disableOutputs();returns true, if disabled
|
||||
In auto enable mode, the stepper is enabled before stepping and disabled
|
||||
afterwards. The delay from stepper enabled till first step and from
|
||||
last step to stepper disabled can be separately adjusted.
|
||||
The delay from enable to first step is done in ticks and as such is limited
|
||||
to MAX_ON_DELAY_TICKS, which translates approximately to 120ms for
|
||||
esp32 and 60ms for avr at 16 MHz). The delay till disable is done in period
|
||||
interrupt/task with 4 or 10 ms repetition rate and as such is with several
|
||||
ms jitter.
|
||||
```cpp
|
||||
void setAutoEnable(bool auto_enable);
|
||||
int8_t setDelayToEnable(uint32_t delay_us);
|
||||
void setDelayToDisable(uint16_t delay_ms);
|
||||
#define DELAY_OK 0
|
||||
#define DELAY_TOO_LOW -1
|
||||
#define DELAY_TOO_HIGH -2
|
||||
```
|
||||
## Stepper Position
|
||||
Retrieve the current position of the stepper
|
||||
|
||||
Comment for esp32 with rmt module:
|
||||
The actual position may be off by the number of steps in the ongoing
|
||||
command. If precise real time position is needed, attaching a pulse counter
|
||||
may be of help.
|
||||
```cpp
|
||||
int32_t getCurrentPosition();
|
||||
```
|
||||
Set the current position of the stepper - either in standstill or while
|
||||
moving.
|
||||
for esp32: the implementation uses getCurrentPosition(), which does not
|
||||
consider the steps of the current command
|
||||
=> recommend to use only in standstill
|
||||
```cpp
|
||||
void setCurrentPosition(int32_t new_pos);
|
||||
```
|
||||
## Stepper running status
|
||||
is true while the stepper is running or ramp generation is active
|
||||
```cpp
|
||||
bool isRunning();
|
||||
```
|
||||
## Speed
|
||||
For stepper movement control by FastAccelStepper's ramp generator
|
||||
|
||||
Speed can be defined in four different units:
|
||||
- In Hz: This means steps/s
|
||||
- In millHz: This means in steps/1000s
|
||||
- In us: This means in us/step
|
||||
|
||||
For the device's maximum allowed speed, the following calls can be used.
|
||||
```cpp
|
||||
uint16_t getMaxSpeedInUs();
|
||||
uint16_t getMaxSpeedInTicks();
|
||||
uint32_t getMaxSpeedInHz();
|
||||
uint32_t getMaxSpeedInMilliHz();
|
||||
```
|
||||
For esp32 and avr, the device's maximum allowed speed can be overridden.
|
||||
Allocating a new stepper will override any absolute speed limit.
|
||||
This is absolutely untested, no error checking implemented.
|
||||
Use at your own risk !
|
||||
```cpp
|
||||
#if SUPPORT_UNSAFE_ABS_SPEED_LIMIT_SETTING == 1
|
||||
void setAbsoluteSpeedLimit(uint16_t max_speed_in_ticks);
|
||||
#endif
|
||||
```
|
||||
Setting the speed can be done with the four `setSpeed...()` calls.
|
||||
The new value will be used only after call of these functions:
|
||||
|
||||
- `move()`
|
||||
- `moveTo()`
|
||||
- `runForward()`
|
||||
- `runBackward()`
|
||||
- `applySpeedAcceleration()`
|
||||
- `moveByAcceleration()`
|
||||
|
||||
Note: no update on `stopMove()`
|
||||
|
||||
Returns 0 on success, or -1 on invalid value.
|
||||
Invalid is faster than MaxSpeed or slower than ~250 Mio ticks/step.
|
||||
```cpp
|
||||
int8_t setSpeedInUs(uint32_t min_step_us);
|
||||
int8_t setSpeedInTicks(uint32_t min_step_ticks);
|
||||
int8_t setSpeedInHz(uint32_t speed_hz);
|
||||
int8_t setSpeedInMilliHz(uint32_t speed_mhz);
|
||||
```
|
||||
To retrieve current set speed. This means, while accelerating and/or
|
||||
decelerating, this is NOT the actual speed !
|
||||
```cpp
|
||||
uint32_t getSpeedInUs() { return _rg.getSpeedInUs(); }
|
||||
uint32_t getSpeedInTicks() { return _rg.getSpeedInTicks(); }
|
||||
uint32_t getSpeedInMilliHz() { return _rg.getSpeedInMilliHz(); }
|
||||
```
|
||||
If the current speed is needed, then use `getCurrentSpeed...()`. This
|
||||
retrieves the actual speed.
|
||||
|
||||
| value | description |
|
||||
|:-----:|:-----------------------------|
|
||||
| = 0 | while not moving |
|
||||
| > 0 | while position counting up |
|
||||
| < 0 | while position counting down |
|
||||
|
||||
If the parameter realtime is true, then the most actual speed
|
||||
from the stepper queue is derived. This works only, if the queue
|
||||
does not contain pauses, which is normally the case for slow speeds.
|
||||
Otherwise the speed from the ramp generator is reported, which is
|
||||
done always in case of `realtime == false`. That speed is typically
|
||||
the value of the speed a couple of milliseconds later.
|
||||
|
||||
The drawback of `realtime == true` is, that the reported speed
|
||||
may either come from the queue or from the ramp generator.
|
||||
This means the returned speed may have jumps during
|
||||
acceleration/deceleration.
|
||||
|
||||
For backward compatibility, the default is true.
|
||||
```cpp
|
||||
int32_t getCurrentSpeedInUs(bool realtime = true);
|
||||
int32_t getCurrentSpeedInMilliHz(bool realtime = true);
|
||||
```
|
||||
## Acceleration
|
||||
setAcceleration() expects as parameter the change of speed
|
||||
as step/s².
|
||||
If for example the speed should ramp up from 0 to 10000 steps/s within
|
||||
10s, then the acceleration is 10000 steps/s / 10s = 1000 steps/s²
|
||||
|
||||
New value will be used after call to
|
||||
move/moveTo/runForward/runBackward/applySpeedAcceleration/moveByAcceleration
|
||||
|
||||
note: no update on stopMove()
|
||||
|
||||
Returns 0 on success, or -1 on invalid value (<=0)
|
||||
```cpp
|
||||
int8_t setAcceleration(int32_t step_s_s) {
|
||||
return _rg.setAcceleration(step_s_s);
|
||||
}
|
||||
uint32_t getAcceleration() { return _rg.getAcceleration(); }
|
||||
```
|
||||
getCurrentAcceleration() retrieves the actual acceleration.
|
||||
= 0 while idle or coasting
|
||||
> 0 while speed is changing towards positive values
|
||||
< 0 while speed is changeing towards negative values
|
||||
```cpp
|
||||
int32_t getCurrentAcceleration() {
|
||||
return _rg.getCurrentAcceleration();
|
||||
}
|
||||
```
|
||||
## Linear Acceleration
|
||||
setLinearAcceleration expects as parameter the number of steps,
|
||||
where the acceleration is increased linearly from standstill up to the
|
||||
configured acceleration value. If this parameter is 0, then there will be
|
||||
no linear acceleration phase
|
||||
|
||||
If for example the acceleration should ramp up from 0 to 10000 steps/s^2
|
||||
within 100 steps, then call setLinearAcceleration(100)
|
||||
|
||||
The speed at which linear acceleration turns into constant acceleration
|
||||
can be calculated from the parameter linear_acceleration_steps.
|
||||
Let's call this parameter `s_h` for handover steps.
|
||||
Then the speed is:
|
||||
`v_h = sqrt(1.5 * a * s_h)`
|
||||
|
||||
New value will be used after call to
|
||||
move/moveTo/runForward/runBackward/applySpeedAcceleration/moveByAcceleration
|
||||
|
||||
note: no update on stopMove()
|
||||
```cpp
|
||||
void setLinearAcceleration(uint32_t linear_acceleration_steps) {
|
||||
_rg.setLinearAcceleration(linear_acceleration_steps);
|
||||
}
|
||||
```
|
||||
## Jump Start
|
||||
setJumpStart expects as parameter the ramp step to start from standstill.
|
||||
|
||||
The speed at which the stepper will start can be calculated like this:
|
||||
- If linear acceleration is not in use:
|
||||
start speed `v = sqrt(2 * a * jump_step)`
|
||||
- If linear acceleration is in use and `jump_step <= s_h`:
|
||||
start speed `v = sqrt(1.5*a)/s_h^(1/6) * jump_step^(2/3)`
|
||||
- If linear acceleration is in use and `jump_step > s_h`:
|
||||
start speed `v = sqrt(2 * a * (jump_step - s_h/4))`
|
||||
|
||||
|
||||
New value will be used after call to
|
||||
move/moveTo/runForward/runBackward
|
||||
```cpp
|
||||
void setJumpStart(uint32_t jump_step) { _rg.setJumpStart(jump_step); }
|
||||
```
|
||||
## Apply new speed/acceleration value
|
||||
This function applies new values for speed/acceleration.
|
||||
This is convenient especially, if the stepper is set to continuous running.
|
||||
```cpp
|
||||
void applySpeedAcceleration();
|
||||
```
|
||||
## Move commands
|
||||
### move() and moveTo()
|
||||
start/move the stepper for (move) steps or to an absolute position.
|
||||
|
||||
If the stepper is already running, then the current running move will be
|
||||
updated together with any updated values of acceleration/speed. The move is
|
||||
relative to the target position of any ongoing move ! If the new
|
||||
move/moveTo for an ongoing command would reverse the direction, then the
|
||||
command is silently ignored.
|
||||
return values are the MOVE_... constants
|
||||
```cpp
|
||||
int8_t move(int32_t move, bool blocking = false);
|
||||
int8_t moveTo(int32_t position, bool blocking = false);
|
||||
```
|
||||
### keepRunning()
|
||||
This command flags the stepper to keep run continuously into current
|
||||
direction. It can be stopped by stopMove.
|
||||
Be aware, if the motor is currently decelerating towards reversed
|
||||
direction, then keepRunning() will speed up again and not finish direction
|
||||
reversal first.
|
||||
```cpp
|
||||
void keepRunning();
|
||||
bool isRunningContinuously() { return _rg.isRunningContinuously(); }
|
||||
```
|
||||
### runForward() and runBackwards()
|
||||
These commands just let the motor run continuously in one direction.
|
||||
If the motor is running in the opposite direction, it will reverse
|
||||
return value as with move/moveTo
|
||||
```cpp
|
||||
int8_t runForward();
|
||||
int8_t runBackward();
|
||||
```
|
||||
### forwardStep() and backwardStep()
|
||||
forwardStep()/backwardstep() can be called, while stepper is not moving
|
||||
If stepper is moving, this is a no-op.
|
||||
backwardStep() is a no-op, if no direction pin defined
|
||||
It will immediately let the stepper perform one single step.
|
||||
If blocking = true, then the routine will wait till isRunning() is false
|
||||
```cpp
|
||||
void forwardStep(bool blocking = false);
|
||||
void backwardStep(bool blocking = false);
|
||||
```
|
||||
### moveByAcceleration()
|
||||
moveByAcceleration() can be called, if only the speed of the stepper
|
||||
is of interest and that speed to be controlled by acceleration.
|
||||
The maximum speed (in both directions) to be set by setSpeedInUs() before.
|
||||
The behaviour will be:
|
||||
acceleration > 0 => accelerate towards positive maximum speed
|
||||
acceleration = 0 => keep current speed
|
||||
acceleration < 0
|
||||
=> accelerate towards negative maximum speed if allow_reverse
|
||||
=> decelerate towards motor stop if allow_reverse = false
|
||||
return value as with move/moveTo
|
||||
```cpp
|
||||
int8_t moveByAcceleration(int32_t acceleration, bool allow_reverse = true);
|
||||
```
|
||||
### stopMove()
|
||||
Stop the running stepper with normal deceleration.
|
||||
This only sets a flag and can be called from an interrupt !
|
||||
```cpp
|
||||
void stopMove();
|
||||
bool isStopping() { return _rg.isStopping(); }
|
||||
```
|
||||
### stepsToStop()
|
||||
This returns the current step value of the ramp.
|
||||
This equals the number of steps for a motor to
|
||||
reach the current position and speed from standstill
|
||||
and to come to standstill with deceleration if stopped
|
||||
immediately.
|
||||
This value is valid with or without linear acceleration
|
||||
being used.
|
||||
Primary use is to forecast possible stop position.
|
||||
The stop position is:
|
||||
getCurrentPosition() + stepsToStop()
|
||||
in case of a motor running in positive direction.
|
||||
```cpp
|
||||
uint32_t stepsToStop() { return _rg.stepsToStop(); }
|
||||
```
|
||||
### forceStop()
|
||||
Abruptly stop the running stepper without deceleration.
|
||||
This can be called from an interrupt !
|
||||
|
||||
The stepper command queue will be processed, but no further commands are
|
||||
added. This means, that the stepper can be expected to stop within approx.
|
||||
20ms.
|
||||
```cpp
|
||||
void forceStop();
|
||||
```
|
||||
abruptly stop the running stepper without deceleration.
|
||||
This can be called from an interrupt !
|
||||
|
||||
No further step will be issued. As this is aborting all commands in the
|
||||
queue, the actual stop position is lost (recovering this position cannot be
|
||||
done within an interrupt). So the new position after stop has to be
|
||||
provided and will be set as current position after stop.
|
||||
```cpp
|
||||
void forceStopAndNewPosition(int32_t new_pos);
|
||||
```
|
||||
get the target position for the current move.
|
||||
As of now, this position is the view of the stepper task.
|
||||
This means, the value will stay unchanged after a move/moveTo until the
|
||||
stepper task is executed.
|
||||
In keep running mode, the targetPos() is not updated
|
||||
```cpp
|
||||
int32_t targetPos() { return _rg.targetPosition(); }
|
||||
```
|
||||
### Task planning
|
||||
The stepper task adds commands to the stepper queue until
|
||||
either at least two commands are planned, or the commands
|
||||
cover sufficient time into the future. Default value for that time is 20ms.
|
||||
|
||||
The stepper task is cyclically executed every ~4ms.
|
||||
Especially for avr, the step interrupts puts a significant load on the uC,
|
||||
so the cyclical stepper task can even run for 2-3 ms. On top of that,
|
||||
other interrupts caused by the application could increase the load even
|
||||
further.
|
||||
|
||||
Consequently, the forward planning should fill the queue for ideally two
|
||||
cycles, this means 8ms. This means, the default 20ms provide a sufficient
|
||||
margin and even a missed cycle is not an issue.
|
||||
|
||||
The drawback of the 20ms is, that any change in speed/acceleration are
|
||||
added after those 20ms and for an application, requiring fast reaction
|
||||
times, this may impact the expected performance.
|
||||
|
||||
Due to this the forward planning time can be adjusted with the following
|
||||
API call for each stepper individually.
|
||||
|
||||
Attention:
|
||||
- This is only for advanced users: no error checking is implemented.
|
||||
- Only change the forward planning time, if the stepper is not running.
|
||||
- Too small values bear the risk of a stepper running at full speed
|
||||
suddenly stopping
|
||||
due to lack of commands in the queue.
|
||||
```cpp
|
||||
void setForwardPlanningTimeInMs(uint8_t ms) {
|
||||
_forward_planning_in_ticks = ms;
|
||||
```
|
||||
_forward_planning_in_ticks *= TICKS_PER_S / 1000;ticks per ms
|
||||
```cpp
|
||||
}
|
||||
```
|
||||
## Low Level Stepper Queue Management (low level access)
|
||||
|
||||
If the queue is already running, then the start parameter is obsolote.
|
||||
But the queue may run out of commands while executing addQueueEntry,
|
||||
so it is better to set start=true to automatically restart/continue
|
||||
a running queue.
|
||||
|
||||
If the queue is not running, then the start parameter defines starting it
|
||||
or not. The latter case is of interest to first fill the queue and then
|
||||
start it.
|
||||
|
||||
The call addQueueEntry(NULL, true) just starts the queue. This is intended
|
||||
to achieve a near synchronous start of several steppers. Consequently it
|
||||
should be called with interrupts disabled and return very fast.
|
||||
Actually this is necessary, too, in case the queue is full and not
|
||||
started.
|
||||
```cpp
|
||||
int8_t addQueueEntry(const struct stepper_command_s* cmd, bool start = true);
|
||||
```
|
||||
Return codes for addQueueEntry
|
||||
positive values mean, that caller should retry later
|
||||
```cpp
|
||||
#define AQE_OK 0
|
||||
#define AQE_QUEUE_FULL 1
|
||||
#define AQE_DIR_PIN_IS_BUSY 2
|
||||
#define AQE_WAIT_FOR_ENABLE_PIN_ACTIVE 3
|
||||
#define AQE_DEVICE_NOT_READY 4
|
||||
#define AQE_ERROR_TICKS_TOO_LOW -1
|
||||
#define AQE_ERROR_EMPTY_QUEUE_TO_START -2
|
||||
#define AQE_ERROR_NO_DIR_PIN_TO_TOGGLE -3
|
||||
```
|
||||
### check functions for command queue being empty, full or running.
|
||||
```cpp
|
||||
bool isQueueEmpty();
|
||||
bool isQueueFull();
|
||||
bool isQueueRunning();
|
||||
```
|
||||
### functions to get the fill level of the queue
|
||||
|
||||
To retrieve the forward planning time in the queue, ticksInQueue()
|
||||
can be used. It sums up all ticks of the not yet processed commands.
|
||||
For commands defining pauses, the summed up value is entry.ticks.
|
||||
For commands with steps, the summed up value is entry.steps*entry.ticks
|
||||
```cpp
|
||||
uint32_t ticksInQueue();
|
||||
```
|
||||
This function can be used to check, if the commands in the queue
|
||||
will last for <min_ticks> ticks. This is again without the
|
||||
currently processed command.
|
||||
```cpp
|
||||
bool hasTicksInQueue(uint32_t min_ticks);
|
||||
```
|
||||
This function allows to check the number of commands in the queue.
|
||||
This is including the currently processed command.
|
||||
```cpp
|
||||
uint8_t queueEntries();
|
||||
```
|
||||
Get the future position of the stepper after all commands in queue are
|
||||
completed
|
||||
```cpp
|
||||
int32_t getPositionAfterCommandsCompleted();
|
||||
```
|
||||
Get the future speed of the stepper after all commands in queue are
|
||||
completed. This is in µs. Returns 0 for stopped motor
|
||||
|
||||
This value comes from the ramp generator and is not valid for raw command
|
||||
queue
|
||||
==> Will be renamed in future release
|
||||
```cpp
|
||||
uint32_t getPeriodInUsAfterCommandsCompleted();
|
||||
uint32_t getPeriodInTicksAfterCommandsCompleted();
|
||||
```
|
||||
Set the future position of the stepper after all commands in queue are
|
||||
completed. This has immediate effect to getCurrentPosition().
|
||||
```cpp
|
||||
void setPositionAfterCommandsCompleted(int32_t new_pos);
|
||||
```
|
||||
This function provides info, in which state the high level stepper control
|
||||
is operating. The return value is an `or` of RAMP_STATE_... and
|
||||
RAMP_DIRECTION_... flags. Definitions are above
|
||||
```cpp
|
||||
uint8_t rampState() { return _rg.rampState(); }
|
||||
```
|
||||
returns true, if the ramp generation is active
|
||||
```cpp
|
||||
bool isRampGeneratorActive() { return _rg.isRampGeneratorActive(); }
|
||||
```
|
||||
These functions allow to detach and reAttach a step pin for other use.
|
||||
Pretty low level, use with care or not at all
|
||||
```cpp
|
||||
void detachFromPin();
|
||||
void reAttachToPin();
|
||||
```
|
||||
## ESP32 only: Free pulse counter
|
||||
These four functions are only available on esp32.
|
||||
The first can attach any of the eight pulse counters to this stepper.
|
||||
The second then will read the current pulse counter value
|
||||
|
||||
The user is responsible to not use a pulse counter, which is occupied by a
|
||||
stepper and by this render the stepper or even blow up the uC.
|
||||
|
||||
Pulse counter 6 and 7 are not used by the stepper library and are judged as
|
||||
available. If only five steppers are defined, then 5 gets available. If
|
||||
four steppers are defined, then 4 is usable,too.
|
||||
|
||||
These functions are intended primarily for testing, because the library
|
||||
should always output the correct amount of pulses. Possible application
|
||||
usage would be an immediate and interrupt friendly version for
|
||||
getCurrentPosition()
|
||||
|
||||
The pulse counter counts up towards high_value.
|
||||
If the high_value is reached, then the pulse counter is reset to 0.
|
||||
Similarly, if direction pin is configured and set to count down,
|
||||
then the pulse counter counts towards low_value. When the low value is hit,
|
||||
the pulse counter is reset to 0.
|
||||
|
||||
If low_value and high_value are set to zero, then the pulse counter is just
|
||||
counting like any int16_t counter: 0...32767,-32768,-32767,...,0 and
|
||||
backwards accordingly
|
||||
|
||||
Possible application:
|
||||
Assume the stepper, to which the pulse counter attached to, needs 3200
|
||||
steps/revolution. If now attachToPulseCounter is called with -3200 and 3200
|
||||
for the low and high values respectively, then the momentary angle of the
|
||||
stepper (at exact this moment) can be retrieved just by reading the pulse
|
||||
counter. If the value is negative, then just add 3200.
|
||||
|
||||
Update for idf5 version:
|
||||
The pcnt_unit value is not used, because the available units are managed
|
||||
by the system. The parameter is kept for compatibility.
|
||||
|
||||
```cpp
|
||||
#if defined(SUPPORT_ESP32_PULSE_COUNTER) && (ESP_IDF_VERSION_MAJOR == 5)
|
||||
bool attachToPulseCounter(uint8_t unused_pcnt_unit = 0,
|
||||
int16_t low_value = -16384,
|
||||
int16_t high_value = 16384);
|
||||
int16_t readPulseCounter();
|
||||
void clearPulseCounter();
|
||||
bool pulseCounterAttached() { return _attached_pulse_unit != NULL; }
|
||||
#endif
|
||||
#if defined(SUPPORT_ESP32_PULSE_COUNTER) && (ESP_IDF_VERSION_MAJOR == 4)
|
||||
bool attachToPulseCounter(uint8_t pcnt_unit, int16_t low_value = -16384,
|
||||
int16_t high_value = 16384);
|
||||
int16_t readPulseCounter();
|
||||
void clearPulseCounter();
|
||||
bool pulseCounterAttached() { return _attached_pulse_cnt_unit >= 0; }
|
||||
#endif
|
||||
```
|
||||
44
extras/doc/application.wxm
Normal file
44
extras/doc/application.wxm
Normal file
@@ -0,0 +1,44 @@
|
||||
/* [wxMaxima batch file version 1] [ DO NOT EDIT BY HAND! ]*/
|
||||
/* [ Created with wxMaxima version 21.11.0 ] */
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
With the parameters a_ref and T² and the function f with derivatives and inverses, the formulas are:
|
||||
*/
|
||||
s_max = a_ref * T²;
|
||||
t = T * f_inv(s / s_max);
|
||||
s(t,T) := s_max * f(t/T);
|
||||
v(s,T) := s_max / T * (1 / f_inv_1(s / s_max));
|
||||
p(s,T) := T / s_max * f_inv_1(s / s_max);
|
||||
a(t,T) := a_ref * f_2(t/T);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
It is important, that f_inv_1(x) can be calculated stable for x<<<1 over several decades well.
|
||||
|
||||
Due to this a segmental definition could be useful.
|
||||
For example x³ and cubic root can be calculated easily for several decades close to 0.
|
||||
*/
|
||||
a:0.5 $
|
||||
b:1.46 $
|
||||
select(x) := x < a $
|
||||
seg1(x) :=1.295* x^3 $
|
||||
seg2(x) := 1-(1-seg1(a))*sin((1-x)*%pi/b)/sin((1-a)*%pi/b) $
|
||||
seg1_1(x):=diff(seg1(x),x)$
|
||||
seg2_1(x):=diff(seg2(x),x)$
|
||||
seg1_2(x):=diff(seg1(x),x,2)$
|
||||
seg2_2(x):=diff(seg2(x),x,2)$
|
||||
f(x) := if select(x) then seg1(x) else seg2(x) $
|
||||
[seg1_1(x),seg2_1(x)];wxplot2d([f(x),%[1],%[2]],[x,0,1]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
[seg1_2(x),seg2_2(x)];wxplot2d([%[1],%[2]],[x,0,1]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
|
||||
/* Old versions of Maxima abort on loading files that end in a comment. */
|
||||
"Created with wxMaxima 21.11.0"$
|
||||
62
extras/doc/arch_esp32.txt
Normal file
62
extras/doc/arch_esp32.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
StepperISR_esp32.cpp
|
||||
|
||||
The driver makes use of mcpwm module(s) and pcnt modules.
|
||||
The mcpwm generates the timing of the pulses.
|
||||
The pcnt module counts fast pulses.
|
||||
|
||||
The io-matrix of esp32 is configured, that the mcpwm generated
|
||||
pulses are routed to the GPIO pad and those pulses are
|
||||
fed back into the pulse counter.
|
||||
|
||||
If the command queue contains a command with 0 steps aka pause,
|
||||
then there is nothing to count and thus the mcpwm module interrupt
|
||||
is enabled. Otherwise the mcpwm module interrupt is disabled
|
||||
and the a pcnt interrupt is generated, when the number of steps is completed.
|
||||
|
||||
Thereof the interrupt rate is max(n,1)*p ticks,
|
||||
where n is the number of steps and p the ticks - as defined in the command.
|
||||
|
||||
Timing:
|
||||
|
||||
For steps = 0: ===========================================================================
|
||||
|
||||
Timer 0 1 2 3 4 ... ticks-1 ticks ticks-1 ... 1 0 1 2 3
|
||||
TEA X X X
|
||||
TEP X
|
||||
STEP 0 0 0 0 0 0 0 0 0 ...
|
||||
PWMint |---| (|-----|) in case of another pause
|
||||
PCNTint
|
||||
|
||||
Only PWM interrupt is triggered on time == 1. On down counting, a second event is generated two clock cycles later: too fast for two interrupts.
|
||||
|
||||
|
||||
For steps = 1: ===========================================================================
|
||||
|
||||
Timer 0 1 2 3 4 ... ticks-1 ticks ticks-1 ... 1 0 1 2 3
|
||||
CMPA X X
|
||||
TEP X
|
||||
STEP 0 1 1 1 1 1 0 0 0.....
|
||||
PCNT 0 1 1 1 1 ............................
|
||||
PWMint
|
||||
PCNTint |---|
|
||||
|
||||
PCNT interrupt is triggered on time == 1.
|
||||
On time == 1, the step output is triggered high and transition to 0 on ticks
|
||||
|
||||
For steps = 2 ===========================================================================
|
||||
|
||||
Timer 0 1 2 3 4 ... ticks ... 1 0 1 2 3...ticks ... 1 0
|
||||
CMPA X X
|
||||
TEP X X
|
||||
STEP 0 1 1 1 1 0 0 0 1 1 1... 0 0 0...
|
||||
PCNT 0 1 1 1 1 1 1 1 2 2 2...
|
||||
PWMint
|
||||
PCNTint |---|
|
||||
|
||||
PCNT interrupt is triggered on time == 1 and counter value 2.
|
||||
|
||||
Important: mcpwm update method at TEZ
|
||||
|
||||
Consequently: The ticks wait time is AFTER the pulse and started from L->H transition.
|
||||
Thus the pulse of the following command starts after this command's period
|
||||
|
||||
338
extras/doc/generalization.wxm
Normal file
338
extras/doc/generalization.wxm
Normal file
@@ -0,0 +1,338 @@
|
||||
/* [wxMaxima batch file version 1] [ DO NOT EDIT BY HAND! ]*/
|
||||
/* [ Created with wxMaxima version 21.11.0 ] */
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
For a constant acceleration ramp the steps and speed at time t can be calculated by:
|
||||
*/
|
||||
s(t,a) := 1/2 * a * t^2;
|
||||
v(t,a) := a * t;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The time at a given step is then
|
||||
*/
|
||||
t(s,a) := sqrt(2 * s / a);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
and the speed at a given step is then
|
||||
*/
|
||||
v(s,a) := sqrt(2 * s * a);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
and finally the step rate R (time distance between two pulses):
|
||||
*/
|
||||
R(s,a) := 1 / sqrt(2 * s * a);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
With s_ramp being the number of steps to accelerate or decelerate and s_total being the number of the steps for the total ramp including acceleration and deceleration, the complete ramp with acceleration, coasting and deceleration can be written as
|
||||
*/
|
||||
v(s,a, s_ramp, s_total) := if s < s_ramp then sqrt(2 * s * a) else if s < s_total-s_ramp then sqrt(2 * s_ramp * a) else if s < s_total then sqrt(2 * (s_total-s) * a) else 0 $
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
And the acceleration over steps is simply
|
||||
*/
|
||||
a(s,a, s_ramp, s_total) := if s < s_ramp then a else if s < s_total-s_ramp then 0 else if s < s_total then -a else 0 $
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
As an example the acceleration over steps for acceleration = 5 m/s², ramp steps = 100 and total ramp steps = 1000:
|
||||
*/
|
||||
wxplot2d([a(s,5,100,1000)],[s,0,1000]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
wxplot2d([v(s,5,100,1000)],[s,0,1000]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
In order to generalize the ramp function, we introduce a dimensionless function f, which translates from range [0,1] into the range [0,1].
|
||||
*/
|
||||
v(s) := v_max * f(s/s_ramp);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The first derivate of v aka v_1 is with df/fx = f_1:
|
||||
*/
|
||||
v_1(s) := v_max/s_ramp * f_1(s/s_ramp);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Using this definition, the acceleration a(s) can be approximated at steps s:
|
||||
*/
|
||||
a(s) := v_1(s) * v(s);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
With these two functions a(s) is:
|
||||
*/
|
||||
a(s);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Now we need a mathematical function, which can be used to define a smooth acceleration ramp without jumps.
|
||||
tanh(x) poses problem for the x-range. The smoothstep function starts at x=0 too slow. As we are controlling the speed,
|
||||
we already get one factor of x. So idea is to have a function coming out linearly from x with a maximum at x=1.
|
||||
|
||||
This time sine function with its first derivative to be used:
|
||||
*/
|
||||
f(x) := sin(x * %pi/2);
|
||||
diff(f(x),x);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
So the first derivative is simply
|
||||
*/
|
||||
f_1(x) :=%pi*cos(%pi*x/2)/2;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Drawn over the x-range from 0 to 1:
|
||||
*/
|
||||
wxplot2d([f(x),f_1(x)],[x,0,1]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
How does this apply to our functions v and a as dependent from step s ?
|
||||
*/
|
||||
fundef(v); fundef(a);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
With this the function to calculate the acceleration is (with constant factors eliminated):
|
||||
*/
|
||||
expand(a(s));
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The shape of a(s) is:
|
||||
*/
|
||||
wxplot2d([f(x)*f_1(x)],[x,0,1]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The maximum of this function is c at x_c
|
||||
*/
|
||||
x_c : 0.5;
|
||||
c : f(x_c)*f_1(x_c);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The maximum value of the function is pi/4
|
||||
*/
|
||||
float(c);float(%pi/4);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Consequently the maximum acceleration is:
|
||||
*/
|
||||
a_max = v_max² / s_ramp * %pi/4;
|
||||
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
In FastAccelStepper maximum acceleration and maximum speed are configured, so this equation can be used to determine s_ramp.
|
||||
*/
|
||||
ref: s_ramp = %pi/4 * v_max² / a_max;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The speed at step s = 1 is:
|
||||
*/
|
||||
ev(v(s), s=1,ref);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
As sin(x) ~ x for x << 1 the speed at step s=1 is:
|
||||
*/
|
||||
2*a_max/v_max;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
For reference: with constant acceleration the speed at step s=1 is independent of v_max
|
||||
*/
|
||||
v = sqrt(2*a);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
The issue is, that the relation v(s) in this manner cannot be applied, if the speed shall change from v_max to -v_max. In this scenario, the acceleration at v = 0 aka the turn point shall be at maximum during deceleration/acceleration phase.
|
||||
|
||||
Second problem is an adequate forecast, if at turnpoint the acceleration needs to be reduced in order to not overshoot the targetted speed.
|
||||
*/;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
From application point of view, it is better to define acceleration as dependent of time and mathematically being a second derivative:
|
||||
*/
|
||||
a(t,T) := a_ref * f_2(t/T);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Using this approach speed and distance can be deducted easily:
|
||||
*/
|
||||
v(t,T) := T * a_ref * f_1(t/T);
|
||||
s(t,T) := T² * a_ref * f(t/T);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
with f_inv(x) = y as solution for f(y) = x, time can be derived as dependent of distance:
|
||||
*/
|
||||
t = T * f_inv(s / (a_ref*T²));
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Using this approach v(t,T) can be stated as dependent of s:
|
||||
*/
|
||||
v(s,T) := T * a_ref * f_1(f_inv(s / (a_ref*T²)));
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Analyzing the first derivative as function of its inverse:
|
||||
*/
|
||||
F_1(F_inv(y)) = d/dx * F(x = F_inv(y)) ;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
F_1(F_inv(y)) = d/dy * dy/dx* F(x = F_inv(y)) $
|
||||
d/dy * F(x = F_inv(y)) = 1 $
|
||||
F_1(F_inv(y)) = dy/dx $
|
||||
F_1(F_inv(y)) = 1/F_inv_1(y) ;
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Applied to v(s,T):
|
||||
*/
|
||||
v(s,T) := T * a_ref / f_inv_1(s / (a_ref*T²));
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
And for the period being 1/v:
|
||||
*/
|
||||
p(s,T) := f_inv_1(s / (a_ref*T²)) / (T*a_ref);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Using s_max = a_ref * T² this can be rewritten to:
|
||||
*/
|
||||
p(s,T) := T / s_max * f_inv_1(s / s_max);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
cross check with constant acceleration. => OK
|
||||
*/
|
||||
f_2(x) = 1;
|
||||
f(x) = x²/2;
|
||||
f_inv(x) = sqrt(2*x);
|
||||
f_inv_1(x) = 1/sqrt(2*x);
|
||||
p(s,T) := 1/sqrt(2*s/a_ref/T²)/(T*a_ref) $
|
||||
p(s,T) := sqrt(a_ref*T²)/sqrt(2*s)/(T*a_ref) $
|
||||
p(s,T) := 1/sqrt(2*s *a_ref);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
as the math is ok. So try the sine function for ramp up of the speed
|
||||
*/
|
||||
f(x) := sin(x*%pi/2);
|
||||
wxplot2d([f(x)],[x,0,1]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
solve(y=f(x),x);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
diff(rhs(%[1]),y);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
wxplot2d([%],[y,0.1,0.9]);
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
/* [wxMaxima: input start ] */
|
||||
/*
|
||||
Looks ok, too. Just the cosine will generate maximum acceleration at ramp start and end. So another function is needed.
|
||||
The smoothstep function would be interesting, but the inverse is algebraically very complex.
|
||||
*/
|
||||
/* [wxMaxima: input end ] */
|
||||
|
||||
|
||||
|
||||
/* Old versions of Maxima abort on loading files that end in a comment. */
|
||||
"Created with wxMaxima 21.11.0"$
|
||||
125
extras/doc/ramp.md
Normal file
125
extras/doc/ramp.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Ramp generator
|
||||
|
||||
# Introduction
|
||||
|
||||
The ramp generator increases the speed linearly with the acceleration.
|
||||
If e.g. a=1000 steps/s², then after 1s the speed is 1000 steps/s.
|
||||
|
||||
The full ramp consists of three phases: acceleration+coasting+deceleration.
|
||||
If the number of steps are less than the required steps for acceleration and deceleration, then there will not be any coasting phase.
|
||||
|
||||
For illustration here an ascii chart of the ramp
|
||||
|
||||
```
|
||||
Speed
|
||||
| -------------------
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
0---/ \------
|
||||
|<->|<--------------->|<->|
|
||||
Ta Tc Td
|
||||
-----------------------------------> Time
|
||||
|
||||
Ta ... acceleration time.
|
||||
Tc ... coasting time.
|
||||
Td ... deceleration time.
|
||||
```
|
||||
|
||||
The total ramp time is Ta + Tc + Td = T
|
||||
|
||||
Due to acceleration = deceleration, the related times Ta and Td are same.
|
||||
$$
|
||||
T=2*Ta+Tc
|
||||
$$
|
||||
|
||||
The steps executed in coasting are simply:
|
||||
$$
|
||||
steps_{coasting} = v * Tc
|
||||
$$
|
||||
|
||||
The steps executed during acceleration and deceleration are:
|
||||
$$
|
||||
steps_{acceleration} = 0.5 * a * Ta²
|
||||
$$
|
||||
|
||||
The total steps are:
|
||||
$$
|
||||
\begin{align}
|
||||
steps &= steps_{acceleration} + steps_{coasting} + steps_{deceleration} \\
|
||||
&= 2 * steps_{acceleration} + steps_{coasting} \\
|
||||
&= a * Ta² + v * Tc \\
|
||||
&= a * Ta² + (a * Ta) * Tc \\
|
||||
&= a * Ta * (Ta + Tc) \\
|
||||
&= a * Ta * (T - Ta)
|
||||
\end{align}
|
||||
$$
|
||||
This can be rearranged to calculate the required acceleration to perform `steps` during the total ramp time T and acceleration time Ta:
|
||||
$$
|
||||
a = \frac{steps}{ Ta * (T - Ta) }
|
||||
$$
|
||||
|
||||
# Calculation example
|
||||
|
||||
The stepper motor should perform 32000 steps in 10s.
|
||||
|
||||
The required acceleration and speed for different acceleration times are calculated as this:
|
||||
$$
|
||||
\begin{align}
|
||||
Ta = 1s => a &= \frac{32000}{1 * 9} steps/s² = 3556 steps/s² \\
|
||||
v &= a*Ta = 3556 steps/s => 281us/step \\
|
||||
Ta = 2s => a &= \frac{32000}{2 * 8} steps/s² = 2000 steps/s² \\
|
||||
v &= a*Ta = 4000 steps/s => 250us/step \\
|
||||
Ta = 3s => a &= \frac{32000}{3 * 7} steps/s² = 1524 steps/s² \\
|
||||
v &= a*Ta = 4571 steps/s => 218us/step \\
|
||||
Ta = 4s => a &= \frac{32000}{4 * 6} steps/s² = 1333 steps/s² \\
|
||||
v &= a*Ta = 5333 steps/s => 188us/step \\
|
||||
Ta = 5s => a &= \frac{32000}{5 * 5} steps/s² = 1280 steps/s² \\
|
||||
v &= a*Ta = 6400 steps/s => 156us/step
|
||||
\end{align}
|
||||
$$
|
||||
|
||||
As commands for the StepperDemo:
|
||||
```
|
||||
M1 A3556 V281 R32000
|
||||
M1 A2000 V250 R32000
|
||||
M1 A1524 V218 R32000
|
||||
M1 A1333 V188 R32000
|
||||
M1 A1280 V156 R32000
|
||||
```
|
||||
|
||||
# Special case
|
||||
|
||||
The minimum command time for a bundle of steps is `MIN_CMD_TICKS`. The ramp generator need to make sure,
|
||||
that any command issued to the command queue meets this requirement.
|
||||
|
||||
With high acceleration this can get problematic coming from or going to stand still.
|
||||
|
||||
The required condition to meet this requirement is:
|
||||
$$
|
||||
steps * \frac{1}{v} \ge T_{CMD}
|
||||
$$
|
||||
|
||||
From stand still the speed after `MIN_CMD_TICKS`is:
|
||||
$$
|
||||
v_{start} = a * T_{CMD}
|
||||
$$
|
||||
|
||||
So the related condition for steps is:
|
||||
$$
|
||||
\begin{align}
|
||||
steps * \frac{1}{a * T_{CMD}} &\ge T_{CMD} \\
|
||||
steps &\ge a * T_{CMD}^2
|
||||
\end{align}
|
||||
$$
|
||||
|
||||
Based on this there can be deducted two problems:
|
||||
|
||||
1. Any move command with less steps cannot be fulfilled with this acceleration
|
||||
|
||||
=> Remedy: Run these steps at maximum allowed speed based on `MIN_CMD_TICKS`
|
||||
|
||||
2. While ramping down the last issued command may violate this condition
|
||||
|
||||
=> Remedy: Run these steps at maximum allowed speed based on `MIN_CMD_TICKS`.
|
||||
The problem is to distinguish this case from a legitimate overshoot situation.
|
||||
63
extras/doc/ramp_cubic_quadratic.md
Normal file
63
extras/doc/ramp_cubic_quadratic.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Ramp generator concept for cubic/quadratic ramp combination
|
||||
|
||||
Piecewise ramp:
|
||||
- ramp up with s = 1/6 * j * t^3 and v = 1/2 * j * t^2
|
||||
so t = sqrt(2*v/j) and s = 1/6 * j * (2*v/j)^(3/2)
|
||||
and (6*s/j)^(2/3) = 2*v / j
|
||||
and v = j/2 * (6/j)^(2/3) * s^(2/3) = 6^(2/3)/2 * j^(1/3) * s^(2/3)
|
||||
- define hand over point s_h
|
||||
- ramp up to max speed with s = 1/2 * a * t'^2 = 1/2 * v^2 / a
|
||||
|
||||
Conditions at hand over point s_h:
|
||||
- s_h = 1/6 * j * t_h^3 = 1/2 * a * t'_h^2 + ds
|
||||
- v_h = 1/2 * j * t_h^2 = a * t'_h
|
||||
- a_h = j * t_h = a
|
||||
|
||||
Replacing j = a / t_h:
|
||||
- s_h = 1/6 * a * t_h^2 = 1/2 * a * t'_h^2 + ds
|
||||
- v_h = 1/2 * a * t_h = a * t'_h
|
||||
|
||||
With t'_h = t_h - dt:
|
||||
- s_h = 1/6 * a * t_h^2 = 1/2 * a * (t_h^2 - 2*t_h*dt + dt^2) + ds
|
||||
- v_h = 1/2 * a * t_h = a * t_h - a * dt
|
||||
|
||||
Consequently from v_h:
|
||||
dt = th/2
|
||||
|
||||
And for s:
|
||||
- s_h = 1/6 * a * t_h^2 = 1/8 * a * t_h^2 + ds
|
||||
|
||||
So ds:
|
||||
ds = (8-6)/48 * a * t_h^2 = 1/24 * a * t_h^2 = s_h/4
|
||||
|
||||
So we have clear relation from s to the ramp speed:
|
||||
|
||||
if s < s_h, then cubic ramp: v(s) = 6^(2/3)/2 * j^(1/3) * s^(2/3)
|
||||
if s >= s_h, then quadratic ramp: v(s) = sqrt(2 * a * (s-s_h/4))
|
||||
|
||||
Still need to reduce j and s_h to one parameter of freedom.
|
||||
If choose s_h as user defined parameter, then:
|
||||
|
||||
t_h = sqrt(6 * s_h / a)
|
||||
|
||||
and so:
|
||||
|
||||
j = a / t_h = a / sqrt(6 * s_h / a) = sqrt(a^3 / (6 * s_h))
|
||||
|
||||
For the speed calculation cubic ramp, this yields:
|
||||
|
||||
v(s) = [6^(2/3)/2 * (a^3/6/s_h)^(1/6)] * s^(2/3)
|
||||
= [6^(4/6)/2 * (a^3)^(1/6) * 6^(-1/6) * s_h^(-1/6)] * s^(2/3)
|
||||
= [6^(3/6)/2 * sqrt(a) * s_h^(-1/6)] * s^(2/3)
|
||||
= [sqrt(6)/2 * sqrt(a) * s_h^(-1/6)] * s^(2/3)
|
||||
= [sqrt(3/2) * sqrt(a) / s_h^(1/6) ] * s^(2/3)
|
||||
|
||||
Check cubic:
|
||||
v(s_h) = [sqrt(3/2) * sqrt(a) / s_h^(1/6) ] * s_h^(2/3)
|
||||
= [sqrt(3/2) * sqrt(a) ] * s_h^(1/2)
|
||||
|
||||
and quadratic:
|
||||
v(s_h) = sqrt(2 * a * s_h * 3 / 4) = sqrt(3/2 * a * s_h) equals cubic
|
||||
|
||||
With s_h = 0 there will be no cubic ramp start.
|
||||
|
||||
Reference in New Issue
Block a user