From 5e8aaed41f59705a0e14233a567267191c8878f5 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 17 Feb 2018 13:39:37 -0500 Subject: [PATCH] docs: Add tips for porting to new kinematics to Code_Overview.md Signed-off-by: Kevin O'Connor --- docs/Code_Overview.md | 86 +++++++++++++++++++++++++++++++++++++++++++ klippy/stepper.py | 1 + 2 files changed, 87 insertions(+) diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index 9acd29d3..d31d8213 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -212,6 +212,92 @@ ToolHead and kinematic classes. It's this part of the code which specifies the movements and their timings. The remaining parts of the processing is mostly just communication and plumbing. +Adding new kinematics +===================== + +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). + +Useful steps: +1. Start by studying the [above section](#code-flow-of-a-move-command) + and the [Kinematics document](Kinematics.md). +2. Review the existing kinematic classes in cartesian.py, corexy.py, + 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 + 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 + 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/klippy/stepper.py b/klippy/stepper.py index 682b306b..46bd0b3a 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -48,6 +48,7 @@ class PrinterStepper: self.mcu_stepper.setup_dir_pin(dir_pin_params) self.step_dist = config.getfloat('step_distance', above=0.) self.mcu_stepper.setup_step_distance(self.step_dist) + self.step = self.mcu_stepper.step self.step_const = self.mcu_stepper.step_const self.step_delta = self.mcu_stepper.step_delta self.enable = lookup_enable_pin(printer, config.get('enable_pin', None))