This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/Move.h
David Crocker b30e6ad4e1 Version 0.78p
Fixed bug that caused the cooling fan PWM frequency to be 165kHz. It is
now 25kHz.
Fan RPM is now included in the web interface status response (thanks
zpl).
Many more error messages are now reported to the web interface as well
as the USB interface.
Interrupt-driven network timer task to better cope with slow SD card
writes (thanks zpl)
Module SamNonDuePin is now used for all digital and PWM pin I/O.
2014-08-29 21:38:14 +01:00

678 lines
24 KiB
C++

/****************************************************************************************************
RepRapFirmware - Move
This is all the code to deal with movement and kinematics.
-----------------------------------------------------------------------------------------------------
Version 0.1
18 November 2012
Adrian Bowyer
RepRap Professional Ltd
http://reprappro.com
Licence: GPL
****************************************************************************************************/
#ifndef MOVE_H
#define MOVE_H
#define DDA_RING_LENGTH 5
#define LOOK_AHEAD_RING_LENGTH 30
#define LOOK_AHEAD 20 // Must be less than LOOK_AHEAD_RING_LENGTH
enum MovementProfile
{
moving = 0, // Ordinary trapezoidal-velocity-profile movement
noFlat = 1, // Triangular profile movement
change = 2 // To make this movement, the initial and/or final velocities must change
};
// The possible states of a movement in the look-ahead ring as the look-ahead is
// being done.
enum MovementState
{
unprocessed = 0,
vCosineSet = 1,
upPass = 2,
complete = 4,
released = 8
};
enum PointCoordinateSet
{
unset = 0,
xSet = 1,
ySet = 2,
zSet = 4
};
/**
* This class implements a look-ahead buffer for moves. It allows colinear
* moves not to decelerate between them, sets velocities at ends and beginnings
* for angled moves, and so on. Entries are joined in a doubly-linked list
* to form a ring buffer.
*/
class LookAhead
{
friend class Move;
friend class DDA;
protected:
LookAhead(Move* m, Platform* p, LookAhead* n);
void Init(long ep[], float requsestedFeedRate, float minSpeed, // Set up this move
float maxSpeed, float acceleration, EndstopChecks ce);
LookAhead* Next() const; // Next one in the ring
LookAhead* Previous() const; // Previous one in the ring
const long* MachineCoordinates() const; // Endpoints of a move in machine coordinates
float MachineToEndPoint(int8_t drive) const; // Convert a move endpoint to real mm coordinates
static float MachineToEndPoint(int8_t drive, long coord); // Convert any number to a real coordinate
static long EndPointToMachine(int8_t drive, float coord); // Convert real mm to a machine coordinate
float FeedRate() const; // How fast is the set speed for this move
float MinSpeed() const; // What is the slowest that this move can be
float MaxSpeed() const; // What is the fastest this move can be
float Acceleration() const; // What is the acceleration available for this move
float V() const; // The speed at the end of the move
void SetV(float vv); // Set the end speed
void SetFeedRate(float f); // Set the desired feedrate
int8_t Processed() const; // Where we are in the look-ahead prediction sequence
void SetProcessed(MovementState ms); // Set where we are the the look ahead processing
void SetDriveCoordinateAndZeroEndSpeed(float a, int8_t drive); // Force an end point and set its speed to stopped
EndstopChecks EndStopsToCheck() const; // Which endstops we are checking on this move
void Release(); // This move has been processed and executed
void PrintMove(); // Print diagnostics
private:
Move* move; // The main movement control class
Platform* platform; // The RepRap machine
LookAhead* next; // Next entry in the ring
LookAhead* previous; // Previous entry in the ring
long endPoint[DRIVES+1]; // Machine coordinates of the endpoint. Should never use the +1, but safety first
float Cosine(); // The angle between the previous move and this one
EndstopChecks endStopsToCheck; // Endstops to check for this move
float cosine; // Store for the cosine value - the function uses lazy evaluation
float v; // The feedrate we can actually do
float requestedFeedrate; // The requested feedrate
float minSpeed; // The slowest that this move may run at
float maxSpeed; // The fastest this move may run at
float acceleration; // The fastest acceleration allowed
volatile int8_t processed; // The stage in the look ahead process that this move is at.
};
/**
* This implements an integer space machine coordinates Bressenham-style DDA to step the drives.
* DDAs are stored in a linked list forming a ring buffer.
*/
class DDA
{
friend class Move;
friend class LookAhead;
protected:
DDA(Move* m, Platform* p, DDA* n);
MovementProfile Init(LookAhead* lookAhead, float& u, float& v); // Set up the DDA. Also used experimentally in look ahead.
void Start(); // Start executing the DDA. I.e. move the move.
void Step(); // Take one step of the DDA. Called by timed interrupt.
bool Active() const;
DDA* Next(); // Next entry in the ring
float InstantDv() const;
private:
MovementProfile AccelerationCalculation(float& u, float& v, // Compute acceleration profiles
MovementProfile result);
Move* move; // The main movement control class
Platform* platform; // The RepRap machine
DDA* next; // The next one in the ring
LookAhead* myLookAheadEntry; // The look-ahead entry corresponding to this DDA
long counter[DRIVES]; // Step counters
long delta[DRIVES]; // How far to move each drive
bool directions[DRIVES]; // Forwards or backwards?
long totalSteps; // Total number of steps for this move
long stepCount; // How many steps we have already taken
EndstopChecks endStopsToCheck; // Which endstops we are checking
float timeStep; // The current timestep (seconds)
float velocity; // The current velocity
long stopAStep; // The stepcount at which we stop accelerating
long startDStep; // The stepcount at which we start decelerating
float distance; // How long is the move in real distance
float acceleration; // The acceleration to use
float instantDv; // The lowest possible velocity
float feedRate;
volatile bool active; // Is the DDA running?
};
/**
* This is the master movement class. It controls all movement in the machine.
*/
class Move
{
friend class DDA;
public:
Move(Platform* p, GCodes* g);
void Init(); // Start me up
void Spin(); // Called in a tight loop to keep the class going
void Exit(); // Shut down
bool GetCurrentUserPosition(float m[]); // Return the current position in transformed coords if possible. Send false otherwise
// DANGER!!! the above function is mis-named because it has the side-effect of clearing currentFeedrate!!!
void LiveCoordinates(float m[]) const; // Gives the last point at the end of the last complete DDA transformed to user coords
void Interrupt(); // The hardware's (i.e. platform's) interrupt should call this.
void InterruptTime(); // Test function - not used
bool AllMovesAreFinished(); // Is the look-ahead ring empty? Stops more moves being added as well.
void ResumeMoving(); // Allow moves to be added after a call to AllMovesAreFinished()
void DoLookAhead(); // Run the look-ahead procedure
void HitLowStop(int8_t drive, // What to do when a low endstop is hit
LookAhead* la, DDA* hitDDA);
void HitHighStop(int8_t drive, // What to do when a high endstop is hit
LookAhead* la, DDA* hitDDA);
void SetPositions(float move[]); // Force the coordinates to be these
void SetFeedrate(float feedRate); // Sometimes we want to override the feedrate
void SetLiveCoordinates(float coords[]); // Force the live coordinates (see above) to be these
void SetXBedProbePoint(int index, float x); // Record the X coordinate of a probe point
void SetYBedProbePoint(int index, float y); // Record the Y coordinate of a probe point
void SetZBedProbePoint(int index, float z); // Record the Z coordinate of a probe point
float XBedProbePoint(int index) const; // Get the X coordinate of a probe point
float YBedProbePoint(int index) const; // Get the Y coordinate of a probe point
float ZBedProbePoint(int index)const ; // Get the Z coordinate of a probe point
int NumberOfProbePoints() const; // How many points to probe have been set? 0 if incomplete
int NumberOfXYProbePoints() const; // How many XY coordinates of probe points have been set (Zs may not have been probed yet)
bool AllProbeCoordinatesSet(int index) const; // XY, and Z all set for this one?
bool XYProbeCoordinatesSet(int index) const; // Just XY set for this one?
void SetZProbing(bool probing); // Set the Z probe live
void SetProbedBedEquation(StringRef& reply); // When we have a full set of probed points, work out the bed's equation
float SecondDegreeTransformZ(float x, float y) const; // Used for second degree bed equation
float GetLastProbedZ() const; // What was the Z when the probe last fired?
void SetAxisCompensation(int8_t axis, float tangent); // Set an axis-pair compensation angle
float AxisCompensation(int8_t axis) const; // The tangent value
void SetIdentityTransform(); // Cancel the bed equation; does not reset axis angle compensation
void Transform(float move[]) const; // Take a position and apply the bed and the axis-angle compensations
void InverseTransform(float move[]) const; // Go from a transformed point back to user coordinates
void Diagnostics(); // Report useful stuff
float ComputeCurrentCoordinate(int8_t drive,// Turn a DDA value back into a real world coordinate
LookAhead* la, DDA* runningDDA);
float Normalise(float v[], int8_t dimensions); // Normalise a vector to unit length
void Absolute(float v[], int8_t dimensions); // Put a vector in the positive hyperquadrant
float Magnitude(const float v[], int8_t dimensions); // Return the length of a vector
void Scale(float v[], float scale, // Multiply a vector by a scalar
int8_t dimensions);
float VectorBoxIntersection(const float v[], // Compute the length that a vector would have to have to...
const float box[], int8_t dimensions);// ...just touch the surface of a hyperbox.
private:
void BedTransform(float move[]) const; // Take a position and apply the bed compensations
bool GetCurrentMachinePosition(float m[]); // Get the current position in untransformed coords if possible. Return false otherwise
// DANGER!!! the above function is mis-named because it has the side-effect of clearing currentFeedrate!!!
void InverseBedTransform(float move[]) const; // Go from a bed-transformed point back to user coordinates
void AxisTransform(float move[]) const; // Take a position and apply the axis-angle compensations
void InverseAxisTransform(float move[]) const; // Go from an axis transformed point back to user coordinates
void BarycentricCoordinates(int8_t p0, int8_t p1, // Compute the barycentric coordinates of a point in a triangle
int8_t p2, float x, float y, float& l1, // (see http://en.wikipedia.org/wiki/Barycentric_coordinate_system).
float& l2, float& l3) const;
float TriangleZ(float x, float y) const; // Interpolate onto a triangular grid
bool DDARingAdd(LookAhead* lookAhead); // Add a processed look-ahead entry to the DDA ring
DDA* DDARingGet(); // Get the next DDA ring entry to be run
bool DDARingEmpty() const;
bool NoLiveMovement() const;
bool DDARingFull() const;
bool GetDDARingLock(); // Lock the ring so only this function may access it
void ReleaseDDARingLock(); // Release the DDA ring lock
bool LookAheadRingEmpty() const; // Anything there?
bool LookAheadRingFull() const; // Any more room?
bool LookAheadRingAdd(long ep[], float requestedFeedRate, // Add an entry to the look-ahead ring for processing
float minSpeed, float maxSpeed,
float acceleration, EndstopChecks ce);
LookAhead* LookAheadRingGet(); // Get the next entry from the look-ahead ring
Platform* platform; // The RepRap machine
GCodes* gCodes; // The G Codes processing class
// These implement the DDA ring
DDA* dda;
DDA* ddaRingAddPointer;
DDA* ddaRingGetPointer;
volatile bool ddaRingLocked;
// These implement the look-ahead ring
LookAhead* lookAheadRingAddPointer;
LookAhead* lookAheadRingGetPointer;
LookAhead* lastMove;
DDA* lookAheadDDA;
int lookAheadRingCount;
float lastTime; // The last time we were called (secs)
bool addNoMoreMoves; // If true, allow no more moves to be added to the look-ahead
bool active; // Are we live and running?
float currentFeedrate; // Err... the current feed rate...
volatile float liveCoordinates[DRIVES + 1]; // The last endpoint that the machine moved to
float nextMove[DRIVES + 1]; // The endpoint of the next move to processExtra entry is for feedrate
float normalisedDirectionVector[DRIVES]; // Used to hold a unit-length vector in the direction of motion
long nextMachineEndPoints[DRIVES+1]; // The next endpoint in machine coordinates (i.e. steps)
float xBedProbePoints[NUMBER_OF_PROBE_POINTS]; // The X coordinates of the points on the bed at which to probe
float yBedProbePoints[NUMBER_OF_PROBE_POINTS]; // The Y coordinates of the points on the bed at which to probe
float zBedProbePoints[NUMBER_OF_PROBE_POINTS]; // The Z coordinates of the points on the bed at which to probe
float baryXBedProbePoints[NUMBER_OF_PROBE_POINTS]; // The X coordinates of the triangle corner points
float baryYBedProbePoints[NUMBER_OF_PROBE_POINTS]; // The Y coordinates of the triangle corner points
float baryZBedProbePoints[NUMBER_OF_PROBE_POINTS]; // The Z coordinates of the triangle corner points
uint8_t probePointSet[NUMBER_OF_PROBE_POINTS]; // Has the XY of this point been set? Has the Z been probed?
float aX, aY, aC; // Bed plane explicit equation z' = z + aX*x + aY*y + aC
float tanXY, tanYZ, tanXZ; // Axis compensation - 90 degrees + angle gives angle between axes
bool identityBedTransform; // Is the bed transform in operation?
float xRectangle, yRectangle; // The side lengths of the rectangle used for second-degree bed compensation
volatile float lastZHit; // The last Z value hit by the probe
bool zProbing; // Are we bed probing as well as moving?
float longWait; // A long time for things that need to be done occasionally
};
//********************************************************************************************************
inline LookAhead* LookAhead::Next() const
{
return next;
}
inline LookAhead* LookAhead::Previous() const
{
return previous;
}
inline float LookAhead::MachineToEndPoint(int8_t drive) const
{
if(drive >= DRIVES)
{
platform->Message(HOST_MESSAGE, "MachineToEndPoint() called for feedrate!\n");
return 0.0;
}
return ((float)(endPoint[drive]))/platform->DriveStepsPerUnit(drive);
}
inline float LookAhead::FeedRate() const
{
return requestedFeedrate;
}
inline float LookAhead::MinSpeed() const
{
return minSpeed;
}
inline float LookAhead::MaxSpeed() const
{
return maxSpeed;
}
inline float LookAhead::Acceleration() const
{
return acceleration;
}
inline void LookAhead::SetV(float vv)
{
v = vv;
}
inline float LookAhead::V() const
{
return v;
}
inline void LookAhead::SetFeedRate(float f)
{
requestedFeedrate = f;
v = f;
}
inline int8_t LookAhead::Processed() const
{
return processed;
}
inline void LookAhead::SetProcessed(MovementState ms)
{
if(ms == unprocessed)
processed = unprocessed;
else
processed |= ms;
}
inline void LookAhead::Release()
{
processed = released;
}
inline EndstopChecks LookAhead::EndStopsToCheck() const
{
return endStopsToCheck;
}
// This is called from the step ISR. Any variables it modifies that are also read by code outside the ISR should be declared 'volatile'.
inline void LookAhead::SetDriveCoordinateAndZeroEndSpeed(float a, int8_t drive)
{
endPoint[drive] = EndPointToMachine(drive, a);
cosine = 2.0;
v = platform->InstantDv(platform->SlowestDrive());
}
inline const long* LookAhead::MachineCoordinates() const
{
return endPoint;
}
//inline int8_t LookAhead::GetMovementType()
//{
// return movementType;
//}
//******************************************************************************************************
inline bool DDA::Active() const
{
return active;
}
inline DDA* DDA::Next()
{
return next;
}
inline float DDA::InstantDv() const
{
return instantDv;
}
//***************************************************************************************
inline bool Move::DDARingEmpty() const
{
return ddaRingGetPointer == ddaRingAddPointer;
}
inline bool Move::NoLiveMovement() const
{
if(dda != NULL)
return false;
return DDARingEmpty();
}
// Leave a gap of 2 as the last Get result may still be being processed
inline bool Move::DDARingFull() const
{
return ddaRingAddPointer->Next()->Next() == ddaRingGetPointer;
}
inline bool Move::LookAheadRingEmpty() const
{
return lookAheadRingCount == 0;
}
// Leave a gap of 2 as the last Get result may still be being processed
inline bool Move::LookAheadRingFull() const
{
if(!(lookAheadRingAddPointer->Processed() & released))
return true;
return lookAheadRingAddPointer->Next()->Next() == lookAheadRingGetPointer; // probably not needed; just return the bool in the if above
}
inline bool Move::GetDDARingLock()
{
if(ddaRingLocked)
return false;
ddaRingLocked = true;
return true;
}
inline void Move::ReleaseDDARingLock()
{
ddaRingLocked = false;
}
inline void Move::LiveCoordinates(float m[]) const
{
for(int8_t drive = 0; drive <= DRIVES; drive++)
{
m[drive] = liveCoordinates[drive];
}
InverseTransform(m);
}
// These are the actual numbers that we want to be the coordinates, so
// don't transform them.
inline void Move::SetLiveCoordinates(float coords[])
{
for(int8_t drive = 0; drive <= DRIVES; drive++)
{
liveCoordinates[drive] = coords[drive];
}
}
// To wait until all the current moves in the buffers are
// complete, call this function repeatedly and wait for it to
// return true. Then do whatever you wanted to do after all
// current moves have finished. THEN CALL THE ResumeMoving() FUNCTION
// OTHERWISE NOTHING MORE WILL EVER HAPPEN.
inline bool Move::AllMovesAreFinished()
{
addNoMoreMoves = true;
return LookAheadRingEmpty() && NoLiveMovement();
}
inline void Move::ResumeMoving()
{
addNoMoreMoves = false;
}
inline void Move::SetXBedProbePoint(int index, float x)
{
if(index < 0 || index >= NUMBER_OF_PROBE_POINTS)
{
platform->Message(HOST_MESSAGE, "Z probe point X index out of range.\n");
return;
}
xBedProbePoints[index] = x;
probePointSet[index] |= xSet;
}
inline void Move::SetYBedProbePoint(int index, float y)
{
if(index < 0 || index >= NUMBER_OF_PROBE_POINTS)
{
platform->Message(HOST_MESSAGE, "Z probe point Y index out of range.\n");
return;
}
yBedProbePoints[index] = y;
probePointSet[index] |= ySet;
}
inline void Move::SetZBedProbePoint(int index, float z)
{
if(index < 0 || index >= NUMBER_OF_PROBE_POINTS)
{
platform->Message(HOST_MESSAGE, "Z probe point Z index out of range.\n");
return;
}
zBedProbePoints[index] = z;
probePointSet[index] |= zSet;
}
inline float Move::XBedProbePoint(int index) const
{
return xBedProbePoints[index];
}
inline float Move::YBedProbePoint(int index) const
{
return yBedProbePoints[index];
}
inline float Move::ZBedProbePoint(int index) const
{
return zBedProbePoints[index];
}
inline void Move::SetZProbing(bool probing)
{
zProbing = probing;
}
inline float Move::GetLastProbedZ() const
{
return lastZHit;
}
// Note that we don't set the tan values to 0 here. This means that the bed probe
// values will be a fraction of a millimeter out in X and Y, which, as the bed should
// be nearly flat (and the probe doesn't coincide with the nozzle anyway), won't matter.
// But it means that the tan values can be set for the machine
// at the start in the configuration file and be retained, without having to know and reset
// them after every Z probe of the bed.
inline void Move::SetIdentityTransform()
{
identityBedTransform = true;
}
inline bool Move::AllProbeCoordinatesSet(int index) const
{
return probePointSet[index] == (xSet | ySet | zSet);
}
inline bool Move::XYProbeCoordinatesSet(int index) const
{
return (probePointSet[index] & xSet) && (probePointSet[index] & ySet);
}
inline int Move::NumberOfProbePoints() const
{
for(int i = 0; i < NUMBER_OF_PROBE_POINTS; i++)
{
if(!AllProbeCoordinatesSet(i))
return i;
}
return NUMBER_OF_PROBE_POINTS;
}
inline int Move::NumberOfXYProbePoints() const
{
for(int i = 0; i < NUMBER_OF_PROBE_POINTS; i++)
{
if(!XYProbeCoordinatesSet(i))
return i;
}
return NUMBER_OF_PROBE_POINTS;
}
/*
* Transform to a ruled-surface quadratic. The corner points for interpolation are indexed:
*
* ^ [1] [2]
* |
* Y
* |
* | [0] [3]
* -----X---->
*
* The values of x and y are transformed to put them in the interval [0, 1].
*/
inline float Move::SecondDegreeTransformZ(float x, float y) const
{
x = (x - xBedProbePoints[0])*xRectangle;
y = (y - yBedProbePoints[0])*yRectangle;
return (1.0 - x)*(1.0 - y)*zBedProbePoints[0] + x*(1.0 - y)*zBedProbePoints[3] + (1.0 - x)*y*zBedProbePoints[1] + x*y*zBedProbePoints[2];
}
// This is called from the step ISR. Any variables it modifies that are also read by code outside the ISR must be declared 'volatile'.
inline void Move::HitLowStop(int8_t drive, LookAhead* la, DDA* hitDDA)
{
float hitPoint = platform->AxisMinimum(drive);
if(drive == Z_AXIS)
{
if(zProbing)
{
// Executing G32, so record the Z position at which we hit the end stop
if (gCodes->GetAxisIsHomed(drive))
{
// Z-axis has already been homed, so just record the height of the bed at this point
lastZHit = ComputeCurrentCoordinate(drive, la, hitDDA);
la->SetDriveCoordinateAndZeroEndSpeed(lastZHit, drive);
lastZHit = lastZHit - platform->ZProbeStopHeight();
}
else
{
// Z axis has not yet been homed, so treat this probe as a homing command
la->SetDriveCoordinateAndZeroEndSpeed(platform->ZProbeStopHeight(), drive);
gCodes->SetAxisIsHomed(drive);
lastZHit = hitPoint;
}
return;
} else
{
// Executing G30, so set the current Z height to the value at which the end stop is triggered
// Transform it first so that the height is correct in user coordinates
float xyzPoint[DRIVES + 1];
LiveCoordinates(xyzPoint);
xyzPoint[Z_AXIS] = lastZHit = platform->ZProbeStopHeight();
Transform(xyzPoint);
hitPoint = xyzPoint[Z_AXIS];
}
}
la->SetDriveCoordinateAndZeroEndSpeed(hitPoint, drive);
gCodes->SetAxisIsHomed(drive);
}
// This is called from the step ISR. Any variables it modifies that are also read by code outside the ISR must be declared 'volatile'.
inline void Move::HitHighStop(int8_t drive, LookAhead* la, DDA* hitDDA)
{
la->SetDriveCoordinateAndZeroEndSpeed(platform->AxisMaximum(drive), drive);
gCodes->SetAxisIsHomed(drive);
}
inline float Move::ComputeCurrentCoordinate(int8_t drive, LookAhead* la, DDA* runningDDA)
{
float previous = la->Previous()->MachineToEndPoint(drive);
if(runningDDA->totalSteps <= 0)
return previous;
return previous + (la->MachineToEndPoint(drive) - previous)*(float)runningDDA->stepCount/(float)runningDDA->totalSteps;
}
inline float Move::AxisCompensation(int8_t axis) const
{
switch(axis)
{
case X_AXIS:
return tanXY;
case Y_AXIS:
return tanYZ;
case Z_AXIS:
return tanXZ;
default:
platform->Message(HOST_MESSAGE, "Axis compensation requested for non-existent axis.");
}
return 0.0;
}
#endif