diff options
56 files changed, 1782 insertions, 612 deletions
@@ -59,7 +59,7 @@ </tool> <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.831729502" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/> <tool id="cdt.managedbuild.tool.gnu.cross.archiver.586905748" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/> - <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM3X8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.77650722" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> + <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} "${workspace_loc:/${CoreName}/SAM3X8E/cores/arduino/syscalls.o}" ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.77650722" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> <option id="gnu.cpp.link.option.nostdlibs.296038599" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/> <option id="gnu.cpp.link.option.paths.75045718" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths"> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/SAM3X8E/}""/> @@ -176,7 +176,7 @@ </tool> <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1561858475" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/> <tool id="cdt.managedbuild.tool.gnu.cross.archiver.367299064" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/> - <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.2096521800" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> + <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} "${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o}" ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.2096521800" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> <option id="gnu.cpp.link.option.nostdlibs.111689408" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/> <option id="gnu.cpp.link.option.paths.314107745" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths"> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/SAM4E8E/}""/> @@ -298,7 +298,7 @@ </tool> <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1692168928" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/> <tool id="cdt.managedbuild.tool.gnu.cross.archiver.1755453550" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/> - <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/RADDS/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1176271302" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> + <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} "${workspace_loc:/${CoreName}/RADDS/cores/arduino/syscalls.o}" ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1176271302" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> <option id="gnu.cpp.link.option.nostdlibs.706270025" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/> <option id="gnu.cpp.link.option.paths.1160723414" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths"> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/RADDS/}""/> @@ -416,7 +416,7 @@ </tool> <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.212863977" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/> <tool id="cdt.managedbuild.tool.gnu.cross.archiver.136873036" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/> - <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.394147701" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> + <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} "${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o}" ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.394147701" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> <option id="gnu.cpp.link.option.nostdlibs.278980629" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/> <option id="gnu.cpp.link.option.paths.1667456082" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths"> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/SAM4E8E/}""/> diff --git a/Maths/BedLevelling-2-point.wxm b/Maths/BedLevelling-2-point.wxm new file mode 100644 index 00000000..27876d05 --- /dev/null +++ b/Maths/BedLevelling-2-point.wxm @@ -0,0 +1,57 @@ +/* [wxMaxima batch file version 1] [ DO NOT EDIT BY HAND! ]*/ +/* [ Created with wxMaxima version 14.12.1 ] */ + +/* [wxMaxima: input start ] */ +eq1:h=H+a*x+b*y; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq2:h0=H+a*x0+b*y0; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq3:h1=H+a*x1+b*y1; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq4:h2=H+a*(x0+y1-y0)+b*(y0-(x1-x0)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq5:h2=h0; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq6:subst(eq5,eq4); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq7:solve([eq2,eq3,eq6],[a,b,H]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq8:subst(eq7,eq1); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq9:dhbydh0=factor(diff(rhs(eq8),h0)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq10:dhbydh1=factor(diff(rhs(eq8),h1)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq11:expand((x1-x0)^2+(y1-y0)^2)=d^2; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq12:subst(eq11,eq9); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq13:subst(eq11,eq10); +/* [wxMaxima: input end ] */ + +/* Maxima can't load/batch files which end with a comment! */ +"Created with wxMaxima"$ diff --git a/Maths/BedLevelling-3-point.wxm b/Maths/BedLevelling-3-point.wxm new file mode 100644 index 00000000..6ec01b62 --- /dev/null +++ b/Maths/BedLevelling-3-point.wxm @@ -0,0 +1,57 @@ +/* [wxMaxima batch file version 1] [ DO NOT EDIT BY HAND! ]*/ +/* [ Created with wxMaxima version 14.12.1 ] */ + +/* [wxMaxima: input start ] */ +eq1:h=H+a*x+b*y; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq2:h0=H+a*x0+b*y0; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq3:h1=H+a*x1+b*y1; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq4:h2=H+a*x2+b*y2; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq5:solve([eq2,eq3,eq4],[a,b,H]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq6:subst(eq5,eq1); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq7:dhbydh0=factor(diff(rhs(eq6),h0)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq8:dhbydh1=factor(diff(rhs(eq6),h1)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq9:dhbydh2=factor(diff(rhs(eq6),h2)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq10:x1*y2-x0*y2-x2*y1+x0*y1+x2*y0-x1*y0=d; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq11:subst(eq10,eq7); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq12:subst(eq10,eq8); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq13:subst(eq10,eq9); +/* [wxMaxima: input end ] */ + +/* Maxima can't load/batch files which end with a comment! */ +"Created with wxMaxima"$ diff --git a/Maths/BedLevelling-4-point.wxm b/Maths/BedLevelling-4-point.wxm new file mode 100644 index 00000000..2fe1f63f --- /dev/null +++ b/Maths/BedLevelling-4-point.wxm @@ -0,0 +1,71 @@ +/* [wxMaxima batch file version 1] [ DO NOT EDIT BY HAND! ]*/ +/* [ Created with wxMaxima version 14.12.1 ] */ + +/* [wxMaxima: input start ] */ +eq1:h=H+a*x+b*y+c*x*y; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq2:h0=H+a*x0+b*y0+c*x0*y0; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq3:h1=H+a*x1+b*y1+c*x1*y1; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq4:h2=H+a*x2+b*y2+c*x2*y2; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq5:h3=H+a*x3+b*y3+c*x3*y3; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq6:solve([eq2,eq3,eq4,eq5],[a,b,c,H]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq7:subst(eq6,eq1); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq8:dhbydh0=factor(diff(rhs(eq7),h0)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq9:dhbydh1=factor(diff(rhs(eq7),h1)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq10:dhbydh2=factor(diff(rhs(eq7),h2)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq11:dhbydh3=factor(diff(rhs(eq7),h3)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq12:(x1*x3*y2*y3-x0*x3*y2*y3-x1*x2*y2*y3+x0*x2*y2*y3-x2*x3*y1*y3+x0* +x3*y1*y3+x1*x2*y1*y3-x0*x1*y1*y3+x2*x3*y0*y3-x1*x3*y0*y3-x0*x2*y0*y3+x0*x1*y0*y3+x2*x3*y1*y2-x1*x3*y1*y2-x0*x2*y1*y2+x0*x1*y1*y2-x2*x3*y0*y2+x0*x3*y0*y2+x1*x2*y0*y2-x0*x1*y0* +y2+x1*x3*y0*y1-x0*x3*y0*y1-x1*x2*y0*y1+x0*x2*y0*y1)=d; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq13:subst(eq12,eq8); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq14:subst(eq12,eq9); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq15:subst(eq12,eq10); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq16:subst(eq12,eq11); +/* [wxMaxima: input end ] */ + +/* Maxima can't load/batch files which end with a comment! */ +"Created with wxMaxima"$ diff --git a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.19beta9.bin b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.19beta9.bin Binary files differnew file mode 100644 index 00000000..3d5075fe --- /dev/null +++ b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.19beta9.bin diff --git a/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.19beta9.bin b/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.19beta9.bin Binary files differnew file mode 100644 index 00000000..59851196 --- /dev/null +++ b/Release/Duet-Ethernet/Edge/DuetEthernetFirmware-1.19beta9.bin diff --git a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.19beta9.bin b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.19beta9.bin Binary files differnew file mode 100644 index 00000000..8f090f79 --- /dev/null +++ b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.19beta9.bin diff --git a/Release/Duet-WiFi/Edge/DuetWiFiServer-1.19beta9.bin b/Release/Duet-WiFi/Edge/DuetWiFiServer-1.19beta9.bin Binary files differnew file mode 100644 index 00000000..7fe6017b --- /dev/null +++ b/Release/Duet-WiFi/Edge/DuetWiFiServer-1.19beta9.bin diff --git a/Release/RADDS/Edge/RepRapFirmware-RADDS-1.19beta9.bin b/Release/RADDS/Edge/RepRapFirmware-RADDS-1.19beta9.bin Binary files differnew file mode 100644 index 00000000..3c8d25fa --- /dev/null +++ b/Release/RADDS/Edge/RepRapFirmware-RADDS-1.19beta9.bin diff --git a/src/DuetNG/DuetWiFi/Network.cpp b/src/DuetNG/DuetWiFi/Network.cpp index 1a8b1a89..405e3910 100644 --- a/src/DuetNG/DuetWiFi/Network.cpp +++ b/src/DuetNG/DuetWiFi/Network.cpp @@ -660,11 +660,20 @@ void Network::Diagnostics(MessageType mtype) r.macAddress[0], r.macAddress[1], r.macAddress[2], r.macAddress[3], r.macAddress[4], r.macAddress[5]); platform.MessageF(mtype, "WiFi Vcc %.2f, reset reason %s\n", (float)r.vcc/1024, TranslateEspResetReason(r.resetReason)); platform.MessageF(mtype, "WiFi flash size %u, free heap %u\n", r.flashSize, r.freeHeap); - if (currentMode == WiFiState::connected) + + if (currentMode == WiFiState::connected || currentMode == WiFiState::runningAsAccessPoint) { platform.MessageF(mtype, "WiFi IP address %d.%d.%d.%d\n", r.ipAddress & 255, (r.ipAddress >> 8) & 255, (r.ipAddress >> 16) & 255, (r.ipAddress >> 24) & 255); - platform.MessageF(mtype, "WiFi signal strength %ddb\n", r.rssi); + } + + if (currentMode == WiFiState::connected) + { + platform.MessageF(mtype, "WiFi signal strength %ddb\n", (int)r.rssi); + } + else if (currentMode == WiFiState::runningAsAccessPoint) + { + platform.MessageF(mtype, "Connected clients %u\n", (unsigned int)r.numClients); } // status, ssid and hostName not displayed diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h index e2a61e5b..c9e37a4e 100644 --- a/src/DuetNG/Pins_DuetNG.h +++ b/src/DuetNG/Pins_DuetNG.h @@ -111,7 +111,7 @@ const Pin Z_PROBE_PIN = 33; // AFE1_AD4/PC1 Z probe analog input const Pin PowerMonitorVinDetectPin = 36; // AFE1_AD7/PC4 Vin monitor const Pin PowerMonitor5vDetectPin = 29; // AFE1_AD1/PB3 5V regulator input monitor -const float PowerFailVoltageRange = 11.0 * 3.3; // we use an 11:1 voltage divider +const float PowerMonitorVoltageRange = 11.0 * 3.3; // We use an 11:1 voltage divider const Pin VssaSensePin = 103; diff --git a/src/Fan.cpp b/src/Fan.cpp index 46cb3b9b..8063fc1c 100644 --- a/src/Fan.cpp +++ b/src/Fan.cpp @@ -319,4 +319,22 @@ void Fan::Disable() pin = NoPin; } +#ifdef DUET_NG + +// Save the settings of this fan if it isn't thermostatic +bool Fan::WriteSettings(FileStore *f, size_t fanNum) const +{ + if (heatersMonitored == 0) + { + char bufSpace[50]; + StringRef buf(bufSpace, ARRAY_SIZE(bufSpace)); + buf.printf("M106 P%u S%.2f\n", fanNum, val); + return f->Write(buf.Pointer()); + } + + return true; +} + +#endif + // End @@ -31,6 +31,10 @@ public: void Check(); void Disable(); +#ifdef DUET_NG + bool WriteSettings(FileStore *f, size_t fanNum) const; // Save the settings of this fan if it isn't thermostatic +#endif + private: float val; float lastVal; diff --git a/src/GCodes/GCodeBuffer.cpp b/src/GCodes/GCodeBuffer.cpp index fcc18068..fe2e41cb 100644 --- a/src/GCodes/GCodeBuffer.cpp +++ b/src/GCodes/GCodeBuffer.cpp @@ -490,7 +490,7 @@ void GCodeBuffer::TryGetIValue(char c, int32_t& val, bool& seen) } // Try to get a float array exactly 'numVals' long after parameter letter 'c'. -// If the wrong number of value is provided, generate an error message and return true. +// If the wrong number of values is provided, generate an error message and return true. // Else set 'seen' if we saw the letter and value, and return false. bool GCodeBuffer::TryGetFloatArray(char c, size_t numVals, float vals[], StringRef& reply, bool& seen) { diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp index abb830b7..266a413d 100644 --- a/src/GCodes/GCodes.cpp +++ b/src/GCodes/GCodes.cpp @@ -39,14 +39,7 @@ #endif const char GCodes::axisLetters[MaxAxes] = AXES_('X', 'Y', 'Z', 'U', 'V', 'W', 'A', 'B', 'C'); - -const char* const PAUSE_G = "pause.g"; -const char* const HomingFileNames[MaxAxes] = AXES_("homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g", "homea.g", "homeb.g", "homec.g"); -const char* const HOME_ALL_G = "homeall.g"; -const char* const HOME_DELTA_G = "homedelta.g"; -const char* const DefaultHeightMapFile = "heightmap.csv"; -const char* const LOAD_FILAMENT_G = "load.g"; -const char* const UNLOAD_FILAMENT_G = "unload.g"; +const char* const GCodes::HomingFileNames[MaxAxes] = AXES_("homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g", "homea.g", "homeb.g", "homec.g"); const size_t gcodeReplyLength = 2048; // long enough to pass back a reasonable number of files in response to M20 @@ -67,7 +60,9 @@ GCodes::GCodes(Platform& p) : auxGCode = new GCodeBuffer("aux", AUX_MESSAGE, false); daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE, false); queuedGCode = new GCodeBuffer("queue", GENERIC_MESSAGE, false); - +#ifdef DUET_NG + autoPauseGCode = new GCodeBuffer("autopause", GENERIC_MESSAGE, false); +#endif codeQueue = new GCodeQueue(); } @@ -132,6 +127,10 @@ void GCodes::Reset() auxGCode->Reset(); auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port daemonGCode->Reset(); + queuedGCode->Reset(); +#ifdef DUET_NG + autoPauseGCode->Reset(); +#endif nextGcodeSource = 0; @@ -152,6 +151,7 @@ void GCodes::Reset() ClearMove(); ClearBabyStepping(); moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; #if SUPPORT_IOBITS moveBuffer.ioBits = 0; #endif @@ -176,6 +176,9 @@ void GCodes::Reset() simulationMode = 0; simulationTime = 0.0; isPaused = false; +#ifdef DUET_NG + isAutoPaused = resumeInfoSaved = false; +#endif doingToolChange = false; doingManualBedProbe = false; moveBuffer.filePos = noFilePosition; @@ -348,6 +351,12 @@ void GCodes::Spin() case GCodeState::toolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools) and run tpost case GCodeState::m109ToolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools) and run tpost reprap.SelectTool(newToolNumber); + + // The user position reflects the position of the old tool, but on an IDEX machine the new tool is at a different place + // Also tool offsets may have changed, but as some axes may not have been homed we should avoid moving those axes when the next movement command is given. + // So adjust the current user position to reflect the actual position of the tool. + ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); + gb.AdvanceState(); if (AllAxesAreHomed()) { @@ -357,12 +366,6 @@ void GCodes::Spin() DoFileMacro(gb, scratchString.Pointer(), false); } } - else - { - // Tool offsets may have changed, but as some axes have not been homed we should avoid moving those axes when the next movement command is given. - // So adjust the current user position to reflect the actual position of the tool. - ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); - } break; case GCodeState::toolChangeComplete: @@ -391,7 +394,11 @@ void GCodes::Spin() gb.SetState(GCodeState::pausing2); if (AllAxesAreHomed()) { +#ifdef DUET_NG + DoFileMacro(gb, (isAutoPaused) ? POWER_FAIL_G : PAUSE_G, true); +#else DoFileMacro(gb, PAUSE_G, true); +#endif } } break; @@ -399,6 +406,9 @@ void GCodes::Spin() case GCodeState::pausing2: if (LockMovementAndWaitForStandstill(gb)) { +#ifdef DUET_NG + reprap.GetHeat().SuspendHeaters(false); // resume the heaters, we may have turned them off while we executed the pause macro +#endif reply.copy("Printing paused"); gb.SetState(GCodeState::normal); } @@ -549,6 +559,7 @@ void GCodes::Spin() moveBuffer.coords[Z_AXIS] = platform.GetZProbeStartingHeight(); moveBuffer.feedRate = platform.GetZProbeTravelSpeed(); moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.AdvanceState(); } @@ -604,6 +615,7 @@ void GCodes::Spin() moveBuffer.coords[Z_AXIS] = -platform.GetZProbeDiveHeight(); moveBuffer.feedRate = platform.GetCurrentZProbeParameters().probeSpeed; moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.AdvanceState(); } @@ -643,6 +655,7 @@ void GCodes::Spin() moveBuffer.coords[Z_AXIS] = platform.GetZProbeStartingHeight(); moveBuffer.feedRate = platform.GetZProbeTravelSpeed(); moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.SetState(GCodeState::gridProbing4); } @@ -717,6 +730,7 @@ void GCodes::Spin() moveBuffer.coords[Z_AXIS] = platform.GetZProbeStartingHeight(); moveBuffer.feedRate = platform.GetZProbeTravelSpeed(); moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.AdvanceState(); @@ -777,6 +791,7 @@ void GCodes::Spin() : -1.1 * platform.AxisTotalLength(Z_AXIS); // Z axis not homed yet, so treat this as a homing move moveBuffer.feedRate = platform.GetCurrentZProbeParameters().probeSpeed; moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.AdvanceState(); } @@ -883,6 +898,7 @@ void GCodes::Spin() moveBuffer.coords[Z_AXIS] = platform.GetZProbeStartingHeight(); moveBuffer.feedRate = platform.GetZProbeTravelSpeed(); moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.SetState(GCodeState::normal); break; @@ -893,7 +909,8 @@ void GCodes::Spin() if (segmentsLeft == 0) { const uint32_t xAxes = reprap.GetCurrentXAxes(); - reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, xAxes); + const uint32_t yAxes = reprap.GetCurrentYAxes(); + reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, xAxes, yAxes); for (size_t i = numTotalAxes; i < DRIVES; ++i) { moveBuffer.coords[i] = 0.0; @@ -907,6 +924,7 @@ void GCodes::Spin() moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; moveBuffer.canPauseAfter = false; // don't pause after a retraction because that could cause too much retraction moveBuffer.xAxes = xAxes; + moveBuffer.yAxes = yAxes; segmentsLeft = 1; gb.SetState(GCodeState::normal); } @@ -920,7 +938,8 @@ void GCodes::Spin() if (tool != nullptr) { const uint32_t xAxes = reprap.GetCurrentXAxes(); - reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, xAxes); + const uint32_t yAxes = reprap.GetCurrentYAxes(); + reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, xAxes, yAxes); for (size_t i = numTotalAxes; i < DRIVES; ++i) { moveBuffer.coords[i] = 0.0; @@ -936,6 +955,7 @@ void GCodes::Spin() moveBuffer.filePos = (&gb == fileGCode) ? gb.MachineState().fileState.GetPosition() - fileInput->BytesCached() : noFilePosition; moveBuffer.canPauseAfter = true; moveBuffer.xAxes = xAxes; + moveBuffer.yAxes = yAxes; segmentsLeft = 1; } gb.SetState(GCodeState::normal); @@ -1100,6 +1120,9 @@ void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply) fd.Close(); UnlockAll(gb); reprap.GetPrintMonitor().StoppedPrint(); +#ifdef DUET_NG + platform.GetMassStorage()->Delete(platform.GetSysDir(), RESUME_AFTER_POWER_FAIL_G); +#endif if (platform.Emulating() == marlin) { // Pronterface expects a "Done printing" message @@ -1172,7 +1195,7 @@ void GCodes::CheckTriggers() else if (LockMovement(*daemonGCode)) // need to lock movement before executing the pause macro { triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger - DoPause(*daemonGCode); + DoPause(*daemonGCode, false); } } else @@ -1195,7 +1218,7 @@ void GCodes::DoEmergencyStop() } // Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock. -void GCodes::DoPause(GCodeBuffer& gb) +void GCodes::DoPause(GCodeBuffer& gb, bool isAuto) { if (&gb == fileGCode) { @@ -1287,8 +1310,180 @@ void GCodes::DoPause(GCodeBuffer& gb) SaveFanSpeeds(); gb.SetState(GCodeState::pausing1); isPaused = true; + +#ifdef DUET_NG + isAutoPaused = isAuto; + resumeInfoSaved = false; +#endif +} + +bool GCodes::IsPaused() const +{ + return isPaused && !IsPausing() && !IsResuming(); +} + +bool GCodes::IsPausing() const +{ + const GCodeState topState = fileGCode->OriginalMachineState().state; + return topState == GCodeState::pausing1 || topState == GCodeState::pausing2; +} + +bool GCodes::IsResuming() const +{ + const GCodeState topState = fileGCode->OriginalMachineState().state; + return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3; +} + +bool GCodes::IsRunning() const +{ + return !IsPaused() && !IsPausing() && !IsResuming(); +} + +#ifdef DUET_NG + +// Try to pause the current SD card print, returning true if successful, false if needs to be called again +bool GCodes::AutoPause() +{ + if (IsPausing() || IsResuming()) + { + return false; + } + + if (isPaused) + { + if (!resumeInfoSaved) + { + SaveResumeInfo(); + } + } + else if (reprap.GetPrintMonitor().IsPrinting()) + { + if (!LockMovement(*autoPauseGCode)) + { + return false; + } + reprap.GetHeat().SuspendHeaters(true); // turn heaters off to conserve power for the motors to execute the pause + DoPause(*autoPauseGCode, true); + SaveResumeInfo(); + platform.Message(GENERIC_MESSAGE, "Print auto-paused due to low voltage\n"); + } + return true; +} + +// Suspend the printer, only ever called after it has been auto paused +bool GCodes::AutoShutdown() +{ + if (isPaused && isAutoPaused) + { + if (!LockMovementAndWaitForStandstill(*autoPauseGCode)) + { + return false; + } + CancelPrint(); + platform.Message(GENERIC_MESSAGE, "Print cancelled due to low voltage\n"); + return true; + } + return true; } +// Resume printing, normally only ever called after it has been auto paused +bool GCodes::AutoResume() +{ + if (isPaused && isAutoPaused) + { + autoPauseGCode->SetState(GCodeState::resuming1); + if (AllAxesAreHomed()) + { + DoFileMacro(*autoPauseGCode, POWER_RESTORE_G, true); + } + platform.Message(GENERIC_MESSAGE, "Print auto-resumed\n"); + } + return true; +} + +// Permit printing to be resumed after it has been suspended +bool GCodes::AutoResumeAfterShutdown() +{ + // Currently we don't do anything here + return true; +} + +void GCodes::SaveResumeInfo() +{ + const char* const printingFilename = reprap.GetPrintMonitor().GetPrintingFilename(); + if (printingFilename != nullptr) + { + FileStore * const f = platform.GetFileStore(platform.GetSysDir(), RESUME_AFTER_POWER_FAIL_G, true); + if (f == nullptr) + { + platform.MessageF(GENERIC_MESSAGE, "Error: failed to create file %s", RESUME_AFTER_POWER_FAIL_G); + } + else + { + char bufferSpace[200]; + StringRef buf(bufferSpace, ARRAY_SIZE(bufferSpace)); + + // Write the header comment + buf.printf("; File \"%s\" resume print after auto-pause", printingFilename); + if (reprap.GetPlatform().IsDateTimeSet()) + { + time_t timeNow = reprap.GetPlatform().GetDateTime(); + const struct tm * const timeInfo = gmtime(&timeNow); + buf.catf(" at %04u-%02u-%02u %02u:%02u", + timeInfo->tm_year + 1900, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min); + } + buf.cat('\n'); + bool ok = f->Write(buf.Pointer()) + && reprap.GetHeat().WriteBedAndChamberTempSettings(f) // turn on bed and chamber heaters + && reprap.WriteToolSettings(f) // set tool temperatures, tool mix ratios etc. + && reprap.GetMove().WriteResumeSettings(f); // load grid, if we are using one + if (ok) + { + buf.printf("M98 P%s\n", RESUME_PROLOGUE_G); // call the prologue - must contain at least M116 + ok = f->Write(buf.Pointer()) + && platform.WriteFanSettings(f); // set the speeds of non-thermostatic fans + } + if (ok) + { + buf.printf("M106 S%.2f\n", lastDefaultFanSpeed); + ok = f->Write(buf.Pointer()); + } + if (ok) + { + buf.printf("M116\nM290 S%.3f\nM23 %s\nM26 S%u\n", currentBabyStepZOffset, printingFilename, pauseRestorePoint.filePos); + ok = f->Write(buf.Pointer()); // write baby stepping offset, filename and file position + } + if (ok) + { + buf.copy("G1 F6000"); // start building command to restore head position + for (size_t axis = 0; axis < numVisibleAxes; ++axis) + { + buf.catf(" %c%.2f", axisLetters[axis], pauseRestorePoint.moveCoords[axis]); + } + buf.catf("\nG1 F%.1f P%u\nM24\n", pauseRestorePoint.feedRate * MinutesToSeconds, (unsigned int)pauseRestorePoint.ioBits); + ok = f->Write(buf.Pointer()); // restore feed rate and output bits + } + if (!f->Close()) + { + ok = false; + } + if (ok) + { + platform.Message(GENERIC_MESSAGE, "Resume-after-power-fail state saved\n"); + } + else + { + platform.GetMassStorage()->Delete(platform.GetSysDir(), RESUME_AFTER_POWER_FAIL_G); + platform.MessageF(GENERIC_MESSAGE, "Error: failed to write or close file %s\n", RESUME_AFTER_POWER_FAIL_G); + } + } + + resumeInfoSaved = true; // say we saved it even if there was an error, to avoid constant retrying + } +} + +#endif + void GCodes::Diagnostics(MessageType mtype) { platform.Message(mtype, "=== GCodes ===\n"); @@ -1328,7 +1523,7 @@ bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb) } // Get the current positions. These may not be the same as the ones we remembered from last time if we just did a special move. - reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); + reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); memcpy(moveBuffer.initialCoords, moveBuffer.coords, numVisibleAxes * sizeof(moveBuffer.initialCoords[0])); ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); return true; @@ -1457,6 +1652,7 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, StringRef& reply) moveBuffer.moveType = 0; doingArcMove = false; moveBuffer.xAxes = reprap.GetCurrentXAxes(); + moveBuffer.yAxes = reprap.GetCurrentYAxes(); moveBuffer.usePressureAdvance = false; moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; moveBuffer.virtualExtruderPosition = GetVirtualExtruderPosition(); @@ -1470,6 +1666,7 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, StringRef& reply) { moveBuffer.moveType = ival; moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; } if (ival == 1 || ival == 3) @@ -1547,7 +1744,7 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, StringRef& reply) if (moveBuffer.moveType != 0) { // This is a raw motor move, so we need the current raw motor positions in moveBuffer.coords - reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes()); + reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); } // Set up the initial coordinates @@ -1674,6 +1871,11 @@ bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) memcpy(moveBuffer.initialCoords, moveBuffer.coords, numVisibleAxes * sizeof(moveBuffer.initialCoords[0])); + // Save the arc centre user coordinates for later + const float userArcCentreX = currentUserPosition[X_AXIS] + iParam; + const float userArcCentreY = currentUserPosition[Y_AXIS] + jParam; + + // Work out the new user position const bool axesRelative = gb.MachineState().axesRelative; if (axesRelative) { @@ -1706,31 +1908,39 @@ bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position } - // Set up the arc centre coordinates and record which axes behave like an X axis. - // The I and J parameters are always relative to present position. - // For X we need to set up the arc centre for each axis that X is mapped to. - // For simplicity we assume that X may be mapped to all axes except Y, so we set up the arc centre for all of those axes. - arcAxesMoving = reprap.GetCurrentXAxes() & ~((1 << Y_AXIS) | (1 << Z_AXIS)); - for (size_t axis = 0; axis < numVisibleAxes; ++axis) - { - arcCentre[axis] = moveBuffer.initialCoords[axis] + (axis == Y_AXIS) ? jParam : iParam; - } + // Compute the angle at which we stop + const float finalTheta = atan2(currentUserPosition[Y_AXIS] - userArcCentreY, currentUserPosition[X_AXIS] - userArcCentreX); // Set up default move parameters moveBuffer.endStopsToCheck = 0; moveBuffer.moveType = 0; moveBuffer.canPauseAfter = moveBuffer.canPauseBefore = true; moveBuffer.xAxes = reprap.GetCurrentXAxes(); + moveBuffer.yAxes = reprap.GetCurrentYAxes(); moveBuffer.virtualExtruderPosition = GetVirtualExtruderPosition(); moveBuffer.endStopsToCheck = 0; moveBuffer.usePressureAdvance = true; moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; + // Set up the arc centre coordinates and record which axes behave like an X axis. + // The I and J parameters are always relative to present position. + // For X and Y we need to set up the arc centre for each axis that X or Y is mapped to. + for (size_t axis = 0; axis < numVisibleAxes; ++axis) + { + if ((moveBuffer.xAxes & (1u << axis)) != 0) + { + arcCentre[axis] = moveBuffer.initialCoords[axis] + iParam; + } + else if ((moveBuffer.yAxes & (1u << axis)) != 0) + { + arcCentre[axis] = moveBuffer.initialCoords[axis] + jParam; + } + } + LoadExtrusionAndFeedrateFromGCode(gb, moveBuffer.moveType); arcRadius = sqrtf(iParam * iParam + jParam * jParam); arcCurrentAngle = atan2(-jParam, -iParam); - const float finalTheta = atan2(moveBuffer.coords[Y_AXIS] - arcCentre[Y_AXIS], moveBuffer.coords[X_AXIS] - arcCentre[X_AXIS]); // Calculate the total angle moved, which depends on which way round we are going float totalArc = (clockwise) ? arcCurrentAngle - finalTheta : finalTheta - arcCurrentAngle; @@ -1738,14 +1948,17 @@ bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) { totalArc += 2 * PI; } - arcAngleIncrement = totalArc/segmentsLeft; + + // Compute how many segments we need to move, but don't store it yet + const unsigned int segsLeft = max<unsigned int>((unsigned int)((arcRadius * totalArc)/arcSegmentLength + 0.8), 1u); + arcAngleIncrement = totalArc/segsLeft; if (clockwise) { arcAngleIncrement = -arcAngleIncrement; } doingArcMove = true; - segmentsLeft = max<unsigned int>((unsigned int)((arcRadius * totalArc)/arcSegmentLength + 0.8), 1); // must do this last for RTOS + segmentsLeft = segsLeft; // must do this last for RTOS // debugPrintf("Radius %.2f, initial angle %.1f, increment %.1f, segments %u\n", // arcRadius, arcCurrentAngle * RadiansToDegrees, arcAngleIncrement * RadiansToDegrees, segmentsLeft); return false; @@ -1782,11 +1995,12 @@ bool GCodes::ReadMove(RawMove& m) { if (doingArcMove && drive != Z_AXIS) { - if (drive == Y_AXIS) + if ((moveBuffer.yAxes & (1u << drive)) != 0) { + // Y axis or a substitute Y axis moveBuffer.initialCoords[drive] = arcCentre[drive] + arcRadius * sinf(arcCurrentAngle); } - else if ((arcAxesMoving & (1 << drive)) != 0) + else if ((moveBuffer.xAxes & (1u << drive)) != 0) { // X axis or a substitute X axis moveBuffer.initialCoords[drive] = arcCentre[drive] + arcRadius * cosf(arcCurrentAngle); @@ -1866,22 +2080,21 @@ bool GCodes::SetPositions(GCodeBuffer& gb) { // Don't wait for the machine to stop if only extruder drives are being reset. // This avoids blobs and seams when the gcode uses absolute E coordinates and periodically includes G92 E0. - bool includingAxes = false; + uint32_t axesIncluded = 0; for (size_t axis = 0; axis < numVisibleAxes; ++axis) { if (gb.Seen(axisLetters[axis])) { const float axisValue = gb.GetFValue(); - if (!includingAxes) + if (axesIncluded == 0) { if (!LockMovementAndWaitForStandstill(gb)) // lock movement and get current coordinates { return false; } - includingAxes = true; } + axesIncluded |= (1u << axis); currentUserPosition[axis] = axisValue; - SetAxisIsHomed(axis); } } @@ -1912,14 +2125,15 @@ bool GCodes::SetPositions(GCodeBuffer& gb) } } - if (includingAxes) + if (axesIncluded != 0) { ToolOffsetTransform(currentUserPosition, moveBuffer.coords); - if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, numVisibleAxes, axesHomed)) + if (reprap.GetMove().GetKinematics().LimitPosition(moveBuffer.coords, numVisibleAxes, (1u << numVisibleAxes) - 1)) // pretend that all axes are homed { ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); // make sure the limits are reflected in the user position } reprap.GetMove().SetNewPosition(moveBuffer.coords, true); + axesHomed |= reprap.GetMove().GetKinematics().AxesAssumedHomed(axesIncluded); #if SUPPORT_ROLAND if (reprap.GetRoland()->Active()) @@ -1985,7 +2199,6 @@ bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error) { // Homing on a delta printer uses homedelta.g instead of homeall.g and we can only home all towers at once SetAllAxesNotHomed(); - ClearBabyStepping(); DoFileMacro(gb, HOME_DELTA_G, true); } else @@ -2000,10 +2213,6 @@ bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error) } } - if (toBeHomed == 0 || (toBeHomed & (1u << Z_AXIS)) != 0) - { - ClearBabyStepping(); - } if (toBeHomed == 0 || toBeHomed == ((1u << numTotalAxes) - 1)) { // Homing everything @@ -2073,6 +2282,7 @@ bool GCodes::ExecuteG30(GCodeBuffer& gb, StringRef& reply) moveBuffer.coords[Z_AXIS] = platform.GetZProbeStartingHeight(); moveBuffer.feedRate = platform.GetZProbeTravelSpeed(); moveBuffer.xAxes = DefaultXAxisMapping; + moveBuffer.yAxes = DefaultYAxisMapping; segmentsLeft = 1; gb.SetState(GCodeState::probingAtPoint1); } @@ -2361,7 +2571,7 @@ bool GCodes::SaveHeightMap(GCodeBuffer& gb, StringRef& reply) const void GCodes::GetCurrentCoordinates(StringRef& s) const { float liveCoordinates[DRIVES]; - reprap.GetMove().LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes()); + reprap.GetMove().LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); const Tool * const currentTool = reprap.GetCurrentTool(); if (currentTool != nullptr) { @@ -2507,6 +2717,7 @@ void GCodes::QueueFileToPrint(const char* fileName) reprap.GetMove().ResetExtruderPositions(); fileToPrint.Set(f); + fileOffsetToPrint = 0; } else { @@ -2591,7 +2802,7 @@ bool GCodes::DoDwellTime(GCodeBuffer& gb, uint32_t dwellMillis) } // Set offset, working and standby temperatures for a tool. I.e. handle a G10. -bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply) +bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply, bool& error) { Tool *tool; if (gb.Seen('P')) @@ -2603,16 +2814,17 @@ bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply) if (tool == nullptr) { reply.printf("Attempt to set/report offsets and temperatures for non-existent tool: %d", toolNumber); + error = true; return true; } } else { tool = reprap.GetCurrentTool(); - if (tool == nullptr) { reply.printf("Attempt to set/report offsets and temperatures for no selected tool"); + error = true; return true; } } @@ -2683,7 +2895,8 @@ bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply) return true; } -void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) +// Create a new tool definition, returning true if an error was reported +bool GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) { if (!gb.Seen('P')) { @@ -2693,7 +2906,7 @@ void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) int adjust = gb.GetIValue(); gb.SetToolNumberAdjust(adjust); } - return; + return false; } // Check tool number @@ -2701,8 +2914,8 @@ void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) const int toolNumber = gb.GetIValue(); if (toolNumber < 0) { - platform.Message(GENERIC_MESSAGE, "Error: Tool number must be positive!\n"); - return; + reply.copy("Tool number must be positive"); + return true; } // Check tool name @@ -2711,8 +2924,8 @@ void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) { if (!gb.GetQuotedString(name, ARRAY_SIZE(name))) { - platform.Message(GENERIC_MESSAGE, "Error: Invalid tool name!\n"); - return; + reply.copy("Invalid tool name"); + return true; } seen = true; } @@ -2759,7 +2972,28 @@ void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) } else { - xMap = 1; // by default map X axis straight through + xMap = DefaultXAxisMapping; // by default map X axis straight through + } + + // Check Y axis mapping + uint32_t yMap; + if (gb.Seen('Y')) + { + long yMapping[MaxAxes]; + size_t yCount = numVisibleAxes; + gb.GetLongArray(yMapping, yCount); + xMap = LongArrayToBitMap(yMapping, yCount) & ((1u << numVisibleAxes) - 1); + seen = true; + } + else + { + yMap = DefaultYAxisMapping; // by default map X axis straight through + } + + if ((xMap & yMap) != 0) + { + reply.copy("Cannot map bith X and Y to the aame axis"); + return true; } // Check for fan mapping @@ -2789,7 +3023,7 @@ void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) } else { - Tool* tool = Tool::Create(toolNumber, name, drives, dCount, heaters, hCount, xMap, fanMap); + Tool* const tool = Tool::Create(toolNumber, name, drives, dCount, heaters, hCount, xMap, yMap, fanMap); if (tool != nullptr) { reprap.AddTool(tool); @@ -2800,6 +3034,7 @@ void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply) { reprap.PrintTool(toolNumber, reply); } + return false; } // Does what it says. @@ -2823,12 +3058,11 @@ void GCodes::SetMACAddress(GCodeBuffer& gb) { if (ipString[sp] == ':') { - mac[ipp] = strtoul(&ipString[spp], NULL, 16); + mac[ipp] = strtoul(&ipString[spp], nullptr, 16); ipp++; if (ipp > 5) { - platform.MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer()); - return; + break; } sp++; spp = sp; @@ -2838,9 +3072,9 @@ void GCodes::SetMACAddress(GCodeBuffer& gb) sp++; } } - mac[ipp] = strtoul(&ipString[spp], NULL, 16); if (ipp == 5) { + mac[ipp] = strtoul(&ipString[spp], nullptr, 16); platform.SetMACAddress(mac); } else @@ -2922,66 +3156,66 @@ void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply) switch (c) { - case me: - case reprapFirmware: - if (error) - { - platform.Message(type, "Error: "); - } + case me: + case reprapFirmware: + if (error) + { + platform.Message(type, "Error: "); + } + platform.Message(type, reply); + platform.Message(type, "\n"); + return; + + case marlin: + // We don't need to handle M20 here because we always allocate an output buffer for that one + if (gb.Seen('M') && gb.GetIValue() == 28) + { + platform.Message(type, response); + platform.Message(type, "\n"); platform.Message(type, reply); platform.Message(type, "\n"); return; + } - case marlin: - // We don't need to handle M20 here because we always allocate an output buffer for that one - if (gb.Seen('M') && gb.GetIValue() == 28) - { - platform.Message(type, response); - platform.Message(type, "\n"); - platform.Message(type, reply); - platform.Message(type, "\n"); - return; - } - - if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998)) - { - platform.Message(type, response); - platform.Message(type, " "); - platform.Message(type, reply); - platform.Message(type, "\n"); - return; - } - - if (reply[0] != 0 && !gb.IsDoingFileMacro()) - { - platform.Message(type, reply); - platform.Message(type, "\n"); - platform.Message(type, response); - platform.Message(type, "\n"); - } - else if (reply[0] != 0) - { - platform.Message(type, reply); - platform.Message(type, "\n"); - } - else - { - platform.Message(type, response); - platform.Message(type, "\n"); - } + if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998)) + { + platform.Message(type, response); + platform.Message(type, " "); + platform.Message(type, reply); + platform.Message(type, "\n"); return; + } - case teacup: - emulationType = "teacup"; - break; - case sprinter: - emulationType = "sprinter"; - break; - case repetier: - emulationType = "repetier"; - break; - default: - emulationType = "unknown"; + if (reply[0] != 0 && !gb.IsDoingFileMacro()) + { + platform.Message(type, reply); + platform.Message(type, "\n"); + platform.Message(type, response); + platform.Message(type, "\n"); + } + else if (reply[0] != 0) + { + platform.Message(type, reply); + platform.Message(type, "\n"); + } + else + { + platform.Message(type, response); + platform.Message(type, "\n"); + } + return; + + case teacup: + emulationType = "teacup"; + break; + case sprinter: + emulationType = "sprinter"; + break; + case repetier: + emulationType = "repetier"; + break; + default: + emulationType = "unknown"; } if (emulationType != 0) @@ -3012,72 +3246,72 @@ void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply) switch (c) { - case me: - case reprapFirmware: - if (error) - { - platform.Message(type, "Error: "); - } + case me: + case reprapFirmware: + if (error) + { + platform.Message(type, "Error: "); + } + platform.Message(type, reply); + return; + + case marlin: + if (gb.Seen('M') && gb.GetIValue() == 20) + { + platform.Message(type, "Begin file list\n"); platform.Message(type, reply); + platform.Message(type, "End file list\n"); + platform.Message(type, response); + platform.Message(type, "\n"); return; + } - case marlin: - if (gb.Seen('M') && gb.GetIValue() == 20) - { - platform.Message(type, "Begin file list\n"); - platform.Message(type, reply); - platform.Message(type, "End file list\n"); - platform.Message(type, response); - platform.Message(type, "\n"); - return; - } - - if (gb.Seen('M') && gb.GetIValue() == 28) - { - platform.Message(type, response); - platform.Message(type, "\n"); - platform.Message(type, reply); - return; - } - - if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998)) - { - platform.Message(type, response); - platform.Message(type, " "); - platform.Message(type, reply); - return; - } + if (gb.Seen('M') && gb.GetIValue() == 28) + { + platform.Message(type, response); + platform.Message(type, "\n"); + platform.Message(type, reply); + return; + } - if (reply->Length() != 0 && !gb.IsDoingFileMacro()) - { - platform.Message(type, reply); - platform.Message(type, "\n"); - platform.Message(type, response); - platform.Message(type, "\n"); - } - else if (reply->Length() != 0) - { - platform.Message(type, reply); - } - else - { - OutputBuffer::ReleaseAll(reply); - platform.Message(type, response); - platform.Message(type, "\n"); - } + if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998)) + { + platform.Message(type, response); + platform.Message(type, " "); + platform.Message(type, reply); return; + } - case teacup: - emulationType = "teacup"; - break; - case sprinter: - emulationType = "sprinter"; - break; - case repetier: - emulationType = "repetier"; - break; - default: - emulationType = "unknown"; + if (reply->Length() != 0 && !gb.IsDoingFileMacro()) + { + platform.Message(type, reply); + platform.Message(type, "\n"); + platform.Message(type, response); + platform.Message(type, "\n"); + } + else if (reply->Length() != 0) + { + platform.Message(type, reply); + } + else + { + OutputBuffer::ReleaseAll(reply); + platform.Message(type, response); + platform.Message(type, "\n"); + } + return; + + case teacup: + emulationType = "teacup"; + break; + case sprinter: + emulationType = "sprinter"; + break; + case repetier: + emulationType = "repetier"; + break; + default: + emulationType = "unknown"; } // If we get here then we didn't handle the message, so release the buffer(s) @@ -3129,7 +3363,7 @@ bool GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply) { if (gb.Seen('P')) { - int heater = gb.GetIValue(); + const int heater = gb.GetIValue(); if ((heater >= 0 && heater < (int)Heaters) || (heater >= (int)FirstVirtualHeater && heater < (int)(FirstVirtualHeater + MaxVirtualHeaters))) { Heat& heat = reprap.GetHeat(); @@ -3175,7 +3409,7 @@ bool GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply) void GCodes::SetToolHeaters(Tool *tool, float temperature) { - if (tool == NULL) + if (tool == nullptr) { platform.Message(GENERIC_MESSAGE, "Setting temperature: no tool selected.\n"); return; @@ -3196,6 +3430,11 @@ bool GCodes::RetractFilament(GCodeBuffer& gb, bool retract) { if (retract != isRetracted && (retractLength != 0.0 || retractHop != 0.0 || (!retract && retractExtra != 0.0))) { + if (!LockMovement(gb)) + { + return false; + } + if (segmentsLeft != 0) { return false; @@ -3204,7 +3443,8 @@ bool GCodes::RetractFilament(GCodeBuffer& gb, bool retract) // New code does the retraction and the Z hop as separate moves // Get ready to generate a move const uint32_t xAxes = reprap.GetCurrentXAxes(); - reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, xAxes); + const uint32_t yAxes = reprap.GetCurrentYAxes(); + reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, xAxes, yAxes); for (size_t i = numTotalAxes; i < DRIVES; ++i) { moveBuffer.coords[i] = 0.0; @@ -3214,6 +3454,7 @@ bool GCodes::RetractFilament(GCodeBuffer& gb, bool retract) moveBuffer.usePressureAdvance = false; moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; moveBuffer.xAxes = xAxes; + moveBuffer.yAxes = yAxes; if (retract) { @@ -3434,7 +3675,7 @@ void GCodes::SetMachinePosition(const float positionNow[DRIVES], bool doBedCompe // Get the current position from the Move class void GCodes::GetCurrentUserPosition() { - reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); + reprap.GetMove().GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); ToolOffsetInverseTransform(moveBuffer.coords, currentUserPosition); } @@ -3487,12 +3728,17 @@ void GCodes::ToolOffsetTransform(const float coordsIn[MaxAxes], float coordsOut[ else { const uint32_t xAxes = currentTool->GetXAxisMap(); + const uint32_t yAxes = currentTool->GetYAxisMap(); for (size_t axis = 0; axis < numVisibleAxes; ++axis) { - if (axis != X_AXIS || (xAxes & (1 << X_AXIS)) != 0) + if ( (axis != X_AXIS || (xAxes & (1 << X_AXIS)) != 0) + && (axis != Y_AXIS || (yAxes & (1 << Y_AXIS)) != 0) + ) { const float totalOffset = currentTool->GetOffset()[axis] + axisOffsets[axis]; - const size_t inputAxis = ((xAxes & (1u << axis)) != 0) ? X_AXIS : axis; + const size_t inputAxis = ((xAxes & (1u << axis)) != 0) ? X_AXIS + : ((yAxes & (1u << axis)) != 0) ? Y_AXIS + : axis; coordsOut[axis] = (coordsIn[inputAxis] * axisScaleFactors[axis]) - totalOffset; } } @@ -3500,7 +3746,7 @@ void GCodes::ToolOffsetTransform(const float coordsIn[MaxAxes], float coordsOut[ coordsOut[Z_AXIS] += (currentZHop + currentBabyStepZOffset); } -// Convert head reference point coordinates to user coordinates, allowing for X axis mapping +// Convert head reference point coordinates to user coordinates, allowing for XY axis mapping // Caution: coordsIn and coordsOut may address the same array! void GCodes::ToolOffsetInverseTransform(const float coordsIn[MaxAxes], float coordsOut[MaxAxes]) { @@ -3515,8 +3761,9 @@ void GCodes::ToolOffsetInverseTransform(const float coordsIn[MaxAxes], float coo else { const uint32_t xAxes = reprap.GetCurrentXAxes(); - float xCoord = 0.0; - size_t numXAxes = 0; + const uint32_t yAxes = reprap.GetCurrentYAxes(); + float xCoord = 0.0, yCoord = 0.0; + size_t numXAxes = 0, numYAxes = 0; for (size_t axis = 0; axis < numVisibleAxes; ++axis) { coordsOut[axis] = coordsIn[axis] + currentTool->GetOffset()[axis]; @@ -3524,37 +3771,23 @@ void GCodes::ToolOffsetInverseTransform(const float coordsIn[MaxAxes], float coo { xCoord += coordsIn[axis]/axisScaleFactors[axis] + currentTool->GetOffset()[axis]; } + if ((yAxes & (1u << axis)) != 0) + { + yCoord += coordsIn[axis]/axisScaleFactors[axis] + currentTool->GetOffset()[axis]; + } } if (numXAxes != 0) { coordsOut[X_AXIS] = xCoord/numXAxes; } + if (numYAxes != 0) + { + coordsOut[Y_AXIS] = yCoord/numYAxes; + } } coordsOut[Z_AXIS] -= (currentZHop + currentBabyStepZOffset)/axisScaleFactors[Z_AXIS]; } -bool GCodes::IsPaused() const -{ - return isPaused && !IsPausing() && !IsResuming(); -} - -bool GCodes::IsPausing() const -{ - const GCodeState topState = fileGCode->OriginalMachineState().state; - return topState == GCodeState::pausing1 || topState == GCodeState::pausing2; -} - -bool GCodes::IsResuming() const -{ - const GCodeState topState = fileGCode->OriginalMachineState().state; - return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3; -} - -bool GCodes::IsRunning() const -{ - return !IsPaused() && !IsPausing() && !IsResuming(); -} - const char *GCodes::TranslateEndStopResult(EndStopHit es) { switch (es) diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h index a722aa15..0b46c304 100644 --- a/src/GCodes/GCodes.h +++ b/src/GCodes/GCodes.h @@ -63,7 +63,7 @@ struct Trigger } }; -// Bits for T-code P-parameter to specify which macros are supposted to be run +// Bits for T-code P-parameter to specify which macros are supposed to be run const int TFreeBit = 1 << 0; const int TPreBit = 1 << 1; const int TPostBit = 1 << 2; @@ -84,6 +84,7 @@ public: float virtualExtruderPosition; // the virtual extruder position of the current tool at the start of this move FilePosition filePos; // offset in the file being printed at the start of reading this move uint32_t xAxes; // axes that X is mapped to + uint32_t yAxes; // axes that Y is mapped to EndstopChecks endStopsToCheck; // endstops to check #if SUPPORT_IOBITS IoBits_t ioBits; // I/O bits to set/clear at the start of this move @@ -151,7 +152,14 @@ public: size_t GetVisibleAxes() const { return numVisibleAxes; } size_t GetNumExtruders() const { return numExtruders; } - static const char axisLetters[MaxAxes]; // 'X', 'Y', 'Z' +#ifdef DUET_NG + bool AutoPause(); + bool AutoShutdown(); + bool AutoResume(); + bool AutoResumeAfterShutdown(); +#endif + + static const char axisLetters[MaxAxes]; private: GCodes(const GCodes&); // private copy constructor to prevent copying @@ -198,7 +206,7 @@ private: bool ExecuteG30(GCodeBuffer& gb, StringRef& reply); // Probes at a given position - see the comment at the head of the function itself void SetBedEquationWithProbe(int sParam, StringRef& reply); // Probes a series of points and sets the bed equation bool SetPrintZProbe(GCodeBuffer& gb, StringRef& reply); // Either return the probe value, or set its threshold - bool SetOrReportOffsets(GCodeBuffer& gb, StringRef& reply); // Deal with a G10 + bool SetOrReportOffsets(GCodeBuffer& gb, StringRef& reply, bool& error); // Deal with a G10 bool SetPositions(GCodeBuffer& gb); // Deal with a G92 bool LoadExtrusionAndFeedrateFromGCode(GCodeBuffer& gb, int moveType); // Set up the extrusion and feed rate of a move for the Move class @@ -215,7 +223,7 @@ private: bool OffsetAxes(GCodeBuffer& gb); // Set offsets - deprecated, use G10 void SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply); // Set the P/I/D parameters for a heater bool SetHeaterParameters(GCodeBuffer& gb, StringRef& reply); // Set the thermistor and ADC parameters for a heater, returning true if an error occurs - void ManageTool(GCodeBuffer& gb, StringRef& reply); // Create a new tool definition + bool ManageTool(GCodeBuffer& gb, StringRef& reply); // Create a new tool definition, returning true if an error was reported void SetToolHeaters(Tool *tool, float temperature); // Set all a tool's heaters to the temperature. For M104... bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures void GenerateTemperatureReport(StringRef& reply) const; // Store a standard-format temperature report in reply @@ -239,7 +247,7 @@ private: void CheckTriggers(); // Check for and execute triggers void DoEmergencyStop(); // Execute an emergency stop - void DoPause(GCodeBuffer& gb) // Pause the print + void DoPause(GCodeBuffer& gb, bool isAuto) // Pause the print pre(resourceOwners[movementResource] = &gb); void SetMappedFanSpeed(); // Set the speeds of fans mapped for the current tool @@ -258,6 +266,10 @@ private: MessageType GetMessageBoxDevice(GCodeBuffer& gb) const; // Decide which device to display a message box on void DoManualProbe(GCodeBuffer& gb); // Do a manual bed probe +#ifdef DUET_NG + void SaveResumeInfo(); +#endif + static uint32_t LongArrayToBitMap(const long *arr, size_t numEntries); // Convert an array of longs to a bit map Platform& platform; // The RepRap machine @@ -268,7 +280,12 @@ private: StreamGCodeInput* serialInput; // ... StreamGCodeInput* auxInput; // ...for the GCodeBuffers below +#ifdef DUET_NG + GCodeBuffer* gcodeSources[8]; // The various sources of gcodes + GCodeBuffer*& autoPauseGCode = gcodeSources[7]; // GCode state machine used to run pause.g and resume.g +#else GCodeBuffer* gcodeSources[7]; // The various sources of gcodes +#endif GCodeBuffer*& httpGCode = gcodeSources[0]; GCodeBuffer*& telnetGCode = gcodeSources[1]; @@ -282,7 +299,11 @@ private: const GCodeBuffer* resourceOwners[NumResources]; // Which gcode buffer owns each resource bool active; // Live and running? - bool isPaused; // true if the print has been paused + bool isPaused; // true if the print has been paused manually or automatically +#ifdef DUET_NG + bool isAutoPaused; // true if the print was paused automatically + bool resumeInfoSaved; // true if we have saved resume info at this pause point +#endif bool runningConfigFile; // We are running config.g during the startup process bool doingToolChange; // We are running tool change macros @@ -296,7 +317,6 @@ private: float arcRadius; float arcCurrentAngle; float arcAngleIncrement; - uint32_t arcAxesMoving; bool doingArcMove; RestorePoint simulationRestorePoint; // The position and feed rate when we started a simulation @@ -313,7 +333,8 @@ private: float record[DRIVES]; // Temporary store for move positions float distanceScale; // MM or inches float arcSegmentLength; // Length of segments that we split arc moves into - FileData fileToPrint; + FileData fileToPrint; // The next file to print + FilePosition fileOffsetToPrint; // The offset to print from FileStore* fileBeingWritten; // A file to write G Codes (or sometimes HTML) to uint16_t toBeHomed; // Bitmap of axes still to be homed int oldToolNumber, newToolNumber; // Tools being changed @@ -378,6 +399,33 @@ private: bool displayNoToolWarning; // True if we need to display a 'no tool selected' warning bool displayDeltaNotHomedWarning; // True if we need to display a 'attempt to move before homing on a delta printer' message char filamentToLoad[FilamentNameLength]; // Name of the filament being loaded + + static const char* const HomingFileNames[MaxAxes]; + + static constexpr const char* BED_EQUATION_G = "bed.g"; + static constexpr const char* RESUME_G = "resume.g"; + static constexpr const char* CANCEL_G = "cancel.g"; + static constexpr const char* STOP_G = "stop.g"; + static constexpr const char* SLEEP_G = "sleep.g"; + static constexpr const char* CONFIG_OVERRIDE_G = "config-override.g"; + static constexpr const char* DEPLOYPROBE_G = "deployprobe.g"; + static constexpr const char* RETRACTPROBE_G = "retractprobe.g"; + static constexpr const char* RESUME_PROLOGUE_G = "resurrect-prologue.g"; + static constexpr const char* PAUSE_G = "pause.g"; + static constexpr const char* HOME_ALL_G = "homeall.g"; + static constexpr const char* HOME_DELTA_G = "homedelta.g"; + static constexpr const char* DefaultHeightMapFile = "heightmap.csv"; + static constexpr const char* LOAD_FILAMENT_G = "load.g"; + static constexpr const char* UNLOAD_FILAMENT_G = "unload.g"; + +#ifdef DUET_NG + static constexpr const char* POWER_FAIL_G = "powerfail.g"; + static constexpr const char* POWER_RESTORE_G = "powerrestore.g"; + static constexpr const char* RESUME_AFTER_POWER_FAIL_G = "resurrect.g"; +#endif + + static constexpr const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0; + static const uint16_t ServoRefreshFrequency = 50; }; //***************************************************************************************************** diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index e1e24934..ae7f8b69 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -28,18 +28,6 @@ # include "FirmwareUpdater.h" #endif -const char* const BED_EQUATION_G = "bed.g"; -const char* const RESUME_G = "resume.g"; -const char* const CANCEL_G = "cancel.g"; -const char* const STOP_G = "stop.g"; -const char* const SLEEP_G = "sleep.g"; -const char* const CONFIG_OVERRIDE_G = "config-override.g"; -const char* const DEPLOYPROBE_G = "deployprobe.g"; -const char* const RETRACTPROBE_G = "retractprobe.g"; - -const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0; -const uint16_t ServoRefreshFrequency = 50; - // If the code to act on is completed, this returns true, // otherwise false. It is called repeatedly for a given // code until it returns true for that code. @@ -132,10 +120,7 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) case 10: // Set/report offsets and temperatures, or retract { - bool modifyingTool = false; - modifyingTool |= gb.Seen('P'); - modifyingTool |= gb.Seen('R'); - modifyingTool |= gb.Seen('S'); + bool modifyingTool = gb.Seen('P') || gb.Seen('R') || gb.Seen('S'); for (size_t axis = 0; axis < numVisibleAxes; ++axis) { modifyingTool |= gb.Seen(axisLetters[axis]); @@ -143,27 +128,19 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) if (modifyingTool) { - if (!SetOrReportOffsets(gb, reply)) + if (!SetOrReportOffsets(gb, reply, error)) { return false; } } else { - if (!LockMovement(gb)) - { - return false; - } result = RetractFilament(gb, true); } } break; case 11: // Un-retract - if (!LockMovement(gb)) - { - return false; - } result = RetractFilament(gb, false); break; @@ -215,7 +192,6 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) } else { - ClearBabyStepping(); error = ExecuteG30(gb, reply); } break; @@ -230,8 +206,6 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) return false; } - ClearBabyStepping(); - // We need to unlock the movement system here in case there is no Z probe and we are doing manual probing. // Otherwise, even though the bed probing code calls UnlockAll when doing a manual bed probe, the movement system // remains locked because the current MachineState object already held the lock when the macro file was started, @@ -467,7 +441,8 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) case 23: // Set file to print case 32: // Select file and start SD print - if (fileGCode->OriginalMachineState().fileState.IsLive()) + // We now allow a file that is being printed to chain to another file. This is required for the resume-after-power-fail functoinality. + if (fileGCode->OriginalMachineState().fileState.IsLive() && (&gb) != fileGCode) { reply.copy("Cannot set file to print, because a file is already being printed"); error = true; @@ -498,7 +473,9 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) if (code == 32) { + fileToPrint.Seek(fileOffsetToPrint); fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); + fileInput->Reset(); reprap.GetPrintMonitor().StartedPrint(); } } @@ -531,7 +508,9 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) } else { + fileToPrint.Seek(fileOffsetToPrint); fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint); + fileInput->Reset(); reprap.GetPrintMonitor().StartedPrint(); } break; @@ -543,7 +522,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) { return false; } - DoPause(gb); + DoPause(gb, false); } break; @@ -564,45 +543,16 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) { return false; } - DoPause(gb); + DoPause(gb, false); } break; case 26: // Set SD position + // This is used between executing M23 to set up the file to print, and M25 to print it if (gb.Seen('S')) { - const FilePosition value = gb.GetIValue(); - if (value < 0) - { - reply.copy("SD positions can't be negative!"); - error = true; - } - else if (fileGCode->OriginalMachineState().fileState.IsLive()) - { - if (!fileGCode->OriginalMachineState().fileState.Seek(value)) - { - reply.copy("The specified SD position is invalid!"); - error = true; - } - } - else if (fileToPrint.IsLive()) - { - if (!fileToPrint.Seek(value)) - { - reply.copy("The specified SD position is invalid!"); - error = true; - } - } - else - { - reply.copy("Cannot set SD file position, because no print is in progress!"); - error = true; - } - } - else - { - reply.copy("You must specify the SD position in bytes using the S parameter."); - error = true; + // Ideally we would get an unsigned value here in case of the file offset being >2Gb + fileOffsetToPrint = (FilePosition)gb.GetIValue(); } break; @@ -611,7 +561,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) { // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; - reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length()); + reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition() - fileInput->BytesCached(), fileBeingPrinted.Length()); } else { @@ -691,7 +641,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) if (!wasSimulating) { // Starting a new simulation, so save the current position - reprap.GetMove().GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes()); + reprap.GetMove().GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); simulationRestorePoint.feedRate = gb.MachineState().feedrate; } } @@ -884,10 +834,6 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) break; case 101: // Un-retract - if (!LockMovement(gb)) - { - return false; - } result = RetractFilament(gb, false); break; @@ -897,10 +843,6 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) break; case 103: // Retract - if (!LockMovement(gb)) - { - return false; - } result = RetractFilament(gb, true); break; @@ -2451,7 +2393,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) break; case 563: // Define tool - ManageTool(gb, reply); + error = ManageTool(gb, reply); break; case 564: // Think outside the box? @@ -3179,24 +3121,16 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) else { // List remembered networks - const size_t declaredBufferLength = MaxRememberedNetworks * (SsidLength + 1) + 1; // enough for all the remembered SSIDs with null terminator, plus an extra null + const size_t declaredBufferLength = (MaxRememberedNetworks + 1) * (SsidLength + 1) + 1; // enough for all the remembered SSIDs with newline terminator, plus an extra null uint32_t buffer[NumDwords(declaredBufferLength + 1)]; const int32_t rslt = reprap.GetNetwork().SendCommand(NetworkCommand::networkListSsids, 0, 0, nullptr, 0, buffer, declaredBufferLength); if (rslt >= 0) { char* const cbuf = reinterpret_cast<char *>(buffer); cbuf[declaredBufferLength] = 0; // ensure null terminated - size_t len = strlen(cbuf); - - // If there is a trailing newline, remove it - if (len != 0 && cbuf[len - 1] == '\n') - { - --len; - cbuf[len] = 0; - } // DuetWiFiServer 1.19beta7 and later include the SSID used in access point mode at the start - const char *bufp = strchr(cbuf, '\n'); + char *bufp = strchr(cbuf, '\n'); if (bufp == nullptr) { bufp = cbuf; // must be an old version of DuetWiFiServer @@ -3206,6 +3140,15 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) ++bufp; // slip the first entry } + // If there is a trailing newline, remove it + { + const size_t len = strlen(bufp); + if (len != 0 && bufp[len - 1] == '\n') + { + bufp[len - 1] = 0; + } + } + if (strlen(bufp) == 0) { reply.copy("No remembered networks"); @@ -3312,7 +3255,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) } else { - const size_t declaredBufferLength = MaxRememberedNetworks * (SsidLength + 1) + 1; // enough for all the remembered SSIDs with null terminator, plus an extra null + const size_t declaredBufferLength = (MaxRememberedNetworks + 1) * (SsidLength + 1) + 1; // enough for all the remembered SSIDs with null terminator, plus an extra null uint32_t buffer[NumDwords(declaredBufferLength + 1)]; const int32_t rslt = reprap.GetNetwork().SendCommand(NetworkCommand::networkListSsids, 0, 0, nullptr, 0, buffer, declaredBufferLength); if (rslt >= 0) @@ -3500,6 +3443,14 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) break; #endif + case 671: // Set Z leadscrew positions + if (!LockMovementAndWaitForStandstill(gb)) + { + return false; + } + (void)reprap.GetMove().GetKinematics().Configure(code, gb, reply, error); + break; + case 701: // Load filament result = LoadFilament(gb, reply, error); break; @@ -3797,9 +3748,11 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) } break; - case 911: // Set power monitor threshold voltages - reply.printf("M911 not implemented yet"); +#ifdef DUET_NG + case 911: // Enable auto save + platform.ConfigureAutoSave(gb, reply, error); break; +#endif case 912: // Set electronics temperature monitor adjustment // Currently we ignore the P parameter (i.e. temperature measurement channel) @@ -3961,7 +3914,7 @@ bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply) newToolNumber = gb.GetIValue(); newToolNumber += gb.GetToolNumberAdjust(); - reprap.GetMove().GetCurrentUserPosition(toolChangeRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes()); + reprap.GetMove().GetCurrentUserPosition(toolChangeRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); toolChangeRestorePoint.feedRate = gb.MachineState().feedrate; if (simulationMode == 0) // we don't yet simulate any T codes diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp index 3ef32a72..6b716024 100644 --- a/src/Heating/FOPDT.cpp +++ b/src/Heating/FOPDT.cpp @@ -20,7 +20,7 @@ FopDt::FopDt() } // Check the model parameters are sensible, if they are then save them and return true. -bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, bool pUsePid) +bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, bool pUsePid) { if (pg == -1.0 && ptc == -1.0 && pdt == -1.0) { @@ -30,7 +30,8 @@ bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, bool pU } // DC 2017-06-20: allow S down to 0.01 for one of our OEMs (use > 0.0099 because >= 0.01 doesn't work due to rounding error) - if (pg > 10.0 && pg <= 1500.0 && pdt > 0.099 && ptc >= 2 * pdt && pMaxPwm > 0.0099 && pMaxPwm <= 1.0) + const float maxGain = max<float>(1500.0, temperatureLimit + 500.0); + if (pg > 10.0 && pg <= maxGain && pdt > 0.099 && ptc >= 2 * pdt && pMaxPwm > 0.0099 && pMaxPwm <= 1.0) { gain = pg; timeConstant = ptc; diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h index 80850865..36fd6355 100644 --- a/src/Heating/FOPDT.h +++ b/src/Heating/FOPDT.h @@ -35,7 +35,7 @@ class FopDt public: FopDt(); - bool SetParameters(float pg, float ptc, float pdt, float pMaxPwm, bool pUsePid); + bool SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, bool pUsePid); float GetGain() const { return gain; } float GetTimeConstant() const { return timeConstant; } diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index 4b4224f8..bfb982a6 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -258,9 +258,9 @@ void Heat::SwitchOff(int8_t heater) void Heat::SwitchOffAll() { - for (size_t heater = 0; heater < Heaters; ++heater) + for (PID *p : pids) { - pids[heater]->SwitchOff(); + p->SwitchOff(); } } @@ -468,4 +468,34 @@ float Heat::GetTemperature(size_t heater, TemperatureError& err) return t; } +#ifdef DUET_NG + +// Suspend the heaters to conserve power +void Heat::SuspendHeaters(bool sus) +{ + for (PID *p : pids) + { + p->Suspend(sus); + } +} + +// Save some resume information returning true if successful. +// We assume that the bed and chamber heaters are either on and active, or off (not on standby). +bool Heat::WriteBedAndChamberTempSettings(FileStore *f) const +{ + char bufSpace[100]; + StringRef buf(bufSpace, ARRAY_SIZE(bufSpace)); + if (bedHeater >= 0 && pids[bedHeater]->Active() && !pids[bedHeater]->SwitchedOff()) + { + buf.printf("M140 S%.1f\n", GetActiveTemperature(bedHeater)); + } + if (chamberHeater >= 0 && pids[chamberHeater]->Active() && !pids[chamberHeater]->SwitchedOff()) + { + buf.printf("M141 S%.1f\n", GetActiveTemperature(chamberHeater)); + } + return (buf.Length() == 0) || f->Write(buf.Pointer()); +} + +#endif + // End diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index ef3998ab..a793f25f 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -122,6 +122,11 @@ public: float GetTemperature(size_t heater, TemperatureError& err); // Result is in degrees Celsius +#ifdef DUET_NG + void SuspendHeaters(bool sus); // Suspend the heaters to conserve power + bool WriteBedAndChamberTempSettings(FileStore *f) const; // Save some resume information +#endif + private: Heat(const Heat&); // Private copy constructor to prevent copying TemperatureSensor **GetSensor(size_t heater); // Get a pointer to the temperature sensor entry diff --git a/src/Heating/Pid.cpp b/src/Heating/Pid.cpp index 145ff014..c8494f4e 100644 --- a/src/Heating/Pid.cpp +++ b/src/Heating/Pid.cpp @@ -47,7 +47,7 @@ void PID::Init(float pGain, float pTc, float pTd, float tempLimit, bool usePid) temperatureLimit = tempLimit; maxTempExcursion = DefaultMaxTempExcursion; maxHeatingFaultTime = DefaultMaxHeatingFaultTime; - model.SetParameters(pGain, pTc, pTd, 1.0, usePid); + model.SetParameters(pGain, pTc, pTd, 1.0, tempLimit, usePid); Reset(); if (model.IsEnabled()) @@ -74,12 +74,16 @@ void PID::Reset() averagePWM = lastPwm = 0.0; heatingFaultCount = 0; temperature = BAD_ERROR_TEMPERATURE; +#ifdef DUET_NG + suspended = false; +#endif } // Set the process model bool PID::SetModel(float gain, float tc, float td, float maxPwm, bool usePid) { - const bool rslt = model.SetParameters(gain, tc, td, maxPwm, usePid); + const float temperatureLimit = reprap.GetHeat().GetTemperatureLimit(heater); + const bool rslt = model.SetParameters(gain, tc, td, maxPwm, temperatureLimit, usePid); if (rslt) { #if !defined(DUET_NG) && !defined(__RADDS__) && !defined(__ALLIGATOR__) @@ -91,13 +95,13 @@ bool PID::SetModel(float gain, float tc, float td, float maxPwm, bool usePid) #endif if (model.IsEnabled()) { - const float safeGain = (heater == reprap.GetHeat().GetBedHeater() || heater == reprap.GetHeat().GetChamberHeater()) - ? 170.0 : 480.0; - if (gain > safeGain) + const float predictedMaxTemp = gain + NormalAmbientTemperature; + const float noWarnTemp = (temperatureLimit - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C + if (predictedMaxTemp > noWarnTemp) { platform.MessageF(GENERIC_MESSAGE, - "Warning: Heater %u appears to be over-powered. If left on at full power, its temperature is predicted to reach %uC.\n", - heater, (unsigned int)gain + 20); + "Warning: Heater %u appears to be over-powered. If left on at full power, its temperature is predicted to reach %dC.\n", + heater, (int)predictedMaxTemp); } } else @@ -181,6 +185,13 @@ void PID::Spin() { if (model.IsEnabled()) { +#ifdef DUET_NG + if (suspended) + { + SetHeater(0.0); + return; + } +#endif // Read the temperature const TemperatureError err = ReadTemperature(); @@ -826,4 +837,18 @@ void PID::DisplayBuffer(const char *intro) } } +#ifdef DUET_NG + +// Suspend the heater to conserve power, or resume it +void PID::Suspend(bool sus) +{ + suspended = sus; + if (sus && model.IsEnabled()) + { + SetHeater(0.0); + } +} + +#endif + // End diff --git a/src/Heating/Pid.h b/src/Heating/Pid.h index 53cea4fb..8907063a 100644 --- a/src/Heating/Pid.h +++ b/src/Heating/Pid.h @@ -86,6 +86,10 @@ public: void SetM301PidParameters(const M301PidParameters& params) { model.SetM301PidParameters(params); } +#ifdef DUET_NG + void Suspend(bool sus); // Suspend the heater to conserve power +#endif + private: void SwitchOn(); // Turn the heater on and set the mode @@ -128,6 +132,9 @@ private: HeaterMode mode; // Current state of the heater bool active; // Are we active or standby? bool tuned; // True if tuning was successful +#ifdef DUET_NG + bool suspended; // True if suspended to save power +#endif uint8_t badTemperatureCount; // Count of sequential dud readings static_assert(sizeof(previousTemperaturesGood) * 8 >= NumPreviousTemperatures, "too few bits in previousTemperaturesGood"); @@ -156,7 +163,6 @@ private: }; - inline bool PID::Active() const { return active; diff --git a/src/Heating/Sensors/TemperatureSensor.cpp b/src/Heating/Sensors/TemperatureSensor.cpp index 82a5399d..536a7dd0 100644 --- a/src/Heating/Sensors/TemperatureSensor.cpp +++ b/src/Heating/Sensors/TemperatureSensor.cpp @@ -70,7 +70,7 @@ void TemperatureSensor::TryConfigureHeaterName(GCodeBuffer& gb, bool& seen) { char buf[MaxHeaterNameLength + 1]; bool localSeen = false; - gb.TryGetQuotedString('H', buf, ARRAY_SIZE(buf), localSeen); + gb.TryGetQuotedString('S', buf, ARRAY_SIZE(buf), localSeen); if (localSeen) { SetHeaterName(buf); diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp index 153be884..0979c504 100644 --- a/src/Movement/DDA.cpp +++ b/src/Movement/DDA.cpp @@ -290,6 +290,7 @@ bool DDA::Init(const GCodes::RawMove &nextMove, bool doMotorMapping) // 3. Store some values xAxes = nextMove.xAxes; + yAxes = nextMove.yAxes; endStopsToCheck = nextMove.endStopsToCheck; canPauseBefore = nextMove.canPauseBefore; canPauseAfter = nextMove.canPauseAfter; @@ -1049,21 +1050,30 @@ void DDA::Prepare() float DDA::NormaliseXYZ() { // First calculate the magnitude of the vector. If there is more than one X axis, take an average of their movements (they should be equal). - float magSquared = 0.0; - unsigned int numXaxes = 0; + float xMagSquared = 0.0, yMagSquared = 0.0; + unsigned int numXaxes = 0, numYaxes = 0; for (size_t d = 0; d < MaxAxes; ++d) { if (((1 << d) & xAxes) != 0) { - magSquared += fsquare(directionVector[d]); + xMagSquared += fsquare(directionVector[d]); ++numXaxes; } + if (((1 << d) & yAxes) != 0) + { + yMagSquared += fsquare(directionVector[d]); + ++numYaxes; + } + } + if (numXaxes > 1) + { + xMagSquared /= numXaxes; } - if (numXaxes != 0) + if (numYaxes > 1) { - magSquared /= numXaxes; + yMagSquared /= numYaxes; } - const float magnitude = sqrtf(magSquared + fsquare(directionVector[Y_AXIS]) + fsquare(directionVector[Z_AXIS])); + const float magnitude = sqrtf(xMagSquared + yMagSquared + fsquare(directionVector[Z_AXIS])); if (magnitude <= 0.0) { return 0.0; diff --git a/src/Movement/DDA.h b/src/Movement/DDA.h index 9f193482..72f4ec51 100644 --- a/src/Movement/DDA.h +++ b/src/Movement/DDA.h @@ -69,6 +69,7 @@ public: float AdvanceBabyStepping(float amount); // Try to push babystepping earlier in the move queue bool IsHomingAxes() const { return (endStopsToCheck & HomeAxes) != 0; } uint32_t GetXAxes() const { return xAxes; } + uint32_t GetYAxes() const { return yAxes; } #if SUPPORT_IOBITS uint32_t GetMoveStartTime() const { return moveStartTime; } @@ -142,6 +143,7 @@ private: EndstopChecks endStopsToCheck; // Which endstops we are checking on this move uint32_t xAxes; // Which axes are behaving as X axes + uint32_t yAxes; // Which axes are behaving as Y axes FilePosition filePos; // The position in the SD card file after this move was read, or zero if not read from SD card diff --git a/src/Movement/Kinematics/CartesianKinematics.cpp b/src/Movement/Kinematics/CartesianKinematics.cpp index 5aa8a1f9..28fb7e27 100644 --- a/src/Movement/Kinematics/CartesianKinematics.cpp +++ b/src/Movement/Kinematics/CartesianKinematics.cpp @@ -7,7 +7,7 @@ #include "CartesianKinematics.h" -CartesianKinematics::CartesianKinematics() : Kinematics(KinematicsType::cartesian) +CartesianKinematics::CartesianKinematics() : ZLeadscrewKinematics(KinematicsType::cartesian) { } diff --git a/src/Movement/Kinematics/CartesianKinematics.h b/src/Movement/Kinematics/CartesianKinematics.h index f2ad05ec..4064e0e1 100644 --- a/src/Movement/Kinematics/CartesianKinematics.h +++ b/src/Movement/Kinematics/CartesianKinematics.h @@ -8,9 +8,9 @@ #ifndef SRC_MOVEMENT_KINEMATICS_CARTESIANKINEMATICS_H_ #define SRC_MOVEMENT_KINEMATICS_CARTESIANKINEMATICS_H_ -#include "Kinematics.h" +#include "ZLeadscrewKinematics.h" -class CartesianKinematics : public Kinematics +class CartesianKinematics : public ZLeadscrewKinematics { public: CartesianKinematics(); @@ -19,7 +19,6 @@ public: const char *GetName(bool forStatusReport) const override; bool CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const override; void MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const override; - bool SupportsAutoCalibration() const override { return false; } bool DriveIsShared(size_t drive) const override { return false; } HomingMode GetHomingMode() const override { return homeCartesianAxes; } }; diff --git a/src/Movement/Kinematics/CoreBaseKinematics.cpp b/src/Movement/Kinematics/CoreBaseKinematics.cpp index f712b4da..a75a70d4 100644 --- a/src/Movement/Kinematics/CoreBaseKinematics.cpp +++ b/src/Movement/Kinematics/CoreBaseKinematics.cpp @@ -8,7 +8,7 @@ #include "CoreBaseKinematics.h" #include "GCodes/GCodes.h" -CoreBaseKinematics::CoreBaseKinematics(KinematicsType t) : Kinematics(t) +CoreBaseKinematics::CoreBaseKinematics(KinematicsType t) : ZLeadscrewKinematics(t) { for (float& af : axisFactors) { @@ -16,16 +16,6 @@ CoreBaseKinematics::CoreBaseKinematics(KinematicsType t) : Kinematics(t) } } -// Convert Cartesian coordinates to motor coordinates -bool CoreBaseKinematics::CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const -{ - for (size_t axis = 0; axis < numVisibleAxes; ++axis) - { - motorPos[axis] = (int32_t)roundf(MotorFactor(axis, machinePos) * stepsPerMm[axis]); - } - return true; -} - // Set the parameters from a M665, M666 or M669 command // Return true if we changed any parameters. Set 'error' true if there was an error, otherwise leave it alone. // This function is used for CoreXY and CoreXZ kinematics, but it overridden for CoreXYU kinematics diff --git a/src/Movement/Kinematics/CoreBaseKinematics.h b/src/Movement/Kinematics/CoreBaseKinematics.h index 5132e7f3..12c05602 100644 --- a/src/Movement/Kinematics/CoreBaseKinematics.h +++ b/src/Movement/Kinematics/CoreBaseKinematics.h @@ -8,24 +8,18 @@ #ifndef SRC_MOVEMENT_KINEMATICS_COREBASEKINEMATICS_H_ #define SRC_MOVEMENT_KINEMATICS_COREBASEKINEMATICS_H_ -#include "Kinematics.h" +#include "ZLeadscrewKinematics.h" -class CoreBaseKinematics : public Kinematics +class CoreBaseKinematics : public ZLeadscrewKinematics { public: CoreBaseKinematics(KinematicsType t); // Overridden base class functions. See Kinematics.h for descriptions. - bool CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const override final; bool Configure(unsigned int mCode, GCodeBuffer& gb, StringRef& reply, bool& error) override; - bool SupportsAutoCalibration() const override final { return false; } HomingMode GetHomingMode() const override { return homeCartesianAxes; } protected: - // Calculate the movement fraction for a single axis motor of a Cartesian-like printer. - // The default implementation just returns directionVector[drive] but this needs to be overridden for CoreXY and CoreXZ printers. - virtual float MotorFactor(size_t drive, const float directionVector[]) const = 0; - float axisFactors[MaxAxes]; // allow more than just XYZ so that we can support e.g. CoreXYU kinematics }; diff --git a/src/Movement/Kinematics/CoreXYKinematics.cpp b/src/Movement/Kinematics/CoreXYKinematics.cpp index 8ff27a88..cb4123e1 100644 --- a/src/Movement/Kinematics/CoreXYKinematics.cpp +++ b/src/Movement/Kinematics/CoreXYKinematics.cpp @@ -17,14 +17,28 @@ const char *CoreXYKinematics::GetName(bool forStatusReport) const return (forStatusReport) ? "coreXY" : "CoreXY"; } +// Convert Cartesian coordinates to motor coordinates returning true if successful +bool CoreXYKinematics::CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const +{ + motorPos[X_AXIS] = (int32_t)roundf(((machinePos[X_AXIS] * axisFactors[X_AXIS]) + (machinePos[Y_AXIS] * axisFactors[Y_AXIS])) * stepsPerMm[X_AXIS]); + motorPos[Y_AXIS] = (int32_t)roundf(((machinePos[X_AXIS] * axisFactors[X_AXIS]) - (machinePos[Y_AXIS] * axisFactors[Y_AXIS])) * stepsPerMm[Y_AXIS]); + + for (size_t axis = Z_AXIS; axis < numVisibleAxes; ++axis) + { + motorPos[axis] = (int32_t)roundf(machinePos[axis] * stepsPerMm[axis]); + } + return true; +} + // Convert motor coordinates to machine coordinates. Used after homing and after individual motor moves. void CoreXYKinematics::MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const { - // Convert the axes - machinePos[X_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) - (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) - /(2 * axisFactors[X_AXIS] * stepsPerMm[X_AXIS] * stepsPerMm[Y_AXIS]); - machinePos[Y_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) + (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) - /(2 * axisFactors[Y_AXIS] * stepsPerMm[X_AXIS] * stepsPerMm[Y_AXIS]); + // Convert the main axes + const float xyStepsMm = stepsPerMm[X_AXIS] * stepsPerMm[Y_AXIS]; + machinePos[X_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) + (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) + /(2 * axisFactors[X_AXIS] * xyStepsMm); + machinePos[Y_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) - (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) + /(2 * axisFactors[Y_AXIS] * xyStepsMm); machinePos[Z_AXIS] = motorPos[Z_AXIS]/stepsPerMm[Z_AXIS]; // Convert any additional axes @@ -41,20 +55,4 @@ bool CoreXYKinematics::DriveIsShared(size_t drive) const return drive == X_AXIS || drive == Y_AXIS; } -// Calculate the movement fraction for a single axis motor -float CoreXYKinematics::MotorFactor(size_t drive, const float directionVector[]) const -{ - switch(drive) - { - case X_AXIS: - return (directionVector[X_AXIS] * axisFactors[X_AXIS]) + (directionVector[Y_AXIS] * axisFactors[Y_AXIS]); - - case Y_AXIS: - return (directionVector[Y_AXIS] * axisFactors[Y_AXIS]) - (directionVector[X_AXIS] * axisFactors[X_AXIS]); - - default: - return directionVector[drive]; - } -} - // End diff --git a/src/Movement/Kinematics/CoreXYKinematics.h b/src/Movement/Kinematics/CoreXYKinematics.h index 1c7cc797..0b426c55 100644 --- a/src/Movement/Kinematics/CoreXYKinematics.h +++ b/src/Movement/Kinematics/CoreXYKinematics.h @@ -17,11 +17,9 @@ public: // Overridden base class functions. See Kinematics.h for descriptions. const char *GetName(bool forStatusReport) const override; + bool CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const override; void MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const override; bool DriveIsShared(size_t drive) const override; - -protected: - float MotorFactor(size_t drive, const float directionVector[]) const override; }; #endif /* SRC_MOVEMENT_KINEMATICS_COREXYKINEMATICS_H_ */ diff --git a/src/Movement/Kinematics/CoreXYUKinematics.cpp b/src/Movement/Kinematics/CoreXYUKinematics.cpp index f14ecf87..39272486 100644 --- a/src/Movement/Kinematics/CoreXYUKinematics.cpp +++ b/src/Movement/Kinematics/CoreXYUKinematics.cpp @@ -54,18 +54,36 @@ bool CoreXYUKinematics::Configure(unsigned int mCode, GCodeBuffer& gb, StringRef } } +// Convert Cartesian coordinates to motor coordinates +bool CoreXYUKinematics::CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const +{ + motorPos[X_AXIS] = (int32_t)roundf(((machinePos[X_AXIS] * axisFactors[X_AXIS]) + (machinePos[Y_AXIS] * axisFactors[Y_AXIS])) * stepsPerMm[X_AXIS]); + motorPos[Y_AXIS] = (int32_t)roundf(((machinePos[X_AXIS] * axisFactors[X_AXIS]) - (machinePos[Y_AXIS] * axisFactors[Y_AXIS])) * stepsPerMm[Y_AXIS]); + motorPos[Z_AXIS] = (int32_t)roundf(machinePos[Z_AXIS] * stepsPerMm[Z_AXIS]); + motorPos[U_AXIS] = (int32_t)roundf(((machinePos[U_AXIS] * axisFactors[U_AXIS]) + (machinePos[Y_AXIS] * axisFactors[Y_AXIS])) * stepsPerMm[U_AXIS]); + motorPos[V_AXIS] = (int32_t)roundf(((machinePos[U_AXIS] * axisFactors[U_AXIS]) - (machinePos[Y_AXIS] * axisFactors[Y_AXIS])) * stepsPerMm[V_AXIS]); + + for (size_t axis = CoreXYU_AXES; axis < numVisibleAxes; ++axis) + { + motorPos[axis] = (int32_t)roundf(machinePos[axis] * stepsPerMm[axis]); + } + return true; +} + // Convert motor coordinates to machine coordinates. Used after homing and after individual motor moves. void CoreXYUKinematics::MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const { - // Convert the axes - machinePos[X_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) - (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) - /(2 * axisFactors[X_AXIS] * stepsPerMm[X_AXIS] * stepsPerMm[Y_AXIS]); - machinePos[Y_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) + (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) - /(2 * axisFactors[Y_AXIS] * stepsPerMm[X_AXIS] * stepsPerMm[Y_AXIS]); - machinePos[U_AXIS] = ((motorPos[U_AXIS] * stepsPerMm[V_AXIS]) - (motorPos[V_AXIS] * stepsPerMm[U_AXIS])) - /(2 * axisFactors[V_AXIS] * stepsPerMm[U_AXIS] * stepsPerMm[V_AXIS]); - machinePos[V_AXIS] = ((motorPos[U_AXIS] * stepsPerMm[V_AXIS]) + (motorPos[V_AXIS] * stepsPerMm[U_AXIS])) - /(2 * axisFactors[V_AXIS] * stepsPerMm[U_AXIS] * stepsPerMm[V_AXIS]); + // Convert the main axes + const float xyStepsMm = stepsPerMm[X_AXIS] * stepsPerMm[Y_AXIS]; + const float uvStepsMm = stepsPerMm[U_AXIS] * stepsPerMm[V_AXIS]; + machinePos[X_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) + (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) + /(2 * axisFactors[X_AXIS] * xyStepsMm); + machinePos[Y_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Y_AXIS]) - (motorPos[Y_AXIS] * stepsPerMm[X_AXIS])) + /(2 * axisFactors[Y_AXIS] * xyStepsMm); + machinePos[U_AXIS] = ((motorPos[U_AXIS] * stepsPerMm[V_AXIS]) + (motorPos[V_AXIS] * stepsPerMm[U_AXIS])) + /(2 * axisFactors[V_AXIS] * uvStepsMm); + machinePos[V_AXIS] = ((motorPos[U_AXIS] * stepsPerMm[V_AXIS]) - (motorPos[V_AXIS] * stepsPerMm[U_AXIS])) + /(2 * axisFactors[V_AXIS] * uvStepsMm); machinePos[Z_AXIS] = motorPos[Z_AXIS]/stepsPerMm[Z_AXIS]; @@ -84,22 +102,4 @@ bool CoreXYUKinematics::DriveIsShared(size_t drive) const || drive == V_AXIS; // V doesn't have endstop switches, but include it here just in case } -// Calculate the movement fraction for a single axis motor -float CoreXYUKinematics::MotorFactor(size_t drive, const float directionVector[]) const -{ - switch(drive) - { - case X_AXIS: - return (directionVector[X_AXIS] * axisFactors[X_AXIS]) + (directionVector[Y_AXIS] * axisFactors[Y_AXIS]); - case Y_AXIS: - return (directionVector[Y_AXIS] * axisFactors[Y_AXIS]) - (directionVector[X_AXIS] * axisFactors[X_AXIS]); - case U_AXIS: // X2, Use Y and U to calculate - return (directionVector[U_AXIS] * axisFactors[U_AXIS]) + (directionVector[Y_AXIS] * axisFactors[Y_AXIS]); - case V_AXIS: // Y2, Use Y and U to calculate - return (directionVector[Y_AXIS] * axisFactors[Y_AXIS]) - (directionVector[U_AXIS] * axisFactors[U_AXIS]); - default: - return directionVector[drive]; - } -} - // End diff --git a/src/Movement/Kinematics/CoreXYUKinematics.h b/src/Movement/Kinematics/CoreXYUKinematics.h index c59c693f..67034f13 100644 --- a/src/Movement/Kinematics/CoreXYUKinematics.h +++ b/src/Movement/Kinematics/CoreXYUKinematics.h @@ -18,11 +18,9 @@ public: // Overridden base class functions. See Kinematics.h for descriptions. const char *GetName(bool forStatusReport) const override; bool Configure(unsigned int mCode, GCodeBuffer& gb, StringRef& reply, bool& error) override; + bool CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const override; void MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const override; bool DriveIsShared(size_t drive) const override; - -protected: - float MotorFactor(size_t drive, const float directionVector[]) const override; }; #endif /* SRC_MOVEMENT_KINEMATICS_COREXYKINEMATICS_H_ */ diff --git a/src/Movement/Kinematics/CoreXZKinematics.cpp b/src/Movement/Kinematics/CoreXZKinematics.cpp index ede4d8ca..ad4975c0 100644 --- a/src/Movement/Kinematics/CoreXZKinematics.cpp +++ b/src/Movement/Kinematics/CoreXZKinematics.cpp @@ -17,14 +17,30 @@ const char *CoreXZKinematics::GetName(bool forStatusReport) const return (forStatusReport) ? "coreXZ" : "CoreXZ"; } +// Convert Cartesian coordinates to motor coordinates +bool CoreXZKinematics::CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const +{ + motorPos[X_AXIS] = (int32_t)roundf(((machinePos[X_AXIS] * axisFactors[X_AXIS]) + (machinePos[Z_AXIS] * axisFactors[Z_AXIS])) * stepsPerMm[X_AXIS]); + motorPos[Y_AXIS] = (int32_t)roundf(machinePos[Y_AXIS] * stepsPerMm[Y_AXIS]); + motorPos[Z_AXIS] = (int32_t)roundf(((machinePos[X_AXIS] * axisFactors[X_AXIS]) - (machinePos[Z_AXIS] * axisFactors[Z_AXIS])) * stepsPerMm[Z_AXIS]); + + for (size_t axis = XYZ_AXES; axis < numVisibleAxes; ++axis) + { + motorPos[axis] = (int32_t)roundf(machinePos[axis] * stepsPerMm[axis]); + } + return true; +} + // Convert motor coordinates to machine coordinates. Used after homing and after individual motor moves. void CoreXZKinematics::MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const { - machinePos[X_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Z_AXIS]) - (motorPos[Z_AXIS] * stepsPerMm[X_AXIS])) - /(2 * axisFactors[X_AXIS] * stepsPerMm[X_AXIS] * stepsPerMm[Z_AXIS]); + // Convert the main axes + const float xzStepsMmm = stepsPerMm[X_AXIS] * stepsPerMm[Z_AXIS]; + machinePos[X_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Z_AXIS]) + (motorPos[Z_AXIS] * stepsPerMm[X_AXIS])) + /(2 * axisFactors[X_AXIS] * xzStepsMmm); machinePos[Y_AXIS] = motorPos[Y_AXIS]/stepsPerMm[Y_AXIS]; - machinePos[Z_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Z_AXIS]) + (motorPos[Z_AXIS] * stepsPerMm[X_AXIS])) - /(2 * axisFactors[Z_AXIS] * stepsPerMm[X_AXIS] * stepsPerMm[Z_AXIS]); + machinePos[Z_AXIS] = ((motorPos[X_AXIS] * stepsPerMm[Z_AXIS]) - (motorPos[Z_AXIS] * stepsPerMm[X_AXIS])) + /(2 * axisFactors[Z_AXIS] * xzStepsMmm); // Convert any additional axes linearly for (size_t drive = XYZ_AXES; drive < numVisibleAxes; ++drive) @@ -40,20 +56,4 @@ bool CoreXZKinematics::DriveIsShared(size_t drive) const return drive == X_AXIS || drive == Z_AXIS; } -// Calculate the movement fraction for a single axis motor of a Cartesian-like printer -float CoreXZKinematics::MotorFactor(size_t drive, const float directionVector[]) const -{ - switch(drive) - { - case X_AXIS: - return (directionVector[X_AXIS] * axisFactors[X_AXIS]) + (directionVector[Z_AXIS] * axisFactors[Z_AXIS]); - - case Z_AXIS: - return (directionVector[Z_AXIS] * axisFactors[Z_AXIS]) - (directionVector[X_AXIS] * axisFactors[X_AXIS]); - - default: - return directionVector[drive]; - } -} - // End diff --git a/src/Movement/Kinematics/CoreXZKinematics.h b/src/Movement/Kinematics/CoreXZKinematics.h index fb72648b..fb672d7a 100644 --- a/src/Movement/Kinematics/CoreXZKinematics.h +++ b/src/Movement/Kinematics/CoreXZKinematics.h @@ -17,12 +17,11 @@ public: // Overridden base class functions. See Kinematics.h for descriptions. const char *GetName(bool forStatusReport) const override; + bool CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const override; void MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const override; - uint16_t AxesToHomeBeforeProbing() const override { return (1 << X_AXIS) | (1 << Y_AXIS) | (1 << Z_AXIS); } + uint32_t AxesToHomeBeforeProbing() const override { return (1u << X_AXIS) | (1u << Y_AXIS) | (1u << Z_AXIS); } bool DriveIsShared(size_t drive) const override; - -protected: - float MotorFactor(size_t drive, const float directionVector[]) const override; + bool SupportsAutoCalibration() const override { return false; } }; #endif /* SRC_MOVEMENT_KINEMATICS_COREXZKINEMATICS_H_ */ diff --git a/src/Movement/Kinematics/Kinematics.cpp b/src/Movement/Kinematics/Kinematics.cpp index dd55b2ed..21bfd378 100644 --- a/src/Movement/Kinematics/Kinematics.cpp +++ b/src/Movement/Kinematics/Kinematics.cpp @@ -101,4 +101,35 @@ void Kinematics::GetAssumedInitialPosition(size_t numAxes, float positions[]) co } } +/*static*/ void Kinematics::PrintMatrix(const char* s, const MathMatrix<floatc_t>& m, size_t maxRows, size_t maxCols) +{ + debugPrintf("%s\n", s); + if (maxRows == 0) + { + maxRows = m.rows(); + } + if (maxCols == 0) + { + maxCols = m.cols(); + } + + for (size_t i = 0; i < maxRows; ++i) + { + for (size_t j = 0; j < maxCols; ++j) + { + debugPrintf("%7.4f%c", m(i, j), (j == maxCols - 1) ? '\n' : ' '); + } + } +} + +/*static*/ void Kinematics::PrintVector(const char *s, const floatc_t *v, size_t numElems) +{ + debugPrintf("%s:", s); + for (size_t i = 0; i < numElems; ++i) + { + debugPrintf(" %7.4f", v[i]); + } + debugPrintf("\n"); +} + // End diff --git a/src/Movement/Kinematics/Kinematics.h b/src/Movement/Kinematics/Kinematics.h index ab340b4a..737a5d3a 100644 --- a/src/Movement/Kinematics/Kinematics.h +++ b/src/Movement/Kinematics/Kinematics.h @@ -10,6 +10,19 @@ #include "GCodes/GCodeBuffer.h" #include "Movement/BedProbing/RandomProbePointSet.h" +#include "Libraries/Math/Matrix.h" + +#ifdef DUET_NG +typedef double floatc_t; // type of matrix element used for calibration +#else +// We are more memory-constrained on the SAM3X +typedef float floatc_t; // type of matrix element used for calibration +#endif + +inline floatc_t fcsquare(floatc_t a) +{ + return a * a; +} // Different types of kinematics we support. Each of these has a class to represent it. // These must have the same numeric assignments as the K parameter of the M669 command, as documented in ??? @@ -75,7 +88,7 @@ public: virtual void MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const = 0; // Return true if the kinematics supports auto calibration based on bed probing. - // Normally returns false, but overridden for delta kinematics. + // Normally returns false, but overridden for delta kinematics and kinematics with multiple independently-drive Z leadscrews. virtual bool SupportsAutoCalibration() const { return false; } // Perform auto calibration. Override this implementation in kinematics that support it. @@ -104,7 +117,7 @@ public: // Return the set of axes that must have been homed before bed probing is allowed // The default implementation requires just X and Y, but some kinematics require additional axes to be homed (e.g. delta, CoreXZ) - virtual uint16_t AxesToHomeBeforeProbing() const { return (1 << X_AXIS) | (1 << Y_AXIS); } + virtual uint32_t AxesToHomeBeforeProbing() const { return (1u << X_AXIS) | (1u << Y_AXIS); } // Return the initial Cartesian coordinates we assume after switching to this kinematics virtual void GetAssumedInitialPosition(size_t numAxes, float positions[]) const; @@ -125,6 +138,15 @@ public: // Return the type of homing we do virtual HomingMode GetHomingMode() const = 0; + // Return the axes that we can assume are homed after executing a G92 command to set the specified axis coordinates + // This default is good for Cartesian and Core printers, but not deltas or SCARA + virtual uint32_t AxesAssumedHomed(uint32_t g92Axes) const { return g92Axes; } + +#ifdef DUET_NG + // Write any calibration data that we need to resume a print after power fail, returning true if successful. Override where necessary. + virtual bool WriteResumeSettings(FileStore *f) const { return true; } +#endif + // Override this virtual destructor if your constructor allocates any dynamic memory virtual ~Kinematics() { } @@ -147,6 +169,10 @@ protected: // This constructor is used by derived classes that implement segmented linear motion Kinematics(KinematicsType t, float segsPerSecond, float minSegLength, bool doUseRawG0); + // Debugging functions + static void PrintMatrix(const char* s, const MathMatrix<floatc_t>& m, size_t numRows = 0, size_t maxCols = 0); + static void PrintVector(const char *s, const floatc_t *v, size_t numElems); + float segmentsPerSecond; // if we are using segmentation, the target number of segments/second float minSegmentLength; // if we are using segmentation, the minimum segment size diff --git a/src/Movement/Kinematics/LinearDeltaKinematics.cpp b/src/Movement/Kinematics/LinearDeltaKinematics.cpp index ee01fbed..e663b322 100644 --- a/src/Movement/Kinematics/LinearDeltaKinematics.cpp +++ b/src/Movement/Kinematics/LinearDeltaKinematics.cpp @@ -30,6 +30,7 @@ void LinearDeltaKinematics::Init() xTilt = yTilt = 0.0; printRadius = defaultPrintRadius; homedHeight = defaultDeltaHomedHeight; + doneAutoCalibration = false; for (size_t axis = 0; axis < DELTA_AXES; ++axis) { @@ -223,7 +224,7 @@ void LinearDeltaKinematics::DoAutoCalibration(size_t numFactors, const RandomPro if (numFactors < 3 || numFactors > NumDeltaFactors || numFactors == 5) { - reprap.GetPlatform().MessageF(GENERIC_MESSAGE, "Delta calibration error: %d factors requested but only 3, 4, 6, 7, 8 and 9 supported\n", numFactors); + reply.printf("Error: Delta calibration with %d factors requested but only 3, 4, 6, 7, 8 and 9 supported", numFactors); return; } @@ -239,19 +240,19 @@ void LinearDeltaKinematics::DoAutoCalibration(size_t numFactors, const RandomPro // Transform the probing points to motor endpoints and store them in a matrix, so that we can do multiple iterations using the same data FixedMatrix<floatc_t, MaxDeltaCalibrationPoints, DELTA_AXES> probeMotorPositions; floatc_t corrections[MaxDeltaCalibrationPoints]; - float initialSumOfSquares = 0.0; + floatc_t initialSumOfSquares = 0.0; for (size_t i = 0; i < numPoints; ++i) { corrections[i] = 0.0; float machinePos[DELTA_AXES]; - const float zp = reprap.GetMove().GetProbeCoordinates(i, machinePos[X_AXIS], machinePos[Y_AXIS], probePoints.PointWasCorrected(i)); + const floatc_t zp = reprap.GetMove().GetProbeCoordinates(i, machinePos[X_AXIS], machinePos[Y_AXIS], probePoints.PointWasCorrected(i)); machinePos[Z_AXIS] = 0.0; probeMotorPositions(i, A_AXIS) = Transform(machinePos, A_AXIS); probeMotorPositions(i, B_AXIS) = Transform(machinePos, B_AXIS); probeMotorPositions(i, C_AXIS) = Transform(machinePos, C_AXIS); - initialSumOfSquares += fsquare(zp); + initialSumOfSquares += fcsquare(zp); } // Do 1 or more Newton-Raphson iterations @@ -356,7 +357,7 @@ void LinearDeltaKinematics::DoAutoCalibration(size_t numFactors, const RandomPro InverseTransform(probeMotorPositions(i, A_AXIS), probeMotorPositions(i, B_AXIS), probeMotorPositions(i, C_AXIS), newPosition); corrections[i] = newPosition[Z_AXIS]; expectedResiduals[i] = probePoints.GetZHeight(i) + newPosition[Z_AXIS]; - sumOfSquares += fsquare(expectedResiduals[i]); + sumOfSquares += fcsquare(expectedResiduals[i]); } expectedRmsError = sqrt(sumOfSquares/numPoints); @@ -384,8 +385,10 @@ void LinearDeltaKinematics::DoAutoCalibration(size_t numFactors, const RandomPro debugPrintf("%s\n", scratchString.Pointer()); } - reply.printf("Calibrated %d factors using %d points, deviation before %.3f after %.3f\n", + reply.printf("Calibrated %d factors using %d points, deviation before %.3f after %.3f", numFactors, numPoints, sqrt(initialSumOfSquares/numPoints), expectedRmsError); + + doneAutoCalibration = true; } // Return the type of motion computation needed by an axis @@ -532,7 +535,7 @@ void LinearDeltaKinematics::PrintParameters(StringRef& reply) const angleCorrections[A_AXIS], angleCorrections[B_AXIS], angleCorrections[C_AXIS], xTilt * 100.0, yTilt * 100.0); } -// Write the parameters that are set by auto calibration to the config-override.g file, returning true if success +// Write the parameters that are set by auto calibration to a file, returning true if success bool LinearDeltaKinematics::WriteCalibrationParameters(FileStore *f) const { bool ok = f->Write("; Delta parameters\n"); @@ -551,6 +554,16 @@ bool LinearDeltaKinematics::WriteCalibrationParameters(FileStore *f) const return ok; } +#ifdef DUET_NG + +// Write any calibration data that we need to resume a print after power fail, returning true if successful +bool LinearDeltaKinematics::WriteResumeSettings(FileStore *f) const +{ + return !doneAutoCalibration || WriteCalibrationParameters(f); +} + +#endif + // Get the bed tilt fraction for the specified axis float LinearDeltaKinematics::GetTiltCorrection(size_t axis) const { @@ -665,38 +678,16 @@ bool LinearDeltaKinematics::Configure(unsigned int mCode, GCodeBuffer& gb, Strin } } -/*static*/ void LinearDeltaKinematics::PrintMatrix(const char* s, const MathMatrix<floatc_t>& m, size_t maxRows, size_t maxCols) +// Return the axes that we can assume are homed after executing a G92 command to set the specified axis coordinates +uint32_t LinearDeltaKinematics::AxesAssumedHomed(uint32_t g92Axes) const { - debugPrintf("%s\n", s); - if (maxRows == 0) + // If all of X, Y and Z have been specified then we know the positions of all 3 tower motors, otherwise we don't + const uint32_t xyzAxes = (1u << X_AXIS) | (1u << Y_AXIS) | (1u << Z_AXIS); + if ((g92Axes & xyzAxes) != xyzAxes) { - maxRows = m.rows(); + g92Axes &= ~xyzAxes; } - if (maxCols == 0) - { - maxCols = m.cols(); - } - - for (size_t i = 0; i < maxRows; ++i) - { - for (size_t j = 0; j < maxCols; ++j) - { - debugPrintf("%7.4f%c", m(i, j), (j == maxCols - 1) ? '\n' : ' '); - } - } -} - -/*static*/ void LinearDeltaKinematics::PrintVector(const char *s, const floatc_t *v, size_t numElems) -{ - debugPrintf("%s:", s); - for (size_t i = 0; i < numElems; ++i) - { - debugPrintf(" %7.4f", v[i]); - } - debugPrintf("\n"); + return g92Axes; } // End - - - diff --git a/src/Movement/Kinematics/LinearDeltaKinematics.h b/src/Movement/Kinematics/LinearDeltaKinematics.h index 506ea52e..42173c51 100644 --- a/src/Movement/Kinematics/LinearDeltaKinematics.h +++ b/src/Movement/Kinematics/LinearDeltaKinematics.h @@ -10,14 +10,6 @@ #include "RepRapFirmware.h" #include "Kinematics.h" -#include "Libraries/Math/Matrix.h" - -#ifdef DUET_NG -typedef double floatc_t; // type of matrix element used for delta calibration -#else -// We are more memory-constrained on the SAM3X -typedef float floatc_t; // type of matrix element used for delta calibration -#endif const size_t DELTA_AXES = 3; const size_t A_AXIS = 0; @@ -44,11 +36,16 @@ public: bool IsReachable(float x, float y) const override; bool LimitPosition(float coords[], size_t numVisibleAxes, uint16_t axesHomed) const override; void GetAssumedInitialPosition(size_t numAxes, float positions[]) const override; - uint16_t AxesToHomeBeforeProbing() const override { return (1 << X_AXIS) | (1 << Y_AXIS) | (1 << Z_AXIS); } + uint32_t AxesToHomeBeforeProbing() const override { return (1u << X_AXIS) | (1u << Y_AXIS) | (1u << Z_AXIS); } MotionType GetMotionType(size_t axis) const override; size_t NumHomingButtons(size_t numVisibleAxes) const override { return 0; } bool DriveIsShared(size_t drive) const override { return false; } HomingMode GetHomingMode() const override { return homeIndividualMotors; } + uint32_t AxesAssumedHomed(uint32_t g92Axes) const override; + +#ifdef DUET_NG + bool WriteResumeSettings(FileStore *f) const override; +#endif // Public functions specific to this class float GetDiagonalSquared() const { return D2; } @@ -68,9 +65,6 @@ private: void Adjust(size_t numFactors, const floatc_t v[]); // Perform 3-, 4-, 6- or 7-factor adjustment void PrintParameters(StringRef& reply) const; // Print all the parameters for debugging - static void PrintMatrix(const char* s, const MathMatrix<floatc_t>& m, size_t numRows = 0, size_t maxCols = 0); // for debugging - static void PrintVector(const char *s, const floatc_t *v, size_t numElems); // for debugging - // Delta parameter defaults const float defaultDiagonal = 215.0; const float defaultDeltaRadius = 105.6; @@ -87,7 +81,6 @@ private: float xTilt, yTilt; // How much we need to raise Z for each unit of movement in the +X and +Y directions // Derived values - bool deltaMode; // True if this is a delta printer float towerX[DELTA_AXES]; // The X coordinate of each tower float towerY[DELTA_AXES]; // The Y coordinate of each tower float printRadiusSquared; @@ -95,6 +88,7 @@ private: float Xbc, Xca, Xab, Ybc, Yca, Yab; float coreFa, coreFb, coreFc; float Q, Q2, D2; + bool doneAutoCalibration; // True if we have done auto calibration }; #endif /* LINEARDELTAKINEMATICS_H_ */ diff --git a/src/Movement/Kinematics/ScaraKinematics.cpp b/src/Movement/Kinematics/ScaraKinematics.cpp index 8dbd5845..9b25d62c 100644 --- a/src/Movement/Kinematics/ScaraKinematics.cpp +++ b/src/Movement/Kinematics/ScaraKinematics.cpp @@ -9,7 +9,7 @@ ScaraKinematics::ScaraKinematics() : Kinematics(KinematicsType::scara, DefaultSegmentsPerSecond, DefaultMinSegmentSize, true), - proximalArmLength(DefaultProximalArmLength), distalArmLength(DefaultDistalArmLength) + proximalArmLength(DefaultProximalArmLength), distalArmLength(DefaultDistalArmLength), xOffset(0.0), yOffset(0.0) { thetaLimits[0] = DefaultMinTheta; thetaLimits[1] = DefaultMaxTheta; @@ -26,24 +26,24 @@ const char *ScaraKinematics::GetName(bool forStatusReport) const } // Convert Cartesian coordinates to motor coordinates -// In the following, theta is the proximal arm angle relative to the X axis, psi is the distal arm angle relative to the X axis +// In the following, theta is the proximal arm angle relative to the X axis, psi is the distal arm angle relative to the proximal arm bool ScaraKinematics::CartesianToMotorSteps(const float machinePos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, int32_t motorPos[]) const { // No need to limit x,y to reachable positions here, we already did that in class GCodes - const float x = machinePos[X_AXIS]; - const float y = machinePos[Y_AXIS]; - const float cosPsiMinusTheta = (fsquare(x) + fsquare(y) - proximalArmLengthSquared - distalArmLengthSquared) / (2.0f * proximalArmLength * distalArmLength); + const float x = machinePos[X_AXIS] + xOffset; + const float y = machinePos[Y_AXIS] + yOffset; + const float cosPsi = (fsquare(x) + fsquare(y) - proximalArmLengthSquared - distalArmLengthSquared) / (2.0f * proximalArmLength * distalArmLength); // SCARA position is undefined if abs(SCARA_C2) >= 1. In reality abs(SCARA_C2) >0.95 can be problematic. - const float square = 1.0f - fsquare(cosPsiMinusTheta); + const float square = 1.0f - fsquare(cosPsi); if (square < 0.01f) { return false; // not reachable } const float sinPsiMinusTheta = sqrtf(square); - float psiMinusTheta = acos(cosPsiMinusTheta); - const float SCARA_K1 = proximalArmLength + distalArmLength * cosPsiMinusTheta; + float psi = acos(cosPsi); + const float SCARA_K1 = proximalArmLength + distalArmLength * cosPsi; const float SCARA_K2 = distalArmLength * sinPsiMinusTheta; float theta; @@ -66,7 +66,7 @@ bool ScaraKinematics::CartesianToMotorSteps(const float machinePos[], const floa theta = atan2f(SCARA_K1 * y + SCARA_K2 * x, SCARA_K1 * x - SCARA_K2 * y); if (theta <= thetaLimits[1]) { - psiMinusTheta = -psiMinusTheta; + psi = -psi; break; } } @@ -79,8 +79,7 @@ bool ScaraKinematics::CartesianToMotorSteps(const float machinePos[], const floa switchedMode = true; } - const float psi = theta + psiMinusTheta; -//debugPrintf("psiMinusTheta = %.2f, psi = %.2f, theta = %.2f\n", psiMinusTheta * RadiansToDegrees, psi * RadiansToDegrees, theta * RadiansToDegrees); +//debugPrintf("psi = %.2f, theta = %.2f\n", psi * RadiansToDegrees, theta * RadiansToDegrees); motorPos[X_AXIS] = theta * RadiansToDegrees * stepsPerMm[X_AXIS]; motorPos[Y_AXIS] = (psi * RadiansToDegrees * stepsPerMm[Y_AXIS]) - (crosstalk[0] * motorPos[X_AXIS]); @@ -99,10 +98,10 @@ bool ScaraKinematics::CartesianToMotorSteps(const float machinePos[], const floa void ScaraKinematics::MotorStepsToCartesian(const int32_t motorPos[], const float stepsPerMm[], size_t numVisibleAxes, size_t numTotalAxes, float machinePos[]) const { const float arm1Angle = ((float)motorPos[X_AXIS]/stepsPerMm[X_AXIS]) * DegreesToRadians; - const float arm2Angle = (((float)motorPos[Y_AXIS] + ((float)motorPos[X_AXIS] * crosstalk[0]))/stepsPerMm[Y_AXIS]) * DegreesToRadians; + const float arm2Angle = (((float)motorPos[Y_AXIS] + ((float)motorPos[X_AXIS] * (1.0 + crosstalk[0])))/stepsPerMm[Y_AXIS]) * DegreesToRadians; - machinePos[X_AXIS] = cosf(arm1Angle) * proximalArmLength + cosf(arm2Angle) * distalArmLength; - machinePos[Y_AXIS] = sinf(arm1Angle) * proximalArmLength + sinf(arm2Angle) * distalArmLength; + machinePos[X_AXIS] = (cosf(arm1Angle) * proximalArmLength + cosf(arm2Angle) * distalArmLength) - xOffset; + machinePos[Y_AXIS] = (sinf(arm1Angle) * proximalArmLength + sinf(arm2Angle) * distalArmLength) - yOffset; // On some machines (e.g. Helios), the X and/or Y arm motors also affect the Z height machinePos[Z_AXIS] = ((float)motorPos[Z_AXIS] + ((float)motorPos[X_AXIS] * crosstalk[1]) + ((float)motorPos[Y_AXIS] * crosstalk[2]))/stepsPerMm[Z_AXIS]; @@ -125,6 +124,8 @@ bool ScaraKinematics::Configure(unsigned int mCode, GCodeBuffer& gb, StringRef& gb.TryGetFValue('D', distalArmLength, seen); gb.TryGetFValue('S', segmentsPerSecond, seen); gb.TryGetFValue('T', minSegmentLength, seen); + gb.TryGetFValue('X', xOffset, seen); + gb.TryGetFValue('Y', yOffset, seen); if (gb.TryGetFloatArray('A', 2, thetaLimits, reply, seen)) { return true; @@ -173,13 +174,22 @@ bool ScaraKinematics::IsReachable(float x, float y) const bool ScaraKinematics::LimitPosition(float coords[], size_t numVisibleAxes, uint16_t axesHomed) const { bool limited = false; - float& x = coords[X_AXIS]; - float& y = coords[Y_AXIS]; + float x = coords[X_AXIS] + xOffset; + float y = coords[Y_AXIS] + yOffset; const float r = sqrtf(fsquare(x) + fsquare(y)); if (r < minRadius) { - x *= minRadius/r; - y *= minRadius/r; + // The user may have specified x=0 y=0 so allow for this + if (r < 1.0) + { + x = minRadius; + y = 0.0; + } + else + { + x *= minRadius/r; + y *= minRadius/r; + } limited = true; } else if (r > maxRadius) @@ -188,14 +198,21 @@ bool ScaraKinematics::LimitPosition(float coords[], size_t numVisibleAxes, uint1 y *= maxRadius/r; limited = true; } + + if (limited) + { + coords[X_AXIS] = x - xOffset; + coords[Y_AXIS] = y - yOffset; + } return limited; } // Return the initial Cartesian coordinates we assume after switching to this kinematics void ScaraKinematics::GetAssumedInitialPosition(size_t numAxes, float positions[]) const { - positions[X_AXIS] = maxRadius; - for (size_t i = Y_AXIS; i < numAxes; ++i) + positions[X_AXIS] = maxRadius - xOffset; + positions[Y_AXIS] = -yOffset; + for (size_t i = Z_AXIS; i < numAxes; ++i) { positions[i] = 0.0; } @@ -217,6 +234,18 @@ bool ScaraKinematics::DriveIsShared(size_t drive) const } } +// Return the axes that we can assume are homed after executing a G92 command to set the specified axis coordinates +uint32_t ScaraKinematics::AxesAssumedHomed(uint32_t g92Axes) const +{ + // If both X and Y have been specified then we know the positions of both arm motors, otherwise we don't + const uint32_t xyAxes = (1u << X_AXIS) | (1u << Y_AXIS); + if ((g92Axes & xyAxes) != xyAxes) + { + g92Axes &= ~xyAxes; + } + return g92Axes; +} + // Recalculate the derived parameters void ScaraKinematics::Recalc() { diff --git a/src/Movement/Kinematics/ScaraKinematics.h b/src/Movement/Kinematics/ScaraKinematics.h index 73ff2892..1215bdeb 100644 --- a/src/Movement/Kinematics/ScaraKinematics.h +++ b/src/Movement/Kinematics/ScaraKinematics.h @@ -37,6 +37,7 @@ public: const char* HomingButtonNames() const override { return "PDZUVW"; } bool DriveIsShared(size_t drive) const override; HomingMode GetHomingMode() const override { return homeSharedMotors; } + uint32_t AxesAssumedHomed(uint32_t g92Axes) const override; private: static constexpr float DefaultSegmentsPerSecond = 200.0; @@ -56,6 +57,8 @@ private: float thetaLimits[2]; // minimum proximal joint angle float phiMinusThetaLimits[2]; // minimum distal joint angle float crosstalk[3]; // if we rotate the distal arm motor, for each full rotation the Z height goes up by this amount + float xOffset; // where bed X=0 is relative to the proximal joint + float yOffset; // where bed Y=0 is relative to the proximal joint // Derived parameters float minRadius; diff --git a/src/Movement/Kinematics/ZLeadscrewKinematics.cpp b/src/Movement/Kinematics/ZLeadscrewKinematics.cpp new file mode 100644 index 00000000..bbaa8c72 --- /dev/null +++ b/src/Movement/Kinematics/ZLeadscrewKinematics.cpp @@ -0,0 +1,277 @@ +/* + * ZLeadscrewKinematics.cpp + * + * Created on: 8 Jul 2017 + * Author: David + */ + +#include "ZLeadscrewKinematics.h" +#include "RepRap.h" +#include "Platform.h" +#include "Movement/Move.h" + +ZLeadscrewKinematics::ZLeadscrewKinematics(KinematicsType k) : Kinematics(k), numLeadscrews(0), maxCorrection(1.0) +{ +} + +// Configure this kinematics. We only deal with the leadscrew coordinates here +bool ZLeadscrewKinematics::Configure(unsigned int mCode, GCodeBuffer& gb, StringRef& reply, bool& error) +{ + if (mCode == 671 && GetKinematicsType() != KinematicsType::coreXZ) + { + // Configuring leadscrew positions + const size_t numZDrivers = reprap.GetPlatform().GetAxisDriversConfig(Z_AXIS).numDrivers; + if (numZDrivers < 2 || numZDrivers > MaxLeadscrews) + { + reply.copy("Configure 2 to 4 Z drivers before sending M671"); + return true; + } + + bool seenX = false, seenY = false; + if (gb.TryGetFloatArray('X', numZDrivers, leadscrewX, reply, seenX)) + { + return true; + } + if (gb.TryGetFloatArray('Y', numZDrivers, leadscrewY, reply, seenY)) + { + return true; + } + + bool seenS; + gb.TryGetFValue('S', maxCorrection, seenS); + + if (seenX && seenY) + { + numLeadscrews = numZDrivers; + return false; // successful configuration + } + + if (seenX || seenY) + { + reply.copy("Specify both X and Y coordinates in M671"); + return true; + } + + // If no parameters provided so just report the existing setup + if (seenS) + { + return true; // just changed the maximum correction + } + else if (numLeadscrews < 2) + { + reply.copy("Z leadscrew coordinates are not configured"); + } + else + { + reply.copy("Z leadscrew coordinates"); + for (unsigned int i = 0; i < numLeadscrews; ++i) + { + reply.catf(" (%.1f,%.1f)", leadscrewX[i], leadscrewY[i]); + } + } + return false; + } + return Kinematics::Configure(mCode, gb, reply, error); +} + +// Return true if the kinematics supports auto calibration based on bed probing. +bool ZLeadscrewKinematics::SupportsAutoCalibration() const +{ + return numLeadscrews >= 2; +} + +// Perform auto calibration. Override this implementation in kinematics that support it. +void ZLeadscrewKinematics::DoAutoCalibration(size_t numFactors, const RandomProbePointSet& probePoints, StringRef& reply) +{ + if (!SupportsAutoCalibration()) // should be checked by caller, but check it here too + { + return; + } + + if (numFactors != numLeadscrews) + { + reply.printf("Error: Number of calibration factors (%u) not equal to number of leadscrews (%u)", numFactors, numLeadscrews); + } + + const size_t numPoints = probePoints.NumberOfProbePoints(); + + // Build a Nx4 matrix of derivatives with respect to the leadscrew adjustments + // See the wxMaxima documents for the maths involved + FixedMatrix<floatc_t, MaxDeltaCalibrationPoints, MaxLeadscrews> derivativeMatrix; + floatc_t initialSumOfSquares = 0.0; + for (size_t i = 0; i < numPoints; ++i) + { + float x, y; + const floatc_t zp = reprap.GetMove().GetProbeCoordinates(i, x, y, false); + initialSumOfSquares += fcsquare(zp); + + switch (numFactors) + { + case 2: + { + const floatc_t d2 = fcsquare(leadscrewX[1] - leadscrewX[0]) + fcsquare(leadscrewY[1] - leadscrewY[0]); + // There are lot of common subexpressions in the following, but the optimiser should find them + derivativeMatrix(i, 0) = (fcsquare(leadscrewY[1]) - leadscrewY[0] * leadscrewY[1] - y * (leadscrewY[1] - leadscrewY[0]) + fcsquare(leadscrewX[1]) - leadscrewX[0] * leadscrewX[1] - x * (leadscrewX[1] - leadscrewX[0]))/d2; + derivativeMatrix(i, 1) = (fcsquare(leadscrewY[0]) - leadscrewY[0] * leadscrewY[1] + y * (leadscrewY[1] - leadscrewY[0]) + fcsquare(leadscrewX[0]) - leadscrewX[0] * leadscrewX[1] + x * (leadscrewX[1] - leadscrewX[0]))/d2; + } + break; + + case 3: + { + const floatc_t d2 = leadscrewX[1] * leadscrewY[2] - leadscrewX[0] * leadscrewY[2] - leadscrewX[2] * leadscrewY[1] + leadscrewX[0] * leadscrewY[1] + leadscrewX[2] * leadscrewY[0] - leadscrewX[1] * leadscrewY[0]; + derivativeMatrix(i, 0) = (leadscrewX[1] * leadscrewY[2] - x * leadscrewY[2] - leadscrewX[2] * leadscrewY[1] + x * leadscrewY[1] + leadscrewX[2] * y - leadscrewX[1] * y)/d2; + derivativeMatrix(i, 1) = (leadscrewX[0] * leadscrewY[2] - x * leadscrewY[2] - leadscrewX[2] * leadscrewY[0] + x * leadscrewY[0] + leadscrewX[2] * y - leadscrewX[0] * y)/d2; + derivativeMatrix(i, 2) = (leadscrewX[0] * leadscrewY[1] - x * leadscrewY[1] - leadscrewX[1] * leadscrewY[0] + x * leadscrewY[0] + leadscrewX[1] * y - leadscrewX[0] * y)/d2; + } + break; + + case 4: + { + // This one is horribly complicated. It may not work on the older Duets that use single-precision maths. + const float &x0 = leadscrewX[0], &x1 = leadscrewX[1], &x2 = leadscrewX[2], &x3 = leadscrewX[3]; + const float &y0 = leadscrewY[0], &y1 = leadscrewY[1], &y2 = leadscrewY[2], &y3 = leadscrewY[3]; + const floatc_t d2 = x1 * x3 * y2 * y3 + - x0 * x3 * y2 * y3 + - x1 * x2 * y2 * y3 + + x0 * x2 * y2 * y3 + - x2 * x3 * y1 * y3 + + x0 * x3 * y1 * y3 + + x1 * x2 * y1 * y3 + - x0 * x1 * y1 * y3 + + x2 * x3 * y0 * y3 + - x1 * x3 * y0 * y3 + - x0 * x2 * y0 * y3 + + x0 * x1 * y0 * y3 + + x2 * x3 * y1 * y2 + - x1 * x3 * y1 * y2 + - x0 * x2 * y1 * y2 + + x0 * x1 * y1 * y2 + - x2 * x3 * y0 * y2 + + x0 * x3 * y0 * y2 + + x1 * x2 * y0 * y2 + - x0 * x1 * y0 * y2 + + x1 * x3 * y0 * y1 + - x0 * x3 * y0 * y1 + - x1 * x2 * y0 * y1 + + x0 * x2 * y0 * y1; + derivativeMatrix(i, 0) = (x1*x3*y2*y3-x*x3*y2*y3-x1*x2*y2*y3+x*x2*y2*y3-x2*x3*y1*y3+x*x3*y1*y3+x1*x2*y1*y3 + -x*x1*y1*y3+x2*x3*y*y3-x1*x3*y*y3-x*x2*y*y3+x*x1*y*y3+x2*x3*y1*y2-x1*x3*y1*y2 + -x*x2*y1*y2+x*x1*y1*y2-x2*x3*y*y2+x*x3*y*y2+x1*x2*y*y2-x*x1*y*y2+x1*x3*y*y1-x*x3*y*y1-x1*x2*y*y1+x*x2*y*y1)/d2; + derivativeMatrix(i, 1) = -(x0*x3*y2*y3-x*x3*y2*y3-x0*x2*y2*y3+x*x2*y2*y3-x2*x3*y0*y3+x*x3*y0*y3+x0*x2*y0*y3 + -x*x0*y0*y3+x2*x3*y*y3-x0*x3*y*y3-x*x2*y*y3+x*x0*y*y3+x2*x3*y0*y2-x0*x3*y0* + y2-x*x2*y0*y2+x*x0*y0*y2-x2*x3*y*y2+x*x3*y*y2+x0*x2*y*y2-x*x0*y*y2+x0*x3*y*y0-x*x3*y*y0-x0*x2*y*y0+x*x2*y*y0)/d2; + derivativeMatrix(i, 2) = (x0*x3*y1*y3-x*x3*y1*y3-x0*x1*y1*y3+x*x1*y1*y3-x1*x3*y0*y3+x*x3*y0*y3+x0*x1*y0*y3-x*x0*y0*y3+x1*x3*y*y3-x0*x3*y*y3-x*x1*y*y3+x*x0*y*y3+x1*x3*y0*y1-x0*x3*y0*y1 + -x*x1*y0*y1+x*x0*y0*y1-x1*x3*y*y1+x*x3*y*y1+x0*x1*y*y1-x*x0*y*y1+x0*x3*y*y0-x*x3*y*y0-x0*x1*y*y0+x*x1*y*y0)/d2; + derivativeMatrix(i, 3) = -(x0*x2*y1*y2-x*x2*y1*y2-x0*x1*y1*y2+x*x1*y1*y2-x1*x2*y0*y2+x*x2*y0*y2+x0*x1*y0*y2 + -x*x0*y0*y2+x1*x2*y*y2-x0*x2*y*y2-x*x1*y*y2+x*x0*y*y2+x1*x2*y0*y1-x0*x2*y0* + y1-x*x1*y0*y1+x*x0*y0*y1-x1*x2*y*y1+x*x2*y*y1+x0*x1*y*y1-x*x0*y*y1+x0*x2*y*y0-x*x2*y*y0-x0*x1*y*y0+x*x1*y*y0)/d2; + } + break; + } + } + + if (reprap.Debug(moduleMove)) + { + PrintMatrix("Derivative matrix", derivativeMatrix, numPoints, numFactors); + } + + // Now build the normal equations for least squares fitting + FixedMatrix<floatc_t, MaxLeadscrews, MaxLeadscrews + 1> normalMatrix; + for (size_t i = 0; i < numFactors; ++i) + { + for (size_t j = 0; j < numFactors; ++j) + { + floatc_t temp = derivativeMatrix(0, i) * derivativeMatrix(0, j); + for (size_t k = 1; k < numPoints; ++k) + { + temp += derivativeMatrix(k, i) * derivativeMatrix(k, j); + } + normalMatrix(i, j) = temp; + } + floatc_t temp = derivativeMatrix(0, i) * -(probePoints.GetZHeight(0)); + for (size_t k = 1; k < numPoints; ++k) + { + temp += derivativeMatrix(k, i) * -(probePoints.GetZHeight(k)); + } + normalMatrix(i, numFactors) = temp; + } + + if (reprap.Debug(moduleMove)) + { + PrintMatrix("Normal matrix", normalMatrix, numFactors, numFactors + 1); + } + + floatc_t solution[MaxLeadscrews]; + normalMatrix.GaussJordan(solution, numFactors); + + if (reprap.Debug(moduleMove)) + { + PrintMatrix("Solved matrix", normalMatrix, numFactors, numFactors + 1); + PrintVector("Solution", solution, numFactors); + } + + // Calculate and display the residuals, also check for errors + floatc_t residuals[MaxDeltaCalibrationPoints]; + floatc_t sumOfSquares = 0.0; + for (size_t i = 0; i < numPoints; ++i) + { + residuals[i] = probePoints.GetZHeight(i); + for (size_t j = 0; j < numFactors; ++j) + { + residuals[i] += solution[j] * derivativeMatrix(i, j); + } + sumOfSquares += fcsquare(residuals[i]); + } + + if (reprap.Debug(moduleMove)) + { + PrintVector("Residuals", residuals, numPoints); + } + + // Check that the corrections are sensible + bool haveNaN = false, haveLargeCorrection = false; + for (size_t i = 0; i < numFactors; ++i) + { + if (std::isnan(solution[i])) + { + haveNaN = true; + } + else if (fabs(solution[i]) > maxCorrection) + { + haveLargeCorrection = true; + } + } + + if (haveNaN) + { + reply.printf("Error: calibration failed, computed corrections:"); + } + else if (haveLargeCorrection) + { + reply.printf("Error: computed corrections exceed 1mm:"); + } + else + { + //TODO adjust the motors here + reply.printf("Simulated calibrating %d leadscrews using %d points, deviation before %.3f after %.3f, corrections:", + numFactors, numPoints, sqrt(initialSumOfSquares/numPoints), sqrtf(sumOfSquares/numPoints)); + } + + // Append the corrections to the reply in all cases + for (size_t i = 0; i < numFactors; ++i) + { + reply.catf(" %.3f", solution[i]); + } +} + +#ifdef DUET_NG + +// Write any calibration data that we need to resume a print after power fail, returning true if successful +bool ZLeadscrewKinematics::WriteResumeSettings(FileStore *f) const +{ + //TODO write leadscrew corrections, there is a chance that they will be the same as before + return true; +} + +#endif +// End diff --git a/src/Movement/Kinematics/ZLeadscrewKinematics.h b/src/Movement/Kinematics/ZLeadscrewKinematics.h new file mode 100644 index 00000000..59778b8f --- /dev/null +++ b/src/Movement/Kinematics/ZLeadscrewKinematics.h @@ -0,0 +1,33 @@ +/* + * ZLeadscrewKinematics.h + * + * Created on: 8 Jul 2017 + * Author: David + */ + +#ifndef SRC_MOVEMENT_KINEMATICS_ZLEADSCREWKINEMATICS_H_ +#define SRC_MOVEMENT_KINEMATICS_ZLEADSCREWKINEMATICS_H_ + +#include "Kinematics.h" + +class ZLeadscrewKinematics : public Kinematics +{ +public: + ZLeadscrewKinematics(KinematicsType k); + bool Configure(unsigned int mCode, GCodeBuffer& gb, StringRef& reply, bool& error) override; + bool SupportsAutoCalibration() const override; + void DoAutoCalibration(size_t numFactors, const RandomProbePointSet& probePoints, StringRef& reply) override; + +#ifdef DUET_NG + bool WriteResumeSettings(FileStore *f) const override; +#endif + +private: + static const unsigned int MaxLeadscrews = 4; + + unsigned int numLeadscrews; + float leadscrewX[MaxLeadscrews], leadscrewY[MaxLeadscrews]; + float maxCorrection; +}; + +#endif /* SRC_MOVEMENT_KINEMATICS_ZLEADSCREWKINEMATICS_H_ */ diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp index 47f8fa9b..e94b9a3f 100644 --- a/src/Movement/Move.cpp +++ b/src/Movement/Move.cpp @@ -159,7 +159,7 @@ void Move::Spin() const bool doMotorMapping = (nextMove.moveType == 0) || (nextMove.moveType == 1 && kinematics->GetHomingMode() == Kinematics::homeCartesianAxes); if (doMotorMapping) { - AxisAndBedTransform(nextMove.coords, nextMove.xAxes, nextMove.moveType == 0); + AxisAndBedTransform(nextMove.coords, nextMove.xAxes, nextMove.yAxes, nextMove.moveType == 0); } if (ddaRingAddPointer->Init(nextMove, doMotorMapping)) { @@ -371,7 +371,7 @@ bool Move::PausePrint(RestorePoint& rp) rp.moveCoords[axis] = prevDda->GetEndCoordinate(axis, false); } - InverseAxisAndBedTransform(rp.moveCoords, prevDda->GetXAxes()); // we assume that xAxes hasn't changed between the moves + InverseAxisAndBedTransform(rp.moveCoords, prevDda->GetXAxes(), prevDda->GetYAxes()); // we assume that xAxes hasn't changed between the moves const size_t numTotalAxes = reprap.GetGCodes().GetTotalAxes(); for (size_t drive = numTotalAxes; drive < DRIVES; ++drive) @@ -482,7 +482,7 @@ void Move::SetNewPosition(const float positionNow[DRIVES], bool doBedCompensatio { float newPos[DRIVES]; memcpy(newPos, positionNow, sizeof(newPos)); // copy to local storage because Transform modifies it - AxisAndBedTransform(newPos, reprap.GetCurrentXAxes(), doBedCompensation); + AxisAndBedTransform(newPos, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes(), doBedCompensation); SetLiveCoordinates(newPos); SetPositions(newPos); } @@ -544,18 +544,18 @@ bool Move::CartesianToMotorSteps(const float machinePos[MaxAxes], int32_t motorP return b; } -void Move::AxisAndBedTransform(float xyzPoint[MaxAxes], uint32_t xAxes, bool useBedCompensation) const +void Move::AxisAndBedTransform(float xyzPoint[MaxAxes], uint32_t xAxes, uint32_t yAxes, bool useBedCompensation) const { AxisTransform(xyzPoint); if (useBedCompensation) { - BedTransform(xyzPoint, xAxes); + BedTransform(xyzPoint, xAxes, yAxes); } } -void Move::InverseAxisAndBedTransform(float xyzPoint[MaxAxes], uint32_t xAxes) const +void Move::InverseAxisAndBedTransform(float xyzPoint[MaxAxes], uint32_t xAxes, uint32_t yAxes) const { - InverseBedTransform(xyzPoint, xAxes); + InverseBedTransform(xyzPoint, xAxes, yAxes); InverseAxisTransform(xyzPoint); } @@ -576,36 +576,43 @@ void Move::InverseAxisTransform(float xyzPoint[MaxAxes]) const } // Do the bed transform AFTER the axis transform -void Move::BedTransform(float xyzPoint[MaxAxes], uint32_t xAxes) const +void Move::BedTransform(float xyzPoint[MaxAxes], uint32_t xAxes, uint32_t yAxes) const { if (!useTaper || xyzPoint[Z_AXIS] < taperHeight) { float zCorrection = 0.0; const size_t numAxes = reprap.GetGCodes().GetVisibleAxes(); - unsigned int numXAxes = 0; + unsigned int numCorrections = 0; // Transform the Z coordinate based on the average correction for each axis used as an X axis. // We are assuming that the tool Y offsets are small enough to be ignored. - for (uint32_t axis = 0; axis < numAxes; ++axis) + for (uint32_t xAxis = 0; xAxis < numAxes; ++xAxis) { - if ((xAxes & (1u << axis)) != 0) + if ((xAxes & (1u << xAxis)) != 0) { - const float xCoord = xyzPoint[axis]; - if (usingMesh) + const float xCoord = xyzPoint[xAxis]; + for (uint32_t yAxis = 0; yAxis < numAxes; ++yAxis) { - zCorrection += grid.GetInterpolatedHeightError(xCoord, xyzPoint[Y_AXIS]); - } - else - { - zCorrection += probePoints.GetInterpolatedHeightError(xCoord, xyzPoint[Y_AXIS]); + if ((yAxes & (1u << yAxis)) != 0) + { + const float yCoord = xyzPoint[yAxis]; + if (usingMesh) + { + zCorrection += grid.GetInterpolatedHeightError(xCoord, yCoord); + } + else + { + zCorrection += probePoints.GetInterpolatedHeightError(xCoord, yCoord); + } + ++numCorrections; + } } - ++numXAxes; } } - if (numXAxes > 1) + if (numCorrections > 1) { - zCorrection /= numXAxes; // take an average + zCorrection /= numCorrections; // take an average } xyzPoint[Z_AXIS] += (useTaper) ? (taperHeight - xyzPoint[Z_AXIS]) * recipTaperHeight * zCorrection : zCorrection; @@ -613,35 +620,41 @@ void Move::BedTransform(float xyzPoint[MaxAxes], uint32_t xAxes) const } // Invert the bed transform BEFORE the axis transform -void Move::InverseBedTransform(float xyzPoint[MaxAxes], uint32_t xAxes) const +void Move::InverseBedTransform(float xyzPoint[MaxAxes], uint32_t xAxes, uint32_t yAxes) const { float zCorrection = 0.0; const size_t numAxes = reprap.GetGCodes().GetVisibleAxes(); - unsigned int numXAxes = 0; + unsigned int numCorrections = 0; // Transform the Z coordinate based on the average correction for each axis used as an X axis. // We are assuming that the tool Y offsets are small enough to be ignored. - for (uint32_t axis = 0; axis < numAxes; ++axis) + for (uint32_t xAxis = 0; xAxis < numAxes; ++xAxis) { - if ((xAxes & (1u << axis)) != 0) + if ((xAxes & (1u << xAxis)) != 0) { - const float xCoord = xyzPoint[axis]; - if (usingMesh) + const float xCoord = xyzPoint[xAxis]; + for (uint32_t yAxis = 0; yAxis < numAxes; ++yAxis) { - zCorrection += grid.GetInterpolatedHeightError(xCoord, xyzPoint[Y_AXIS]); - } - else - { - zCorrection += probePoints.GetInterpolatedHeightError(xCoord, xyzPoint[Y_AXIS]); - + if ((yAxes & (1u << yAxis)) != 0) + { + const float yCoord = xyzPoint[yAxis]; + if (usingMesh) + { + zCorrection += grid.GetInterpolatedHeightError(xCoord, yCoord); + } + else + { + zCorrection += probePoints.GetInterpolatedHeightError(xCoord, yCoord); + } + ++numCorrections; + } } - ++numXAxes; } } - if (numXAxes > 1) + if (numCorrections > 1) { - zCorrection /= numXAxes; // take an average + zCorrection /= numCorrections; // take an average } if (!useTaper || zCorrection >= taperHeight) // need check on zCorrection to avoid possible divide by zero @@ -898,18 +911,18 @@ bool Move::IsExtruding() const } // Return the transformed machine coordinates -void Move::GetCurrentUserPosition(float m[DRIVES], uint8_t moveType, uint32_t xAxes) const +void Move::GetCurrentUserPosition(float m[DRIVES], uint8_t moveType, uint32_t xAxes, uint32_t yAxes) const { GetCurrentMachinePosition(m, moveType == 2 || (moveType == 1 && IsDeltaMode())); if (moveType == 0) { - InverseAxisAndBedTransform(m, xAxes); + InverseAxisAndBedTransform(m, xAxes, yAxes); } } // Return the current live XYZ and extruder coordinates // Interrupts are assumed enabled on entry -void Move::LiveCoordinates(float m[DRIVES], uint32_t xAxes) +void Move::LiveCoordinates(float m[DRIVES], uint32_t xAxes, uint32_t yAxes) { // The live coordinates and live endpoints are modified by the ISR, so be careful to get a self-consistent set of them const size_t numVisibleAxes = reprap.GetGCodes().GetVisibleAxes(); // do this before we disable interrupts @@ -940,7 +953,7 @@ void Move::LiveCoordinates(float m[DRIVES], uint32_t xAxes) } cpu_irq_enable(); } - InverseAxisAndBedTransform(m, xAxes); + InverseAxisAndBedTransform(m, xAxes, yAxes); } // These are the actual numbers that we want to be the coordinates, so don't transform them. @@ -1009,12 +1022,24 @@ float Move::GetProbeCoordinates(int count, float& x, float& y, bool wantNozzlePo void Move::Simulate(uint8_t simMode) { simulationMode = simMode; - if (simMode) + if (simMode != 0) { simulationTime = 0.0; } } +#ifdef DUET_NG + +// Write settings for resuming the print +// The GCodes module deals with the head position so all we need worry about is the bed compensation +// We don't handle random probe point bed compensation, and we assume that if a height map is being used it is the default one. +bool Move::WriteResumeSettings(FileStore *f) const +{ + return kinematics->WriteResumeSettings(f) && (!usingMesh || f->Write("G29 S1\n")); +} + +#endif + // For debugging void Move::PrintCurrentDda() const { diff --git a/src/Movement/Move.h b/src/Movement/Move.h index c822f575..a7d4f858 100644 --- a/src/Movement/Move.h +++ b/src/Movement/Move.h @@ -35,9 +35,9 @@ public: void Exit(); // Shut down void GetCurrentMachinePosition(float m[DRIVES], bool disableMotorMapping) const; // Get the current position in untransformed coords - void GetCurrentUserPosition(float m[DRIVES], uint8_t moveType, uint32_t xAxes) const; // Return the position (after all queued moves have been executed) in transformed coords + void GetCurrentUserPosition(float m[DRIVES], uint8_t moveType, uint32_t xAxes, uint32_t yAxes) const; // Return the position (after all queued moves have been executed) in transformed coords int32_t GetEndPoint(size_t drive) const { return liveEndPoints[drive]; } // Get the current position of a motor - void LiveCoordinates(float m[DRIVES], uint32_t xAxes); // Gives the last point at the end of the last complete DDA transformed to user coords + void LiveCoordinates(float m[DRIVES], uint32_t xAxes, uint32_t yAxes); // 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. @@ -55,8 +55,8 @@ public: 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 AxisAndBedTransform(float move[], uint32_t xAxes, bool useBedCompensation) const; // Take a position and apply the bed and the axis-angle compensations - void InverseAxisAndBedTransform(float move[], uint32_t xAxes) const; // Go from a transformed point back to user coordinates + void AxisAndBedTransform(float move[], uint32_t xAxes, uint32_t yAxes, bool useBedCompensation) const; // Take a position and apply the bed and the axis-angle compensations + void InverseAxisAndBedTransform(float move[], uint32_t xAxes, uint32_t yAxes) const; // Go from a transformed point back to user coordinates float GetTaperHeight() const { return (useTaper) ? taperHeight : 0.0; } void SetTaperHeight(float h); bool UseMesh(bool b); // Try to enable mesh bed compensation and report the final state @@ -103,6 +103,10 @@ public: const DDA *GetCurrentDDA() const { return currentDda; } // Return the DDA of the currently-executing move +#ifdef DUET_NG + bool WriteResumeSettings(FileStore *f) const; // Write settings for resuming the print +#endif + static int32_t MotorEndPointToMachine(size_t drive, float coord); // Convert a single motor position to number of steps static float MotorEndpointToPosition(int32_t endpoint, size_t drive); // Convert number of motor steps to motor position @@ -110,8 +114,8 @@ private: enum class IdleState : uint8_t { idle, busy, timing }; bool StartNextMove(uint32_t startTime); // start the next move, returning true if Step() needs to be called immediately - void BedTransform(float move[MaxAxes], uint32_t xAxes) const; // Take a position and apply the bed compensations - void InverseBedTransform(float move[MaxAxes], uint32_t xAxes) const; // Go from a bed-transformed point back to user coordinates + void BedTransform(float move[MaxAxes], uint32_t xAxes, uint32_t yAxes) const; // Take a position and apply the bed compensations + void InverseBedTransform(float move[MaxAxes], uint32_t xAxes, uint32_t yAxes) const; // Go from a bed-transformed point back to user coordinates void AxisTransform(float move[MaxAxes]) const; // Take a position and apply the axis-angle compensations void InverseAxisTransform(float move[MaxAxes]) const; // Go from an axis transformed point back to user coordinates void JustHomed(size_t axis, float hitPoint, DDA* hitDDA); // Deal with setting positions after a drive has been homed diff --git a/src/Platform.cpp b/src/Platform.cpp index 62dd6db7..5211346f 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -46,10 +46,22 @@ extern char _end; extern "C" char *sbrk(int i); #ifdef DUET_NG -const uint16_t driverPowerOnAdcReading = (uint16_t)(4096 * 10.0/PowerFailVoltageRange); // minimum voltage at which we initialise the drivers -const uint16_t driverPowerOffAdcReading = (uint16_t)(4096 * 9.5/PowerFailVoltageRange); // voltages below this flag the drivers as unusable -const uint16_t driverOverVoltageAdcReading = (uint16_t)(4096 * 29.0/PowerFailVoltageRange); // voltages above this cause driver shutdown -const uint16_t driverNormalVoltageAdcReading = (uint16_t)(4096 * 27.5/PowerFailVoltageRange); // voltages at or below this are normal + +inline constexpr float AdcReadingToPowerVoltage(uint16_t adcVal) +{ + return adcVal * (PowerMonitorVoltageRange/4096.0); +} + +inline constexpr uint16_t PowerVoltageToAdcReading(float voltage) +{ + return (uint16_t)(voltage * (4096.0/PowerMonitorVoltageRange)); +} + +constexpr uint16_t driverPowerOnAdcReading = PowerVoltageToAdcReading(10.0); // minimum voltage at which we initialise the drivers +constexpr uint16_t driverPowerOffAdcReading = PowerVoltageToAdcReading(9.5); // voltages below this flag the drivers as unusable +constexpr uint16_t driverOverVoltageAdcReading = PowerVoltageToAdcReading(29.0); // voltages above this cause driver shutdown +constexpr uint16_t driverNormalVoltageAdcReading = PowerVoltageToAdcReading(27.5); // voltages at or below this are normal + #endif const uint8_t memPattern = 0xA5; @@ -430,7 +442,7 @@ void Platform::Init() endStopLogicLevel[drive] = true; // assume all endstops use active high logic e.g. normally-closed switch to ground } - driveDriverBits[drive] = CalcDriverBitmap(drive); + driveDriverBits[drive] = driveDriverBits[drive + DRIVES] = CalcDriverBitmap(drive); // Set up the control pins and endstops pinMode(STEP_PINS[drive], OUTPUT_LOW); @@ -495,9 +507,11 @@ void Platform::Init() temperatureShutdownDrivers = temperatureWarningDrivers = shortToGroundDrivers = openLoadDrivers = 0; onBoardDriversFanRunning = offBoardDriversFanRunning = false; + autoSaveEnabled = false; + autoSaveState = AutoSaveState::normal; #endif - // Allow extrusion ancilliary PWM to use FAN0 even if FAN0 has not been disabled, for backwards compatibility + // Allow extrusion ancillary PWM to use FAN0 even if FAN0 has not been disabled, for backwards compatibility extrusionAncilliaryPwmValue = 0.0; extrusionAncilliaryPwmFrequency = DefaultPinWritePwmFreq; extrusionAncilliaryPwmLogicalPin = Fan0LogicalPin; @@ -1405,10 +1419,60 @@ void Platform::Spin() } } +#ifdef DUET_NG + // Check for auto-pause, shutdown or resume + if (autoSaveEnabled) + { + switch (autoSaveState) + { + case AutoSaveState::normal: + if (currentVin < autoPauseReading) + { + if (reprap.GetGCodes().AutoPause()) + { + autoSaveState = AutoSaveState::autoPaused; + } + } + break; + + case AutoSaveState::autoPaused: + if (currentVin < autoShutdownReading) + { + if (reprap.GetGCodes().AutoShutdown()) + { + autoSaveState = AutoSaveState::autoShutdown; + } + } + else if (currentVin >= autoResumeReading) + { + if (reprap.GetGCodes().AutoResume()) + { + autoSaveState = AutoSaveState::normal; + } + } + break; + + case AutoSaveState::autoShutdown: + if (currentVin >= autoResumeReading) + { + if (reprap.GetGCodes().AutoResumeAfterShutdown()) + { + autoSaveState = AutoSaveState::normal; + } + } + break; + + default: + break; + } + } +#endif + ClassReport(longWait); } #ifdef DUET_NG + // Report driver status conditions that require attention. // Sets 'reported' if we reported anything, else leaves 'reported' alone. void Platform::ReportDrivers(uint16_t whichDrivers, const char* text, bool& reported) @@ -1428,6 +1492,48 @@ void Platform::ReportDrivers(uint16_t whichDrivers, const char* text, bool& repo reported = true; } } + +// Configure auto save on power fail +void Platform::ConfigureAutoSave(GCodeBuffer& gb, StringRef& reply, bool& error) +{ + bool seen = false; + float autoSaveVoltages[3]; + if (gb.TryGetFloatArray('S', 3, autoSaveVoltages, reply, seen)) + { + error = true; + } + else if (seen) + { + autoSaveEnabled = (autoSaveVoltages[0] >= 5.0 && autoSaveVoltages[1] > autoSaveVoltages[0] && autoSaveVoltages[2] > autoSaveVoltages[1]); + if (autoSaveEnabled) + { + autoShutdownReading = PowerVoltageToAdcReading(autoSaveVoltages[0]); + autoPauseReading = PowerVoltageToAdcReading(autoSaveVoltages[1]); + autoResumeReading = PowerVoltageToAdcReading(autoSaveVoltages[2]); + } + } + else if (!autoSaveEnabled) + { + reply.copy("Auto save is disabled"); + } + else + { + reply.printf(" Auto shutdown at %.1fV, save/pause at %.1fV, resume at %.1fV", + AdcReadingToPowerVoltage(autoShutdownReading), AdcReadingToPowerVoltage(autoPauseReading), AdcReadingToPowerVoltage(autoResumeReading)); + } +} + +// Save some resume information +bool Platform::WriteFanSettings(FileStore *f) const +{ + bool ok = true; + for (size_t fanNum = 0; ok && fanNum < NUM_FANS; ++fanNum) + { + ok = fans[fanNum].WriteSettings(f, fanNum); + } + return ok; +} + #endif float Platform::AdcReadingToCpuTemperature(uint32_t adcVal) const diff --git a/src/Platform.h b/src/Platform.h index d1f76232..49d1aab1 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -395,8 +395,6 @@ public: // Movement void EmergencyStop(); - void SetPhysicalDrives(size_t drive, uint32_t physicalDrives); - uint32_t GetPhysicalDrives(size_t drive) const; void SetDirection(size_t drive, bool direction); void SetDirectionValue(size_t driver, bool dVal); bool GetDirectionValue(size_t driver) const; @@ -565,6 +563,8 @@ public: void GetPowerVoltages(float& minV, float& currV, float& maxV) const; float GetTmcDriversTemperature(unsigned int board) const; void DriverCoolingFansOn(uint32_t driverChannelsMonitored); + void ConfigureAutoSave(GCodeBuffer& gb, StringRef& reply, bool& error); + bool WriteFanSettings(FileStore *f) const; // Save some resume information #endif // User I/O and servo support @@ -579,7 +579,6 @@ private: float AdcReadingToCpuTemperature(uint32_t reading) const; #ifdef DUET_NG - static float AdcReadingToPowerVoltage(uint16_t reading); void ReportDrivers(uint16_t whichDrivers, const char* text, bool& reported); #endif @@ -682,9 +681,9 @@ private: float pressureAdvance[MaxExtruders]; float motorCurrents[DRIVES]; // the normal motor current for each stepper driver float motorCurrentFraction[DRIVES]; // the percentages of normal motor current that each driver is set to - AxisDriversConfig axisDrivers[MaxAxes]; // the driver numbers assigned to each axis + AxisDriversConfig axisDrivers[MaxAxes]; // the driver numbers assigned to each axis uint8_t extruderDrivers[MaxExtruders]; // the driver number assigned to each extruder - uint32_t driveDriverBits[DRIVES]; // the bitmap of driver port bits for each axis or extruder + uint32_t driveDriverBits[2 * DRIVES]; // the bitmap of driver port bits for each axis or extruder, followed by the raw versions uint32_t slowDriverStepPulseClocks; // minimum high and low step pulse widths, in processor clocks uint32_t slowDrivers; // bitmap of driver port bits that need extended step pulse timing float idleCurrentFactor; @@ -832,6 +831,16 @@ private: bool offBoardDriversFanRunning; // true if a fan is running to cool the drivers on the DueX uint32_t onBoardDriversFanStartMillis; // how many times we have suppressed a temperature warning uint32_t offBoardDriversFanStartMillis; // how many times we have suppressed a temperature warning + uint16_t autoShutdownReading, autoPauseReading, autoResumeReading; + bool autoSaveEnabled; + + enum class AutoSaveState : uint8_t + { + normal = 0, + autoPaused, + autoShutdown + }; + AutoSaveState autoSaveState; #endif // RTC @@ -1214,13 +1223,6 @@ inline OutputBuffer *Platform::GetAuxGCodeReply() return temp; } -#ifdef DUET_NG -inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal) -{ - return adcVal * (PowerFailVoltageRange/4096.0); -} -#endif - // *** These next two functions must use the same bit assignments in the drivers bitmap *** // The bitmaps are organised like this: // Duet WiFi: diff --git a/src/PortControl.cpp b/src/PortControl.cpp index 9b9d8b34..78e89609 100644 --- a/src/PortControl.cpp +++ b/src/PortControl.cpp @@ -37,33 +37,36 @@ void PortControl::Exit() void PortControl::Spin(bool full) { - cpu_irq_disable(); - const DDA * cdda = reprap.GetMove().GetCurrentDDA(); - if (cdda == nullptr) + if (numConfiguredPorts != 0) { - // Movement has stopped, so turn all ports off - cpu_irq_enable(); - UpdatePorts(0); - } - else - { - const uint32_t now = Platform::GetInterruptClocks() + advanceClocks; - uint32_t moveEndTime = cdda->GetMoveStartTime(); - DDA::DDAState st = cdda->GetState(); - do + cpu_irq_disable(); + const DDA * cdda = reprap.GetMove().GetCurrentDDA(); + if (cdda == nullptr) + { + // Movement has stopped, so turn all ports off + cpu_irq_enable(); + UpdatePorts(0); + } + else { - moveEndTime += cdda->GetClocksNeeded(); - if ((int32_t)(moveEndTime - now) >= 0) + const uint32_t now = Platform::GetInterruptClocks() + advanceClocks; + uint32_t moveEndTime = cdda->GetMoveStartTime(); + DDA::DDAState st = cdda->GetState(); + do { - break; - } - cdda = cdda->GetNext(); - st = cdda->GetState(); - } while (st == DDA::executing || st == DDA::frozen); - cpu_irq_enable(); + moveEndTime += cdda->GetClocksNeeded(); + if ((int32_t)(moveEndTime - now) >= 0) + { + break; + } + cdda = cdda->GetNext(); + st = cdda->GetState(); + } while (st == DDA::executing || st == DDA::frozen); + cpu_irq_enable(); - const IoBits_t bits = (st == DDA::executing || st == DDA::frozen || st == DDA::provisional) ? cdda->GetIoBits() : 0; - UpdatePorts(bits); + const IoBits_t bits = (st == DDA::executing || st == DDA::frozen || st == DDA::provisional) ? cdda->GetIoBits() : 0; + UpdatePorts(bits); + } } } diff --git a/src/PrintMonitor.cpp b/src/PrintMonitor.cpp index 0c821d0e..4f5c6fae 100644 --- a/src/PrintMonitor.cpp +++ b/src/PrintMonitor.cpp @@ -116,7 +116,7 @@ void PrintMonitor::Spin() else if (!gCodes.DoingFileMacro() && reprap.GetMove().IsExtruding()) { float liveCoordinates[DRIVES]; - reprap.GetMove().LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes()); + reprap.GetMove().LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); // See if we need to determine the first layer height (usually smaller than the nozzle diameter) if (printingFileInfo.firstLayerHeight == 0.0) diff --git a/src/PrintMonitor.h b/src/PrintMonitor.h index 71c705da..0be4a68e 100644 --- a/src/PrintMonitor.h +++ b/src/PrintMonitor.h @@ -99,6 +99,10 @@ class PrintMonitor float GetFirstLayerDuration() const; float GetFirstLayerHeight() const; +#ifdef DUET_NG + const char *GetPrintingFilename() const { return (isPrinting) ? filenameBeingPrinted : nullptr; } +#endif + private: Platform& platform; GCodes& gCodes; diff --git a/src/RepRap.cpp b/src/RepRap.cpp index 146cab91..706f18d0 100644 --- a/src/RepRap.cpp +++ b/src/RepRap.cpp @@ -39,7 +39,7 @@ extern "C" void hsmciIdle() // Do nothing more in the constructor; put what you want in RepRap:Init() -RepRap::RepRap() : toolList(nullptr), currentTool(nullptr), lastWarningMillis(0), activeExtruders(0), +RepRap::RepRap() : toolList(nullptr), currentTool(nullptr), lastStandbyTool(nullptr), lastWarningMillis(0), activeExtruders(0), activeToolHeaters(0), ticksInSpinState(0), spinningModule(noModule), debug(0), stopped(false), active(false), resetting(false), processingConfig(true), beepFrequency(0), beepDuration(0) { @@ -425,6 +425,7 @@ void RepRap::StandbyTool(int toolNumber) if (tool != nullptr) { tool->Standby(); + lastStandbyTool = tool; if (currentTool == tool) { currentTool = nullptr; @@ -554,7 +555,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) else #endif { - move->LiveCoordinates(liveCoordinates, GetCurrentXAxes()); + move->LiveCoordinates(liveCoordinates, GetCurrentXAxes(), GetCurrentYAxes()); } if (currentTool != nullptr) @@ -933,7 +934,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) } } - // Axis mapping. Currently we only map the X axis, but we return an array of arrays to allow for mapping other axes in future. + // Axis mapping response->cat("],\"axisMap\":[["); bool first = true; for (size_t xi = 0; xi < MaxAxes; ++xi) @@ -951,6 +952,23 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) response->catf("%u", xi); } } + response->cat("],["); + first = true; + for (size_t yi = 0; yi < MaxAxes; ++yi) + { + if ((tool->GetYAxisMap() & (1u << yi)) != 0) + { + if (first) + { + first = false; + } + else + { + response->cat(","); + } + response->catf("%u", yi); + } + } response->cat("]]"); // Filament (if any) @@ -1246,7 +1264,7 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) // Send XYZ positions const size_t numAxes = reprap.GetGCodes().GetVisibleAxes(); float liveCoordinates[DRIVES]; - reprap.GetMove().LiveCoordinates(liveCoordinates, GetCurrentXAxes()); + reprap.GetMove().LiveCoordinates(liveCoordinates, GetCurrentXAxes(), GetCurrentYAxes()); const Tool* const currentTool = reprap.GetCurrentTool(); if (currentTool != nullptr) { @@ -1655,7 +1673,7 @@ unsigned int RepRap::GetProhibitedExtruderMovements(unsigned int extrusions, uns return 0; } - Tool *tool = currentTool; + Tool * const tool = currentTool; if (tool == nullptr) { // This should not happen, but if on tool is selected then don't allow any extruder movement @@ -1709,6 +1727,38 @@ uint32_t RepRap::GetCurrentXAxes() const return (currentTool == nullptr) ? DefaultXAxisMapping : currentTool->GetXAxisMap(); } +// Get the current axes used as X axes +uint32_t RepRap::GetCurrentYAxes() const +{ + return (currentTool == nullptr) ? DefaultYAxisMapping : currentTool->GetYAxisMap(); +} + +#ifdef DUET_NG + +// Save some resume information, returning true if successful +// We assume that the tool configuration doesn't change, only the temperatures and the mix +bool RepRap::WriteToolSettings(FileStore *f) const +{ + // First write the settings of all tools except the current one and the command to select them if they are on standby + bool ok = true; + for (const Tool *t = toolList; t != nullptr && ok; t = t->Next()) + { + if (t != currentTool) + { + ok = t->WriteSettings(f); + } + } + + // Finally write the setting of the active tool and the commands to select it + if (ok && currentTool != nullptr) + { + ok = currentTool->WriteSettings(f); + } + return ok; +} + +#endif + // Helper function for diagnostic tests in Platform.cpp, to cause a deliberate divide-by-zero /*static*/ uint32_t RepRap::DoDivide(uint32_t a, uint32_t b) { diff --git a/src/RepRap.h b/src/RepRap.h index 64376104..8854d8be 100644 --- a/src/RepRap.h +++ b/src/RepRap.h @@ -59,10 +59,12 @@ public: void SelectTool(int toolNumber); void StandbyTool(int toolNumber); Tool* GetCurrentTool() const; + Tool* GetLastStandbyTool() const { return lastStandbyTool; } Tool* GetTool(int toolNumber) const; Tool* GetCurrentOrDefaultTool() const; - const Tool* GetFirstTool() const { return toolList; } // Return the lowest-numbered tool + const Tool* GetFirstTool() const { return toolList; } // Return the lowest-numbered tool uint32_t GetCurrentXAxes() const; // Get the current axes used as X axes + uint32_t GetCurrentYAxes() const; // Get the current axes used as Y axes void SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures); bool IsHeaterAssignedToTool(int8_t heater) const; unsigned int GetNumberOfContiguousTools() const; @@ -103,6 +105,10 @@ public: void SetAlert(const char *msg, const char *title, int mode, float timeout, bool showZControls); void ClearAlert(); +#ifdef DUET_NG + bool WriteToolSettings(FileStore *f) const; // Save some resume information +#endif + static void CopyParameterText(const char* src, char *dst, size_t length); static uint32_t DoDivide(uint32_t a, uint32_t b); // helper function for diagnostic tests static uint32_t ReadDword(const char* p); // helper function for diagnostic tests @@ -127,6 +133,7 @@ private: Tool* toolList; // the tool list is sorted in order of increasing tool number Tool* currentTool; + Tool* lastStandbyTool; uint32_t lastWarningMillis; // When we last sent a warning message for things that can happen very often uint16_t activeExtruders; diff --git a/src/Tools/Tool.cpp b/src/Tools/Tool.cpp index d65bd898..2fdb334a 100644 --- a/src/Tools/Tool.cpp +++ b/src/Tools/Tool.cpp @@ -33,7 +33,7 @@ Tool * Tool::freelist = nullptr; -/*static*/ Tool *Tool::Create(int toolNumber, const char *name, long d[], size_t dCount, long h[], size_t hCount, uint32_t xMap, uint32_t fanMap) +/*static*/ Tool *Tool::Create(int toolNumber, const char *name, long d[], size_t dCount, long h[], size_t hCount, uint32_t xMap, uint32_t yMap, uint32_t fanMap) { const size_t numExtruders = reprap.GetGCodes().GetNumExtruders(); if (dCount > ARRAY_SIZE(Tool::drives)) @@ -93,10 +93,11 @@ Tool * Tool::freelist = nullptr; t->myNumber = toolNumber; SafeStrncpy(t->name, name, ToolNameLength); t->next = nullptr; - t->active = false; + t->state = ToolState::off; t->driveCount = dCount; t->heaterCount = hCount; t->xMapping = xMap; + t->yMapping = yMap; t->fanMapping = fanMap; t->heaterFault = false; t->mixing = false; @@ -178,6 +179,17 @@ void Tool::Print(StringRef& reply) } } + reply.cat("; ymap:"); + sep = ' '; + for (size_t yi = 0; yi < MaxAxes; ++yi) + { + if ((yMapping & (1u << yi)) != 0) + { + reply.catf("%c%c", sep, GCodes::axisLetters[yi]); + sep = ','; + } + } + reply.cat("; fans:"); sep = ' '; for (size_t fi = 0; fi < NUM_FANS; ++fi) @@ -189,7 +201,7 @@ void Tool::Print(StringRef& reply) } } - reply.catf("; status: %s", active ? "selected" : "standby"); + reply.catf("; status: %s", (state == ToolState::active) ? "selected" : (state == ToolState::standby) ? "standby" : "off"); } float Tool::MaxFeedrate() const @@ -296,7 +308,7 @@ bool Tool::AllHeatersAtHighTemperature(bool forExtrusion) const void Tool::Activate(Tool* currentlyActive) { - if (!active) + if (state != ToolState::active) { if (currentlyActive != nullptr && currentlyActive != this) { @@ -308,20 +320,20 @@ void Tool::Activate(Tool* currentlyActive) reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); reprap.GetHeat().Activate(heaters[heater]); } - active = true; + state = ToolState::active; } } void Tool::Standby() { - if (active) + if (state != ToolState::standby) { for (size_t heater = 0; heater < heaterCount; heater++) { reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); reprap.GetHeat().Standby(heaters[heater]); } - active = false; + state = ToolState::standby; } } @@ -349,7 +361,7 @@ void Tool::SetVariables(const float* standby, const float* active) if (standby[heater] < temperatureLimit) { standbyTemperatures[heater] = standby[heater]; - if (currentTool == nullptr || currentTool == this) + if (currentTool == nullptr || currentTool == this || reprap.GetLastStandbyTool() == this) { reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); } @@ -415,4 +427,59 @@ void Tool::DefineMix(const float m[]) } } +#ifdef DUET_NG + +// Write the tool's settings to file returning true if successful +bool Tool::WriteSettings(FileStore *f) const +{ + char bufSpace[50]; + StringRef buf(bufSpace, ARRAY_SIZE(bufSpace)); + + // Set up active and standby heater temperatures + bool ok = true; + if (heaterCount != 0) + { + buf.copy("G10 "); + char c = 'S'; + for (size_t i = 0; i < heaterCount; ++i) + { + buf.catf("%c%d", c, (int)activeTemperatures[i]); + c = ':'; + } + buf.cat(' '); + c = 'R'; + for (size_t i = 0; i < heaterCount; ++i) + { + buf.catf("%c%d", c, (int)standbyTemperatures[i]); + c = ':'; + } + buf.cat('\n'); + ok = f->Write(buf.Pointer()); + } + + if (ok && mixing) + { + buf.printf("M567 P%d ", myNumber); + char c = 'E'; + for (size_t i = 0; i < driveCount; ++i) + { + buf.catf("%c%.1f", c, mix[i]); + c = ':'; + } + buf.catf("\nM568 P%d S1\n", myNumber); + ok = f->Write(buf.Pointer()); + } + + if (ok && state != ToolState::off) + { + // Select tool and set virtual extruder position + buf.printf("T%d P0\nG92 E%.3f\n", myNumber, virtualExtruderPosition); + ok = f->Write(buf.Pointer()); + } + + return ok; +} + +#endif + // End diff --git a/src/Tools/Tool.h b/src/Tools/Tool.h index 7aa36b65..2bc72392 100644 --- a/src/Tools/Tool.h +++ b/src/Tools/Tool.h @@ -28,15 +28,16 @@ Licence: GPL #include "RepRapFirmware.h" -const size_t ToolNameLength = 32; // maximum allowed length for tool names -const uint32_t DefaultXAxisMapping = 0x0001; // by default, X is mapped to X +const size_t ToolNameLength = 32; // maximum allowed length for tool names +const uint32_t DefaultXAxisMapping = 1u << X_AXIS; // by default, X is mapped to X +const uint32_t DefaultYAxisMapping = 1u << Y_AXIS; // by default, Y is mapped to Y class Filament; class Tool { public: - static Tool *Create(int toolNumber, const char *name, long d[], size_t dCount, long h[], size_t hCount, uint32_t xMap, uint32_t fanMap); + static Tool *Create(int toolNumber, const char *name, long d[], size_t dCount, long h[], size_t hCount, uint32_t xMap, uint32_t yMap, uint32_t fanMap); static void Delete(Tool *t); const float *GetOffset() const; @@ -58,10 +59,15 @@ public: float InstantDv() const; void Print(StringRef& reply); uint32_t GetXAxisMap() const { return xMapping; } + uint32_t GetYAxisMap() const { return yMapping; } uint32_t GetFanMapping() const { return fanMapping; } Filament *GetFilament() const { return filament; } Tool *Next() const { return next; } +#ifdef DUET_NG + bool WriteSettings(FileStore *f) const; // write the tool's settings to file +#endif + float virtualExtruderPosition; friend class RepRap; @@ -91,13 +97,20 @@ private: float standbyTemperatures[Heaters]; size_t heaterCount; float offset[MaxAxes]; - uint32_t xMapping; + uint32_t xMapping, yMapping; uint32_t fanMapping; Filament *filament; Tool* next; + enum class ToolState : uint8_t + { + off = 0, + active, + standby + }; + ToolState state; + bool mixing; - bool active; bool heaterFault; volatile bool displayColdExtrudeWarning; }; diff --git a/src/Version.h b/src/Version.h index cbb5afd1..aa80df63 100644 --- a/src/Version.h +++ b/src/Version.h @@ -9,11 +9,11 @@ #define SRC_VERSION_H_ #ifndef VERSION -# define VERSION "1.19beta8" +# define VERSION "1.19beta9" #endif #ifndef DATE -# define DATE "2017-06-30" +# define DATE "2017-07-10" #endif #define AUTHORS "reprappro, dc42, chrishamm, t3p3, dnewman" |