From 3ac9fc6e633ff76aa8ecf47671b8e63d53deca1c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 19 Jun 2018 00:50:59 -0400 Subject: [PATCH] docs: Update documentation with iterative solver changes Signed-off-by: Kevin O'Connor --- docs/Code_Overview.md | 134 ++++++---------- docs/Kinematics.md | 104 ++++++------- docs/img/delta-tower.svg | 273 --------------------------------- docs/img/delta-tower.svg.png | Bin 8371 -> 0 bytes docs/img/virtual-tower.svg | 241 ----------------------------- docs/img/virtual-tower.svg.png | Bin 5865 -> 0 bytes docs/img/xy+z-tower.svg | 241 ----------------------------- docs/img/xy+z-tower.svg.png | Bin 6653 -> 0 bytes 8 files changed, 101 insertions(+), 892 deletions(-) delete mode 100644 docs/img/delta-tower.svg delete mode 100644 docs/img/delta-tower.svg.png delete mode 100644 docs/img/virtual-tower.svg delete mode 100644 docs/img/virtual-tower.svg.png delete mode 100644 docs/img/xy+z-tower.svg delete mode 100644 docs/img/xy+z-tower.svg.png diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index 7a0df7ca..c038f49d 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -163,28 +163,37 @@ provides further information on the mechanics of moves. kinematic class is given a chance to audit the move (`ToolHead.move() -> kin.check_move()`) before it goes on the look-ahead queue, but once the move arrives in *kin*.move() the - kinematic class is required to handle the move as specified. The - kinematic classes translate the three parts of each move - (acceleration, constant "cruising" velocity, and deceleration) to - the associated movement on each stepper. Note that the extruder is - handled in its own kinematic class. Since the Move() class specifies - the exact movement time and since step pulses are sent to the - micro-controller with specific timing, stepper movements produced by - the extruder class will be in sync with head movement even though - the code is kept separate. + kinematic class is required to handle the move as specified. Note + that the extruder is handled in its own kinematic class. Since the + Move() class specifies the exact movement time and since step pulses + are sent to the micro-controller with specific timing, stepper + movements produced by the extruder class will be in sync with head + movement even though the code is kept separate. -* For efficiency reasons, the stepper pulse times are generated in C - code. The code flow is: `kin.move() -> MCU_Stepper.step_const() -> - stepcompress_push_const()`, or for delta kinematics: - `DeltaKinematics.move() -> MCU_Stepper.step_delta() -> - stepcompress_push_delta()`. The MCU_Stepper code just performs unit - and axis transformation (millimeters to step distances), and calls - the C code. The C code calculates the stepper step times for each - movement and fills an array (struct stepcompress.queue) with the - corresponding micro-controller clock counter times for every - step. Here the "micro-controller clock counter" value directly - corresponds to the micro-controller's hardware counter - it is - relative to when the micro-controller was last powered up. +* Klipper uses an + [iterative solver](https://en.wikipedia.org/wiki/Root-finding_algorithm) + to generate the step times for each stepper. For efficiency reasons, + the stepper pulse times are generated in C code. The code flow is: + `kin.move() -> MCU_Stepper.step_itersolve() -> + itersolve_gen_steps()` (in klippy/chelper/itersolve.c). The goal of + the iterative solver is to find step times given a formula that + calculates a stepper position from a given time in a move. This is + done by repeatedly "guessing" various times until the stepper + position formula returns the desired position of the next step on + the stepper. The feedback produced from each guess is used to + improve future guesses so that the process rapidly converges to the + desired time. The kinematic stepper position formulas are located in + the klippy/chelper/ directory (eg, kin_cart.c, kin_corexy.c, + kin_delta.c, kin_extruder.c). + +* After the iterative solver calculates the step times they are added + to an array: `itersolve_gen_steps() -> queue_append()` (in + klippy/chelper/stepcompress.c). The array (struct + stepcompress.queue) stores the corresponding micro-controller clock + counter times for every step. Here the "micro-controller clock + counter" value directly corresponds to the micro-controller's + hardware counter - it is relative to when the micro-controller was + last powered up. * The next major step is to compress the steps: `stepcompress_flush() -> compress_bisect_add()` (in klippy/chelper/stepcompress.c). This @@ -293,8 +302,7 @@ This section provides some tips on adding support to Klipper for additional types of printer kinematics. This type of activity requires excellent understanding of the math formulas for the target kinematics. It also requires software development skills - though one -should only need to update the host software (which is written in -Python). +should only need to update the host software. Useful steps: 1. Start by studying the @@ -304,74 +312,30 @@ Useful steps: and delta.py. The kinematic classes are tasked with converting a move in cartesian coordinates to the movement on each stepper. One should be able to copy one of these files as a starting point. -3. Implement the `get_postion()` method in the new kinematics - class. This method converts the current stepper position of each - stepper axis (stored in millimeters) to a position in cartesian - space (also in millimeters). -4. Implement the `set_postion()` method. This is the inverse of - get_position() - it sets each axis position (in millimeters) given - a position in cartesian coordinates. -5. Implement the `move()` method. The goal of the move() method is to - convert a move defined in cartesian space to a series of stepper - step times that implement the requested movement. - * The `move()` method is passed a "print_time" parameter (which - stores a time in seconds) and a "move" class instance that fully - defines the movement. The goal is to repeatedly invoke the - `stepper.step()` method with the time (relative to print_time) - that each stepper should step at to obtain the desired motion. - * One "trick" to help with the movement calculations is to imagine - there is a physical rail between `move.start_pos` and - `move.end_pos` that confines the print head so that it can only - move along this straight line of motion. Then, if the head is - confined to that imaginary rail, the head is at `move.start_pos`, - only one stepper is enabled (all other steppers can move freely), - and the given stepper is stepped a single step, then one can - imagine that the head will move along the line of movement some - distance. Determine the formula converting this step distance to - distance along the line of movement. Once one has the distance - along the line of movement, one can figure out the time that the - head should be at that position (using the standard formulas for - velocity and acceleration). This time is the ideal step time for - the given stepper and it can be passed to the `stepper.step()` - method. - * The `stepper.step()` method must always be called with an - increasing time for a given stepper (steps must be scheduled in - the order they are to be executed). A common error during - kinematic development is to receive an "Internal error in - stepcompress" failure - this is generally due to the step() - method being invoked with a time earlier than the last scheduled - step. For example, if the last step in move1 is scheduled at a - time greater than the first step in move2 it will generally - result in the above error. - * Fractional steps. Be aware that a move request is given in - cartesian space and it is not confined to discreet - locations. Thus a move's start and end locations may translate to - a location on a stepper axis that is between two steps (a - fractional step). The code must handle this. The preferred - approach is to schedule the next step at the time a move would - position the stepper axis at least half way towards the next - possible step location. Incorrect handling of fractional steps is - a common cause of "Internal error in stepcompress" failures. -6. Other methods. The `home()`, `check_move()`, and other methods +3. Implement the C stepper kinematic position functions for each + stepper if they are not already available (see kin_cart.c, + kin_corexy.c, and kin_delta.c in klippy/chelper/). The function + should call `move_get_coord()` to convert a given move time (in + seconds) to a cartesian coordinate (in millimeters), and then + calculate the desired stepper position (in millimeters) from that + cartesian coordinate. +4. Implement the `set_position()` method in the python code. This also + calculates the desired stepper positions given a cartesian + coordinate. +5. Implement the `get_position()` method in the new kinematics + class. This method is the inverse of set_position(). It does not + need to be efficient as it is typically only called during homing + and probing operations. +6. Implement the `move()` method. This method generally invokes the + iterative solver for each stepper. +7. Other methods. The `home()`, `check_move()`, and other methods should also be implemented. However, at the start of development one can use empty code here. -7. Implement test cases. Create a g-code file with a series of moves +8. Implement test cases. Create a g-code file with a series of moves that can test important cases for the given kinematics. Follow the [debugging documentation](Debugging.md) to convert this g-code file to micro-controller commands. This is useful to exercise corner cases and to check for regressions. -8. Optimize if needed. One may notice that the existing kinematic - classes do not call `stepper.step()`. This is purely an - optimization - the inner loop of the kinematic calculations were - moved to C to reduce load on the host cpu. All of the existing - kinematic classes started development using `stepper.step()` and - then were later optimized. The g-code to mcu command translation - (described in the previous step) is a useful tool during - optimization - if a code change is purely an optimization then it - should not impact the resulting text representation of the mcu - commands (though minor changes in output due to floating point - rounding are possible). So, one can use this system to detect - regressions. Time ==== diff --git a/docs/Kinematics.md b/docs/Kinematics.md index eb0cb94c..7e61b3d2 100644 --- a/docs/Kinematics.md +++ b/docs/Kinematics.md @@ -139,15 +139,32 @@ tracked in millimeters, seconds, and in cartesian coordinate space. It's the task of the kinematic classes to convert from this generic coordinate system to the hardware specifics of the particular printer. -In general, the code determines each step time by first calculating -where along the line of movement the head would be if a step is -taken. It then calculates what time the head should be at that -position. Determining the time along the line of movement can be done -using the formulas for constant acceleration and constant velocity: +Klipper uses an +[iterative solver](https://en.wikipedia.org/wiki/Root-finding_algorithm) +to generate the step times for each stepper. The code contains the +formulas to calculate the ideal cartesian coordinates of the head at +each moment in time, and it has the kinematic formulas to calculate +the ideal stepper positions based on those cartesian coordinates. With +these formulas, Klipper can determine the ideal time that the stepper +should be at each step position. The given steps are then scheduled at +these calculated times. +The key formula to determine how far a move should travel under +constant acceleration is: ``` -time = sqrt(2*distance/accel + (start_velocity/accel)^2) - start_velocity/accel -time = distance/cruise_velocity +move_distance = (start_velocity + .5 * accel * move_time) * move_time +``` +and the key formula for movement with constant velocity is: +``` +move_distance = cruise_velocity * move_time +``` + +The key formulas for determining the cartesian coordinate of a move +given a move distance is: +``` +cartesian_x_position = start_x + move_distance * total_x_movement / total_movement +cartesian_y_position = start_y + move_distance * total_y_movement / total_movement +cartesian_z_position = start_z + move_distance * total_z_movement / total_movement ``` Cartesian Robots @@ -157,54 +174,35 @@ Generating steps for cartesian printers is the simplest case. The movement on each axis is directly related to the movement in cartesian space. +Key formulas: +``` +stepper_x_position = cartesian_x_position +stepper_y_position = cartesian_y_position +stepper_z_position = cartesian_z_position +``` + +CoreXY Robots +---------------- + +Generating steps on a CoreXY machine is only a little more complex +than basic cartesian robots. The key formulas are: +``` +stepper_a_position = cartesian_x_position + cartesian_y_position +stepper_b_position = cartesian_x_position - cartesian_y_position +stepper_z_position = cartesian_z_position +``` + Delta Robots ------------ -To generate step times on Delta printers it is necessary to correlate -the movement in cartesian space with the movement on each stepper -tower. - -To simplify the math, for each stepper tower, the code calculates the -location of a "virtual tower" that is along the line of movement. -This virtual tower is chosen at the point where the line of movement -(extended infinitely in both directions) would be closest to the -actual tower. - -![delta-tower](img/delta-tower.svg.png) - -It is then possible to calculate where the head will be along the line -of movement after each step is taken on the virtual tower. - -![virtual-tower](img/virtual-tower.svg.png) - -The key formula is Pythagoras's theorem: +Step generation on a delta robot is based on Pythagoras's theorem: ``` -distance_to_tower^2 = arm_length^2 - tower_height^2 +stepper_position = (sqrt(arm_length^2 + - (cartesian_x_position - tower_x_position)^2 + - (cartesian_y_position - tower_y_position)^2) + + cartesian_z_position) ``` -One complexity is that if the print head passes the virtual tower -location then the stepper direction must be reversed. In this case -forward steps will be taken at the start of the move and reverse steps -will be taken at the end of the move. - -### Delta movements beyond simple XY plane ### - -Movement calculation is more complicated if a single move contains -both XY movement and Z movement. These moves are rare, but they must -still be handled correctly. A virtual tower along the line of movement -is still calculated, but in this case the tower is not at a 90 degree -angle relative to the line of movement: - -![xy+z-tower](img/xy+z-tower.svg.png) - -The code continues to calculate step times using the same general -scheme as delta moves within an XY plane, but the slope of the tower -must also be used in the calculations. - -Should the move contain only Z movement (ie, no XY movement at all) -then the same math is used - just in this case the tower is parallel -to the line of movement. - ### Stepper motor acceleration limits ### With delta kinematics it is possible for a move that is accelerating @@ -236,8 +234,10 @@ independently from the step time calculations of the print head movement. Basic extruder movement is simple to calculate. The step time -generation uses the same constant acceleration and constant velocity -formulas that cartesian robots use. +generation uses the same formulas that cartesian robots use: +``` +stepper_position = requested_e_position +``` ### Pressure advance ### @@ -264,7 +264,7 @@ through the nozzle orifice (as in key idea is that the relationship between filament, pressure, and flow rate can be modeled using a linear coefficient: ``` -extra_filament = pressure_advance_coefficient * extruder_velocity +stepper_position = requested_e_position + pressure_advance_coefficient * nominal_extruder_velocity ``` See the [pressure advance](Pressure_Advance.md) document for diff --git a/docs/img/delta-tower.svg b/docs/img/delta-tower.svg deleted file mode 100644 index 98dc0951..00000000 --- a/docs/img/delta-tower.svg +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - steppertower - line of movement - move - - - - - - virtual towerlocation - - - diff --git a/docs/img/delta-tower.svg.png b/docs/img/delta-tower.svg.png deleted file mode 100644 index 9ab53ea16db4becbbda609b131105e07062a60fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8371 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 diff --git a/docs/img/virtual-tower.svg b/docs/img/virtual-tower.svg deleted file mode 100644 index 9ba254d5..00000000 --- a/docs/img/virtual-tower.svg +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - virtual tower - line of movement - move - - - virtual arm - - - - diff --git a/docs/img/virtual-tower.svg.png b/docs/img/virtual-tower.svg.png deleted file mode 100644 index 7856192c73a049f6b5f854a3c6989ad8dddcfb5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5865 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 b{(?1;xQC7<7OB9sG{{ibOs7uU@yY)HFa`$d diff --git a/docs/img/xy+z-tower.svg b/docs/img/xy+z-tower.svg deleted file mode 100644 index b1f3101f..00000000 --- a/docs/img/xy+z-tower.svg +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - virtual tower - line of movement - move - - - virtual arm - - - - diff --git a/docs/img/xy+z-tower.svg.png b/docs/img/xy+z-tower.svg.png deleted file mode 100644 index 96c5480884f07766ce26bf73f68f26b46d10b6d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6653 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 Nn>Q?>b=TdW{tw0B5>@~J