diff --git a/src/CAN/CanMotion.cpp b/src/CAN/CanMotion.cpp index 09ea0ff77e..6d311d4146 100644 --- a/src/CAN/CanMotion.cpp +++ b/src/CAN/CanMotion.cpp @@ -178,7 +178,7 @@ void CanMotion::AddExtruderMovement(const PrepParams& params, DriverId canDriver #endif // This is called by DDA::Prepare when all DMs for CAN drives have been processed. Return the calculated move time in steps, or 0 if there are no CAN moves -uint32_t CanMotion::FinishMovement(uint32_t moveStartTime) noexcept +uint32_t CanMotion::FinishMovement(uint32_t moveStartTime, bool simulating) noexcept { boardsActiveInLastMove.ClearAll(); CanMessageBuffer *buf = movementBufferList; @@ -207,7 +207,14 @@ uint32_t CanMotion::FinishMovement(uint32_t moveStartTime) noexcept buf->dataLength = buf->msg.moveLinear.GetActualDataLength(); #endif CanMessageBuffer * const nextBuffer = buf->next; // must get this before sending the buffer, because sending the buffer releases it - CanInterface::SendMotion(buf); // queues the buffer for sending and frees it when done + if (simulating) + { + CanMessageBuffer::Free(buf); + } + else + { + CanInterface::SendMotion(buf); // queues the buffer for sending and frees it when done + } #if 0 ++numMotionMessagesSentLast; #endif diff --git a/src/CAN/CanMotion.h b/src/CAN/CanMotion.h index e14aadcf3b..7b6b0d3275 100644 --- a/src/CAN/CanMotion.h +++ b/src/CAN/CanMotion.h @@ -24,7 +24,7 @@ namespace CanMotion #else void AddMovement(const PrepParams& params, DriverId canDriver, int32_t steps, bool usePressureAdvance = false) noexcept; #endif - uint32_t FinishMovement(uint32_t moveStartTime) noexcept; + uint32_t FinishMovement(uint32_t moveStartTime, bool simulating) noexcept; bool CanPrepareMove() noexcept; CanMessageBuffer *GetUrgentMessage() noexcept; diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp index ee42846eaf..c98779ec49 100644 --- a/src/GCodes/GCodes.cpp +++ b/src/GCodes/GCodes.cpp @@ -276,7 +276,7 @@ void GCodes::Reset() noexcept } triggersPending.Clear(); - simulationMode = 0; + simulationMode = SimulationMode::off; exitSimulationWhenFileComplete = updateFileWhenSimulationComplete = false; simulationTime = 0.0; lastDuration = 0; @@ -990,7 +990,7 @@ void GCodes::DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg, uint1 pauseRestorePoint.fanSpeed = lastDefaultFanSpeed; #if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE - if (simulationMode == 0) + if (!IsSimulating()) { SaveResumeInfo(false); // create the resume file so that we can resume after power down } @@ -1190,7 +1190,7 @@ bool GCodes::DoEmergencyPause() noexcept // Try to pause the current SD card print, returning true if successful, false if needs to be called again bool GCodes::LowVoltagePause() noexcept { - if (simulationMode != 0) + if (IsSimulating()) { return true; // ignore the low voltage indication } @@ -2099,7 +2099,7 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, bool isCoordinated, const char *& e // As soon as we set segmentsLeft nonzero, the Move process will assume that the move is ready to take, so this must be the last thing we do. const Kinematics& kin = reprap.GetMove().GetKinematics(); const SegmentationType st = kin.GetSegmentationType(); - if (st.useSegmentation && simulationMode != 1 && (moveState.hasPositiveExtrusion || moveState.isCoordinated || st.useG0Segmentation)) + if (st.useSegmentation && simulationMode != SimulationMode::normal && (moveState.hasPositiveExtrusion || moveState.isCoordinated || st.useG0Segmentation)) { // This kinematics approximates linear motion by means of segmentation float moveLengthSquared = fsquare(moveState.currentUserPosition[X_AXIS] - initialUserPosition[X_AXIS]) + fsquare(moveState.currentUserPosition[Y_AXIS] - initialUserPosition[Y_AXIS]); @@ -3294,7 +3294,7 @@ void GCodes::StartPrinting(bool fromStart) noexcept lastFilamentError = FilamentSensorStatus::ok; reprap.GetPrintMonitor().StartedPrint(); platform.MessageF(LogWarn, - (simulationMode == 0) ? "Started printing file %s\n" : "Started simulating printing file %s\n", + (IsSimulating()) ? "Started simulating printing file %s\n" : "Started printing file %s\n", reprap.GetPrintMonitor().GetPrintingFilename()); if (fromStart) { @@ -3338,7 +3338,7 @@ GCodeResult GCodes::DoDwell(GCodeBuffer& gb) THROWS(GCodeException) } #endif - if ( simulationMode != 0 // if we are simulating then simulate the G4... + if ( IsSimulating() // if we are simulating then simulate the G4... && &gb != daemonGCode // ...unless it comes from the daemon... && &gb != triggerGCode // ...or a trigger... && (&gb == fileGCode || !exitSimulationWhenFileComplete) // ...or we are simulating a file and this command doesn't come from the file @@ -3410,7 +3410,7 @@ GCodeResult GCodes::SetOrReportOffsets(GCodeBuffer &gb, const StringRef& reply, if (gb.Seen('R')) { settingTemps = true; - if (simulationMode == 0) + if (!IsSimulating()) { float standby[MaxHeaters]; gb.GetFloatArray(standby, hCount, true); @@ -3423,7 +3423,7 @@ GCodeResult GCodes::SetOrReportOffsets(GCodeBuffer &gb, const StringRef& reply, if (gb.Seen('S')) { settingTemps = true; - if (simulationMode == 0) + if (!IsSimulating()) { float activeTemps[MaxHeaters]; gb.GetFloatArray(activeTemps, hCount, true); @@ -3444,7 +3444,7 @@ GCodeResult GCodes::SetOrReportOffsets(GCodeBuffer &gb, const StringRef& reply, if (gb.Seen('F')) { settingOther = true; - if (simulationMode == 0) + if (!IsSimulating()) { tool->SetSpindleRpm(gb.GetUIValue()); } @@ -4163,7 +4163,7 @@ void GCodes::StopPrint(StopPrintReason reason) noexcept #endif exitSimulationWhenFileComplete = false; - simulationMode = 0; // do this after we append the simulation info to the file so that DWC doesn't try to reload the file info too soon + simulationMode = SimulationMode::off; // do this after we append the simulation info to the file so that DWC doesn't try to reload the file info too soon reprap.GetMove().Simulate(simulationMode); EndSimulation(nullptr); @@ -4225,7 +4225,7 @@ void GCodes::StopPrint(StopPrintReason reason) noexcept (reason == StopPrintReason::normalCompletion) ? "Finished" : "Cancelled", printingFilename, printMinutes/60u, printMinutes % 60u); #if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE - if (reason == StopPrintReason::normalCompletion && simulationMode == 0) + if (reason == StopPrintReason::normalCompletion && !IsSimulating()) { platform.DeleteSysFile(RESUME_AFTER_POWER_FAIL_G); } @@ -4458,7 +4458,7 @@ bool GCodes::AllAxesAreHomed() const noexcept // Tell us that the axis is now homed void GCodes::SetAxisIsHomed(unsigned int axis) noexcept { - if (simulationMode == 0) + if (!IsSimulating()) { axesHomed.SetBit(axis); axesVirtuallyHomed = axesHomed; @@ -4469,7 +4469,7 @@ void GCodes::SetAxisIsHomed(unsigned int axis) noexcept // Tell us that the axis is not homed void GCodes::SetAxisNotHomed(unsigned int axis) noexcept { - if (simulationMode == 0) + if (!IsSimulating()) { axesHomed.ClearBit(axis); axesVirtuallyHomed = axesHomed; @@ -4484,7 +4484,7 @@ void GCodes::SetAxisNotHomed(unsigned int axis) noexcept // Flag all axes as not homed void GCodes::SetAllAxesNotHomed() noexcept { - if (simulationMode == 0) + if (!IsSimulating()) { axesHomed.Clear(); axesVirtuallyHomed = axesHomed; @@ -4724,7 +4724,7 @@ OutputBuffer *GCodes::GenerateJsonStatusResponse(int type, int seq, ResponseSour void GCodes::StartToolChange(GCodeBuffer& gb, int toolNum, uint8_t param) noexcept { newToolNumber = toolNum; - toolChangeParam = (simulationMode != 0) ? 0 : param; + toolChangeParam = (IsSimulating()) ? 0 : param; gb.SetState(GCodeState::toolChange0); } diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h index dd3144a7df..21796b31ae 100644 --- a/src/GCodes/GCodes.h +++ b/src/GCodes/GCodes.h @@ -102,6 +102,14 @@ struct M675Settings float minDistance; // the position we reached when probing towards minimum }; +enum class SimulationMode : uint8_t +{ off = 0, // not simulating + debug, // simulating step generation + normal, // not generating steps, just timing + partial, // generating DDAs but doing nothing with them + highest = partial +}; + class LinuxInterface; // The GCode interpreter @@ -162,7 +170,7 @@ class GCodes bool IsReallyPrinting() const noexcept; // Return true if we are printing from SD card and not pausing, paused or resuming bool IsReallyPrintingOrResuming() const noexcept; - bool IsSimulating() const noexcept { return simulationMode != 0; } + bool IsSimulating() const noexcept { return simulationMode != SimulationMode::off; } bool IsDoingToolChange() const noexcept { return doingToolChange; } bool IsHeatingUp() const noexcept; // Return true if the SD card print is waiting for a heater to reach temperature bool IsRunningConfigFile() const noexcept { return runningConfigFile; } @@ -466,7 +474,7 @@ class GCodes GCodeResult ReceiveI2c(GCodeBuffer& gb, const StringRef &reply) THROWS(GCodeException); // Handle M261 #if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE || HAS_EMBEDDED_FILES GCodeResult SimulateFile(GCodeBuffer& gb, const StringRef &reply, const StringRef& file, bool updateFile) THROWS(GCodeException); // Handle M37 to simulate a whole file - GCodeResult ChangeSimulationMode(GCodeBuffer& gb, const StringRef &reply, uint32_t newSimulationMode) THROWS(GCodeException); // Handle M37 to change the simulation mode + GCodeResult ChangeSimulationMode(GCodeBuffer& gb, const StringRef &reply, SimulationMode newSimMode) THROWS(GCodeException); // Handle M37 to change the simulation mode #endif GCodeResult WaitForPin(GCodeBuffer& gb, const StringRef &reply) THROWS(GCodeException); // Handle M577 @@ -639,7 +647,7 @@ class GCodes // Simulation and print time float simulationTime; // Accumulated simulation time uint32_t lastDuration; // Time or simulated time of the last successful print or simulation, in seconds - uint8_t simulationMode; // 0 = not simulating, 1 = simulating, >1 are simulation modes for debugging + SimulationMode simulationMode; // see description of enum SimulationMode bool exitSimulationWhenFileComplete; // true if simulating a file bool updateFileWhenSimulationComplete; // true if simulated time should be appended to the file diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index 59b14e66db..7a376fff7f 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -131,7 +131,7 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx { GCodeResult result = GCodeResult::ok; const int code = gb.GetCommandNumber(); - if (simulationMode != 0 && code > 4 && code != 10 && code != 11 && code != 20 && code != 21 && (code < 53 || code > 59) && (code < 90 || code > 92)) + if (IsSimulating() && code > 4 && code != 10 && code != 11 && code != 20 && code != 21 && (code < 53 || code > 59) && (code < 90 || code > 92)) { HandleReply(gb, result, ""); return true; // we only simulate some gcodes @@ -236,7 +236,7 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx if (modifyingTool) { - if (simulationMode != 0) + if (IsSimulating()) { break; } @@ -452,7 +452,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx { const int code = gb.GetCommandNumber(); - if ( simulationMode != 0 + if ( IsSimulating() && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 109 && code != 111 && code != 112 && code != 122 && code != 200 && code != 204 && code != 207 && code != 408 && code != 409 && code != 486 && code != 999) @@ -1155,15 +1155,15 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx else { uint32_t newSimulationMode; - gb.TryGetUIValue('S', newSimulationMode, seen); + gb.TryGetLimitedUIValue('S', newSimulationMode, seen, (uint32_t)SimulationMode::highest + 1); if (seen) { - result = ChangeSimulationMode(gb, reply, newSimulationMode); + result = ChangeSimulationMode(gb, reply, (SimulationMode)newSimulationMode); } else { reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec", - (simulationMode != 0) ? "on" : "off", (double)reprap.GetMove().GetSimulationTime(), (double)simulationTime); + (IsSimulating()) ? "on" : "off", (double)reprap.GetMove().GetSimulationTime(), (double)simulationTime); } } } @@ -1541,7 +1541,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx // Set the heater temperatures for that tool. We set the standby temperatures as well as the active ones, // because any slicer that uses M109 doesn't understand that there are separate active and standby temperatures. - if (simulationMode == 0) + if (!IsSimulating()) { SetToolHeaters(applicableTool.Ptr(), temperature, true); // this may throw } @@ -1556,7 +1556,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx } newToolNumber = applicableTool->Number(); - toolChangeParam = (simulationMode != 0) ? 0 : DefaultToolChangeParam; + toolChangeParam = (IsSimulating()) ? 0 : DefaultToolChangeParam; gb.SetState(GCodeState::m109ToolChange0); result = GCodeResult::ok; } @@ -1566,15 +1566,15 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx { // Even though the tool is selected, we may have turned it off e.g. when upgrading the WiFi firmware or following a heater fault that has been cleared. // So make sure the tool heaters are on. - reprap.SelectTool(applicableTool->Number(), simulationMode != 0); + reprap.SelectTool(applicableTool->Number(), IsSimulating()); } else { // If we already have an active tool and we are setting temperatures for a different tool, set that tool's heaters to standby in case it is off - reprap.StandbyTool(applicableTool->Number(), simulationMode != 0); + reprap.StandbyTool(applicableTool->Number(), IsSimulating()); } - if (code == 109 && simulationMode == 0) + if (code == 109 && !IsSimulating()) { gb.SetState(GCodeState::m109WaitForTemperature); result = GCodeResult::ok; @@ -4689,7 +4689,7 @@ bool GCodes::HandleTcode(GCodeBuffer& gb, const StringRef& reply) { // Even though the tool is selected, we may have turned it off e.g. when upgrading the WiFi firmware or following a heater fault that has been cleared. // So make sure the tool heaters are on. - reprap.SelectTool(toolNum, simulationMode != 0); + reprap.SelectTool(toolNum, IsSimulating()); } } } diff --git a/src/GCodes/GCodes3.cpp b/src/GCodes/GCodes3.cpp index 528a77dce9..ba7843f674 100644 --- a/src/GCodes/GCodes3.cpp +++ b/src/GCodes/GCodes3.cpp @@ -95,7 +95,7 @@ GCodeResult GCodes::SetPositions(GCodeBuffer& gb) THROWS(GCodeException) ToolOffsetInverseTransform(moveState.coords, moveState.currentUserPosition); // make sure the limits are reflected in the user position } reprap.GetMove().SetNewPosition(moveState.coords, true); - if (simulationMode == 0) + if (!IsSimulating()) { axesHomed |= reprap.GetMove().GetKinematics().AxesAssumedHomed(axesIncluded); axesVirtuallyHomed = axesHomed; @@ -424,7 +424,7 @@ GCodeResult GCodes::SimulateFile(GCodeBuffer& gb, const StringRef &reply, const QueueFileToPrint(file.c_str(), reply)) # endif { - if (simulationMode == 0) + if (!IsSimulating()) { axesVirtuallyHomed = AxesBitmap::MakeLowestNBits(numVisibleAxes); // pretend all axes are homed SavePosition(simulationRestorePoint, gb); @@ -437,7 +437,7 @@ GCodeResult GCodes::SimulateFile(GCodeBuffer& gb, const StringRef &reply, const # else updateFileWhenSimulationComplete = updateFile; # endif - simulationMode = 1; + simulationMode = SimulationMode::normal; reprap.GetMove().Simulate(simulationMode); reprap.GetPrintMonitor().StartingPrint(file.c_str()); StartPrinting(true); @@ -449,22 +449,22 @@ GCodeResult GCodes::SimulateFile(GCodeBuffer& gb, const StringRef &reply, const } // Handle M37 to change the simulation mode -GCodeResult GCodes::ChangeSimulationMode(GCodeBuffer& gb, const StringRef &reply, uint32_t newSimulationMode) +GCodeResult GCodes::ChangeSimulationMode(GCodeBuffer& gb, const StringRef &reply, SimulationMode newSimMode) THROWS(GCodeException) { - if (newSimulationMode != simulationMode) + if (newSimMode != simulationMode) { if (!LockMovementAndWaitForStandstill(gb)) { return GCodeResult::notFinished; } - if (newSimulationMode == 0) + if (newSimMode == SimulationMode::off) { EndSimulation(&gb); } else { - if (simulationMode == 0) + if (!IsSimulating()) { // Starting a new simulation, so save the current position axesVirtuallyHomed = AxesBitmap::MakeLowestNBits(numVisibleAxes); // pretend all axes are homed @@ -473,8 +473,8 @@ GCodeResult GCodes::ChangeSimulationMode(GCodeBuffer& gb, const StringRef &reply simulationTime = 0.0; } exitSimulationWhenFileComplete = updateFileWhenSimulationComplete = false; - simulationMode = (uint8_t)newSimulationMode; - reprap.GetMove().Simulate(simulationMode); + simulationMode = newSimMode; + reprap.GetMove().Simulate(newSimMode); } return GCodeResult::ok; } diff --git a/src/GCodes/GCodes4.cpp b/src/GCodes/GCodes4.cpp index 65ebe0ccfe..ab8c3689bb 100644 --- a/src/GCodes/GCodes4.cpp +++ b/src/GCodes/GCodes4.cpp @@ -345,7 +345,7 @@ void GCodes::RunStateMachine(GCodeBuffer& gb, const StringRef& reply) noexcept const Tool * const oldTool = reprap.GetCurrentTool(); if (oldTool != nullptr) { - reprap.StandbyTool(oldTool->Number(), simulationMode != 0); + reprap.StandbyTool(oldTool->Number(), IsSimulating()); UpdateCurrentUserPosition(); // the tool offset may have changed, so get the current position } gb.AdvanceState(); @@ -362,7 +362,7 @@ void GCodes::RunStateMachine(GCodeBuffer& gb, const StringRef& reply) noexcept case GCodeState::m109ToolChange2: // select the new tool if it exists and run tpost if (LockMovementAndWaitForStandstill(gb)) // wait for tpre.g to finish executing { - reprap.SelectTool(newToolNumber, simulationMode != 0); + reprap.SelectTool(newToolNumber, IsSimulating()); UpdateCurrentUserPosition(); // get the actual position of the new tool gb.AdvanceState(); @@ -432,7 +432,7 @@ void GCodes::RunStateMachine(GCodeBuffer& gb, const StringRef& reply) noexcept break; case GCodeState::m109WaitForTemperature: - if (cancelWait || simulationMode != 0 || ToolHeatersAtSetTemperatures(reprap.GetCurrentTool(), gb.LatestMachineState().waitWhileCooling, TEMPERATURE_CLOSE_ENOUGH)) + if (cancelWait || IsSimulating() || ToolHeatersAtSetTemperatures(reprap.GetCurrentTool(), gb.LatestMachineState().waitWhileCooling, TEMPERATURE_CLOSE_ENOUGH)) { cancelWait = isWaiting = false; gb.SetState(GCodeState::normal); diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp index e743de843c..96e0571512 100644 --- a/src/Movement/DDA.cpp +++ b/src/Movement/DDA.cpp @@ -1279,7 +1279,7 @@ void DDA::EnsureUnshapedSegments(const PrepParams& params) noexcept // Prepare this DDA for execution. // This must not be called with interrupts disabled, because it calls Platform::EnableDrive. -void DDA::Prepare(uint8_t simMode) noexcept +void DDA::Prepare(SimulationMode simMode) noexcept { flags.wasAccelOnlyMove = IsAccelerationMove(); // save this for the next move to look at @@ -1311,7 +1311,7 @@ void DDA::Prepare(uint8_t simMode) noexcept acceleration = params.unshaped.acceleration; deceleration = params.unshaped.deceleration; - if (simMode == 0) + if (simMode < SimulationMode::normal) { if (flags.isDeltaMovement) { @@ -1611,7 +1611,7 @@ void DDA::Prepare(uint8_t simMode) noexcept } #if SUPPORT_CAN_EXPANSION - const uint32_t canClocksNeeded = CanMotion::FinishMovement(afterPrepare.moveStartTime); + const uint32_t canClocksNeeded = CanMotion::FinishMovement(afterPrepare.moveStartTime, simMode != SimulationMode::off); if (canClocksNeeded > clocksNeeded) { // Due to rounding error in the calculations, we quite often calculate the CAN move as being longer than our previously-calculated value, normally by just one clock. @@ -2116,6 +2116,64 @@ void DDA::StepDrivers(Platform& p, uint32_t now) noexcept } } +// Simulate stepping the drivers, for debugging. +// This is basically a copy of DDA::SetDrivers except that instead of being called from the timer ISR and generating steps, +// it is called from the Move task and outputs info on the step timings. It ignores endstops. +void DDA::SimulateSteppingDrivers(Platform& p) noexcept +{ + static uint32_t lastStepTime; + static bool checkTiming = false; + + DriveMovement* dm = activeDMs; + if (dm != nullptr) + { + const uint32_t dueTime = dm->nextStepTime; + while (dm != nullptr && dueTime >= dm->nextStepTime) // if the next step is due + { + const uint32_t timeDiff = dm->nextStepTime - lastStepTime; + const bool badTiming = checkTiming && (timeDiff < 10 || timeDiff > 100000000); + debugPrintf("%10" PRIu32 " D%u %c%s", dm->nextStepTime, dm->drive, (dm->direction) ? 'F' : 'B', (badTiming) ? " *\n" : "\n"); + dm = dm->nextDM; + } + lastStepTime = dueTime; + checkTiming = true; + + for (DriveMovement *dm2 = activeDMs; dm2 != dm; dm2 = dm2->nextDM) + { + (void)dm2->CalcNextStepTime(*this); // calculate next step times + } + + // Remove those drives from the list, update the direction pins where necessary, and re-insert them so as to keep the list in step-time order. + DriveMovement *dmToInsert = activeDMs; // head of the chain we need to re-insert + activeDMs = dm; // remove the chain from the list + while (dmToInsert != dm) // note that both of these may be nullptr + { + DriveMovement * const nextToInsert = dmToInsert->nextDM; + if (dmToInsert->state >= DMState::firstMotionState) + { + InsertDM(dmToInsert); + if (dmToInsert->directionChanged) + { + dmToInsert->directionChanged = false; + } + } + else + { + dmToInsert->nextDM = completedDMs; + completedDMs = dmToInsert; + } + dmToInsert = nextToInsert; + } + } + + // If there are no more steps to do and the time for the move has nearly expired, flag the move as complete + if (activeDMs == nullptr) + { + checkTiming = false; // don't check the timing of the first step in the next move + state = completed; + } +} + // Stop a drive and re-calculate the corresponding endpoint. // For extruder drivers, we need to be able to calculate how much of the extrusion was completed after calling this. void DDA::StopDrive(size_t drive) noexcept diff --git a/src/Movement/DDA.h b/src/Movement/DDA.h index 66019a3571..2c693d9898 100644 --- a/src/Movement/DDA.h +++ b/src/Movement/DDA.h @@ -98,13 +98,14 @@ class DDA void Start(Platform& p, uint32_t tim) noexcept SPEED_CRITICAL; // Start executing the DDA, i.e. move the move. void StepDrivers(Platform& p, uint32_t now) noexcept SPEED_CRITICAL; // Take one step of the DDA, called by timer interrupt. + void SimulateSteppingDrivers(Platform& p) noexcept; // For debugging use bool ScheduleNextStepInterrupt(StepTimer& timer) const noexcept SPEED_CRITICAL; // Schedule the next interrupt, returning true if we can't because it is already due void SetNext(DDA *n) noexcept { next = n; } void SetPrevious(DDA *p) noexcept { prev = p; } void Complete() noexcept { state = completed; } bool Free() noexcept; - void Prepare(uint8_t simMode) noexcept SPEED_CRITICAL; // Calculate all the values and freeze this DDA + void Prepare(SimulationMode simMode) noexcept SPEED_CRITICAL; // Calculate all the values and freeze this DDA bool HasStepError() const noexcept; bool CanPauseAfter() const noexcept; bool IsPrintingMove() const noexcept { return flags.isPrintingMove; } // Return true if this involves both XY movement and extrusion diff --git a/src/Movement/DDARing.cpp b/src/Movement/DDARing.cpp index e8de24e4c0..cc8af5b044 100644 --- a/src/Movement/DDARing.cpp +++ b/src/Movement/DDARing.cpp @@ -262,16 +262,26 @@ bool DDARing::AddAsyncMove(const AsyncMove& nextMove) noexcept // Try to process moves in the ring. Called by the Move task. // Return the maximum time in milliseconds that should elapse before we prepare further unprepared moves that are already in the ring, or TaskBase::TimeoutUnlimited if there are no unprepared moves left. -uint32_t DDARing::Spin(uint8_t simulationMode, bool waitingForSpace, bool shouldStartMove) noexcept +uint32_t DDARing::Spin(SimulationMode simulationMode, bool waitingForSpace, bool shouldStartMove) noexcept { DDA *cdda = currentDda; // capture volatile variable // If we are simulating, simulate completion of the current move. // Do this here rather than at the end, so that when simulating, currentDda is non-null for most of the time and IsExtruding() returns the correct value - if (simulationMode != 0 && cdda != nullptr) + if (simulationMode != SimulationMode::off && cdda != nullptr) { simulationTime += (float)cdda->GetClocksNeeded() * (1.0/StepClockRate); - cdda->Complete(); + if (simulationMode == SimulationMode::debug && reprap.Debug(moduleDda)) + { + do + { + cdda->SimulateSteppingDrivers(reprap.GetPlatform()); + } while (cdda->GetState() != DDA::completed); + } + else + { + cdda->Complete(); + } CurrentMoveCompleted(); // this sets currentDda to nullptr and advances getPointer DDA * const gp = getPointer; // capture volatile variable if (gp->GetState() == DDA::frozen) @@ -300,14 +310,14 @@ uint32_t DDARing::Spin(uint8_t simulationMode, bool waitingForSpace, bool should cdda = cdda->GetNext(); if (cdda == addPointer) { - return (simulationMode == 0) + return (simulationMode == SimulationMode::off) ? TaskBase::TimeoutUnlimited // all the moves we have are already prepared, so nothing to do until new moves arrive : 0; } } uint32_t ret = PrepareMoves(cdda, preparedTime, preparedCount, simulationMode); - if (simulationMode != 0) + if (simulationMode >= SimulationMode::normal) { return 0; } @@ -345,7 +355,7 @@ uint32_t DDARing::Spin(uint8_t simulationMode, bool waitingForSpace, bool should } else if (dda->GetState() == DDA::frozen) { - if (simulationMode != 0) + if (simulationMode != SimulationMode::off) { currentDda = dda; // pretend we are executing this move return 0; // we don't want any delay because we want Spin() to be called again soon to complete this move @@ -392,7 +402,7 @@ uint32_t DDARing::Spin(uint8_t simulationMode, bool waitingForSpace, bool should // Prepare some moves. moveTimeLeft is the total length remaining of moves that are already executing or prepared. // Return the maximum time in milliseconds that should elapse before we prepare further unprepared moves that are already in the ring, or TaskBase::TimeoutUnlimited if there are no unprepared moves left. -uint32_t DDARing::PrepareMoves(DDA *firstUnpreparedMove, int32_t moveTimeLeft, unsigned int alreadyPrepared, uint8_t simulationMode) noexcept +uint32_t DDARing::PrepareMoves(DDA *firstUnpreparedMove, int32_t moveTimeLeft, unsigned int alreadyPrepared, SimulationMode simulationMode) noexcept { // If the number of prepared moves will execute in less than the minimum time, prepare another move. // Try to avoid preparing deceleration-only moves too early @@ -415,7 +425,7 @@ uint32_t DDARing::PrepareMoves(DDA *firstUnpreparedMove, int32_t moveTimeLeft, u if (firstUnpreparedMove->GetState() == DDA::provisional) { // There are more moves waiting to be prepared, so ask to be woken up early - if (simulationMode != 0) + if (simulationMode != SimulationMode::off) { return 1; } diff --git a/src/Movement/DDARing.h b/src/Movement/DDARing.h index 9183db40bb..1bae00c89c 100644 --- a/src/Movement/DDARing.h +++ b/src/Movement/DDARing.h @@ -29,7 +29,7 @@ class DDARing INHERIT_OBJECT_MODEL bool AddAsyncMove(const AsyncMove& nextMove) noexcept; #endif - uint32_t Spin(uint8_t simulationMode, bool waitingForSpace, bool shouldStartMove) noexcept SPEED_CRITICAL; // Try to process moves in the ring + uint32_t Spin(SimulationMode simulationMode, bool waitingForSpace, bool shouldStartMove) noexcept SPEED_CRITICAL; // Try to process moves in the ring bool IsIdle() const noexcept; // Return true if this DDA ring is idle uint32_t GetGracePeriod() const noexcept { return gracePeriod; } // Return the minimum idle time, before we should start a move. Better to have a few moves in the queue so that we can do lookahead @@ -99,7 +99,7 @@ class DDARing INHERIT_OBJECT_MODEL private: bool StartNextMove(Platform& p, uint32_t startTime) noexcept SPEED_CRITICAL; // Start the next move, returning true if laser or IObits need to be controlled - uint32_t PrepareMoves(DDA *firstUnpreparedMove, int32_t moveTimeLeft, unsigned int alreadyPrepared, uint8_t simulationMode) noexcept; + uint32_t PrepareMoves(DDA *firstUnpreparedMove, int32_t moveTimeLeft, unsigned int alreadyPrepared, SimulationMode simulationMode) noexcept; static void TimerCallback(CallbackParameter p) noexcept; diff --git a/src/Movement/DriveMovement.cpp b/src/Movement/DriveMovement.cpp index f00c162439..3ae424b255 100644 --- a/src/Movement/DriveMovement.cpp +++ b/src/Movement/DriveMovement.cpp @@ -197,40 +197,59 @@ bool DriveMovement::NewDeltaSegment(const DDA& dda) noexcept pB = currentSegment->CalcNonlinearB(timeSoFar); } - const float startDistance = distanceSoFar; distanceSoFar += currentSegment->GetSegmentLength(); timeSoFar += currentSegment->GetSegmentTime(); - // Work out whether we reverse in this segment and the movement limit in steps - const float sDx = distanceSoFar * dda.directionVector[0]; - const float sDy = distanceSoFar * dda.directionVector[1]; - const int32_t netStepsAtEnd = (int32_t)(fastSqrtf(mp.delta.fDSquaredMinusAsquaredMinusBsquaredTimesSsquared - fsquare(stepsPerMm) * (sDx * (sDx + mp.delta.fTwoA) + sDy * (sDy + mp.delta.fTwoB))) - + (distanceSoFar * dda.directionVector[2] - mp.delta.h0MinusZ0) * stepsPerMm); - - if (mp.delta.reverseStartDistance <= startDistance) + // Work out whether we reverse in this segment and the movement limit in steps. + // First check whether the first step in this segment is the previously-calculated reverse start step, and if so then do the reversal. + if (nextStep == reverseStartStep) { - // This segment is purely downwards motion and we want the greater of the two quadratic solutions. There may have been upwards motion earlier in the move. - if (direction) - { - direction = false; - directionChanged = true; - } - state = DMState::deltaReverse; - phaseStepLimit = (currentSegment->GetNext() == nullptr) ? totalSteps + 1 - : (reverseStartStep <= totalSteps) ? (uint32_t)((int32_t)(2 * reverseStartStep) - netStepsAtEnd) - : 1 - netStepsAtEnd; + direction = false; // we must have been going up, so now we are going down + directionChanged = true; } - else if (distanceSoFar <= mp.delta.reverseStartDistance) + + if (currentSegment->GetNext() == nullptr) { - // This segment is purely upwards motion of the tower and we want the lower quadratic solution - state = DMState::deltaForwardsNoReverse; - phaseStepLimit = (currentSegment->GetNext() == nullptr) ? totalSteps + 1 : (uint32_t)(netStepsAtEnd + 1); + // This is the last segment, so the phase step limit is the number of total steps, and we can avoid some calculation + phaseStepLimit = totalSteps + 1; + state = (reverseStartStep <= totalSteps && nextStep < reverseStartStep) ? DMState::deltaForwardsReversing : DMState::deltaNormal; } else { - // This segment ends with reverse motion. We want the lower quadratic solution initially. - phaseStepLimit = (currentSegment->GetNext() == nullptr) ? totalSteps + 1 : (uint32_t)((int32_t)(2 * reverseStartStep) - netStepsAtEnd); - state = DMState::deltaForwardsReversing; + // Work out how many whole steps we have moved up or down at the end of this segment + const float sDx = distanceSoFar * dda.directionVector[0]; + const float sDy = distanceSoFar * dda.directionVector[1]; + int32_t netStepsAtEnd = (int32_t)floorf(fastSqrtf(mp.delta.fDSquaredMinusAsquaredMinusBsquaredTimesSsquared - fsquare(stepsPerMm) * (sDx * (sDx + mp.delta.fTwoA) + sDy * (sDy + mp.delta.fTwoB))) + + (distanceSoFar * dda.directionVector[2] - mp.delta.h0MinusZ0) * stepsPerMm); + + // If there is a reversal then we only ever move up by (reverseStartStep - 1) steps, so netStepsAtEnd should be less than reverseStartStep. + // However, because of rounding error, it might possibly be equal. + // If there is no reversal then reverseStartStep is set to totalSteps + 1, so netStepsAtEnd must again be less than reverseStartStep. + if (netStepsAtEnd >= (int32_t)reverseStartStep) + { + netStepsAtEnd = (int32_t)(reverseStartStep - 1); // correct the rounding error - we know that reverseStartStep cannot be 0 so subtracting 1 is safe + } + + if (!direction) + { + // We are going down so any reversal has already happened + state = DMState::deltaNormal; + phaseStepLimit = (nextStep >= reverseStartStep) + ? (uint32_t)((int32_t)(2 * reverseStartStep) - netStepsAtEnd) // we went up (reverseStartStep-1) steps, now we are going down to netStepsAtEnd + : (uint32_t)(-netStepsAtEnd); // we are just going down to netStepsAtEnd + } + else if (distanceSoFar <= mp.delta.reverseStartDistance) + { + // This segment is purely upwards motion of the tower + state = DMState::deltaNormal; + phaseStepLimit = (uint32_t)(netStepsAtEnd + 1); + } + else + { + // This segment ends with reverse motion + phaseStepLimit = (uint32_t)((int32_t)(2 * reverseStartStep) - netStepsAtEnd); + state = DMState::deltaForwardsReversing; + } } #else iC = currentSegment->GetC()/stepsPerMm; //TODO store the reciprocal to avoid the division? Use a scaling factor for C @@ -306,7 +325,6 @@ bool DriveMovement::NewExtruderSegment() noexcept const float startDistance = distanceSoFar; const float startTime = timeSoFar; - // Work out the movement limit in steps distanceSoFar += currentSegment->GetSegmentLength(); timeSoFar += currentSegment->GetSegmentTime(); @@ -331,10 +349,11 @@ bool DriveMovement::NewExtruderSegment() noexcept else { // This is the single decelerating segment. If it includes pressure advance then it may include reversal. - state = DMState::cartDecelForwardsReversing; // assume that it may reverse + state = (reverseStartStep <= totalSteps) ? DMState::cartDecelForwardsReversing : DMState::cartDecelNoReverse; } } + // Work out the movement limit in steps phaseStepLimit = ((currentSegment->GetNext() == nullptr) ? totalSteps : (uint32_t)(distanceSoFar * mp.cart.effectiveStepsPerMm)) + 1; #else const uint32_t startDistance = iDistanceSoFar; @@ -439,7 +458,7 @@ bool DriveMovement::PrepareDeltaAxis(const DDA& dda, const PrepParams& params) n // Calculate the distance at which we need to reverse direction. if (params.a2plusb2 <= 0.0) { - // Pure Z movement. We can't use the main calculation because it divides by a2plusb2. + // Pure Z movement. We can't use the main calculation because it divides by params.a2plusb2. direction = (dda.directionVector[Z_AXIS] >= 0.0); mp.delta.reverseStartDistance = (direction) ? dda.totalDistance + 1.0 : -1.0; // so that we never reverse and NewDeltaSegment knows which way we are going reverseStartStep = totalSteps + 1; @@ -451,18 +470,34 @@ bool DriveMovement::PrepareDeltaAxis(const DDA& dda, const PrepParams& params) n const float drev = ((dda.directionVector[Z_AXIS] * fastSqrtf(params.a2plusb2 * params.dparams->GetDiagonalSquared(drive) - fsquare(A * dda.directionVector[Y_AXIS] - B * dda.directionVector[X_AXIS]))) - aAplusbB)/params.a2plusb2; mp.delta.reverseStartDistance = drev; - if (drev > 0.0 && drev < dda.totalDistance) // if the reversal point is within range + if (drev > 0.0 && drev < dda.totalDistance) // if the reversal point is within range { // Calculate how many steps we need to move up before reversing const float hrev = dda.directionVector[Z_AXIS] * drev + fastSqrtf(dSquaredMinusAsquaredMinusBsquared - 2 * drev * aAplusbB - params.a2plusb2 * fsquare(drev)); const int32_t numStepsUp = (int32_t)((hrev - mp.delta.h0MinusZ0) * stepsPerMm); - // We may be almost at the peak height already, in which case we don't really have a reversal. + // We may be going down but almost at the peak height already, in which case we don't really have a reversal. + // However, we could be going up by a whole step due to rounding, so we need to check the direction if (numStepsUp < 1) { - mp.delta.reverseStartDistance = -1.0; // so that we know we have reversed already + if (direction) + { + mp.delta.reverseStartDistance = dda.totalDistance + 1.0; // indicate that there is no reversal + } + else + { + mp.delta.reverseStartDistance = -1.0; // so that we know we have reversed already + reverseStartStep = totalSteps + 1; + } + } + else if (direction && (uint32_t)numStepsUp <= totalSteps) + { + // If numStepsUp == totalSteps then the reverse segment is too small to do. + // If numStepsUp < totalSteps then there has been a rounding error, because we are supposed to move up more than the calculated number of steps we move up. + // This can happen if the calculated reversal is very close to the end of the move, because we round the final step positions to the nearest step, which may be up. + // Either way, don't do a reverse segment. reverseStartStep = totalSteps + 1; - direction = false; + mp.delta.reverseStartDistance = dda.totalDistance + 1.0; } else { @@ -472,13 +507,13 @@ bool DriveMovement::PrepareDeltaAxis(const DDA& dda, const PrepParams& params) n if (direction) { // Net movement is up, so we will go up first and then down by a lesser amount - totalSteps = (2 * numStepsUp) - totalSteps; + totalSteps = (2 * (uint32_t)numStepsUp) - totalSteps; } else { // Net movement is down, so we will go up first and then down by a greater amount direction = true; - totalSteps = (2 * numStepsUp) + totalSteps; + totalSteps = (2 * (uint32_t)numStepsUp) + totalSteps; } } } @@ -601,6 +636,47 @@ bool DriveMovement::PrepareExtruder(const DDA& dda, const PrepParams& params) no mp.cart.extraExtrusionDistance = mp.cart.pressureAdvanceK * (dda.topSpeed - dda.startSpeed); forwardDistance += mp.cart.extraExtrusionDistance; +# if 0 //SHAPE_EXTRUSION + forwardDistance += params.shaped.decelStartDistance; + reverseDistance = 0.0; + + // Find the deceleration segments + const MoveSegment *decelSeg = dda.unshapedSegments; + while (decelSeg != nullptr && (decelSeg->IsLinear() || decelSeg->IsAccelerating())) + { + decelSeg = decelSeg->GetNext(); + } + + float lastUncorrectedSpeed = dda.topSpeed; + float lastDistance = forwardDistance; + while (decelSeg != nullptr) + { + const float initialDecelSpeed = lastUncorrectedSpeed - mp.cart.pressureAdvanceK * decelSeg->deceleration; + if (initialDecelSpeed <= 0.0) + { + // This entire deceleration segment is in reverse + reverseDistance += ((0.5 * params.unshaped.deceleration * params.unshaped.decelClocks) - initialDecelSpeed) * params.unshaped.decelClocks; + } + else + { + const float timeToReverse = initialDecelSpeed * ((-0.5) * decelSeg->GetC()); // 'c' is -2/deceleration, so -0.5*c is 1/deceleration + if (timeToReverse < params.unshaped.decelClocks) + { + // There is a reversal, although it could be tiny + const float distanceToReverse = fsquare(initialDecelSpeed) * decelSeg->GetC() * (-0.25); // because (v^2-u^2) = 2as, so if v=0 then s=-u^2/2a = u^2/2d = -0.25*u^2*c + forwardDistance += params.unshaped.decelStartDistance + distanceToReverse; + reverseDistance = 0.5 * params.unshaped.deceleration * fsquare(params.unshaped.decelClocks - timeToReverse); // because s = 0.5*a*t^2 + } + else + { + // No reversal + forwardDistance += dda.totalDistance - (mp.cart.pressureAdvanceK * params.unshaped.deceleration * params.unshaped.decelClocks); + reverseDistance = 0.0; + } + } + + } +# else // Check if there is a reversal in the deceleration segment // There is at most one deceleration segment in the unshaped segments const MoveSegment *decelSeg = dda.unshapedSegments; @@ -641,6 +717,7 @@ bool DriveMovement::PrepareExtruder(const DDA& dda, const PrepParams& params) no } } } +# endif } else { @@ -786,7 +863,7 @@ bool DriveMovement::PrepareExtruder(const DDA& dda, const PrepParams& params) no #if MS_USE_FPU -// Version of fastSqrtf that allows for slightly negative operands cause dby rounding error +// Version of fastSqrtf that allows for slightly negative operands caused by rounding error static inline float fastLimSqrtf(float f) noexcept { return (f > 0.0) ? fastSqrtf(f) : 0.0; @@ -914,11 +991,10 @@ pre(nextStep <= totalSteps; stepsTillRecalc == 0) { direction = false; directionChanged = true; - state = DMState::deltaReverse; + state = DMState::deltaNormal; } // no break - case DMState::deltaForwardsNoReverse: - case DMState::deltaReverse: // reversing on this and subsequent steps + case DMState::deltaNormal: // Calculate d*s where d = distance the head has travelled, s = steps/mm for this drive { #if MS_USE_FPU diff --git a/src/Movement/DriveMovement.h b/src/Movement/DriveMovement.h index 8b24ecc1fb..5e4084bab8 100644 --- a/src/Movement/DriveMovement.h +++ b/src/Movement/DriveMovement.h @@ -31,9 +31,8 @@ enum class DMState : uint8_t cartDecelForwardsReversing, // linear decelerating motion, expect reversal cartDecelReverse, // linear decelerating motion, reversed - deltaForwardsNoReverse, // moving forwards, no reversal in this segment - deltaForwardsReversing, // moving forwards but reversing in this segment - deltaReverse, // moving in reverse + deltaNormal, // moving forwards without reversing in this segment, or in reverse + deltaForwardsReversing, // moving forwards to start with, reversing before the end of this segment }; // This class describes a single movement of one drive diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp index d034b61e6e..72ef43fa0b 100644 --- a/src/Movement/Move.cpp +++ b/src/Movement/Move.cpp @@ -200,7 +200,7 @@ void Move::Init() noexcept moveState = MoveState::idle; whenLastMoveAdded = whenIdleTimerStarted = millis(); - simulationMode = 0; + simulationMode = SimulationMode::off; longestGcodeWaitInterval = 0; bedLevellingMoveAvailable = false; @@ -240,7 +240,7 @@ void Move::Exit() noexcept if (bedLevellingMoveAvailable) { moveRead = true; - if (simulationMode < 2) + if (simulationMode < SimulationMode::partial) { if (mainDDARing.AddSpecialMove(reprap.GetPlatform().MaxFeedrate(Z_AXIS), specialMoveCoords)) { @@ -263,7 +263,7 @@ void Move::Exit() noexcept if (reprap.GetGCodes().ReadMove(nextMove)) // if we have a new move { moveRead = true; - if (simulationMode < 2) // in simulation mode 2 and higher, we don't process incoming moves beyond this point + if (simulationMode < SimulationMode::partial) // in simulation mode partial, we don't process incoming moves beyond this point { if (nextMove.moveType == 0) { @@ -916,10 +916,10 @@ float Move::GetProbeCoordinates(int count, float& x, float& y, bool wantNozzlePo } // Enter or leave simulation mode -void Move::Simulate(uint8_t simMode) noexcept +void Move::Simulate(SimulationMode simMode) noexcept { simulationMode = simMode; - if (simMode != 0) + if (simMode != SimulationMode::off) { mainDDARing.ResetSimulationTime(); } diff --git a/src/Movement/Move.h b/src/Movement/Move.h index b5583bb05f..6eee76f2c4 100644 --- a/src/Movement/Move.h +++ b/src/Movement/Move.h @@ -129,7 +129,7 @@ class Move INHERIT_OBJECT_MODEL float IdleTimeout() const noexcept; // Returns the idle timeout in seconds void SetIdleTimeout(float timeout) noexcept; // Set the idle timeout in seconds - void Simulate(uint8_t simMode) noexcept; // Enter or leave simulation mode + void Simulate(SimulationMode simMode) noexcept; // Enter or leave simulation mode float GetSimulationTime() const noexcept { return mainDDARing.GetSimulationTime(); } // Get the accumulated simulation time bool PausePrint(RestorePoint& rp) noexcept; // Pause the print as soon as we can, returning true if we were able to @@ -257,7 +257,7 @@ class Move INHERIT_OBJECT_MODEL DDARing& mainDDARing = rings[0]; // The DDA ring used for regular moves - uint8_t simulationMode; // Are we simulating, or really printing? + SimulationMode simulationMode; // Are we simulating, or really printing? MoveState moveState; // whether the idle timer is active float maxPrintingAcceleration; @@ -345,7 +345,7 @@ inline float Move::GetPressureAdvanceClocks(size_t extruder) const noexcept // This is called from the stepper drivers SPI interface ISR inline __attribute__((always_inline)) uint32_t Move::GetStepInterval(size_t axis, uint32_t microstepShift) const noexcept { - return (simulationMode == 0) ? mainDDARing.GetStepInterval(axis, microstepShift) : 0; + return (simulationMode == SimulationMode::off) ? mainDDARing.GetStepInterval(axis, microstepShift) : 0; } #endif diff --git a/src/Version.h b/src/Version.h index 8896bea8f8..f3a62e550c 100644 --- a/src/Version.h +++ b/src/Version.h @@ -10,7 +10,7 @@ #ifndef VERSION // Note: the complete VERSION string must be in standard version number format and must not contain spaces! This is so that DWC can parse it. -# define MAIN_VERSION "3.4.0beta3+1" +# define MAIN_VERSION "3.4.0beta4" # ifdef USE_CAN0 # define VERSION_SUFFIX "(CAN0)" # else