/* * Move.cpp * * Created on: 7 Dec 2014 * Author: David */ #include "RepRapFirmware.h" void DeltaParameters::Init() { deltaMode = false; diagonal = 0.0; radius = 0.0; printRadius = defaultPrintRadius; homedHeight = defaultDeltaHomedHeight; for (size_t axis = 0; axis < AXES; ++axis) { endstopAdjustments[axis] = 0.0; towerX[axis] = towerY[axis] = 0.0; } } void DeltaParameters::SetRadius(float r) { radius = r; const float cos30 = sqrtf(3.0)/2.0; const float sin30 = 0.5; towerX[A_AXIS] = -(r * cos30); towerX[B_AXIS] = r * cos30; towerX[C_AXIS] = 0.0; towerY[A_AXIS] = towerY[B_AXIS] = -(r * sin30); towerY[C_AXIS] = r; Recalc(); } void DeltaParameters::Recalc() { deltaMode = (radius > 0.0 && diagonal > radius); if (deltaMode) { homedCarriageHeight = homedHeight + sqrtf(fsquare(diagonal) - fsquare(radius)); Xbc = towerX[C_AXIS] - towerX[B_AXIS]; Xca = towerX[A_AXIS] - towerX[C_AXIS]; Xab = towerX[B_AXIS] - towerX[A_AXIS]; Ybc = towerY[C_AXIS] - towerY[B_AXIS]; Yca = towerY[A_AXIS] - towerY[C_AXIS]; Yab = towerY[B_AXIS] - towerY[A_AXIS]; coreFa = fsquare(towerX[A_AXIS]) + fsquare(towerY[A_AXIS]); coreFb = fsquare(towerX[B_AXIS]) + fsquare(towerY[B_AXIS]); coreFc = fsquare(towerX[C_AXIS]) + fsquare(towerY[C_AXIS]); Q = 2 * (Xca * Yab - Xab * Yca); Q2 = fsquare(Q); } } // Calculate the motor position for a single tower from a Cartesian coordinate float DeltaParameters::Transform(const float machinePos[AXES], size_t axis) const { return machinePos[Z_AXIS] + sqrt(fsquare(diagonal) - fsquare(machinePos[X_AXIS] - towerX[axis]) - fsquare(machinePos[Y_AXIS] - towerY[axis])); } void DeltaParameters::InverseTransform(float Ha, float Hb, float Hc, float machinePos[]) const { const float Fa = coreFa + fsquare(Ha); const float Fb = coreFb + fsquare(Hb); const float Fc = coreFc + fsquare(Hc); // debugPrintf("Ha=%f Hb=%f Hc=%f Fa=%f Fb=%f Fc=%f Xbc=%f Xca=%f Xab=%f Ybc=%f Yca=%f Yab=%f\n", // Ha, Hb, Hc, Fa, Fb, Fc, Xbc, Xca, Xab, Ybc, Yca, Yab); // Setup PQRSU such that x = -(S - uz)/P, y = (P - Rz)/Q const float P = (Xbc * Fa) + (Xca * Fb) + (Xab * Fc); const float S = (Ybc * Fa) + (Yca * Fb) + (Yab * Fc); const float R = 2 * ((Xbc * Ha) + (Xca * Hb) + (Xab * Hc)); const float U = 2 * ((Ybc * Ha) + (Yca * Hb) + (Yab * Hc)); // debugPrintf("P= %f R=%f S=%f U=%f Q=%f\n", P, R, S, U, Q); const float R2 = fsquare(R), U2 = fsquare(U); float A = U2 + R2 + Q2; float minusHalfB = S * U + P * R + Ha * Q2 + towerX[A_AXIS] * U * Q - towerY[A_AXIS] * R * Q; float C = fsquare(S + towerX[A_AXIS] * Q) + fsquare(P - towerY[A_AXIS] * Q) + (fsquare(Ha) - fsquare(diagonal)) * Q2; // debugPrintf("A=%f minusHalfB=%f C=%f\n", A, minusHalfB, C); float z = (minusHalfB - sqrtf(fsquare(minusHalfB) - A * C)) / A; machinePos[X_AXIS] = (U * z - S) / Q; machinePos[Y_AXIS] = (P - R * z) / Q; machinePos[Z_AXIS] = z; } void DeltaParameters::PrintParameters(StringRef& reply) { reply.printf("Endstops X%.2f Y%.2f Z%.2f, radius %.2f, height %.2f\n", endstopAdjustments[X_AXIS], endstopAdjustments[Y_AXIS], endstopAdjustments[Z_AXIS], radius, homedHeight); } Move::Move(Platform* p, GCodes* g) : currentDda(NULL) { active = false; // Build the DDA ring DDA *dda = new DDA(NULL); ddaRingGetPointer = ddaRingAddPointer = dda; for(size_t i = 1; i < DdaRingLength; i++) { DDA *oldDda = dda; dda = new DDA(dda); oldDda->SetPrevious(dda); } ddaRingAddPointer->SetNext(dda); dda->SetPrevious(ddaRingAddPointer); } void Move::Init() { // Reset Cartesian mode deltaParams.Init(); // Empty the ring ddaRingGetPointer = ddaRingAddPointer; DDA *dda = ddaRingAddPointer; do { dda->Init(); dda = dda->GetNext(); } while (dda != ddaRingAddPointer); currentDda = nullptr; addNoMoreMoves = false; // Clear the transforms SetIdentityTransform(); tanXY = tanYZ = tanXZ = 0.0; // Put the origin on the lookahead ring with default velocity in the previous position to the first one that will be used. // Do this by calling SetLiveCoordinates and SetPositions, so that the motor coordinates will be correct too even on a delta. float move[DRIVES]; for (size_t i = 0; i < DRIVES; i++) { move[i] = 0.0; reprap.GetPlatform()->SetDirection(i, FORWARDS, false); } SetLiveCoordinates(move); SetPositions(move); size_t slow = reprap.GetPlatform()->SlowestDrive(); currentFeedrate = reprap.GetPlatform()->HomeFeedRate(slow); // Set up default bed probe points. This is only a guess, because we don't know the bed size yet. for (size_t point = 0; point < NUMBER_OF_PROBE_POINTS; point++) { xBedProbePoints[point] = (0.3 + 0.6*(float)(point%2))*reprap.GetPlatform()->AxisMaximum(X_AXIS); yBedProbePoints[point] = (0.0 + 0.9*(float)(point/2))*reprap.GetPlatform()->AxisMaximum(Y_AXIS); zBedProbePoints[point] = 0.0; probePointSet[point] = unset; } xRectangle = 1.0/(0.8*reprap.GetPlatform()->AxisMaximum(X_AXIS)); yRectangle = xRectangle; longWait = reprap.GetPlatform()->Time(); idleCount = 0; simulating = false; simulationTime = 0.0; active = true; } void Move::Exit() { reprap.GetPlatform()->Message(BOTH_MESSAGE, "Move class exited.\n"); active = false; } void Move::Spin() { if (!active) { return; } if (idleCount < 1000) { ++idleCount; } // See if we can add another move to the ring if (!addNoMoreMoves && ddaRingAddPointer->GetState() == DDA::empty) { DDA *dda = ddaRingAddPointer; if (reprap.Debug(moduleMove)) { dda->PrintIfHasStepError(); } // In order to react faster to speed and extrusion rate changes, only add more moves if the total duration of // all un-frozen moves is less than 2 seconds, or the total duration of all but the first un-frozen move is // less than 0.5 seconds. float unPreparedTime = 0.0; float prevMoveTime = 0.0; for(;;) { dda = dda->GetPrevious(); if (dda->GetState() != DDA::provisional) { break; } unPreparedTime += prevMoveTime; prevMoveTime = dda->CalcTime(); } if (unPreparedTime < 0.5 || unPreparedTime + prevMoveTime < 2.0) { // If there's a G Code move available, add it to the DDA ring for processing. float nextMove[DRIVES + 1]; EndstopChecks endStopsToCheck; bool noDeltaMapping; FilePosition filePos; if (reprap.GetGCodes()->ReadMove(nextMove, endStopsToCheck, noDeltaMapping, filePos)) { currentFeedrate = nextMove[DRIVES]; // might be G1 with just an F field if (!noDeltaMapping || !IsDeltaMode()) { Transform(nextMove); } if (ddaRingAddPointer->Init(nextMove, endStopsToCheck, IsDeltaMode() && !noDeltaMapping, filePos)) { ddaRingAddPointer = ddaRingAddPointer->GetNext(); idleCount = 0; } } } } if (simulating) { if (idleCount > 10 && !DDARingEmpty()) { // No move added this time, so simulate executing one already in the queue DDA *dda = ddaRingGetPointer; simulationTime += dda->CalcTime(); liveCoordinatesValid = dda->FetchEndPosition(const_cast(liveEndPoints), const_cast(liveCoordinates)); dda->Release(); ddaRingGetPointer = ddaRingGetPointer->GetNext(); } } else { // See whether we need to kick off a move DDA *cdda = currentDda; // currentDda is volatile, so copy it if (cdda == nullptr) { // No DDA is executing, so start executing a new one if possible if (idleCount > 10) // better to have a few moves in the queue so that we can do lookahead { DDA *dda = ddaRingGetPointer; if (dda->GetState() == DDA::provisional) { dda->Prepare(); } cpu_irq_disable(); // must call StartNextMove and Interrupt with interrupts disabled if (StartNextMove(Platform::GetInterruptClocks())) // start the next move if none is executing already { Interrupt(); } cpu_irq_enable(); } } else { // See whether we need to prepare any moves int32_t preparedTime = 0; DDA::DDAState st; while ((st = cdda->GetState()) == DDA:: completed || st == DDA::executing || st == DDA::frozen) { preparedTime += cdda->GetTimeLeft(); cdda = cdda->GetNext(); } // If the number of prepared moves will execute in less than the minimum time, prepare another move while (st == DDA::provisional && preparedTime < (int32_t)(DDA::stepClockRate/8)) // prepare moves one eighth of a second ahead of when they will be needed { cdda->Prepare(); preparedTime += cdda->GetTimeLeft(); cdda = cdda->GetNext(); st = cdda->GetState(); } } } reprap.GetPlatform()->ClassReport(longWait); } // Pause the print as soon as we can. // Return the file position of the first queue move we are going to skip, or noFilePosition we we are not skipping any moves. // If we skipped any moves then we update 'positions' to the positions and feed rate expected for the next move, else we leave them alone. FilePosition Move::PausePrint(float positions[DRIVES+1]) { // Find a move we can pause after. // Ideally, we would adjust a move if necessary and possible so that we can pause after it, but for now we don't do that. // There are a few possibilities: // 1. There are no moves in the queue. // 2. There is a currently-executing move, and possibly some more in the queue. // 3. There are moves in the queue, but we haven't started executing them yet. Unlikely, but possible. const DDA *savedDdaRingAddPointer = ddaRingAddPointer; // First, see if there is a currently-executing move, and if so, whether we can safely pause at the end of it cpu_irq_disable(); DDA *dda = currentDda; if (dda != nullptr) { if (dda->CanPause()) { ddaRingAddPointer = dda->GetNext(); } else { // We can't safely pause after the currently-executing move because its end speed is too high so we may miss steps. // Search for the next move that we can safely stop after. dda = ddaRingGetPointer; while (dda != ddaRingAddPointer) { if (dda->CanPause()) { ddaRingAddPointer = dda->GetNext(); break; } dda = dda->GetNext(); } } } else { ddaRingAddPointer = ddaRingGetPointer; } cpu_irq_enable(); FilePosition fPos = noFilePosition; if (ddaRingAddPointer != savedDdaRingAddPointer) { // We are going to skip some moves. dda points to the last move we are going to print. for (size_t axis = 0; axis < AXES; ++axis) { positions[axis] = dda->GetEndCoordinate(axis, false); } positions[DRIVES] = dda->GetRequestedSpeed(); dda = ddaRingAddPointer; do { if (fPos == noFilePosition) { fPos = dda->GetFilePosition(); } dda->Release(); dda = dda->GetNext(); } while (dda != savedDdaRingAddPointer); } else { GetCurrentUserPosition(positions, false); } return fPos; } uint32_t maxStepTime=0, maxCalcTime=0, minCalcTime = 999, maxReps = 0; void Move::Diagnostics() { reprap.GetPlatform()->AppendMessage(BOTH_MESSAGE, "Move Diagnostics:\n"); reprap.GetPlatform()->AppendMessage(BOTH_MESSAGE, "MaxStepClocks: %u, minCalcClocks: %u, maxCalcClocks: %u, maxReps: %u\n", maxStepTime, minCalcTime, maxCalcTime, maxReps); reprap.GetPlatform()->AppendMessage(BOTH_MESSAGE, "Simulation time: %f\n", simulationTime); maxStepTime = maxCalcTime = maxReps = 0; minCalcTime = 999; #if 0 if(active) platform->Message(HOST_MESSAGE, " active\n"); else platform->Message(HOST_MESSAGE, " not active\n"); platform->Message(HOST_MESSAGE, " look ahead ring count: "); snprintf(scratchString, STRING_LENGTH, "%d\n", lookAheadRingCount); platform->Message(HOST_MESSAGE, scratchString); if(dda == NULL) platform->Message(HOST_MESSAGE, " dda: NULL\n"); else { if(dda->Active()) platform->Message(HOST_MESSAGE, " dda: active\n"); else platform->Message(HOST_MESSAGE, " dda: not active\n"); } if(ddaRingLocked) platform->Message(HOST_MESSAGE, " dda ring is locked\n"); else platform->Message(HOST_MESSAGE, " dda ring is not locked\n"); if(addNoMoreMoves) platform->Message(HOST_MESSAGE, " addNoMoreMoves is true\n\n"); else platform->Message(HOST_MESSAGE, " addNoMoreMoves is false\n\n"); #endif } // These are the actual numbers we want in the positions, so don't transform them. void Move::SetPositions(const float move[DRIVES]) { if (DDARingEmpty()) { ddaRingAddPointer->GetPrevious()->SetPositions(move); } else { reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "SetPositions called when DDA ring not empty\n"); } } void Move::EndPointToMachine(const float coords[], int32_t ep[], size_t numDrives) const { if (IsDeltaMode()) { DeltaTransform(coords, ep); for (size_t drive = AXES; drive < numDrives; ++drive) { ep[drive] = MotorEndPointToMachine(drive, coords[drive]); } } else { for (size_t drive = 0; drive < DRIVES; drive++) { ep[drive] = MotorEndPointToMachine(drive, coords[drive]); } } } void Move::SetFeedrate(float feedRate) { if (DDARingEmpty()) { DDA *lastMove = ddaRingAddPointer->GetPrevious(); currentFeedrate = feedRate; lastMove->SetFeedRate(feedRate); } else { reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "SetFeedrate called when DDA ring not empty\n"); } } // Returns steps from units (mm) for a particular drive int32_t Move::MotorEndPointToMachine(size_t drive, float coord) { return (int32_t)roundf(coord * reprap.GetPlatform()->DriveStepsPerUnit(drive)); } // Convert motor coordinates to machine coordinates // This is computationally expensive on a delta, so only call it when necessary, and never from the step ISR. void Move::MachineToEndPoint(const int32_t motorPos[], float machinePos[], size_t numDrives) const { if (IsDeltaMode()) { InverseDeltaTransform(motorPos, machinePos); // convert the axes for (size_t drive = AXES; drive < numDrives; ++drive) { machinePos[drive] = MotorEndpointToPosition(motorPos[drive], drive); } } else { for (size_t drive = 0; drive < numDrives; ++drive) { machinePos[drive] = MotorEndpointToPosition(motorPos[drive], drive); } } } // Do the Axis transform BEFORE the bed transform void Move::AxisTransform(float xyzPoint[AXES]) const { xyzPoint[X_AXIS] = xyzPoint[X_AXIS] + tanXY*xyzPoint[Y_AXIS] + tanXZ*xyzPoint[Z_AXIS]; xyzPoint[Y_AXIS] = xyzPoint[Y_AXIS] + tanYZ*xyzPoint[Z_AXIS]; } // Invert the Axis transform AFTER the bed transform void Move::InverseAxisTransform(float xyzPoint[AXES]) const { xyzPoint[Y_AXIS] = xyzPoint[Y_AXIS] - tanYZ*xyzPoint[Z_AXIS]; xyzPoint[X_AXIS] = xyzPoint[X_AXIS] - (tanXY*xyzPoint[Y_AXIS] + tanXZ*xyzPoint[Z_AXIS]); } void Move::Transform(float xyzPoint[AXES]) const { AxisTransform(xyzPoint); BedTransform(xyzPoint); } void Move::InverseTransform(float xyzPoint[AXES]) const { InverseBedTransform(xyzPoint); InverseAxisTransform(xyzPoint); } // Do the bed transform AFTER the axis transform void Move::BedTransform(float xyzPoint[AXES]) const { if (!identityBedTransform) { switch(NumberOfProbePoints()) { case 0: return; case 3: xyzPoint[Z_AXIS] = xyzPoint[Z_AXIS] + aX*xyzPoint[X_AXIS] + aY*xyzPoint[Y_AXIS] + aC; break; case 4: xyzPoint[Z_AXIS] = xyzPoint[Z_AXIS] + SecondDegreeTransformZ(xyzPoint[X_AXIS], xyzPoint[Y_AXIS]); break; case 5: xyzPoint[Z_AXIS] = xyzPoint[Z_AXIS] + TriangleZ(xyzPoint[X_AXIS], xyzPoint[Y_AXIS]); break; default: reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "BedTransform: wrong number of sample points."); } } } // Invert the bed transform BEFORE the axis transform void Move::InverseBedTransform(float xyzPoint[AXES]) const { if (!identityBedTransform) { switch(NumberOfProbePoints()) { case 0: return; case 3: xyzPoint[Z_AXIS] = xyzPoint[Z_AXIS] - (aX*xyzPoint[X_AXIS] + aY*xyzPoint[Y_AXIS] + aC); break; case 4: xyzPoint[Z_AXIS] = xyzPoint[Z_AXIS] - SecondDegreeTransformZ(xyzPoint[X_AXIS], xyzPoint[Y_AXIS]); break; case 5: xyzPoint[Z_AXIS] = xyzPoint[Z_AXIS] - TriangleZ(xyzPoint[X_AXIS], xyzPoint[Y_AXIS]); break; default: reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "InverseBedTransform: wrong number of sample points."); } } } // Convert motor step positions to Cartesian machine coordinates. // Used after homing and after individual motor moves. // Because this is computationally expensive, we only call it when necessary, and never from the step ISR. void Move::InverseDeltaTransform(const int32_t motorPos[AXES], float machinePos[AXES]) const { deltaParams.InverseTransform( MotorEndpointToPosition(motorPos[A_AXIS], A_AXIS), MotorEndpointToPosition(motorPos[B_AXIS], B_AXIS), MotorEndpointToPosition(motorPos[C_AXIS], C_AXIS), machinePos); // We don't do inverse transforms very often, so if debugging is enabled, print them if (reprap.Debug(moduleMove)) { debugPrintf("Inverse transformed %d %d %d to %f %f %f\n", motorPos[0], motorPos[1], motorPos[2], machinePos[0], machinePos[1], machinePos[2]); } } // Convert Cartesian coordinates to delta motor steps void Move::DeltaTransform(const float machinePos[AXES], int32_t motorPos[AXES]) const { for (size_t axis = 0; axis < AXES; ++axis) { motorPos[axis] = MotorEndPointToMachine(axis, deltaParams.Transform(machinePos, axis)); } if (reprap.Debug(moduleMove) && reprap.Debug(moduleDda)) { debugPrintf("Transformed %f %f %f to %d %d %d\n", machinePos[0], machinePos[1], machinePos[2], motorPos[0], motorPos[1], motorPos[2]); } } void Move::SetIdentityTransform() { identityBedTransform = true; } 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: reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "Axis compensation requested for non-existent axis.\n"); } return 0.0; } void Move::SetAxisCompensation(int8_t axis, float tangent) { switch(axis) { case X_AXIS: tanXY = tangent; break; case Y_AXIS: tanYZ = tangent; break; case Z_AXIS: tanXZ = tangent; break; default: reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "SetAxisCompensation: dud axis.\n"); } } void Move::BarycentricCoordinates(size_t p1, size_t p2, size_t p3, float x, float y, float& l1, float& l2, float& l3) const { float y23 = baryYBedProbePoints[p2] - baryYBedProbePoints[p3]; float x3 = x - baryXBedProbePoints[p3]; float x32 = baryXBedProbePoints[p3] - baryXBedProbePoints[p2]; float y3 = y - baryYBedProbePoints[p3]; float x13 = baryXBedProbePoints[p1] - baryXBedProbePoints[p3]; float y13 = baryYBedProbePoints[p1] - baryYBedProbePoints[p3]; float iDet = 1.0 / (y23 * x13 + x32 * y13); l1 = (y23 * x3 + x32 * y3) * iDet; l2 = (-y13 * x3 + x13 * y3) * iDet; l3 = 1.0 - l1 - l2; } /* * Interpolate on a triangular grid. The triangle corners are indexed: * * ^ [1] [2] * | * Y [4] * | * | [0] [3] * -----X----> * */ float Move::TriangleZ(float x, float y) const { for (size_t i = 0; i < 4; i++) { size_t j = (i + 1) % 4; float l1, l2, l3; BarycentricCoordinates(i, j, 4, x, y, l1, l2, l3); if (l1 > TRIANGLE_0 && l2 > TRIANGLE_0 && l3 > TRIANGLE_0) { return l1 * baryZBedProbePoints[i] + l2 * baryZBedProbePoints[j] + l3 * baryZBedProbePoints[4]; } } reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "Triangle interpolation: point outside all triangles!\n"); return 0.0; } // Calibrate or set the bed equation after probing. // sParam is the value of the S parameter in the G30 command that provoked this call. void Move::FinishedBedProbing(int sParam, int probePointIndex, StringRef& reply) { switch (sParam) { case 0: default: // Default action - set up a bed transform SetProbedBedEquation(reply); break; case 4: // On a delta, this calibrates the endstop adjustments and delta radius automatically after a 3 point probe. // Probe points 1,2,3 must be near the bases of the X, Y and Z towers in that order. Point 4 must be in the centre. if (IsDeltaMode() && NumberOfProbePoints() >= 4) { const float averageEdgeHeight = (zBedProbePoints[0] + zBedProbePoints[1] + zBedProbePoints[2])/3.0; const float averageEndstopOffset = (deltaParams.GetEndstopAdjustment(X_AXIS) + deltaParams.GetEndstopAdjustment(Y_AXIS) + deltaParams.GetEndstopAdjustment(Z_AXIS))/3.0; float probeRadiusSquared = 0.0; // Adjust the endstops to account for the differences in reading, while setting the average of the new values to zero for (size_t axis = 0; axis < 3; ++axis) { deltaParams.SetEndstopAdjustment(axis, deltaParams.GetEndstopAdjustment(axis) + averageEdgeHeight - averageEndstopOffset - zBedProbePoints[axis]); probeRadiusSquared += fsquare(xBedProbePoints[axis]) + fsquare(yBedProbePoints[axis]); } // Adjust the delta radius to make the bed appear flat const float edgeDistance = deltaParams.GetRadius() - sqrt(probeRadiusSquared/3.0); const float factor = edgeDistance/sqrt(fsquare(deltaParams.GetDiagonal()) - fsquare(edgeDistance)) - deltaParams.GetRadius()/sqrt(fsquare(deltaParams.GetDiagonal()) - fsquare(deltaParams.GetRadius())); const float diff = zBedProbePoints[3] - averageEdgeHeight; deltaParams.SetRadius(deltaParams.GetRadius() + diff/factor); // Adjust the homed height to account for the error at the centre and the change in average endstop correction deltaParams.SetHomedHeight(deltaParams.GetHomedHeight() + averageEndstopOffset - zBedProbePoints[3]); // Print the parameters so the user can see when they have converged deltaParams.PrintParameters(reply); } break; } } void Move::SetProbedBedEquation(StringRef& reply) { switch(NumberOfProbePoints()) { case 3: /* * Transform to a plane */ { float x10 = xBedProbePoints[1] - xBedProbePoints[0]; float y10 = yBedProbePoints[1] - yBedProbePoints[0]; float z10 = zBedProbePoints[1] - zBedProbePoints[0]; float x20 = xBedProbePoints[2] - xBedProbePoints[0]; float y20 = yBedProbePoints[2] - yBedProbePoints[0]; float z20 = zBedProbePoints[2] - zBedProbePoints[0]; float a = y10 * z20 - z10 * y20; float b = z10 * x20 - x10 * z20; float c = x10 * y20 - y10 * x20; float d = -(xBedProbePoints[1] * a + yBedProbePoints[1] * b + zBedProbePoints[1] * c); aX = -a / c; aY = -b / c; aC = -d / c; identityBedTransform = false; } break; case 4: /* * Transform to a ruled-surface quadratic. The corner points for interpolation are indexed: * * ^ [1] [2] * | * Y * | * | [0] [3] * -----X----> * * These are the scaling factors to apply to x and y coordinates to get them into the * unit interval [0, 1]. */ xRectangle = 1.0 / (xBedProbePoints[3] - xBedProbePoints[0]); yRectangle = 1.0 / (yBedProbePoints[1] - yBedProbePoints[0]); identityBedTransform = false; break; case 5: for(int8_t i = 0; i < 4; i++) { float x10 = xBedProbePoints[i] - xBedProbePoints[4]; float y10 = yBedProbePoints[i] - yBedProbePoints[4]; float z10 = zBedProbePoints[i] - zBedProbePoints[4]; baryXBedProbePoints[i] = xBedProbePoints[4] + 2.0 * x10; baryYBedProbePoints[i] = yBedProbePoints[4] + 2.0 * y10; baryZBedProbePoints[i] = zBedProbePoints[4] + 2.0 * z10; } baryXBedProbePoints[4] = xBedProbePoints[4]; baryYBedProbePoints[4] = yBedProbePoints[4]; baryZBedProbePoints[4] = zBedProbePoints[4]; identityBedTransform = false; break; default: reprap.GetPlatform()->Message(BOTH_ERROR_MESSAGE, "Attempt to set bed compensation before all probe points have been recorded."); } reply.copy("Bed equation fits points"); for (size_t point = 0; point < NumberOfProbePoints(); point++) { reply.catf(" [%.1f, %.1f, %.3f]", xBedProbePoints[point], yBedProbePoints[point], zBedProbePoints[point]); } reply.cat("\n"); } /* * 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]. */ 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 the function that's called by the timer interrupt to step the motors. void Move::Interrupt() { bool again = true; while (again && currentDda != nullptr) { again = currentDda->Step(); } } // This is called from the step ISR when the current move has been completed void Move::CurrentMoveCompleted() { // Save the current motor coordinates, and the machine Cartesian coordinates if known liveCoordinatesValid = currentDda->FetchEndPosition(const_cast(liveEndPoints), const_cast(liveCoordinates)); currentDda->Release(); currentDda = nullptr; ddaRingGetPointer = ddaRingGetPointer->GetNext(); } // Start the next move. Must be called with interrupts disabled, to avoid a race condition. bool Move::StartNextMove(uint32_t startTime) { if (ddaRingGetPointer->GetState() == DDA::frozen) { currentDda = ddaRingGetPointer; return currentDda->Start(startTime); } else { return false; } } // This is called from the step ISR. Any variables it modifies that are also read by code outside the ISR must be declared 'volatile'. void Move::HitLowStop(size_t drive, DDA* hitDDA) { if (drive < AXES && !IsDeltaMode()) // should always be true { float hitPoint; if (drive == Z_AXIS) { // Special case of doing a G1 S1 Z move on a Cartesian printer. This is not how we normally home the Z axis, we use G30 instead. // But I think it used to work, so let's not break it. hitPoint = reprap.GetPlatform()->ZProbeStopHeight(); } else { hitPoint = reprap.GetPlatform()->AxisMinimum(drive); } int32_t coord = MotorEndPointToMachine(drive, hitPoint); hitDDA->SetDriveCoordinate(coord, drive); reprap.GetGCodes()->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'. void Move::HitHighStop(size_t drive, DDA* hitDDA) { if (drive < AXES) // should always be true { float position = (IsDeltaMode()) ? deltaParams.GetHomedCarriageHeight(drive) // this is a delta printer, so the motor is at the homed carriage height for this drive : reprap.GetPlatform()->AxisMaximum(drive); // this is a Cartesian printer, so we're at the maximum for this axis hitDDA->SetDriveCoordinate(MotorEndPointToMachine(drive, position), drive); reprap.GetGCodes()->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'. // The move has already been aborted when this is called, so the endpoints in the DDA are the current motor positions. void Move::ZProbeTriggered(DDA* hitDDA) { // Currently, we don't need to do anything here } // Return the untransformed machine coordinates void Move::GetCurrentMachinePosition(float m[DRIVES + 1], bool disableDeltaMapping) const { DDA *lastQueuedMove = ddaRingAddPointer->GetPrevious(); for (size_t i = 0; i < DRIVES; i++) { if (i < AXES) { m[i] = lastQueuedMove->GetEndCoordinate(i, disableDeltaMapping); } else { m[i] = 0.0; } } m[DRIVES] = currentFeedrate; } /*static*/ float Move::MotorEndpointToPosition(int32_t endpoint, size_t drive) { return ((float)(endpoint))/reprap.GetPlatform()->DriveStepsPerUnit(drive); } // Return the transformed machine coordinates void Move::GetCurrentUserPosition(float m[DRIVES + 1], bool disableDeltaMapping) const { GetCurrentMachinePosition(m, disableDeltaMapping); if (!disableDeltaMapping) { InverseTransform(m); } } // Return the current live XYZ and extruder coordinates // Interrupts are assumed enabled on entry, so do not call this from an ISR void Move::LiveCoordinates(float m[DRIVES]) { // The live coordinates and live endpoints are modified by the ISR, to be careful to get a self-consistent set of them cpu_irq_disable(); if (liveCoordinatesValid) { // All coordinates are valid, so copy them across memcpy(m, const_cast(liveCoordinates), sizeof(m[0]) * DRIVES); cpu_irq_enable(); } else { // Only the extruder coordinates are valid, so we need to convert the motor endpoints to coordinates memcpy(m + AXES, const_cast(liveCoordinates + AXES), sizeof(m[0]) * (DRIVES - AXES)); int32_t tempEndPoints[AXES]; memcpy(tempEndPoints, const_cast(liveEndPoints), sizeof(tempEndPoints)); cpu_irq_enable(); MachineToEndPoint(tempEndPoints, m, AXES); // this is slow, so do it with interrupts enabled // If the ISR has not updated the endpoints, store the live coordinates back so that we don't need to do it again cpu_irq_disable(); if (memcmp(tempEndPoints, const_cast(liveEndPoints), sizeof(tempEndPoints)) == 0) { memcpy(const_cast(liveCoordinates), m, sizeof(m[0]) * AXES); liveCoordinatesValid = true; } cpu_irq_enable(); } InverseTransform(m); } // These are the actual numbers that we want to be the coordinates, so don't transform them. // Interrupts are assumed enabled on entry, so do not call this from an ISR void Move::SetLiveCoordinates(const float coords[DRIVES]) { cpu_irq_disable(); for(size_t drive = 0; drive < DRIVES; drive++) { liveCoordinates[drive] = coords[drive]; } liveCoordinatesValid = true; EndPointToMachine(coords, const_cast(liveEndPoints), AXES); cpu_irq_enable(); } void Move::SetXBedProbePoint(int index, float x) { if(index < 0 || index >= NUMBER_OF_PROBE_POINTS) { reprap.GetPlatform()->Message(BOTH_MESSAGE, "Z probe point X index out of range.\n"); return; } xBedProbePoints[index] = x; probePointSet[index] |= xSet; } void Move::SetYBedProbePoint(int index, float y) { if(index < 0 || index >= NUMBER_OF_PROBE_POINTS) { reprap.GetPlatform()->Message(BOTH_MESSAGE, "Z probe point Y index out of range.\n"); return; } yBedProbePoints[index] = y; probePointSet[index] |= ySet; } void Move::SetZBedProbePoint(int index, float z) { if(index < 0 || index >= NUMBER_OF_PROBE_POINTS) { reprap.GetPlatform()->Message(BOTH_MESSAGE, "Z probe point Z index out of range.\n"); return; } zBedProbePoints[index] = z; probePointSet[index] |= zSet; } float Move::XBedProbePoint(int index) const { return xBedProbePoints[index]; } float Move::YBedProbePoint(int index) const { return yBedProbePoints[index]; } float Move::ZBedProbePoint(int index) const { return zBedProbePoints[index]; } bool Move::AllProbeCoordinatesSet(int index) const { return probePointSet[index] == (xSet | ySet | zSet); } bool Move::XYProbeCoordinatesSet(int index) const { return (probePointSet[index] & xSet) && (probePointSet[index] & ySet); } int Move::NumberOfProbePoints() const { for(int i = 0; i < NUMBER_OF_PROBE_POINTS; i++) { if(!AllProbeCoordinatesSet(i)) { return i; } } return NUMBER_OF_PROBE_POINTS; } int Move::NumberOfXYProbePoints() const { for(int i = 0; i < NUMBER_OF_PROBE_POINTS; i++) { if(!XYProbeCoordinatesSet(i)) { return i; } } return NUMBER_OF_PROBE_POINTS; } // Enter or leave simulation mode void Move::Simulate(bool sim) { simulating = sim; if (sim) { simulationTime = 0.0; } } // For debugging void Move::PrintCurrentDda() const { if (currentDda != nullptr) { currentDda->DebugPrint(); reprap.GetPlatform()->GetLine()->Flush(); } } // End