docs: Add tips for porting to new kinematics to Code_Overview.md

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2018-02-17 13:39:37 -05:00
parent c128a9dfd5
commit 5e8aaed41f
2 changed files with 87 additions and 0 deletions

View File

@ -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 specifies the movements and their timings. The remaining parts of the
processing is mostly just communication and plumbing. 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 Time
==== ====

View File

@ -48,6 +48,7 @@ class PrinterStepper:
self.mcu_stepper.setup_dir_pin(dir_pin_params) self.mcu_stepper.setup_dir_pin(dir_pin_params)
self.step_dist = config.getfloat('step_distance', above=0.) self.step_dist = config.getfloat('step_distance', above=0.)
self.mcu_stepper.setup_step_distance(self.step_dist) 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_const = self.mcu_stepper.step_const
self.step_delta = self.mcu_stepper.step_delta self.step_delta = self.mcu_stepper.step_delta
self.enable = lookup_enable_pin(printer, config.get('enable_pin', None)) self.enable = lookup_enable_pin(printer, config.get('enable_pin', None))