mirror of
https://github.com/QIDITECH/klipper.git
synced 2026-01-30 15:38:42 +03:00
klipper update
This commit is contained in:
581
docs/Code_Overview.md
Normal file
581
docs/Code_Overview.md
Normal file
@@ -0,0 +1,581 @@
|
||||
# Code overview
|
||||
|
||||
This document describes the overall code layout and major code flow of
|
||||
Klipper.
|
||||
|
||||
## Directory Layout
|
||||
|
||||
The **src/** directory contains the C source for the micro-controller
|
||||
code. The **src/atsam/**, **src/atsamd/**, **src/avr/**,
|
||||
**src/linux/**, **src/lpc176x/**, **src/pru/**, and **src/stm32/**
|
||||
directories contain architecture specific micro-controller code. The
|
||||
**src/simulator/** contains code stubs that allow the micro-controller
|
||||
to be test compiled on other architectures. The **src/generic/**
|
||||
directory contains helper code that may be useful across different
|
||||
architectures. The build arranges for includes of "board/somefile.h"
|
||||
to first look in the current architecture directory (eg,
|
||||
src/avr/somefile.h) and then in the generic directory (eg,
|
||||
src/generic/somefile.h).
|
||||
|
||||
The **klippy/** directory contains the host software. Most of the host
|
||||
software is written in Python, however the **klippy/chelper/**
|
||||
directory contains some C code helpers. The **klippy/kinematics/**
|
||||
directory contains the robot kinematics code. The **klippy/extras/**
|
||||
directory contains the host code extensible "modules".
|
||||
|
||||
The **lib/** directory contains external 3rd-party library code that
|
||||
is necessary to build some targets.
|
||||
|
||||
The **config/** directory contains example printer configuration
|
||||
files.
|
||||
|
||||
The **scripts/** directory contains build-time scripts useful for
|
||||
compiling the micro-controller code.
|
||||
|
||||
The **test/** directory contains automated test cases.
|
||||
|
||||
During compilation, the build may create an **out/** directory. This
|
||||
contains temporary build time objects. The final micro-controller
|
||||
object that is built is **out/klipper.elf.hex** on AVR and
|
||||
**out/klipper.bin** on ARM.
|
||||
|
||||
## Micro-controller code flow
|
||||
|
||||
Execution of the micro-controller code starts in architecture specific
|
||||
code (eg, **src/avr/main.c**) which ultimately calls sched_main()
|
||||
located in **src/sched.c**. The sched_main() code starts by running
|
||||
all functions that have been tagged with the DECL_INIT() macro. It
|
||||
then goes on to repeatedly run all functions tagged with the
|
||||
DECL_TASK() macro.
|
||||
|
||||
One of the main task functions is command_dispatch() located in
|
||||
**src/command.c**. This function is called from the board specific
|
||||
input/output code (eg, **src/avr/serial.c**,
|
||||
**src/generic/serial_irq.c**) and it runs the command functions
|
||||
associated with the commands found in the input stream. Command
|
||||
functions are declared using the DECL_COMMAND() macro (see the
|
||||
[protocol](Protocol.md) document for more information).
|
||||
|
||||
Task, init, and command functions always run with interrupts enabled
|
||||
(however, they can temporarily disable interrupts if needed). These
|
||||
functions should avoid long pauses, delays, or do work that lasts a
|
||||
significant time. (Long delays in these "task" functions result in
|
||||
scheduling jitter for other "tasks" - delays over 100us may become
|
||||
noticeable, delays over 500us may result in command retransmissions,
|
||||
delays over 100ms may result in watchdog reboots.) These functions
|
||||
schedule work at specific times by scheduling timers.
|
||||
|
||||
Timer functions are scheduled by calling sched_add_timer() (located in
|
||||
**src/sched.c**). The scheduler code will arrange for the given
|
||||
function to be called at the requested clock time. Timer interrupts
|
||||
are initially handled in an architecture specific interrupt handler
|
||||
(eg, **src/avr/timer.c**) which calls sched_timer_dispatch() located
|
||||
in **src/sched.c**. The timer interrupt leads to execution of schedule
|
||||
timer functions. Timer functions always run with interrupts disabled.
|
||||
The timer functions should always complete within a few micro-seconds.
|
||||
At completion of the timer event, the function may choose to
|
||||
reschedule itself.
|
||||
|
||||
In the event an error is detected the code can invoke shutdown() (a
|
||||
macro which calls sched_shutdown() located in **src/sched.c**).
|
||||
Invoking shutdown() causes all functions tagged with the
|
||||
DECL_SHUTDOWN() macro to be run. Shutdown functions always run with
|
||||
interrupts disabled.
|
||||
|
||||
Much of the functionality of the micro-controller involves working
|
||||
with General-Purpose Input/Output pins (GPIO). In order to abstract
|
||||
the low-level architecture specific code from the high-level task
|
||||
code, all GPIO events are implemented in architecture specific
|
||||
wrappers (eg, **src/avr/gpio.c**). The code is compiled with gcc's
|
||||
"-flto -fwhole-program" optimization which does an excellent job of
|
||||
inlining functions across compilation units, so most of these tiny
|
||||
gpio functions are inlined into their callers, and there is no
|
||||
run-time cost to using them.
|
||||
|
||||
## Klippy code overview
|
||||
|
||||
The host code (Klippy) is intended to run on a low-cost computer (such
|
||||
as a Raspberry Pi) paired with the micro-controller. The code is
|
||||
primarily written in Python, however it does use CFFI to implement
|
||||
some functionality in C code.
|
||||
|
||||
Initial execution starts in **klippy/klippy.py**. This reads the
|
||||
command-line arguments, opens the printer config file, instantiates
|
||||
the main printer objects, and starts the serial connection. The main
|
||||
execution of G-code commands is in the process_commands() method in
|
||||
**klippy/gcode.py**. This code translates the G-code commands into
|
||||
printer object calls, which frequently translate the actions to
|
||||
commands to be executed on the micro-controller (as declared via the
|
||||
DECL_COMMAND macro in the micro-controller code).
|
||||
|
||||
There are four threads in the Klippy host code. The main thread
|
||||
handles incoming gcode commands. A second thread (which resides
|
||||
entirely in the **klippy/chelper/serialqueue.c** C code) handles
|
||||
low-level IO with the serial port. The third thread is used to process
|
||||
response messages from the micro-controller in the Python code (see
|
||||
**klippy/serialhdl.py**). The fourth thread writes debug messages to
|
||||
the log (see **klippy/queuelogger.py**) so that the other threads
|
||||
never block on log writes.
|
||||
|
||||
## Code flow of a move command
|
||||
|
||||
A typical printer movement starts when a "G1" command is sent to the
|
||||
Klippy host and it completes when the corresponding step pulses are
|
||||
produced on the micro-controller. This section outlines the code flow
|
||||
of a typical move command. The [kinematics](Kinematics.md) document
|
||||
provides further information on the mechanics of moves.
|
||||
|
||||
* Processing for a move command starts in gcode.py. The goal of
|
||||
gcode.py is to translate G-code into internal calls. A G1 command
|
||||
will invoke cmd_G1() in klippy/extras/gcode_move.py. The
|
||||
gcode_move.py code handles changes in origin (eg, G92), changes in
|
||||
relative vs absolute positions (eg, G90), and unit changes (eg,
|
||||
F6000=100mm/s). The code path for a move is: `_process_data() ->
|
||||
_process_commands() -> cmd_G1()`. Ultimately the ToolHead class is
|
||||
invoked to execute the actual request: `cmd_G1() -> ToolHead.move()`
|
||||
|
||||
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
||||
the timing of printing actions. The main codepath for a move is:
|
||||
`ToolHead.move() -> MoveQueue.add_move() -> MoveQueue.flush() ->
|
||||
Move.set_junction() -> ToolHead._process_moves()`.
|
||||
* ToolHead.move() creates a Move() object with the parameters of the
|
||||
move (in cartesian space and in units of seconds and millimeters).
|
||||
* The kinematics class is given the opportunity to audit each move
|
||||
(`ToolHead.move() -> kin.check_move()`). The kinematics classes are
|
||||
located in the klippy/kinematics/ directory. The check_move() code
|
||||
may raise an error if the move is not valid. If check_move()
|
||||
completes successfully then the underlying kinematics must be able
|
||||
to handle the move.
|
||||
* MoveQueue.add_move() places the move object on the "look-ahead"
|
||||
queue.
|
||||
* MoveQueue.flush() determines the start and end velocities of each
|
||||
move.
|
||||
* Move.set_junction() implements the "trapezoid generator" on a
|
||||
move. The "trapezoid generator" breaks every move into three parts:
|
||||
a constant acceleration phase, followed by a constant velocity
|
||||
phase, followed by a constant deceleration phase. Every move
|
||||
contains these three phases in this order, but some phases may be of
|
||||
zero duration.
|
||||
* When ToolHead._process_moves() is called, everything about the
|
||||
move is known - its start location, its end location, its
|
||||
acceleration, its start/cruising/end velocity, and distance traveled
|
||||
during acceleration/cruising/deceleration. All the information is
|
||||
stored in the Move() class and is in cartesian space in units of
|
||||
millimeters and seconds.
|
||||
|
||||
* 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 moves are first
|
||||
placed on a "trapezoid motion queue": `ToolHead._process_moves() ->
|
||||
trapq_append()` (in klippy/chelper/trapq.c). The step times are then
|
||||
generated: `ToolHead._process_moves() ->
|
||||
ToolHead._update_move_time() -> MCU_Stepper.generate_steps() ->
|
||||
itersolve_generate_steps() -> itersolve_gen_steps_range()` (in
|
||||
klippy/chelper/itersolve.c). The goal of the iterative solver is to
|
||||
find step times given a function that calculates a stepper position
|
||||
from a time. 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).
|
||||
|
||||
* Note that the extruder is handled in its own kinematic class:
|
||||
`ToolHead._process_moves() -> PrinterExtruder.move()`. 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.
|
||||
|
||||
* After the iterative solver calculates the step times they are added
|
||||
to an array: `itersolve_gen_steps_range() -> stepcompress_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
|
||||
code generates and encodes a series of micro-controller "queue_step"
|
||||
commands that correspond to the list of stepper step times built in
|
||||
the previous stage. These "queue_step" commands are then queued,
|
||||
prioritized, and sent to the micro-controller (via
|
||||
stepcompress.c:steppersync and serialqueue.c:serialqueue).
|
||||
|
||||
* Processing of the queue_step commands on the micro-controller starts
|
||||
in src/command.c which parses the command and calls
|
||||
`command_queue_step()`. The command_queue_step() code (in
|
||||
src/stepper.c) just appends the parameters of each queue_step
|
||||
command to a per stepper queue. Under normal operation the
|
||||
queue_step command is parsed and queued at least 100ms before the
|
||||
time of its first step. Finally, the generation of stepper events is
|
||||
done in `stepper_event()`. It's called from the hardware timer
|
||||
interrupt at the scheduled time of the first step. The
|
||||
stepper_event() code generates a step pulse and then reschedules
|
||||
itself to run at the time of the next step pulse for the given
|
||||
queue_step parameters. The parameters for each queue_step command
|
||||
are "interval", "count", and "add". At a high-level, stepper_event()
|
||||
runs the following, 'count' times: `do_step(); next_wake_time =
|
||||
last_wake_time + interval; interval += add;`
|
||||
|
||||
The above may seem like a lot of complexity to execute a movement.
|
||||
However, the only really interesting parts are in the 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 a host module
|
||||
|
||||
The Klippy host code has a dynamic module loading capability. If a
|
||||
config section named "[my_module]" is found in the printer config file
|
||||
then the software will automatically attempt to load the python module
|
||||
klippy/extras/my_module.py . This module system is the preferred
|
||||
method for adding new functionality to Klipper.
|
||||
|
||||
The easiest way to add a new module is to use an existing module as a
|
||||
reference - see **klippy/extras/servo.py** as an example.
|
||||
|
||||
The following may also be useful:
|
||||
* Execution of the module starts in the module level `load_config()`
|
||||
function (for config sections of the form [my_module]) or in
|
||||
`load_config_prefix()` (for config sections of the form
|
||||
[my_module my_name]). This function is passed a "config" object and
|
||||
it must return a new "printer object" associated with the given
|
||||
config section.
|
||||
* During the process of instantiating a new printer object, the config
|
||||
object can be used to read parameters from the given config
|
||||
section. This is done using `config.get()`, `config.getfloat()`,
|
||||
`config.getint()`, etc. methods. Be sure to read all values from the
|
||||
config during the construction of the printer object - if the user
|
||||
specifies a config parameter that is not read during this phase then
|
||||
it will be assumed it is a typo in the config and an error will be
|
||||
raised.
|
||||
* Use the `config.get_printer()` method to obtain a reference to the
|
||||
main "printer" class. This "printer" class stores references to all
|
||||
the "printer objects" that have been instantiated. Use the
|
||||
`printer.lookup_object()` method to find references to other printer
|
||||
objects. Almost all functionality (even core kinematic modules) are
|
||||
encapsulated in one of these printer objects. Note, though, that
|
||||
when a new module is instantiated, not all other printer objects
|
||||
will have been instantiated. The "gcode" and "pins" modules will
|
||||
always be available, but for other modules it is a good idea to
|
||||
defer the lookup.
|
||||
* Register event handlers using the `printer.register_event_handler()`
|
||||
method if the code needs to be called during "events" raised by
|
||||
other printer objects. Each event name is a string, and by
|
||||
convention it is the name of the main source module that raises the
|
||||
event along with a short name for the action that is occurring (eg,
|
||||
"klippy:connect"). The parameters passed to each event handler are
|
||||
specific to the given event (as are exception handling and execution
|
||||
context). Two common startup events are:
|
||||
* klippy:connect - This event is generated after all printer objects
|
||||
are instantiated. It is commonly used to lookup other printer
|
||||
objects, to verify config settings, and to perform an initial
|
||||
"handshake" with printer hardware.
|
||||
* klippy:ready - This event is generated after all connect handlers
|
||||
have completed successfully. It indicates the printer is
|
||||
transitioning to a state ready to handle normal operations. Do not
|
||||
raise an error in this callback.
|
||||
* If there is an error in the user's config, be sure to raise it
|
||||
during the `load_config()` or "connect event" phases. Use either
|
||||
`raise config.error("my error")` or `raise printer.config_error("my
|
||||
error")` to report the error.
|
||||
* Use the "pins" module to configure a pin on a micro-controller. This
|
||||
is typically done with something similar to
|
||||
`printer.lookup_object("pins").setup_pin("pwm",
|
||||
config.get("my_pin"))`. The returned object can then be commanded at
|
||||
run-time.
|
||||
* If the printer object defines a `get_status()` method then the
|
||||
module can export [status information](Status_Reference.md) via
|
||||
[macros](Command_Templates.md) and via the
|
||||
[API Server](API_Server.md). The `get_status()` method must return a
|
||||
Python dictionary with keys that are strings and values that are
|
||||
integers, floats, strings, lists, dictionaries, True, False, or
|
||||
None. Tuples (and named tuples) may also be used (these appear as
|
||||
lists when accessed via the API Server). Lists and dictionaries that
|
||||
are exported must be treated as "immutable" - if their contents
|
||||
change then a new object must be returned from `get_status()`,
|
||||
otherwise the API Server will not detect those changes.
|
||||
* If the module needs access to system timing or external file
|
||||
descriptors then use `printer.get_reactor()` to obtain access to the
|
||||
global "event reactor" class. This reactor class allows one to
|
||||
schedule timers, wait for input on file descriptors, and to "sleep"
|
||||
the host code.
|
||||
* Do not use global variables. All state should be stored in the
|
||||
printer object returned from the `load_config()` function. This is
|
||||
important as otherwise the RESTART command may not perform as
|
||||
expected. Also, for similar reasons, if any external files (or
|
||||
sockets) are opened then be sure to register a "klippy:disconnect"
|
||||
event handler and close them from that callback.
|
||||
* Avoid accessing the internal member variables (or calling methods
|
||||
that start with an underscore) of other printer objects. Observing
|
||||
this convention makes it easier to manage future changes.
|
||||
* It is recommended to assign a value to all member variables in the
|
||||
Python constructor of Python classes. (And therefore avoid utilizing
|
||||
Python's ability to dynamically create new member variables.)
|
||||
* If a Python variable is to store a floating point value then it is
|
||||
recommended to always assign and manipulate that variable with
|
||||
floating point constants (and never use integer constants). For
|
||||
example, prefer `self.speed = 1.` over `self.speed = 1`, and prefer
|
||||
`self.speed = 2. * x` over `self.speed = 2 * x`. Consistent use of
|
||||
floating point values can avoid hard to debug quirks in Python type
|
||||
conversions.
|
||||
* If submitting the module for inclusion in the main Klipper code, be
|
||||
sure to place a copyright notice at the top of the module. See the
|
||||
existing modules for the preferred format.
|
||||
|
||||
## 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.
|
||||
|
||||
Useful steps:
|
||||
1. Start by studying the
|
||||
"[code flow of a move](#code-flow-of-a-move-command)" section and
|
||||
the [Kinematics document](Kinematics.md).
|
||||
2. Review the existing kinematic classes in the klippy/kinematics/
|
||||
directory. 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 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 `calc_position()` method in the new kinematics class.
|
||||
This method calculates the position of the toolhead in cartesian
|
||||
coordinates from the position of each stepper. It does not need to
|
||||
be efficient as it is typically only called during homing and
|
||||
probing operations.
|
||||
5. Other methods. Implement the `check_move()`, `get_status()`,
|
||||
`get_steppers()`, `home()`, and `set_position()` methods. These
|
||||
functions are typically used to provide kinematic specific checks.
|
||||
However, at the start of development one can use boiler-plate code
|
||||
here.
|
||||
6. 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.
|
||||
|
||||
## Porting to a new micro-controller
|
||||
|
||||
This section provides some tips on porting Klipper's micro-controller
|
||||
code to a new architecture. This type of activity requires good
|
||||
knowledge of embedded development and hands-on access to the target
|
||||
micro-controller.
|
||||
|
||||
Useful steps:
|
||||
1. Start by identifying any 3rd party libraries that will be used
|
||||
during the port. Common examples include "CMSIS" wrappers and
|
||||
manufacturer "HAL" libraries. All 3rd party code needs to be GNU
|
||||
GPLv3 compatible. The 3rd party code should be committed to the
|
||||
Klipper lib/ directory. Update the lib/README file with information
|
||||
on where and when the library was obtained. It is preferable to
|
||||
copy the code into the Klipper repository unchanged, but if any
|
||||
changes are required then those changes should be listed explicitly
|
||||
in the lib/README file.
|
||||
2. Create a new architecture sub-directory in the src/ directory and
|
||||
add initial Kconfig and Makefile support. Use the existing
|
||||
architectures as a guide. The src/simulator provides a basic
|
||||
example of a minimum starting point.
|
||||
3. The first main coding task is to bring up communication support to
|
||||
the target board. This is the most difficult step in a new port.
|
||||
Once basic communication is working, the remaining steps tend to be
|
||||
much easier. It is typical to use a UART type serial device during
|
||||
initial development as these types of hardware devices are
|
||||
generally easier to enable and control. During this phase, make
|
||||
liberal use of helper code from the src/generic/ directory (check
|
||||
how src/simulator/Makefile includes the generic C code into the
|
||||
build). It is also necessary to define timer_read_time() (which
|
||||
returns the current system clock) in this phase, but it is not
|
||||
necessary to fully support timer irq handling.
|
||||
4. Get familiar with the the console.py tool (as described in the
|
||||
[debugging document](Debugging.md)) and verify connectivity to the
|
||||
micro-controller with it. This tool translates the low-level
|
||||
micro-controller communication protocol to a human readable form.
|
||||
5. Add support for timer dispatch from hardware interrupts. See
|
||||
Klipper
|
||||
[commit 970831ee](https://github.com/Klipper3d/klipper/commit/970831ee0d3b91897196e92270d98b2a3067427f)
|
||||
as an example of steps 1-5 done for the LPC176x architecture.
|
||||
6. Bring up basic GPIO input and output support. See Klipper
|
||||
[commit c78b9076](https://github.com/Klipper3d/klipper/commit/c78b90767f19c9e8510c3155b89fb7ad64ca3c54)
|
||||
as an example of this.
|
||||
7. Bring up additional peripherals - for example see Klipper commit
|
||||
[65613aed](https://github.com/Klipper3d/klipper/commit/65613aeddfb9ef86905cb1dade9e773a02ef3c27),
|
||||
[c812a40a](https://github.com/Klipper3d/klipper/commit/c812a40a3782415e454b04bf7bd2158a6f0ec8b5),
|
||||
and
|
||||
[c381d03a](https://github.com/Klipper3d/klipper/commit/c381d03aad5c3ee761169b7c7bced519cc14da29).
|
||||
8. Create a sample Klipper config file in the config/ directory. Test
|
||||
the micro-controller with the main klippy.py program.
|
||||
9. Consider adding build test cases in the test/ directory.
|
||||
|
||||
Additional coding tips:
|
||||
1. Avoid using "C bitfields" to access IO registers; prefer direct
|
||||
read and write operations of 32bit, 16bit, or 8bit integers. The C
|
||||
language specifications don't clearly specify how the compiler must
|
||||
implement C bitfields (eg, endianness, and bit layout), and it's
|
||||
difficult to determine what IO operations will occur on a C
|
||||
bitfield read or write.
|
||||
2. Prefer writing explicit values to IO registers instead of using
|
||||
read-modify-write operations. That is, if updating a field in an IO
|
||||
register where the other fields have known values, then it is
|
||||
preferable to explicitly write the full contents of the register.
|
||||
Explicit writes produce code that is smaller, faster, and easier to
|
||||
debug.
|
||||
|
||||
## Coordinate Systems
|
||||
|
||||
Internally, Klipper primarily tracks the position of the toolhead in
|
||||
cartesian coordinates that are relative to the coordinate system
|
||||
specified in the config file. That is, most of the Klipper code will
|
||||
never experience a change in coordinate systems. If the user makes a
|
||||
request to change the origin (eg, a `G92` command) then that effect is
|
||||
obtained by translating future commands to the primary coordinate
|
||||
system.
|
||||
|
||||
However, in some cases it is useful to obtain the toolhead position in
|
||||
some other coordinate system and Klipper has several tools to
|
||||
facilitate that. This can be seen by running the GET_POSITION
|
||||
command. For example:
|
||||
|
||||
```
|
||||
Send: GET_POSITION
|
||||
Recv: // mcu: stepper_a:-2060 stepper_b:-1169 stepper_c:-1613
|
||||
Recv: // stepper: stepper_a:457.254159 stepper_b:466.085669 stepper_c:465.382132
|
||||
Recv: // kinematic: X:8.339144 Y:-3.131558 Z:233.347121
|
||||
Recv: // toolhead: X:8.338078 Y:-3.123175 Z:233.347878 E:0.000000
|
||||
Recv: // gcode: X:8.338078 Y:-3.123175 Z:233.347878 E:0.000000
|
||||
Recv: // gcode base: X:0.000000 Y:0.000000 Z:0.000000 E:0.000000
|
||||
Recv: // gcode homing: X:0.000000 Y:0.000000 Z:0.000000
|
||||
```
|
||||
|
||||
The "mcu" position (`stepper.get_mcu_position()` in the code) is the
|
||||
total number of steps the micro-controller has issued in a positive
|
||||
direction minus the number of steps issued in a negative direction
|
||||
since the micro-controller was last reset. If the robot is in motion
|
||||
when the query is issued then the reported value includes moves
|
||||
buffered on the micro-controller, but does not include moves on the
|
||||
look-ahead queue.
|
||||
|
||||
The "stepper" position (`stepper.get_commanded_position()`) is the
|
||||
position of the given stepper as tracked by the kinematics code. This
|
||||
generally corresponds to the position (in mm) of the carriage along
|
||||
its rail, relative to the position_endstop specified in the config
|
||||
file. (Some kinematics track stepper positions in radians instead of
|
||||
millimeters.) If the robot is in motion when the query is issued then
|
||||
the reported value includes moves buffered on the micro-controller,
|
||||
but does not include moves on the look-ahead queue. One may use the
|
||||
`toolhead.flush_step_generation()` or `toolhead.wait_moves()` calls to
|
||||
fully flush the look-ahead and step generation code.
|
||||
|
||||
The "kinematic" position (`kin.calc_position()`) is the cartesian
|
||||
position of the toolhead as derived from "stepper" positions and is
|
||||
relative to the coordinate system specified in the config file. This
|
||||
may differ from the requested cartesian position due to the
|
||||
granularity of the stepper motors. If the robot is in motion when the
|
||||
"stepper" positions are taken then the reported value includes moves
|
||||
buffered on the micro-controller, but does not include moves on the
|
||||
look-ahead queue. One may use the `toolhead.flush_step_generation()`
|
||||
or `toolhead.wait_moves()` calls to fully flush the look-ahead and
|
||||
step generation code.
|
||||
|
||||
The "toolhead" position (`toolhead.get_position()`) is the last
|
||||
requested position of the toolhead in cartesian coordinates relative
|
||||
to the coordinate system specified in the config file. If the robot is
|
||||
in motion when the query is issued then the reported value includes
|
||||
all requested moves (even those in buffers waiting to be issued to the
|
||||
stepper motor drivers).
|
||||
|
||||
The "gcode" position is the last requested position from a `G1` (or
|
||||
`G0`) command in cartesian coordinates relative to the coordinate
|
||||
system specified in the config file. This may differ from the
|
||||
"toolhead" position if a g-code transformation (eg, bed_mesh,
|
||||
bed_tilt, skew_correction) is in effect. This may differ from the
|
||||
actual coordinates specified in the last `G1` command if the g-code
|
||||
origin has been changed (eg, `G92`, `SET_GCODE_OFFSET`, `M221`). The
|
||||
`M114` command (`gcode_move.get_status()['gcode_position']`) will
|
||||
report the last g-code position relative to the current g-code
|
||||
coordinate system.
|
||||
|
||||
The "gcode base" is the location of the g-code origin in cartesian
|
||||
coordinates relative to the coordinate system specified in the config
|
||||
file. Commands such as `G92`, `SET_GCODE_OFFSET`, and `M221` alter
|
||||
this value.
|
||||
|
||||
The "gcode homing" is the location to use for the g-code origin (in
|
||||
cartesian coordinates relative to the coordinate system specified in
|
||||
the config file) after a `G28` home command. The `SET_GCODE_OFFSET`
|
||||
command can alter this value.
|
||||
|
||||
## Time
|
||||
|
||||
Fundamental to the operation of Klipper is the handling of clocks,
|
||||
times, and timestamps. Klipper executes actions on the printer by
|
||||
scheduling events to occur in the near future. For example, to turn on
|
||||
a fan, the code might schedule a change to a GPIO pin in a 100ms. It
|
||||
is rare for the code to attempt to take an instantaneous action. Thus,
|
||||
the handling of time within Klipper is critical to correct operation.
|
||||
|
||||
There are three types of times tracked internally in the Klipper host
|
||||
software:
|
||||
* System time. The system time uses the system's monotonic clock - it
|
||||
is a floating point number stored as seconds and it is (generally)
|
||||
relative to when the host computer was last started. System times
|
||||
have limited use in the software - they are primarily used when
|
||||
interacting with the operating system. Within the host code, system
|
||||
times are frequently stored in variables named *eventtime* or
|
||||
*curtime*.
|
||||
* Print time. The print time is synchronized to the main
|
||||
micro-controller clock (the micro-controller defined in the "[mcu]"
|
||||
config section). It is a floating point number stored as seconds and
|
||||
is relative to when the main mcu was last restarted. It is possible
|
||||
to convert from a "print time" to the main micro-controller's
|
||||
hardware clock by multiplying the print time by the mcu's statically
|
||||
configured frequency rate. The high-level host code uses print times
|
||||
to calculate almost all physical actions (eg, head movement, heater
|
||||
changes, etc.). Within the host code, print times are generally
|
||||
stored in variables named *print_time* or *move_time*.
|
||||
* MCU clock. This is the hardware clock counter on each
|
||||
micro-controller. It is stored as an integer and its update rate is
|
||||
relative to the frequency of the given micro-controller. The host
|
||||
software translates its internal times to clocks before transmission
|
||||
to the mcu. The mcu code only ever tracks time in clock
|
||||
ticks. Within the host code, clock values are tracked as 64bit
|
||||
integers, while the mcu code uses 32bit integers. Within the host
|
||||
code, clocks are generally stored in variables with names containing
|
||||
*clock* or *ticks*.
|
||||
|
||||
Conversion between the different time formats is primarily implemented
|
||||
in the **klippy/clocksync.py** code.
|
||||
|
||||
Some things to be aware of when reviewing the code:
|
||||
* 32bit and 64bit clocks: To reduce bandwidth and to improve
|
||||
micro-controller efficiency, clocks on the micro-controller are
|
||||
tracked as 32bit integers. When comparing two clocks in the mcu
|
||||
code, the `timer_is_before()` function must always be used to ensure
|
||||
integer rollovers are handled properly. The host software converts
|
||||
32bit clocks to 64bit clocks by appending the high-order bits from
|
||||
the last mcu timestamp it has received - no message from the mcu is
|
||||
ever more than 2^31 clock ticks in the future or past so this
|
||||
conversion is never ambiguous. The host converts from 64bit clocks
|
||||
to 32bit clocks by simply truncating the high-order bits. To ensure
|
||||
there is no ambiguity in this conversion, the
|
||||
**klippy/chelper/serialqueue.c** code will buffer messages until
|
||||
they are within 2^31 clock ticks of their target time.
|
||||
* Multiple micro-controllers: The host software supports using
|
||||
multiple micro-controllers on a single printer. In this case, the
|
||||
"MCU clock" of each micro-controller is tracked separately. The
|
||||
clocksync.py code handles clock drift between micro-controllers by
|
||||
modifying the way it converts from "print time" to "MCU clock". On
|
||||
secondary mcus, the mcu frequency that is used in this conversion is
|
||||
regularly updated to account for measured drift.
|
||||
Reference in New Issue
Block a user