2016-05-25 17:37:40 +02:00
|
|
|
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/avr/** directory contains specific code for Atmel
|
2016-07-26 16:58:33 +02:00
|
|
|
ATmega micro-controllers. The **src/sam3x8e/** directory contains code
|
|
|
|
specific to the Arduino Due style ARM micro-controllers. The
|
2017-08-11 17:55:30 +02:00
|
|
|
**src/pru/** directory contains code specific to the Beaglebone's
|
|
|
|
on-board PRU micro-controller. 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 host 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).
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
The **klippy/** directory contains the C and Python source for the
|
2017-04-27 21:14:11 +02:00
|
|
|
host part of the software.
|
2016-05-25 17:37:40 +02:00
|
|
|
|
2016-07-26 16:58:33 +02:00
|
|
|
The **lib/** directory contains external 3rd-party library code that
|
|
|
|
is necessary to build some targets.
|
|
|
|
|
2016-05-25 17:37:40 +02:00
|
|
|
The **config/** directory contains example printer configuration
|
|
|
|
files.
|
|
|
|
|
|
|
|
The **scripts/** directory contains build-time scripts useful for
|
|
|
|
compiling the micro-controller code.
|
|
|
|
|
2018-03-28 19:01:24 +02:00
|
|
|
The **test/** directory contains automated test cases.
|
|
|
|
|
2016-05-25 17:37:40 +02:00
|
|
|
During compilation, the build may create an **out/** directory. This
|
|
|
|
contains temporary build time objects. The final micro-controller
|
2016-07-26 16:58:33 +02:00
|
|
|
object that is built is **out/klipper.elf.hex** on AVR and
|
|
|
|
**out/klipper.bin** on ARM.
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
Micro-controller code flow
|
|
|
|
==========================
|
|
|
|
|
2016-07-26 16:58:33 +02:00
|
|
|
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.
|
2016-05-25 17:37:40 +02:00
|
|
|
|
2017-08-11 17:55:30 +02:00
|
|
|
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**) 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).
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
Task, init, and command functions always run with interrupts enabled
|
|
|
|
(however, they can temporarily disable interrupts if needed). These
|
|
|
|
functions should never pause, delay, or do any work that lasts more
|
|
|
|
than a few micro-seconds. These functions schedule work at specific
|
|
|
|
times by scheduling timers.
|
|
|
|
|
2017-03-11 04:12:05 +01:00
|
|
|
Timer functions are scheduled by calling sched_add_timer() (located in
|
2016-05-25 17:37:40 +02:00
|
|
|
**src/sched.c**). The scheduler code will arrange for the given
|
|
|
|
function to be called at the requested clock time. Timer interrupts
|
2016-07-26 16:58:33 +02:00
|
|
|
are initially handled in an architecture specific interrupt handler
|
2017-03-27 22:38:01 +02:00
|
|
|
(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
|
2016-07-26 16:58:33 +02:00
|
|
|
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.
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
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
|
2018-03-28 19:01:24 +02:00
|
|
|
code, all GPIO events are implemented in architecture specific
|
2016-07-26 16:58:33 +02:00
|
|
|
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.
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
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
|
2017-04-27 21:14:11 +02:00
|
|
|
execution of G-code commands is in the process_commands() method in
|
|
|
|
**klippy/gcode.py**. This code translates the G-code commands into
|
2016-05-25 17:37:40 +02:00
|
|
|
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).
|
|
|
|
|
2016-11-12 02:22:39 +01:00
|
|
|
There are four threads in the Klippy host code. The main thread
|
2016-05-25 17:37:40 +02:00
|
|
|
handles incoming gcode commands. A second thread (which resides
|
|
|
|
entirely in the **klippy/serialqueue.c** C code) handles low-level IO
|
|
|
|
with the serial port. The third thread is used to process response
|
2016-07-26 16:58:33 +02:00
|
|
|
messages from the micro-controller in the Python code (see
|
2016-11-12 02:22:39 +01:00
|
|
|
**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.
|
2017-04-10 23:47:38 +02:00
|
|
|
|
|
|
|
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
|
2017-04-15 21:22:50 +02:00
|
|
|
of a typical move command. The [kinematics](Kinematics.md) document
|
|
|
|
provides further information on the mechanics of moves.
|
2017-04-10 23:47:38 +02:00
|
|
|
|
2017-04-13 19:41:58 +02:00
|
|
|
* Processing for a move command starts in gcode.py. The goal of
|
|
|
|
gcode.py is to translate G-code into internal calls. Changes in
|
|
|
|
origin (eg, G92), changes in relative vs absolute positions (eg,
|
|
|
|
G90), and unit changes (eg, F6000=100mm/s) are handled here. 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()`
|
2017-04-10 23:47:38 +02:00
|
|
|
|
2017-04-27 18:02:15 +02:00
|
|
|
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
2017-04-10 23:47:38 +02:00
|
|
|
the timing of printing actions. The codepath for a move is:
|
2017-04-13 19:41:58 +02:00
|
|
|
`ToolHead.move() -> MoveQueue.add_move() -> MoveQueue.flush() ->
|
|
|
|
Move.set_junction() -> Move.move()`.
|
|
|
|
* ToolHead.move() creates a Move() object with the parameters of the
|
|
|
|
move (in cartesian space and in units of seconds and millimeters).
|
2017-04-27 18:02:15 +02:00
|
|
|
* MoveQueue.add_move() places the move object on the "look-ahead"
|
2017-04-13 19:41:58 +02:00
|
|
|
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 Move.move() is called, everything about the move is known -
|
|
|
|
its start location, its end location, its acceleration, its
|
2017-04-10 23:47:38 +02:00
|
|
|
start/crusing/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
|
2017-09-13 14:59:26 +02:00
|
|
|
and seconds.
|
2017-04-13 19:41:58 +02:00
|
|
|
|
|
|
|
The move is then handed off to the kinematics classes: `Move.move()
|
|
|
|
-> kin.move()`
|
2017-04-10 23:47:38 +02:00
|
|
|
|
|
|
|
* The goal of the kinematics classes is to translate the movement in
|
|
|
|
cartesian space to movement on each stepper. The kinematics classes
|
|
|
|
are in cartesian.py, corexy.py, delta.py, and extruder.py. The
|
2017-04-13 19:41:58 +02:00
|
|
|
kinematic class is given a chance to audit the move
|
|
|
|
(`ToolHead.move() -> kin.check_move()`) before it goes on the
|
2017-04-27 18:02:15 +02:00
|
|
|
look-ahead queue, but once the move arrives in *kin*.move() the
|
2017-04-13 19:41:58 +02:00
|
|
|
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.
|
2017-04-10 23:47:38 +02:00
|
|
|
|
|
|
|
* For efficiency reasons, the stepper pulse times are generated in C
|
2017-04-13 19:41:58 +02:00
|
|
|
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
|
2017-09-13 14:59:26 +02:00
|
|
|
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.
|
2017-04-10 23:47:38 +02:00
|
|
|
|
2017-04-13 19:41:58 +02:00
|
|
|
* The next major step is to compress the steps: `stepcompress_flush()
|
|
|
|
-> compress_bisect_add()` (in 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
|
2017-04-10 23:47:38 +02:00
|
|
|
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 command.c which parses the command and calls
|
2017-04-13 19:41:58 +02:00
|
|
|
`command_queue_step()`. The command_queue_step() code (in 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;`
|
2017-04-10 23:47:38 +02:00
|
|
|
|
|
|
|
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.
|
2017-09-27 21:04:48 +02:00
|
|
|
|
2018-03-28 19:01:24 +02:00
|
|
|
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.
|
|
|
|
* Define a `printer_state()` method if the code needs to be called
|
|
|
|
during printer setup and/or shutdown. This method is called twice
|
|
|
|
during setup (with "connect" and then "ready") and may also be
|
|
|
|
called at run-time (with "shutdown" or "disconnect"). It is common
|
|
|
|
to perform "printer object" lookup during the "connect" and "ready"
|
|
|
|
phases.
|
|
|
|
* If there is an error in the user's config, be sure to raise it
|
|
|
|
during the `load_config()` or `printer_state("connect")` 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 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 close them from the
|
|
|
|
`printer_state("disconnect")` 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.
|
|
|
|
* 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.
|
|
|
|
|
2018-02-17 19:39:37 +01:00
|
|
|
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:
|
2018-04-24 00:23:39 +02:00
|
|
|
1. Start by studying the
|
|
|
|
"[code flow of a move](#code-flow-of-a-move-command)" section and
|
|
|
|
the [Kinematics document](Kinematics.md).
|
2018-02-17 19:39:37 +01:00
|
|
|
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.
|
|
|
|
|
2017-09-27 21:04:48 +02:00
|
|
|
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 calculates 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/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.
|