diff --git a/Repetier-Firmware 1.0.3/README.txt b/Repetier-Firmware 1.0.3/README.txt new file mode 100644 index 0000000..92b93cb --- /dev/null +++ b/Repetier-Firmware 1.0.3/README.txt @@ -0,0 +1,40 @@ +Compilation instructions + +1. Install Arduino IDE + You get the latest Arduino IDE here: + http://arduino.cc/en/Main/Software + + You need at least 1.0 for the Atmel AVR based boards and 1.5 for the ARM based boards like the due. + +2. Make sure the serial driver for your printer board is installed. The Arduino IDE contains signed drivers + for the Arduino boards. Depending on your board you might need different drivers then these. For Linux and Mac + you often do not need any additional drivers. Ask your printer/board vendor which driver you need, + if that is not clear to you. + +3. Some boards that are not original arduino boards and not 100% compatible to them, need separate extensions + to the Arduino IDE. Install them. + +4. Start Arduino IDE and open the file "Repetier.ino" + +5. Select the board and port for upload in the arduino ide. + +6. Check Configuration.h for hints, if something needs to be checked or modified. + +7. Upload the firmware with the upload button (right arrow in toolbar). + +If you have a normal mega 2560 compatible board, you can use codeblocks for arduino instead of the arduino ide: +http://arduinodev.com/codeblocks/ +Open the Repetier.cbp file instead of the ino file and start with 6. + +HINT: It you have enabled eeprom support, the first upload will copy the configurations into the eeprom. Later +uploads will NOT overwrite these settings! Connect with Repetier-Host to your printer and open the eeprom editor +to change these values. Alternatively, send the commands + M502 + M500 +to copy the new values in Configuration.h to eeprom. + + +IMPORTANT FOR DUE USERS + +There is an additional folder AdditionalArduinoFiles with separate readme that describes how to get +watchdog working. It is a very good idea to compile with working watchdog!!! diff --git a/Repetier-Firmware 1.0.3/Repetier/BedLeveling.cpp b/Repetier-Firmware 1.0.3/Repetier/BedLeveling.cpp new file mode 100644 index 0000000..e498c1c --- /dev/null +++ b/Repetier-Firmware 1.0.3/Repetier/BedLeveling.cpp @@ -0,0 +1,956 @@ + +/* +More and more printers now have automatic bed leveling using an ever increasing variety of methods. +This makes the leveling routine one of the most complex parts of the firmware and there is not one +way to level but hundreds of combinations. + +First you should decide on the correction method. Once we know how our bed is tilted we want to +remove that. This correction is defined by BED_CORRECTION_METHOD and allows the following values: +BED_CORRECTION_METHOD 0 +Use a rotation matrix. This will make z axis go up/down while moving in x/y direction to compensate +the tilt. For multiple extruders make sure the height match the tilt of the bed or one will scratch. + +BED_CORRECTION_METHOD 1 +Motorized correction. This method needs a bed that is fixed on 3 points from which 2 have a motor +to change the height. The positions are defined by +BED_MOTOR_1_X, BED_MOTOR_1_Y, BED_MOTOR_2_X, BED_MOTOR_2_Y, BED_MOTOR_3_X, BED_MOTOR_3_Y +Motor 2 and 3 are the one driven by motor driver 0 and 1. These can be extra motors like Felix Pro 1 +uses them or a system with 3 z axis where motors can be controlled individually like the Sparkcube +does. + +Next we have to distinguish several methods of z probing sensors. Each have their own advantages and +disadvantages. First the z probe has a position when activated and that position is defined by +#define Z_PROBE_X_OFFSET 0 +#define Z_PROBE_Y_OFFSET 0 +This is needed since we need to know where we measure the height when the z probe triggers. When +probing is activated you will see a move to make probe go over current extruder position. The +position can be changed in eeprom later on. + +Some probes need to be activated/deactivated so we can use them. This is defined in the scripts +#define Z_PROBE_START_SCRIPT "" +#define Z_PROBE_FINISHED_SCRIPT "" + +Now when we probe we want to know the distance of the extruder to the bed. This is defined by +#define Z_PROBE_HEIGHT 4 +The 4 means that when we trigger the distance of the nozzle tip is 4mm. If your switch tends +to return different points you might repeat a measured point and use the average height: +#define Z_PROBE_SWITCHING_DISTANCE 1 +#define Z_PROBE_REPETITIONS 5 +Switching distance is the z raise needed to turn back a signal reliably to off. Inductive sensors +need only a bit while mechanical switches may require a bit more. + +Next thing to consider is the force for switching. Some beds use a cantilever design and pushing on +the outside easily bends the bed. If your sensor needs some force to trigger you add the error of +bending. For this reason you might add a bending correction. Currently you define +#define BENDING_CORRECTION_A 0 +#define BENDING_CORRECTION_B 0 +#define BENDING_CORRECTION_C 0 +which are the deflections at the 3 z probe points. For all other possible measurements these values +get interpolated. You can modify the values later on in eeprom. For force less sensors set them to 0. + +Next thing is endstop handling. Without bed leveling you normally home to minimum position for x,y and z. +With bed leveling this is not that easy any more. Since we do bed leveling we already assume the bed is +not leveled for x/y moves. So without correction we would hit the bed for different x/y positions at +different heights. As a result we have no real minimum position. That makes a z min endstop quite useless. +There is an exception to this. If your nozzle triggers z min or if a inductive sensor would trigger at a given +position we could use that signal. With nozzle triggers you need to be careful as a drop of filament +would change the height. The other problem is that while homing the auto leveling is not used. So +the only position would be if the z min sensor is directly over the 0,0 coordinate which is the rotation point +if we have matrix based correction. For motor based correction this will work everywhere correctly. + +So the only useful position for a z endstop is z max position. Apart from not having the bed tilt problem it +also allows homing with a full bed so you can continue an aborted print with some gcode tweaking. With z max +homing we adjust the error by simply changing the max. z height. One thing you need to remember is setting +#define ENDSTOP_Z_BACK_ON_HOME 4 +so we release the z max endstop. This is very important if we move xy at z max. Auto leveling might want to +increase z and the endstop might prevent it causing wrong position and a head crash if we later go down. +The value should be larger then the maximum expected tilt. + +Now it is time to define how we measure the bed rotation. Here again we have several methods to choose. +All methods need at least 3 points to define the bed rotation correctly. The quality we get comes +from the selection of the right points and method. + +BED_LEVELING_METHOD 0 +This method measures at the 3 probe points and creates a plane through these points. If you have +a really planar bed this gives the optimum result. The 3 points must not be in one line and have +a long distance to increase numerical stability. + +BED_LEVELING_METHOD 1 +This measures a grid. Probe point 1 is the origin and points 2 and 3 span a grid. We measure +BED_LEVELING_GRID_SIZE points in each direction and compute a regression plane through all +points. This gives a good overall plane if you have small bumps measuring inaccuracies. + +BED_LEVELING_METHOD 2 +Bending correcting 4 point measurement. This is for cantilevered beds that have the rotation axis +not at the side but inside the bed. Here we can assume no bending on the axis and a symmetric +bending to both sides of the axis. So probe points 2 and 3 build the symmetric axis and +point 1 is mirrored to 1m across the axis. Using the symmetry we then remove the bending +from 1 and use that as plane. + +By now the leveling process is finished. All errors that remain are measuring errors and bumps on +the bed it self. For deltas you can enable distortion correction to follow the bumps. + +There are 2 ways to consider a changing bed coating, which are defined by Z_PROBE_Z_OFFSET_MODE. +Z_PROBE_Z_OFFSET_MODE = 0 means we measure the surface of the bed below any coating. This is e.g. +the case with inductive sensors where we put BuildTak on top. In that case we can set Z_PROBE_Z_OFFSET +to the thickness of BuildTak to compensate. If we later change the coating, we only change Z_PROBE_Z_OFFSET +to new coating thickness. + +Z_PROBE_Z_OFFSET_MODE = 1 means we measure the surface of the coating, e.g. because we have a mechanical switch. +In that case we add Z_PROBE_Z_OFFSET for the measured height to compensate for correct distance to bed surface. + +In homing to max we reduce z length by Z_PROBE_Z_OFFSET to get a correct height. +In homing to z min we assume z endstop is bed level so we move up Z_PROBE_Z_OFFSET after endstop is hit. This +requires the extruder to bend the coating thickness without harm! +*/ + +#include "Repetier.h" + +#ifndef BED_LEVELING_METHOD +#define BED_LEVELING_METHOD 0 +#endif + +#ifndef BED_CORRECTION_METHOD +#define BED_CORRECTION_METHOD 0 +#endif + +#ifndef BED_LEVELING_GRID_SIZE +#define BED_LEVELING_GRID_SIZE 5 +#endif + +#ifndef BED_LEVELING_REPETITIONS +#define BED_LEVELING_REPETITIONS 1 +#endif + +#if FEATURE_Z_PROBE +void Printer::prepareForProbing() { +#ifndef SKIP_PROBE_PREPARE + // 1. Ensure we are homed so positions make sense + if(!Printer::isHomedAll()) { + Printer::homeAxis(true, true, true); + } + // 2. Go to z probe bed distance for probing + Printer::moveToReal(IGNORE_COORDINATE, IGNORE_COORDINATE, RMath::max(EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0), static_cast(ZHOME_HEAT_HEIGHT)), IGNORE_COORDINATE, Printer::homingFeedrate[Z_AXIS]); + // 3. Ensure we can activate z probe at current xy position + // Delta is at center already so does not need special testing here! +#if EXTRUDER_IS_Z_PROBE == 0 + float ZPOffsetX = EEPROM::zProbeXOffset(); + float ZPOffsetY = EEPROM::zProbeYOffset(); +#if DRIVE_SYSTEM != DELTA + float targetX = Printer::currentPosition[X_AXIS]; + float targetY = Printer::currentPosition[Y_AXIS]; + if(ZPOffsetX > 0 && targetX - ZPOffsetX < Printer::xMin) { + targetX = Printer::xMin + ZPOffsetX; + } + if(ZPOffsetY > 0 && targetY - ZPOffsetY < Printer::yMin) { + targetY = Printer::yMin + ZPOffsetY; + } + if(ZPOffsetX < 0 && targetX - ZPOffsetX > Printer::xMin + Printer::xLength) { + targetX = Printer::xMin + Printer::xLength + ZPOffsetX; + } + if(ZPOffsetY < 0 && targetY - ZPOffsetY > Printer::yMin + Printer::yLength) { + targetY = Printer::yMin + Printer::yLength + ZPOffsetY; + } + Printer::moveToReal(targetX, targetY, IGNORE_COORDINATE, IGNORE_COORDINATE, EXTRUDER_SWITCH_XY_SPEED); + Printer::updateCurrentPosition(true); + Commands::waitUntilEndOfAllMoves(); +#endif +#endif +#endif +} +#endif + +#if FEATURE_AUTOLEVEL && FEATURE_Z_PROBE + +bool measureAutolevelPlane(Plane &plane) { + PlaneBuilder builder; + builder.reset(); +#if BED_LEVELING_METHOD == 0 // 3 point + float h; + Printer::moveTo(EEPROM::zProbeX1(), EEPROM::zProbeY1(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h = Printer::runZProbe(false, false); + if(h == ILLEGAL_Z_PROBE) + return false; + builder.addPoint(EEPROM::zProbeX1(), EEPROM::zProbeY1(), h); + Printer::moveTo(EEPROM::zProbeX2(), EEPROM::zProbeY2(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h = Printer::runZProbe(false, false); + if(h == ILLEGAL_Z_PROBE) + return false; + builder.addPoint(EEPROM::zProbeX2(), EEPROM::zProbeY2(), h); + Printer::moveTo(EEPROM::zProbeX3(), EEPROM::zProbeY3(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h = Printer::runZProbe(false, false); + if(h == ILLEGAL_Z_PROBE) + return false; + builder.addPoint(EEPROM::zProbeX3(), EEPROM::zProbeY3(), h); +#elif BED_LEVELING_METHOD == 1 // linear regression + float delta = 1.0 / (BED_LEVELING_GRID_SIZE - 1); + float ox = EEPROM::zProbeX1(); + float oy = EEPROM::zProbeY1(); + float ax = delta * (EEPROM::zProbeX2() - EEPROM::zProbeX1()); + float ay = delta * (EEPROM::zProbeY2() - EEPROM::zProbeY1()); + float bx = delta * (EEPROM::zProbeX3() - EEPROM::zProbeX1()); + float by = delta * (EEPROM::zProbeY3() - EEPROM::zProbeY1()); + for(int ix = 0; ix < BED_LEVELING_GRID_SIZE; ix++) { + for(int iy = 0; iy < BED_LEVELING_GRID_SIZE; iy++) { + float px = ox + static_cast(ix) * ax + static_cast(iy) * bx; + float py = oy + static_cast(ix) * ay + static_cast(iy) * by; + Printer::moveTo(px, py, IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + float h = Printer::runZProbe(false, false); + if(h == ILLEGAL_Z_PROBE) + return false; + builder.addPoint(px, py, h); + } + } + +#elif BED_LEVELING_METHOD == 2 // 4 point symmetric + float h1, h2, h3, h4; + float apx = EEPROM::zProbeX1() - EEPROM::zProbeX2(); + float apy = EEPROM::zProbeY1() - EEPROM::zProbeY2(); + float abx = EEPROM::zProbeX3() - EEPROM::zProbeX2(); + float aby = EEPROM::zProbeY3() - EEPROM::zProbeY2(); + float ab2 = abx * abx + aby * aby; + float abap = apx * abx + apy * aby; + float t = abap / ab2; + float xx = EEPROM::zProbeX2() + t * abx; + float xy = EEPROM::zProbeY2() + t * aby; + float x1Mirror = EEPROM::zProbeX1() + 2.0 * (xx - EEPROM::zProbeX1()); + float y1Mirror = EEPROM::zProbeY1() + 2.0 * (xy - EEPROM::zProbeY1()); + Printer::moveTo(EEPROM::zProbeX1(), EEPROM::zProbeY1(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h1 = Printer::runZProbe(false, false); + if(h1 == ILLEGAL_Z_PROBE) + return false; + Printer::moveTo(EEPROM::zProbeX2(), EEPROM::zProbeY2(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h2 = Printer::runZProbe(false, false); + if(h2 == ILLEGAL_Z_PROBE) + return false; + Printer::moveTo(EEPROM::zProbeX3(), EEPROM::zProbeY3(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h3 = Printer::runZProbe(false, false); + if(h3 == ILLEGAL_Z_PROBE) + return false; + Printer::moveTo(x1Mirror, y1Mirror, IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + h4 = Printer::runZProbe(false, false); + if(h4 == ILLEGAL_Z_PROBE) + return false; + t = h2 + (h3 - h2) * t; // theoretical height for crossing point for symmetric axis + h1 = t - (h4 - h1) * 0.5; // remove bending part + builder.addPoint(EEPROM::zProbeX1(), EEPROM::zProbeY1(), h1); + builder.addPoint(EEPROM::zProbeX2(), EEPROM::zProbeY2(), h2); + builder.addPoint(EEPROM::zProbeX3(), EEPROM::zProbeY3(), h3); +#else +#error Unknown bed leveling method +#endif + builder.createPlane(plane, false); + return true; +} + +void correctAutolevel(Plane &plane) { +#if BED_CORRECTION_METHOD == 0 // rotation matrix + //Printer::buildTransformationMatrix(plane.z(EEPROM::zProbeX1(),EEPROM::zProbeY1()),plane.z(EEPROM::zProbeX2(),EEPROM::zProbeY2()),plane.z(EEPROM::zProbeX3(),EEPROM::zProbeY3())); + Printer::buildTransformationMatrix(plane); +#elif BED_CORRECTION_METHOD == 1 // motorized correction +#if !defined(NUM_MOTOR_DRIVERS) || NUM_MOTOR_DRIVERS < 2 +#error You need to define 2 motors for motorized bed correction +#endif + Commands::waitUntilEndOfAllMoves(); // move steppers might be leveling steppers as well ! + float h1 = plane.z(BED_MOTOR_1_X, BED_MOTOR_1_Y); + float h2 = plane.z(BED_MOTOR_2_X, BED_MOTOR_2_Y); + float h3 = plane.z(BED_MOTOR_3_X, BED_MOTOR_3_Y); + // h1 is reference heights, h2 => motor 0, h3 => motor 1 + h2 -= h1; + h3 -= h1; +#if defined(LIMIT_MOTORIZED_CORRECTION) + if(h2 < -LIMIT_MOTORIZED_CORRECTION) h2 = -LIMIT_MOTORIZED_CORRECTION; + if(h2 > LIMIT_MOTORIZED_CORRECTION) h2 = LIMIT_MOTORIZED_CORRECTION; + if(h3 < -LIMIT_MOTORIZED_CORRECTION) h3 = -LIMIT_MOTORIZED_CORRECTION; + if(h3 > LIMIT_MOTORIZED_CORRECTION) h3 = LIMIT_MOTORIZED_CORRECTION; +#endif + MotorDriverInterface *motor2 = getMotorDriver(0); + MotorDriverInterface *motor3 = getMotorDriver(1); + motor2->setCurrentAs(0); + motor3->setCurrentAs(0); + motor2->gotoPosition(h2); + motor3->gotoPosition(h3); + motor2->disable(); + motor3->disable(); // now bed is even + Printer::currentPositionSteps[Z_AXIS] = h1 * Printer::axisStepsPerMM[Z_AXIS]; +#if NONLINEAR_SYSTEM + transformCartesianStepsToDeltaSteps(Printer::currentPositionSteps, Printer::currentNonlinearPositionSteps); +#endif +#else +#error Unknown bed correction method set +#endif +} + + +/** +Implementation of the G32 command +G32 S<0..2> - Autolevel print bed. S = 1 measure zLength, S = 2 Measure and store new zLength +S = 0 : Do not update length - use this if you have not homed before or you mess up zLength! +S = 1 : Measure zLength so homing works +S = 2 : Like s = 1 plus store results in EEPROM for next connection. +*/ +bool runBedLeveling(int s) { + bool success = true; + Printer::prepareForProbing(); +#if defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE && Z_PROBE_REQUIRES_HEATING + float actTemp[NUM_EXTRUDER]; + for(int i = 0; i < NUM_EXTRUDER; i++) + actTemp[i] = extruder[i].tempControl.targetTemperatureC; + Printer::moveToReal(IGNORE_COORDINATE, IGNORE_COORDINATE, RMath::max(EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0), static_cast(ZHOME_HEAT_HEIGHT)), IGNORE_COORDINATE, Printer::homingFeedrate[Z_AXIS]); + Commands::waitUntilEndOfAllMoves(); +#if ZHOME_HEAT_ALL + for(int i = 0; i < NUM_EXTRUDER; i++) { + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, false); + } + for(int i = 0; i < NUM_EXTRUDER; i++) { + if(extruder[i].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, true); + } +#else + if(extruder[Extruder::current->id].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[Extruder::current->id], static_cast(ZPROBE_MIN_TEMPERATURE)), Extruder::current->id, false, true); +#endif +#endif // defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE && Z_PROBE_REQUIRES_HEATING + + + float h1, h2, h3, hc, oldFeedrate = Printer::feedrate; +#if DISTORTION_CORRECTION + bool distEnabled = Printer::distortion.isEnabled(); + Printer::distortion.disable(false); // if level has changed, distortion is also invalid +#endif + Printer::setAutolevelActive(false); // iterate + Printer::resetTransformationMatrix(true); // in case we switch from matrix to motorized! +#if DRIVE_SYSTEM == DELTA + // It is not possible to go to the edges at the top, also users try + // it often and wonder why the coordinate system is then wrong. + // For that reason we ensure a correct behavior by code. + Printer::homeAxis(true, true, true); + Printer::moveTo(IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0), IGNORE_COORDINATE, Printer::homingFeedrate[Z_AXIS]); +#else + if(!Printer::isXHomed() || !Printer::isYHomed()) + Printer::homeAxis(true, true, false); + Printer::updateCurrentPosition(true); + Printer::moveTo(EEPROM::zProbeX1(), EEPROM::zProbeY1(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); +#endif + Printer::coordinateOffset[X_AXIS] = Printer::coordinateOffset[Y_AXIS] = Printer::coordinateOffset[Z_AXIS] = 0; + Printer::startProbing(true); + //GCode::executeFString(Com::tZProbeStartScript); + Plane plane; +#if BED_CORRECTION_METHOD == 1 + success = false; + for(int r = 0; r < BED_LEVELING_REPETITIONS; r++) { +#if DRIVE_SYSTEM == DELTA + if(r > 0) { + Printer::finishProbing(); + Printer::homeAxis(true, true, true); + Printer::moveTo(IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0), IGNORE_COORDINATE, Printer::homingFeedrate[Z_AXIS]); + Printer::startProbing(true); + } +#endif // DELTA +#endif // BED_CORRECTION_METHOD == 1 + if(!measureAutolevelPlane(plane)) { + Com::printErrorFLN(PSTR("Probing had returned errors - autoleveling canceled.")); + UI_MESSAGE(1); + return false; + } + correctAutolevel(plane); + + // Leveling is finished now update own positions and store leveling data if needed + //float currentZ = plane.z((float)Printer::currentPositionSteps[X_AXIS] * Printer::invAxisStepsPerMM[X_AXIS],(float)Printer::currentPositionSteps[Y_AXIS] * Printer::invAxisStepsPerMM[Y_AXIS]); + float currentZ = plane.z(0.0, 0.0); // we rotated around this point, so that is now z height + // With max z end stop we adjust z length so after next homing we have also a calibrated printer + Printer::zMin = 0; +#if MAX_HARDWARE_ENDSTOP_Z + //float xRot,yRot,zRot; + //Printer::transformFromPrinter(Printer::currentPosition[X_AXIS],Printer::currentPosition[Y_AXIS],Printer::currentPosition[Z_AXIS],xRot,yRot,zRot); + //Com::printFLN(PSTR("Z after rotation:"),zRot); + // With max z end stop we adjust z length so after next homing we have also a calibrated printer + if(s != 0) { + // at origin rotations have no influence so use values there to update + Printer::zLength += currentZ - Printer::currentPosition[Z_AXIS]; + //Printer::zLength += /*currentZ*/ plane.z((float)Printer::currentPositionSteps[X_AXIS] * Printer::invAxisStepsPerMM[X_AXIS],(float)Printer::currentPositionSteps[Y_AXIS] * Printer::invAxisStepsPerMM[Y_AXIS]) - zRot; + Com::printFLN(Com::tZProbePrinterHeight, Printer::zLength); + } +#endif +#if Z_PROBE_Z_OFFSET_MODE == 1 + currentZ -= EEPROM::zProbeZOffset(); +#endif + Com::printF(PSTR("CurrentZ:"), currentZ); + Com::printFLN(PSTR(" atZ:"), Printer::currentPosition[Z_AXIS]); + Printer::currentPositionSteps[Z_AXIS] = currentZ * Printer::axisStepsPerMM[Z_AXIS]; + Printer::updateCurrentPosition(true); // set position based on steps position +#if BED_CORRECTION_METHOD == 1 + if(fabsf(plane.a) < 0.00025 && fabsf(plane.b) < 0.00025 ) { + success = true; + break; // we reached achievable precision so we can stop + } + } // for BED_LEVELING_REPETITIONS +#if Z_HOME_DIR > 0 && MAX_HARDWARE_ENDSTOP_Z + float zall = Printer::runZProbe(false, false, 1, false); + if(zall == ILLEGAL_Z_PROBE) + return false; + Printer::currentPosition[Z_AXIS] = zall; + Printer::currentPositionSteps[Z_AXIS] = zall * Printer::axisStepsPerMM[Z_AXIS]; +#if NONLINEAR_SYSTEM + transformCartesianStepsToDeltaSteps(Printer::currentPositionSteps, Printer::currentNonlinearPositionSteps); +#endif + if(s >= 1) { + float zMax = Printer::runZMaxProbe(); + if(zMax == ILLEGAL_Z_PROBE) + return false; + zall += zMax - ENDSTOP_Z_BACK_ON_HOME; + Printer::zLength = zall; + } +#endif +#endif // BED_CORRECTION_METHOD == 1 + Printer::updateDerivedParameter(); + Printer::finishProbing(); +#if BED_CORRECTION_METHOD != 1 + Printer::setAutolevelActive(true); // only for software correction or we can spare the comp. time +#endif + if(s >= 2) { + EEPROM::storeDataIntoEEPROM(); + } + Printer::updateCurrentPosition(true); + Commands::printCurrentPosition(); +#if DISTORTION_CORRECTION + if(distEnabled) + Printer::distortion.enable(false); // if level has changed, distortion is also invalid +#endif +#if DRIVE_SYSTEM == DELTA + Printer::homeAxis(true, true, true); // shifting z makes positioning invalid, need to recalibrate +#endif + Printer::feedrate = oldFeedrate; + +#if defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE && Z_PROBE_REQUIRES_HEATING +#if ZHOME_HEAT_ALL + for(int i = 0; i < NUM_EXTRUDER; i++) { + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, false); + } + for(int i = 0; i < NUM_EXTRUDER; i++) { + if(extruder[i].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, true); + } +#else + if(extruder[Extruder::current->id].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[Extruder::current->id], static_cast(ZPROBE_MIN_TEMPERATURE)), Extruder::current->id, false, true); +#endif +#endif + + return success; +} + +#endif + +/** \brief Activate or deactivate rotation correction. + +\param on True if Rotation correction should be enabled. +*/ +void Printer::setAutolevelActive(bool on) { +#if FEATURE_AUTOLEVEL + if(on == isAutolevelActive()) return; + flag0 = (on ? flag0 | PRINTER_FLAG0_AUTOLEVEL_ACTIVE : flag0 & ~PRINTER_FLAG0_AUTOLEVEL_ACTIVE); + if(on) + Com::printInfoFLN(Com::tAutolevelEnabled); + else + Com::printInfoFLN(Com::tAutolevelDisabled); + updateCurrentPosition(false); +#endif // FEATURE_AUTOLEVEL +} +#if MAX_HARDWARE_ENDSTOP_Z +/** \brief Measure distance from current position until triggering z max endstop. + +\return Distance until triggering in mm. */ +float Printer::runZMaxProbe() { +#if NONLINEAR_SYSTEM + long startZ = realDeltaPositionSteps[Z_AXIS] = currentNonlinearPositionSteps[Z_AXIS]; // update real +#endif + Commands::waitUntilEndOfAllMoves(); + long probeDepth = 2 * (Printer::zMaxSteps - Printer::zMinSteps); + stepsRemainingAtZHit = -1; + setZProbingActive(true); + PrintLine::moveRelativeDistanceInSteps(0, 0, probeDepth, 0, homingFeedrate[Z_AXIS] / ENDSTOP_Z_RETEST_REDUCTION_FACTOR, true, true); + if(stepsRemainingAtZHit < 0) { + Com::printErrorFLN(PSTR("z-max homing failed")); + return ILLEGAL_Z_PROBE; + } + setZProbingActive(false); + currentPositionSteps[Z_AXIS] -= stepsRemainingAtZHit; +#if NONLINEAR_SYSTEM + transformCartesianStepsToDeltaSteps(Printer::currentPositionSteps, Printer::currentNonlinearPositionSteps); + probeDepth = (realDeltaPositionSteps[Z_AXIS] - startZ); +#else + probeDepth -= stepsRemainingAtZHit; +#endif + float distance = (float)probeDepth * invAxisStepsPerMM[Z_AXIS]; + Com::printF(Com::tZProbeMax, distance); + Com::printF(Com::tSpaceXColon, realXPosition()); + Com::printFLN(Com::tSpaceYColon, realYPosition()); + PrintLine::moveRelativeDistanceInSteps(0, 0, -probeDepth, 0, homingFeedrate[Z_AXIS], true, true); + return distance; +} +#endif + +#if FEATURE_Z_PROBE +/** \brief Activate z-probe + +Tests if switching from active tool to z-probe is possible at current position. If not the operation is aborted. +If ok, it runs start script, checks z position and applies the z-probe offset. + +\param runScript Run start z-probe script from configuration. +\param enforceStartHeight If true moves z to EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0) + 0.1 if current position is higher. +\return True if activation was successful. */ +bool Printer::startProbing(bool runScript, bool enforceStartHeight) { + float cx, cy, cz; + realPosition(cx, cy, cz); + // Fix position to be inside print area when probe is enabled +#if EXTRUDER_IS_Z_PROBE == 0 + float ZPOffsetX = EEPROM::zProbeXOffset(); + float ZPOffsetY = EEPROM::zProbeYOffset(); +#if DRIVE_SYSTEM == DELTA + float rad = EEPROM::deltaMaxRadius(); + float dx = Printer::currentPosition[X_AXIS] - ZPOffsetX; + float dy = Printer::currentPosition[Y_AXIS] - ZPOffsetY; + if(sqrt(dx * dx + dy * dy) > rad) +#else + if((ZPOffsetX > 0 && Printer::currentPosition[X_AXIS] - ZPOffsetX < Printer::xMin) || + (ZPOffsetY > 0 && Printer::currentPosition[Y_AXIS] - ZPOffsetY < Printer::yMin) || + (ZPOffsetX < 0 && Printer::currentPosition[X_AXIS] - ZPOffsetX > Printer::xMin + Printer::xLength) || + (ZPOffsetY < 0 && Printer::currentPosition[Y_AXIS] - ZPOffsetY > Printer::yMin + Printer::yLength)) +#endif + { + Com::printErrorF(PSTR("Activating z-probe would lead to forbidden xy position: ")); + Com::print(Printer::currentPosition[X_AXIS] - ZPOffsetX); + Com::printFLN(PSTR(", "), Printer::currentPosition[Y_AXIS] - ZPOffsetY); + GCode::fatalError(PSTR("Could not activate z-probe offset due to coordinate constraints - result is inaccurate!")); + return false; + } else { + if(runScript) { + GCode::executeFString(Com::tZProbeStartScript); + } + float maxStartHeight = EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0) + 0.1; + if(currentPosition[Z_AXIS] > maxStartHeight && enforceStartHeight) { + cz = maxStartHeight; + moveTo(IGNORE_COORDINATE, IGNORE_COORDINATE, maxStartHeight, IGNORE_COORDINATE, homingFeedrate[Z_AXIS]); + } + + // Update position + Printer::offsetX = -ZPOffsetX; + Printer::offsetY = -ZPOffsetY; + Printer::offsetZ = 0; +#if FEATURE_AUTOLEVEL + // we must not change z for the probe offset even if we are rotated, so add a correction for z + float dx, dy; + transformToPrinter(EEPROM::zProbeXOffset(), EEPROM::zProbeYOffset(), 0, dx, dy, offsetZ2); + //Com::printFLN(PSTR("ZPOffset2:"),offsetZ2,3); +#endif + } +#else + if(runScript) { + GCode::executeFString(Com::tZProbeStartScript); + } +#endif + Printer::moveToReal(cx, cy, cz, IGNORE_COORDINATE, EXTRUDER_SWITCH_XY_SPEED); + updateCurrentPosition(false); + return true; +} + +/** \brief Deactivate z-probe. */ +void Printer::finishProbing() { + float cx, cy, cz; + realPosition(cx, cy, cz); + GCode::executeFString(Com::tZProbeEndScript); + if(Extruder::current) { +#if DUAL_X_AXIS + offsetX = 0; // offsets are parking positions for dual x axis! +#else + offsetX = -Extruder::current->xOffset * invAxisStepsPerMM[X_AXIS]; +#endif + offsetY = -Extruder::current->yOffset * invAxisStepsPerMM[Y_AXIS]; + offsetZ = -Extruder::current->zOffset * invAxisStepsPerMM[Z_AXIS]; + } else { + offsetX = offsetY = offsetZ = 0; + } + offsetZ2 = 0; + Printer::moveToReal(cx, cy, cz, IGNORE_COORDINATE, EXTRUDER_SWITCH_XY_SPEED); +} + +/** \brief Measure distance to bottom at current position. + +This is the most important function for bed leveling. It does +1. Run probe start script if first = true and runStartScript = true +2. Position zProbe at current position if first = true. If we are more then maxStartHeight away from bed we also go down to that distance. +3. Measure the the steps until probe hits the bed. +4. Undo positioning to z probe and run finish script if last = true. + +Now we compute the nozzle height as follows: +a) Compute average height from repeated measurements +b) Add zProbeHeight to correct difference between triggering point and nozzle height above bed +c) If Z_PROBE_Z_OFFSET_MODE == 1 we add zProbeZOffset() that is coating thickness if we measure below coating with indictive sensor. +d) Add distortion correction. +e) Add bending correction + +Then we return the measured and corrected z distance. + +\param first If true, Printer::startProbing is called. +\param last If true, Printer::finishProbing is called at the end. +\param repeat Number of repetitions to average measurement errors. +\param runStartScript If true tells startProbing to run start script. +\param enforceStartHeight Tells start script to enforce a maximum distance to bed. +\return ILLEGAL_Z_PROBE on errors or measured distance. +*/ +float Printer::runZProbe(bool first, bool last, uint8_t repeat, bool runStartScript, bool enforceStartHeight) { + float oldOffX = Printer::offsetX; + float oldOffY = Printer::offsetY; + float oldOffZ = Printer::offsetZ; + if(first) { + if(!startProbing(runStartScript, enforceStartHeight)) + return ILLEGAL_Z_PROBE; + } + Commands::waitUntilEndOfAllMoves(); +#if defined(Z_PROBE_USE_MEDIAN) && Z_PROBE_USE_MEDIAN + int32_t measurements[Z_PROBE_REPETITIONS]; + repeat = RMath::min(repeat, Z_PROBE_REPETITIONS); +#else + int32_t sum = 0; +#endif + int32_t probeDepth; + int32_t shortMove = static_cast((float)Z_PROBE_SWITCHING_DISTANCE * axisStepsPerMM[Z_AXIS]); // distance to go up for repeated moves + int32_t lastCorrection = currentPositionSteps[Z_AXIS]; // starting position +#if NONLINEAR_SYSTEM + realDeltaPositionSteps[Z_AXIS] = currentNonlinearPositionSteps[Z_AXIS]; // update real +#endif + //int32_t updateZ = 0; + waitForZProbeStart(); +#if defined(Z_PROBE_DELAY) && Z_PROBE_DELAY > 0 + HAL::delayMilliseconds(Z_PROBE_DELAY); +#endif + Endstops::update(); + Endstops::update(); // need to call twice for full update! + if(Endstops::zProbe()) { + Com::printErrorFLN(PSTR("z-probe triggered before starting probing.")); + return ILLEGAL_Z_PROBE; + } +#if Z_PROBE_DISABLE_HEATERS + Extruder::pauseExtruders(true); + HAL::delayMilliseconds(70); +#endif + for(int8_t r = 0; r < repeat; r++) { + probeDepth = 2 * (Printer::zMaxSteps - Printer::zMinSteps); // probe should always hit within this distance + stepsRemainingAtZHit = -1; // Marker that we did not hit z probe + setZProbingActive(true); +#if defined(Z_PROBE_DELAY) && Z_PROBE_DELAY > 0 + HAL::delayMilliseconds(Z_PROBE_DELAY); +#endif + PrintLine::moveRelativeDistanceInSteps(0, 0, -probeDepth, 0, EEPROM::zProbeSpeed(), true, true); + setZProbingActive(false); + if(stepsRemainingAtZHit < 0) { + Com::printErrorFLN(Com::tZProbeFailed); + return ILLEGAL_Z_PROBE; + } +#if NONLINEAR_SYSTEM + stepsRemainingAtZHit = realDeltaPositionSteps[C_TOWER] - currentNonlinearPositionSteps[C_TOWER]; // nonlinear moves may split z so stepsRemainingAtZHit is only what is left from last segment not total move. This corrects the problem. +#endif +#if DRIVE_SYSTEM == DELTA + currentNonlinearPositionSteps[A_TOWER] += stepsRemainingAtZHit; // Update difference + currentNonlinearPositionSteps[B_TOWER] += stepsRemainingAtZHit; + currentNonlinearPositionSteps[C_TOWER] += stepsRemainingAtZHit; +#elif NONLINEAR_SYSTEM + currentNonlinearPositionSteps[Z_AXIS] += stepsRemainingAtZHit; +#endif + currentPositionSteps[Z_AXIS] += stepsRemainingAtZHit; // now current position is correct +#if defined(Z_PROBE_USE_MEDIAN) && Z_PROBE_USE_MEDIAN + measurements[r] = lastCorrection - currentPositionSteps[Z_AXIS]; +#else + sum += lastCorrection - currentPositionSteps[Z_AXIS]; +#endif + //Com::printFLN(PSTR("ZHSteps:"),lastCorrection - currentPositionSteps[Z_AXIS]); + if(r + 1 < repeat) { + // go only shortest possible move up for repetitions + PrintLine::moveRelativeDistanceInSteps(0, 0, shortMove, 0, HOMING_FEEDRATE_Z, true, true); + if(Endstops::zProbe()) { + Com::printErrorFLN(PSTR("z-probe did not untrigger on repetitive measurement - maybe you need to increase distance!")); + UI_MESSAGE(1); + return ILLEGAL_Z_PROBE; + } + } +#ifdef Z_PROBE_RUN_AFTER_EVERY_PROBE + GCode::executeFString(PSTR(Z_PROBE_RUN_AFTER_EVERY_PROBE)); +#endif + } +#if Z_PROBE_DISABLE_HEATERS + Extruder::unpauseExtruders(false); +#endif + + // Go back to start position + PrintLine::moveRelativeDistanceInSteps(0, 0, lastCorrection - currentPositionSteps[Z_AXIS], 0, HOMING_FEEDRATE_Z, true, true); + if(Endstops::zProbe()) { // did we untrigger? If not don't trust result! + Com::printErrorFLN(PSTR("z-probe did not untrigger on repetitive measurement - maybe you need to increase distance!")); + UI_MESSAGE(1); + return ILLEGAL_Z_PROBE; + } + updateCurrentPosition(false); + //Com::printFLN(PSTR("after probe")); + //Commands::printCurrentPosition(); +#if defined(Z_PROBE_USE_MEDIAN) && Z_PROBE_USE_MEDIAN + // bubble sort the measurements + int32_t tmp; + for(fast8_t i = 0 ; i < repeat - 1; i++) { // n numbers require at most n-1 rounds of swapping + for(fast8_t j = 0; j < repeat - i - 1; j++) { // + if( measurements[j] > measurements[j + 1] ) { // out of order? + tmp = measurements[j]; // swap them: + measurements[j] = measurements[j + 1]; + measurements[j + 1] = tmp; + } + } + } +// process result + float distance = static_cast(measurements[repeat >> 1]) * invAxisStepsPerMM[Z_AXIS] + EEPROM::zProbeHeight(); +#else + float distance = static_cast(sum) * invAxisStepsPerMM[Z_AXIS] / static_cast(repeat) + EEPROM::zProbeHeight(); +#endif +#if FEATURE_AUTOLEVEL + // we must change z for the z change from moving in rotated coordinates away from real position + float dx, dy, dz; + transformToPrinter(0, 0, currentPosition[Z_AXIS], dx, dy, dz); // what is our x,y offset from z position + dz -= currentPosition[Z_AXIS]; + //Com::printF(PSTR("ZXO:"),dx,3);Com::printF(PSTR(" ZYO:"),dy,3); + //transformToPrinter(dx,dy,0,dx,dy,dz); // how much changes z from x,y offset? + //Com::printFLN(PSTR(" Z from xy off:"), dz,7); + distance += dz; +#endif + //Com::printFLN(PSTR("OrigDistance:"),distance); +#if Z_PROBE_Z_OFFSET_MODE == 1 + distance += EEPROM::zProbeZOffset(); // We measured including coating, so we need to add coating thickness! +#endif + +#if DISTORTION_CORRECTION + float zCorr = 0; + if(Printer::distortion.isEnabled()) { + zCorr = distortion.correct(currentPositionSteps[X_AXIS]/* + EEPROM::zProbeXOffset() * axisStepsPerMM[X_AXIS]*/, currentPositionSteps[Y_AXIS] + /* + EEPROM::zProbeYOffset() * axisStepsPerMM[Y_AXIS]*/, zMinSteps) * invAxisStepsPerMM[Z_AXIS]; + distance += zCorr; + } +#endif + + distance += bendingCorrectionAt(currentPosition[X_AXIS], currentPosition[Y_AXIS]); + + Com::printF(Com::tZProbe, distance, 3); + Com::printF(Com::tSpaceXColon, realXPosition()); +#if DISTORTION_CORRECTION + if(Printer::distortion.isEnabled()) { + Com::printF(Com::tSpaceYColon, realYPosition()); + Com::printFLN(PSTR(" zCorr:"), zCorr, 3); + } else { + Com::printFLN(Com::tSpaceYColon, realYPosition()); + } +#else + Com::printFLN(Com::tSpaceYColon, realYPosition()); +#endif + if(Endstops::zProbe()) { + Com::printErrorFLN(PSTR("z-probe did not untrigger after going back to start position.")); + UI_MESSAGE(1); + return ILLEGAL_Z_PROBE; + } + if(last) + finishProbing(); + return distance; +} + +/** + * Having printer's height set properly (i.e. after calibration of Z=0), one can use this procedure to measure Z-probe height. + * It deploys the sensor, takes several probes at center, then updates Z-probe height with average. + */ +void Printer::measureZProbeHeight(float curHeight) { +#if FEATURE_Z_PROBE + currentPositionSteps[Z_AXIS] = curHeight * axisStepsPerMM[Z_AXIS]; + updateCurrentPosition(true); +#if NONLINEAR_SYSTEM + transformCartesianStepsToDeltaSteps(currentPositionSteps, currentNonlinearPositionSteps); +#endif + float startHeight = EEPROM::zProbeBedDistance() + (EEPROM::zProbeHeight() > 0 ? EEPROM::zProbeHeight() : 0); + moveTo(IGNORE_COORDINATE, IGNORE_COORDINATE, startHeight, IGNORE_COORDINATE, homingFeedrate[Z_AXIS]); + float zheight = Printer::runZProbe(true, true, Z_PROBE_REPETITIONS, true); + if(zheight == ILLEGAL_Z_PROBE) { + return; + } + float zProbeHeight = EEPROM::zProbeHeight() + startHeight -zheight; + +#if EEPROM_MODE != 0 // Com::tZProbeHeight is not declared when EEPROM_MODE is 0 + EEPROM::setZProbeHeight(zProbeHeight); // will also report on output +#else + Com::printFLN(PSTR("Z-probe height [mm]:"), zProbeHeight); +#endif +#endif +} + +float Printer::bendingCorrectionAt(float x, float y) { + PlaneBuilder builder; + builder.addPoint(EEPROM::zProbeX1(), EEPROM::zProbeY1(), EEPROM::bendingCorrectionA()); + builder.addPoint(EEPROM::zProbeX2(), EEPROM::zProbeY2(), EEPROM::bendingCorrectionB()); + builder.addPoint(EEPROM::zProbeX3(), EEPROM::zProbeY3(), EEPROM::bendingCorrectionC()); + Plane plane; + builder.createPlane(plane, true); + return plane.z(x, y); +} + +void Printer::waitForZProbeStart() { +#if Z_PROBE_WAIT_BEFORE_TEST + Endstops::update(); + Endstops::update(); // double test to get right signal. Needed for crosstalk protection. + if(Endstops::zProbe()) return; +#if UI_DISPLAY_TYPE != NO_DISPLAY + uid.setStatusP(Com::tHitZProbe); + uid.refreshPage(); +#endif +#ifdef DEBUG_PRINT + debugWaitLoop = 3; +#endif + while(!Endstops::zProbe()) { + defaultLoopActions(); + Endstops::update(); + Endstops::update(); // double test to get right signal. Needed for crosstalk protection. + } +#ifdef DEBUG_PRINT + debugWaitLoop = 4; +#endif + HAL::delayMilliseconds(30); + while(Endstops::zProbe()) { + defaultLoopActions(); + Endstops::update(); + Endstops::update(); // double test to get right signal. Needed for crosstalk protection. + } + HAL::delayMilliseconds(30); + UI_CLEAR_STATUS; +#endif +} +#endif + +/* + Transforms theoretical correct coordinates to corrected coordinates resulting from bed rotation + and shear transformations. + + We have 2 coordinate systems. The printer step position where we want to be. These are the positions + we send to printers, the theoretical coordinates. In contrast we have the printer coordinates that + we need to be at to get the desired result, the real coordinates. +*/ +void Printer::transformToPrinter(float x, float y, float z, float &transX, float &transY, float &transZ) { +#if FEATURE_AXISCOMP + // Axis compensation: + x = x + y * EEPROM::axisCompTanXY() + z * EEPROM::axisCompTanXZ(); + y = y + z * EEPROM::axisCompTanYZ(); +#endif +#if BED_CORRECTION_METHOD != 1 && FEATURE_AUTOLEVEL + if(isAutolevelActive()) { + transX = x * autolevelTransformation[0] + y * autolevelTransformation[3] + z * autolevelTransformation[6]; + transY = x * autolevelTransformation[1] + y * autolevelTransformation[4] + z * autolevelTransformation[7]; + transZ = x * autolevelTransformation[2] + y * autolevelTransformation[5] + z * autolevelTransformation[8]; + } else { + transX = x; + transY = y; + transZ = z; + } +#else + transX = x; + transY = y; + transZ = z; +#endif +} + +/* Transform back to real printer coordinates. */ +void Printer::transformFromPrinter(float x, float y, float z, float &transX, float &transY, float &transZ) { +#if BED_CORRECTION_METHOD != 1 && FEATURE_AUTOLEVEL + if(isAutolevelActive()) { + transX = x * autolevelTransformation[0] + y * autolevelTransformation[1] + z * autolevelTransformation[2]; + transY = x * autolevelTransformation[3] + y * autolevelTransformation[4] + z * autolevelTransformation[5]; + transZ = x * autolevelTransformation[6] + y * autolevelTransformation[7] + z * autolevelTransformation[8]; + } else { + transX = x; + transY = y; + transZ = z; + } +#else + transX = x; + transY = y; + transZ = z; +#endif +#if FEATURE_AXISCOMP + // Axis compensation: + transY = transY - transZ * EEPROM::axisCompTanYZ(); + transX = transX - transY * EEPROM::axisCompTanXY() - transZ * EEPROM::axisCompTanXZ(); +#endif +} +#if FEATURE_AUTOLEVEL +void Printer::resetTransformationMatrix(bool silent) { + autolevelTransformation[0] = autolevelTransformation[4] = autolevelTransformation[8] = 1; + autolevelTransformation[1] = autolevelTransformation[2] = autolevelTransformation[3] = + autolevelTransformation[5] = autolevelTransformation[6] = autolevelTransformation[7] = 0; + if(!silent) + Com::printInfoFLN(Com::tAutolevelReset); +} + +void Printer::buildTransformationMatrix(Plane &plane) { + float z0 = plane.z(0, 0); + float az = z0 - plane.z(1, 0); // ax = 1, ay = 0 + float bz = z0 - plane.z(0, 1); // bx = 0, by = 1 + // First z direction + autolevelTransformation[6] = -az; + autolevelTransformation[7] = -bz; + autolevelTransformation[8] = 1; + float len = sqrt(az * az + bz * bz + 1); + autolevelTransformation[6] /= len; + autolevelTransformation[7] /= len; + autolevelTransformation[8] /= len; + autolevelTransformation[0] = 1; + autolevelTransformation[1] = 0; + autolevelTransformation[2] = -autolevelTransformation[6] / autolevelTransformation[8]; + len = sqrt(autolevelTransformation[0] * autolevelTransformation[0] + autolevelTransformation[1] * autolevelTransformation[1] + autolevelTransformation[2] * autolevelTransformation[2]); + autolevelTransformation[0] /= len; + autolevelTransformation[1] /= len; + autolevelTransformation[2] /= len; + // cross(z,x) y,z) + autolevelTransformation[3] = autolevelTransformation[7] * autolevelTransformation[2] - autolevelTransformation[8] * autolevelTransformation[1]; + autolevelTransformation[4] = autolevelTransformation[8] * autolevelTransformation[0] - autolevelTransformation[6] * autolevelTransformation[2]; + autolevelTransformation[5] = autolevelTransformation[6] * autolevelTransformation[1] - autolevelTransformation[7] * autolevelTransformation[0]; + len = sqrt(autolevelTransformation[3] * autolevelTransformation[3] + autolevelTransformation[4] * autolevelTransformation[4] + autolevelTransformation[5] * autolevelTransformation[5]); + autolevelTransformation[3] /= len; + autolevelTransformation[4] /= len; + autolevelTransformation[5] /= len; + + Com::printArrayFLN(Com::tTransformationMatrix, autolevelTransformation, 9, 6); +} +/* +void Printer::buildTransformationMatrix(float h1,float h2,float h3) { + float ax = EEPROM::zProbeX2() - EEPROM::zProbeX1(); + float ay = EEPROM::zProbeY2() - EEPROM::zProbeY1(); + float az = h1 - h2; + float bx = EEPROM::zProbeX3() - EEPROM::zProbeX1(); + float by = EEPROM::zProbeY3() - EEPROM::zProbeY1(); + float bz = h1 - h3; + // First z direction + autolevelTransformation[6] = ay * bz - az * by; + autolevelTransformation[7] = az * bx - ax * bz; + autolevelTransformation[8] = ax * by - ay * bx; + float len = sqrt(autolevelTransformation[6] * autolevelTransformation[6] + autolevelTransformation[7] * autolevelTransformation[7] + autolevelTransformation[8] * autolevelTransformation[8]); + if(autolevelTransformation[8] < 0) len = -len; + autolevelTransformation[6] /= len; + autolevelTransformation[7] /= len; + autolevelTransformation[8] /= len; + autolevelTransformation[3] = 0; + autolevelTransformation[4] = autolevelTransformation[8]; + autolevelTransformation[5] = -autolevelTransformation[7]; + // cross(y,z) + autolevelTransformation[0] = autolevelTransformation[4] * autolevelTransformation[8] - autolevelTransformation[5] * autolevelTransformation[7]; + autolevelTransformation[1] = autolevelTransformation[5] * autolevelTransformation[6];// - autolevelTransformation[3] * autolevelTransformation[8]; + autolevelTransformation[2] = autolevelTransformation[3] * autolevelTransformation[7] - autolevelTransformation[4] * autolevelTransformation[6]; + len = sqrt(autolevelTransformation[0] * autolevelTransformation[0] + autolevelTransformation[1] * autolevelTransformation[1] + autolevelTransformation[2] * autolevelTransformation[2]); + autolevelTransformation[0] /= len; + autolevelTransformation[1] /= len; + autolevelTransformation[2] /= len; + len = sqrt(autolevelTransformation[4] * autolevelTransformation[4] + autolevelTransformation[5] * autolevelTransformation[5]); + autolevelTransformation[4] /= len; + autolevelTransformation[5] /= len; + Com::printArrayFLN(Com::tTransformationMatrix,autolevelTransformation, 9, 6); +} +*/ +#endif diff --git a/Repetier-Firmware 1.0.3/Repetier/Commands.cpp b/Repetier-Firmware 1.0.3/Repetier/Commands.cpp new file mode 100644 index 0000000..4653005 --- /dev/null +++ b/Repetier-Firmware 1.0.3/Repetier/Commands.cpp @@ -0,0 +1,3000 @@ +/* +This file is part of Repetier-Firmware. + +Repetier-Firmware is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Repetier-Firmware is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Repetier-Firmware. If not, see . + +This firmware is a nearly complete rewrite of the sprinter firmware +by kliment (https://github.com/kliment/Sprinter) +which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware. +*/ + +#include "Repetier.h" + +const int8_t sensitive_pins[] PROGMEM = SENSITIVE_PINS; // Sensitive pin list for M42 +int Commands::lowestRAMValue = MAX_RAM; +int Commands::lowestRAMValueSend = MAX_RAM; + +void Commands::commandLoop() { + //while(true) { +#ifdef DEBUG_PRINT + debugWaitLoop = 1; +#endif + if(!Printer::isBlockingReceive()) { + GCode::readFromSerial(); + GCode *code = GCode::peekCurrentCommand(); + //UI_SLOW; // do longer timed user interface action + UI_MEDIUM; // do check encoder + if(code) { +#if SDSUPPORT + if(sd.savetosd) { + if(!(code->hasM() && code->M == 29)) // still writing to file + sd.writeCommand(code); + else + sd.finishWrite(); +#if ECHO_ON_EXECUTE + code->echoCommand(); +#endif + } else +#endif + Commands::executeGCode(code); + code->popCurrentCommand(); + } + } else { + GCode::keepAlive(Paused); + UI_MEDIUM; + } + Printer::defaultLoopActions(); + //} +} + +void Commands::checkForPeriodicalActions(bool allowNewMoves) { + Printer::handleInterruptEvent(); + EVENT_PERIODICAL; +#if defined(DOOR_PIN) && DOOR_PIN > -1 + if(Printer::updateDoorOpen()) { +#if defined(SUPPORT_LASER) && SUPPORT_LASER + if(Printer::mode == PRINTER_MODE_LASER) { + LaserDriver::changeIntensity(0); + } +#endif + } +#endif + if(!executePeriodical) return; // gets true every 100ms + executePeriodical = 0; + EVENT_TIMER_100MS; + Extruder::manageTemperatures(); + if(--counter500ms == 0) { + if(manageMonitor) + writeMonitor(); + counter500ms = 5; + EVENT_TIMER_500MS; + } + // If called from queueDelta etc. it is an error to start a new move since it + // would invalidate old computation resulting in unpredicted behavior. + // lcd controller can start new moves, so we disallow it if called from within + // a move command. + UI_SLOW(allowNewMoves); +} + +/** \brief Waits until movement cache is empty. + +Some commands expect no movement, before they can execute. This function +waits, until the steppers are stopped. In the meanwhile it buffers incoming +commands and manages temperatures. +*/ +void Commands::waitUntilEndOfAllMoves() { +#ifdef DEBUG_PRINT + debugWaitLoop = 8; +#endif + while(PrintLine::hasLines()) { + //GCode::readFromSerial(); + checkForPeriodicalActions(false); + GCode::keepAlive(Processing); + UI_MEDIUM; + } +} + +void Commands::waitUntilEndOfAllBuffers() { + GCode *code = NULL; +#ifdef DEBUG_PRINT + debugWaitLoop = 9; +#endif + while(PrintLine::hasLines() || (code != NULL)) { + //GCode::readFromSerial(); + code = GCode::peekCurrentCommand(); + UI_MEDIUM; // do check encoder + if(code) { +#if SDSUPPORT + if(sd.savetosd) { + if(!(code->hasM() && code->M == 29)) // still writing to file + sd.writeCommand(code); + else + sd.finishWrite(); +#if ECHO_ON_EXECUTE + code->echoCommand(); +#endif + } else +#endif + Commands::executeGCode(code); + code->popCurrentCommand(); + } + Commands::checkForPeriodicalActions(false); // only called from memory + UI_MEDIUM; + } +} + +void Commands::printCurrentPosition() { + float x, y, z; + Printer::realPosition(x, y, z); + x += Printer::coordinateOffset[X_AXIS]; + y += Printer::coordinateOffset[Y_AXIS]; + z += Printer::coordinateOffset[Z_AXIS]; + Com::printF(Com::tXColon, x * (Printer::unitIsInches ? 0.03937 : 1), 2); + Com::printF(Com::tSpaceYColon, y * (Printer::unitIsInches ? 0.03937 : 1), 2); + Com::printF(Com::tSpaceZColon, z * (Printer::unitIsInches ? 0.03937 : 1), 3); + Com::printFLN(Com::tSpaceEColon, Printer::currentPositionSteps[E_AXIS] * Printer::invAxisStepsPerMM[E_AXIS] * (Printer::unitIsInches ? 0.03937 : 1), 4); +#ifdef DEBUG_POS + Com::printF(PSTR("OffX:"), Printer::offsetX); // to debug offset handling + Com::printF(PSTR(" OffY:"), Printer::offsetY); + Com::printF(PSTR(" OffZ:"), Printer::offsetZ); + Com::printF(PSTR(" OffZ2:"), Printer::offsetZ2); + Com::printF(PSTR(" XS:"), Printer::currentPositionSteps[X_AXIS]); + Com::printF(PSTR(" YS:"), Printer::currentPositionSteps[Y_AXIS]); + Com::printFLN(PSTR(" ZS:"), Printer::currentPositionSteps[Z_AXIS]); + +#endif +} + +void Commands::printTemperatures(bool showRaw) { + int error; +#if NUM_EXTRUDER > 0 + float temp = Extruder::current->tempControl.currentTemperatureC; +#if HEATED_BED_SENSOR_TYPE == 0 + Com::printF(Com::tTColon, temp); + Com::printF(Com::tSpaceSlash, Extruder::current->tempControl.targetTemperatureC, 0); +#else + Com::printF(Com::tTColon, temp); + Com::printF(Com::tSpaceSlash, Extruder::current->tempControl.targetTemperatureC, 0); +#if HAVE_HEATED_BED + Com::printF(Com::tSpaceBColon, Extruder::getHeatedBedTemperature()); + Com::printF(Com::tSpaceSlash, heatedBedController.targetTemperatureC, 0); + if((error = heatedBedController.errorState()) > 0) { + Com::printF(PSTR(" DB:"), error); + } + if(showRaw) { + Com::printF(Com::tSpaceRaw, (int)NUM_EXTRUDER); + Com::printF(Com::tColon, (1023 << (2 - ANALOG_REDUCE_BITS)) - heatedBedController.currentTemperature); + } + Com::printF(Com::tSpaceBAtColon, (pwm_pos[heatedBedController.pwmIndex])); // Show output of auto tune when tuning! +#endif +#endif + Com::printF(Com::tSpaceAtColon, (autotuneIndex == 255 ? pwm_pos[Extruder::current->id] : pwm_pos[autotuneIndex])); // Show output of auto tune when tuning! +#if NUM_EXTRUDER > 1 && MIXING_EXTRUDER == 0 + for(uint8_t i = 0; i < NUM_EXTRUDER; i++) { + Com::printF(Com::tSpaceT, (int)i); + Com::printF(Com::tColon, extruder[i].tempControl.currentTemperatureC); + Com::printF(Com::tSpaceSlash, extruder[i].tempControl.targetTemperatureC, 0); + Com::printF(Com::tSpaceAt, (int)i); + Com::printF(Com::tColon, (pwm_pos[extruder[i].tempControl.pwmIndex])); // Show output of auto tune when tuning! + if((error = extruder[i].tempControl.errorState()) > 0) { + Com::printF(PSTR(" D"), (int)i); + Com::printF(Com::tColon, error); + } + if(showRaw) { + Com::printF(Com::tSpaceRaw, (int)i); + Com::printF(Com::tColon, (1023 << (2 - ANALOG_REDUCE_BITS)) - extruder[i].tempControl.currentTemperature); + } + } +#elif NUM_EXTRUDER == 1 || MIXING_EXTRUDER + if((error = extruder[0].tempControl.errorState()) > 0) { + Com::printF(PSTR(" D0:"), error); + } + if(showRaw) { + Com::printF(Com::tSpaceRaw, (int)0); + Com::printF(Com::tColon, (1023 << (2 - ANALOG_REDUCE_BITS)) - extruder[0].tempControl.currentTemperature); + } +#endif +#ifdef FAKE_CHAMBER + Com::printF(PSTR(" C:"), extruder[0].tempControl.currentTemperatureC); + Com::printF(Com::tSpaceSlash, extruder[0].tempControl.targetTemperatureC, 0); + Com::printF(PSTR(" @:"), (pwm_pos[extruder[0].tempControl.pwmIndex])); +#endif + Com::println(); +#endif +} +void Commands::changeFeedrateMultiply(int factor) { + if(factor < 25) factor = 25; + if(factor > 500) factor = 500; + Printer::feedrate *= (float)factor / (float)Printer::feedrateMultiply; + Printer::feedrateMultiply = factor; + Com::printFLN(Com::tSpeedMultiply, factor); +} + +void Commands::changeFlowrateMultiply(int factor) { + if(factor < 25) factor = 25; + if(factor > 200) factor = 200; + Printer::extrudeMultiply = factor; + if(Extruder::current->diameter <= 0) + Printer::extrusionFactor = 0.01f * static_cast(factor); + else + Printer::extrusionFactor = 0.01f * static_cast(factor) * 4.0f / (Extruder::current->diameter * Extruder::current->diameter * 3.141592654f); + Com::printFLN(Com::tFlowMultiply, factor); +} + +#if FEATURE_FAN_CONTROL +uint8_t fanKickstart; +#endif +#if FEATURE_FAN2_CONTROL +uint8_t fan2Kickstart; +#endif + +void Commands::setFanSpeed(int speed, bool immediately) { +#if FAN_PIN >- 1 && FEATURE_FAN_CONTROL + if(Printer::fanSpeed == speed) + return; + speed = constrain(speed, 0, 255); + Printer::setMenuMode(MENU_MODE_FAN_RUNNING, speed != 0); + Printer::fanSpeed = speed; + if(PrintLine::linesCount == 0 || immediately) { + if(Printer::mode == PRINTER_MODE_FFF) { + for(fast8_t i = 0; i < PRINTLINE_CACHE_SIZE; i++) + PrintLine::lines[i].secondSpeed = speed; // fill all printline buffers with new fan speed value + } + Printer::setFanSpeedDirectly(speed); + } + Com::printFLN(Com::tFanspeed, speed); // send only new values to break update loops! +#endif +} +void Commands::setFan2Speed(int speed) { +#if FAN2_PIN >- 1 && FEATURE_FAN2_CONTROL + speed = constrain(speed, 0, 255); + Printer::setFan2SpeedDirectly(speed); + Com::printFLN(Com::tFan2speed, speed); // send only new values to break update loops! +#endif +} + +void Commands::reportPrinterUsage() { +#if EEPROM_MODE != 0 + float dist = Printer::filamentPrinted * 0.001 + HAL::eprGetFloat(EPR_PRINTING_DISTANCE); + Com::printF(Com::tPrintedFilament, dist, 2); + Com::printF(Com::tSpacem); + bool alloff = true; +#if NUM_EXTRUDER > 0 + for(uint8_t i = 0; i < NUM_EXTRUDER; i++) + if(tempController[i]->targetTemperatureC > 15) alloff = false; +#endif + int32_t seconds = (alloff ? 0 : (HAL::timeInMilliseconds() - Printer::msecondsPrinting) / 1000) + HAL::eprGetInt32(EPR_PRINTING_TIME); + int32_t tmp = seconds / 86400; + seconds -= tmp * 86400; + Com::printF(Com::tPrintingTime, tmp); + tmp = seconds / 3600; + Com::printF(Com::tSpaceDaysSpace, tmp); + seconds -= tmp * 3600; + tmp = seconds / 60; + Com::printF(Com::tSpaceHoursSpace, tmp); + Com::printFLN(Com::tSpaceMin); +#endif +} +#if STEPPER_CURRENT_CONTROL == CURRENT_CONTROL_DIGIPOT +// Digipot methods for controling current and microstepping + +#if defined(DIGIPOTSS_PIN) && DIGIPOTSS_PIN > -1 +int digitalPotWrite(int address, uint16_t value) { // From Arduino DigitalPotControl example + if(value > 255) + value = 255; + WRITE(DIGIPOTSS_PIN, LOW); // take the SS pin low to select the chip + HAL::spiSend(address); // send in the address and value via SPI: + HAL::spiSend(value); + WRITE(DIGIPOTSS_PIN, HIGH); // take the SS pin high to de-select the chip: + //delay(10); +} + +void setMotorCurrent(uint8_t driver, uint16_t current) { + if(driver > 4) return; + const uint8_t digipot_ch[] = DIGIPOT_CHANNELS; + digitalPotWrite(digipot_ch[driver], current); +} + +void setMotorCurrentPercent( uint8_t channel, float level) { + uint16_t raw_level = ( level * 255 / 100 ); + setMotorCurrent(channel, raw_level); +} +#endif + +void motorCurrentControlInit() { //Initialize Digipot Motor Current +#if DIGIPOTSS_PIN && DIGIPOTSS_PIN > -1 + HAL::spiInit(0); //SPI.begin(); + SET_OUTPUT(DIGIPOTSS_PIN); +#ifdef MOTOR_CURRENT_PERCENT + const float digipot_motor_current[] = MOTOR_CURRENT_PERCENT; + for(int i = 0; i <= 4; i++) + //digitalPotWrite(digipot_ch[i], digipot_motor_current[i]); + setMotorCurrentPercent(i, digipot_motor_current[i]); +#else + const uint8_t digipot_motor_current[] = MOTOR_CURRENT; + for(int i = 0; i <= 4; i++) + //digitalPotWrite(digipot_ch[i], digipot_motor_current[i]); + setMotorCurrent(i, digipot_motor_current[i]); +#endif +#endif +} +#endif + +#if STEPPER_CURRENT_CONTROL == CURRENT_CONTROL_LTC2600 + +void setMotorCurrent( uint8_t channel, unsigned short level ) { + if(channel >= LTC2600_NUM_CHANNELS) return; + const uint8_t ltc_channels[] = LTC2600_CHANNELS; + if(channel > LTC2600_NUM_CHANNELS) return; + uint8_t address = ltc_channels[channel]; + fast8_t i; + + + // NOTE: Do not increase the current endlessly. In case the engine reaches its current saturation, the engine and the driver can heat up and loss power. + // When the saturation is reached, more current causes more heating and more power loss. + // In case of engines with lower quality, the saturation current may be reached before the nominal current. + + // configure the pins + WRITE( LTC2600_CS_PIN, HIGH ); + SET_OUTPUT( LTC2600_CS_PIN ); + WRITE( LTC2600_SCK_PIN, LOW ); + SET_OUTPUT( LTC2600_SCK_PIN ); + WRITE( LTC2600_SDI_PIN, LOW ); + SET_OUTPUT( LTC2600_SDI_PIN ); + + // enable the command interface of the LTC2600 + WRITE( LTC2600_CS_PIN, LOW ); + + // transfer command and address + for( i = 7; i >= 0; i-- ) { + WRITE( LTC2600_SDI_PIN, address & (0x01 << i)); + WRITE( LTC2600_SCK_PIN, 1 ); + WRITE( LTC2600_SCK_PIN, 0 ); + } + + // transfer the data word + for( i = 15; i >= 0; i-- ) { + WRITE( LTC2600_SDI_PIN, level & (0x01 << i)); + WRITE( LTC2600_SCK_PIN, 1 ); + WRITE( LTC2600_SCK_PIN, 0 ); + } + + // disable the ommand interface of the LTC2600 - + // this carries out the specified command + WRITE( LTC2600_CS_PIN, HIGH ); + +} // setLTC2600 +void setMotorCurrentPercent( uint8_t channel, float level) { + if(level > 100.0f) level = 100.0f; + uint16_t raw_level = static_cast( (long)level * 65535L / 100L ); + setMotorCurrent(channel, raw_level); +} + +void motorCurrentControlInit() { //Initialize LTC2600 Motor Current + uint8_t i; +#ifdef MOTOR_CURRENT_PERCENT + const float digipot_motor_current[] = MOTOR_CURRENT_PERCENT; + for(int i = 0; i < LTC2600_NUM_CHANNELS; i++) + //digitalPotWrite(digipot_ch[i], digipot_motor_current[i]); + setMotorCurrentPercent(i, digipot_motor_current[i]); +#else + const unsigned int ltc_current[] = MOTOR_CURRENT; + for(i = 0; i < LTC2600_NUM_CHANNELS; i++) { + setMotorCurrent(i, ltc_current[i] ); + } +#endif +} +#endif + +#if STEPPER_CURRENT_CONTROL == CURRENT_CONTROL_ALLIGATOR +void setMotorCurrent(uint8_t channel, unsigned short value) { + if(channel >= 7) // max channel (X,Y,Z,E0,E1,E2,E3) + return; + if(value > 255) + value = 255; + + uint8_t externalDac_buf[2] = {0x10, 0x00}; + + if(channel > 3) + externalDac_buf[0] |= (7 - channel << 6); + else + externalDac_buf[0] |= (3 - channel << 6); + + externalDac_buf[0] |= (value >> 4); + externalDac_buf[1] |= (value << 4); + + // All SPI chip-select HIGH + WRITE(DAC0_SYNC, HIGH); + WRITE(DAC1_SYNC, HIGH); + WRITE(SPI_EEPROM1_CS, HIGH); + WRITE(SPI_EEPROM2_CS, HIGH); + WRITE(SPI_FLASH_CS, HIGH); + WRITE(SDSS, HIGH); + + if(channel > 3) { // DAC Piggy E1,E2,E3 + WRITE(DAC1_SYNC, LOW); + HAL::delayMicroseconds(2); + WRITE(DAC1_SYNC, HIGH); + HAL::delayMicroseconds(2); + WRITE(DAC1_SYNC, LOW); + } else { // DAC onboard X,Y,Z,E0 + WRITE(DAC0_SYNC, LOW); + HAL::delayMicroseconds(2); + WRITE(DAC0_SYNC, HIGH); + HAL::delayMicroseconds(2); + WRITE(DAC0_SYNC, LOW); + } + + HAL::delayMicroseconds(2); + HAL::spiSend(SPI_CHAN_DAC, externalDac_buf, 2); +} + +void setMotorCurrentPercent( uint8_t channel, float level) { + uint16_t raw_level = ( level * 255 / 100 ); + setMotorCurrent(channel, raw_level); +} + +void motorCurrentControlInit() { //Initialize Motor Current + uint8_t externalDac_buf[2] = {0x20, 0x00};//all off + + // All SPI chip-select HIGH + WRITE(DAC0_SYNC, HIGH); + WRITE(DAC1_SYNC, HIGH); + WRITE(SPI_EEPROM1_CS, HIGH); + WRITE(SPI_EEPROM2_CS, HIGH); + WRITE(SPI_FLASH_CS, HIGH); + WRITE(SDSS, HIGH); + + // init onboard DAC + WRITE(DAC0_SYNC, LOW); + HAL::delayMicroseconds(2); + WRITE(DAC0_SYNC, HIGH); + HAL::delayMicroseconds(2); + WRITE(DAC0_SYNC, LOW); + + HAL::spiSend(SPI_CHAN_DAC, externalDac_buf, 2); + WRITE(DAC0_SYNC, HIGH); + +#if NUM_EXTRUDER > 1 + // init Piggy DAC + WRITE(DAC1_SYNC, LOW); + HAL::delayMicroseconds(2); + WRITE(DAC1_SYNC, HIGH); + HAL::delayMicroseconds(2); + WRITE(DAC1_SYNC, LOW); + + HAL::spiSend(SPI_CHAN_DAC, externalDac_buf, 2); + WRITE(DAC1_SYNC, HIGH); +#endif + +#ifdef MOTOR_CURRENT_PERCENT + const float digipot_motor_current[] = MOTOR_CURRENT_PERCENT; + for(int i = 0; i < NUM_EXTRUDER + 3; i++) + setMotorCurrentPercent(i, digipot_motor_current[i]); +#else + const uint8_t digipot_motor_current[] = MOTOR_CURRENT; + for(uint8_t i = 0; i < NUM_EXTRUDER + 3; i++) + setMotorCurrent(i, digipot_motor_current[i]); +#endif +} +#endif + +#if STEPPER_CURRENT_CONTROL == CURRENT_CONTROL_TMC2130 +TMC2130Stepper* tmcDriverByIndex(uint8_t index) { + switch(index) { +#if TMC2130_ON_X + case 0: // X Axis + return Printer::tmc_driver_x; + break; +#endif +#if TMC2130_ON_Y + case 1: // Y Axis + return Printer::tmc_driver_y; + break; +#endif +#if TMC2130_ON_Z + case 2: // Z Axis + return Printer::tmc_driver_z; + break; +#endif +#if TMC2130_ON_EXT0 + case 3: // E0 Axis + return Printer::tmc_driver_e0; + break; +#endif +#if TMC2130_ON_EXT1 + case 4: // E1 Axis + return Printer::tmc_driver_e1; + break; +#endif +#if TMC2130_ON_EXT2 + case 5: // E2 Axis + return Printer::tmc_driver_e2; + break; +#endif + default: + return NULL; + break; + } +} + +void setMotorCurrent( uint8_t driver, uint16_t level ) { + TMC2130Stepper* tmc_driver = tmcDriverByIndex(driver); + if(tmc_driver) { +#if MOTHERBOARD == 310 + tmc_driver->rms_current(level, 0.5, 0.22); +#else + tmc_driver->rms_current(level); +#endif + } +} + +void setMotorCurrentPercent( uint8_t channel, float level ) { + const uint16_t tmc_motor_current[] = MOTOR_CURRENT; + uint16_t raw_level = ( level * tmc_motor_current[channel] / 100 ); + setMotorCurrent(channel, raw_level); +} + +void motorCurrentControlInit() { + const uint16_t tmc_motor_current[] = MOTOR_CURRENT; + for(uint8_t i = 0; i < (sizeof(tmc_motor_current) / sizeof(uint16_t)); i++) { + setMotorCurrent(i, tmc_motor_current[i]); + } +} + +void motorCurrentReadings() { + Com::printF(Com::tTrinamicMotorCurrent); +#if TMC2130_ON_X + Com::printF(Com::tSpaceXColon, (uint32_t)(Printer::tmc_driver_x->rms_current())); +#endif +#if TMC2130_ON_Y + Com::printF(Com::tSpaceYColon, (uint32_t)(Printer::tmc_driver_y->rms_current())); +#endif +#if TMC2130_ON_Z + Com::printF(Com::tSpaceZColon, (uint32_t)(Printer::tmc_driver_z->rms_current())); +#endif +#if TMC2130_ON_EXT0 + Com::printF(Com::tSpaceEColon, (uint32_t)(Printer::tmc_driver_e0->rms_current())); +#endif +#if TMC2130_ON_EXT1 + Com::printF(PSTR(" E1:"), (uint32_t)(Printer::tmc_driver_e1->rms_current())); +#endif +#if TMC2130_ON_EXT2 + Com::printF(PSTR(" E2:"), (uint32_t)(Printer::tmc_driver_e2->rms_current())); +#endif + Com::printFLN(Com::tSpace); +} +#endif // CURRENT_CONTROL_TMC2130 + +#if STEPPER_CURRENT_CONTROL == CURRENT_CONTROL_MCP4728 +uint8_t _intVref[] = {MCP4728_VREF, MCP4728_VREF, MCP4728_VREF, MCP4728_VREF}; +uint8_t _gain[] = {MCP4728_GAIN, MCP4728_GAIN, MCP4728_GAIN, MCP4728_GAIN}; +uint8_t _powerDown[] = {0, 0, 0, 0}; +int16_t dac_motor_current[] = {0, 0, 0, 0}; + +uint8_t _intVrefEp[] = {MCP4728_VREF, MCP4728_VREF, MCP4728_VREF, MCP4728_VREF}; +uint8_t _gainEp[] = {MCP4728_GAIN, MCP4728_GAIN, MCP4728_GAIN, MCP4728_GAIN}; +uint8_t _powerDownEp[] = {0, 0, 0, 0}; +int16_t _valuesEp[] = {0, 0, 0, 0}; + +uint8_t dac_stepper_channel[] = MCP4728_STEPPER_ORDER; + +int dacSimpleCommand(uint8_t simple_command) { + HAL::i2cStartWait(MCP4728_GENERALCALL_ADDRESS + I2C_WRITE); + HAL::i2cWrite(simple_command); + HAL::i2cStop(); +} + +void dacReadStatus() { + HAL::delayMilliseconds(500); + HAL::i2cStartWait(MCP4728_I2C_ADDRESS | I2C_READ); + + for (int i = 0; i < 8; i++) { // 2 sets of 4 Channels (1 EEPROM, 1 Runtime) + uint8_t deviceID = HAL::i2cReadAck(); + uint8_t hiByte = HAL::i2cReadAck(); + uint8_t loByte = ((i < 7) ? HAL::i2cReadAck() : HAL::i2cReadNak()); + + uint8_t isEEPROM = (deviceID & 0B00001000) >> 3; + uint8_t channel = (deviceID & 0B00110000) >> 4; + if (isEEPROM == 1) { + _intVrefEp[channel] = (hiByte & 0B10000000) >> 7; + _gainEp[channel] = (hiByte & 0B00010000) >> 4; + _powerDownEp[channel] = (hiByte & 0B01100000) >> 5; + _valuesEp[channel] = word((hiByte & 0B00001111), loByte); + } else { + _intVref[channel] = (hiByte & 0B10000000) >> 7; + _gain[channel] = (hiByte & 0B00010000) >> 4; + _powerDown[channel] = (hiByte & 0B01100000) >> 5; + dac_motor_current[channel] = word((hiByte & 0B00001111), loByte); + } + } + + HAL::i2cStop(); +} + +void dacAnalogUpdate(bool saveEEPROM = false) { + uint8_t dac_write_cmd = MCP4728_CMD_SEQ_WRITE; + + HAL::i2cStartWait(MCP4728_I2C_ADDRESS + I2C_WRITE); + if (saveEEPROM) HAL::i2cWrite(dac_write_cmd); + + for (int i = 0; i < MCP4728_NUM_CHANNELS; i++) { + uint16_t level = dac_motor_current[i]; + + uint8_t highbyte = ( _intVref[i] << 7 | _gain[i] << 4 | (uint8_t)((level) >> 8) ); + uint8_t lowbyte = ( (uint8_t) ((level) & 0xff) ); + dac_write_cmd = MCP4728_CMD_MULTI_WRITE | (i << 1); + + if (!saveEEPROM) HAL::i2cWrite(dac_write_cmd); + HAL::i2cWrite(highbyte); + HAL::i2cWrite(lowbyte); + } + + HAL::i2cStop(); + + // Instruct the MCP4728 to reflect our updated value(s) on its DAC Outputs + dacSimpleCommand((uint8_t)MCP4728_CMD_GC_UPDATE); // MCP4728 General Command Software Update (Update all DAC Outputs to reflect settings) + + // if (saveEEPROM) dacReadStatus(); // Not necessary, just a read-back sanity check. +} + +void dacCommitEeprom() { + dacAnalogUpdate(true); + dacReadStatus(); // Refresh EEPROM Values with values actually stored in EEPROM. . +} + +void dacPrintSet(int dacChannelSettings[], const char* dacChannelPrefixes[]) { + for (int i = 0; i < MCP4728_NUM_CHANNELS; i++) { + uint8_t dac_channel = dac_stepper_channel[i]; // DAC Channel is a mapped lookup. + Com::printF(dacChannelPrefixes[i], ((float)dacChannelSettings[dac_channel] * 100 / MCP4728_VOUT_MAX)); + Com::printF(Com::tSpaceRaw); + Com::printFLN(Com::tColon, dacChannelSettings[dac_channel]); + } +} + +void dacPrintValues() { + const char* dacChannelPrefixes[] = {Com::tSpaceXColon, Com::tSpaceYColon, Com::tSpaceZColon, Com::tSpaceEColon}; + + Com::printFLN(Com::tMCPEpromSettings); + dacPrintSet(_valuesEp, dacChannelPrefixes); // Once for the EEPROM set + + Com::printFLN(Com::tMCPCurrentSettings); + dacPrintSet(dac_motor_current, dacChannelPrefixes); // And another for the RUNTIME set +} + +void setMotorCurrent( uint8_t xyz_channel, uint16_t level ) { + if (xyz_channel >= MCP4728_NUM_CHANNELS) return; + uint8_t stepper_channel = dac_stepper_channel[xyz_channel]; + dac_motor_current[stepper_channel] = level < MCP4728_VOUT_MAX ? level : MCP4728_VOUT_MAX; + dacAnalogUpdate(); +} + +void setMotorCurrentPercent( uint8_t channel, float level) { + uint16_t raw_level = ( level * MCP4728_VOUT_MAX / 100 ); + setMotorCurrent(channel, raw_level); +} + +void motorCurrentControlInit() { //Initialize MCP4728 Motor Current + HAL::i2cInit(400000); // Initialize the i2c bus. + dacSimpleCommand((uint8_t)MCP4728_CMD_GC_RESET); // MCP4728 General Command Reset + dacReadStatus(); // Load Values from EEPROM. + + for(int i = 0; i < MCP4728_NUM_CHANNELS; i++) { + setMotorCurrent(dac_stepper_channel[i], _valuesEp[i] ); // This is not strictly necessary, but serves as a good sanity check to ensure we're all on the same page. + } +} +#endif + +#if defined(DRV_TMC2130) +void microstepMode(uint8_t driver, uint16_t stepping_mode) { + TMC2130Stepper* tmc_driver; + if(tmc_driver = tmcDriverByIndex(driver)) + tmc_driver->microsteps(stepping_mode); +} + +void microstepInit() { + const uint16_t microstep_modes[] = MICROSTEP_MODES; + for(uint8_t i = 0; i < (sizeof(microstep_modes) / sizeof(uint16_t)); i++) { + microstepMode(i, microstep_modes[i]); + } +} + +void microstepReadings() { + Com::printF(Com::tTrinamicMicrostepMode); +#if TMC2130_ON_X + Com::printF(Com::tSpaceXColon, (uint32_t)(Printer::tmc_driver_x->microsteps())); +#endif +#if TMC2130_ON_Y + Com::printF(Com::tSpaceYColon, (uint32_t)(Printer::tmc_driver_y->microsteps())); +#endif +#if TMC2130_ON_Z + Com::printF(Com::tSpaceZColon, (uint32_t)(Printer::tmc_driver_z->microsteps())); +#endif +#if TMC2130_ON_EXT0 + Com::printF(Com::tSpaceEColon, (uint32_t)(Printer::tmc_driver_e0->microsteps())); +#endif +#if TMC2130_ON_EXT1 + Com::printF(PSTR(" E1:"), (uint32_t)(Printer::tmc_driver_e1->microsteps())); +#endif +#if TMC2130_ON_EXT2 + Com::printF(PSTR(" E1:"), (uint32_t)(Printer::tmc_driver_e2->microsteps())); +#endif + Com::printFLN(Com::tSpace); +} +#else +#if defined(X_MS1_PIN) && X_MS1_PIN > -1 +void microstepMS(uint8_t driver, int8_t ms1, int8_t ms2) { + if(ms1 > -1) switch(driver) { + case 0: +#if X_MS1_PIN > -1 + WRITE( X_MS1_PIN, ms1); +#endif + break; + case 1: +#if Y_MS1_PIN > -1 + WRITE( Y_MS1_PIN, ms1); +#endif + break; + case 2: +#if Z_MS1_PIN > -1 + WRITE( Z_MS1_PIN, ms1); +#endif + break; + case 3: +#if E0_MS1_PIN > -1 + WRITE(E0_MS1_PIN, ms1); +#endif + break; + case 4: +#if E1_MS1_PIN > -1 + WRITE(E1_MS1_PIN, ms1); +#endif + break; + } + if(ms2 > -1) switch(driver) { + case 0: +#if X_MS2_PIN > -1 + WRITE( X_MS2_PIN, ms2); +#endif + break; + case 1: +#if Y_MS2_PIN > -1 + WRITE( Y_MS2_PIN, ms2); +#endif + break; + case 2: +#if Z_MS2_PIN > -1 + WRITE( Z_MS2_PIN, ms2); +#endif + break; + case 3: +#if E0_MS2_PIN > -1 + WRITE(E0_MS2_PIN, ms2); +#endif + break; + case 4: +#if E1_MS2_PIN > -1 + WRITE(E1_MS2_PIN, ms2); +#endif + break; + } +} + +void microstepMode(uint8_t driver, uint8_t stepping_mode) { + switch(stepping_mode) { + case 1: + microstepMS(driver, MICROSTEP1); + break; + case 2: + microstepMS(driver, MICROSTEP2); + break; + case 4: + microstepMS(driver, MICROSTEP4); + break; + case 8: + microstepMS(driver, MICROSTEP8); + break; + case 16: + microstepMS(driver, MICROSTEP16); + break; + case 32: + microstepMS(driver, MICROSTEP32); + break; + } +} + +void microstepReadings() { + Com::printFLN(Com::tMS1MS2Pins); +#if X_MS1_PIN > -1 && X_MS2_PIN > -1 + Com::printF(Com::tXColon, READ(X_MS1_PIN)); + Com::printFLN(Com::tComma, READ(X_MS2_PIN)); +#elif X_MS1_PIN > -1 + Com::printFLN(Com::tXColon, READ(X_MS1_PIN)); +#endif +#if Y_MS1_PIN > -1 && Y_MS2_PIN > -1 + Com::printF(Com::tYColon, READ(Y_MS1_PIN)); + Com::printFLN(Com::tComma, READ(Y_MS2_PIN)); +#elif Y_MS1_PIN > -1 + Com::printFLN(Com::tYColon, READ(Y_MS1_PIN)); +#endif +#if Z_MS1_PIN > -1 && Z_MS2_PIN > -1 + Com::printF(Com::tZColon, READ(Z_MS1_PIN)); + Com::printFLN(Com::tComma, READ(Z_MS2_PIN)); +#elif Z_MS1_PIN > -1 + Com::printFLN(Com::tZColon, READ(Z_MS1_PIN)); +#endif +#if E0_MS1_PIN > -1 && E0_MS2_PIN > -1 + Com::printF(Com::tE0Colon, READ(E0_MS1_PIN)); + Com::printFLN(Com::tComma, READ(E0_MS2_PIN)); +#elif E0_MS1_PIN > -1 + Com::printFLN(Com::tE0Colon, READ(E0_MS1_PIN)); +#endif +#if E1_MS1_PIN > -1 && E1_MS2_PIN > -1 + Com::printF(Com::tE1Colon, READ(E1_MS1_PIN)); + Com::printFLN(Com::tComma, READ(E1_MS2_PIN)); +#elif E1_MS1_PIN > -1 + Com::printFLN(Com::tE1Colon, READ(E1_MS1_PIN)); +#endif +} +#endif + +void microstepInit() { +#if defined(X_MS1_PIN) && X_MS1_PIN > -1 + const uint8_t microstep_modes[] = MICROSTEP_MODES; +#if X_MS1_PIN > -1 + SET_OUTPUT(X_MS1_PIN); +#endif +#if Y_MS1_PIN > -1 + SET_OUTPUT(Y_MS1_PIN); +#endif +#if Z_MS1_PIN > -1 + SET_OUTPUT(Z_MS1_PIN); +#endif +#if E0_MS1_PIN > -1 + SET_OUTPUT(E0_MS1_PIN); +#endif +#if E1_MS1_PIN > -1 + SET_OUTPUT(E1_MS1_PIN); +#endif +#if X_MS2_PIN > -1 + SET_OUTPUT(X_MS2_PIN); +#endif +#if Y_MS2_PIN > -1 + SET_OUTPUT(Y_MS2_PIN); +#endif +#if Z_MS2_PIN > -1 + SET_OUTPUT(Z_MS2_PIN); +#endif +#if E0_MS2_PIN > -1 + SET_OUTPUT(E0_MS2_PIN); +#endif +#if E1_MS2_PIN > -1 + SET_OUTPUT(E1_MS2_PIN); +#endif + for(int i = 0; i <= 4; i++) microstepMode(i, microstep_modes[i]); +#endif +} +#endif +/** +\brief Execute the Arc command stored in com. +*/ +#if ARC_SUPPORT +void Commands::processArc(GCode *com) { + float position[Z_AXIS_ARRAY]; + Printer::realPosition(position[X_AXIS], position[Y_AXIS], position[Z_AXIS]); + if(!Printer::setDestinationStepsFromGCode(com)) return; // For X Y Z E F + float offset[2] = {Printer::convertToMM(com->hasI() ? com->I : 0), Printer::convertToMM(com->hasJ() ? com->J : 0)}; + float target[E_AXIS_ARRAY] = {Printer::realXPosition(), Printer::realYPosition(), Printer::realZPosition(), Printer::destinationSteps[E_AXIS]*Printer::invAxisStepsPerMM[E_AXIS]}; + float r; + if (com->hasR()) { + /* + We need to calculate the center of the circle that has the designated radius and passes + through both the current position and the target position. This method calculates the following + set of equations where [x,y] is the vector from current to target position, d == magnitude of + that vector, h == hypotenuse of the triangle formed by the radius of the circle, the distance to + the center of the travel vector. A vector perpendicular to the travel vector [-y,x] is scaled to the + length of h [-y/d*h, x/d*h] and added to the center of the travel vector [x/2,y/2] to form the new point + [i,j] at [x/2-y/d*h, y/2+x/d*h] which will be the center of our arc. + + d^2 == x^2 + y^2 + h^2 == r^2 - (d/2)^2 + i == x/2 - y/d*h + j == y/2 + x/d*h + + O <- [i,j] + - | + r - | + - | + - | h + - | + [0,0] -> C -----------------+--------------- T <- [x,y] + | <------ d/2 ---->| + + C - Current position + T - Target position + O - center of circle that pass through both C and T + d - distance from C to T + r - designated radius + h - distance from center of CT to O + + Expanding the equations: + + d -> sqrt(x^2 + y^2) + h -> sqrt(4 * r^2 - x^2 - y^2)/2 + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + + Which can be written: + + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + + Which we for size and speed reasons optimize to: + + h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) + i = (x - (y * h_x2_div_d))/2 + j = (y + (x * h_x2_div_d))/2 + + */ + r = Printer::convertToMM(com->R); + // Calculate the change in position along each selected axis + double x = target[X_AXIS] - position[X_AXIS]; + double y = target[Y_AXIS] - position[Y_AXIS]; + + double h_x2_div_d = -sqrt(4 * r * r - x * x - y * y) / hypot(x, y); // == -(h * 2 / d) + // If r is smaller than d, the arc is now traversing the complex plane beyond the reach of any + // real CNC, and thus - for practical reasons - we will terminate promptly: + if(isnan(h_x2_div_d)) { + Com::printErrorFLN(Com::tInvalidArc); + return; + } + // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) + if (com->G == 3) { + h_x2_div_d = -h_x2_div_d; + } + + /* The counter clockwise circle lies to the left of the target direction. When offset is positive, + the left hand circle will be generated - when it is negative the right hand circle is generated. + + + T <-- Target position + + ^ + Clockwise circles with this center | Clockwise circles with this center will have + will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! + \ | / + center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative + | + | + + C <-- Current position */ + + + // Negative R is g-code-alias for "I want a circle with more than 180 degrees of travel" (go figure!), + // even though it is advised against ever generating such circles in a single line of g-code. By + // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of + // travel and thus we get the inadvisable long arcs as prescribed. + if (r < 0) { + h_x2_div_d = -h_x2_div_d; + r = -r; // Finished with r. Set to positive for mc_arc + } + // Complete the operation by calculating the actual center of the arc + offset[0] = 0.5 * (x - (y * h_x2_div_d)); + offset[1] = 0.5 * (y + (x * h_x2_div_d)); + + } else { // Offset mode specific computations + r = hypot(offset[0], offset[1]); // Compute arc radius for arc + } + // Set clockwise/counter-clockwise sign for arc computations + uint8_t isclockwise = com->G == 2; + // Trace the arc + PrintLine::arc(position, target, offset, r, isclockwise); +} +#endif + +/** +\brief Execute the G command stored in com. +*/ +void Commands::processGCode(GCode *com) { + if(EVENT_UNHANDLED_G_CODE(com)) { + previousMillisCmd = HAL::timeInMilliseconds(); + return; + } + uint32_t codenum; //throw away variable + switch(com->G) { + case 0: // G0 -> G1 + case 1: // G1 +#if defined(SUPPORT_LASER) && SUPPORT_LASER + { + // disable laser for G0 moves + bool laserOn = LaserDriver::laserOn; + if(Printer::mode == PRINTER_MODE_LASER) { + if(com->G == 0) { + LaserDriver::laserOn = false; + LaserDriver::firstMove = true; //set G1 flag for Laser + } else { +#if LASER_WARMUP_TIME > 0 + uint8_t power = (com->hasX() || com->hasY()) && (LaserDriver::laserOn || com->hasE()) ? LaserDriver::intensity : 0; + if(power > 0 && LaserDriver::firstMove) { + PrintLine::waitForXFreeLines(1, true); + PrintLine::LaserWarmUp(LASER_WARMUP_TIME); + LaserDriver::firstMove = false; + } +#endif + } + } +#endif // defined + if(com->hasS()) Printer::setNoDestinationCheck(com->S != 0); + if(Printer::setDestinationStepsFromGCode(com)) // For X Y Z E F +#if NONLINEAR_SYSTEM + if (!PrintLine::queueNonlinearMove(ALWAYS_CHECK_ENDSTOPS, true, true)) { + Com::printWarningFLN(PSTR("executeGCode / queueDeltaMove returns error")); + } +#else + PrintLine::queueCartesianMove(ALWAYS_CHECK_ENDSTOPS, true); +#endif +#if UI_HAS_KEYS + // ui can only execute motion commands if we are not waiting inside a move for an + // old move to finish. For normal response times, we always leave one free after + // sending a line. Drawback: 1 buffer line less for limited time. Since input cache + // gets filled while waiting, the lost is neglectable. + PrintLine::waitForXFreeLines(1, true); +#endif // UI_HAS_KEYS +#ifdef DEBUG_QUEUE_MOVE + { + + InterruptProtectedBlock noInts; + int lc = (int)PrintLine::linesCount; + int lp = (int)PrintLine::linesPos; + int wp = (int)PrintLine::linesWritePos; + int n = (wp - lp); + if(n < 0) n += PRINTLINE_CACHE_SIZE; + noInts.unprotect(); + if(n != lc) + Com::printFLN(PSTR("Buffer corrupted")); + } +#endif + +#if defined(SUPPORT_LASER) && SUPPORT_LASER + LaserDriver::laserOn = laserOn; + } +#endif // defined + break; +#if ARC_SUPPORT + case 2: // CW Arc + case 3: // CCW Arc MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: +#if defined(SUPPORT_LASER) && SUPPORT_LASER + { + bool laserOn = LaserDriver::laserOn; +#if LASER_WARMUP_TIME > 0 + if(Printer::mode == PRINTER_MODE_LASER && LaserDriver::firstMove && (LaserDriver::laserOn || com->hasE())) { + PrintLine::waitForXFreeLines(1, true); + PrintLine::LaserWarmUp(LASER_WARMUP_TIME); + LaserDriver::firstMove = false; + } +#endif +#endif // defined + processArc(com); +#if defined(SUPPORT_LASER) && SUPPORT_LASER + LaserDriver::laserOn = laserOn; + } +#endif // defined + break; +#endif + case 4: // G4 dwell + Commands::waitUntilEndOfAllMoves(); + codenum = 0; + if(com->hasP()) codenum = com->P; // milliseconds to wait + if(com->hasS()) codenum = com->S * 1000; // seconds to wait + codenum += HAL::timeInMilliseconds(); // keep track of when we started waiting + while((uint32_t)(codenum - HAL::timeInMilliseconds()) < 2000000000 ) { + GCode::keepAlive(Processing); + Commands::checkForPeriodicalActions(true); + } + break; +#if FEATURE_RETRACTION && NUM_EXTRUDER > 0 + case 10: // G10 S<1 = long retract, 0 = short retract = default> retracts filament according to stored setting +#if NUM_EXTRUDER > 1 + Extruder::current->retract(true, com->hasS() && com->S > 0); +#else + Extruder::current->retract(true, false); +#endif + break; + case 11: // G11 S<1 = long retract, 0 = short retract = default> = Undo retraction according to stored setting +#if NUM_EXTRUDER > 1 + Extruder::current->retract(false, com->hasS() && com->S > 0); +#else + Extruder::current->retract(false, false); +#endif + break; +#endif // FEATURE_RETRACTION + case 20: // G20 Units to inches + Printer::unitIsInches = 1; + break; + case 21: // G21 Units to mm + Printer::unitIsInches = 0; + break; + case 28: { //G28 Home all Axis one at a time + uint8_t homeAllAxis = (com->hasNoXYZ() && !com->hasE()); + if(com->hasE()) + Printer::currentPositionSteps[E_AXIS] = 0; + if(homeAllAxis || !com->hasNoXYZ()) + Printer::homeAxis(homeAllAxis || com->hasX(), homeAllAxis || com->hasY(), homeAllAxis || com->hasZ()); + } + break; +#if FEATURE_Z_PROBE + case 29: { // G29 3 points, build average or distortion compensation + Printer::prepareForProbing(); +#if defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE && Z_PROBE_REQUIRES_HEATING + float actTemp[NUM_EXTRUDER]; + for(int i = 0; i < NUM_EXTRUDER; i++) + actTemp[i] = extruder[i].tempControl.targetTemperatureC; + Printer::moveToReal(IGNORE_COORDINATE, IGNORE_COORDINATE, RMath::max(EEPROM::zProbeHeight(), static_cast(ZHOME_HEAT_HEIGHT)), IGNORE_COORDINATE, Printer::homingFeedrate[Z_AXIS]); + Commands::waitUntilEndOfAllMoves(); +#if ZHOME_HEAT_ALL + for(int i = 0; i < NUM_EXTRUDER; i++) { + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, false); + } + for(int i = 0; i < NUM_EXTRUDER; i++) { + if(extruder[i].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, true); + } +#else + if(extruder[Extruder::current->id].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[Extruder::current->id], static_cast(ZPROBE_MIN_TEMPERATURE)), Extruder::current->id, false, true); +#endif +#endif + bool ok = true; + Printer::startProbing(true); + bool oldAutolevel = Printer::isAutolevelActive(); + Printer::setAutolevelActive(false); + float sum = 0, last, oldFeedrate = Printer::feedrate; + Printer::moveTo(EEPROM::zProbeX1(), EEPROM::zProbeY1(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + sum = Printer::runZProbe(true, false, Z_PROBE_REPETITIONS, false); + if(sum == ILLEGAL_Z_PROBE) ok = false; + if(ok) { + Printer::moveTo(EEPROM::zProbeX2(), EEPROM::zProbeY2(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + last = Printer::runZProbe(false, false); + if(last == ILLEGAL_Z_PROBE) ok = false; + sum += last; + } + if(ok) { + Printer::moveTo(EEPROM::zProbeX3(), EEPROM::zProbeY3(), IGNORE_COORDINATE, IGNORE_COORDINATE, EEPROM::zProbeXYSpeed()); + last = Printer::runZProbe(false, true); + if(last == ILLEGAL_Z_PROBE) ok = false; + sum += last; + } + if(ok) { + sum *= 0.33333333333333; + Com::printFLN(Com::tZProbeAverage, sum); + if(com->hasS() && com->S) { +#if MAX_HARDWARE_ENDSTOP_Z +#if DRIVE_SYSTEM == DELTA + Printer::updateCurrentPosition(); + Printer::zLength += sum - Printer::currentPosition[Z_AXIS]; + Printer::updateDerivedParameter(); + Printer::homeAxis(true, true, true); +#else + Printer::currentPositionSteps[Z_AXIS] = sum * Printer::axisStepsPerMM[Z_AXIS]; + float zup = Printer::runZMaxProbe(); + if(zup == ILLEGAL_Z_PROBE) { + ok = false; + } else + Printer::zLength = zup + sum - ENDSTOP_Z_BACK_ON_HOME; +#endif // DELTA + Com::printInfoFLN(Com::tZProbeZReset); + Com::printFLN(Com::tZProbePrinterHeight, Printer::zLength); +#else + Printer::currentPositionSteps[Z_AXIS] = sum * Printer::axisStepsPerMM[Z_AXIS]; + Com::printFLN(PSTR("Adjusted z origin")); +#endif // max z endstop + } + Printer::feedrate = oldFeedrate; + Printer::setAutolevelActive(oldAutolevel); + if(ok && com->hasS() && com->S == 2) + EEPROM::storeDataIntoEEPROM(); + } + Printer::updateCurrentPosition(true); + printCurrentPosition(); + Printer::finishProbing(); + Printer::feedrate = oldFeedrate; + if(!ok) { + GCode::fatalError(PSTR("G29 leveling failed!")); + break; + } +#if defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE && Z_PROBE_REQUIRES_HEATING +#if ZHOME_HEAT_ALL + for(int i = 0; i < NUM_EXTRUDER; i++) { + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, false); + } + for(int i = 0; i < NUM_EXTRUDER; i++) { + if(extruder[i].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, true); + } +#else + if(extruder[Extruder::current->id].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[Extruder::current->id], static_cast(ZPROBE_MIN_TEMPERATURE)), Extruder::current->id, false, true); +#endif +#endif + } + break; + case 30: { + // G30 [Pn] [S] + // G30 (the same as G30 P3) single probe set Z0 + // G30 S1 Z - measures probe height (P is ignored) assuming we are at real height Z + // G30 H R Make probe define new Z and z offset (R) at trigger point assuming z-probe measured an object of H height. + if (com->hasS()) { + Printer::measureZProbeHeight(com->hasZ() ? com->Z : Printer::currentPosition[Z_AXIS]); + } else { + uint8_t p = (com->hasP() ? (uint8_t)com->P : 3); + float z = Printer::runZProbe(p & 1, p & 2, Z_PROBE_REPETITIONS, true, false); + if(z == ILLEGAL_Z_PROBE) { + GCode::fatalError(PSTR("G30 probing failed!")); + break; + } + if(com->hasR() || com->hasH()) { + float h = Printer::convertToMM(com->hasH() ? com->H : 0); + float o = Printer::convertToMM(com->hasR() ? com->R : h); +#if DISTORTION_CORRECTION + // Undo z distortion correction contained in z + float zCorr = 0; + if(Printer::distortion.isEnabled()) { + zCorr = Printer::distortion.correct(Printer::currentPositionSteps[X_AXIS], Printer::currentPositionSteps[Y_AXIS], Printer::zMinSteps) * Printer::invAxisStepsPerMM[Z_AXIS]; + z -= zCorr; + } +#endif + Printer::coordinateOffset[Z_AXIS] = o - h; + Printer::currentPosition[Z_AXIS] = Printer::lastCmdPos[Z_AXIS] = z + h + Printer::zMin; + Printer::updateCurrentPositionSteps(); + Printer::setZHomed(true); +#if NONLINEAR_SYSTEM + transformCartesianStepsToDeltaSteps(Printer::currentPositionSteps, Printer::currentNonlinearPositionSteps); +#endif + } else { + Printer::updateCurrentPosition(p & 1); + } + } + } + break; + case 31: // G31 display hall sensor output + Endstops::update(); + Endstops::update(); + Com::printF(Com::tZProbeState); + Com::printF(Endstops::zProbe() ? Com::tHSpace : Com::tLSpace); + Com::println(); + break; +#if FEATURE_AUTOLEVEL + case 32: // G32 Auto-Bed leveling + if(!runBedLeveling(com->hasS() ? com->S : -1)) { + GCode::fatalError(PSTR("G32 leveling failed!")); + } + break; +#endif +#if DISTORTION_CORRECTION + case 33: { + if(com->hasL()) { // G33 L0 - List distortion matrix + Printer::distortion.showMatrix(); + } else if(com->hasR()) { // G33 R0 - Reset distortion matrix + Printer::distortion.resetCorrection(); + } else if(com->hasX() || com->hasY() || com->hasZ()) { // G33 X Y Z - Set correction for nearest point + if(com->hasX() && com->hasY() && com->hasZ()) { + Printer::distortion.set(com->X, com->Y, com->Z); + } else { + Com::printErrorFLN(PSTR("You need to define X, Y and Z to set a point!")); + } + } else { // G33 + Printer::measureDistortion(); + } + } + break; +#endif +#endif + case 90: // G90 + Printer::relativeCoordinateMode = false; + if(com->internalCommand) + Com::printInfoFLN(PSTR("Absolute positioning")); + break; + case 91: // G91 + Printer::relativeCoordinateMode = true; + if(com->internalCommand) + Com::printInfoFLN(PSTR("Relative positioning")); + break; + case 92: { // G92 + float xOff = Printer::coordinateOffset[X_AXIS]; + float yOff = Printer::coordinateOffset[Y_AXIS]; + float zOff = Printer::coordinateOffset[Z_AXIS]; + if(com->hasX()) xOff = Printer::convertToMM(com->X) - Printer::currentPosition[X_AXIS]; + if(com->hasY()) yOff = Printer::convertToMM(com->Y) - Printer::currentPosition[Y_AXIS]; + if(com->hasZ()) zOff = Printer::convertToMM(com->Z) - Printer::currentPosition[Z_AXIS]; + Printer::setOrigin(xOff, yOff, zOff); + if(com->hasE()) { + Printer::destinationSteps[E_AXIS] = Printer::currentPositionSteps[E_AXIS] = Printer::convertToMM(com->E) * Printer::axisStepsPerMM[E_AXIS]; + } + if(com->hasX() || com->hasY() || com->hasZ()) { + Com::printF(PSTR("X_OFFSET:"), Printer::coordinateOffset[X_AXIS], 3); + Com::printF(PSTR(" Y_OFFSET:"), Printer::coordinateOffset[Y_AXIS], 3); + Com::printFLN(PSTR(" Z_OFFSET:"), Printer::coordinateOffset[Z_AXIS], 3); + } + } + break; +#if DRIVE_SYSTEM == DELTA + case 100: { // G100 Calibrate floor or rod radius + // Using manual control, adjust hot end to contact floor. + // G100 No action. Avoid accidental floor reset. + // G100 [X] [Y] [Z] set floor for argument passed in. Number ignored and may be absent. + // G100 R with X Y or Z flag error, sets only floor or radius, not both. + // G100 R[n] Add n to radius. Adjust to be above floor if necessary + // G100 R[0] set radius based on current z measurement. Moves to (0,0,0) + float currentZmm = Printer::currentPosition[Z_AXIS]; + if (currentZmm / Printer::zLength > 0.1) { + Com::printErrorFLN(PSTR("Calibration code is limited to bottom 10% of Z height")); + break; + } + if (com->hasR()) { + if (com->hasX() || com->hasY() || com->hasZ()) + Com::printErrorFLN(PSTR("Cannot set radius and floor at same time.")); + else if (com->R != 0) { + //add r to radius + if (abs(com->R) <= 10) EEPROM::incrementRodRadius(com->R); + else Com::printErrorFLN(PSTR("Calibration movement is limited to 10mm.")); + } else { + // auto set radius. Head must be at 0,0 and touching + // Z offset will be corrected for. + if (Printer::currentPosition[X_AXIS] == 0 + && Printer::currentPosition[Y_AXIS] == 0) { + if(Printer::isLargeMachine()) { + // calculate radius assuming we are at surface + // If Z is greater than 0 it will get calculated out for correct radius + // Use either A or B tower as they anchor x Cartesian axis and always have + // Radius distance to center in simplest set up. + float h = Printer::deltaDiagonalStepsSquaredB.f; + unsigned long bSteps = Printer::currentNonlinearPositionSteps[B_TOWER]; + // The correct Rod Radius would put us here at z==0 and B height is + // square root (rod length squared minus rod radius squared) + // Reverse that to get calculated Rod Radius given B height + h -= RMath::sqr((float)bSteps); + h = sqrt(h); + EEPROM::setRodRadius(h * Printer::invAxisStepsPerMM[Z_AXIS]); + } else { + // calculate radius assuming we are at surface + // If Z is greater than 0 it will get calculated out for correct radius + // Use either A or B tower as they anchor x Cartesian axis and always have + // Radius distance to center in simplest set up. + unsigned long h = Printer::deltaDiagonalStepsSquaredB.l; + unsigned long bSteps = Printer::currentNonlinearPositionSteps[B_TOWER]; + // The correct Rod Radius would put us here at z==0 and B height is + // square root (rod length squared minus rod radius squared) + // Reverse that to get calculated Rod Radius given B height + h -= RMath::sqr(bSteps); + h = SQRT(h); + EEPROM::setRodRadius(h * Printer::invAxisStepsPerMM[Z_AXIS]); + } + } else + Com::printErrorFLN(PSTR("First move to touch at x,y=0,0 to auto-set radius.")); + } + } else { + bool tooBig = false; + if (com->hasX()) { + if (abs(com->X) <= 10) + EEPROM::setTowerXFloor(com->X + currentZmm + Printer::xMin); + else tooBig = true; + } + if (com->hasY()) { + if (abs(com->Y) <= 10) + EEPROM::setTowerYFloor(com->Y + currentZmm + Printer::yMin); + else tooBig = true; + } + if (com->hasZ()) { + if (abs(com->Z) <= 10) + EEPROM::setTowerZFloor(com->Z + currentZmm + Printer::zMin); + else tooBig = true; + } + if (tooBig) + Com::printErrorFLN(PSTR("Calibration movement is limited to 10mm.")); + } + // after adjusting zero, physical position is out of sync with memory position + // this could cause jerky movement or push head into print surface. + // moving gets back into safe zero'ed position with respect to newle set floor or Radius. + Printer::moveTo(IGNORE_COORDINATE, IGNORE_COORDINATE, 12.0, IGNORE_COORDINATE, IGNORE_COORDINATE); + break; + } + case 131: { // G131 Remove offset + float cx, cy, cz; + Printer::realPosition(cx, cy, cz); + float oldfeedrate = Printer::feedrate; + Printer::offsetX = 0; + Printer::offsetY = 0; + Printer::moveToReal(cx, cy, cz, IGNORE_COORDINATE, Printer::homingFeedrate[X_AXIS]); + Printer::feedrate = oldfeedrate; + Printer::updateCurrentPosition(); + } + break; + case 132: { // G132 Calibrate endstop offsets + // This has the probably unintended side effect of turning off leveling. + Printer::setAutolevelActive(false); // don't let transformations change result! + Printer::coordinateOffset[X_AXIS] = 0; + Printer::coordinateOffset[Y_AXIS] = 0; + Printer::coordinateOffset[Z_AXIS] = 0; + // I think this is coded incorrectly, as it depends on the start position of the + // of the hot end, and so should first move to x,y,z= 0,0,0, but as that may not + // be possible if the printer is not in the homes/zeroed state, the printer + // cannot safely move to 0 z coordinate without crashing into the print surface. + // so other than commenting, I'm not meddling. + // but you will always get different counts from different positions. + Printer::deltaMoveToTopEndstops(Printer::homingFeedrate[Z_AXIS]); + int32_t m = RMath::max(Printer::stepsRemainingAtXHit, RMath::max(Printer::stepsRemainingAtYHit, Printer::stepsRemainingAtZHit)); + int32_t offx = m - Printer::stepsRemainingAtXHit; + int32_t offy = m - Printer::stepsRemainingAtYHit; + int32_t offz = m - Printer::stepsRemainingAtZHit; + Com::printFLN(Com::tTower1, offx); + Com::printFLN(Com::tTower2, offy); + Com::printFLN(Com::tTower3, offz); +#if EEPROM_MODE != 0 + if(com->hasS() && com->S > 0) { + EEPROM::setDeltaTowerXOffsetSteps(offx); + EEPROM::setDeltaTowerYOffsetSteps(offy); + EEPROM::setDeltaTowerZOffsetSteps(offz); + } +#endif + PrintLine::moveRelativeDistanceInSteps(0, 0, -5 * Printer::axisStepsPerMM[Z_AXIS], 0, Printer::homingFeedrate[Z_AXIS], true, true); + Printer::homeAxis(true, true, true); + } + break; + case 133: { // G133 Measure steps to top + bool oldAuto = Printer::isAutolevelActive(); + Printer::setAutolevelActive(false); // don't let transformations change result! + Printer::currentPositionSteps[X_AXIS] = 0; + Printer::currentPositionSteps[Y_AXIS] = 0; + Printer::currentPositionSteps[Z_AXIS] = 0; + Printer::coordinateOffset[X_AXIS] = 0; + Printer::coordinateOffset[Y_AXIS] = 0; + Printer::coordinateOffset[Z_AXIS] = 0; + Printer::currentNonlinearPositionSteps[A_TOWER] = 0; + Printer::currentNonlinearPositionSteps[B_TOWER] = 0; + Printer::currentNonlinearPositionSteps[C_TOWER] = 0; + // similar to comment above, this will get a different answer from any different starting point + // so it is unclear how this is helpful. It must start at a well defined point. + Printer::deltaMoveToTopEndstops(Printer::homingFeedrate[Z_AXIS]); + int32_t offx = HOME_DISTANCE_STEPS - Printer::stepsRemainingAtXHit; + int32_t offy = HOME_DISTANCE_STEPS - Printer::stepsRemainingAtYHit; + int32_t offz = HOME_DISTANCE_STEPS - Printer::stepsRemainingAtZHit; + Com::printFLN(Com::tTower1, offx); + Com::printFLN(Com::tTower2, offy); + Com::printFLN(Com::tTower3, offz); + Printer::setAutolevelActive(oldAuto); + PrintLine::moveRelativeDistanceInSteps(0, 0, Printer::axisStepsPerMM[Z_AXIS] * -ENDSTOP_Z_BACK_MOVE, 0, Printer::homingFeedrate[Z_AXIS] / ENDSTOP_X_RETEST_REDUCTION_FACTOR, true, false); + Printer::homeAxis(true, true, true); + } + break; + case 135: // G135 + Com::printF(PSTR("CompDelta:"), Printer::currentNonlinearPositionSteps[A_TOWER]); + Com::printF(Com::tComma, Printer::currentNonlinearPositionSteps[B_TOWER]); + Com::printFLN(Com::tComma, Printer::currentNonlinearPositionSteps[C_TOWER]); +#ifdef DEBUG_REAL_POSITION + Com::printF(PSTR("RealDelta:"), Printer::realDeltaPositionSteps[A_TOWER]); + Com::printF(Com::tComma, Printer::realDeltaPositionSteps[B_TOWER]); + Com::printFLN(Com::tComma, Printer::realDeltaPositionSteps[C_TOWER]); +#endif + Printer::updateCurrentPosition(); + Com::printF(PSTR("PosFromSteps:")); + printCurrentPosition(); + break; + +#endif // DRIVE_SYSTEM +#if FEATURE_Z_PROBE && NUM_EXTRUDER > 1 + case 134: { + // - G134 Px Sx Zx - Calibrate nozzle height difference (need z probe in nozzle!) Px = reference extruder, Sx = only measure extrude x against reference, Zx = add to measured z distance for Sx for correction. + float z = com->hasZ() ? com->Z : 0; + int p = com->hasP() ? com->P : 0; + int s = com->hasS() ? com->S : -1; + int startExtruder = Extruder::current->id; + extruder[p].zOffset = 0; + float mins[NUM_EXTRUDER], maxs[NUM_EXTRUDER], avg[NUM_EXTRUDER]; + for(int i = 0; i < NUM_EXTRUDER; i++) { // silence unnecessary compiler warning + avg[i] = 0; + } + bool bigError = false; + +#if defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE + float actTemp[NUM_EXTRUDER]; + for(int i = 0; i < NUM_EXTRUDER; i++) + actTemp[i] = extruder[i].tempControl.targetTemperatureC; + Printer::moveToReal(IGNORE_COORDINATE, IGNORE_COORDINATE, ZHOME_HEAT_HEIGHT, IGNORE_COORDINATE, Printer::homingFeedrate[Z_AXIS]); + Commands::waitUntilEndOfAllMoves(); +#if ZHOME_HEAT_ALL + for(int i = 0; i < NUM_EXTRUDER; i++) { + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, false); + } + for(int i = 0; i < NUM_EXTRUDER; i++) { + if(extruder[i].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[i], static_cast(ZPROBE_MIN_TEMPERATURE)), i, false, true); + } +#else + if(extruder[Extruder::current->id].tempControl.currentTemperatureC < ZPROBE_MIN_TEMPERATURE) + Extruder::setTemperatureForExtruder(RMath::max(actTemp[Extruder::current->id], static_cast(ZPROBE_MIN_TEMPERATURE)), Extruder::current->id, false, true); +#endif +#endif + +#ifndef G134_REPETITIONS +#define G134_REPETITIONS 3 +#endif +#ifndef G134_PRECISION +#define G134_PRECISION 0.05 +#endif + Printer::startProbing(true); + for(int r = 0; r < G134_REPETITIONS && !bigError; r++) { + Extruder::selectExtruderById(p); + float refHeight = Printer::runZProbe(false, false); + if(refHeight == ILLEGAL_Z_PROBE) { + bigError = true; + break; + } + for(int i = 0; i < NUM_EXTRUDER && !bigError; i++) { + if(i == p) continue; + if(s >= 0 && i != s) continue; + extruder[i].zOffset = 0; + Extruder::selectExtruderById(i); + float height = Printer::runZProbe(false, false); + if(height == ILLEGAL_Z_PROBE) { + bigError = true; + break; + } + float off = (height - refHeight + z); + if(r == 0) { + avg[i] = mins[i] = maxs[i] = off; + } else { + avg[i] += off; + if(off < mins[i]) mins[i] = off; + if(off > maxs[i]) maxs[i] = off; + if(maxs[i] - mins[i] > G134_PRECISION) { + Com::printWarningFLN(PSTR("Deviation between measurements were too big, please repeat.")); + Com::printFLN(PSTR("Z Offset not computed due to errors")); + bigError = true; + break; + } + } + } + } + if(!bigError) { + for(int i = 0; i < NUM_EXTRUDER; i++) { + if(s >= 0 && i != s) continue; + extruder[i].zOffset = avg[i] * Printer::axisStepsPerMM[Z_AXIS] / G134_REPETITIONS; + } +#if EEPROM_MODE != 0 + EEPROM::storeDataIntoEEPROM(0); +#endif + Com::printFLN(PSTR("Z Offset stored")); + } else { + Com::printFLN(PSTR("Z Offset not computed due to errors")); + } + Extruder::selectExtruderById(startExtruder); + Printer::finishProbing(); +#if defined(Z_PROBE_MIN_TEMPERATURE) && Z_PROBE_MIN_TEMPERATURE +#if ZHOME_HEAT_ALL + for(int i = 0; i < NUM_EXTRUDER; i++) + Extruder::setTemperatureForExtruder(actTemp[i], i, false, false); + for(int i = 0; i < NUM_EXTRUDER; i++) + Extruder::setTemperatureForExtruder(actTemp[i], i, false, actTemp[i] > MAX_ROOM_TEMPERATURE); +#else + Extruder::setTemperatureForExtruder(actTemp[Extruder::current->id], Extruder::current->id, false, actTemp[Extruder::current->id] > MAX_ROOM_TEMPERATURE); +#endif +#endif + } + break; +#endif +#if defined(NUM_MOTOR_DRIVERS) && NUM_MOTOR_DRIVERS > 0 + case 201: + commandG201(*com); + break; + case 202: + commandG202(*com); + break; + case 203: + commandG203(*com); + break; + case 204: + commandG204(*com); + break; + case 205: + commandG205(*com); + break; +#endif // defined + default: + if(Printer::debugErrors()) { + Com::printF(Com::tUnknownCommand); + com->printCommand(); + } + } + previousMillisCmd = HAL::timeInMilliseconds(); +} +/** +\brief Execute the G command stored in com. +*/ +void Commands::processMCode(GCode *com) { + if(EVENT_UNHANDLED_M_CODE(com)) + return; + switch( com->M ) { + case 3: // Spindle/laser on +#if defined(SUPPORT_LASER) && SUPPORT_LASER + if(Printer::mode == PRINTER_MODE_LASER) { + if(com->hasS()) + LaserDriver::intensity = constrain(com->S, 0, LASER_PWM_MAX); + LaserDriver::laserOn = true; + Com::printFLN(PSTR("LaserOn:"), (int)LaserDriver::intensity); + } +#endif // defined +#if defined(SUPPORT_CNC) && SUPPORT_CNC + if(Printer::mode == PRINTER_MODE_CNC) { + waitUntilEndOfAllMoves(); + CNCDriver::spindleOnCW(com->hasS() ? com->S : CNC_RPM_MAX); + } +#endif // defined + break; + case 4: // Spindle CCW +#if defined(SUPPORT_CNC) && SUPPORT_CNC + if(Printer::mode == PRINTER_MODE_CNC) { + waitUntilEndOfAllMoves(); + CNCDriver::spindleOnCCW(com->hasS() ? com->S : CNC_RPM_MAX); + } +#endif // defined + break; + case 5: // Spindle/laser off +#if defined(SUPPORT_LASER) && SUPPORT_LASER + if(Printer::mode == PRINTER_MODE_LASER) { + LaserDriver::laserOn = false; + } +#endif // defined +#if defined(SUPPORT_CNC) && SUPPORT_CNC + if(Printer::mode == PRINTER_MODE_CNC) { + waitUntilEndOfAllMoves(); + CNCDriver::spindleOff(); + } +#endif // defined + break; +#if SDSUPPORT + case 20: // M20 - list SD card +#if JSON_OUTPUT + if (com->hasString() && com->text[1] == '2') { // " S2 P/folder" + if (com->text[3] == 'P') { + sd.lsJSON(com->text + 4); + } + } else sd.ls(); +#else + sd.ls(); +#endif + break; + case 21: // M21 - init SD card + sd.mount(); + break; + case 22: //M22 - release SD card + sd.unmount(); + break; + case 23: //M23 - Select file + if(com->hasString()) { + sd.fat.chdir(); + sd.selectFile(com->text); + } + break; + case 24: //M24 - Start SD print + sd.startPrint(); + break; + case 25: //M25 - Pause SD print + sd.pausePrint(); + break; + case 26: //M26 - Set SD index + if(com->hasS()) + sd.setIndex(com->S); + break; + case 27: //M27 - Get SD status + sd.printStatus(); + break; + case 28: //M28 - Start SD write + if(com->hasString()) + sd.startWrite(com->text); + break; + case 29: //M29 - Stop SD write + //processed in write to file routine above + //savetosd = false; + break; + case 30: // M30 filename - Delete file + if(com->hasString()) { + sd.fat.chdir(); + sd.deleteFile(com->text); + } + break; + case 32: // M32 directoryname + if(com->hasString()) { + sd.fat.chdir(); + sd.makeDirectory(com->text); + } + break; +#endif +#if JSON_OUTPUT && SDSUPPORT + case 36: // M36 JSON File Info + if (com->hasString()) { + sd.JSONFileInfo(com->text); + } + break; +#endif + case 42: //M42 -Change pin status via gcode + if (com->hasP()) { + int pin_number = com->P; + for(uint8_t i = 0; i < (uint8_t)sizeof(sensitive_pins); i++) { + if (pgm_read_byte(&sensitive_pins[i]) == pin_number) { + pin_number = -1; + break; + } + } + if (pin_number > -1) { + if(com->hasS()) { + if(com->S >= 0 && com->S <= 255) { + pinMode(pin_number, OUTPUT); + digitalWrite(pin_number, com->S); + analogWrite(pin_number, com->S); + Com::printF(Com::tSetOutputSpace, pin_number); + Com::printFLN(Com::tSpaceToSpace, (int)com->S); + } else + Com::printErrorFLN(PSTR("Illegal S value for M42")); + } else { + pinMode(pin_number, INPUT_PULLUP); + Com::printF(Com::tSpaceToSpace, pin_number); + Com::printFLN(Com::tSpaceIsSpace, digitalRead(pin_number)); + } + } else { + Com::printErrorFLN(PSTR("Pin can not be set by M42, is in sensitive pins! ")); + } + } + break; + case 80: // M80 - ATX Power On +#if PS_ON_PIN > -1 + Commands::waitUntilEndOfAllMoves(); + previousMillisCmd = HAL::timeInMilliseconds(); + SET_OUTPUT(PS_ON_PIN); //GND + Printer::setPowerOn(true); + WRITE(PS_ON_PIN, (POWER_INVERTING ? HIGH : LOW)); +#endif + break; + case 81: // M81 - ATX Power Off +#if PS_ON_PIN > -1 + Commands::waitUntilEndOfAllMoves(); + SET_OUTPUT(PS_ON_PIN); //GND + Printer::setPowerOn(false); + WRITE(PS_ON_PIN, (POWER_INVERTING ? LOW : HIGH)); +#endif + break; + case 82: // M82 + Printer::relativeExtruderCoordinateMode = false; + break; + case 83: // M83 + Printer::relativeExtruderCoordinateMode = true; + break; + case 18: // M18 is to disable named axis + { + Commands::waitUntilEndOfAllMoves(); + bool named = false; + if(com->hasX()) { + named = true; + Printer::disableXStepper(); + } + if(com->hasY()) { + named = true; + Printer::disableYStepper(); + } + if(com->hasZ()) { + named = true; + Printer::disableZStepper(); + } + if(com->hasE()) { + named = true; + Extruder::disableCurrentExtruderMotor(); + } + if(!named) { + Printer::disableXStepper(); + Printer::disableYStepper(); + Printer::disableZStepper(); + Extruder::disableAllExtruderMotors(); + } + } + break; + case 84: // M84 + if(com->hasS()) { + stepperInactiveTime = com->S * 1000; + } else { + Commands::waitUntilEndOfAllMoves(); + Printer::kill(true); + } + break; + case 85: // M85 + if(com->hasS()) + maxInactiveTime = (int32_t)com->S * 1000; + else + maxInactiveTime = 0; + break; + case 92: // M92 + if(com->hasX()) Printer::axisStepsPerMM[X_AXIS] = com->X; + if(com->hasY()) Printer::axisStepsPerMM[Y_AXIS] = com->Y; + if(com->hasZ()) Printer::axisStepsPerMM[Z_AXIS] = com->Z; + Printer::updateDerivedParameter(); + if(com->hasE()) { + Extruder::current->stepsPerMM = com->E; + Extruder::selectExtruderById(Extruder::current->id); + } + break; + case 99: { // M99 S