aboutsummaryrefslogtreecommitdiffstats
path: root/docs/Code_Overview.md
blob: aa353f0bfeb33adb81af99cb659c911892c56f88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
ATmega micro-controllers. The **src/sam3x8e/** directory contains code
specific to the Arduino Due style ARM micro-controllers. 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).

The **klippy/** directory contains the C and Python source for the
host part of the firmware.

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.

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_task() located in
**src/command.c**. This function processes incoming serial commands
and runs the associated command function for them. Command functions
are declared using the DECL_COMMAND() macro.

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.

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 architectures 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 gcode commands is in the process_commands() method in
**klippy/gcode.py**. This code translates the gcode 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/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.

* Processing for a move command (eg, "G1 X10 F6000") starts in
  gcode.py. The goal of gcode.py is to translate gcode 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([10, 0, 0, 0], 100.0)

* The ToolHead class (in toolhead.py) handles "lookahead" and tracks
  the timing of printing actions. The codepath for a move is:
  ToolHead.move() (create a Move() object) -> MoveQueue.add_move()
  (place move on lookahead queue) -> MoveQueue.flush() (determine
  start/end velocity of each move) -> Move.set_junction() (perform
  "trapezoid generation" for a move) -> Move.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
  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
  and seconds. Times are stored relative to the start of the
  print. The move is then handed off to the kinematics classes:
  Move.move() -> kin.move()

* 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
  kinematic class is given a chance to audit the move (ToolHead.move()
  -> kin.check_move()) before it goes on the lookahead 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.

* For efficiency reasons, the stepper pulse times are generated in C
  code. The code flow is: kin.move() -> MCU_Stepper.step_const() (in
  mcu.py) -> stepcompress_push_const() (in stepcompress.c), or for
  delta kinematics: DeltaKinematics.move() -> MCU_Stepper.step_delta()
  -> stepcompress_push_delta(). The MCU_Stepper code just performs
  unit and axis transformation (seconds to clock ticks and 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 (in 64bit integers) 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 stepcompress.c). This code generates
  and encodes micro-controller "queue_step" commands. The compression
  involves finding a series of queue_step interval/count/add
  parameters that correspond to the list of micro-controller step
  times. 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 command.c which parses the command and calls
  command_queue_step(). The command_queue_step() code (in stepper.c)
  just appends the interval/count/add parameters to a per stepper
  queue. Under normal operation the queue_step command is parsed and
  queued at least 100ms before the time of the 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 interval/count/add sequence. At a high-level, the
  stepper_event() code does: 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.