
Added almost instant pause functonality Support pause and resume macros Support nested macros Support zpl's web interface Merge in zpl's web interface and network changes Add R parameter to M105 command for PanelDue M98 can now run macros from any SD card folder
930 lines
29 KiB
C++
930 lines
29 KiB
C++
/*
|
|
* DDA.cpp
|
|
*
|
|
* Created on: 7 Dec 2014
|
|
* Author: David
|
|
*/
|
|
|
|
#include "RepRapFirmware.h"
|
|
|
|
|
|
DDA::DDA(DDA* n) : state(empty), next(n), prev(nullptr)
|
|
{
|
|
memset(ddm, 0, sizeof(ddm)); //DEBUG to clear stepError field
|
|
}
|
|
|
|
// Return the number of clocks this DDA still needs to execute.
|
|
// This could be slightly negative, if the move is overdue for completion.
|
|
int32_t DDA::GetTimeLeft() const
|
|
//pre(state == executing || state == frozen || state == completed)
|
|
{
|
|
return (state == completed) ? 0
|
|
: (state == executing) ? (int32_t)(moveStartTime + timeNeeded - Platform::GetInterruptClocks())
|
|
: (int32_t)timeNeeded;
|
|
}
|
|
|
|
void DDA::DebugPrintVector(const char *name, const float *vec, size_t len) const
|
|
{
|
|
debugPrintf("%s=", name);
|
|
for (size_t i = 0; i < len; ++i)
|
|
{
|
|
debugPrintf("%c%f", ((i == 0) ? '[' : ' '), vec[i]);
|
|
}
|
|
debugPrintf("]");
|
|
}
|
|
|
|
void DDA::DebugPrint() const
|
|
{
|
|
debugPrintf("DDA:");
|
|
if (endCoordinatesValid)
|
|
{
|
|
DebugPrintVector(" end", endCoordinates, AXES);
|
|
}
|
|
|
|
debugPrintf(" d=%f", totalDistance);
|
|
DebugPrintVector(" vec", directionVector, 5);
|
|
debugPrintf(" a=%f reqv=%f topv=%f startv=%f endv=%f\n"
|
|
"daccel=%f ddecel=%f fstep=%u\n",
|
|
acceleration, requestedSpeed, topSpeed, startSpeed, endSpeed,
|
|
accelDistance, decelDistance, firstStepTime);
|
|
// reprap.GetPlatform()->GetLine()->Flush();
|
|
ddm[0].DebugPrint('x', isDeltaMovement);
|
|
ddm[1].DebugPrint('y', isDeltaMovement);
|
|
ddm[2].DebugPrint('z', isDeltaMovement);
|
|
ddm[3].DebugPrint('1', false);
|
|
ddm[4].DebugPrint('2', false);
|
|
// reprap.GetPlatform()->GetLine()->Flush();
|
|
}
|
|
|
|
// This is called by Move to initialize all DDAs
|
|
void DDA::Init()
|
|
{
|
|
// Set the endpoints to zero, because Move asks for them.
|
|
// They will be wrong if we are on a delta. We take care of that when we process the M665 command in config.g.
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
endPoint[drive] = 0;
|
|
ddm[drive].stepError = false;
|
|
}
|
|
state = empty;
|
|
endCoordinatesValid = false;
|
|
}
|
|
|
|
// Set up a real move. Return true if it represents real movement, else false.
|
|
bool DDA::Init(const float nextMove[], EndstopChecks ce, bool doDeltaMapping, FilePosition fPos)
|
|
{
|
|
// 1. Compute the new endpoints and the movement vector
|
|
const int32_t *positionNow = prev->DriveCoordinates();
|
|
if (doDeltaMapping)
|
|
{
|
|
reprap.GetMove()->DeltaTransform(nextMove, endPoint); // transform the axis coordinates if on a delta printer
|
|
isDeltaMovement = (endPoint[X_AXIS] != positionNow[X_AXIS]) || (endPoint[Y_AXIS] != positionNow[Y_AXIS]) || (endPoint[Z_AXIS] != positionNow[Z_AXIS]);
|
|
}
|
|
else
|
|
{
|
|
isDeltaMovement = false;
|
|
}
|
|
|
|
bool realMove = false, xyMoving = false;
|
|
float accelerations[DRIVES];
|
|
const float *normalAccelerations = reprap.GetPlatform()->Accelerations();
|
|
for (size_t drive = 0; drive < DRIVES; drive++)
|
|
{
|
|
accelerations[drive] = normalAccelerations[drive];
|
|
if (drive >= AXES || !doDeltaMapping)
|
|
{
|
|
endPoint[drive] = Move::MotorEndPointToMachine(drive, nextMove[drive]);
|
|
}
|
|
|
|
int32_t delta;
|
|
if (drive < AXES)
|
|
{
|
|
endCoordinates[drive] = nextMove[drive]; // this will be wrong if we are doing a special move
|
|
delta = endPoint[drive] - positionNow[drive];
|
|
}
|
|
else
|
|
{
|
|
delta = endPoint[drive];
|
|
}
|
|
|
|
DriveMovement& dm = ddm[drive];
|
|
if (drive < AXES && isDeltaMovement)
|
|
{
|
|
directionVector[drive] = nextMove[drive] - prev->GetEndCoordinate(drive, false);
|
|
dm.moving = true; // on a delta printer, if one tower moves then we assume they all do
|
|
}
|
|
else
|
|
{
|
|
directionVector[drive] = (float)delta/reprap.GetPlatform()->DriveStepsPerUnit(drive);
|
|
dm.moving = (delta != 0);
|
|
}
|
|
|
|
if (dm.moving)
|
|
{
|
|
dm.totalSteps = labs(delta); // for now this is the number of net steps, but gets adjusted later if there is a reverse in direction
|
|
dm.direction = (delta >= 0); // for now this is the direction of net movement, but gets adjusted later if it is a delta movement
|
|
realMove = true;
|
|
|
|
if (drive < Z_AXIS)
|
|
{
|
|
xyMoving = true;
|
|
}
|
|
|
|
if (drive >= AXES && xyMoving)
|
|
{
|
|
float compensationTime = reprap.GetPlatform()->GetElasticComp(drive);
|
|
if (compensationTime > 0.0)
|
|
{
|
|
// Compensation causes instant velocity changes equal to acceleration * k, so we may need to limit the acceleration
|
|
accelerations[drive] = min<float>(accelerations[drive], reprap.GetPlatform()->ConfiguredInstantDv(drive)/compensationTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Throw it away if there's no real movement.
|
|
if (!realMove)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 3. Store some values
|
|
endStopsToCheck = ce;
|
|
filePos = fPos;
|
|
// The end coordinates will be valid at the end of this move if it does not involve endstop checks and is not a special move on a delta printer
|
|
endCoordinatesValid = (ce == 0) && (doDeltaMapping || !reprap.GetMove()->IsDeltaMode());
|
|
|
|
// 4. Normalise the direction vector and compute the amount of motion.
|
|
// If there is any XYZ movement, then we normalise it so that the total XYZ movement has unit length.
|
|
// This means that the user gets the feed rate that he asked for. It also makes the delta calculations simpler.
|
|
if (xyMoving || ddm[Z_AXIS].moving)
|
|
{
|
|
totalDistance = Normalise(directionVector, DRIVES, AXES);
|
|
if (isDeltaMovement)
|
|
{
|
|
// The following are only needed when doing delta movements. We could defer computing them until Prepare(), which would make simulation faster.
|
|
a2plusb2 = fsquare(directionVector[X_AXIS]) + fsquare(directionVector[Y_AXIS]);
|
|
cKc = (int32_t)(directionVector[Z_AXIS] * DriveMovement::Kc);
|
|
|
|
const DeltaParameters& dparams = reprap.GetMove()->GetDeltaParams();
|
|
const float initialX = prev->GetEndCoordinate(X_AXIS, false);
|
|
const float initialY = prev->GetEndCoordinate(Y_AXIS, false);
|
|
const float diagonalSquared = fsquare(dparams.GetDiagonal());
|
|
const float a2b2D2 = a2plusb2 * diagonalSquared;
|
|
|
|
for (size_t drive = 0; drive < AXES; ++drive)
|
|
{
|
|
const float A = initialX - dparams.GetTowerX(drive);
|
|
const float B = initialY - dparams.GetTowerY(drive);
|
|
const float stepsPerMm = reprap.GetPlatform()->DriveStepsPerUnit(drive);
|
|
DriveMovement& dm = ddm[drive];
|
|
const float aAplusbB = A * directionVector[X_AXIS] + B * directionVector[Y_AXIS];
|
|
const float dSquaredMinusAsquaredMinusBsquared = diagonalSquared - fsquare(A) - fsquare(B);
|
|
float h0MinusZ0 = sqrtf(dSquaredMinusAsquaredMinusBsquared);
|
|
dm.mp.delta.hmz0sK = (int32_t)(h0MinusZ0 * stepsPerMm * DriveMovement::K2);
|
|
dm.mp.delta.minusAaPlusBbTimesKs = -(int32_t)(aAplusbB * stepsPerMm * DriveMovement::K2);
|
|
dm.mp.delta.dSquaredMinusAsquaredMinusBsquaredTimesKsquaredSsquared =
|
|
(int64_t)(dSquaredMinusAsquaredMinusBsquared * fsquare(stepsPerMm * DriveMovement::K2));
|
|
|
|
// Calculate the distance at which we need to reverse direction.
|
|
if (a2plusb2 <= 0.0)
|
|
{
|
|
// Pure Z movement. We can't use the main calculation because it divides by a2plusb2.
|
|
dm.direction = (directionVector[Z_AXIS] >= 0.0);
|
|
dm.mp.delta.reverseStartStep = dm.totalSteps + 1;
|
|
}
|
|
else
|
|
{
|
|
const float drev = ((directionVector[Z_AXIS] * sqrt(a2b2D2 - fsquare(A * directionVector[Y_AXIS] - B * directionVector[X_AXIS])))
|
|
- aAplusbB)/a2plusb2;
|
|
if (drev > 0.0 && drev < totalDistance) // if the reversal point is within range
|
|
{
|
|
// Calculate how many steps we need to move up before reversing
|
|
float hrev = directionVector[Z_AXIS] * drev + sqrt(dSquaredMinusAsquaredMinusBsquared - 2 * drev * aAplusbB - a2plusb2 * fsquare(drev));
|
|
int32_t numStepsUp = (int32_t)((hrev - h0MinusZ0) * stepsPerMm);
|
|
|
|
// We may be almost at the peak height already, in which case we don't really have a reversal.
|
|
// We must not set reverseStartStep to 1, because then we would set the direction when Prepare() calls CalcStepTime(), before the previous move finishes.
|
|
if (numStepsUp < 1 || (dm.direction && (uint32_t)numStepsUp <= dm.totalSteps))
|
|
{
|
|
dm.mp.delta.reverseStartStep = dm.totalSteps + 1;
|
|
}
|
|
else
|
|
{
|
|
dm.mp.delta.reverseStartStep = (uint32_t)numStepsUp + 1;
|
|
|
|
// Correct the initial direction and the total number of steps
|
|
if (dm.direction)
|
|
{
|
|
// Net movement is up, so we will go up a bit and then down by a lesser amount
|
|
dm.totalSteps = (2 * numStepsUp) - dm.totalSteps;
|
|
}
|
|
else
|
|
{
|
|
// Net movement is down, so we will go up first and then down by a greater amount
|
|
dm.direction = true;
|
|
dm.totalSteps = (2 * numStepsUp) + dm.totalSteps;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dm.mp.delta.reverseStartStep = dm.totalSteps + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
totalDistance = Normalise(directionVector, DRIVES, DRIVES);
|
|
}
|
|
|
|
// 5. Compute the maximum acceleration available and maximum top speed
|
|
float normalisedDirectionVector[DRIVES]; // Used to hold a unit-length vector in the direction of motion
|
|
memcpy(normalisedDirectionVector, directionVector, sizeof(normalisedDirectionVector));
|
|
Absolute(normalisedDirectionVector, DRIVES);
|
|
acceleration = VectorBoxIntersection(normalisedDirectionVector, accelerations, DRIVES);
|
|
|
|
// Set the speed to the smaller of the requested and maximum speed.
|
|
// Also enforce a minimum speed of 0.5mm/sec. We need a minimum speed to avoid overflow in the movement calculations.
|
|
requestedSpeed = max<float>(0.5, min<float>(nextMove[DRIVES], VectorBoxIntersection(normalisedDirectionVector, reprap.GetPlatform()->MaxFeedrates(), DRIVES)));
|
|
|
|
// On a Cartesian printer, it is OK to limit the X and Y speeds and accelerations independently, and in consequence to allow greater values
|
|
// for diagonal moves. On a delta, this is not OK and any movement in the XY plane should be limited to the X/Y axis values, which we assume to be equal.
|
|
if (isDeltaMovement)
|
|
{
|
|
const float xyFactor = sqrt(fsquare(normalisedDirectionVector[X_AXIS]) + fsquare(normalisedDirectionVector[X_AXIS]));
|
|
const float maxSpeed = reprap.GetPlatform()->MaxFeedrates()[X_AXIS];
|
|
if (requestedSpeed * xyFactor > maxSpeed)
|
|
{
|
|
requestedSpeed = maxSpeed/xyFactor;
|
|
}
|
|
|
|
const float maxAcceleration = normalAccelerations[X_AXIS];
|
|
if (acceleration * xyFactor > maxAcceleration)
|
|
{
|
|
acceleration = maxAcceleration/xyFactor;
|
|
}
|
|
}
|
|
|
|
// 6. Calculate the provisional accelerate and decelerate distances and the top speed
|
|
endSpeed = 0.0; // until the next move asks us to adjust it
|
|
|
|
if (prev->state != provisional)
|
|
{
|
|
// There is no previous move that we can adjust, so this move must start at zero speed.
|
|
startSpeed = 0.0;
|
|
}
|
|
else
|
|
{
|
|
// Try to meld this move to the previous move to avoid stop/start
|
|
// Assuming that this move ends with zero speed, calculate the maximum possible starting speed: u^2 = v^2 - 2as
|
|
float maxStartSpeed = sqrtf(acceleration * totalDistance * 2.0);
|
|
prev->targetNextSpeed = min<float>(maxStartSpeed, requestedSpeed);
|
|
DoLookahead(prev);
|
|
startSpeed = prev->targetNextSpeed;
|
|
}
|
|
|
|
RecalculateMove();
|
|
state = provisional;
|
|
return true;
|
|
}
|
|
|
|
float DDA::GetMotorPosition(size_t drive) const
|
|
{
|
|
return Move::MotorEndpointToPosition(endPoint[drive], drive);
|
|
}
|
|
|
|
void DDA::DoLookahead(DDA *laDDA)
|
|
//pre(state == provisional)
|
|
{
|
|
// if (reprap.Debug(moduleDda)) debugPrintf("Adjusting, %f\n", laDDA->targetNextSpeed);
|
|
unsigned int laDepth = 0;
|
|
bool goingUp = true;
|
|
|
|
for(;;) // this loop is used to nest lookahead without making recursive calls
|
|
{
|
|
bool recurse = false;
|
|
if (goingUp)
|
|
{
|
|
// We have been asked to adjust the end speed of this move to targetStartSpeed
|
|
if (laDDA->topSpeed == laDDA->requestedSpeed)
|
|
{
|
|
// This move already reaches its top speed, so just need to adjust the deceleration part
|
|
laDDA->endSpeed = laDDA->requestedSpeed;
|
|
laDDA->CalcNewSpeeds();
|
|
}
|
|
else if (laDDA->decelDistance == laDDA->totalDistance && laDDA->prev->state == provisional)
|
|
{
|
|
// This move doesn't reach its requested speed, so we may have to adjust the previous move as well to get optimum behaviour
|
|
laDDA->endSpeed = laDDA->requestedSpeed;
|
|
laDDA->CalcNewSpeeds();
|
|
laDDA->prev->targetNextSpeed = min<float>(sqrtf((laDDA->endSpeed * laDDA->endSpeed) + (2 * laDDA->acceleration * laDDA->totalDistance)), laDDA->requestedSpeed);
|
|
recurse = true;
|
|
}
|
|
else
|
|
{
|
|
// This move doesn't reach its requested speed, but we can't adjust the previous one
|
|
laDDA->endSpeed = min<float>(sqrtf((laDDA->startSpeed * laDDA->startSpeed) + (2 * laDDA->acceleration * laDDA->totalDistance)), laDDA->requestedSpeed);
|
|
laDDA->CalcNewSpeeds();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
laDDA->startSpeed = laDDA->prev->targetNextSpeed;
|
|
float maxEndSpeed = sqrtf((laDDA->startSpeed * laDDA->startSpeed) + (2 * laDDA->acceleration * laDDA->totalDistance));
|
|
if (maxEndSpeed < laDDA->endSpeed)
|
|
{
|
|
// Oh dear, we were too optimistic! Have another go.
|
|
laDDA->endSpeed = maxEndSpeed;
|
|
laDDA->CalcNewSpeeds();
|
|
}
|
|
}
|
|
|
|
if (recurse)
|
|
{
|
|
laDDA = laDDA->prev;
|
|
++laDepth;
|
|
if (reprap.Debug(moduleDda)) debugPrintf("Recursion start %u\n", laDepth);
|
|
}
|
|
else
|
|
{
|
|
laDDA->RecalculateMove();
|
|
|
|
if (laDepth == 0)
|
|
{
|
|
// if (reprap.Debug(moduleDda)) debugPrintf("Complete, %f\n", laDDA->targetNextSpeed);
|
|
return;
|
|
}
|
|
|
|
laDDA = laDDA->next;
|
|
--laDepth;
|
|
goingUp = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recalculate the top speed, acceleration distance and deceleration distance, and whether we can pause after this move
|
|
void DDA::RecalculateMove()
|
|
{
|
|
accelDistance = ((requestedSpeed * requestedSpeed) - (startSpeed * startSpeed))/(2.0 * acceleration);
|
|
decelDistance = ((requestedSpeed * requestedSpeed) - (endSpeed * endSpeed))/(2.0 * acceleration);
|
|
if (accelDistance + decelDistance >= totalDistance)
|
|
{
|
|
// It's an accelerate-decelerate move. If V is the peak speed, then (V^2 - u^2)/2a + (V^2 - v^2)/2a = distance.
|
|
// So (2V^2 - u^2 - v^2)/2a = distance
|
|
// So V^2 = a * distance + 0.5(u^2 + v^2)
|
|
float vsquared = (acceleration * totalDistance) + 0.5 * ((startSpeed * startSpeed) + (endSpeed * endSpeed));
|
|
// Calculate accelerate distance from: V^2 = u^2 + 2as
|
|
if (vsquared >= 0.0)
|
|
{
|
|
accelDistance = max<float>((vsquared - (startSpeed * startSpeed))/(2.0 * acceleration), 0.0);
|
|
decelDistance = totalDistance - accelDistance;
|
|
topSpeed = sqrtf(vsquared);
|
|
}
|
|
else if (startSpeed < endSpeed)
|
|
{
|
|
// This would ideally never happen, but might because of rounding errors
|
|
accelDistance = totalDistance;
|
|
decelDistance = 0.0;
|
|
topSpeed = endSpeed;
|
|
}
|
|
else
|
|
{
|
|
// This would ideally never happen, but might because of rounding errors
|
|
accelDistance = 0.0;
|
|
decelDistance = totalDistance;
|
|
topSpeed = startSpeed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
topSpeed = requestedSpeed;
|
|
}
|
|
|
|
canPause = (endStopsToCheck == 0);
|
|
if (canPause && endSpeed != 0.0)
|
|
{
|
|
const Platform *p = reprap.GetPlatform();
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
if (ddm[drive].moving && endSpeed * fabs(directionVector[drive]) > p->ActualInstantDv(drive))
|
|
{
|
|
canPause = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DDA::CalcNewSpeeds()
|
|
{
|
|
// Decide what speed we would really like to start at. There are several possibilities:
|
|
// 1. If the top speed is already the requested speed, use the requested speed.
|
|
// 2. Else if this is a deceleration-only move and the previous move is not frozen, we may be able to increase the start speed,
|
|
// so use the requested speed again.
|
|
// 3. Else the start speed must be pinned, so use the lower of the maximum speed we can accelerate to and the requested speed.
|
|
|
|
// We may have to make multiple passes, because reducing one of the speeds may solve some problems but actually make matters worse on another axis.
|
|
bool limited;
|
|
do
|
|
{
|
|
// debugPrintf(" Pass, start=%f end=%f\n", targetStartSpeed, endSpeed);
|
|
limited = false;
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
const float thisMoveFraction = directionVector[drive];
|
|
const float nextMoveFraction = next->directionVector[drive];
|
|
const DriveMovement& thisMoveDm = ddm[drive];
|
|
const DriveMovement& nextMoveDm = next->ddm[drive];
|
|
if (thisMoveDm.moving || nextMoveDm.moving)
|
|
{
|
|
float thisMoveSpeed = endSpeed * thisMoveFraction;
|
|
float nextMoveSpeed = targetNextSpeed * nextMoveFraction;
|
|
float idealDeltaV = fabsf(thisMoveSpeed - nextMoveSpeed);
|
|
float maxDeltaV = reprap.GetPlatform()->ActualInstantDv(drive);
|
|
if (idealDeltaV > maxDeltaV)
|
|
{
|
|
// This drive can't change speed fast enough, so reduce the start and/or end speeds
|
|
// This algorithm sometimes converges very slowly, requiring many passes.
|
|
// To ensure it converges at all, and to speed up convergence, we over-adjust the speed to achieve an even lower deltaV.
|
|
maxDeltaV *= 0.8;
|
|
if ((thisMoveFraction >= 0.0) == (nextMoveFraction >= 0.0))
|
|
{
|
|
// Drives moving in the same direction, so we must reduce the faster one
|
|
if (fabsf(thisMoveSpeed) > fabsf(nextMoveSpeed))
|
|
{
|
|
endSpeed = fabsf((nextMoveSpeed) + maxDeltaV)/fabsf(thisMoveFraction);
|
|
}
|
|
else
|
|
{
|
|
targetNextSpeed = (fabsf(thisMoveSpeed) + maxDeltaV)/fabsf(nextMoveFraction);
|
|
}
|
|
}
|
|
else if (fabsf(thisMoveSpeed) * 2 < maxDeltaV)
|
|
{
|
|
targetNextSpeed = (maxDeltaV - fabsf(thisMoveSpeed))/fabsf(nextMoveFraction);
|
|
}
|
|
else if (fabsf(nextMoveSpeed) * 2 < maxDeltaV)
|
|
{
|
|
endSpeed = (maxDeltaV - fabsf(nextMoveSpeed))/fabsf(thisMoveFraction);
|
|
}
|
|
else
|
|
{
|
|
targetNextSpeed = maxDeltaV/(2 * fabsf(nextMoveFraction));
|
|
endSpeed = maxDeltaV/(2 * fabsf(thisMoveFraction));
|
|
}
|
|
limited = true;
|
|
// Most conflicts are between X and Y. So if we just did Y, start another pass immediately to save time.
|
|
if (drive == 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (limited);
|
|
}
|
|
|
|
// This is called by Move::CurrentMoveCompleted to update the live coordinates from the move that has just finished
|
|
bool DDA::FetchEndPosition(volatile int32_t ep[DRIVES], volatile float endCoords[AXES])
|
|
{
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
ep[drive] = endPoint[drive];
|
|
}
|
|
if (endCoordinatesValid)
|
|
{
|
|
for (size_t axis = 0; axis < AXES; ++axis)
|
|
{
|
|
endCoords[axis] = endCoordinates[axis];
|
|
}
|
|
}
|
|
return endCoordinatesValid;
|
|
}
|
|
|
|
void DDA::SetPositions(const float move[DRIVES])
|
|
{
|
|
reprap.GetMove()->EndPointToMachine(move, endPoint, DRIVES);
|
|
for (size_t axis = 0; axis < AXES; ++axis)
|
|
{
|
|
endCoordinates[axis] = move[axis];
|
|
}
|
|
endCoordinatesValid = true;
|
|
}
|
|
|
|
// Get a Cartesian end coordinate from this move
|
|
float DDA::GetEndCoordinate(size_t drive, bool disableDeltaMapping)
|
|
//pre(drive < AXES)
|
|
{
|
|
if (disableDeltaMapping)
|
|
{
|
|
return Move::MotorEndpointToPosition(endPoint[drive], drive);
|
|
}
|
|
else
|
|
{
|
|
if (drive < AXES && !endCoordinatesValid)
|
|
{
|
|
reprap.GetMove()->MachineToEndPoint(endPoint, endCoordinates, AXES);
|
|
endCoordinatesValid = true;
|
|
}
|
|
return endCoordinates[drive];
|
|
}
|
|
}
|
|
|
|
// Calculate the time needed for this move. Called instead of Prepare when we are in simulation mode.
|
|
float DDA::CalcTime() const
|
|
{
|
|
return (topSpeed - startSpeed)/acceleration // acceleration time
|
|
+ (totalDistance - accelDistance - decelDistance)/topSpeed // steady speed time
|
|
+ (topSpeed - endSpeed)/acceleration;
|
|
}
|
|
|
|
// Prepare this DDA for execution
|
|
void DDA::Prepare()
|
|
{
|
|
//debugPrintf("Prep\n");
|
|
//reprap.GetPlatform()->GetLine()->Flush();
|
|
|
|
PrepParams params;
|
|
params.decelStartDistance = totalDistance - decelDistance;
|
|
|
|
// Convert the accelerate/decelerate distances to times
|
|
float accelStopTime = (topSpeed - startSpeed)/acceleration;
|
|
float decelStartTime = accelStopTime + (params.decelStartDistance - accelDistance)/topSpeed;
|
|
float totalTime = decelStartTime + (topSpeed - endSpeed)/acceleration;
|
|
timeNeeded = (uint32_t)(totalTime * stepClockRate);
|
|
|
|
params.startSpeedTimesCdivA = (uint32_t)((startSpeed * stepClockRate)/acceleration);
|
|
params.topSpeedTimesCdivA = (uint32_t)((topSpeed * stepClockRate)/acceleration);
|
|
params.decelStartClocks = decelStartTime * stepClockRate;
|
|
params.topSpeedTimesCdivAPlusDecelStartClocks = params.topSpeedTimesCdivA + params.decelStartClocks;
|
|
params.accelClocksMinusAccelDistanceTimesCdivTopSpeed = (uint32_t)((accelStopTime - (accelDistance/topSpeed)) * stepClockRate);
|
|
params.compFactor = 1.0 - startSpeed/topSpeed;
|
|
|
|
firstStepTime = DriveMovement::NoStepTime;
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
DriveMovement& dm = ddm[drive];
|
|
if (dm.moving)
|
|
{
|
|
if (drive >= AXES)
|
|
{
|
|
dm.PrepareExtruder(*this, params, drive);
|
|
|
|
// Check for sensible values, print them if they look dubious
|
|
if (reprap.Debug(moduleDda)
|
|
&& ( dm.totalSteps > 1000000
|
|
|| dm.mp.cart.reverseStartStep < dm.mp.cart.decelStartStep
|
|
|| (dm.mp.cart.reverseStartStep <= dm.totalSteps
|
|
&& dm.mp.cart.fourMaxStepDistanceMinusTwoDistanceToStopTimesCsquaredDivA > (int64_t)(dm.mp.cart.twoCsquaredTimesMmPerStepDivA * dm.mp.cart.reverseStartStep))
|
|
)
|
|
)
|
|
{
|
|
DebugPrint();
|
|
reprap.GetPlatform()->GetLine()->Flush();
|
|
}
|
|
}
|
|
else if (isDeltaMovement)
|
|
{
|
|
dm.PrepareDeltaAxis(*this, params, drive);
|
|
|
|
// Check for sensible values, print them if they look dubious
|
|
if (reprap.Debug(moduleDda) && dm.totalSteps > 1000000)
|
|
{
|
|
DebugPrint();
|
|
reprap.GetPlatform()->GetLine()->Flush();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dm.PrepareCartesianAxis(*this, params, drive);
|
|
|
|
// Check for sensible values, print them if they look dubious
|
|
if (reprap.Debug(moduleDda) && dm.totalSteps > 1000000)
|
|
{
|
|
DebugPrint();
|
|
reprap.GetPlatform()->GetLine()->Flush();
|
|
}
|
|
}
|
|
|
|
// Prepare for the first step
|
|
dm.nextStep = 0;
|
|
dm.nextStepTime = 0;
|
|
dm.stepError = false; // clear any previous step error before we call CalcNextStep
|
|
uint32_t st = (isDeltaMovement && drive < AXES) ? dm.CalcNextStepTimeDelta(*this, drive) : dm.CalcNextStepTimeCartesian(drive);
|
|
if (st < firstStepTime)
|
|
{
|
|
firstStepTime = st;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reprap.Debug(moduleDda) && reprap.Debug(moduleMove)) // temp show the prepared DDA if debug enabled for both modules
|
|
{
|
|
DebugPrint();
|
|
reprap.GetPlatform()->GetLine()->Flush();
|
|
}
|
|
//debugPrintf("Done\n");
|
|
//reprap.GetPlatform()->GetLine()->Flush();
|
|
|
|
state = frozen; // must do this last so that the ISR doesn't start executing it before we have finished setting it up
|
|
}
|
|
|
|
// The remaining functions are speed-critical, so use full optimisation
|
|
#pragma GCC optimize ("O3")
|
|
|
|
// Start executing the move, returning true if Step() needs to be called immediately. Must be called with interrupts disabled, to avoid a race condition.
|
|
bool DDA::Start(uint32_t tim)
|
|
//pre(state == frozen)
|
|
{
|
|
moveStartTime = tim;
|
|
state = executing;
|
|
|
|
if (firstStepTime == DriveMovement::NoStepTime)
|
|
{
|
|
// No steps are pending. This should not happen!
|
|
state = completed;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < DRIVES; ++i)
|
|
{
|
|
DriveMovement& dm = ddm[i];
|
|
if (dm.moving)
|
|
{
|
|
reprap.GetPlatform()->SetDirection(i, dm.direction, true);
|
|
}
|
|
}
|
|
return reprap.GetPlatform()->ScheduleInterrupt(firstStepTime + moveStartTime);
|
|
}
|
|
}
|
|
|
|
extern uint32_t maxReps;
|
|
|
|
// This is called by the interrupt service routine to execute steps.
|
|
// It returns true if it needs to be called again with the DDA of the next move, otherwise false.
|
|
bool DDA::Step()
|
|
{
|
|
if (state != executing)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool repeat;
|
|
uint32_t numReps = 0;
|
|
do
|
|
{
|
|
++numReps;
|
|
if (numReps > maxReps)
|
|
{
|
|
maxReps = numReps;
|
|
}
|
|
uint32_t now = Platform::GetInterruptClocks() - moveStartTime; // how long since the move started
|
|
|
|
if ((endStopsToCheck & ZProbeActive) != 0) // if the Z probe is enabled in this move
|
|
{
|
|
// Check whether the Z probe has been triggered. On a delta at least, this must be done separately from endstop checks,
|
|
// because we have both a high endstop and a Z probe, and the Z motor is not the same thing as the Z axis.
|
|
switch (reprap.GetPlatform()->GetZProbeResult())
|
|
{
|
|
case lowHit:
|
|
MoveAborted(now); // set the state to completed and recalculate the endpoints
|
|
reprap.GetMove()->ZProbeTriggered(this);
|
|
break;
|
|
|
|
case lowNear:
|
|
ReduceHomingSpeed(reprap.GetPlatform()->ConfiguredInstantDv(Z_AXIS));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t nextInterruptTime = DriveMovement::NoStepTime;
|
|
if (state != completed)
|
|
{
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
DriveMovement& dm = ddm[drive];
|
|
if (dm.moving && !dm.stepError)
|
|
{
|
|
// Hit anything?
|
|
if ((endStopsToCheck & (1 << drive)) != 0)
|
|
{
|
|
switch(reprap.GetPlatform()->Stopped(drive))
|
|
{
|
|
case lowHit:
|
|
endStopsToCheck &= ~(1 << drive); // clear this check so that we can check for more
|
|
if (endStopsToCheck == 0) // if no more endstops to check
|
|
{
|
|
MoveAborted(now);
|
|
}
|
|
else
|
|
{
|
|
StopDrive(drive);
|
|
}
|
|
reprap.GetMove()->HitLowStop(drive, this);
|
|
break;
|
|
|
|
case highHit:
|
|
endStopsToCheck &= ~(1 << drive); // clear this check so that we can check for more
|
|
if (endStopsToCheck == 0) // if no more endstops to check
|
|
{
|
|
MoveAborted(now);
|
|
}
|
|
else
|
|
{
|
|
StopDrive(drive);
|
|
}
|
|
reprap.GetMove()->HitHighStop(drive, this);
|
|
break;
|
|
|
|
case lowNear:
|
|
// Only reduce homing speed if there are no more axes to be homed.
|
|
// This allows us to home X and Y simultaneously.
|
|
if (endStopsToCheck == (1 << drive))
|
|
{
|
|
ReduceHomingSpeed(reprap.GetPlatform()->ConfiguredInstantDv(drive));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t st0 = dm.nextStepTime;
|
|
if (now + minInterruptInterval >= st0)
|
|
{
|
|
reprap.GetPlatform()->StepHigh(drive);
|
|
uint32_t st1 = (isDeltaMovement && drive < AXES) ? dm.CalcNextStepTimeDelta(*this, drive) : dm.CalcNextStepTimeCartesian(drive);
|
|
if (st1 < nextInterruptTime)
|
|
{
|
|
nextInterruptTime = st1;
|
|
}
|
|
reprap.GetPlatform()->StepLow(drive);
|
|
//uint32_t t3 = Platform::GetInterruptClocks() - t2;
|
|
//if (t3 > maxCalcTime) maxCalcTime = t3;
|
|
//if (t3 < minCalcTime) minCalcTime = t3;
|
|
}
|
|
else if (st0 < nextInterruptTime)
|
|
{
|
|
nextInterruptTime = st0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextInterruptTime == DriveMovement::NoStepTime)
|
|
{
|
|
state = completed;
|
|
}
|
|
|
|
if (state == completed)
|
|
{
|
|
uint32_t finishTime = Platform::GetInterruptClocks();
|
|
Move *move = reprap.GetMove();
|
|
move->CurrentMoveCompleted(); // tell Move that the current move is complete
|
|
return move->StartNextMove(finishTime); // schedule the next move
|
|
}
|
|
repeat = reprap.GetPlatform()->ScheduleInterrupt(nextInterruptTime + moveStartTime);
|
|
} while (repeat);
|
|
return false;
|
|
}
|
|
|
|
// Stop a drive and re-calculate the corresponding endpoint
|
|
void DDA::StopDrive(size_t drive)
|
|
{
|
|
DriveMovement& dm = ddm[drive];
|
|
if (dm.moving)
|
|
{
|
|
int32_t stepsLeft = dm.totalSteps - dm.nextStep;
|
|
if (dm.direction)
|
|
{
|
|
endPoint[drive] -= stepsLeft; // we were going forwards
|
|
}
|
|
else
|
|
{
|
|
endPoint[drive] += stepsLeft; // we were going backwards
|
|
}
|
|
dm.moving = false;
|
|
endCoordinatesValid = false; // the XYZ position is no longer valid
|
|
}
|
|
}
|
|
|
|
// This is called when we abort a move because we have hit an endstop.
|
|
// It adjusts the end points of the current move to account for how far through the move we got.
|
|
void DDA::MoveAborted(uint32_t clocksFromStart)
|
|
{
|
|
for (size_t drive = 0; drive < AXES; ++drive)
|
|
{
|
|
StopDrive(drive);
|
|
}
|
|
state = completed;
|
|
}
|
|
|
|
// Reduce the speed of this move to the indicated speed.
|
|
// This is called from the ISR, so interrupts are disabled and nothing else can mess with us.
|
|
// As this is only called for homing moves and with very low speeds, we assume that we don't need acceleration or deceleration phases.
|
|
void DDA::ReduceHomingSpeed(float newSpeed)
|
|
{
|
|
if (newSpeed < topSpeed)
|
|
{
|
|
const float factor = topSpeed/newSpeed; // the factor by which we are reducing the speed
|
|
topSpeed = newSpeed;
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
DriveMovement& dm = ddm[drive];
|
|
if (dm.moving)
|
|
{
|
|
dm.ReduceSpeed(*this, factor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DDA::PrintIfHasStepError()
|
|
{
|
|
bool printed = false;
|
|
for (size_t drive = 0; drive < DRIVES; ++drive)
|
|
{
|
|
if (ddm[drive].stepError)
|
|
{
|
|
if (!printed)
|
|
{
|
|
DebugPrint();
|
|
printed = true;
|
|
}
|
|
ddm[drive].stepError = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Take a unit positive-hyperquadrant vector, and return the factor needed to obtain
|
|
// length of the vector as projected to touch box[].
|
|
float DDA::VectorBoxIntersection(const float v[], const float box[], size_t dimensions)
|
|
{
|
|
// Generate a vector length that is guaranteed to exceed the size of the box
|
|
float biggerThanBoxDiagonal = 2.0*Magnitude(box, dimensions);
|
|
float magnitude = biggerThanBoxDiagonal;
|
|
for (size_t d = 0; d < dimensions; d++)
|
|
{
|
|
if (biggerThanBoxDiagonal*v[d] > box[d])
|
|
{
|
|
float a = box[d]/v[d];
|
|
if (a < magnitude)
|
|
{
|
|
magnitude = a;
|
|
}
|
|
}
|
|
}
|
|
return magnitude;
|
|
}
|
|
|
|
// Normalise a vector with dim1 dimensions so that it is unit in the first dim2 dimensions, and also return its previous magnitude in dim2 dimensions
|
|
float DDA::Normalise(float v[], size_t dim1, size_t dim2)
|
|
{
|
|
float magnitude = Magnitude(v, dim2);
|
|
if (magnitude <= 0.0)
|
|
{
|
|
return 0.0;
|
|
}
|
|
Scale(v, 1.0/magnitude, dim1);
|
|
return magnitude;
|
|
}
|
|
|
|
// Return the magnitude of a vector
|
|
float DDA::Magnitude(const float v[], size_t dimensions)
|
|
{
|
|
float magnitude = 0.0;
|
|
for (size_t d = 0; d < dimensions; d++)
|
|
{
|
|
magnitude += v[d]*v[d];
|
|
}
|
|
magnitude = sqrtf(magnitude);
|
|
return magnitude;
|
|
}
|
|
|
|
// Multiply a vector by a scalar
|
|
void DDA::Scale(float v[], float scale, size_t dimensions)
|
|
{
|
|
for(size_t d = 0; d < dimensions; d++)
|
|
{
|
|
v[d] = scale*v[d];
|
|
}
|
|
}
|
|
|
|
// Move a vector into the positive hyperquadrant
|
|
void DDA::Absolute(float v[], size_t dimensions)
|
|
{
|
|
for(size_t d = 0; d < dimensions; d++)
|
|
{
|
|
v[d] = fabsf(v[d]);
|
|
}
|
|
}
|
|
|
|
// End
|