229 Commits

Author SHA1 Message Date
Kevin O'Connor
8f1d0c2a7c docs: Note version 0.4.0 release
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-03 14:32:36 -04:00
Kevin O'Connor
02549c9299 docs: Make it clear a RESTART is likely needed in Installation document
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-03 11:15:27 -04:00
Kevin O'Connor
98f2adbcb5 extruder: Rework maximum retraction check
On a retract move (which are common during "wipe" operations), treat
the move as if it were an extrude only move.  It's valid for a retract
move to reverse more filament then it would be practical to push.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-02 08:57:38 -04:00
Kevin O'Connor
c9d21574d8 gcode: Check for invalid speeds
Raise an error if the move speed is set to a zero or negative value.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-02 07:54:48 -04:00
Kevin O'Connor
253517096e extruder: Extend over extrusion checks to retractions
Ensure a move with a retraction (negative extrude) is also checked for
sane extrusion rates.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-02 07:54:48 -04:00
Kevin O'Connor
0fa35254c6 msgproto: Wrap strings passed via output() in repr()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-02 07:54:48 -04:00
Kevin O'Connor
fc9fb7473c gcode: Don't report an error if turning off an unknown fan or heater
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-01 15:02:27 -04:00
Kevin O'Connor
31ca2331d2 queuelogger: Add critical information to each logfile on rollover
When the log file does a rollover, start the top of the log with
critical system information (eg, software versions).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-01 14:48:44 -04:00
Kevin O'Connor
b5062a07d1 docs: Recommend users stop klipper before flashing the micro-controller
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-01 12:08:45 -04:00
Kevin O'Connor
4112007314 docs: Reword XY+Z delta moves in Kinematics
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-01 11:58:32 -04:00
Kevin O'Connor
a3162b17d9 docs: Use only single space at start of new sentence
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-01 11:58:32 -04:00
Kevin O'Connor
e177d4f70d docs: Reword smoothed look-ahead description in Kinematics
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-05-01 11:58:32 -04:00
Kevin O'Connor
631b0e6c37 docs: Improve wording of slow lookahead description
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 16:05:26 -04:00
Kevin O'Connor
a7f339ad1c docs: Improve documentation for those starting in docs/
Those that reach Klipper via github may jump directly to the docs/
directory in search of documentation.  Add README.md and rework
Overview.md with that in mind.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 16:05:26 -04:00
Kevin O'Connor
c1c0b2dd38 docs: Avoid using "firmware" in the documentation
The term "firmware" is ambiguous - it could refer to the entire
project (host and micro-controller software) or to just the
micro-controller software.  Avoid the term in the documentation.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 15:59:33 -04:00
Kevin O'Connor
d7a1111955 docs: Add backticks around commands in Firmware Commands
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 12:10:38 -04:00
Kevin O'Connor
d73340474b docs: Use "look-ahead" instead of "lookahead" in Code Overview
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 12:02:15 -04:00
Kevin O'Connor
4f7237de44 docs: Update Firmware Commands document with an integer example
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 12:00:10 -04:00
Kevin O'Connor
4096745a58 docs: Use a markdown link to docs/prints/square.stl
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 12:00:09 -04:00
Kevin O'Connor
a97e074022 docs: Kinematics document image updates
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 12:00:09 -04:00
Kevin O'Connor
917c6aa94a docs: Todo updates
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-27 09:52:38 -04:00
Kevin O'Connor
05bd6fda7e config: Add a sample config file for RAMBo boards
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-26 11:51:40 -04:00
Kevin O'Connor
7c78de989d config: Add a sample config file for RAMPS boards
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-26 11:48:35 -04:00
Kevin O'Connor
35a6d9ba87 gcode: Sort the order of commands in HELP
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-26 10:44:22 -04:00
Kevin O'Connor
c63754fc32 gcode: Limit build_handlers() method to just building available commands
The set_printer_ready() can be called from a background thread on a
shutdown event, so don't try to lookup the printer components in that
case.  Simplify build_handlers() so that it no longer tests for
components being available - test for component availability in the
command handlers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-26 10:31:58 -04:00
Kevin O'Connor
ccb93068fe msgproto: Rework dump() so it also works with params
Always call the regular .parse() method for each message type during
dump() - add a new .format_params() method for dumping a verbose
representation of the parsed message.  This allows the new
format_params() to also be used with data already parsed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-25 13:58:13 -04:00
Kevin O'Connor
a6fe355801 console: Automatically convert float values to int during evaluation
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-25 12:26:13 -04:00
Kevin O'Connor
fe11c3e348 console: Use stdout.write() instead of print
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-25 12:26:13 -04:00
Kevin O'Connor
56d4422d31 docs: Reword alternate linux machine in installation instructions
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-24 09:39:40 -04:00
Kevin O'Connor
e507848a8f docs: Remove "experimental" from descriptions
The Klipper software has progressed to the point where it does not
need to be described as "experimental" software.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-24 09:31:39 -04:00
Kevin O'Connor
70599667cb docs: Installation updates
Recommend using 'make flash' and clean up the octoprint instructions.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-24 09:21:21 -04:00
Kevin O'Connor
1878da228d build: Add workaround to suppress broken avr-gcc "misspelled" warnings
Detect avr gcc v4.8.1 and then disable warnings during the klipper.o
linking to suppress bogus "misspelled signal handler" warnings.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-24 08:54:10 -04:00
Kevin O'Connor
37865d69a2 basecmd: Add debugging commands for testing communication
Add "debug_ping" and "debug_nop" testing commands.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-23 13:37:49 -04:00
Kevin O'Connor
83eba902a3 build: Support makefile rule with default flashing commands
Support a "make flash FLASH_DEVICE=/dev/ttyACM0" rule with the default
commands for flashing a device.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-21 11:01:07 -04:00
Kevin O'Connor
ec805aee2e scripts: Add octopi installation scripts
Add a system startup script so that Klipper can automatically start at
boot time.  Create an installation script that will install the system
dependencies and the startup script.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-21 10:14:27 -04:00
Kevin O'Connor
167b18b58f gcode: Ignore M21 command
No need to recommend users disable "SD card support" in octoprint -
instead, just ignore the M21 command.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-20 10:11:10 -04:00
Kevin O'Connor
4b1a530330 stepcompress: Simplify delta Z only move calculations
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-20 00:56:18 -04:00
Kevin O'Connor
19ffaa9ff0 docs: Reword parts of the pressure advance kinematics description.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-19 20:48:23 -04:00
Kevin O'Connor
0f5167a407 docs: Misc image updates
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-19 19:55:50 -04:00
Kevin O'Connor
563ab5caa5 docs: Add info on delta acceleration limits
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-19 19:54:48 -04:00
Kevin O'Connor
db5b5f121c docs: Add initial Pressure Advance tuning document
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-19 16:02:11 -04:00
Kevin O'Connor
1f417a8441 docs: Update Todo - initial kinematic document written
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-15 22:59:34 -04:00
Kevin O'Connor
b74b09ea7a docs: Updates to Features document
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-15 22:52:36 -04:00
Kevin O'Connor
2cce67ad84 docs: Add initial Kinematics document
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-15 22:26:03 -04:00
Kevin O'Connor
2cb935c300 mcu: No need to log mcu_stats debugging
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-14 10:43:14 -04:00
Kevin O'Connor
b9623c1128 klippy: Don't log stats when the printer is idle
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-14 10:38:37 -04:00
Kevin O'Connor
7a386cff7d mcu: Change "Synchronizing mcu clock" logging to debug
Always log the last synchronized clock during a shutdown event, and
use debug level for normal reports.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-14 10:06:11 -04:00
Kevin O'Connor
839725e3c5 queuelogger: Automatically roll log file
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-14 09:58:34 -04:00
Kevin O'Connor
8920479f85 klippy: Remove CLEAR_SHUTDOWN command
Advice users to issue a FIRMWARE_RESTART command on a printer shutdown
event, and remove support for CLEAR_SHUTDOWN.  A full mcu reset is
preferable and it simplifies the interface.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 14:53:41 -04:00
Kevin O'Connor
4c25eae9b4 mcu: Make sure a FIRMWARE_RESTART actually resets the mcu
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 14:29:18 -04:00
Kevin O'Connor
a3a45b5037 docs: Update Todo - RPi power over USB now implemented
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 14:18:48 -04:00
Kevin O'Connor
dc645d76b4 docs: Reword and reformat parts of move code flow in Code_Overview
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 14:08:02 -04:00
Kevin O'Connor
daff83ee9a hub-ctrl: Add support for micro-controller reset via RPi usb power toggling
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 13:20:13 -04:00
Kevin O'Connor
9f9e3e61d6 mcu: Support reset command
Extend the FIRMWARE_RESTART command so that it can use the firmware
"reset" command instead of the "arduino" mechanism.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 13:20:13 -04:00
Kevin O'Connor
1592395036 reactor: Fix bug causing end() to not always work
Only set the self._process flag in run() not _dispatch_loop().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-13 13:20:13 -04:00
Kevin O'Connor
8491b1f86a docs: Update Features document with latest performance benchmarks
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 13:53:33 -04:00
Kevin O'Connor
a7b4d70cc6 docs: Add documentation on how to run the graphstats.py script
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 13:41:11 -04:00
Kevin O'Connor
fa193e9618 graphstats: The times are no longer in UTC time
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 13:40:25 -04:00
Kevin O'Connor
050008f3c8 docs: Update Debugging.md file with python virtual env setup
Direct readers to the main Installation file to setup the python
virtual environment and remove the outdated instructions in the
Debugging file.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 12:49:12 -04:00
Kevin O'Connor
70e53cb080 docs: Update Firmware_Commands with recent end stop changes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 12:36:29 -04:00
Kevin O'Connor
7b03b04c78 klippy: Support minimum/maximum value checks on configuration variables
Verify that numeric parameters are in a sane range when reading the
config.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 11:42:55 -04:00
Kevin O'Connor
7a7b98cc31 sam3x8e: Rework adc pin search to be more clear
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 10:01:22 -04:00
Kevin O'Connor
15d5837322 avr: Rework adc and pwm pin search to be more clear
Rework the pin search loop.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 10:01:22 -04:00
Kevin O'Connor
f9ebb8b23e avr: Move code around in gpio.c
Move the PWM, ADC, and SPI pin tables closer to their corresponding
code.  This is code movement only - no code changes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 10:00:54 -04:00
Kevin O'Connor
77b94451de avr: Fix irqstatus_t typo in gpio_pwm_setup
The flags and cs variables should be uint8_t not irqstatus_t.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-11 09:49:35 -04:00
Kevin O'Connor
ca6245e974 docs: Update Code Overview document with code flow for a typical move
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-10 22:10:49 -04:00
Kevin O'Connor
1cdddeec30 stepcompress: Add comment on common suffixes and units
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 20:22:32 -04:00
Kevin O'Connor
657c908f88 stepcompress: Modify check_expand() into check_push()
Add the new item at the same time as checking if there is space in the
queue.

Also, update the default optimization level of c_helper.so to O2 to
improve the compiled code layout.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 19:05:04 -04:00
Kevin O'Connor
d7a0e22d59 stepcompress: Remove step_dist from stepcompress_push_delta()
Pass the step direction explicitly to the low-level delta kinematic C
code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 15:08:42 -04:00
Kevin O'Connor
33b809714f delta: Do reverse direction checking in C code
Calculate where a tower must reverse direction during a move in the C
code instead of the delta.py kinematic code.  This simplifies the
python code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 15:05:41 -04:00
Kevin O'Connor
b915a2ad7d delta: Make it clear that a "virtual tower" is created
The delta code calculates a "virtual tower" along the line of
movement.  Rework the variable names and comments to make it clear
that this is occurring.

It is not necessary to pass the start_pos variable to the C code as it
is simple to update the start_pos at the start of each movement.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 15:05:41 -04:00
Kevin O'Connor
85ed5cef7f stepcompress: Merge stepcompress_delta_const and stepcompress_delta_accel
It's not necessary to have separate C delta kinematic functions for
constant acceleration and constant velocity as constant velocity can
be obtained by using a constant acceleration of zero.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 14:47:00 -04:00
Kevin O'Connor
df42b0d1ac stepcompress: Pass delta velocity and acceleration directly to C code
Update the C delta kinematic code to take velocity and acceleration
directly in step distances and clock ticks.  This simplifies the
mcu.py python code as it only needs to do unit and axis conversion.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 14:43:48 -04:00
Kevin O'Connor
98add22891 stepcompress: Merge stepcompress_push_accel() and stepcompress_push_const()
It's not necessary to have separate C functions for constant
acceleration and constant velocity as constant velocity can be
obtained by using a constant acceleration of zero.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 14:43:25 -04:00
Kevin O'Connor
1d81bf5596 stepcompress: Pass constant velocity and acceleration directly to C code
Update the C code to take velocity and acceleration directly in step
distances and clock ticks.  This simplifies the mcu.py python code as
it only needs to do unit and axis conversion.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 13:57:24 -04:00
Kevin O'Connor
e4153a536f mcu: Change mcu_stepper.set_position() to take a location in millimeters
Update the set_position() method to convert from millimeters to
absolute step position.

Also, update PrinterStepper.get_homed_offset() and
mcu_stepper.get_commanded_position() to return millimeters.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 13:44:17 -04:00
Kevin O'Connor
79f31238b0 mcu: Don't export the commanded_position variable from mcu_stepper
Now that the kinematic classes call the mcu_stepper with millimeters
and seconds it is no longer necessary for them to directly access the
stepper's position in absolute steps.  Rename
mcu_stepper.commanded_position to mcu_stepper._commanded_pos to make
clear it is not a variable intended to be externally accessed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 13:44:17 -04:00
Kevin O'Connor
8c712d959d mcu: Pass delta velocity and acceleration directly to mcu_stepper
Rework the parameters of step_delta_const() and step_delta_accel() so
that they take velocity and acceleration directly in millimeters and
seconds.  This simplifies the delta.py kinematic code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 13:44:04 -04:00
Kevin O'Connor
c4b1a79db2 mcu: Pass constant velocity and acceleration directly to mcu_stepper
Rename step_sqrt/step_factor to step_accel/step_const and have them
directly take the velocity and acceleration in millimeters and
seconds.  This simplifies the kinematic classes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 13:43:56 -04:00
Kevin O'Connor
47f12f107d stepcompress: Move stepcompress_push_* functions to their own section
This is only code movement; no code changes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-07 13:26:39 -04:00
Kevin O'Connor
bfad970e4d mcu: Rename self.ffi_lib to self._ffi_lib
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-04 10:13:02 -04:00
Kevin O'Connor
49bdc6fbd1 corexy: Initial corexy kinematic implementation
Add initial support for corexy kinematics.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-03 17:27:14 -04:00
Kevin O'Connor
57f279677f endstop: Support halting more than one stepper on trigger
Extend the endstop code so that more than one stepper can be halted
during endstop homing.  Some kinematic setups (eg, corexy) require an
endstop to support this.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-03 17:27:10 -04:00
Kevin O'Connor
fff73c7735 avr: Invert diff in timer checks
Minor optimization on avr.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-03 10:39:49 -04:00
Kevin O'Connor
e44678ceba avr: Implement reset command
Support restarting the mcu using the watchdog feature of AVR chips via
a new reset command.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-02 23:19:29 -04:00
Kevin O'Connor
85c0e9c574 sam3x8e: Implement reset command
Support restarting the mcu via a new reset command.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-02 23:19:25 -04:00
Kevin O'Connor
0c3ec7f9a5 avr: Do not use --relax linker option
The relax option corrupts the compilation on at least some versions of
gcc/binutils (eg, on fedora's avr-gcc 6.2.0 / binutils 2.27) due to
corruption of switch tables that use jump offsets.  This issue is also
the root cause that resulted in commit d67f962a.  Since the --relax
option provides minimal size / performance improvements it can simply
be dropped.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-02 23:19:19 -04:00
Kevin O'Connor
74e15b2eb5 checkstack: Be more flexible in finding the timer irq function
If __vector_13 doesn't exist then try __vector_17.  If neither exists
then handle that gracefully.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-02 23:19:02 -04:00
Kevin O'Connor
565861f680 reactor: Support pause() command even when the reactor is not running
If the reactor isn't running then implement pause using the system
sleep command.  This simplifies the users of pause().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-04-01 12:26:24 -04:00
Kevin O'Connor
7c991399ac timer: Remove dup timer_shutdown code
Commmit a1c61563 renamed timer_shutdown() to timer_reset() but
neglected to delete timer_shutdown().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-31 19:20:45 -04:00
Kevin O'Connor
384c853a39 docs: Update benchmark results in Features document
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-31 17:50:36 -04:00
Kevin O'Connor
c0380d0280 stepper: Disable homing_endstop_accuracy errors on debug runs
If klippy is started in file output debugging, disable the
homing_endstop_accuracy error check.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-31 14:38:09 -04:00
Kevin O'Connor
a1c61563a0 avr: Fix bug causing timer_read_time() to not be in sync with scheduler
Commit 16e3dbb1 changed the avr implementation of timer_read_time().
Unfortunately, it raised the possibility for timer_read_time() to be
out of sync with the scheduler's understanding of the current time.
In particular, it was common for the timer irq to overflow the 16bit
hardware counter once at startup, and this would lead to
timer_read_time() always returning a time ~4ms ahead of the scheduler
on 16Mhz chips.  This resulted in "Move queue empty" errors.

To resolve this issue, only increment timer_high from timer_periodic()
and make sure the timer irqs start immediately after timer_init().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-31 14:36:57 -04:00
Kevin O'Connor
aa0f1aaeb2 toolhead: Increase the motor_off_time default to 10 minutes
Increase the default motor_off_time from 1 minute to 10 minutes.  The
small value is a common source of confusion.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 14:09:17 -04:00
Kevin O'Connor
03ddd64b93 config: Decrease example max_z_velocity to 25mm/s
Change the example files for cartesian printers to have a 25mm/s
maximum Z velocity.  It's common to use lead screws for the Z axis on
cartesians and it is unlikely one would want to drive them at 250mm/s.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 14:05:05 -04:00
Kevin O'Connor
7fc9ba7d3a mcu: Log the mcu clock each time print_time is synchronized
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 12:51:10 -04:00
Kevin O'Connor
63b6bab5c3 sam3x8e: Align the TC0_Handler timer irq handler
The code alignment of the TC0_Handler function seems to noticeably
impact performance benchmarks.  Set the function alignment to 16 bytes
to improve testing consistency.  An alignment of 16 doesn't
necessarily improve performance, but it seems to improve testing
consistency on code changes unrelated to timer dispatch.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 12:04:08 -04:00
Kevin O'Connor
eb4eeb6f73 timer_irq: Integrate timer_try_set_next() into timer_dispatch_many()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 11:25:23 -04:00
Kevin O'Connor
6988507998 timer_irq: Rename generic/timer.c to generic/timer_irq.c
Rename the file to make it clear that the code is helper functions for
boards with irq based timers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 11:24:44 -04:00
Kevin O'Connor
65be6d5146 avr: Integrate timer_try_set_next() into the irq handler
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 11:24:44 -04:00
Kevin O'Connor
6d05dd07f5 sched: Move timer dispatch loop to board code
Rename sched_timer_kick() to sched_timer_dispatch() and move its loop
into its callers in the board code.  This eliminates the need to
export timer_try_set_next() from the board code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 11:24:42 -04:00
Kevin O'Connor
7436ec093a sched: Rename reschedule_timer() to insert_timer() and use in sched_add_timer()
Use the same code in both rescheduling of a timer and adding a new
timer.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 10:57:35 -04:00
Kevin O'Connor
2b735daae5 timer: Make sure to reset the timer repeat checks on a shutdown
Reset the timer repeat checks on shutdown, otherwise it is possible to
get into an infinite shutdown loop.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 10:57:35 -04:00
Kevin O'Connor
f8b0c884b0 stepper: Improve performance of scheduled unsteps
On faster MCUs where a delay is needed between step and unstep use a
"busy loop" in the scheduler instead of trying to schedule to the
unstep time.  This reduces the chance of jitter in the scheduler
accumulating.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 10:57:35 -04:00
Kevin O'Connor
18f4d343f5 avr: Remove F_CPU compile time definition
Directly use the Kconfig defined CONFIG_CLOCK_FREQ in the code and
avoid defining F_CPU.  Also, remove the unnecessary O2 option - that
is already the default from the main makefile.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-30 10:56:17 -04:00
Kevin O'Connor
31e78c90e2 avr: Minor optimization for timer_read_time() / timer_periodic()
Tell the compiler that the TOV1 bit is rarely set.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-27 13:37:20 -04:00
Kevin O'Connor
3238256b79 docs: Update benchmark results in Features document
Some additional tweeks to the scheduler has resulted in minor
improvements to the stepper benchmarks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 23:31:33 -04:00
Kevin O'Connor
59b71d5d05 sched: Be explicit with loading of the waketime variable
Explicilty load the timer waketime variable into local variables in
sched_timer_kick().  Change the optimization level from Os to O2.
This helps gcc to avoid unnecessary reloads from memory in the common
stepper_event() case.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 22:45:58 -04:00
Kevin O'Connor
4dfa6c6ee4 avr: Introduce optimized timer_is_before()
Provide hand-coded assembler for timer_is_before() on AVR as that code
is used frequently in the time-critical timer dispatch loop and gcc
doesn't do a good job at compiling that comparison code.  Remove the
no longer needed waketime+1 hack from reschedule_timer().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 22:45:58 -04:00
Kevin O'Connor
60e488eb17 timer: Allow board code to define its own timer_is_before implementation
Move sched_is_before() from sched.c to timer_is_before() in the board
specific timer code.  This allows the board code to provide its own
definition.

Also, remove the sched_from_us() and sched_read_time() wrapper
functions and change the callers to directly invoke timer_from_us() /
timer_read_time().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 22:45:58 -04:00
Kevin O'Connor
14340ac4df timer: Organize timer_try_set_next() with priority for repeat timers
Organize the code flow to optimize for repeat timers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 22:01:07 -04:00
Kevin O'Connor
efbfc2b1ab avrsim: Catch simulation errors
Request that simulavr throw an exception on a fatal emulation error
instead of exiting the process.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 22:01:07 -04:00
Kevin O'Connor
d4f09bc20d checkstack: Updates for newer binutils
Newer versions of binutils report the variable name on memory
accesses.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 21:55:24 -04:00
Kevin O'Connor
d67f962a38 command: Simplify sendf() switch
Commit f28eb902 reworked the switch to fix int16 encoding.  However,
at least one version of avr gcc doesn't like that switch layout (it
uses a jump table).  Reorg the switch to avoid that issue.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-26 21:55:24 -04:00
Kevin O'Connor
6de85d02ae serialqueue: Message receive_time must be taken after read()
The est_clock calculation code requires timestamps on status messages
to never be prior to the reception of the message.  The eventtime of
handle_message() is taken before the read() and there is a small
possibility that it could be inaccurate enough to corrupt the
est_clock calculation.  Take a new timestamp when storing receive_time
to prevent this.  This fix prevents some firmware "Move queue empty"
shutdowns.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 19:16:33 -04:00
Kevin O'Connor
f28eb902df command: Fix encoding of 16bit signed integers
The code wasn't properly sign-extending 16bit integers which caused
int16_t reports in output() to appear as uint16_t.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 15:31:26 -04:00
Kevin O'Connor
9702d522a4 sched: Report the time of a shutdown
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 15:05:52 -04:00
Kevin O'Connor
4f710b0470 avrsim: Support pacing the simulation
Support timing the simulation scaled to the system clock.  This can be
used to make the host estimation of the mcu clock more accurate.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 13:55:55 -04:00
Kevin O'Connor
5f29787dc7 avrsim: Do IO directly from simulavr callbacks
Don't start/stop the simulavr simulation loop to do IO - instead
implement the IO directly in the serial callback handlers.  This
improves the speed and consistency of the simulation.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 11:58:32 -04:00
Kevin O'Connor
1fbb36fa87 serialhdl: Make sure to calculate est_clock before connect() finishes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 11:13:55 -04:00
Kevin O'Connor
3cafcc2bc7 serialqueue: Don't retransmit on a NAK if no sent data is pending
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-24 11:13:55 -04:00
Kevin O'Connor
8d92c898ee stepcompress: Always return 0 on negative number in safe_sqrt()
sqrt() of a negative number in the C code returns NaN.  This value
results in behavior that is difficult to debug.  Always return 0.0
instead as this results in better behavior that is easier to track
down.  Also, on some code paths, safe_sqrt is called on numbers that
are multiplied by very large amounts (eg, 16000000**2) and thus
distinguishing between large and small negative numbers is difficult.
For now, report in the log if the value is below -0.001.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-22 10:54:53 -04:00
Kevin O'Connor
9bf73cd72d extruder: Make sure EXTRUDE_DIFF_IGNORE doesn't trigger due to rounding
The code disables lookahead between two extruding moves with
significantly different extrude ratios.  Unfortunately, it was
possible for very tiny moves to show different extrude ratios just due
to how the slicer implements rounding when it produces the gcode.
Allow lookahead to be enabled between moves with extrude ratios that
are different if they don't noticeably produce more or less extrusion.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-20 14:39:06 -04:00
Kevin O'Connor
f97cf5c3b6 extruder: Avoid maximum extrude cross section errors on infinitesimal moves
Be less likely to raise a "Move exceeds maximum extrusion cross
section" error on very short moves.  It's okay to extrude a little
extra plastic on moves shorter than the nozzle diameter.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-19 21:38:59 -04:00
Kevin O'Connor
5ff2d5aee6 mcu: Defer MCU_adc register_msg until ready to receive messages
Register the callback handler only after it's required state is setup.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 14:41:26 -04:00
Kevin O'Connor
1f474742eb klippy: Log python info at start of log
Log the python version and startup command parameters to the log.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 14:41:25 -04:00
Kevin O'Connor
0041a0079d stepcompress: Improve check_line() error messages
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 13:15:25 -04:00
Kevin O'Connor
9bb8b0c622 toolhead: Don't raise exception from force_shutdown
Catch and ignore any exceptions when trying to shutdown the printer in
toolhead.force_shutdown() - there's a good chance an exception will be
raised as this method is often called after an invalid internal state
is found.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 13:15:25 -04:00
Kevin O'Connor
df6d3107f2 stepper: Fix set_min_stop_interval() calculation
The previous calculation was only valid if the stepper is always
commanded to a position that is an exact multiple of the
step_distance.  The safety check was programmed with a value too large
for other commanded positions, which could result in "No next step"
errors.  Fix by changing the calculation to use the worst case
scenario.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 13:15:09 -04:00
Kevin O'Connor
cbdc54843d gcode: Catch common gcode parameter errors
Don't force a firmware shutdown on a simple gcode parse error.
Instead, report the error back to the user and otherwise ignore the
command.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 13:13:36 -04:00
Kevin O'Connor
d2027cb4a9 serialhdl: Catch SerialException as well as OSError on serial open
Catch the right exceptions so that a retry is possible when attempting
to open the serial port.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 12:49:15 -04:00
Kevin O'Connor
e60779bfe1 heater: Force set_pwm of zero when target_temp is zero
Fix a corner case where a residual in the PID could cause a non-zero
pwm request even when the target_temp is zero.  (Which could lead to a
firmware "Missed scheduling of next pwm event" shutdown.)

Simplify the logic for suppressing duplicate pwm updates and make sure
a zero target_temp always results in a zero pwm update on the
following set_pwm calculation.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-16 12:49:15 -04:00
Kevin O'Connor
f66b1ac450 heater: Add support for AD595 type sensors
Add support for sensor chips that produce a voltage that linearly
scales with temperature.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:48:30 -04:00
Kevin O'Connor
ff6a96665a heater: Rename thermistor_type config name to sensor_type
Rename the thermistor_type and thermistor_pin config variables to
sensor_type and sensor_pin.  This is in preparation for support of
sensors beyond thermistors.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:46:33 -04:00
Kevin O'Connor
4388a294a4 heater: Handle case where min adc value is less than max adc value
When using a sensor that isn't a thermisistor, the maximum and minimum
adc values may be swapped - handle that case.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:44:53 -04:00
Kevin O'Connor
d21b9280f0 klippy: Eliminate high-level build_config phase
Now that the mcu objects can be created prior to connecting to the
mcu, it is no longer necessary to separate the init and build_config
phases in the high-level code.  Move the mcu objection creation from
the build_config phase to the init phase and eliminate the
build_config phase.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:38:17 -04:00
Kevin O'Connor
92649332ce mcu: Delay setting of mcu_stepper.min_stop_interval
Create a separate callback for setting the min_stop_interval.

Also, move the setting of the stepper max_error from the stepper
configs to the mcu config and rename it to max_stepper_error.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:38:17 -04:00
Kevin O'Connor
be91c1229f gcode: Eliminate build_config() method
Lookup the printer components during the set_printer_ready() callback.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:38:17 -04:00
Kevin O'Connor
168cb95bd5 mcu: Allow each oid object to define its own build_config() method
Create a build_config() method on each oid object and call it just
after connecting to the MCU.  Move code that requires a connected
state from the oid init to its new build_config method.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:38:17 -04:00
Kevin O'Connor
1d796a4e24 mcu: Support config mechanism for translating seconds to clock ticks
Introduce a TICKS() macro during config parsing that will translate
time in seconds to time in clock ticks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:38:17 -04:00
Kevin O'Connor
8e6d5efdac pins: Simplify pin map alias setup
Use map_pins() to obtain the pin mapping - don't export
mcu_to_pins().  The functionality of mcu_to_pins() can be obtained by
calling map_pins() with name=None.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-13 00:38:15 -04:00
Kevin O'Connor
0f2478b62f docs: Update benchmark results in Features document
Recent scheduler optimizations have made a minor improvement to the
stepper benchmarks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-11 12:23:24 -05:00
Kevin O'Connor
e5d7e593ec generic: Move generic parts of sam3x8e timer.c to generic directory
Most of sam3x8e/timer.c is going to be platform agnostic for any board
with standard irq handling.  Move the generic code into a new file
generic/timer.c.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-11 12:15:07 -05:00
Kevin O'Connor
69b927bfe9 sched: Move functions within sched.c
Just code movement - no code changes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-11 11:14:06 -05:00
Kevin O'Connor
944d176856 sched: Rename sched_timer() to sched_add_timer()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-11 11:14:06 -05:00
Kevin O'Connor
cdd5a772e8 sched: Don't overwrite shutdown reason if shutdown called while shutdown
If a shutdown occurs while the machine is already shutdown, then keep
the original shutdown reason code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-10 23:24:20 -05:00
Kevin O'Connor
0a3c23bcf6 sched: Avoid rescheduling the currently active timer
It's tricky to reschedule the timer irq correctly (due to race
conditions with the irq) and in practice it's very rarely needed.
Handle the special cases in the generic sched.c code so that the board
code doesn't have to handle it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-10 23:24:15 -05:00
Kevin O'Connor
cb286ede9d sched: Use a sentinel timer at the end of the timer_list
Introduce a dummy sentinel timer object that is always the last item
on timer_list.  This optimizes the timer_list walking code as it no
longer needs to check for NULL when traversing the list.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-10 16:14:49 -05:00
Kevin O'Connor
16e3dbb18c avr: Optimize 16bit timer upscaling
The hardware timer overflow bit can be used to optimize the conversion
from 16bit timers to 32bit timers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-10 13:31:38 -05:00
Kevin O'Connor
a38437f378 stepper: Introduce stepper_get_position command and remove from endstop.c
Move the logic to calculate and report the stepper's current position
from endstop.c to stepper.c.  This localizes the stepper code into
stepper.c.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-09 14:54:52 -05:00
Kevin O'Connor
8d6ecd9af8 endstop: No need to store pin_value
The pin_value can be stored in the existing flags variable.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-09 14:50:40 -05:00
Kevin O'Connor
342a7096ea basecmd: Remove unimplemented command_reset()
It's better to not have the unimplemented command defined so that the
host can detect when it is actually implemented.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-09 14:18:24 -05:00
Kevin O'Connor
60a4bda9d4 basecmd: Use oid_ prefix for the oid manipulation functions
Consistently use an "oid_" prefix on the oid functions - this makes
them similar to other functions with a common prefix.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-09 13:49:03 -05:00
Kevin O'Connor
d5fc594317 mcu: Support inverted PWM pins
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-09 00:46:53 -05:00
Kevin O'Connor
64407dc5d2 klippy: Support FIRMWARE_RESTART command
Add initial support for micro-controller resets via the Arduino reset
mechanism.  Also, automatically attempt a firmware restart if the
printer CRC does not match.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 23:02:31 -05:00
Kevin O'Connor
b0329465ec serialhdl: Make sure to close the serial port on disconnect()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 23:02:31 -05:00
Kevin O'Connor
0f70b420f2 mcu: Improve error messages on failure to config printer
Don't report a CRC mismatch if a shutdown or other failure occurs
during config - instead report the appropriate details.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 21:44:17 -05:00
Kevin O'Connor
21c4dea0e6 serialhdl: Detect timeout in SerialReader.send_with_response()
Raise an error if the response is never received.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 21:25:54 -05:00
Kevin O'Connor
bcaf818c0e fan: Default to using software PWM
Not all hardware has PWM support and there is no compelling reason to
use hardware PWM for fans.  Change the default to use software PWM.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 20:23:09 -05:00
Kevin O'Connor
37bac916e7 basecmd: Generalize the "move queue" runtime storage
Detect the maximum size of each "move queue" item during the
configuration phase instead of using the stepper move struct.  This
allows the stepper code to be contained entirely in stepper.c and it
allows for future run time allocations from other types of objects.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 13:37:51 -05:00
Kevin O'Connor
affdbbf9ca sam3x8e: Fix typo in gpio_in_setup() shutdown message
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-08 13:17:45 -05:00
Kevin O'Connor
4fcf0ff2ac docs: Fix typo in description of fan pin.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-07 19:13:37 -05:00
Kevin O'Connor
c19af4fb2b serialhdl: Load the mcu's 64bit clock at start of connection
Store a full 64bit uptime in the mcu and query it at the start of each
connection.  This ensures the host's 64bit clock is always in synch
with the mcu's clock.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-03 22:02:27 -05:00
Kevin O'Connor
f53897758d heater: Support max_power setting for heaters
Change the mcu PWM value from an integer (0-255) to a float (0. - 1.).
Add support for limiting the maximum power (as measured over a
sufficiently long duration) to a particular heater.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-03 20:00:16 -05:00
Kevin O'Connor
54002c4391 extruder: Pressure advance lookahead time should start after decel
The pressure advance lookahead time should start after any full
deceleration moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-03-03 14:20:41 -05:00
Kevin O'Connor
6a53eaefc0 extruder: Allow configuration of pressure advance lookahead time
Instead of defaulting the pressure advance lookahead time to be the
same as the pressure_advance variable, allow it to be configured.
Default the new config setting (pressure_advance_lookahead_time) to
10ms.

Also, make the setting more accurate if a future move is accelerating
in the middle of the lookahead window.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-22 15:20:22 -05:00
Kevin O'Connor
4bc114336c delta: Simplify maximum stepper velocity and accel checks
Simplify the mechanism for limiting stepper speed (introduced in
commit bdfdf7ef) - split the extreme end of the build envelope into
two zones and use the same speeds for all moves that traverse any part
of one of those zones.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-21 11:18:56 -05:00
Kevin O'Connor
47f1d377f5 heater: Enforce min/max_temp in heater.set_temp()
Raise an error if the user requests a temperate outside the configured
min/max_temp range.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-21 10:48:42 -05:00
Kevin O'Connor
566699f68a toolhead: Fix error in lookahead logic
Commit c24b7a7e reworked the way lookahead was done and it introduced
a bug when a full acceleration move is immiedietly followed by a full
deceleration move.  In that situation, depending on when the lookahead
queue was flushed, it was possible to call move.move() without calling
move.set_junction().  This resulted in a "Move instance has no
attribute 'accel_t'" internal error.

Simplify and fix the logic for checking full accel moves followed by
full decel moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-20 12:50:12 -05:00
Kevin O'Connor
29ba92a551 delta: Use position_endstop for position_max
There's no reason for the user to specify position_max - it can be
inferred on deltas from the endstop positions.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-19 11:00:46 -05:00
Kevin O'Connor
38e9484f9f armcm_irq: Move ARM Cortex-M irq handling to new file
The irq handling in sam3x8e isn't specific to the sam3x8e proccessor -
it's generic for all armcm type machines.  So, move the definitions
into a new file generic/armcm-irq.c

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-19 11:00:46 -05:00
Kevin O'Connor
fec12030a9 sam3x8e/timer: Be careful of races in timer_set_next()
It's possible for sched_del_timer() to be called on a timer that fires
just after sched_del_timer disables irqs but before the next timer is
scheduled.  In this case be sure to clear the irq pending status flag
after scheduling the next timer so that a delayed irq doesn't cause
the wrong timer to be run.  For the same reason, make sure to check
the irq pending status flag at the start of the timer irq.

Also, as a safety check, make sure timer_set_next() isn't called from
within the timer irq dispatch loop.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-14 22:15:51 -05:00
Kevin O'Connor
bdfdf7ef55 delta: Cap maximum stepper velocity and acceleration
Some XY moves at the extreme end of the build envelope could cause
excessive axis stepper movement.  Check for any moves that could
possibly result in a stepper movement of more than 3 times the XY
movement and cap the move's acceleration and speed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-13 21:47:10 -05:00
Kevin O'Connor
9f65ae72c3 delta: Rework boundary checks
Calculate and store the maximum xy2 value for the given z level each
time the head moves to a new z level.  This simplifies the boundary
check for common XY moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-13 17:51:13 -05:00
Kevin O'Connor
3434ea540c klippy: Log the type of cpu the host is running on
Report in the log the host CPU type and count.  This helps distinguish
between different rpi versions when debugging the log from a problem
report.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 19:14:26 -05:00
Kevin O'Connor
29131c873a gcode: Attempt to shutdown heaters and fans prior to a RESTART
If the user requests a restart and the machine appears to be otherwise
functioning normally, then attempt to stop the heaters and fans prior
to restarting the host.  This prevents the firmware from going into a
shutdown state when the heater is on and the host restarts.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 18:48:21 -05:00
Kevin O'Connor
ab1eb70d1c toolhead: Rework lookahead flush to be more stable during high cpu
Change the lookahead queue so that it attempts to buffer at least
buffer_time_high amount of moves when first starting a print.  This
helps ensure the buffer is normally always full.

If the buffer falls below buffer_time_low then it is either due to the
end of a print or because octoprint/klippy is unable to keep up.
Change the code so that in this case the lookahead queue will attempt
to gather buffer_time_high amount of moves before restarting movement.

Update the default buffer_time_low to 1 second and buffer_time_high to
2 seconds.  With the above changes a smaller buffer_time_high and a
larger buffer_time_low are more practical.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 17:20:40 -05:00
Kevin O'Connor
71256f9456 toolhead: Flush lookahead buffer by time
Use a minimum time window as a heuristic for determining when to try
to lazily flush the lookahead buffer.  In the common case this will
result in more moves processed for each flush and thus reduce the
overall cost of the lookahead processing.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 17:20:40 -05:00
Kevin O'Connor
6179839215 toolhead: Separate motor off timer from main flush timer
Move the motor off time checking to its own code.  This simplifies the
main flush handler.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 17:20:40 -05:00
Kevin O'Connor
0ca96e543c toolhead: Increase maximum stepper halt velocity
Do a better job of calculating the maximum halt velocity for the
stepper motors.  The maximum cornering velocity is related to both the
maximum acceleration and the junction_deviation, so both should be in
the formula.  Tests show that "math.sqrt(8. * self.junction_deviation
* self.max_accel)" very closely fits the maximum on cartesian robots.

This fixes potential "no next step" shutdowns that could occur on
some print moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 17:20:40 -05:00
Kevin O'Connor
acb0b8f599 klippy: Fix omission causing gcode dump to not function
Fix bug that broke the gcode command dump after a shutdown.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-12 17:19:58 -05:00
Kevin O'Connor
20d0936fa2 reactor: Use the system monotonic clock instead of the normal system clock
The normal system clock can have sudden jumps if the system clock is
changed.  Use the system monotonic clock to avoid these sudden changes
in time.

It appears the Raspbian OS (which is used by OctoPi) is setup to
update the system clock upon network connectivity.  This could cause
sudden system clock changes which could lead to Klippy processing
errors.  Using the monotonic clock eliminates these issues.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 13:31:34 -05:00
Kevin O'Connor
c24b7a7ef9 toolhead: Introduce "smoothed" acceleration during lookahead
Update the lookahead code to track both normal toolhead acceleration
as well as a pseudo acceleration to the point of deceleration.  This
reduces the top speed of small zig-zag moves and it reduces printer
vibration during these moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
074495a13a toolhead: Remove unneeded forward pass in MoveQueue.flush()
Simplify the code now that the extruder lookahead is separate from the
main lookahead code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
e14d86d8b8 toolhead: Remove the do_calc_junction flag
It is not necessary to track the do_calc_junction flag as it can just
as easily be determined at the top of the calc_junction() method.
This simplifies the code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
528c29c01c extruder: Do extruder lookahead based on time instead of cornering
When calculating the extruder lookahead, determine how far to
lookahead by the amount of elapsed time each move takes.  This makes
the extruder lookahead code more flexible as it is no longer limited
to the next immediate cornering moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
1bb7a22115 extruder: Move extruder specific lookahead into extruder class
Instead of calculating min/max_corner_v in the toolhead class,
calculate it in the extruder class.  This keeps the extruder specific
code together.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
19ed67331d stepcompress: Propagate errors back to python code
Propagate error codes back to the python code and raise an exception
on an error.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
667b72870f gcode: Exit on an unhandled exception when reading from a file
When testing via a gcode input file, it's easier to debug problems if
the program exits upon the first exception.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-06 12:17:50 -05:00
Kevin O'Connor
4194ebf9df graphstats: Display host buffer stats in graph
Prune host buffer stats near the start and end of the print.  Graph
the remaining buffer stats.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-02 10:46:48 -05:00
Kevin O'Connor
5beceaae5c io.h: read/write[bwl] should use barrier
Add barrier() calls to low-level read/write io calls so that their
callers don't need to.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-02-02 10:46:42 -05:00
Kevin O'Connor
9c1bf1387c toolhead: Make sure max_corner_v2 is fully calculated on a lazy flush
Make sure max_corner_v2 is fully calculated before proactively
flushing moves from the lookahead queue.  Without this, some moves
would do unnecessary pressure advance during cornering.

Also, handle the min/max_corner_v2 calculations correctly in the rare
case where a move that does only acceleration is immediately followed
by a move that does only deceleration.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-16 15:32:35 -05:00
Kevin O'Connor
fc6a31eac8 toolhead: Change variables to use suffix "_v2" instead of prefix "junction_"
The junction speeds are tracked in velocity squared - introduce the
common suffix "_v2" to track that instead of using a prefix of
"junction_".

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-16 15:32:35 -05:00
Kevin O'Connor
064e8bdd84 toolhead: Clear do_calc_junction if using non-default accel
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-14 12:48:16 -05:00
Kevin O'Connor
262ccbcf30 serial: Be careful with comparison of transmit_max to transmit_pos
There is a small possibility that a shutdown could occur between
clearing transmit_max and clearing transmit_pos - so make sure to
handle the case where transmit_pos > transmit_max.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-14 12:11:30 -05:00
Kevin O'Connor
7567885115 sched: Minor change - remove unneeded header files
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-14 11:32:27 -05:00
Kevin O'Connor
ed715ec437 command: No need to disable irqs in sendf reentrant check
As long as the code is careful when writing the in_sendf variable it
should be safe to update it without having to disable irqs.

Also, make sure in_sendf is cleared on shutdown.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-14 11:32:27 -05:00
Kevin O'Connor
9a44a20a9d command: Check for reentrant calls to sendf()
Allow sendf() to be called from irq and timer context - check for the
case where sendf() is called while already in sendf() and simply
discard those messages.  This makes it safe to use output() debugging
calls even in irq and timer context.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-13 12:10:00 -05:00
Kevin O'Connor
f335045273 heater: Resend PWM values even if last value was zero
Continue to resend the pwm value even if the last value was zero -
this extends the debugging info.

Also, add the target temperature to the pwm debugging.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-12 15:13:41 -05:00
Kevin O'Connor
4ea091339e heater: Only create a soft PWM object for PID heaters
The "watermark" style heater only needs a digital_out pin - not a
software PWM pin.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-10 18:36:43 -05:00
Kevin O'Connor
8378b7345b toolhead: Change cornering_min/max variable name to junction_corner_min/max
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-10 18:35:52 -05:00
Kevin O'Connor
4a71c7a2bd heater: Report last temperature in PWM debugging
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-10 12:36:51 -05:00
Kevin O'Connor
b2885a53cb klippy: Increase precision of reported statistics timestamp
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-10 12:35:30 -05:00
Kevin O'Connor
46b6b4037d reactor: Reload eventtime if a greenlet is reactivated
Fix a bug causing timers to be delayed when pause() is called from a
fd event.  The eventtime needs to be reloaded when an old greenlet is
reactivated.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-10 11:19:39 -05:00
Kevin O'Connor
93d3a6e1d1 klippy: Warn the user on common errors due to old firmware
Check for msgproto.error and warn the user about version firmware
version mismatch.  Raise msgproto.error when extracting firmware
constants.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-10 00:13:28 -05:00
Kevin O'Connor
eebaeeff96 util: Use path of script instead of current directory for git version
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-09 23:33:23 -05:00
Kevin O'Connor
3a7a77d49e basecmd: Improve accuracy of stats "sumsq" variable
Use a base of 256 instead of 65536 when calculating the sum of the
square of the clock differences in the stats.  This makes the
calculation more accurate.  Export the new base via DECL_CONSTANT for
the host to access.  Use DIV_ROUND_UP() when adjusting for the base to
ensure no lost ticks.  Do the division after multiplication in the
common case where the time between stats_task() invocations is less
than 64K ticks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-09 23:08:23 -05:00
Kevin O'Connor
c87c090264 extruder: Calculate sane defaults for extrude only velocity and accel
Instead of requiring the user enter velocity and accel parameters for
extrude only moves, calculate sane defaults from the printer's maximum
velocity and accel.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-03 18:18:30 -05:00
Kevin O'Connor
b26922978a extruder: Do sanity checks on extrusion rates
Add a run-time check to ensure the incoming g-code doesn't have a
ridiculously large e move.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-03 18:17:43 -05:00
Kevin O'Connor
5a5bd2596a extruder: Add nozzle and filament diameter config settings
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-03 18:12:51 -05:00
Kevin O'Connor
38ca051381 config: Update example config files to be more clear on default values
Be more clear on which parameters have defaults and which parameters
must be specified in the config.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-02 13:22:20 -05:00
Kevin O'Connor
91056809dd mcu: Change the default baud rate to 250000
Update the default baud rate in mcu.py to 250000 (as that is the
default in all other places).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-02 12:22:43 -05:00
Kevin O'Connor
f75430e95f gpio: Fix sam38xe ADC startup check
The status register needs to be inspected, not the enable register.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-01-01 22:10:24 -05:00
Kevin O'Connor
8e797e6830 stepcompress: Flush periodically if adding more than 64K steps in a move
It's possible for a printer with very fine resolution to require a
large number of steps for a homing operation.  Instead of storing all
of those steps in memory, periodically flush the queue should more
than 64K steps be present.  This keeps a reasonable limit on the
amount of ram needed to store steps.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-31 13:21:53 -05:00
Kevin O'Connor
73c4be3fd3 docs: Update protocol document with internal VLQ link
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-31 11:30:50 -05:00
Kevin O'Connor
c552ba06b4 serialqueue: Remove serialqueue_flush_ready()
The serialqueue_flush_ready() code was used to flush queue_step
commands during a homing operation.  It's no longer necessary now that
moves during a homing operation use the normal message priority
system.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-30 20:15:05 -05:00
Kevin O'Connor
6bd5f4e44e stepcompress: Using normal message priority system during homing
The endstop homing system requires all queue_step commands be in the
MCU's 'move queue' before endstop checking starts.  Use the normal
message priority system to request that stepper queue_step commands
are received prior to the start of the end_stop_home command.  This
simplifies the code and removes the need for special serial queue
flushing.

This also fixes a bug in homing operations that take longer than 2^31
clock ticks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-30 20:14:48 -05:00
Kevin O'Connor
6138d18f4b toolhead: Also call reset_print_time() on force_shutdown()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-28 22:38:29 -05:00
Kevin O'Connor
d028f42e99 mcu: Don't call steppersync_flush if steppersync not created
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-28 22:38:29 -05:00
Kevin O'Connor
860fc3e91d gcode: Add support for M115 command
Support querying the firmware type and version.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-28 22:38:29 -05:00
Kevin O'Connor
2e03d84755 gcode: Add support for M400 command
Add ability to fully stall the input until all moves are complete.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-28 22:38:28 -05:00
Kevin O'Connor
f2b406fc5e toolhead: Don't call into kinematic class on extrude only moves
Add a is_kinematic_move flag to the Move class and clear it on extrude
only moves.  Don't call the kinematic check_move() or move() methods
for extrude only moves.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-28 22:38:28 -05:00
Kevin O'Connor
f46bc0ef04 stepper: Change default max_error from 50us to 25us
Change the default compression error window (max_error) from 50us to
25us - it's common for stepper motor drivers to have 30us for their
"pwm fixed off time" and it would be good for the steps to be
scheduled within that time.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-28 22:38:27 -05:00
Kevin O'Connor
800d53db6a stepcompress: Rework addfactor integer overflow check
Revert 4a16053c and avoid integer overflows in the addfactor
calculation by exiting the loop early if count > 0x200.  This provides
stronger protection against overflows.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-26 12:47:17 -05:00
Kevin O'Connor
a9444d3399 mcu: Log the MCU configuration during connect phase
Log the constants reported by the MCU and log the number of move items
allocated after configuration.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-24 12:33:56 -05:00
Kevin O'Connor
4a16053c00 stepcompress: Fix integer overflow leading to infinite loop
Commit a217c0f3 changed the way the "addfactor" was calculated.
Unfortunately, it was possible for the updated method to cause an
integer overflow and have a negative addfactor.  Fix this by
explicitly casting the addfactor calculation to uint32_t.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-24 12:02:37 -05:00
Kevin O'Connor
d0c61f0f76 klippy: Log the contents of the config file at startup
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-24 10:18:41 -05:00
Kevin O'Connor
451ffd567d klippy: Log the host software git version at startup
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-24 10:17:48 -05:00
Kevin O'Connor
f3a49604f1 stepcompress: Increase check on max count to 10000000
Some motors have very small step distances and they can generate over
a million steps during a homing operation.  Increase the maximum count
to ten million to avoid triggering the internal sanity check.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-23 23:13:35 -05:00
112 changed files with 7851 additions and 2732 deletions

View File

@@ -83,7 +83,7 @@ $(OUT)declfunc.lds: src/declfunc.lds.S
$(OUT)klipper.o: $(patsubst %.c, $(OUT)src/%.o,$(src-y)) $(OUT)declfunc.lds
@echo " Linking $@"
$(Q)$(CC) $(CFLAGS) -Wl,-r -Wl,-T,$(OUT)declfunc.lds -nostdlib $(patsubst %.c, $(OUT)src/%.o,$(src-y)) -o $@
$(Q)$(CC) $(CFLAGS) $(CFLAGS_klipper.o) -Wl,-r -Wl,-T,$(OUT)declfunc.lds -nostdlib $(patsubst %.c, $(OUT)src/%.o,$(src-y)) -o $@
$(OUT)compile_time_request.o: $(OUT)klipper.o ./scripts/buildcommands.py
@echo " Building $@"

View File

@@ -1,7 +1,7 @@
# Support for internal testing with the "simulavr" program. To use
# this config, compile the firmware for an AVR atmega644p, disable the
# AVR watchdog timer, set the MCU frequency to 20000000, and set the
# serial baud rate to 115200.
# serial baud rate to 250000.
[stepper_x]
# Pins: PA5, PA4, PA1
@@ -42,11 +42,11 @@ step_pin: ar19
dir_pin: ar18
enable_pin: ar25
step_distance: .004242
max_velocity: 200000
max_accel: 3000
nozzle_diameter: 0.500
filament_diameter: 3.500
heater_pin: ar4
thermistor_pin: analog1
thermistor_type: EPCOS 100K B57560G104F
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog1
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
@@ -57,19 +57,17 @@ max_temp: 210
[heater_bed]
heater_pin: ar3
thermistor_pin: analog0
thermistor_type: EPCOS 100K B57560G104F
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog0
control: watermark
min_temp: 0
max_temp: 110
[fan]
pin: ar14
hard_pwm: 1
[mcu]
serial: /tmp/pseudoserial
baud: 250000
pin_map: arduino
[printer]

84
config/example-corexy.cfg Normal file
View File

@@ -0,0 +1,84 @@
# This file serves as documentation for config parameters of corexy
# style printers. One may copy and edit this file to configure a new
# corexy printer. Only parameters unique to corexy printers are
# described here - see the "example.cfg" file for description of
# common config parameters.
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
# FIRST. Incorrectly configured parameters may cause damage.
# The stepper_x section is used to describe the X axis as well as the
# stepper controlling the X+Y movement
[stepper_x]
step_pin: ar54
dir_pin: ar55
enable_pin: !ar38
step_distance: .01
endstop_pin: ^ar2
homing_speed: 50.0
position_min: 0
position_endstop: 0
position_max: 200
# The stepper_y section is used to describe the Y axis as well as the
# stepper controlling the X-Y movement
[stepper_y]
step_pin: ar60
dir_pin: ar61
enable_pin: !ar56
step_distance: .01
endstop_pin: ^ar15
homing_speed: 50.0
position_min: 0
position_endstop: 0
position_max: 200
[stepper_z]
step_pin: ar46
dir_pin: ar48
enable_pin: !ar62
step_distance: .01
endstop_pin: ^ar19
position_min: 0.1
position_endstop: 0.5
position_max: 200
[extruder]
step_pin: ar26
dir_pin: ar28
enable_pin: !ar24
step_distance: .0022
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: ar10
sensor_type: ATC Semitec 104GT-2
sensor_pin: analog13
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: ar8
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog14
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: ar9
[mcu]
serial: /dev/ttyACM0
pin_map: arduino
[printer]
kinematics: corexy
# This option must be "corexy" for corexy printers.
max_velocity: 300
max_accel: 3000
max_z_velocity: 25
max_z_accel: 30

View File

@@ -9,8 +9,7 @@
# The stepper_a section describes the stepper controlling the front
# left tower (at 210 degrees). This section also controls the homing
# parameters (homing_speed, homing_retract_dist) and maximum tower
# length (position_max) for all towers.
# parameters (homing_speed, homing_retract_dist) for all towers.
[stepper_a]
step_pin: ar54
dir_pin: ar55
@@ -19,7 +18,6 @@ step_distance: .01
endstop_pin: ^ar2
homing_speed: 50.0
position_endstop: 297.05
position_max: 297.55
# The stepper_b section describes the stepper controlling the front
# right tower (at 330 degrees)
@@ -46,11 +44,11 @@ step_pin: ar26
dir_pin: ar28
enable_pin: !ar24
step_distance: .0022
max_velocity: 200
max_accel: 3000
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: ar10
thermistor_pin: analog13
thermistor_type: ATC Semitec 104GT-2
sensor_type: ATC Semitec 104GT-2
sensor_pin: analog13
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
@@ -60,8 +58,8 @@ max_temp: 250
[heater_bed]
heater_pin: ar8
thermistor_pin: analog14
thermistor_type: EPCOS 100K B57560G104F
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog14
control: watermark
min_temp: 0
max_temp: 130
@@ -69,27 +67,37 @@ max_temp: 130
# Extruder print fan (omit section if fan not present)
#[fan]
#pin: ar9
#hard_pwm: 1
[mcu]
serial: /dev/ttyACM0
baud: 250000
pin_map: arduino
[printer]
kinematics: delta
# This option must be "delta" for linear delta printers
# This option must be "delta" for linear delta printers.
max_velocity: 300
# Maximum velocity (in mm/s) of the toolhead relative to the
# print. This limits the velocity of the toolhead relative to the
# print - at the extreme end of the print envelope the delta axis
# steppers themselves may briefly exceed this speed by up to 3
# times. This parameter must be specified.
max_accel: 3000
# Maximum acceleration (in mm/s^2) of the toolhead relative to the
# print. This limits the acceleration of the toolhead relative to
# the print - at the extreme end of the print envelope the delta
# axis steppers may briefly exceed this acceleration by up to 3
# times. This parameter must be specified.
max_z_velocity: 200
# For delta printers this limits the maximum velocity (in mm/s) of
# moves with z axis movement. This setting can be used to reduce the
# maximum speed of up/down moves (which require a higher step rate
# than other moves on a delta printer).
# than other moves on a delta printer). The default is to use
# max_velocity for max_z_velocity.
delta_arm_length: 333.0
# Length (in mm) of the diagonal rods that connect the linear axes
# to the print head
# to the print head. This parameter must be provided.
delta_radius: 174.75
# Radius (in mm) of the horizontal circle formed by the three linear
# axis towers. This parameter may also be calculated as:
# delta_radius = smooth_rod_offset - effector_offset - carriage_offset
# This parameter must be provided.

View File

@@ -1,6 +1,8 @@
# This file serves as documentation for config parameters. One may
# copy and edit this file to configure a new cartesian style
# printer. For delta style printers, see the "example-delta.cfg" file.
# printer. For delta style printers, see the "example-delta.cfg"
# file. For corexy/h-bot style printers, see the "example-corexy.cfg"
# file.
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
# FIRST. Incorrectly configured parameters may cause damage.
@@ -19,27 +21,35 @@
# the X axis in a cartesian robot
[stepper_x]
step_pin: ar29
# Step GPIO pin (triggered high)
# Step GPIO pin (triggered high). This parameter must be provided.
dir_pin: !ar28
# Direction GPIO pin (high indicates positive direction)
# Direction GPIO pin (high indicates positive direction). This
# parameter must be provided.
enable_pin: !ar25
# Enable pin (default is enable high; use ! to indicate enable low)
# Enable pin (default is enable high; use ! to indicate enable
# low). If this parameter is not provided then the stepper motor
# driver must always be enabled.
step_distance: .0225
# Distance in mm that each step causes the axis to travel
# Distance in mm that each step causes the axis to travel. This
# parameter must be provided.
endstop_pin: ^ar0
# Endstop switch detection pin
homing_speed: 50.0
# Maximum velocity (in mm/s) of the stepper when homing
homing_retract_dist: 5.0
# Distance to backoff (in mm) before homing a second time during homing
homing_positive_dir: False
# If true, homes in a positive direction (away from zero)
homing_stepper_phases: 0
# Endstop switch detection pin. This parameter must be provided for
# the X, Y, and Z steppers on cartesian style printers.
#homing_speed: 5.0
# Maximum velocity (in mm/s) of the stepper when homing. The default
# is 5mm/s.
#homing_retract_dist: 5.0
# Distance to backoff (in mm) before homing a second time during
# homing. The default is 5mm.
#homing_positive_dir: False
# If true, homes in a positive direction (away from zero). The
# default is False.
#homing_stepper_phases: 0
# One may optionally set this to the number of phases of the stepper
# motor driver (which is the number of micro-steps multiplied by
# four). When set, the phase of the stepper driver will be used
# during homing to improve the accuracy of the endstop switch.
homing_endstop_accuracy: 0.200
#homing_endstop_accuracy: 0.200
# Sets the expected accuracy (in mm) of the endstop. This represents
# the maximum error distance the endstop may trigger (eg, if an
# endstop may occasionally trigger 100um early or up to 100um late
@@ -56,12 +66,14 @@ homing_endstop_accuracy: 0.200
# phase will be used on all subsequent homes.
position_min: -0.25
# Minimum valid distance (in mm) the user may command the stepper to
# move to
# move to. The default is 0mm.
position_endstop: 0
# Location of the endstop (in mm)
# Location of the endstop (in mm). This parameter must be provided
# for the X, Y, and Z steppers on cartesian style printers.
position_max: 200
# Maximum valid distance (in mm) the user may command the stepper to
# move to
# move to. This parameter must be provided for the X, Y, and Z
# steppers on cartesian style printers.
# The stepper_y section is used to describe the stepper controlling
# the Y axis in a cartesian robot. It has the same settings as the
@@ -99,84 +111,148 @@ step_pin: ar19
dir_pin: ar18
enable_pin: !ar25
step_distance: .004242
max_velocity: 200000
nozzle_diameter: 0.500
# Diameter of the nozzle orifice (in mm). This parameter must be
# provided.
filament_diameter: 3.500
# Diameter of the raw filament (in mm) as it enters the
# extruder. This parameter must be provided.
#max_extrude_cross_section:
# Maximum area of the cross section of an extrusion line (in
# mm^2). If a move requests an extrusion rate that would exceed this
# value it will cause an error to be returned. The default is: 4.0 *
# nozzle_diameter^2
#max_extrude_only_distance: 50.0
# Maximum length (in mm of raw filament) that an extrude only move
# may be. If an extrude only move requests a distance greater than
# this value it will cause an error to be returned. The default is
# 50mm.
#max_extrude_only_velocity:
# Maximum velocity (in mm/s) of the extruder motor for extrude only
# moves.
max_accel: 3000
# moves. If this is not specified then it is calculated to match the
# limit an XY printing move with a max_extrude_cross_section
# extrusion would have.
#max_extrude_only_accel:
# Maximum acceleration (in mm/s^2) of the extruder motor for extrude
# only moves.
#
# The remaining variables describe the extruder heater
pressure_advance: 0.0
# only moves. If this is not specified then it is calculated to
# match the limit an XY printing move with a
# max_extrude_cross_section extrusion would have.
#pressure_advance: 0.0
# The amount of raw filament to push into the extruder during
# extruder acceleration. An equal amount of filament is retracted
# during deceleration. It is measured in millimeters per
# millimeter/second
# millimeter/second. The default is 0, which disables pressure
# advance.
#pressure_advance_lookahead_time: 0.010
# A time (in seconds) to "look ahead" at future extrusion moves when
# calculating pressure advance. This is used to reduce the
# application of pressure advance during cornering moves that would
# otherwise cause retraction followed immediately by pressure
# buildup. This setting only applies if pressure_advance is
# non-zero. The default is 0.010 (10 milliseconds).
#
# The remaining variables describe the extruder heater
heater_pin: ar4
# PWM output pin controlling the heater
thermistor_pin: analog1
# Analog input pin connected to thermistor
thermistor_type: EPCOS 100K B57560G104F
# Type of thermistor (see klippy/heater.py for available types)
pullup_resistor: 4700
# The resistance (in ohms) of the pullup attached to the thermistor
# PWM output pin controlling the heater. This parameter must be
# provided.
#max_power: 1.0
# The maximum power (expressed as a value from 0.0 to 1.0) that the
# heater_pin may be set to. The value 1.0 allows the pin to be set
# fully enabled for extended periods, while a value of 0.5 would
# allow the pin to be enabled for no more than half the time. This
# setting may be used to limit the total power output (over extended
# periods) to the heater. The default is 1.0.
sensor_type: EPCOS 100K B57560G104F
# Type of sensor - this may be "EPCOS 100K B57560G104F", "ATC
# Semitec 104GT-2", or "AD595". This parameter must be provided.
sensor_pin: analog1
# Analog input pin connected to the sensor. This parameter must be
# provided.
#pullup_resistor: 4700
# The resistance (in ohms) of the pullup attached to the
# thermistor. This parameter is only valid when the sensor is a
# thermistor. The default is 4700 ohms.
#adc_voltage: 5.0
# The ADC comparison voltage. This parameter is only valid when the
# sensor is an AD595. The default is 5 volts.
control: pid
# Control algorithm (either pid or watermark)
# Control algorithm (either pid or watermark). This parameter must
# be provided.
pid_Kp: 22.2
# Kp is the "proportional" constant for the pid
# Kp is the "proportional" constant for the pid. This parameter must
# be provided for PID heaters.
pid_Ki: 1.08
# Ki is the "integral" constant for the pid
# Ki is the "integral" constant for the pid. This parameter must be
# provided for PID heaters.
pid_Kd: 114
# Kd is the "derivative" constant for the pid
pid_deriv_time: 2.0
# Kd is the "derivative" constant for the pid. This parameter must
# be provided for PID heaters.
#pid_deriv_time: 2.0
# A time value (in seconds) over which the derivative in the pid
# will be smoothed to reduce the impact of measurement noise
pid_integral_max: 255
# The maximum "windup" the integral term may accumulate
min_extrude_temp: 170
# will be smoothed to reduce the impact of measurement noise. The
# default is 2 seconds.
#pid_integral_max:
# The maximum "windup" the integral term may accumulate. The default
# is to use the same value as max_power.
#min_extrude_temp: 170
# The minimum temperature (in Celsius) at which extruder move
# commands may be issued
# commands may be issued. The default is 170 Celsius.
min_temp: 0
# Minimum temperature in Celsius (mcu will shutdown if not met)
# Minimum temperature in Celsius (mcu will shutdown if not
# met). This parameter must be provided.
max_temp: 210
# Maximum temperature (mcu will shutdown if temperature is above
# this value)
# this value). This parameter must be provided.
# The heater_bed section describes a heated bed (if present - omit
# section if not present).
[heater_bed]
heater_pin: ar3
thermistor_pin: analog0
thermistor_type: EPCOS 100K B57560G104F
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog0
control: watermark
max_delta: 2.0
# The number of degrees in Celsius above the target temperature
# before disabling the heater as well as the number of degrees below
# the target before re-enabling the heater.
#max_delta: 2.0
# On 'watermark' controlled heaters this is the number of degrees in
# Celsius above the target temperature before disabling the heater
# as well as the number of degrees below the target before
# re-enabling the heater. The default is 2 degrees Celsius.
min_temp: 0
max_temp: 110
# Extruder print fan (omit section if fan not present)
[fan]
pin: ar14
# PWM output pin controlling the heater
hard_pwm: 1
# PWM output pin controlling the fan. This parameter must be
# provided.
#hard_pwm: 0
# Set this value to force hardware PWM instead of software PWM. Set
# to 1 to force a hardware PWM at the fastest rate; set to a higher
# number (eg, 1024) to force hardware PWM with the given cycle time
# in clock ticks.
kick_start_time: 0.100
# number to force hardware PWM with the given cycle time in clock
# ticks. The default is 0 which enables software PWM with a cycle
# time of 10ms.
#kick_start_time: 0.100
# Time (in seconds) to run the fan at full speed when first enabling
# it (helps get the fan spinning)
# it (helps get the fan spinning). The default is 0.100 seconds.
# Micro-controller information
[mcu]
serial: /dev/ttyACM0
# The serial port to connect to the MCU
baud: 250000
# The baud rate to use
# The serial port to connect to the MCU. The default is /dev/ttyS0
#baud: 250000
# The baud rate to use. The default is 250000.
pin_map: arduino
# This option may be used to enable Arduino pin name aliases
# This option may be used to enable Arduino pin name aliases. The
# default is to not enable the aliases.
#restart_method: arduino
# This controls the mechanism the host will use to reset the
# micro-controller. The choices are 'arduino', 'rpi_usb', and
# 'command'. The 'arduino' method (toggle DTR; set baud to 1200) is
# common on Arduino boards and clones. The 'rpi_usb' method is
# useful on Raspberry Pi boards with micro-controllers powered over
# USB - it briefly disables power to all USB ports to accomplish a
# micro-controller reset. The 'command' method involves sending a
# Klipper command to the micro-controller so that it can reset
# itself. The default is 'arduino'.
custom:
# This option may be used to specify a set of custom
# micro-controller commands to be sent at the start of the
@@ -187,25 +263,34 @@ custom:
# The printer section controls high level printer settings
[printer]
kinematics: cartesian
# This option must be "cartesian" for cartesian printers
# This option must be "cartesian" for cartesian printers.
max_velocity: 500
# Maximum velocity (in mm/s) of the toolhead (relative to the
# print)
# print). This parameter must be specified.
max_accel: 3000
# Maximum acceleration (in mm/s^2) of the toolhead (relative to the
# print)
max_z_velocity: 250
# print). This parameter must be specified.
#max_accel_to_decel:
# A pseudo acceleration (in mm/s^2) controlling how fast the
# toolhead may go from acceleration to deceleration. It is used to
# reduce the top speed of short zig-zag moves (and thus reduce
# printer vibration from these moves). The default is half of
# max_accel.
max_z_velocity: 25
# For cartesian printers this sets the maximum velocity (in mm/s) of
# movement along the z axis. This setting can be used to restrict
# the maximum speed of the z stepper motor on cartesian printers.
# the maximum speed of the z stepper motor on cartesian
# printers. The default is to use max_velocity for max_z_velocity.
max_z_accel: 30
# For cartesian printers this sets the maximum acceleration (in
# mm/s^2) of movement along the z axis. It limits the acceleration
# of the z stepper motor on cartesian printers.
motor_off_time: 60
# of the z stepper motor on cartesian printers. The default is to
# use max_accel for max_z_accel.
#motor_off_time: 600
# Time (in seconds) of idle time before the printer will try to
# disable active motors.
junction_deviation: 0.02
# disable active motors. The default is 600 seconds.
#junction_deviation: 0.02
# Distance (in mm) used to control the internal approximated
# centripetal velocity cornering algorithm. A larger number will
# permit higher "cornering speeds" at the junction of two moves.
# permit higher "cornering speeds" at the junction of two moves. The
# default is 0.02mm.

90
config/generic-rambo.cfg Normal file
View File

@@ -0,0 +1,90 @@
# This file contains common pin mappings for RAMBo boards. To use this
# config, the firmware should be compiled for the AVR atmega2560.
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: PC0
dir_pin: PL1
enable_pin: !PA7
step_distance: .0125
endstop_pin: ^PB6
homing_speed: 50
position_endstop: 0
position_max: 200
[stepper_y]
step_pin: PC1
dir_pin: !PL0
enable_pin: !PA6
step_distance: .0125
endstop_pin: ^PB5
homing_speed: 50
position_endstop: 0
position_max: 200
[stepper_z]
step_pin: PC2
dir_pin: PL2
enable_pin: !PA5
step_distance: 0.00025
endstop_pin: ^PB4
homing_speed: 5
position_endstop: 0
position_max: 200
[extruder]
step_pin: PC3
dir_pin: PL6
enable_pin: !PA4
step_distance: .002
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PH6
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF0
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PE5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF2
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: PH5
[mcu]
serial: /dev/ttyACM0
custom:
# Turn off yellow led
set_digital_out pin=PB7 value=0
# Stepper micro-step pins
set_digital_out pin=PG1 value=1
set_digital_out pin=PG0 value=1
set_digital_out pin=PK7 value=1
set_digital_out pin=PG2 value=1
set_digital_out pin=PK6 value=1
set_digital_out pin=PK5 value=1
set_digital_out pin=PK3 value=1
set_digital_out pin=PK4 value=1
# Initialize digipot
send_spi_message pin=PD7 msg=0487 # X = ~0.75A
send_spi_message pin=PD7 msg=0587 # Y = ~0.75A
send_spi_message pin=PD7 msg=0387 # Z = ~0.75A
send_spi_message pin=PD7 msg=00A5 # E0
send_spi_message pin=PD7 msg=017D # E1
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100

74
config/generic-ramps.cfg Normal file
View File

@@ -0,0 +1,74 @@
# This file contains common pin mappings for RAMPS (v1.3 and later)
# boards. RAMPS boards typically use a firmware compiled for the AVR
# atmega2560 (though other AVR chips are also possible).
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: ar54
dir_pin: ar55
enable_pin: !ar38
step_distance: .0125
endstop_pin: ^ar3
homing_speed: 50
position_endstop: 0
position_max: 200
[stepper_y]
step_pin: ar60
dir_pin: !ar61
enable_pin: !ar56
step_distance: .0125
endstop_pin: ^ar14
homing_speed: 50
position_endstop: 0
position_max: 200
[stepper_z]
step_pin: ar46
dir_pin: ar48
enable_pin: !ar62
step_distance: 0.00025
endstop_pin: ^ar18
homing_speed: 5
position_endstop: 0
position_max: 200
[extruder]
step_pin: ar26
dir_pin: ar28
enable_pin: !ar24
step_distance: .002
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: ar10
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog13
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: ar8
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog14
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: ar9
[mcu]
serial: /dev/ttyACM0
pin_map: arduino
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100

View File

@@ -48,12 +48,12 @@ step_pin: PC3
dir_pin: PL6
enable_pin: !PA4
step_distance: .004242
max_velocity: 200000
max_accel: 3000
nozzle_diameter: 0.350
filament_diameter: 1.750
pressure_advance: 0.07
heater_pin: PH6
thermistor_pin: PF0
thermistor_type: EPCOS 100K B57560G104F
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF0
control: pid
pid_Kp: 7.0
pid_Ki: 0.1
@@ -63,19 +63,17 @@ max_temp: 210
[heater_bed]
heater_pin: PE5
thermistor_pin: PF2
thermistor_type: EPCOS 100K B57560G104F
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF2
control: watermark
min_temp: 0
max_temp: 100
[fan]
pin: PH5
hard_pwm: 1
[mcu]
serial: /dev/ttyACM0
baud: 250000
custom:
# Nozzle fan
set_pwm_out pin=PH3 cycle_ticks=1 value=155
@@ -101,6 +99,5 @@ custom:
kinematics: cartesian
max_velocity: 500
max_accel: 3000
max_z_velocity: 250
max_z_velocity: 25
max_z_accel: 30
motor_off_time: 600

View File

@@ -17,7 +17,7 @@ host architectures. The build arranges for includes of
src/generic/somefile.h).
The **klippy/** directory contains the C and Python source for the
host part of the firmware.
host part of the software.
The **lib/** directory contains external 3rd-party library code that
is necessary to build some targets.
@@ -54,13 +54,13 @@ 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_timer() (located in
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**), but this just calls sched_timer_kick()
located in **src/sched.c**. The timer interrupt leads to execution of
schedule timer functions. Timer functions always run with interrupts
(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.
@@ -92,8 +92,8 @@ 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
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).
@@ -106,3 +106,106 @@ 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. 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()`
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
the timing of printing actions. The codepath for a move is:
`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).
* 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 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
look-ahead 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() ->
stepcompress_push_const()`, 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 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 command.c which parses the command and calls
`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;`
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.

View File

@@ -1,17 +1,18 @@
The Klippy host code has some tools to help in debugging the firmware.
The Klippy host code has some tools to help in debugging.
Translating gcode files to firmware commands
============================================
Translating gcode files to micro-controller commands
====================================================
The Klippy host code can run in a batch mode to produce the low-level
firmware commands associated with a gcode file. Inspecting these
low-level firmware commands is useful when trying to understand the
micro-controller commands associated with a gcode file. Inspecting
these low-level commands is useful when trying to understand the
actions of the low-level hardware. It can also be useful to compare
the difference in firmware commands after a code change.
the difference in micro-controller commands after a code change.
To run Klippy in this batch mode, there is a one time step necessary
to generate the firmware "data dictionary". This is done by compiling
the firmware code to obtain the **out/klipper.dict** file:
to generate the micro-controller "data dictionary". This is done by
compiling the micro-controller code to obtain the **out/klipper.dict**
file:
```
make menuconfig
@@ -34,13 +35,13 @@ output. This output can be translated to readable text with:
```
The resulting file **test.txt** contains a human readable list of
firmware commands.
micro-controller commands.
The batch mode disables certain response / request commands in order
to function. As a result, there will be some differences between
actual firmware commands and the above output. The generated data is
useful for testing and inspection; it is not useful for sending to a
real micro-controller.
actual commands and the above output. The generated data is useful for
testing and inspection; it is not useful for sending to a real
micro-controller.
Testing with simulavr
=====================
@@ -74,25 +75,20 @@ cd /patch/to/klipper
make menuconfig
```
and compile the firmware for an AVR atmega644p, disable the AVR
watchdog timer, and set the MCU frequency to 20000000. Then one can
compile Klipper (run `make`) and then start the simulation with:
and compile the micro-controller software for an AVR atmega644p,
disable the AVR watchdog timer, and set the MCU frequency
to 20000000. Then one can compile Klipper (run `make`) and then start
the simulation with:
```
PYTHONPATH=/path/to/simulavr/src/python/ ./scripts/avrsim.py -m atmega644 -s 20000000 -b 250000 out/klipper.elf
```
It may be necessary to create a python virtual environment to run
Klippy on the target machine. To do so, run:
```
virtualenv ~/klippy-env
~/klippy-env/bin/pip install cffi==1.6.0 pyserial==2.7
```
Then, with simulavr running in another window, one can run the
following to read gcode from a file (eg, "test.gcode"), process it
with Klippy, and send it to Klipper running in simulavr:
with Klippy, and send it to Klipper running in simulavr (see
[installation](Installation.md) for the steps necessary to build the
python virtual environment):
```
~/klippy-env/bin/python ./klippy/klippy.py config/avrsim.cfg -i test.gcode -v
@@ -129,3 +125,26 @@ Klipper source code). To do so, run:
```
~/klippy-env/bin/python ./klippy/console.py /tmp/pseudoserial 250000
```
Generating load graphs
======================
The Klippy log file (/tmp/klippy.log) stores statistics on bandwidth,
micro-controller load, and host buffer load. It can be useful to graph
these statistics after a print.
To generate a graph, a one time step is necessary to install the
"matplotlib" package:
```
sudo apt-get update
sudo apt-get install python-matplotlib
```
Then graphs can be produced with:
```
~/klipper/scripts/graphstats.py /tmp/klippy.log loadgraph.png
```
One can then view the resulting **loadgraph.png** file.

View File

@@ -1,5 +1,4 @@
Klipper is an experimental 3d printer firmware. It has several
compelling features:
Klipper has several compelling features:
* High precision stepper movement. Klipper utilizes an application
processor (such as a low-cost Raspberry Pi) when calculating printer
@@ -7,17 +6,16 @@ compelling features:
stepper motor, it compresses those events, transmits them to the
micro-controller, and then the micro-controller executes each event
at the requested time. Each stepper event is scheduled with a
precision of no less than 50 micro-seconds. The software does not
use kinematic estimations (such as the Bresenham algorithm) -
instead it calculates precise step times based on the physics of
acceleration and the physics of the machine kinematics. More precise
stepper movement translates to quieter and more stable printer
operation.
precision of 25 micro-seconds or better. The software does not use
kinematic estimations (such as the Bresenham algorithm) - instead it
calculates precise step times based on the physics of acceleration
and the physics of the machine kinematics. More precise stepper
movement translates to quieter and more stable printer operation.
* Best in class performance. Klipper is able to achieve high stepping
rates on both new and old micro-controllers. Even an old 8bit AVR
micro-controller can obtain rates up to 150K steps per second. On
more recent ARM micro-controllers, rates over 350K steps per second
micro-controller can obtain rates over 175K steps per second. On
more recent ARM micro-controllers, rates over 450K steps per second
are possible. Higher stepper rates enable higher print
velocities. The stepper event timing remains precise even at high
speeds which improves overall stability.
@@ -34,16 +32,21 @@ compelling features:
micro-controller architectures as well.
* Simpler code. Klipper uses a very high level language (Python) for
most code. The kinematics algorithms, the gcode parsing, the heating
and thermistor algorithms, etc. are all written in Python. This
makes it easier to develop new functionality.
most code. The kinematics algorithms, the G-code parsing, the
heating and thermistor algorithms, etc. are all written in
Python. This makes it easier to develop new functionality.
* Advanced features. Klipper implements the "pressure advance"
algorithm for extruders. When properly tuned, pressure advance
reduces extruder ooze. Klipper also implements a novel "stepper
phase endstop" algorithm that can dramatically improve the accuracy
of typical endstop switches. When properly tuned it can improve a
print's first layer bed adhesion.
* Advanced features:
* Klipper implements the "pressure advance" algorithm for
extruders. When properly tuned, pressure advance reduces extruder
ooze.
* Klipper also implements a novel "stepper phase endstop" algorithm
that can dramatically improve the accuracy of typical endstop
switches. When properly tuned it can improve a print's first layer
bed adhesion.
* Support for limiting the top speed of short "zigzag" moves to
reduce printer vibration and noise. See the
[kinematics](Kinematics.md) document for more information.
To get started with Klipper, read the [installation](Installation.md)
guide.
@@ -65,12 +68,12 @@ Klipper supports many standard 3d printer features:
gradually accelerate from standstill to cruising speed and then
decelerate back to a standstill.
* "Lookahead" support. The incoming stream of G-Code movement commands
are queued and analyzed - the acceleration between movements in a
similar direction will be optimized to reduce print stalls and
improve overall print time.
* "Look-ahead" support. The incoming stream of G-Code movement
commands are queued and analyzed - the acceleration between
movements in a similar direction will be optimized to reduce print
stalls and improve overall print time.
* Support for both delta printers and cartesian style printers.
* Support for cartesian, delta, and corexy style printers.
Step Benchmarks
===============
@@ -80,6 +83,6 @@ represent total number of steps per second on the micro-controller.
| Micro-controller | 1 stepper active | 3 steppers active |
| ----------------- | ---------------- | ----------------- |
| 20Mhz AVR | 158.7K | 103K |
| 16Mhz AVR | 126.9K | 82K |
| Arduino Due (ARM) | 352.9K | 288K |
| 20Mhz AVR | 177K | 117K |
| 16Mhz AVR | 140K | 93K |
| Arduino Due (ARM) | 462K | 406K |

View File

@@ -1,278 +0,0 @@
This document provides high-level information on common firmware
commands. It is not an authoritative reference for these commands, nor
is it an exclusive list of all available firmware commands.
This document may be useful for users needing to configure a set of
hardware actions that their printer may require at startup (via the
"custom" field in the printer config file), and it may be useful for
developers wishing to obtain a high-level feel for available firmware
commands.
See the [protocol](Protocol.md) document for more information on the
format of commands and their low-level transmission. The commands here
are described using their "printf" style syntax - for those unfamiliar
with that format, just note that where a '%...' sequence is seen it
should be replaced with an actual integer.
Startup Commands
================
It may be necessary to take certain one-time actions to configure the
micro-controller and its peripherals. This section lists common
commands available for that purpose. Unlike other firmware commands,
these commands run as soon as they are received by the firmware and
they do not require any particular setup.
These commands are most useful in the "custom" block of the "mcu"
section of the printer configuration file. This feature is typically
used to configure the initial settings of LEDs, to configure
micro-stepping pins, to configure a digipot, etc.
Several of these commands will take a "pin=%u" parameter. The
low-level firmware uses integer encodings of the hardware pin numbers,
but to make things more readable the host will translate human
readable pin names (eg, "PA3") to their equivalent integer
encodings. By convention, any parameter named "pin" or that has a
"_pin" suffix will use pin name translation by the host.
Common startup commands:
* set_digital_out pin=%u value=%c : This command immediately
configures the given pin as a digital out GPIO and it sets it to
either a low level (value=0) or a high level (value=1). This command
may be useful for configuring the initial value of LEDs and for
configuring the initial value of stepper driver micro-stepping pins.
* set_pwm_out pin=%u cycle_ticks=%u value=%c : This command will
immediately configure the given pin to use hardware based
pulse-width-modulation (PWM) with the given number of
cycle_ticks. The "cycle_ticks" is the number of MCU clock ticks each
power on and power off cycle should last. A cycle_ticks value of 1
can be used to request the fastest possible cycle time. The "value"
parameter is between 0 and 255 with 0 indicating a full off state
and 255 indicating a full on state. This command may be useful for
enabling CPU and nozzle cooling fans.
* send_spi_message pin=%u msg=%*s : This command can be used to
transmit messages to a serial-peripheral-interface (SPI) component
connected to the micro-controller. It has been used to configure the
startup settings of AD5206 digipots. The 'pin' parameter specifies
the chip select line to use during the transmission. The 'msg'
indicates the binary message to transmit to the given chip.
Firmware configuration
======================
Most commands in the firmware require an initial setup before they can
be successfully invoked. This section provides a high-level overview
of the micro-controller configuration process. This section and the
following sections are likely only of interest to developers
interested in the internal details of Klipper.
When the host first connects to the firmware it always starts by
obtaining the firmware's data dictionary (see [protocol](Protocol.md)
for more information). After the data dictionary is obtained the host
will check if the firmware is in a "configured" state and configure it
if not. Configuration involves the following phases:
* get_config : The host starts by checking if the firmware is already
configured. The firmware responds to this command with a "config"
response message. At micro-controller power-on the firmware always
starts in an unconfigured state. It remains in this state until the
host completes the configuration processes (by issuing a
finalize_config command). If the firmware is already configured (and
is configured with the desired settings) from a previous
host/firmware session then no further action is needed by the host
and the configuration process ends successfully.
* allocate_oids count=%c : This command is issued to inform the
firmware the maximum number of object-ids (oid) that the host
requires. It is only valid to issue this command once. An oid is an
integer identifier allocated to each stepper, each endstop, and each
schedulable gpio pin. The host determines in advance the number of
oids it will require to operate the hardware and passes this to the
firmware so that the firmware may allocate sufficient memory to
store a mapping from oid to internal firmware object.
* config_XXX oid=%c ... : By convention any command starting with the
"config_" prefix creates a new firmware object and assigns the given
oid to it. For example, the config_digital_out command will
configure the specified pin as a digital output GPIO and create an
internal object that the host can use to schedule changes to the
given GPIO. The oid parameter passed into the config command is
selected by the host and must be between zero and the maximum count
supplied in the allocate_oids command. The config commands may only
be run when the firmware is not in a configured state (ie, prior to
the host sending finalize_config) and after the allocate_oids
command has been sent.
* finalize_config crc=%u : The finalize_config command transitions the
firmware from an unconfigured state to a configured state. The crc
parameter passed to the firmware is stored in the firmware and
provided back to the host in "config" response messages. By
convention, the host takes a 32bit CRC of the firmware configuration
it will request and at the start of subsequent host/firmware
communication sessions it checks that the CRC stored in the firmware
exactly matches its desired CRC. If the CRC does not match then the
host knows the firmware has not been configured in the state desired
by the host.
Common firmware objects
-----------------------
This section lists some commonly used config commands.
* config_digital_out oid=%c pin=%u default_value=%c max_duration=%u :
This command creates an internal firmware object for the given GPIO
'pin'. The pin will be configured in digital output mode and set to
an initial value as specified by 'default_value' (0 for low, 1 for
high). Creating a digital_out object allows the host to schedule
GPIO updates for the given pin at specified times (see the
schedule_digital_out command described below). Should the firmware
go into shutdown mode then all configured digital_out objects will
be set back to their default values. The 'max_duration' parameter is
used to implement a safety check - if it is non-zero then it is the
maximum number of clock ticks that the host may set the given GPIO
to a non-default value without further updates. For example, if the
default_value is zero and the max_duration is 16000 then if the host
sets the gpio to a value of one then it must schedule another update
to the gpio pin (to either zero or one) within 16000 clock
ticks. This safety feature can be used with heater pins to ensure
the host does not set the heater to a value of one and then go
off-line.
* config_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
max_duration=%u : This command creates an internal object for
hardware based PWM pins that the host may schedule updates for. Its
usage is analogous to config_digital_out - see the description of
the 'set_pwm_out' and 'config_digital_out' commands for parameter
description.
* config_soft_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
max_duration=%u : This command creates an internal firmware object
for software implemented PWM. Unlike hardware pwm pins, a software
pwm object does not require any special hardware support (other than
the ability to configure the pin as a digital output GPIO). Because
the output switching is implemented in the software of the firmware,
it is recommended that the cycle_ticks parameter correspond to a
time of 10ms or greater. See the description of the 'set_pwm_out'
and 'config_digital_out' commands for parameter description.
* config_analog_in oid=%c pin=%u : This command is used to configure a
pin in analog input sampling mode. Once configured, the pin can be
sampled at regular interval using the query_analog_in command (see
below).
* config_stepper oid=%c step_pin=%c dir_pin=%c min_stop_interval=%u
invert_step=%c : This command creates an internal stepper
object. The 'step_pin' and 'dir_pin' parameters specify the step and
direction pins respectively; this command will configure them in
digital output mode. The 'invert_step' parameter specifies whether a
step occurs on a rising edge (invert_step=0) or falling edge
(invert_step=1). The 'min_stop_interval' implements a safety
feature - it is checked when the firmware finishes all moves for a
stepper - if it is non-zero it specifies the minimum number of clock
ticks since the last step. It is used as a check on the maximum
stepper velocity that a stepper may have before stopping.
* config_end_stop oid=%c pin=%c pull_up=%c stepper_oid=%c : This
command creates an internal "endstop" object. It is used to specify
the endstop pins and to enable "homing" operations (see the
end_stop_home command below). The command will configure the
specified pin in digital input mode. The 'pull_up' parameter
determines whether hardware provided pullup resistors for the pin
(if available) will be enabled. The 'stepper_oid' parameter
specifies the oid of an associated stepper for the given endstop -
it is used during homing operations.
Common commands
===============
This section lists some commonly used run-time commands. It is likely
only of interest to developers looking to gain insight into Klippy.
* schedule_digital_out oid=%c clock=%u value=%c : This command will
schedule a change to a digital output GPIO pin at the given clock
time. To use this command a 'config_digital_out' command with the
same 'oid' parameter must have been issued during firmware
configuration.
* schedule_pwm_out oid=%c clock=%u value=%c : Schedules a change to a
hardware PWM output pin. See the 'schedule_digital_out' and
'config_pwm_out' commands for more info.
* schedule_soft_pwm_out oid=%c clock=%u value=%c : Schedules a change
to a software PWM output pin. See the 'schedule_digital_out' and
'config_soft_pwm_out' commands for more info.
* query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c
rest_ticks=%u min_value=%hu max_value=%hu : This command sets up a
recurring schedule of analog input samples. To use this command a
'config_analog_in' command with the same 'oid' parameter must have
been issued during firmware configuration. The samples will start as
of 'clock' time, it will report on the obtained value every
'rest_ticks' clock ticks, it will over-sample 'sample_count' number
of times, and it will pause 'sample_ticks' number of clock ticks
between over-sample samples. The 'min_value' and 'max_value'
parameters implement a safety feature - the firmware will verify the
sampled value (after any oversampling) is always between the
supplied range. This is intended for use with pins attached to
thermistors controlling heaters - it can be used to check that a
heater is within a temperature range.
* get_status : This command causes the firmware to generate a "status"
response message. The host sends this command once a second to
obtain the value of the micro-controller clock and to estimate the
drift between host and micro-controller clocks. It enables the host
to accurately estimate the micro-controller clock.
Stepper commands
----------------
* queue_step oid=%c interval=%u count=%hu add=%hi : This command
schedules 'count' number of steps for the given stepper, with
'interval' number of clock ticks between each step. The first step
will be 'interval' number of clock ticks since the last scheduled
step for the given stepper. If 'add' is non-zero then the interval
will be adjusted by 'add' amount after each step. This command
appends the given interval/count/add sequence to a per-stepper
queue. There may be hundreds of these sequences queued during normal
operation. New sequence are appended to the end of the queue and as
each sequence completes its 'count' number of steps it is popped
from the front of the queue. This system allows the firmware to
queue potentially hundreds of thousands of steps - all with reliable
and predictable schedule times.
* set_next_step_dir oid=%c dir=%c : This command specifies the value
of the dir_pin that the next queue_step command will use.
* reset_step_clock oid=%c clock=%u : Normally, step timing is relative
to the last step for a given stepper. This command resets the clock
so that the next step is relative to the supplied 'clock' time. The
host usually only sends this command at the start of a print.
* end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c : This
command is used during stepper "homing" operations. To use this
command a 'config_end_stop' command with the same 'oid' parameter
must have been issued during firmware configuration. When invoked,
the firmware will sample the endstop pin every 'rest_ticks' clock
ticks and check if it has a value equal to 'pin_value'. If the value
matches then the movement queue for the associated stepper will be
cleared and the stepper will come to an immediate halt. The host
uses this command to implement homing - the host instructs the
endstop to sample for the endstop trigger and then it issues a
series of queue_step commands to the stepper to move it towards the
endstop. Once the stepper hits the endstop, the trigger will be
detected, the movement halted, and the host notified.
### Move queue
Each queue_step command utilizes an entry in the firmware "move
queue". The firmware allocates this queue when it receives the
"finalize_config" command, and it reports the number of available
queue entries in "config" response messages.
It is the responsibility of the host to ensure that there is available
space in the queue before sending a queue_step command. The host does
this by calculating when each queue_step command completes and
scheduling new queue_step commands accordingly.

View File

@@ -1,12 +1,15 @@
Klipper is currently in an experimental state. These instructions
assume the software will run on a Raspberry Pi computer in conjunction
with OctoPrint. Klipper supports Atmel ATmega based micro-controllers
and Arduino Due (Atmel SAM3x8e ARM micro-controllers) printers.
These instructions assume the software will run on a Raspberry Pi
computer in conjunction with OctoPrint. It is recommended that a
Raspberry Pi 2 or Raspberry Pi 3 computer be used as the host
machine.
It is recommended that a Raspberry Pi 2 or Raspberry Pi 3 computer be
used as the host. The software will run on a first generation
Raspberry Pi, but the combined load of OctoPrint, Klipper, and a web
cam (if applicable) can overwhelm its CPU leading to print stalls.
It should be possible to run the Klipper host software on any computer
running a recent Linux distribution, but doing so will require Linux
admin knowledge to translate these installation instructions to the
particulars of that machine.
Klipper currently supports Atmel ATmega based micro-controllers and
Arduino Due (Atmel SAM3x8e ARM micro-controller) printers.
Prepping an OS image
====================
@@ -16,40 +19,29 @@ Raspberry Pi computer. Use OctoPi v0.13.0 or later - see the
[octopi releases](https://github.com/guysoft/OctoPi/releases) for
release information. One should verify that OctoPi boots and that the
OctoPrint web server works. After connecting to the OctoPrint web
page, follow the prompt to upgrade OctoPrint to v1.3.0 or later.
page, follow the prompt to upgrade OctoPrint to v1.3.2 or later.
After installing OctoPi and upgrading OctoPrint, ssh into the target
machine (ssh pi@octopi -- password is "raspberry") and run the
following commands:
```
sudo apt-get update
sudo apt-get install libncurses-dev
sudo apt-get install avrdude gcc-avr binutils-avr avr-libc # AVR toolchain
sudo apt-get install bossa-cli libnewlib-arm-none-eabi # ARM toolchain
```
The host software (Klippy) requires a one-time setup - run as the
regular "pi" user:
```
virtualenv ~/klippy-env
~/klippy-env/bin/pip install cffi==1.6.0 pyserial==3.2.1 greenlet==0.4.10
```
Building Klipper
================
To obtain Klipper, run the following command on the target machine:
```
git clone https://github.com/KevinOConnor/klipper
cd klipper/
./klipper/scripts/install-octopi.sh
```
The above will download Klipper, install some system dependencies,
setup Klipper to run at system startup, and start the Klipper host
software. It will require an internet connection and it may take a few
minutes to complete.
Building and flashing the micro-controller
==========================================
To compile the micro-controller code, start by configuring it:
```
cd ~/klipper/
make menuconfig
```
@@ -60,54 +52,39 @@ configured, run:
make
```
Ignore any warnings you may see about "misspelled signal handler" (it
is due to a bug fixed in gcc v4.8.3).
Installing Klipper on an AVR micro-controller
---------------------------------------------
The avrdude package can be used to install the micro-controller code
on an AVR ATmega chip. The exact syntax of the avrdude command is
different for each micro-controller. The following is an example
command for atmega2560 chips:
Finally, for common micro-controllers, the code can be flashed with:
```
example-only$ avrdude -C/etc/avrdude.conf -v -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/home/pi/klipper/out/klipper.elf.hex:i
sudo service klipper stop
make flash FLASH_DEVICE=/dev/ttyACM0
sudo service klipper start
```
Installing Klipper on an Arduino Due
------------------------------------
Configuring Klipper
===================
Klipper currently uses the Arduino Due USB programming port (it will
not work when connected to the application USB port). The programming
port is the USB port closest to the power supply. To flash Klipper to
the Due connect it to the host machine and run:
```
stty -F /dev/ttyACM0 1200
bossac -i -p ttyACM0 -R -e -w -v -b ~/klipper/out/klipper.bin
```
Setting up the printer configuration
====================================
It is necessary to configure the printer. This is done by modifying a
configuration file that resides on the host. Start by copying an
example configuration and editing it. For example:
The Klipper configuration is stored in a text file on the Raspberry
Pi. Take a look at the example config files in the
[config directory](../config/). The
[example.cfg](../config/example.cfg) file contains documentation on
command parameters and it can also be used as an initial config file
template. However, for most printers, one of the other config files
may be a more concise starting point. The next step is to copy and
edit one of these config files - for example:
```
cp ~/klipper/config/example.cfg ~/printer.cfg
nano printer.cfg
nano ~/printer.cfg
```
Make sure to look at and update each setting that is appropriate for
Make sure to review and update each setting that is appropriate for
the hardware.
Configuring OctoPrint to use Klippy
===================================
Configuring OctoPrint to use Klipper
====================================
The OctoPrint web server needs to be configured to communicate with
the Klippy host software. Using a web-browser, login to the OctoPrint
the Klipper host software. Using a web browser, login to the OctoPrint
web page, and navigate to the Settings tab. Then configure the
following items:
@@ -115,40 +92,30 @@ Under "Serial Connection" in "Additional serial ports" add
"/tmp/printer". Then click "Save".
Enter the Settings tab again and under "Serial Connection" change the
"Serial Port" setting to "/tmp/printer". Change the Baudrate field to
250000 (this buad rate field is not related to the firmware baudrate
and may be safely left at 250000). Unselect the "Not only cancel
ongoing prints but also disconnect..." checkbox.
"Serial Port" setting to "/tmp/printer". Unselect the "Not only cancel
ongoing prints but also disconnect..." checkbox. Click "Save".
Under the "Features" tab, unselect "Enable SD support". Then click
"Save".
Running the host software
=========================
The host software is executed by running the following as the regular
"pi" user:
```
~/klippy-env/bin/python ~/klipper/klippy/klippy.py ~/printer.cfg -l /tmp/klippy.log < /dev/null > /dev/null 2>&1 &
```
Once Klippy is running, use a web-browser and navigate to the
OctoPrint web site. Under the "Connection" tab (on the left of the
main page) make sure the "Serial Port" is set to "/tmp/printer" and
From the main page, under the "Connection" section (at the top left of
the page) make sure the "Serial Port" is set to "/tmp/printer" and
click "Connect". (If "/tmp/printer" is not an available selection then
try reloading the page.)
Once connected, navigate to the "Terminal" tab and type "status"
(without the quotes) into the command entry box and click "Send". If
the Klippy config file was successfully read, and the micro-controller
was successfully found and configured, then this command will report
that the printer is ready. Klippy reports error messages via this
terminal tab. The "status" command can be used to re-report error
messages.
(without the quotes) into the command entry box and click "Send". The
terminal window will likely report there is an error opening the
config file - issue a "restart" command in the OctoPrint terminal to
load the config. A "status" command will report the printer is ready
if the Klipper config file is successfully read and the
micro-controller is successfully found and configured. It is not
unusual to have configuration errors during the initial setup - update
the printer config file and issue "restart" until "status" reports the
printer is ready.
In addition to common g-code commands, Klippy supports a few extended
commands - "status" is an example of one of these commands. Use the
"help" command to get a list of other extended commands. In
particular, note the "restart" command - use this command to reload
the Klippy config file after any changes.
Klipper reports error messages via the OctoPrint terminal tab. The
"status" command can be used to re-report error messages. The default
Klipper startup script also places a log in **/tmp/klippy.log** which
provides more detailed information.
In addition to common g-code commands, Klipper supports a few extended
commands - "status" and "restart" are examples of these commands. Use
the "help" command to get a list of other extended commands.

293
docs/Kinematics.md Normal file
View File

@@ -0,0 +1,293 @@
This document provides an overview of how Klipper implements robot
motion (its [kinematics](https://en.wikipedia.org/wiki/Kinematics)).
The contents may be of interest to both developers interested in
working on the Klipper software as well as users interested in better
understanding the mechanics of their machines.
Acceleration
============
Klipper implements a constant acceleration scheme whenever the print
head changes velocity - the velocity is gradually changed to the new
speed instead of suddenly jerking to it. Klipper always enforces
acceleration between the tool head and the print. The filament leaving
the extruder can be quite fragile - rapid jerks and/or extruder flow
changes lead to poor quality and poor bed adhesion. Even when not
extruding, if the print head is at the same level as the print then
rapid jerking of the head can cause disruption of recently deposited
filament. Limiting speed changes of the print head (relative to the
print) reduces risks of disrupting the print.
It is also important to enforce a maximum acceleration of the stepper
motors to ensure they do not skip or put excessive stress on the
machine. Klipper limits the acceleration of each stepper by virtue of
limiting the acceleration of the print head. Enforcing acceleration at
the print head naturally also enforces acceleration at the steppers
that control that print head (the inverse is not always true).
Klipper implements constant acceleration. The key formula for constant
acceleration is:
```
velocity(time) = start_velocity + accel*time
```
Trapezoid generator
===================
Klipper uses a traditional "trapezoid generator" to model the motion
of each move - each move has a start speed, it accelerates to a
cruising speed at constant acceleration, it cruises at a constant
speed, and then decelerates to the end speed using constant
acceleration.
![trapezoid](img/trapezoid.svg.png)
It's called a "trapezoid generator" because a velocity diagram of the
move looks like a trapezoid.
The cruising speed is always greater than or equal to both the start
speed and the end speed. The acceleration phase may be of zero
duration (if the start speed is equal to the cruising speed), the
cruising phase may be of zero duration (if the move immediately starts
decelerating after acceleration), and/or the deceleration phase may be
of zero duration (if the end speed is equal to the cruising speed).
![trapezoids](img/trapezoids.svg.png)
Look-ahead
==========
The "look-ahead" system is used to determine cornering speeds between
moves.
Consider the following two moves contained on an XY plane:
![corner](img/corner.svg.png)
In the above situation it is possible to fully decelerate after the
first move and then fully accelerate at the start of the next move,
but that is not ideal as all that acceleration and deceleration would
greatly increase the print time and the frequent changes in extruder
flow would result in poor print quality.
To solve this, the "look-ahead" mechanism queues multiple incoming
moves and analyzes the angles between moves to determine a reasonable
speed that can be obtained during the "junction" between two moves. If
the next move is nearly in the same direction then the head need only
slow down a little (if at all).
![lookahead](img/lookahead.svg.png)
However, if the next move forms an acute angle (the head is going to
travel in nearly a reverse direction on the next move) then only a
small junction speed is permitted.
![lookahead](img/lookahead-slow.svg.png)
The junction speeds are determined using "approximated centripetal
acceleration". Best
[described by the author](https://onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/).
Klipper implements look-ahead between moves contained in the XY plane
that have similar extruder flow rates. Other moves are relatively rare
and implementing look-ahead between them is unnecessary.
Key formula for look-ahead:
```
end_velocity^2 = start_velocity^2 + 2*accel*move_distance
```
Smoothed look-ahead
-------------------
Klipper also implements a mechanism for smoothing out the motions of
short "zigzag" moves. Consider the following moves:
![zigzag](img/zigzag.svg.png)
In the above, the frequent changes from acceleration to deceleration
can cause the machine to vibrate which causes stress on the machine
and increases the noise. To reduce this, Klipper tracks both regular
move acceleration as well as a virtual "acceleration to deceleration"
rate. Using this system, the top speed of these short "zigzag" moves
are limited to smooth out the printer motion:
![smoothed](img/smoothed.svg.png)
Specifically, the code calculates what the velocity of each move would
be if it were limited to this virtual "acceleration to deceleration"
rate (half the normal acceleration rate by default). In the above
picture the dashed gray lines represent this virtual acceleration rate
for the first move. If a move can not reach its full cruising speed
using this virtual acceleration rate then its top speed is reduced to
the maximum speed it could obtain at this virtual acceleration
rate. For most moves the limit will be at or above the move's existing
limits and no change in behavior is induced. For short zigzag moves,
however, this limit reduces the top speed. Note that it does not
change the actual acceleration within the move - the move continues to
use the normal acceleration scheme up to its adjusted top-speed.
Generating steps
================
Once the look-ahead process completes, the print head movement for the
given move is fully known (time, start position, end position,
velocity at each point) and it is possible to generate the step times
for the move. This process is done within "kinematic classes" in the
Klipper code. Outside of these kinematic classes, everything is
tracked in millimeters, seconds, and in cartesian coordinate space.
It's the task of the kinematic classes to convert from this generic
coordinate system to the hardware specifics of the particular printer.
In general, the code determines each step time by first calculating
where along the line of movement the head would be if a step is
taken. It then calculates what time the head should be at that
position. Determining the time along the line of movement can be done
using the formulas for constant acceleration and constant velocity:
```
time = sqrt(2*distance/accel + (start_velocity/accel)^2) - start_velocity/accel
time = distance/cruise_velocity
```
Cartesian Robots
----------------
Generating steps for cartesian printers is the simplest case. The
movement on each axis is directly related to the movement in cartesian
space.
Delta Robots
------------
To generate step times on Delta printers it is necessary to correlate
the movement in cartesian space with the movement on each stepper
tower.
To simplify the math, for each stepper tower, the code calculates the
location of a "virtual tower" that is along the line of movement.
This virtual tower is chosen at the point where the line of movement
(extended infinitely in both directions) would be closest to the
actual tower.
![delta-tower](img/delta-tower.svg.png)
It is then possible to calculate where the head will be along the line
of movement after each step is taken on the virtual tower.
![virtual-tower](img/virtual-tower.svg.png)
The key formula is Pythagoras's theorem:
```
distance_to_tower^2 = arm_length^2 - tower_height^2
```
One complexity is that if the print head passes the virtual tower
location then the stepper direction must be reversed. In this case
forward steps will be taken at the start of the move and reverse steps
will be taken at the end of the move.
### Delta movements beyond simple XY plane ###
Movement calculation is more complicated if a single move contains
both XY movement and Z movement. These moves are rare, but they must
still be handled correctly. A virtual tower along the line of movement
is still calculated, but in this case the tower is not at a 90 degree
angle relative to the line of movement:
![xy+z-tower](img/xy+z-tower.svg.png)
The code continues to calculate step times using the same general
scheme as delta moves within an XY plane, but the slope of the tower
must also be used in the calculations.
Should the move contain only Z movement (ie, no XY movement at all)
then the same math is used - just in this case the tower is parallel
to the line of movement.
### Stepper motor acceleration limits ###
With delta kinematics it is possible for a move that is accelerating
in cartesian space to require an acceleration on a particular stepper
motor greater than the move's acceleration. This can occur when a
stepper arm is more horizontal than vertical and the line of movement
is near that stepper's tower.
Klipper does enforce a maximum ceiling on stepper acceleration that is
three times the maximum acceleration of a move in cartesian
space. (Similarly, the maximum velocity of the stepper is limited to
three times the maximum move velocity.) In order to enforce this
limit, moves at the extreme edge of the build envelope (where a
stepper arm may be nearly horizontal) will have a lower maximum
acceleration and velocity.
Extruder kinematics
-------------------
Klipper implements extruder motion in its own kinematic class. Since
the timing and speed of each print head movement is fully known for
each move, it's possible to calculate the step times for the extruder
independently from the step time calculations of the print head
movement.
Basic extruder movement is simple to calculate. The step time
generation uses the same constant acceleration and constant velocity
formulas that cartesian robots use.
### Pressure advance ###
Experimentation has shown that it's possible to improve the modeling
of the extruder beyond the basic extruder formula. In the ideal case,
as an extrusion move progresses, the same volume of filament should be
deposited at each point along the move and there should be no volume
extruded after the move. Unfortunately, it's common to find that the
basic extrusion formulas cause too little filament to exit the
extruder at the start of extrusion moves and for excess filament to
extrude after extrusion ends. This is often referred to as "ooze".
![ooze](img/ooze.svg.png)
The "pressure advance" system attempts to account for this by using a
different model for the extruder. Instead of naively believing that
each mm^3 of filament fed into the extruder will result in that amount
of mm^3 immediately exiting the extruder, it uses a model based on
pressure. Pressure increases when filament is pushed into the extruder
(as in [Hooke's law](https://en.wikipedia.org/wiki/Hooke%27s_law)) and
the pressure necessary to extrude is dominated by the flow rate
through the nozzle orifice (as in
[Poiseuille's law](https://en.wikipedia.org/wiki/Poiseuille_law)). The
key idea is that the relationship between filament, pressure, and flow
rate can be modeled using a linear coefficient:
```
extra_filament = pressure_advance_coefficient * extruder_velocity
```
See the [pressure advance](Pressure_Advance.md) document for
information on how to find this pressure advance coefficient.
Once configured, Klipper will push in an additional amount of filament
during acceleration. The higher the desired filament flow rate, the
more filament must be pushed in during acceleration to account for
pressure. During head deceleration the extra filament is retracted
(the extruder will have a negative velocity).
![pressure-advance](img/pressure-advance.svg.png)
One may notice that the pressure advance algorithm can cause the
extruder motor to make sudden velocity changes. This is tolerated
based on the idea that the majority of the inertia in the system is in
changing the extruder pressure. As long as the extruder pressure does
not change rapidly the sudden changes in extruder motor velocity are
tolerated.
One area where sudden velocity changes become problematic is during
small changes in head speed due to cornering.
![pressure-cornering](img/pressure-cornering.svg.png)
To prevent this, the Klipper pressure advance code utilizes the move
look-ahead queue to detect intermittent speed changes. During a
deceleration event the code finds the maximum upcoming head speed
within a configurable time window. The pressure is then only adjusted
to this found maximum. This can greatly reduce (or even completely
eliminate) pressure changes during cornering.

294
docs/MCU_Commands.md Normal file
View File

@@ -0,0 +1,294 @@
This document provides information on the low-level micro-controller
commands that are sent from the Klipper "host" software and processed
by the Klipper micro-controller software. This document is not an
authoritative reference for these commands, nor is it an exclusive
list of all available commands.
This document may be useful for users needing to configure a set of
hardware actions that their printer may require at startup (via the
"custom" field in the printer config file), and it may be useful for
developers wishing to obtain a high-level feel for low-level commands.
See the [protocol](Protocol.md) document for more information on the
format of commands and their transmission. The commands here are
described using their "printf" style syntax - for those unfamiliar
with that format, just note that where a '%...' sequence is seen it
should be replaced with an actual integer. For example, a description
with "count=%c" could be replaced with the text "count=10".
Startup Commands
================
It may be necessary to take certain one-time actions to configure the
micro-controller and its peripherals. This section lists common
commands available for that purpose. Unlike most micro-controller
commands, these commands run as soon as they are received and they do
not require any particular setup.
These commands are most useful in the "custom" block of the "mcu"
section of the printer configuration file. This feature is typically
used to configure the initial settings of LEDs, to configure
micro-stepping pins, to configure a digipot, etc.
Several of these commands will take a "pin=%u" parameter. The
low-level micro-controller software uses integer encodings of the
hardware pin numbers, but to make things more readable the host will
translate human readable pin names (eg, "PA3") to their equivalent
integer encodings. By convention, any parameter named "pin" or that
has a "_pin" suffix will use pin name translation by the
host. Similarly, several commands take time parameters specified in
clock ticks. One can specify a value for these parameters in seconds
using the "TICKS()" macro - for example "cycle_ticks=TICKS(0.001)"
would result in "cycle_ticks=16000" on a micro-controller with a 16Mhz
clock.
Common startup commands:
* `set_digital_out pin=%u value=%c` : This command immediately
configures the given pin as a digital out GPIO and it sets it to
either a low level (value=0) or a high level (value=1). This command
may be useful for configuring the initial value of LEDs and for
configuring the initial value of stepper driver micro-stepping pins.
* `set_pwm_out pin=%u cycle_ticks=%u value=%c` : This command will
immediately configure the given pin to use hardware based
pulse-width-modulation (PWM) with the given number of
cycle_ticks. The "cycle_ticks" is the number of MCU clock ticks each
power on and power off cycle should last. A cycle_ticks value of 1
can be used to request the fastest possible cycle time. The "value"
parameter is between 0 and 255 with 0 indicating a full off state
and 255 indicating a full on state. This command may be useful for
enabling CPU and nozzle cooling fans.
* `send_spi_message pin=%u msg=%*s` : This command can be used to
transmit messages to a serial-peripheral-interface (SPI) component
connected to the micro-controller. It has been used to configure the
startup settings of AD5206 digipots. The 'pin' parameter specifies
the chip select line to use during the transmission. The 'msg'
indicates the binary message to transmit to the given chip.
Low-level micro-controller configuration
========================================
Most commands in the micro-controller require an initial setup before
they can be successfully invoked. This section provides an overview of
the configuration process. This section and the following sections are
likely only of interest to developers interested in the internal
details of Klipper.
When the host first connects to the micro-controller it always starts
by obtaining a data dictionary (see [protocol](Protocol.md) for more
information). After the data dictionary is obtained the host will
check if the micro-controller is in a "configured" state and configure
it if not. Configuration involves the following phases:
* `get_config` : The host starts by checking if the micro-controller
is already configured. The micro-controller responds to this command
with a "config" response message. The micro-controller software
always starts in an unconfigured state at power-on. It remains in
this state until the host completes the configuration processes (by
issuing a finalize_config command). If the micro-controller is
already configured from a previous session (and is configured with
the desired settings) then no further action is needed by the host
and the configuration process ends successfully.
* `allocate_oids count=%c` : This command is issued to inform the
micro-controller of the maximum number of object-ids (oid) that the
host requires. It is only valid to issue this command once. An oid
is an integer identifier allocated to each stepper, each endstop,
and each schedulable gpio pin. The host determines in advance the
number of oids it will require to operate the hardware and passes
this to the micro-controller so that it may allocate sufficient
memory to store a mapping from oid to internal object.
* `config_XXX oid=%c ...` : By convention any command starting with
the "config_" prefix creates a new micro-controller object and
assigns the given oid to it. For example, the config_digital_out
command will configure the specified pin as a digital output GPIO
and create an internal object that the host can use to schedule
changes to the given GPIO. The oid parameter passed into the config
command is selected by the host and must be between zero and the
maximum count supplied in the allocate_oids command. The config
commands may only be run when the micro-controller is not in a
configured state (ie, prior to the host sending finalize_config) and
after the allocate_oids command has been sent.
* `finalize_config crc=%u` : The finalize_config command transitions
the micro-controller from an unconfigured state to a configured
state. The crc parameter passed to the micro-controller is stored
and provided back to the host in "config" response messages. By
convention, the host takes a 32bit CRC of the configuration it will
request and at the start of subsequent communication sessions it
checks that the CRC stored in the micro-controller exactly matches
its desired CRC. If the CRC does not match then the host knows the
micro-controller has not been configured in the state desired by the
host.
Common micro-controller objects
-------------------------------
This section lists some commonly used config commands.
* `config_digital_out oid=%c pin=%u default_value=%c
max_duration=%u` : This command creates an internal micro-controller
object for the given GPIO 'pin'. The pin will be configured in
digital output mode and set to an initial value as specified by
'default_value' (0 for low, 1 for high). Creating a digital_out
object allows the host to schedule GPIO updates for the given pin at
specified times (see the schedule_digital_out command described
below). Should the micro-controller software go into shutdown mode
then all configured digital_out objects will be set back to their
default values. The 'max_duration' parameter is used to implement a
safety check - if it is non-zero then it is the maximum number of
clock ticks that the host may set the given GPIO to a non-default
value without further updates. For example, if the default_value is
zero and the max_duration is 16000 then if the host sets the gpio to
a value of one then it must schedule another update to the gpio pin
(to either zero or one) within 16000 clock ticks. This safety
feature can be used with heater pins to ensure the host does not
enable the heater and then go off-line.
* `config_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
max_duration=%u` : This command creates an internal object for
hardware based PWM pins that the host may schedule updates for. Its
usage is analogous to config_digital_out - see the description of
the 'set_pwm_out' and 'config_digital_out' commands for parameter
description.
* `config_soft_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
max_duration=%u` : This command creates an internal micro-controller
object for software implemented PWM. Unlike hardware pwm pins, a
software pwm object does not require any special hardware support
(other than the ability to configure the pin as a digital output
GPIO). Because the output switching is implemented in the
micro-controller software, it is recommended that the cycle_ticks
parameter correspond to a time of 10ms or greater. See the
description of the 'set_pwm_out' and 'config_digital_out' commands
for parameter description.
* `config_analog_in oid=%c pin=%u` : This command is used to configure
a pin in analog input sampling mode. Once configured, the pin can be
sampled at regular interval using the query_analog_in command (see
below).
* `config_stepper oid=%c step_pin=%c dir_pin=%c min_stop_interval=%u
invert_step=%c` : This command creates an internal stepper
object. The 'step_pin' and 'dir_pin' parameters specify the step and
direction pins respectively; this command will configure them in
digital output mode. The 'invert_step' parameter specifies whether a
step occurs on a rising edge (invert_step=0) or falling edge
(invert_step=1). The 'min_stop_interval' implements a safety
feature - it is checked when the micro-controller finishes all moves
for a stepper - if it is non-zero it specifies the minimum number of
clock ticks since the last step. It is used as a check on the
maximum stepper velocity that a stepper may have before stopping.
* `config_end_stop oid=%c pin=%c pull_up=%c stepper_count=%c` : This
command creates an internal "endstop" object. It is used to specify
the endstop pins and to enable "homing" operations (see the
end_stop_home command below). The command will configure the
specified pin in digital input mode. The 'pull_up' parameter
determines whether hardware provided pullup resistors for the pin
(if available) will be enabled. The 'stepper_count' parameter
specifies the maximum number of steppers that this endstop may need
to halt during a homing operation (see end_stop_home below).
Common commands
===============
This section lists some commonly used run-time commands. It is likely
only of interest to developers looking to gain insight into Klipper.
* `schedule_digital_out oid=%c clock=%u value=%c` : This command will
schedule a change to a digital output GPIO pin at the given clock
time. To use this command a 'config_digital_out' command with the
same 'oid' parameter must have been issued during micro-controller
configuration.
* `schedule_pwm_out oid=%c clock=%u value=%c` : Schedules a change to
a hardware PWM output pin. See the 'schedule_digital_out' and
'config_pwm_out' commands for more info.
* `schedule_soft_pwm_out oid=%c clock=%u value=%c` : Schedules a
change to a software PWM output pin. See the 'schedule_digital_out'
and 'config_soft_pwm_out' commands for more info.
* `query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c
rest_ticks=%u min_value=%hu max_value=%hu` : This command sets up a
recurring schedule of analog input samples. To use this command a
'config_analog_in' command with the same 'oid' parameter must have
been issued during micro-controller configuration. The samples will
start as of 'clock' time, it will report on the obtained value every
'rest_ticks' clock ticks, it will over-sample 'sample_count' number
of times, and it will pause 'sample_ticks' number of clock ticks
between over-sample samples. The 'min_value' and 'max_value'
parameters implement a safety feature - the micro-controller
software will verify the sampled value (after any oversampling) is
always between the supplied range. This is intended for use with
pins attached to thermistors controlling heaters - it can be used to
check that a heater is within a temperature range.
* `get_status` : This command causes the micro-controller to generate
a "status" response message. The host sends this command once a
second to obtain the value of the micro-controller clock and to
estimate the drift between host and micro-controller clocks. It
enables the host to accurately estimate the micro-controller clock.
Stepper commands
----------------
* `queue_step oid=%c interval=%u count=%hu add=%hi` : This command
schedules 'count' number of steps for the given stepper, with
'interval' number of clock ticks between each step. The first step
will be 'interval' number of clock ticks since the last scheduled
step for the given stepper. If 'add' is non-zero then the interval
will be adjusted by 'add' amount after each step. This command
appends the given interval/count/add sequence to a per-stepper
queue. There may be hundreds of these sequences queued during normal
operation. New sequence are appended to the end of the queue and as
each sequence completes its 'count' number of steps it is popped
from the front of the queue. This system allows the micro-controller
to queue potentially hundreds of thousands of steps - all with
reliable and predictable schedule times.
* `set_next_step_dir oid=%c dir=%c` : This command specifies the value
of the dir_pin that the next queue_step command will use.
* `reset_step_clock oid=%c clock=%u` : Normally, step timing is
relative to the last step for a given stepper. This command resets
the clock so that the next step is relative to the supplied 'clock'
time. The host usually only sends this command at the start of a
print.
* `stepper_get_position oid=%c` : This command causes the
micro-controller to generate a "stepper_position" response message
with the stepper's current position. The position is the total
number of steps generated with dir=1 minus the total number of steps
generated with dir=0.
* `end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c` : This
command is used during stepper "homing" operations. To use this
command a 'config_end_stop' command with the same 'oid' parameter
must have been issued during micro-controller configuration. When
this command is invoked, the micro-controller will sample the
endstop pin every 'rest_ticks' clock ticks and check if it has a
value equal to 'pin_value'. If the value matches then the movement
queue for the associated stepper will be cleared and the stepper
will come to an immediate halt. The host uses this command to
implement homing - the host instructs the endstop to sample for the
endstop trigger and then it issues a series of queue_step commands
to move a stepper towards the endstop. Once the stepper hits the
endstop, the trigger will be detected, the movement halted, and the
host notified.
### Move queue
Each queue_step command utilizes an entry in the micro-controller
"move queue". This queue is allocated when it receives the
"finalize_config" command, and it reports the number of available
queue entries in "config" response messages.
It is the responsibility of the host to ensure that there is available
space in the queue before sending a queue_step command. The host does
this by calculating when each queue_step command completes and
scheduling new queue_step commands accordingly.

View File

@@ -1,18 +1,35 @@
See [installation](Installation.md) for information on compiling,
installing, and running Klipper. Read [features](Features.md) for a
high-level description of useful capabilities. The history of releases
is available at [releases](Releases.md).
Welcome to the Klipper documentation. There are two parts to Klipper -
code that runs on a micro-controller and code that runs on a "host"
machine. The host code is intended to run on a low-cost
general-purpose machine such as a Raspberry Pi, while the
micro-controller code is intended to run on commodity micro-controller
chips. Read [features](Features.md) for reasons to use Klipper. See
[installation](Installation.md) to get started with Klipper.
The Klipper configuration is stored in a simple text file on the host
machine. The [config/example.cfg](../config/example.cfg) file serves
as a reference for the config file. The
[Pressure Advance](Pressure_Advance.md) document contains information
on tuning the pressure advance config.
The [kinematics](Kinematics.md) document provides some technical
details on how Klipper implements motion.
The history of Klipper releases is available at
[releases](Releases.md).
Developer Documentation
=======================
There are also several documents available for developers interested
in understanding how Klipper works:
in understanding how Klipper works. Start with the
[code overview](Code_Overview.md) document - it provides information
on the structure and layout of the Klipper code.
See [code overview](Code_Overview.md) for information on the structure
and layout of the Klipper code.
See [protocol](Protocol.md) for information on the messaging protocol
between host and firmware. See also
[firmware commands](Firmware_Commands.md) for a high-level description
of common commands implemented in the firmware.
See [protocol](Protocol.md) for information on the low-level messaging
protocol between host and micro-controller. See also
[MCU commands](MCU_Commands.md) for a description of low-level
commands implemented in the micro-controller software.
See [debugging](Debugging.md) for information on how to test and debug
Klipper.

74
docs/Pressure_Advance.md Normal file
View File

@@ -0,0 +1,74 @@
This document provides information on tuning the "pressure advance"
configuration variables for a particular nozzle and filament. The
pressure advance feature can be helpful in reducing ooze. For more
information on how pressure advance is implemented see the
[kinematics](Kinematics.md) document.
Tuning pressure advance
=======================
Pressure advance does two useful things - it reduces ooze during
non-extrude moves and it reduces blobbing during cornering. This guide
uses the second feature (reducing blobbing during cornering) as a
mechanism for measuring and tuning the pressure advance configuration.
Start by changing the extruder section of the config file so that
pressure_advance is set to 0.0. (Make sure to issue a RESTART command
after each update to the config file so that the new configuration
takes effect.) Then print at least 10 layers of a large hollow square
at high speed (eg, 100mm/s). See
[docs/prints/square.stl](prints/square.stl) file for an STL file that
one may use. While the object is printing, make a note of which
direction the head is moving during external perimeters. What many
people see here is blobbing occurring at the corners - extra filament
at the corner in the direction the head travels followed by a possible
lack of filament on the side immediately after that corner:
![corner-blob](img/corner-blob.jpg)
This blobbing is the result of pressure in the extruder being released
as a blob when the head slows down to corner.
The next step is to set pressure_advance_lookahead_time to 0.0, slowly
increase pressure_advance (eg, start with 0.05), and reprint the test
object. (Be sure to issue RESTART between each config change.) The
goal is to attempt to eliminate the blobbing during cornering. (With
pressure advance, the extruder will retract when the head slows down,
thus countering the pressure buildup and ideally eliminate the
blobbing.) If a test run is done with a pressure_advance setting that
is too high, one typically sees a dimple in the corner followed by
possible blobbing after the corner (too much filament is retracted
during slow down and then too much filament is extruded during the
following speed up after cornering):
![corner-dimple](img/corner-dimple.jpg)
The goal is to find the smallest pressure_advance value that results
in good quality corners:
![corner-good](img/corner-good.jpg)
Typical pressure_advance values are between 0.05 and 0.20 (the high
end usually only with bowden extruders).
Once a good pressure_advance value is found, return
pressure_advance_lookahead_time to its default (0.010). This parameter
controls how far in advance to check if a head slow-down is
immediately followed by a speed-up - it reduces pointless pressure
changes in the head. It's possible to tune this - higher values will
decrease the number of pressure changes in the nozzle at the expense
of permitting more blobbing during cornering. (Tuning this value is
unlikely to impact ooze.) The default of 10ms should work well on most
printers.
Although this tuning exercise directly improves the quality of
corners, it's worth remembering that a good pressure advance
configuration can reduce ooze throughout the print.
Finally, once pressure_advance is tuned in Klipper, it may still be
useful to configure a small retract value in the slicer (eg, 0.75mm)
and to utilize the slicer's "wipe on retract option" if available.
These slicer settings may help counteract ooze caused by filament
cohesion (filament pulled out of the nozzle due to the stickiness of
the plastic). It is recommended to disable the slicer's "z-lift on
retract" option.

View File

@@ -1,8 +1,9 @@
The Klipper transmission protocol can be thought of, at a high level,
as a series of command and response strings that are compressed,
transmitted over a serial line, and then processed at the receiving
side. An example series of commands in uncompressed human-readable
format might look like:
The Klipper messaging protocol is used for low-level communication
between the Klipper host software and the Klipper micro-controller
software. At a high level the protocol can be thought of as a series
of command and response strings that are compressed, transmitted, and
then processed at the receiving side. An example series of commands in
uncompressed human-readable format might look like:
```
set_digital_out pin=86 value=1
@@ -12,34 +13,35 @@ queue_step oid=7 interval=7458 count=10 add=331
queue_step oid=7 interval=11717 count=4 add=1281
```
See the [firmware commands](Firmware_Commands.md) document for
information on available commands. See the [debugging](Debugging.md)
document for information on how to translate a G-Code file into its
corresponding human-readable firmware commands.
See the [mcu commands](MCU_Commands.md) document for information on
available commands. See the [debugging](Debugging.md) document for
information on how to translate a G-Code file into its corresponding
human-readable micro-controller commands.
This page provides a high-level description of the Klipper
transmission protocol itself. It describes how messages are declared,
encoded in binary format (the "compression" scheme), and transmitted.
This page provides a high-level description of the Klipper messaging
protocol itself. It describes how messages are declared, encoded in
binary format (the "compression" scheme), and transmitted.
The goal of the protocol is to enable an error-free communication
channel between the host and firmware that is low-latency,
low-bandwidth, and low-complexity for the firmware.
channel between the host and micro-controller that is low-latency,
low-bandwidth, and low-complexity for the micro-controller.
Firmware Interface
==================
Micro-controller Interface
==========================
The Klipper transmission protocol can be thought of as a
[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) mechanism
between firmware and host. The firmware declares the commands that the
host may invoke along with the response messages that it can
generate. The host uses that information to command the firmware to
perform actions and to interpret the results.
between micro-controller and host. The micro-controller software
declares the commands that the host may invoke along with the response
messages that it can generate. The host uses that information to
command the micro-controller to perform actions and to interpret the
results.
Declaring commands
------------------
The firmware declares a "command" by using the DECL_COMMAND() macro in
the C code. For example:
The micro-controller software declares a "command" by using the
DECL_COMMAND() macro in the C code. For example:
```
DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");
@@ -48,11 +50,11 @@ DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");
The above declares a command named "set_digital_out". This allows the
host to "invoke" this command which would cause the
command_set_digital_out() C function to be executed in the
firmware. The above also indicates that the command takes two integer
parameters. When the command_set_digital_out() C code is executed, it
will be passed an array containing these two integers - the first
corresponding to the 'pin' and the second corresponding to the
'value'.
micro-controller. The above also indicates that the command takes two
integer parameters. When the command_set_digital_out() C code is
executed, it will be passed an array containing these two integers -
the first corresponding to the 'pin' and the second corresponding to
the 'value'.
In general, the parameters are described with printf() style syntax
(eg, "%u"). The formatting directly corresponds to the human-readable
@@ -63,64 +65,61 @@ documentation. In this example, the "%c" is also used as documentation
to indicate the expected integer is 1 byte in size (the declared
integer size does not impact the parsing or encoding).
At firmware compile time, the build will collect all commands declared
with DECL_COMMAND(), determine their parameters, and arrange for them
to be callable.
The micro-controller build will collect all commands declared with
DECL_COMMAND(), determine their parameters, and arrange for them to be
callable.
Declaring responses
-------------------
To send information from the firmware to the host a "response" is
generated. These are both declared and transmitted using the sendf() C
macro. For example:
To send information from the micro-controller to the host a "response"
is generated. These are both declared and transmitted using the
sendf() C macro. For example:
```
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
```
The above transmits a "status" response message that contains two
integer parameters ("clock" and "status"). At firmware compile time
the build automatically finds all sendf() calls and generates encoders
for them. The first parameter of the sendf() function describes the
integer parameters ("clock" and "status"). The micro-controller build
automatically finds all sendf() calls and generates encoders for
them. The first parameter of the sendf() function describes the
response and it is in the same format as command declarations.
The host can arrange to register a callback function for each
response. So, in effect, commands allow the host to invoke C functions
in the firmware and responses allow the firmware to invoke code in the
host.
in the micro-controller and responses allow the micro-controller
software to invoke code in the host.
The firmware should only invoke sendf() from command or task handlers,
and it should not be invoked from interrupts or timers. The firmware
does not need to issue a sendf() in response to a received command, it
is not limited in the number of times sendf() may be invoked, and it
may invoke sendf() at any time from a task handler.
The sendf() macro should only be invoked from command or task
handlers, and it should not be invoked from interrupts or timers. The
code does not need to issue a sendf() in response to a received
command, it is not limited in the number of times sendf() may be
invoked, and it may invoke sendf() at any time from a task handler.
### Output responses
To simplify debugging, the firmware also has an output() C
function. For example:
To simplify debugging, there is also has an output() C function. For
example:
```
output("The value of %u is %s with size %u.", x, buf, buf_len);
```
The output() function is similar in usage to printf() - it is intended
to generate and format arbitrary messages for human consumption. It is
a wrapper around sendf() and as with sendf() it should not be called
from interrupts or timers.
to generate and format arbitrary messages for human consumption.
Declaring constants
-------------------
The firmware can also define constants to be exported. For example,
the following:
Constants can also be exported. For example, the following:
```
DECL_CONSTANT(SERIAL_BAUD, 250000);
```
would export a constant named "SERIAL_BAUD" with a value of 250000
from the firmware to the host.
from the micro-controller to the host.
Low-level message encoding
==========================
@@ -132,9 +131,9 @@ the transmission system.
Message Blocks
--------------
All data sent from host to firmware and vice-versa are contained in
"message blocks". A message block has a two byte header and a three
byte trailer. The format of a message block is:
All data sent from host to micro-controller and vice-versa are
contained in "message blocks". A message block has a two byte header
and a three byte trailer. The format of a message block is:
```
<1 byte length><1 byte sequence><n-byte content><2 byte crc><1 byte sync>
@@ -162,10 +161,11 @@ present in the message block content.
Message Block Contents
----------------------
Each message block sent from host to firmware contains a series of
zero or more message commands in its contents. Each command starts
with a Variable Length Quantity (VLQ) encoded integer command-id
followed by zero or more VLQ parameters for the given command.
Each message block sent from host to micro-controller contains a
series of zero or more message commands in its contents. Each command
starts with a [Variable Length Quantity](#variable-length-quantities)
(VLQ) encoded integer command-id followed by zero or more VLQ
parameters for the given command.
As an example, the following four commands might be placed in a single
message block:
@@ -184,21 +184,22 @@ and encoded into the following eight VLQ integers:
```
In order to encode and parse the message contents, both the host and
firmware must agree on the command ids and the number of parameters
each command has. So, in the above example, both the host and firmware
would know that "id_set_digital_out" is always followed by two
parameters, and "id_get_config" and "id_get_status" have zero
parameters. The host and firmware share a "data dictionary" that maps
the command descriptions (eg, "set_digital_out pin=%u value=%c") to
their integer command-ids. When processing the data, the parser will
know to expect a specific number of VLQ encoded parameters following a
given command id.
micro-controller must agree on the command ids and the number of
parameters each command has. So, in the above example, both the host
and micro-controller would know that "id_set_digital_out" is always
followed by two parameters, and "id_get_config" and "id_get_status"
have zero parameters. The host and micro-controller share a "data
dictionary" that maps the command descriptions (eg, "set_digital_out
pin=%u value=%c") to their integer command-ids. When processing the
data, the parser will know to expect a specific number of VLQ encoded
parameters following a given command id.
The message contents for blocks sent from firmware to host follow the
same format. The identifiers in these messages are "response ids", but
they serve the same purpose and follow the same encoding rules. In
practice, message blocks sent from the firmware to the host never
contain more than one response in the message block contents.
The message contents for blocks sent from micro-controller to host
follow the same format. The identifiers in these messages are
"response ids", but they serve the same purpose and follow the same
encoding rules. In practice, message blocks sent from the
micro-controller to the host never contain more than one response in
the message block contents.
### Variable Length Quantities
@@ -230,60 +231,62 @@ the length as a VLQ encoded integer followed by the contents itself:
```
The command descriptions found in the data dictionary allow both the
host and firmware to know which command parameters use simple VLQ
encoding and which parameters use string encoding.
host and micro-controller to know which command parameters use simple
VLQ encoding and which parameters use string encoding.
Data Dictionary
===============
In order for meaningful communications to be established between
firmware and host, both sides must agree on a "data dictionary". This
data dictionary contains the integer identifiers for commands and
responses along with their descriptions.
micro-controller and host, both sides must agree on a "data
dictionary". This data dictionary contains the integer identifiers for
commands and responses along with their descriptions.
At compile time the firmware build uses the contents of DECL_COMMAND()
and sendf() macros to generate the data dictionary. The build
The micro-controller build uses the contents of DECL_COMMAND() and
sendf() macros to generate the data dictionary. The build
automatically assigns unique identifiers to each command and
response. This system allows both the host and firmware code to
seamlessly use descriptive human-readable names while still using
response. This system allows both the host and micro-controller code
to seamlessly use descriptive human-readable names while still using
minimal bandwidth.
The host queries the data dictionary when it first connects to the
firmware. Once the host downloads the data dictionary from the
firmware, it uses that data dictionary to encode all commands and to
parse all responses from the firmware. The host must therefore handle
a dynamic data dictionary. However, to keep the firmware simple, the
firmware always uses its static (compiled in) data dictionary.
micro-controller. Once the host downloads the data dictionary from the
micro-controller, it uses that data dictionary to encode all commands
and to parse all responses from the micro-controller. The host must
therefore handle a dynamic data dictionary. However, to keep the
micro-controller software simple, the micro-controller always uses its
static (compiled in) data dictionary.
The data dictionary is queried by sending "identify" commands to the
firmware. The firmware will respond to each identify command with an
"identify_response" message. Since these two commands are needed prior
to obtaining the data dictionary, their integer ids and parameter
types are hard-coded in both the firmware and the host. The
"identify_response" response id is 0, the "identify" command id
is 1. Other than having hard-coded ids the identify command and its
response are declared and transmitted the same way as other commands
and responses. No other command or response is hard-coded.
micro-controller. The micro-controller will respond to each identify
command with an "identify_response" message. Since these two commands
are needed prior to obtaining the data dictionary, their integer ids
and parameter types are hard-coded in both the micro-controller and
the host. The "identify_response" response id is 0, the "identify"
command id is 1. Other than having hard-coded ids the identify command
and its response are declared and transmitted the same way as other
commands and responses. No other command or response is hard-coded.
The format of the transmitted data dictionary itself is a zlib
compressed JSON string. The firmware compile process generates the
string, compresses it, and stores it in the text section of the
firmware. The data dictionary can be much larger than the maximum
message block size - the host downloads it by sending multiple
identify commands requesting progressive chunks of the data
compressed JSON string. The micro-controller build process generates
the string, compresses it, and stores it in the text section of the
micro-controller flash. The data dictionary can be much larger than
the maximum message block size - the host downloads it by sending
multiple identify commands requesting progressive chunks of the data
dictionary. Once all chunks are obtained the host will assemble the
chunks, uncompress the data, and parse the contents.
In addition to information on the communication protocol, the data
dictionary also contains firmware version, constants (as defined by
DECL_CONSTANT), and static strings.
dictionary also contains the software version, constants (as defined
by DECL_CONSTANT), and static strings.
Static Strings
--------------
To reduce bandwidth the data dictionary also contains a set of static
strings known to the firmware. This is useful when sending messages
from firmware to host. For example, if the firmware were to run:
strings known to the micro-controller. This is useful when sending
messages from micro-controller to host. For example, if the
micro-controller were to run:
```
shutdown("Unable to handle command");
@@ -296,22 +299,22 @@ to their associated human-readable strings.
Message flow
============
Message commands sent from host to firmware are intended to be
error-free. The firmware will check the CRC and sequence numbers in
each message block to ensure the commands are accurate and
in-order. The firmware always processes message blocks in-order -
should it receive a block out-of-order it will discard it and any
other out-of-order blocks until it receives blocks with the correct
sequencing.
Message commands sent from host to micro-controller are intended to be
error-free. The micro-controller will check the CRC and sequence
numbers in each message block to ensure the commands are accurate and
in-order. The micro-controller always processes message blocks
in-order - should it receive a block out-of-order it will discard it
and any other out-of-order blocks until it receives blocks with the
correct sequencing.
The low-level host code implements an automatic retransmission system
for lost and corrupt message blocks sent to the firmware. To
facilitate this, the firmware transmits an "ack message block" after
each successfully received message block. The host schedules a timeout
after sending each block and it will retransmit should the timeout
expire without receiving a corresponding "ack". In addition, if the
firmware detects a corrupt or out-of-order block it may transmit a
"nak message block" to facilitate fast retransmission.
for lost and corrupt message blocks sent to the micro-controller. To
facilitate this, the micro-controller transmits an "ack message block"
after each successfully received message block. The host schedules a
timeout after sending each block and it will retransmit should the
timeout expire without receiving a corresponding "ack". In addition,
if the micro-controller detects a corrupt or out-of-order block it may
transmit a "nak message block" to facilitate fast retransmission.
An "ack" is a message block with empty content (ie, a 5 byte message
block) and a sequence number greater than the last received host
@@ -326,15 +329,15 @@ in the event of transmission latency. The timeout, retransmit,
windowing, and ack mechanism are inspired by similar mechanisms in
[TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).
In the other direction, message blocks sent from firmware to host are
designed to be error-free, but they do not have assured
In the other direction, message blocks sent from micro-controller to
host are designed to be error-free, but they do not have assured
transmission. (Responses should not be corrupt, but they may go
missing.) This is done to keep the implementation in the firmware
simple. There is no automatic retransmission system for responses -
the high-level code is expected to be capable of handling an
occasional missing response (usually by re-requesting the content or
setting up a recurring schedule of response transmission). The
sequence number field in message blocks sent to the host is always one
greater than the last received sequence number of message blocks
received from the host. It is not used to track sequences of response
message blocks.
missing.) This is done to keep the implementation in the
micro-controller simple. There is no automatic retransmission system
for responses - the high-level code is expected to be capable of
handling an occasional missing response (usually by re-requesting the
content or setting up a recurring schedule of response
transmission). The sequence number field in message blocks sent to the
host is always one greater than the last received sequence number of
message blocks received from the host. It is not used to track
sequences of response message blocks.

2
docs/README.md Normal file
View File

@@ -0,0 +1,2 @@
Welcome to the Klipper documentation. The
[overview document](Overview.md) is a good starting point.

View File

@@ -1,6 +1,26 @@
History of Klipper releases. Please see
[installation](Installation.md) for information on installing Klipper.
Klipper 0.4.0
=============
Available on 20170503. Major changes in this release:
* Improved installation on Raspberry Pi machines. Most of the install
is now scripted.
* Support for corexy kinematics
* Documentation updates: New Kinematics document, new Pressure Advance
tuning guide, new example config files, and more
* Stepper performance improvements (20Mhz AVRs over 175K steps per
second, Arduino Due over 460K)
* Support for automatic micro-controller resets. Support for resets
via toggling USB power on Raspberry Pi.
* The pressure advance algorithm now works with look-ahead to reduce
pressure changes during cornering.
* Support for limiting the top speed of short zigzag moves
* Support for AD595 sensors
* Several bug fixes and code cleanups
Klipper 0.3.0
=============

View File

@@ -1,5 +1,5 @@
Klipper is currently in an experimental state. There are several
features still to be implemented. In no particular order:
There are several features still to be implemented in Klipper. In no
particular order:
Host user interaction
=====================
@@ -9,13 +9,6 @@ Host user interaction
find the error) and errors written to the log can be non-obvious to
a user.
* Improve startup:
* Provide startup scripts so that Klippy can startup at system
bootup.
* Allow loading of a new config without having to restart the mcu.
* Improve gcode interface:
* Provide a better way to handle print nozzle z offsets. The M206
@@ -28,12 +21,6 @@ Host user interaction
* Improve logging:
* Automatically roll Klippy log files. The default log file should
have the current date in the log file name.
* Report the Klippy git version in log file. Log the contents of the
config file at startup.
* Possibly collate and report the statistics messages in the log in a
more friendly way.
@@ -44,12 +31,12 @@ Host user interaction
Safety features
===============
* Support loading a valid step range into the firmware after
homing. This would provide a sanity check in the firmware that would
reduce the risk of the host commanding a stepper motor past its
valid step range. To maintain high efficiency in the firmware, the
firmware would only need to check periodically (eg, every 100ms)
that the stepper is in range.
* Support loading a valid step range into the micro-controller
software after homing. This would provide a sanity check in the
micro-controller that would reduce the risk of the host commanding a
stepper motor past its valid step range. To maintain high
efficiency, the micro-controller would only need to check
periodically (eg, every 100ms) that the stepper is in range.
* Possibly support periodically querying the endstop switches and use
multiple step ranges depending on the switch state. This would
@@ -61,17 +48,14 @@ Safety features
can be useful to detect a sensor failure (eg, thermistor short) that
could otherwise cause the PID to command excessive heating.
* Possibly implement host based checking on the ratio between extrude
amount and head movement.
* Enforce acceleration and speed limits on extruder stepper motor.
Testing features
================
* Complete the host based simulator. It's possible to compile the
firmware for a "host simulator", but that simulator doesn't do
anything currently. It would be useful to expand the code to support
more error checks, kinematic simulations, and improved logging.
micro-controller for a "host simulator", but that simulator doesn't
do anything currently. It would be useful to expand the code to
support more error checks, kinematic simulations, and improved
logging.
Documentation
=============
@@ -81,20 +65,15 @@ Documentation
* Add documentation describing how to perform bed-leveling accurately
in Klipper. Improve description of stepper phase based bed leveling.
* Document the kinematic formulas in Klippy. Document how acceleration
and jerk limits are enforced.
* Document how one can tune the pressure advance setting.
Hardware features
=================
* Port firmware to more architectures:
* Port to additional micro-controller architectures:
* Beagle Bone Black PRU
* Smoothieboard / NXP LPC1769 (ARM cortex-M3)
* Unix based scheduling; Unix based real-time scheduling
* Support for additional kinematics: scara, corexy, etc.
* Support for additional kinematics: scara, etc.
* Support shared motor enable GPIO lines.
@@ -108,11 +87,6 @@ Hardware features
it would also be useful to handle panels already hardwired to the
micro-controller.)
* The raspberry pi has the ability to cut power to its USB ports. This
feature is useful for resetting micro-controllers that are powered
over USB. It would be useful to have a high-level command interface
in Klippy to request a micro-controller reset via this mechanism.
* Possibly support printers using multiple micro-controllers.
Misc features

BIN
docs/img/corner-blob.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
docs/img/corner-dimple.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/img/corner-good.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

184
docs/img/corner.svg Normal file
View File

@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="54.904114mm"
height="6.0860338mm"
viewBox="0 0 194.54213 21.564687"
id="svg3506"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="corner.svg">
<defs
id="defs3508">
<marker
inkscape:stockid="DiamondL"
orient="auto"
refY="0"
refX="0"
id="DiamondL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4399"
d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="scale(0.8,0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Lend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Lend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4341"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="marker4596"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4598"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4329"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend-1"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4329-1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.49"
inkscape:cx="95.030833"
inkscape:cy="-0.17370789"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:window-width="1068"
inkscape:window-height="478"
inkscape:window-x="378"
inkscape:window-y="113"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid6021"
spacingx="9.9999997"
spacingy="10.000001"
originx="0.89299989"
originy="-30.954583" />
</sodipodi:namedview>
<metadata
id="metadata3511">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-253.40821,-436.43703)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
d="m 345.38554,440.31401 96.88541,12.96764"
id="path3514"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
d="m 253.63788,454.62572 89.78715,-13.91164"
id="path3514-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="189.09824"
y="482.48389"
id="text12656-9"
sodipodi:linespacing="125%"
transform="matrix(0.98759291,-0.15703579,0.15703579,0.98759291,0,0)"
inkscape:transform-center-x="1.3563414"
inkscape:transform-center-y="-5.7099754"><tspan
sodipodi:role="line"
id="tspan5552"
x="189.09824"
y="482.48389">move 1</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="427.95532"
y="379.5321"
id="text12656-9-8"
sodipodi:linespacing="125%"
transform="matrix(0.98949457,0.14457001,-0.14457001,0.98949457,0,0)"><tspan
sodipodi:role="line"
id="tspan5554"
x="427.95532"
y="379.5321">move 2</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/img/corner.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

273
docs/img/delta-tower.svg Normal file
View File

@@ -0,0 +1,273 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="delta-tower.svg">
<defs
id="defs4">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6618"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mend">
<path
transform="matrix(-0.4,0,0,-0.4,-4,0)"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
id="path6620"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker6500"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path6502"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker6082"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Mend"
inkscape:collect="always">
<path
transform="scale(0.4) rotate(180) translate(10,0)"
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path6084" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Mend"
style="overflow:visible;"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path5747"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path5744"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
transform="scale(0.4) translate(10,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend-1"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
inkscape:connector-curvature="0"
id="path4329-1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6082-9"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mend"
inkscape:collect="always">
<path
inkscape:connector-curvature="0"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
id="path6084-2" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.75"
inkscape:cx="172.24895"
inkscape:cy="45.708959"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="928"
inkscape:window-height="628"
inkscape:window-x="162"
inkscape:window-y="50"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<ellipse
style="fill:#4d4d4d;stroke:#4b4b4b;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:2, 1, 0.5, 1;stroke-dashoffset:0;stroke-opacity:1"
id="path4255"
cx="353.79568"
cy="327.87662"
rx="4.2857141"
ry="4.8571429" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000006;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
d="M 181.50998,381.87664 359.22427,275.01949"
id="path5510"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="371.22427"
y="354.44806"
id="text6058"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6060"
x="371.22427"
y="354.44806">stepper</tspan><tspan
sodipodi:role="line"
x="371.22427"
y="366.94806"
id="tspan9337">tower</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="166.65283"
y="304.16235"
id="text6062"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6064"
x="166.65283"
y="304.16235">line of movement</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.0000006px;line-height:125%;font-family:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'DejaVu Sans, Normal';font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;"
x="252.93855"
y="384.16235"
id="text6066"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6068"
x="252.93855"
y="384.16235">move</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000006;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker6618)"
d="m 382.65284,344.73378 c -0.19273,-13.52091 -9.87887,-14.83602 -20.57143,-14.85715"
id="path6070"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker6500)"
d="m 254.65284,374.44806 c 3.39239,-12.86009 -2.06023,-20.09154 -15.42857,-22.28571"
id="path6072"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082)"
d="m 257.50997,296.16234 c 31.05376,-3.13332 32.38959,-1.23784 42.28572,10.28572"
id="path6074"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
d="m 219.61431,358.80126 57.78715,-34.48307"
id="path3514-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:0.50000003;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 351.22427,323.59092 -20.57143,-32"
id="path3358"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="244.36713"
y="257.30521"
id="text4460"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4462"
x="244.36713"
y="257.30521">virtual tower</tspan><tspan
sodipodi:role="line"
x="244.36713"
y="269.80521"
id="tspan5867">location</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082-9)"
d="m 310.90661,257.14111 c 21.29088,8.40268 18.35244,16.28958 20.57143,29.7143"
id="path6074-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

208
docs/img/lookahead-slow.svg Normal file
View File

@@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="lookahead-slow.svg"
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/lookahead-slow.svg.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4">
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend-1"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4329-1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4329"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.15"
inkscape:cx="3.4198125"
inkscape:cy="101.26451"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="149"
inkscape:window-y="422"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="434.04257"
y="365.1282"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="434.04257"
y="365.1282">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-313.86618"
y="140.27856"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan3355"
x="-313.86618"
y="140.27856">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.62366331px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179.63013,351.45141 16.05677,-60.94328 43.25999,0 16.53759,47.11348"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 254.72406,337.27551 22.65101,-77.77178 43.89917,0 24.69858,91.73948"
id="path3361-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="195.45749"
y="286.52051"
id="text12656-9"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan7078"
x="195.45749"
y="286.52051">move A</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="280.5639"
y="254.60564"
id="text12656-9-3"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan7080"
x="280.5639"
y="254.60564">move B</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
d="m 74.333855,283.02668 -147.83244,52.19984"
id="path3514"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
d="m -79.633985,251.6122 154.13499,31.81455"
id="path3514-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="36.7374"
y="252.31848"
id="text12656-9-1"
sodipodi:linespacing="125%"
transform="matrix(0.96753827,0.25272454,-0.25272454,0.96753827,0,0)"
inkscape:transform-center-x="-0.91382951"
inkscape:transform-center-y="-4.9145266"><tspan
sodipodi:role="line"
id="tspan7074"
x="36.7374"
y="252.31848">move A</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-126.26086"
y="304.35226"
id="text12656-9-8"
sodipodi:linespacing="125%"
transform="matrix(0.93839918,-0.34555314,0.34555314,0.93839918,0,0)"><tspan
sodipodi:role="line"
id="tspan7076"
x="-126.26086"
y="304.35226">move B</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

136
docs/img/lookahead.svg Normal file
View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="lookahead.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.94"
inkscape:cx="116.54041"
inkscape:cy="45.708959"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="149"
inkscape:window-y="422"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="434.04257"
y="365.1282"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="434.04257"
y="365.1282">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-313.86618"
y="140.27856"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan3355"
x="-313.86618"
y="140.27856">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.62366331px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179.63013,351.45141 16.05677,-60.94328 61.3451,0 4.83546,8.81561"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 261.70791,300.17937 13.5395,-40.67564 59.85662,0 24.69858,91.73948"
id="path3361-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="200.77664"
y="286.52051"
id="text12656-9"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan5532"
x="200.77664"
y="286.52051">move 1</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="280.5639"
y="255.66946"
id="text12656-9-3"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan5534"
x="280.5639"
y="255.66946">move 2</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
docs/img/lookahead.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

207
docs/img/ooze.svg Normal file
View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="98.140816mm"
height="63.537022mm"
viewBox="0 0 347.74305 225.13119"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ooze.svg"
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/ooze.svg.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.94"
inkscape:cx="199.68782"
inkscape:cy="58.510649"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="266"
inkscape:window-y="106"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436"
originx="-1.5695746e-05"
originy="109.79552" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22431,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-346.84067"
y="139.75046"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan12733"
x="-346.84067"
y="139.75046">head velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 179.63013,351.45141 16.05677,-60.94328 120.91957,-1.06383 16.53759,62.00711"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.78742969px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 332.99641,351.24986 16.72764,-63.00287 133.24331,0"
id="path3361-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="200.11789"
y="284.45413"
id="text12656"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan12658"
x="200.11789"
y="284.45413">extrude move</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="356.50089"
y="283.39032"
id="text12660"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan12662"
x="356.50089"
y="283.39032">non-extrude move</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 152.72419,471.73218 1.06383,102.12766 327.12768,-0.53192 -7.97872,5.85107"
id="path3347-0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="436.76678"
y="586.62585"
id="text3349-3"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351-4"
x="436.76678"
y="586.62585">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-551.11292"
y="125.37186"
id="text3353-0"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan4197"
x="-551.11292"
y="125.37186">actual</tspan><tspan
sodipodi:role="line"
id="tspan4199"
x="-551.11292"
y="140.99686">filament</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 152.72419,471.73218 -5.31915,8.51063"
id="path3359-9"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 182.35432,572.94905 c 17.46262,-43.80215 52.67413,-56.54375 92.65253,-60.94329 l 48.57915,-1.06383 c 24.916,55.4715 110.00504,59.23318 151.64398,62.00712"
id="path3361-1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150.87843,360.66993 1.06383,102.12766 327.12768,-0.53192 -7.97872,5.85107"
id="path3347-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-444.27307"
y="124.17301"
id="text3353-6"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan4193"
x="-444.27307"
y="124.17301">desired</tspan><tspan
sodipodi:role="line"
id="tspan4195"
x="-444.27307"
y="139.79802">filament</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150.87843,360.66993 -5.31915,8.51063"
id="path3359-8"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 180.50856,461.8868 16.05678,-60.94329 120.91958,-1.06383 16.53759,62.00712"
id="path3361-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
docs/img/ooze.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="102.61139mm"
height="99.266594mm"
viewBox="0 0 363.58366 351.73204"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="pressure-advance.svg"
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/pressure-advance.svg.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.97968464"
inkscape:cx="147.06528"
inkscape:cy="169.55487"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="419"
inkscape:window-y="273"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436"
originx="17.089787"
originy="126.61899" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-118.13451,-140.19216)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150.47693,249.82417 0.58688,123.9666 m 0,-21.42857 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-334.86746"
y="122.91875"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan12816"
x="-334.86746"
y="122.91875">extruder</tspan><tspan
sodipodi:role="line"
id="tspan12818"
x="-334.86746"
y="138.54375">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 316.60647,311.78473 16.53759,62.00711 m -154.57776,-52.12767 16.05677,-60.94328 1.06383,29.78724 m 0,0 120.91957,-1.06383 0,22.34043"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 148.75077,140.45716 1.06383,102.12766 327.12769,-0.53192 -7.97872,5.85107"
id="path3347-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-237.05733"
y="140.25932"
id="text3353-2"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan12733-6"
x="-237.05733"
y="140.25932">head velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 148.75077,140.45716 -5.31915,8.51063"
id="path3359-7"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 178.3809,241.67403 16.05679,-60.94329 120.91958,-1.06383 16.53759,62.00712"
id="path3361-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.78742969px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 331.74721,241.47248 16.72764,-63.00288 133.24332,0"
id="path3361-7-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="198.86868"
y="174.67674"
id="text12656-9"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan12658-8"
x="198.86868"
y="174.67674">extrude move</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="355.25165"
y="173.61293"
id="text12660-7"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan12662-2"
x="355.25165"
y="173.61293">non-extrude move</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 151.94226,384.0742 1.06383,102.12766 327.12769,-0.53192 -7.97872,5.85107"
id="path3347-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-467.65732"
y="122.73453"
id="text3353-1"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan12868"
x="-467.65732"
y="122.73453">extruder</tspan><tspan
sodipodi:role="line"
id="tspan12870"
x="-467.65732"
y="138.35953">pressure</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 151.94226,384.0742 -5.31915,8.51063"
id="path3359-5"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 181.57239,485.29107 16.05679,-60.94329 120.91958,-1.06383 16.53759,62.00712"
id="path3361-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="438.03586"
y="499.53384"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="438.03586"
y="499.53384">time</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="102.04809mm"
height="61.494076mm"
viewBox="0 0 361.58771 217.8924"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="pressure-cornering.svg"
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/pressure-cornering.svg.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98"
inkscape:cx="110.74341"
inkscape:cy="35.715236"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="656"
inkscape:window-y="0"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436"
originx="17.089805"
originy="-7.2206491" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-118.13449,-140.19216)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-334.86746"
y="122.91875"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan12816"
x="-334.86746"
y="122.91875">extruder</tspan><tspan
sodipodi:role="line"
id="tspan12818"
x="-334.86746"
y="138.54375">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 178.5663,321.66417 16.05677,-60.94328 1.06383,29.78724 m 0,0 117.21969,-0.77616 5.99519,7.3871 5.9952,-6.89861 131.50541,-0.77616"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 148.75077,140.45716 1.06383,102.12766 327.12769,-0.53192 -7.97872,5.85107"
id="path3347-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-237.05733"
y="140.25932"
id="text3353-2"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan12733-6"
x="-237.05733"
y="140.25932">head velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 148.75077,140.45716 -5.31915,8.51063"
id="path3359-7"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 178.3809,241.67403 16.05679,-60.94329 120.91958,-1.06383 4.29269,10.98671"
id="path3361-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 319.50231,193.5133 4.48274,-15.0437 133.24332,0"
id="path3361-7-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="198.86868"
y="174.67674"
id="text12656-9"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan12891"
x="198.86868"
y="174.67674">first extrude</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="355.25165"
y="173.61293"
id="text12660-7"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan12893"
x="355.25165"
y="173.61293">second extrude</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 2, 1, 2;stroke-dashoffset:0;stroke-opacity:1"
d="m 314.05288,288.86296 0,17.34694 5.10204,17.34694 -1.02041,-48.97959 7.14286,-17.34694 0,31.63265"
id="path12895"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="435.80841"
y="366.8262"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="435.80841"
y="366.8262">time</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

132
docs/img/smoothed.svg Normal file
View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="smoothed.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.75"
inkscape:cx="167.32577"
inkscape:cy="45.708959"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="840"
inkscape:window-height="628"
inkscape:window-x="162"
inkscape:window-y="50"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="434.04257"
y="365.1282"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="434.04257"
y="365.1282">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-313.86618"
y="140.27856"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan3355"
x="-313.86618"
y="140.27856">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 165.50998,351.59092 15.42857,-35.42857 35.71428,0 11.71429,36.57143"
id="path3362"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.07142997px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 227.83578,352.85633 15.76217,-47.37239 22.00135,0 5.58242,17.6933"
id="path3362-3"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.21482575px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 271.35434,323.73296 11.15454,-30.54076 19.27119,-0.11152 8.73883,29.51288"
id="path3362-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.01949775px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 311.26144,322.49329 11.63197,-21.06975 15.33656,0 13.51652,50.22357"
id="path3362-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.00000012, 1.00000006, 0.50000003, 1.00000006;stroke-dashoffset:0;stroke-opacity:1"
d="m 165.50999,351.59092 33.14285,-34.28571 29.14286,34.28571"
id="path3426"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
docs/img/smoothed.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

102
docs/img/trapezoid.svg Normal file
View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="trapezoid.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.94"
inkscape:cx="294.7319"
inkscape:cy="45.708959"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="113"
inkscape:window-y="31"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="434.04257"
y="365.1282"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="434.04257"
y="365.1282">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-313.86618"
y="140.27856"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan3355"
x="-313.86618"
y="140.27856">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179.78723,351.29837 41.48937,-60.63829 158.51063,0 37.23405,60.63829"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
docs/img/trapezoid.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

182
docs/img/trapezoids.svg Normal file
View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="trapezoids.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.67"
inkscape:cx="164.48301"
inkscape:cy="76.011989"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1091"
inkscape:window-height="588"
inkscape:window-x="113"
inkscape:window-y="31"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="434.04257"
y="365.1282"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="434.04257"
y="365.1282">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-313.86618"
y="140.27856"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan3355"
x="-313.86618"
y="140.27856">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.62366331px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179.63013,351.45141 16.05677,-60.94328 61.3451,0 6.96312,32.21987"
id="path3361"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 263.83557,321.45597 11.41184,-61.95224 59.85662,0 9.80496,29.88826"
id="path3361-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 344.93248,289.80297 59.85663,0 15.12411,61.95225"
id="path3361-7-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 263.86975,321.69491 0.37453,30.52957"
id="path3412"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 345.25947,289.31929 0.14909,62.46104"
id="path3414"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 222.45831,289.77329 -0.0598,62.07666"
id="path3416"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:20.00000119px;line-height:125%;font-family:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'DejaVu Sans, Normal';font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;"
x="193.73491"
y="338.70944"
id="text3418"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3420"
x="193.73491"
y="338.70944">1</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20.00000191px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="236.28812"
y="338.70947"
id="text3422"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3434"
x="236.28812"
y="338.70947">2</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20.00000191px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="294.79874"
y="335.51794"
id="text3426"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3428"
x="294.79874"
y="335.51794">3</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:20.00000119px;line-height:125%;font-family:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'DejaVu Sans, Normal';font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;"
x="367.13916"
y="337.6456"
id="text3430"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3432"
x="367.13916"
y="337.6456">4</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
docs/img/trapezoids.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

241
docs/img/virtual-tower.svg Normal file
View File

@@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64.619751mm"
height="27.45583mm"
viewBox="0 0 228.96762 97.284438"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="virtual-tower.svg">
<defs
id="defs4">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6618"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mend">
<path
transform="matrix(-0.4,0,0,-0.4,-4,0)"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
id="path6620"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="marker6500"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path6502"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path5747"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path5744"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend-1"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
inkscape:connector-curvature="0"
id="path4329-1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6082"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mend"
inkscape:collect="always">
<path
inkscape:connector-curvature="0"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
id="path6084" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.75"
inkscape:cx="169.7719"
inkscape:cy="-4.0565054"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="922"
inkscape:window-height="628"
inkscape:window-x="162"
inkscape:window-y="50"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436"
originx="-14.085234"
originy="-95.286988" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-149.30952,-172.73378)">
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
d="m 176.36712,256.16235 200.00001,-0.57143"
id="path5510"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-254.01012"
y="369.20834"
id="text6058"
sodipodi:linespacing="125%"
transform="matrix(-0.01833576,-0.99983189,0.99983189,-0.01833576,0,0)"
inkscape:transform-center-x="-8.0000002"
inkscape:transform-center-y="12.571429"><tspan
sodipodi:role="line"
id="tspan10365"
x="-254.01012"
y="369.20834">virtual tower</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="148.36713"
y="269.87662"
id="text6062"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6064"
x="148.36713"
y="269.87662">line of movement</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="237.50998"
y="251.01949"
id="text6066"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6068"
x="237.50998"
y="251.01949">move</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:7.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 358.08141,255.01949 0,-82.28571"
id="path10351"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 220.36713,255.01949 357.50998,173.30521"
id="path10373"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="200.36713"
y="187.59093"
id="text10375"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan10377"
x="200.36713"
y="187.59093">virtual arm</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082)"
d="m 261.76375,182.85539 c 22.73118,-0.70136 26.45506,3.7437 40.00001,18.28573"
id="path6074"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
d="m 219.61431,255.37268 70.93001,0.37408"
id="path3514-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

241
docs/img/xy+z-tower.svg Normal file
View File

@@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64.619751mm"
height="27.45583mm"
viewBox="0 0 228.96762 97.284438"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="xy+z-tower.svg">
<defs
id="defs4">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6618"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mend">
<path
transform="matrix(-0.4,0,0,-0.4,-4,0)"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
id="path6620"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="marker6500"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path6502"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path5747"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path5744"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mend-1"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
inkscape:connector-curvature="0"
id="path4329-1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6082"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mend"
inkscape:collect="always">
<path
inkscape:connector-curvature="0"
transform="matrix(-0.4,0,0,-0.4,-4,0)"
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
id="path6084" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.75"
inkscape:cx="169.7719"
inkscape:cy="-4.0565054"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="922"
inkscape:window-height="628"
inkscape:window-x="162"
inkscape:window-y="50"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436"
originx="-14.085234"
originy="-95.286988" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-149.30952,-172.73378)">
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
d="m 176.36712,256.16235 200.00001,-0.57143"
id="path5510"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-119.27526"
y="434.37329"
id="text6058"
sodipodi:linespacing="125%"
transform="matrix(0.32454206,-0.94587127,0.94587127,0.32454206,0,0)"
inkscape:transform-center-x="-3.9400734"
inkscape:transform-center-y="14.789029"><tspan
sodipodi:role="line"
id="tspan10365"
x="-119.27526"
y="434.37329">virtual tower</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="148.36713"
y="269.87662"
id="text6062"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6064"
x="148.36713"
y="269.87662">line of movement</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="237.50998"
y="251.01949"
id="text6066"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6068"
x="237.50998"
y="251.01949">move</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:7.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 354.70239,255.76769 28.12779,-77.32895"
id="path10351"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 220.36713,255.01949 380.93855,178.44807"
id="path10373"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="200.36713"
y="187.59093"
id="text10375"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan10377"
x="200.36713"
y="187.59093">virtual arm</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082)"
d="m 261.76375,182.85539 c 22.73118,-0.70136 26.45506,3.7437 40.00001,18.28573"
id="path6074"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
d="m 219.61431,255.37268 70.93001,0.37408"
id="path3514-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
docs/img/xy+z-tower.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

126
docs/img/zigzag.svg Normal file
View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="97.22496mm"
height="32.550285mm"
viewBox="0 0 344.49789 115.33566"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="zigzag.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.75"
inkscape:cx="167.32577"
inkscape:cy="45.708959"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="840"
inkscape:window-height="628"
inkscape:window-x="160"
inkscape:window-y="35"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="false"
inkscape:snap-global="false"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid3436" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-135.22429,-249.96955)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
id="path3347"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="434.04257"
y="365.1282"
id="text3349"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3351"
x="434.04257"
y="365.1282">time</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="-313.86618"
y="140.27856"
id="text3353"
sodipodi:linespacing="125%"
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
sodipodi:role="line"
id="tspan3355"
x="-313.86618"
y="140.27856">velocity</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 150,250.23455 -5.31915,8.51063"
id="path3359"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 165.50998,351.59092 24.57143,-73.14286 23.42857,73.14286"
id="path3362"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 214.08032,352.31321 24.57144,-73.14287 13.14286,43.42858"
id="path3362-3"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.09757805px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 252.42963,323.78006 17.64919,-60.34771 14.22362,59.20485"
id="path3362-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 284.36604,322.59892 14.85715,-44.00001 3.71429,0 16.85715,73.14287"
id="path3362-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
docs/img/zigzag.svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

14
docs/prints/square.scad Normal file
View File

@@ -0,0 +1,14 @@
// Test square
//
// Generate STL using OpenSCAD:
// openscad square.scad -o square.stl
square_width = 5;
square_size = 60;
square_height = 5;
difference() {
cube([square_size, square_size, square_height]);
translate([square_width, square_width, -1])
cube([square_size-2*square_width, square_size-2*square_width, square_height+2]);
}

226
docs/prints/square.stl Normal file
View File

@@ -0,0 +1,226 @@
solid OpenSCAD_Model
facet normal -1 0 0
outer loop
vertex 0 0 0
vertex 0 60 5
vertex 0 60 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 0 60 5
vertex 0 0 0
vertex 0 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 60 60 5
vertex 55 55 5
vertex 60 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 60 60 5
vertex 5 55 5
vertex 55 55 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 55 5
vertex 0 60 5
vertex 5 5 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 60 5
vertex 5 55 5
vertex 60 60 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 55 5 5
vertex 60 0 5
vertex 55 55 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 5 5 5
vertex 60 0 5
vertex 55 5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 5 5
vertex 0 0 5
vertex 60 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 0 5
vertex 5 5 5
vertex 0 60 5
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 60 0 5
vertex 60 60 0
vertex 60 60 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 60 60 0
vertex 60 0 5
vertex 60 0 0
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 60 60 0
vertex 0 60 5
vertex 60 60 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 0 60 5
vertex 60 60 0
vertex 0 60 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 60 0 0
vertex 55 5 0
vertex 60 60 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 60 0 0
vertex 5 5 0
vertex 55 5 0
endloop
endfacet
facet normal -0 0 -1
outer loop
vertex 5 5 0
vertex 0 0 0
vertex 5 55 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 0 0
vertex 5 5 0
vertex 60 0 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 55 55 0
vertex 60 60 0
vertex 55 5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 5 55 0
vertex 60 60 0
vertex 55 55 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 5 55 0
vertex 0 60 0
vertex 60 60 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 60 0
vertex 5 55 0
vertex 0 0 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 0 0
vertex 60 0 5
vertex 0 0 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 60 0 5
vertex 0 0 0
vertex 60 0 0
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 5 5 5
vertex 5 55 0
vertex 5 55 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 5 55 0
vertex 5 5 5
vertex 5 5 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 55 5 0
vertex 55 55 5
vertex 55 55 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 55 55 5
vertex 55 5 0
vertex 55 5 5
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 55 5 0
vertex 5 5 5
vertex 55 5 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 5 5 5
vertex 55 5 0
vertex 5 5 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 5 55 0
vertex 55 55 5
vertex 5 55 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 55 55 5
vertex 5 55 0
vertex 55 55 0
endloop
endfacet
endsolid OpenSCAD_Model

View File

@@ -13,21 +13,19 @@ class CartKinematics:
self.steppers = [stepper.PrinterStepper(
printer, config.getsection('stepper_' + n), n)
for n in ['x', 'y', 'z']]
self.max_z_velocity = config.getfloat('max_z_velocity', 9999999.9)
self.max_z_accel = config.getfloat('max_z_accel', 9999999.9)
self.max_z_velocity = config.getfloat(
'max_z_velocity', 9999999.9, above=0.)
self.max_z_accel = config.getfloat(
'max_z_accel', 9999999.9, above=0.)
self.need_motor_enable = True
self.limits = [(1.0, -1.0)] * 3
def set_max_jerk(self, max_xy_halt_velocity, max_accel):
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
self.steppers[0].set_max_jerk(max_xy_halt_velocity, max_accel)
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
self.steppers[2].set_max_jerk(0., self.max_z_accel)
def build_config(self):
for stepper in self.steppers:
stepper.build_config()
def set_position(self, newpos):
for i in StepList:
s = self.steppers[i]
s.mcu_stepper.set_position(int(newpos[i]*s.inv_step_dist + 0.5))
self.steppers[i].mcu_stepper.set_position(newpos[i])
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
@@ -58,7 +56,7 @@ class CartKinematics:
homing_state.home(
list(coord), homepos, [s], s.homing_speed/2.0, second_home=True)
# Set final homed position
coord[axis] = s.position_endstop + s.get_homed_offset()*s.step_dist
coord[axis] = s.position_endstop + s.get_homed_offset()
homing_state.set_homed_position(coord)
def motor_off(self, move_time):
self.limits = [(1.0, -1.0)] * 3
@@ -102,46 +100,33 @@ class CartKinematics:
def move(self, move_time, move):
if self.need_motor_enable:
self._check_motor_enable(move_time, move)
inv_accel = 1. / move.accel
inv_cruise_v = 1. / move.cruise_v
for i in StepList:
if not move.axes_d[i]:
axis_d = move.axes_d[i]
if not axis_d:
continue
mcu_stepper = self.steppers[i].mcu_stepper
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
step_pos = mcu_stepper.commanded_position
inv_step_dist = self.steppers[i].inv_step_dist
step_offset = step_pos - move.start_pos[i] * inv_step_dist
steps = move.axes_d[i] * inv_step_dist
move_step_d = move.move_d / abs(steps)
start_pos = move.start_pos[i]
axis_r = abs(axis_d) / move.move_d
accel = move.accel * axis_r
cruise_v = move.cruise_v * axis_r
# Acceleration steps
accel_multiplier = 2.0 * move_step_d * inv_accel
if move.accel_r:
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
accel_time_offset = move.start_v * inv_accel
accel_sqrt_offset = accel_time_offset**2
accel_steps = move.accel_r * steps
count = mcu_stepper.step_sqrt(
mcu_time - accel_time_offset, accel_steps, step_offset
, accel_sqrt_offset, accel_multiplier)
step_offset += count - accel_steps
accel_d = move.accel_r * axis_d
mcu_stepper.step_const(
mcu_time, start_pos, accel_d, move.start_v * axis_r, accel)
start_pos += accel_d
mcu_time += move.accel_t
# Cruising steps
if move.cruise_r:
#t = pos/cruise_v
cruise_multiplier = move_step_d * inv_cruise_v
cruise_steps = move.cruise_r * steps
count = mcu_stepper.step_factor(
mcu_time, cruise_steps, step_offset, cruise_multiplier)
step_offset += count - cruise_steps
cruise_d = move.cruise_r * axis_d
mcu_stepper.step_const(
mcu_time, start_pos, cruise_d, cruise_v, 0.)
start_pos += cruise_d
mcu_time += move.cruise_t
# Deceleration steps
if move.decel_r:
#t = cruise_v/accel - sqrt((cruise_v/accel)**2 - 2*pos/accel)
decel_time_offset = move.cruise_v * inv_accel
decel_sqrt_offset = decel_time_offset**2
decel_steps = move.decel_r * steps
count = mcu_stepper.step_sqrt(
mcu_time + decel_time_offset, decel_steps, step_offset
, decel_sqrt_offset, -accel_multiplier)
decel_d = move.decel_r * axis_d
mcu_stepper.step_const(
mcu_time, start_pos, decel_d, cruise_v, -accel)

View File

@@ -1,12 +1,17 @@
# Wrapper around C helper code
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, logging
import cffi
COMPILE_CMD = "gcc -Wall -g -O -shared -fPIC -o %s %s"
######################################################################
# c_helper.so compiling
######################################################################
COMPILE_CMD = "gcc -Wall -g -O2 -shared -fPIC -o %s %s"
SOURCE_FILES = ['stepcompress.c', 'serialqueue.c', 'pyhelper.c']
DEST_LIB = "c_helper.so"
OTHER_FILES = ['list.h', 'serialqueue.h', 'pyhelper.h']
@@ -16,31 +21,22 @@ defs_stepcompress = """
, uint32_t queue_step_msgid, uint32_t set_next_step_dir_msgid
, uint32_t invert_sdir, uint32_t oid);
void stepcompress_free(struct stepcompress *sc);
void stepcompress_push(struct stepcompress *sc, double step_clock
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
int stepcompress_set_homing(struct stepcompress *sc, uint64_t homing_clock);
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
int stepcompress_push(struct stepcompress *sc, double step_clock
, int32_t sdir);
int32_t stepcompress_push_factor(struct stepcompress *sc
, double steps, double step_offset
, double clock_offset, double factor);
int32_t stepcompress_push_sqrt(struct stepcompress *sc
, double steps, double step_offset
, double clock_offset, double sqrt_offset, double factor);
int32_t stepcompress_push_delta_const(struct stepcompress *sc
, double clock_offset, double dist, double start_pos
, double inv_velocity, double step_dist, double height
, double closestxy_d, double closest_height2, double movez_r);
int32_t stepcompress_push_delta_accel(struct stepcompress *sc
, double clock_offset, double dist, double start_pos
, double accel_multiplier, double step_dist, double height
, double closestxy_d, double closest_height2, double movez_r);
void stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
void stepcompress_queue_msg(struct stepcompress *sc
, uint32_t *data, int len);
uint32_t stepcompress_get_errors(struct stepcompress *sc);
int32_t stepcompress_push_const(struct stepcompress *sc, double clock_offset
, double step_offset, double steps, double start_sv, double accel);
int32_t stepcompress_push_delta(struct stepcompress *sc
, double clock_offset, double move_sd, double start_sv, double accel
, double height, double startxy_sd, double arm_d, double movez_r);
struct steppersync *steppersync_alloc(struct serialqueue *sq
, struct stepcompress **sc_list, int sc_num, int move_num);
void steppersync_free(struct steppersync *ss);
void steppersync_flush(struct steppersync *ss, uint64_t move_clock);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
"""
defs_serialqueue = """
@@ -65,7 +61,6 @@ defs_serialqueue = """
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust);
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
, double last_ack_time, uint64_t last_ack_clock);
void serialqueue_flush_ready(struct serialqueue *sq);
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
int serialqueue_extract_old(struct serialqueue *sq, int sentq
, struct pull_queue_message *q, int max);
@@ -73,6 +68,7 @@ defs_serialqueue = """
defs_pyhelper = """
void set_python_logging_callback(void (*func)(const char *));
double get_monotonic(void);
"""
# Return the list of file modification times
@@ -88,14 +84,14 @@ def get_mtimes(srcdir, filelist):
return out
# Check if the code needs to be compiled
def check_build_code(srcdir):
src_times = get_mtimes(srcdir, SOURCE_FILES + OTHER_FILES)
obj_times = get_mtimes(srcdir, [DEST_LIB])
def check_build_code(srcdir, target, sources, cmd, other_files=[]):
src_times = get_mtimes(srcdir, sources + other_files)
obj_times = get_mtimes(srcdir, [target])
if not obj_times or max(src_times) > min(obj_times):
logging.info("Building C code module")
srcfiles = [os.path.join(srcdir, fname) for fname in SOURCE_FILES]
destlib = os.path.join(srcdir, DEST_LIB)
os.system(COMPILE_CMD % (destlib, ' '.join(srcfiles)))
logging.info("Building C code module %s" % (target,))
srcfiles = [os.path.join(srcdir, fname) for fname in sources]
destlib = os.path.join(srcdir, target)
os.system(cmd % (destlib, ' '.join(srcfiles)))
FFI_main = None
FFI_lib = None
@@ -106,7 +102,8 @@ def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback
if FFI_lib is None:
srcdir = os.path.dirname(os.path.realpath(__file__))
check_build_code(srcdir)
check_build_code(srcdir, DEST_LIB, SOURCE_FILES, COMPILE_CMD
, OTHER_FILES)
FFI_main = cffi.FFI()
FFI_main.cdef(defs_stepcompress)
FFI_main.cdef(defs_serialqueue)
@@ -119,3 +116,20 @@ def get_ffi():
"void(const char *)", logging_callback)
FFI_lib.set_python_logging_callback(pyhelper_logging_callback)
return FFI_main, FFI_lib
######################################################################
# hub-ctrl hub power controller
######################################################################
HC_COMPILE_CMD = "gcc -Wall -g -O2 -o %s %s -lusb"
HC_SOURCE_FILES = ['hub-ctrl.c']
HC_SOURCE_DIR = '../lib/hub-ctrl'
HC_TARGET = "hub-ctrl"
HC_CMD = "sudo %s/hub-ctrl -h 0 -P 2 -p %d"
def run_hub_ctrl(enable_power):
srcdir = os.path.dirname(os.path.realpath(__file__))
hubdir = os.path.join(srcdir, HC_SOURCE_DIR)
check_build_code(hubdir, HC_TARGET, HC_SOURCE_FILES, HC_COMPILE_CMD)
os.system(HC_CMD % (hubdir, enable_power))

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# Script to implement a test console with firmware over serial port
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, os, re, logging
@@ -16,6 +16,7 @@ class KeyboardReader:
self.reactor = reactor
self.fd = sys.stdin.fileno()
util.set_nonblock(self.fd)
self.mcu_freq = 0
self.pins = None
self.data = ""
reactor.register_fd(self.fd, self.process_kbd)
@@ -24,49 +25,51 @@ class KeyboardReader:
self.eval_globals = {}
def connect(self, eventtime):
self.ser.connect()
self.ser.handle_default = self.handle_default
self.mcu_freq = self.ser.msgparser.get_constant_float('CLOCK_FREQ')
mcu = self.ser.msgparser.get_constant('MCU')
self.pins = pins.get_pin_map(mcu)
self.reactor.unregister_timer(self.connect_timer)
return self.reactor.NEVER
def output(self, msg):
sys.stdout.write("%s\n" % (msg,))
sys.stdout.flush()
def handle_default(self, params):
self.output(self.ser.msgparser.format_params(params))
def update_evals(self, eventtime):
f = int(self.ser.msgparser.config.get('CLOCK_FREQ', 1))
c = self.ser.get_clock(eventtime)
self.eval_globals['freq'] = f
self.eval_globals['clock'] = int(c)
self.eval_globals['freq'] = self.mcu_freq
self.eval_globals['clock'] = self.ser.get_clock(eventtime)
def set_pin_map(self, parts):
mcu = self.ser.msgparser.config['MCU']
self.pins = pins.map_pins(parts[1], mcu)
mcu = self.ser.msgparser.get_constant('MCU')
self.pins = pins.get_pin_map(mcu, parts[1])
def set_var(self, parts):
val = parts[2]
try:
val = int(val)
except ValueError:
try:
val = float(val)
except ValueError:
pass
self.eval_globals[parts[1]] = val
def lookup_pin(self, value):
if self.pins is None:
self.pins = pins.mcu_to_pins(self.ser.msgparser.config['MCU'])
return self.pins[value]
def translate(self, line, eventtime):
evalparts = re_eval.split(line)
if len(evalparts) > 1:
self.update_evals(eventtime)
try:
for i in range(1, len(evalparts), 2):
evalparts[i] = str(eval(evalparts[i], self.eval_globals))
e = eval(evalparts[i], self.eval_globals)
if type(e) == type(0.):
e = int(e)
evalparts[i] = str(e)
except:
print "Unable to evaluate: ", line
self.output("Unable to evaluate: %s" % (line,))
return None
line = ''.join(evalparts)
print "Eval:", line
if self.pins is None and self.ser.msgparser.config:
self.pins = pins.mcu_to_pins(self.ser.msgparser.config['MCU'])
self.output("Eval: %s" % (line,))
if self.pins is not None:
try:
line = pins.update_command(line, self.pins).strip()
line = pins.update_command(
line, self.mcu_freq, self.pins).strip()
except:
print "Unable to map pin: ", line
self.output("Unable to map pin: %s" % (line,))
return None
if line:
parts = line.split()
@@ -76,7 +79,7 @@ class KeyboardReader:
try:
msg = self.ser.msgparser.create_command(line)
except msgproto.error, e:
print "Error:", e
self.output("Error: %s" % (str(e),))
return None
return msg
def process_kbd(self, eventtime):

146
klippy/corexy.py Normal file
View File

@@ -0,0 +1,146 @@
# Code for handling the kinematics of corexy robots
#
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import stepper, homing
StepList = (0, 1, 2)
class CoreXYKinematics:
def __init__(self, printer, config):
self.steppers = [stepper.PrinterStepper(
printer, config.getsection('stepper_' + n), n)
for n in ['x', 'y', 'z']]
self.steppers[0].mcu_endstop.add_stepper(self.steppers[1].mcu_stepper)
self.steppers[1].mcu_endstop.add_stepper(self.steppers[0].mcu_stepper)
self.max_z_velocity = config.getfloat(
'max_z_velocity', 9999999.9, above=0.)
self.max_z_accel = config.getfloat(
'max_z_accel', 9999999.9, above=0.)
self.need_motor_enable = True
self.limits = [(1.0, -1.0)] * 3
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
self.steppers[0].set_max_jerk(max_xy_halt_velocity, max_accel)
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
self.steppers[2].set_max_jerk(0., self.max_z_accel)
def set_position(self, newpos):
pos = (newpos[0] + newpos[1], newpos[0] - newpos[1], newpos[2])
for i in StepList:
self.steppers[i].mcu_stepper.set_position(pos[i])
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
s = self.steppers[axis]
self.limits[axis] = (s.position_min, s.position_max)
# Determine moves
if s.homing_positive_dir:
pos = s.position_endstop - 1.5*(
s.position_endstop - s.position_min)
rpos = s.position_endstop - s.homing_retract_dist
r2pos = rpos - s.homing_retract_dist
else:
pos = s.position_endstop + 1.5*(
s.position_max - s.position_endstop)
rpos = s.position_endstop + s.homing_retract_dist
r2pos = rpos + s.homing_retract_dist
# Initial homing
homepos = [None, None, None, None]
homepos[axis] = s.position_endstop
coord = [None, None, None, None]
coord[axis] = pos
homing_state.home(list(coord), homepos, [s], s.homing_speed)
# Retract
coord[axis] = rpos
homing_state.retract(list(coord), s.homing_speed)
# Home again
coord[axis] = r2pos
homing_state.home(
list(coord), homepos, [s], s.homing_speed/2.0, second_home=True)
if axis == 2:
# Support endstop phase detection on Z axis
coord[axis] = s.position_endstop + s.get_homed_offset()
homing_state.set_homed_position(coord)
def motor_off(self, move_time):
self.limits = [(1.0, -1.0)] * 3
for stepper in self.steppers:
stepper.motor_enable(move_time, 0)
self.need_motor_enable = True
def _check_motor_enable(self, move_time, move):
if move.axes_d[0] or move.axes_d[1]:
self.steppers[0].motor_enable(move_time, 1)
self.steppers[1].motor_enable(move_time, 1)
if move.axes_d[2]:
self.steppers[2].motor_enable(move_time, 1)
need_motor_enable = False
for i in StepList:
need_motor_enable |= self.steppers[i].need_motor_enable
self.need_motor_enable = need_motor_enable
def query_endstops(self, print_time):
endstops = [(s, s.query_endstop(print_time)) for s in self.steppers]
return [(s.name, es.query_endstop_wait()) for s, es in endstops]
def _check_endstops(self, move):
end_pos = move.end_pos
for i in StepList:
if (move.axes_d[i]
and (end_pos[i] < self.limits[i][0]
or end_pos[i] > self.limits[i][1])):
if self.limits[i][0] > self.limits[i][1]:
raise homing.EndstopMoveError(
end_pos, "Must home axis first")
raise homing.EndstopMoveError(end_pos)
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
if (xpos < limits[0][0] or xpos > limits[0][1]
or ypos < limits[1][0] or ypos > limits[1][1]):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
return
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
move.limit_speed(
self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
def move(self, move_time, move):
if self.need_motor_enable:
self._check_motor_enable(move_time, move)
sxp = move.start_pos[0]
syp = move.start_pos[1]
move_start_pos = (sxp + syp, sxp - syp, move.start_pos[2])
exp = move.end_pos[0]
eyp = move.end_pos[1]
axes_d = ((exp + eyp) - move_start_pos[0],
(exp - eyp) - move_start_pos[1], move.axes_d[2])
for i in StepList:
axis_d = axes_d[i]
if not axis_d:
continue
mcu_stepper = self.steppers[i].mcu_stepper
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
start_pos = move_start_pos[i]
axis_r = abs(axis_d) / move.move_d
accel = move.accel * axis_r
cruise_v = move.cruise_v * axis_r
# Acceleration steps
if move.accel_r:
accel_d = move.accel_r * axis_d
mcu_stepper.step_const(
mcu_time, start_pos, accel_d, move.start_v * axis_r, accel)
start_pos += accel_d
mcu_time += move.accel_t
# Cruising steps
if move.cruise_r:
cruise_d = move.cruise_r * axis_d
mcu_stepper.step_const(
mcu_time, start_pos, cruise_d, cruise_v, 0.)
start_pos += cruise_d
mcu_time += move.cruise_t
# Deceleration steps
if move.decel_r:
decel_d = move.decel_r * axis_d
mcu_stepper.step_const(
mcu_time, start_pos, decel_d, cruise_v, -accel)

View File

@@ -1,6 +1,6 @@
# Code for handling the kinematics of linear delta robots
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
@@ -8,40 +8,61 @@ import stepper, homing
StepList = (0, 1, 2)
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
SLOW_RATIO = 3.
class DeltaKinematics:
def __init__(self, printer, config):
self.config = config
self.steppers = [stepper.PrinterStepper(
printer, config.getsection('stepper_' + n), n)
for n in ['a', 'b', 'c']]
self.need_motor_enable = True
self.max_z_velocity = config.getfloat('max_z_velocity', 9999999.9)
radius = config.getfloat('delta_radius')
arm_length = config.getfloat('delta_arm_length')
self.need_motor_enable = self.need_home = True
self.max_velocity = self.max_z_velocity = self.max_accel = 0.
radius = config.getfloat('delta_radius', above=0.)
arm_length = config.getfloat('delta_arm_length', above=radius)
self.arm_length2 = arm_length**2
self.max_xy2 = min(radius, arm_length - radius)**2
self.limit_xy2 = -1.
tower_height_at_zeros = math.sqrt(self.arm_length2 - radius**2)
self.max_z = self.steppers[0].position_max
self.max_z = max([s.position_endstop for s in self.steppers])
self.limit_z = self.max_z - (arm_length - tower_height_at_zeros)
logging.info(
"Delta max build height %.2fmm (radius tapered above %.2fmm)" % (
self.max_z, self.limit_z))
sin = lambda angle: math.sin(math.radians(angle))
cos = lambda angle: math.cos(math.radians(angle))
self.towers = [
(cos(210.)*radius, sin(210.)*radius),
(cos(330.)*radius, sin(330.)*radius),
(cos(90.)*radius, sin(90.)*radius)]
def set_max_jerk(self, max_xy_halt_velocity, max_accel):
# XXX - this sets conservative values
# Find the point where an XY move could result in excessive
# tower movement
half_min_step_dist = min([s.step_dist for s in self.steppers]) * .5
def ratio_to_dist(ratio):
return (ratio * math.sqrt(self.arm_length2 / (ratio**2 + 1.)
- half_min_step_dist**2)
+ half_min_step_dist)
self.slow_xy2 = (ratio_to_dist(SLOW_RATIO) - radius)**2
self.very_slow_xy2 = (ratio_to_dist(2. * SLOW_RATIO) - radius)**2
self.max_xy2 = min(radius, arm_length - radius,
ratio_to_dist(4. * SLOW_RATIO) - radius)**2
logging.info(
"Delta max build radius %.2fmm (moves slowed past %.2fmm and %.2fmm)"
% (math.sqrt(self.max_xy2), math.sqrt(self.slow_xy2),
math.sqrt(self.very_slow_xy2)))
self.set_position([0., 0., 0.])
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
self.max_velocity = max_velocity
max_z_velocity = self.config.getfloat(
'max_z_velocity', max_velocity, above=0.)
self.max_z_velocity = min(max_velocity, max_z_velocity)
self.max_accel = max_accel
for stepper in self.steppers:
stepper.set_max_jerk(max_xy_halt_velocity, max_accel)
def build_config(self):
for stepper in self.steppers:
stepper.build_config()
self.set_position([0., 0., 0.])
def _cartesian_to_actuator(self, coord):
return [int((math.sqrt(self.arm_length2
return [math.sqrt(self.arm_length2
- (self.towers[i][0] - coord[0])**2
- (self.towers[i][1] - coord[1])**2) + coord[2])
* self.steppers[i].inv_step_dist + 0.5)
- (self.towers[i][1] - coord[1])**2) + coord[2]
for i in StepList]
def _actuator_to_cartesian(self, pos):
# Based on code from Smoothieware
@@ -78,13 +99,14 @@ class DeltaKinematics:
pos = self._cartesian_to_actuator(newpos)
for i in StepList:
self.steppers[i].mcu_stepper.set_position(pos[i])
self.limit_xy2 = -1.
def home(self, homing_state):
# All axes are homed simultaneously
homing_state.set_axes([0, 1, 2])
s = self.steppers[0] # Assume homing parameters same for all steppers
self.limit_xy2 = self.max_xy2
s = self.steppers[0] # Assume homing speed same for all steppers
self.need_home = False
# Initial homing
homepos = [0., 0., s.position_endstop, None]
homepos = [0., 0., self.max_z, None]
coord = list(homepos)
coord[2] = -1.5 * math.sqrt(self.arm_length2-self.max_xy2)
homing_state.home(list(coord), homepos, self.steppers, s.homing_speed)
@@ -96,15 +118,14 @@ class DeltaKinematics:
homing_state.home(list(coord), homepos, self.steppers
, s.homing_speed/2.0, second_home=True)
# Set final homed position
coord = [(s.mcu_stepper.commanded_position + s.get_homed_offset())
* s.step_dist
coord = [s.mcu_stepper.get_commanded_position() + s.get_homed_offset()
for s in self.steppers]
homing_state.set_homed_position(self._actuator_to_cartesian(coord))
def motor_off(self, move_time):
self.limit_xy2 = -1.
for stepper in self.steppers:
stepper.motor_enable(move_time, 0)
self.need_motor_enable = True
self.need_motor_enable = self.need_home = True
def _check_motor_enable(self, move_time):
for i in StepList:
self.steppers[i].motor_enable(move_time, 1)
@@ -115,127 +136,90 @@ class DeltaKinematics:
def check_move(self, move):
end_pos = move.end_pos
xy2 = end_pos[0]**2 + end_pos[1]**2
if xy2 > self.limit_xy2 or end_pos[2] < 0.:
if self.limit_xy2 < 0.:
if xy2 <= self.limit_xy2 and not move.axes_d[2]:
# Normal XY move
return
if self.need_home:
raise homing.EndstopMoveError(end_pos, "Must home first")
raise homing.EndstopMoveError(end_pos)
limit_xy2 = self.max_xy2
if end_pos[2] > self.limit_z:
if end_pos[2] > self.max_z or xy2 > (self.max_z - end_pos[2])**2:
limit_xy2 = min(limit_xy2, (self.max_z - end_pos[2])**2)
if xy2 > limit_xy2 or end_pos[2] < 0. or end_pos[2] > self.max_z:
raise homing.EndstopMoveError(end_pos)
if move.axes_d[2]:
move.limit_speed(self.max_z_velocity, 9999999.9)
move.limit_speed(self.max_z_velocity, move.accel)
limit_xy2 = -1.
# Limit the speed/accel of this move if is is at the extreme
# end of the build envelope
extreme_xy2 = max(xy2, move.start_pos[0]**2 + move.start_pos[1]**2)
if extreme_xy2 > self.slow_xy2:
r = 0.5
if extreme_xy2 > self.very_slow_xy2:
r = 0.25
max_velocity = self.max_velocity
if move.axes_d[2]:
max_velocity = self.max_z_velocity
move.limit_speed(max_velocity * r, self.max_accel * r)
limit_xy2 = -1.
self.limit_xy2 = min(limit_xy2, self.slow_xy2)
def move(self, move_time, move):
if self.need_motor_enable:
self._check_motor_enable(move_time)
axes_d = move.axes_d
move_d = movexy_d = move.move_d
move_d = move.move_d
movexy_r = 1.
movez_r = 0.
inv_movexy_d = 1. / movexy_d
inv_movexy_d = 1. / move_d
if not axes_d[0] and not axes_d[1]:
if not axes_d[2]:
return
# Z only move
movez_r = axes_d[2] * inv_movexy_d
movexy_d = movexy_r = inv_movexy_d = 0.
movexy_r = inv_movexy_d = 0.
elif axes_d[2]:
# XY+Z move
movexy_d = math.sqrt(axes_d[0]**2 + axes_d[1]**2)
movexy_r = movexy_d * inv_movexy_d
movez_r = axes_d[2] * inv_movexy_d
inv_movexy_d = 1. / movexy_d
if self.need_motor_enable:
self._check_motor_enable(move_time)
origx, origy, origz = move.start_pos[:3]
accel_t = move.accel_t
cruise_end_t = accel_t + move.cruise_t
accel = move.accel
cruise_v = move.cruise_v
accel_d = move.accel_r * move_d
cruise_end_d = accel_d + move.cruise_r * move_d
inv_cruise_v = 1. / move.cruise_v
inv_accel = 1. / move.accel
accel_time_offset = move.start_v * inv_accel
accel_multiplier = 2.0 * inv_accel
accel_offset = move.start_v**2 * 0.5 * inv_accel
decel_time_offset = move.cruise_v * inv_accel + cruise_end_t
decel_offset = move.cruise_v**2 * 0.5 * inv_accel + cruise_end_d
cruise_d = move.cruise_r * move_d
decel_d = move.decel_r * move_d
for i in StepList:
# Find point on line of movement closest to tower
# Calculate a virtual tower along the line of movement at
# the point closest to this stepper's tower.
towerx_d = self.towers[i][0] - origx
towery_d = self.towers[i][1] - origy
closestxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
tangentxy_d2 = towerx_d**2 + towery_d**2 - closestxy_d**2
closest_height2 = self.arm_length2 - tangentxy_d2
# Calculate accel/cruise/decel portions of move
reversexy_d = closestxy_d + math.sqrt(closest_height2)*movez_r
accel_up_d = cruise_up_d = decel_up_d = 0.
accel_down_d = cruise_down_d = decel_down_d = 0.
if reversexy_d <= 0.:
accel_down_d = accel_d
cruise_down_d = cruise_end_d
decel_down_d = move_d
elif reversexy_d >= movexy_d:
accel_up_d = accel_d
cruise_up_d = cruise_end_d
decel_up_d = move_d
elif reversexy_d < accel_d * movexy_r:
accel_up_d = reversexy_d * move_d * inv_movexy_d
accel_down_d = accel_d
cruise_down_d = cruise_end_d
decel_down_d = move_d
elif reversexy_d < cruise_end_d * movexy_r:
accel_up_d = accel_d
cruise_up_d = reversexy_d * move_d * inv_movexy_d
cruise_down_d = cruise_end_d
decel_down_d = move_d
else:
accel_up_d = accel_d
cruise_up_d = cruise_end_d
decel_up_d = reversexy_d * move_d * inv_movexy_d
decel_down_d = move_d
vt_startxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
tangentxy_d2 = towerx_d**2 + towery_d**2 - vt_startxy_d**2
vt_arm_d = math.sqrt(self.arm_length2 - tangentxy_d2)
vt_startz = origz
# Generate steps
mcu_stepper = self.steppers[i].mcu_stepper
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
step_pos = mcu_stepper.commanded_position
step_dist = self.steppers[i].step_dist
height = step_pos*step_dist - origz
if accel_up_d > 0.:
count = mcu_stepper.step_delta_accel(
mcu_time - accel_time_offset, accel_up_d,
accel_offset, accel_multiplier, step_dist,
height, closestxy_d, closest_height2, movez_r)
height += count * step_dist
if cruise_up_d > 0.:
count = mcu_stepper.step_delta_const(
mcu_time + accel_t, cruise_up_d,
-accel_d, inv_cruise_v, step_dist,
height, closestxy_d, closest_height2, movez_r)
height += count * step_dist
if decel_up_d > 0.:
count = mcu_stepper.step_delta_accel(
mcu_time + decel_time_offset, decel_up_d,
-decel_offset, -accel_multiplier, step_dist,
height, closestxy_d, closest_height2, movez_r)
height += count * step_dist
if accel_down_d > 0.:
count = mcu_stepper.step_delta_accel(
mcu_time - accel_time_offset, accel_down_d,
accel_offset, accel_multiplier, -step_dist,
height, closestxy_d, closest_height2, movez_r)
height += count * step_dist
if cruise_down_d > 0.:
count = mcu_stepper.step_delta_const(
mcu_time + accel_t, cruise_down_d,
-accel_d, inv_cruise_v, -step_dist,
height, closestxy_d, closest_height2, movez_r)
height += count * step_dist
if decel_down_d > 0.:
count = mcu_stepper.step_delta_accel(
mcu_time + decel_time_offset, decel_down_d,
-decel_offset, -accel_multiplier, -step_dist,
height, closestxy_d, closest_height2, movez_r)
if accel_d:
mcu_stepper.step_delta(
mcu_time, accel_d, move.start_v, accel,
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
vt_startz += accel_d * movez_r
vt_startxy_d -= accel_d * movexy_r
mcu_time += move.accel_t
if cruise_d:
mcu_stepper.step_delta(
mcu_time, cruise_d, cruise_v, 0.,
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
vt_startz += cruise_d * movez_r
vt_startxy_d -= cruise_d * movexy_r
mcu_time += move.cruise_t
if decel_d:
mcu_stepper.step_delta(
mcu_time, decel_d, cruise_v, -accel,
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
######################################################################

View File

@@ -3,45 +3,127 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import math, logging
import stepper, heater, homing
EXTRUDE_DIFF_IGNORE = 1.02
class PrinterExtruder:
def __init__(self, printer, config):
self.config = config
self.heater = heater.PrinterHeater(printer, config)
self.stepper = stepper.PrinterStepper(printer, config, 'extruder')
self.pressure_advance = config.getfloat('pressure_advance', 0.)
self.max_e_velocity = config.getfloat('max_velocity')
self.max_e_accel = config.getfloat('max_accel')
self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
filament_diameter = config.getfloat(
'filament_diameter', minval=self.nozzle_diameter)
filament_area = math.pi * (filament_diameter * .5)**2
max_cross_section = config.getfloat(
'max_extrude_cross_section', 4. * self.nozzle_diameter**2
, above=0.)
self.max_extrude_ratio = max_cross_section / filament_area
self.max_e_dist = config.getfloat(
'max_extrude_only_distance', 50., minval=0.)
self.max_e_velocity = self.max_e_accel = None
self.pressure_advance = config.getfloat(
'pressure_advance', 0., minval=0.)
self.pressure_advance_lookahead_time = 0.
if self.pressure_advance:
self.pressure_advance_lookahead_time = config.getfloat(
'pressure_advance_lookahead_time', 0.010, minval=0.)
self.need_motor_enable = True
self.extrude_pos = 0.
def build_config(self):
self.heater.build_config()
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
self.max_e_velocity = self.config.getfloat(
'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio
, above=0.)
self.max_e_accel = self.config.getfloat(
'max_extrude_only_accel', max_accel * self.max_extrude_ratio
, above=0.)
self.stepper.set_max_jerk(9999999.9, 9999999.9)
self.stepper.build_config()
def motor_off(self, move_time):
self.stepper.motor_enable(move_time, 0)
self.need_motor_enable = True
def check_move(self, move):
move.extrude_r = move.axes_d[3] / move.move_d
move.extrude_max_corner_v = 0.
if not self.heater.can_extrude:
raise homing.EndstopMoveError(
move.end_pos, "Extrude below minimum temp")
if (not move.do_calc_junction
and not move.axes_d[0] and not move.axes_d[1]
and not move.axes_d[2]):
# Extrude only move - limit accel and velocity
move.limit_speed(self.max_e_velocity, self.max_e_accel)
if not move.is_kinematic_move or move.extrude_r < 0.:
# Extrude only move (or retraction move) - limit accel and velocity
if abs(move.axes_d[3]) > self.max_e_dist:
raise homing.EndstopMoveError(
move.end_pos, "Extrude move too long")
inv_extrude_r = 1. / abs(move.extrude_r)
move.limit_speed(self.max_e_velocity * inv_extrude_r
, self.max_e_accel * inv_extrude_r)
elif (move.extrude_r > self.max_extrude_ratio
and move.axes_d[3] > self.nozzle_diameter*self.max_extrude_ratio):
logging.debug("Overextrude: %s vs %s" % (
move.extrude_r, self.max_extrude_ratio))
raise homing.EndstopMoveError(
move.end_pos, "Move exceeds maximum extrusion cross section")
def calc_junction(self, prev_move, move):
extrude = move.axes_d[3]
prev_extrude = prev_move.axes_d[3]
if extrude or prev_extrude:
if not extrude or not prev_extrude:
# Extrude move to non-extrude move - disable lookahead
return 0.
if ((move.extrude_r > prev_move.extrude_r * EXTRUDE_DIFF_IGNORE
or prev_move.extrude_r > move.extrude_r * EXTRUDE_DIFF_IGNORE)
and abs(move.move_d * prev_move.extrude_r - extrude) >= .001):
# Extrude ratio between moves is too different
return 0.
move.extrude_r = prev_move.extrude_r
return move.max_cruise_v2
def lookahead(self, moves, flush_count, lazy):
lookahead_t = self.pressure_advance_lookahead_time
if not lookahead_t:
return flush_count
# Calculate max_corner_v - the speed the head will accelerate
# to after cornering.
for i in range(flush_count):
move = moves[i]
if not move.decel_t:
continue
cruise_v = move.cruise_v
max_corner_v = 0.
sum_t = lookahead_t
for j in range(i+1, flush_count):
fmove = moves[j]
if not fmove.max_start_v2:
break
if fmove.cruise_v > max_corner_v:
if (not max_corner_v
and not fmove.accel_t and not fmove.cruise_t):
# Start timing after any full decel moves
continue
if sum_t >= fmove.accel_t:
max_corner_v = fmove.cruise_v
else:
max_corner_v = max(
max_corner_v, fmove.start_v + fmove.accel * sum_t)
if max_corner_v >= cruise_v:
break
sum_t -= fmove.accel_t + fmove.cruise_t + fmove.decel_t
if sum_t <= 0.:
break
else:
if lazy:
return i
move.extrude_max_corner_v = max_corner_v
return flush_count
def move(self, move_time, move):
if self.need_motor_enable:
self.stepper.motor_enable(move_time, 1)
self.need_motor_enable = False
axis_d = move.axes_d[3]
extrude_r = abs(axis_d) / move.move_d
inv_accel = 1. / (move.accel * extrude_r)
start_v = move.start_v * extrude_r
cruise_v = move.cruise_v * extrude_r
end_v = move.end_v * extrude_r
axis_r = abs(axis_d) / move.move_d
accel = move.accel * axis_r
start_v = move.start_v * axis_r
cruise_v = move.cruise_v * axis_r
end_v = move.end_v * axis_r
accel_t, cruise_t, decel_t = move.accel_t, move.cruise_t, move.decel_t
accel_d = move.accel_r * axis_d
cruise_d = move.cruise_r * axis_d
@@ -55,25 +137,19 @@ class PrinterExtruder:
if (axis_d >= 0. and (move.axes_d[0] or move.axes_d[1])
and self.pressure_advance):
# Increase accel_d and start_v when accelerating
move_extrude_r = move.extrude_r
pressure_advance = self.pressure_advance * move.extrude_r
prev_pressure_d = start_pos - move.start_pos[3]
if accel_t:
npd = move.cruise_v * move_extrude_r * self.pressure_advance
if accel_d:
npd = move.cruise_v * pressure_advance
extra_accel_d = npd - prev_pressure_d
if extra_accel_d > 0.:
accel_d += extra_accel_d
start_v += extra_accel_d / accel_t
prev_pressure_d += extra_accel_d
# Update decel and retract parameters when decelerating
if decel_t:
if move.corner_min:
npd = move.corner_max*move_extrude_r * self.pressure_advance
extra_decel_d = prev_pressure_d - npd
if move.end_v > move.corner_min:
extra_decel_d *= ((move.cruise_v - move.end_v)
/ (move.cruise_v - move.corner_min))
else:
npd = move.end_v * move_extrude_r * self.pressure_advance
emcv = move.extrude_max_corner_v
if decel_d and emcv < move.cruise_v:
npd = max(emcv, move.end_v) * pressure_advance
extra_decel_d = prev_pressure_d - npd
if extra_decel_d > 0.:
extra_decel_v = extra_decel_d / decel_t
@@ -87,7 +163,7 @@ class PrinterExtruder:
decel_t = decel_d = 0.
elif end_v < 0.:
# Split decel phase into decel and retraction
retract_t = -end_v * inv_accel
retract_t = -end_v / accel
retract_d = -end_v * 0.5 * retract_t
decel_t -= retract_t
decel_d = decel_v * 0.5 * decel_t
@@ -96,53 +172,41 @@ class PrinterExtruder:
decel_d -= extra_decel_d
# Prepare for steps
inv_step_dist = self.stepper.inv_step_dist
step_dist = self.stepper.step_dist
mcu_stepper = self.stepper.mcu_stepper
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
step_pos = mcu_stepper.commanded_position
step_offset = step_pos - start_pos * inv_step_dist
# Acceleration steps
accel_multiplier = 2.0 * step_dist * inv_accel
if accel_d:
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
accel_time_offset = start_v * inv_accel
accel_sqrt_offset = accel_time_offset**2
accel_steps = accel_d * inv_step_dist
count = mcu_stepper.step_sqrt(
mcu_time - accel_time_offset, accel_steps, step_offset
, accel_sqrt_offset, accel_multiplier)
step_offset += count - accel_steps
mcu_stepper.step_const(mcu_time, start_pos, accel_d, start_v, accel)
start_pos += accel_d
mcu_time += accel_t
# Cruising steps
if cruise_d:
#t = pos/cruise_v
cruise_multiplier = step_dist / cruise_v
cruise_steps = cruise_d * inv_step_dist
count = mcu_stepper.step_factor(
mcu_time, cruise_steps, step_offset, cruise_multiplier)
step_offset += count - cruise_steps
mcu_stepper.step_const(mcu_time, start_pos, cruise_d, cruise_v, 0.)
start_pos += cruise_d
mcu_time += cruise_t
# Deceleration steps
if decel_d:
#t = cruise_v/accel - sqrt((cruise_v/accel)**2 - 2*pos/accel)
decel_time_offset = decel_v * inv_accel
decel_sqrt_offset = decel_time_offset**2
decel_steps = decel_d * inv_step_dist
count = mcu_stepper.step_sqrt(
mcu_time + decel_time_offset, decel_steps, step_offset
, decel_sqrt_offset, -accel_multiplier)
step_offset += count - decel_steps
mcu_stepper.step_const(mcu_time, start_pos, decel_d, decel_v, -accel)
start_pos += decel_d
mcu_time += decel_t
# Retraction steps
if retract_d:
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
accel_time_offset = retract_v * inv_accel
accel_sqrt_offset = accel_time_offset**2
accel_steps = -retract_d * inv_step_dist
count = mcu_stepper.step_sqrt(
mcu_time - accel_time_offset, accel_steps, step_offset
, accel_sqrt_offset, accel_multiplier)
mcu_stepper.step_const(
mcu_time, start_pos, -retract_d, retract_v, accel)
start_pos -= retract_d
self.extrude_pos = start_pos
self.extrude_pos = start_pos + accel_d + cruise_d + decel_d - retract_d
# Dummy extruder class used when a printer has no extruder at all
class DummyExtruder:
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
pass
def motor_off(self, move_time):
pass
def check_move(self, move):
raise homing.EndstopMoveError(
move.end_pos, "Extrude when no extruder present")
def calc_junction(self, prev_move, move):
return move.max_cruise_v2
def lookahead(self, moves, flush_count, lazy):
return flush_count

View File

@@ -5,30 +5,27 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
FAN_MIN_TIME = 0.1
PWM_CYCLE_TIME = 0.010
class PrinterFan:
def __init__(self, printer, config):
self.printer = printer
self.config = config
self.mcu_fan = None
self.last_fan_value = 0
self.last_fan_value = 0.
self.last_fan_time = 0.
self.kick_start_time = config.getfloat('kick_start_time', 0.1)
def build_config(self):
pin = self.config.get('pin')
hard_pwm = self.config.getint('hard_pwm', 128)
self.mcu_fan = self.printer.mcu.create_pwm(pin, hard_pwm, 0)
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
pin = config.get('pin')
hard_pwm = config.getint('hard_pwm', 0)
self.mcu_fan = printer.mcu.create_pwm(pin, PWM_CYCLE_TIME, hard_pwm, 0.)
# External commands
def set_speed(self, print_time, value):
value = max(0, min(255, int(value*255. + 0.5)))
value = max(0., min(1., value))
if value == self.last_fan_value:
return
mcu_time = self.mcu_fan.print_to_mcu_time(print_time)
mcu_time = max(self.last_fan_time + FAN_MIN_TIME, mcu_time)
if (value and value < 255
if (value and value < 1.
and not self.last_fan_value and self.kick_start_time):
# Run fan at full speed for specified kick_start_time
self.mcu_fan.set_pwm(mcu_time, 255)
self.mcu_fan.set_pwm(mcu_time, 1.)
mcu_time += self.kick_start_time
self.mcu_fan.set_pwm(mcu_time, value)
self.last_fan_time = mcu_time

View File

@@ -3,7 +3,7 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, re, logging, collections, time
import os, re, logging, collections
import homing
# Parse out incoming GCode and find and translate head movements
@@ -23,7 +23,7 @@ class GCodeParser:
self.bytes_read = 0
self.input_log = collections.deque([], 50)
# Command handling
self.gcode_handlers = {}
self.gcode_handlers = self.build_handlers(False)
self.is_printer_ready = False
self.need_ack = False
self.toolhead = self.heater_nozzle = self.heater_bed = self.fan = None
@@ -33,50 +33,47 @@ class GCodeParser:
self.last_position = [0.0, 0.0, 0.0, 0.0]
self.homing_add = [0.0, 0.0, 0.0, 0.0]
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
self.build_handlers()
def build_config(self):
self.toolhead = self.printer.objects['toolhead']
self.heater_nozzle = None
extruder = self.printer.objects.get('extruder')
if extruder:
self.heater_nozzle = extruder.heater
self.heater_bed = self.printer.objects.get('heater_bed')
self.fan = self.printer.objects.get('fan')
def build_handlers(self):
handlers = ['G1', 'G4', 'G20', 'G21', 'G28', 'G90', 'G91', 'G92',
'M18', 'M82', 'M83', 'M105', 'M110', 'M112', 'M114', 'M206',
'HELP', 'QUERY_ENDSTOPS', 'RESTART', 'CLEAR_SHUTDOWN',
'STATUS']
if self.heater_nozzle is not None:
handlers.extend(['M104', 'M109', 'PID_TUNE'])
if self.heater_bed is not None:
handlers.extend(['M140', 'M190'])
if self.fan is not None:
handlers.extend(['M106', 'M107'])
if not self.is_printer_ready:
def build_handlers(self, is_ready):
handlers = self.all_handlers
if not is_ready:
handlers = [h for h in handlers
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
self.gcode_handlers = dict((h, getattr(self, 'cmd_'+h))
for h in handlers)
for h, f in self.gcode_handlers.items():
gcode_handlers = dict((h, getattr(self, 'cmd_'+h)) for h in handlers)
for h, f in gcode_handlers.items():
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
self.gcode_handlers.update(dict([(a, f) for a in aliases]))
gcode_handlers.update(dict([(a, f) for a in aliases]))
return gcode_handlers
def stats(self, eventtime):
return "gcodein=%d" % (self.bytes_read,)
def set_printer_ready(self, is_ready):
if self.is_printer_ready == is_ready:
return
self.is_printer_ready = is_ready
self.build_handlers()
if is_ready and self.is_fileinput and self.fd_handle is None:
self.gcode_handlers = self.build_handlers(is_ready)
if not is_ready:
# Printer is shutdown (could be running in a background thread)
return
# Lookup printer components
self.toolhead = self.printer.objects.get('toolhead')
self.heater_nozzle = None
extruder = self.printer.objects.get('extruder')
if extruder:
self.heater_nozzle = extruder.heater
self.heater_bed = self.printer.objects.get('heater_bed')
self.fan = self.printer.objects.get('fan')
if self.is_fileinput and self.fd_handle is None:
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
def motor_heater_off(self):
if self.toolhead is not None:
if self.toolhead is None:
return
self.toolhead.motor_off()
print_time = self.toolhead.get_last_move_time()
if self.heater_nozzle is not None:
self.heater_nozzle.set_temp(0., 0.)
self.heater_nozzle.set_temp(print_time, 0.)
if self.heater_bed is not None:
self.heater_bed.set_temp(0., 0.)
self.heater_bed.set_temp(print_time, 0.)
if self.fan is not None:
self.fan.set_speed(print_time, 0.)
def dump_debug(self):
logging.info("Dumping gcode input %d blocks" % (
len(self.input_log),))
@@ -109,10 +106,15 @@ class GCodeParser:
handler = self.gcode_handlers.get(cmd, self.cmd_default)
try:
handler(params)
except error, e:
self.respond_error(str(e))
except:
logging.exception("Exception in command handler")
self.toolhead.force_shutdown()
self.respond_error('Internal error on command:"%s"' % (cmd,))
if self.is_fileinput:
self.printer.request_exit('exit_eof')
break
self.ack()
def process_data(self, eventtime):
data = os.read(self.fd, 4096)
@@ -136,7 +138,7 @@ class GCodeParser:
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
if not data and self.is_fileinput:
self.motor_heater_off()
self.printer.request_exit_eof()
self.printer.request_exit('exit_eof')
# Response handling
def ack(self, msg=None):
if not self.need_ack or self.is_fileinput:
@@ -159,6 +161,27 @@ class GCodeParser:
if len(lines) > 1:
self.respond_info("\n".join(lines[:-1]))
self.respond('!! %s' % (lines[-1].strip(),))
# Parameter parsing helpers
def get_int(self, name, params, default=None):
if name in params:
try:
return int(params[name])
except ValueError:
raise error("Error on '%s': unable to parse %s" % (
params['#original'], params[name]))
if default is not None:
return default
raise error("Error on '%s': missing %s" % (params['#original'], name))
def get_float(self, name, params, default=None):
if name in params:
try:
return float(params[name])
except ValueError:
raise error("Error on '%s': unable to parse %s" % (
params['#original'], params[name]))
if default is not None:
return default
raise error("Error on '%s': missing %s" % (params['#original'], name))
# Temperature wrappers
def get_temp(self):
if not self.is_printer_ready:
@@ -175,17 +198,32 @@ class GCodeParser:
def bg_temp(self, heater):
if self.is_fileinput:
return
eventtime = time.time()
eventtime = self.reactor.monotonic()
while self.is_printer_ready and heater.check_busy(eventtime):
self.toolhead.reset_motor_off_time(eventtime)
print_time = self.toolhead.get_last_move_time()
self.respond(self.get_temp())
eventtime = self.reactor.pause(eventtime + 1.)
def set_temp(self, heater, params, wait=False):
temp = self.get_float('S', params, 0.)
if heater is None:
if temp > 0.:
self.respond_error("Heater not configured")
return
print_time = self.toolhead.get_last_move_time()
temp = float(params.get('S', '0'))
try:
heater.set_temp(print_time, temp)
except heater.error, e:
self.respond_error(str(e))
return
if wait:
self.bg_temp(heater)
def set_fan_speed(self, speed):
if self.fan is None:
if speed:
self.respond_info("Fan not configured")
return
print_time = self.toolhead.get_last_move_time()
self.fan.set_speed(print_time, speed)
# Individual command handlers
def cmd_default(self, params):
if not self.is_printer_ready:
@@ -196,20 +234,34 @@ class GCodeParser:
logging.debug(params['#original'])
return
self.respond('echo:Unknown command:"%s"' % (cmd,))
all_handlers = [
'G1', 'G4', 'G20', 'G28', 'G90', 'G91', 'G92',
'M82', 'M83', 'M18', 'M105', 'M104', 'M109', 'M112', 'M114', 'M115',
'M140', 'M190', 'M106', 'M107', 'M206', 'M400',
'IGNORE', 'QUERY_ENDSTOPS', 'PID_TUNE', 'RESTART', 'FIRMWARE_RESTART',
'STATUS', 'HELP']
cmd_G1_aliases = ['G0']
def cmd_G1(self, params):
# Move
try:
for a, p in self.axis2pos.items():
if a in params:
v = float(params[a])
if not self.absolutecoord or (p>2 and not self.absoluteextrude):
if (not self.absolutecoord
or (p>2 and not self.absoluteextrude)):
# value relative to position of last move
self.last_position[p] += v
else:
# value relative to base coordinate position
self.last_position[p] = v + self.base_position[p]
if 'F' in params:
self.speed = float(params['F']) / 60.
speed = float(params['F']) / 60.
if speed <= 0.:
raise ValueError()
self.speed = speed
except ValueError, e:
self.last_position = self.toolhead.get_position()
raise error("Unable to parse move '%s'" % (params['#original'],))
try:
self.toolhead.move(self.last_position, self.speed)
except homing.EndstopError, e:
@@ -218,16 +270,13 @@ class GCodeParser:
def cmd_G4(self, params):
# Dwell
if 'S' in params:
delay = float(params['S'])
delay = self.get_float('S', params)
else:
delay = float(params.get('P', '0')) / 1000.
delay = self.get_float('P', params, 0.) / 1000.
self.toolhead.dwell(delay)
def cmd_G20(self, params):
# Set units to inches
self.respond_error('Machine does not support G20 (inches) command')
def cmd_G21(self, params):
# Set units to millimeters
pass
def cmd_G28(self, params):
# Move to origin
axes = []
@@ -257,12 +306,11 @@ class GCodeParser:
self.absolutecoord = False
def cmd_G92(self, params):
# Set position
mcount = 0
for a, p in self.axis2pos.items():
if a in params:
self.base_position[p] = self.last_position[p] - float(params[a])
mcount += 1
if not mcount:
offsets = { p: self.get_float(a, params)
for a, p in self.axis2pos.items() if a in params }
for p, offset in offsets.items():
self.base_position[p] = self.last_position[p] - offset
if not offsets:
self.base_position = list(self.last_position)
def cmd_M82(self, params):
# Use absolute distances for extrusion
@@ -284,10 +332,6 @@ class GCodeParser:
def cmd_M109(self, params):
# Set Extruder Temperature and Wait
self.set_temp(self.heater_nozzle, params, wait=True)
cmd_M110_when_not_ready = True
def cmd_M110(self, params):
# Set Current Line Number
pass
def cmd_M112(self, params):
# Emergency Stop
self.toolhead.force_shutdown()
@@ -302,6 +346,12 @@ class GCodeParser:
self.last_position[0], self.last_position[1],
self.last_position[2], self.last_position[3],
kinpos[0], kinpos[1], kinpos[2]))
cmd_M115_when_not_ready = True
def cmd_M115(self, params):
# Get Firmware Version and Capabilities
kw = {"FIRMWARE_NAME": "Klipper"
, "FIRMWARE_VERSION": self.printer.software_version}
self.ack(" ".join(["%s:%s" % (k, v) for k, v in kw.items()]))
def cmd_M140(self, params):
# Set Bed Temperature
self.set_temp(self.heater_bed, params)
@@ -310,19 +360,25 @@ class GCodeParser:
self.set_temp(self.heater_bed, params, wait=True)
def cmd_M106(self, params):
# Set fan speed
print_time = self.toolhead.get_last_move_time()
self.fan.set_speed(print_time, float(params.get('S', '255')) / 255.)
self.set_fan_speed(self.get_float('S', params, 255.) / 255.)
def cmd_M107(self, params):
# Turn fan off
print_time = self.toolhead.get_last_move_time()
self.fan.set_speed(print_time, 0)
self.set_fan_speed(0.)
def cmd_M206(self, params):
# Set home offset
for a, p in self.axis2pos.items():
if a in params:
v = float(params[a])
self.base_position[p] += self.homing_add[p] - v
self.homing_add[p] = v
offsets = { p: self.get_float(a, params)
for a, p in self.axis2pos.items() if a in params }
for p, offset in offsets.items():
self.base_position[p] += self.homing_add[p] - offset
self.homing_add[p] = offset
def cmd_M400(self, params):
# Wait for current moves to finish
self.toolhead.wait_moves()
cmd_IGNORE_when_not_ready = True
cmd_IGNORE_aliases = ["G21", "M110", "M21"]
def cmd_IGNORE(self, params):
# Commands that are just silently accepted
pass
cmd_QUERY_ENDSTOPS_help = "Report on the status of each endstop"
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
def cmd_QUERY_ENDSTOPS(self, params):
@@ -340,23 +396,29 @@ class GCodeParser:
cmd_PID_TUNE_aliases = ["M303"]
def cmd_PID_TUNE(self, params):
# Run PID tuning
heater = int(params.get('E', '0'))
heater = self.get_int('E', params, 0)
heater = {0: self.heater_nozzle, -1: self.heater_bed}[heater]
temp = float(params.get('S', '60'))
if heater is None:
self.respond_error("Heater not configured")
temp = self.get_float('S', params)
heater.start_auto_tune(temp)
self.bg_temp(heater)
cmd_CLEAR_SHUTDOWN_when_not_ready = True
cmd_CLEAR_SHUTDOWN_help = "Clear a firmware shutdown and restart"
def cmd_CLEAR_SHUTDOWN(self, params):
if self.toolhead is None:
self.cmd_default(params)
return
self.printer.mcu.clear_shutdown()
self.printer.request_restart()
def prep_restart(self):
if self.is_printer_ready:
self.respond_info("Preparing to restart...")
self.motor_heater_off()
self.toolhead.dwell(0.500)
self.toolhead.wait_moves()
cmd_RESTART_when_not_ready = True
cmd_RESTART_help = "Reload config file and restart host software"
def cmd_RESTART(self, params):
self.printer.request_restart()
self.prep_restart()
self.printer.request_exit('restart')
cmd_FIRMWARE_RESTART_when_not_ready = True
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
def cmd_FIRMWARE_RESTART(self, params):
self.prep_restart()
self.printer.request_exit('firmware_restart')
cmd_STATUS_when_not_ready = True
cmd_STATUS_help = "Report the printer status"
def cmd_STATUS(self, params):
@@ -371,8 +433,11 @@ class GCodeParser:
if not self.is_printer_ready:
cmdhelp.append("Printer is not ready - not all commands available.")
cmdhelp.append("Available extended commands:")
for cmd in self.gcode_handlers:
for cmd in sorted(self.gcode_handlers):
desc = getattr(self, 'cmd_'+cmd+'_help', None)
if desc is not None:
cmdhelp.append("%-10s: %s" % (cmd, desc))
self.respond_info("\n".join(cmdhelp))
class error(Exception):
pass

View File

@@ -5,84 +5,110 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging, threading
# Mapping from name to Steinhart-Hart coefficients
Thermistors = {
# Available sensors
Sensors = {
# Common thermistors and their Steinhart-Hart coefficients
"EPCOS 100K B57560G104F": (
"thermistor",
0.000722136308968056, 0.000216766566488498, 8.92935804531095e-08),
"ATC Semitec 104GT-2": (
"thermistor",
0.000809651054275124, 0.000211636030735685, 7.07420883993973e-08),
# Linear style conversion chips and their gain/offset
"AD595": ("linear", 300.0 / 3.022, 0.),
}
SAMPLE_TIME = 0.001
SAMPLE_COUNT = 8
REPORT_TIME = 0.300
PWM_CYCLE_TIME = 0.100
KELVIN_TO_CELCIUS = -273.15
MAX_HEAT_TIME = 5.0
AMBIENT_TEMP = 25.
PWM_MAX = 255
PID_PARAM_BASE = 255.
class error(Exception):
pass
class PrinterHeater:
error = error
def __init__(self, printer, config):
self.printer = printer
self.config = config
self.mcu_pwm = self.mcu_adc = None
self.thermistor_c = config.getchoice('thermistor_type', Thermistors)
self.pullup_r = config.getfloat('pullup_resistor', 4700.)
self.min_extrude_temp = config.getfloat('min_extrude_temp', 170.)
self.can_extrude = (self.min_extrude_temp <= 0.)
self.name = config.section
sensor_params = config.getchoice('sensor_type', Sensors)
self.is_linear_sensor = (sensor_params[0] == 'linear')
if self.is_linear_sensor:
adc_voltage = config.getfloat('adc_voltage', 5., above=0.)
self.sensor_coef = sensor_params[1] * adc_voltage, sensor_params[2]
else:
pullup = config.getfloat('pullup_resistor', 4700., above=0.)
self.sensor_coef = sensor_params[1:] + (pullup,)
self.min_temp = config.getfloat('min_temp', minval=0.)
self.max_temp = config.getfloat('max_temp', above=self.min_temp)
self.min_extrude_temp = config.getfloat(
'min_extrude_temp', 170., minval=self.min_temp, maxval=self.max_temp)
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
self.can_extrude = (self.min_extrude_temp <= 0.
or printer.mcu.is_fileoutput())
self.lock = threading.Lock()
self.last_temp = 0.
self.last_temp_time = 0.
self.target_temp = 0.
self.control = None
algos = {'watermark': ControlBangBang, 'pid': ControlPID}
algo = config.getchoice('control', algos)
heater_pin = config.get('heater_pin')
sensor_pin = config.get('sensor_pin')
if algo is ControlBangBang and self.max_power == 1.:
self.mcu_pwm = printer.mcu.create_digital_out(
heater_pin, MAX_HEAT_TIME)
else:
self.mcu_pwm = printer.mcu.create_pwm(
heater_pin, PWM_CYCLE_TIME, 0, MAX_HEAT_TIME)
self.mcu_adc = printer.mcu.create_adc(sensor_pin)
adc_range = [self.calc_adc(self.min_temp), self.calc_adc(self.max_temp)]
self.mcu_adc.set_minmax(SAMPLE_TIME, SAMPLE_COUNT,
minval=min(adc_range), maxval=max(adc_range))
self.mcu_adc.set_adc_callback(REPORT_TIME, self.adc_callback)
self.control = algo(self, config)
# pwm caching
self.next_pwm_time = 0.
self.last_pwm_value = 0
def build_config(self):
heater_pin = self.config.get('heater_pin')
thermistor_pin = self.config.get('thermistor_pin')
self.mcu_pwm = self.printer.mcu.create_pwm(heater_pin, 0, MAX_HEAT_TIME)
self.mcu_adc = self.printer.mcu.create_adc(thermistor_pin)
min_adc = self.calc_adc(self.config.getfloat('max_temp'))
max_adc = self.calc_adc(self.config.getfloat('min_temp'))
self.mcu_adc.set_minmax(
SAMPLE_TIME, SAMPLE_COUNT, minval=min_adc, maxval=max_adc)
self.mcu_adc.set_adc_callback(REPORT_TIME, self.adc_callback)
algos = {'watermark': ControlBangBang, 'pid': ControlPID}
self.control = self.config.getchoice('control', algos)(self, self.config)
if self.printer.mcu.is_fileoutput():
self.can_extrude = True
def set_pwm(self, read_time, value):
if value:
if self.target_temp <= 0.:
return
if (read_time < self.next_pwm_time
and abs(value - self.last_pwm_value) < 15):
return
elif not self.last_pwm_value:
value = 0.
if ((read_time < self.next_pwm_time or not self.last_pwm_value)
and abs(value - self.last_pwm_value) < 0.05):
# No significant change in value - can suppress update
return
pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
self.last_pwm_value = value
logging.debug("pwm=%d@%.3f (%.3f)" % (value, read_time, pwm_time))
logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])" % (
self.name, value, pwm_time,
self.last_temp, self.last_temp_time, self.target_temp))
self.mcu_pwm.set_pwm(pwm_time, value)
# Temperature calculation
def calc_temp(self, adc):
r = self.pullup_r * adc / (1.0 - adc)
if self.is_linear_sensor:
gain, offset = self.sensor_coef
return adc * gain + offset
c1, c2, c3, pullup = self.sensor_coef
r = pullup * adc / (1.0 - adc)
ln_r = math.log(r)
c1, c2, c3 = self.thermistor_c
temp_inv = c1 + c2*ln_r + c3*math.pow(ln_r, 3)
return 1.0/temp_inv + KELVIN_TO_CELCIUS
def calc_adc(self, temp):
if temp is None:
return None
c1, c2, c3 = self.thermistor_c
if self.is_linear_sensor:
gain, offset = self.sensor_coef
return (temp - offset) / gain
c1, c2, c3, pullup = self.sensor_coef
temp -= KELVIN_TO_CELCIUS
temp_inv = 1./temp
y = (c1 - temp_inv) / (2*c3)
x = math.sqrt(math.pow(c2 / (3.*c3), 3.) + math.pow(y, 2.))
r = math.exp(math.pow(x-y, 1./3.) - math.pow(x+y, 1./3.))
return r / (self.pullup_r + r)
return r / (pullup + r)
def adc_callback(self, read_time, read_value):
temp = self.calc_temp(read_value)
with self.lock:
@@ -93,6 +119,9 @@ class PrinterHeater:
#logging.debug("temp: %.3f %f = %f" % (read_time, read_value, temp))
# External commands
def set_temp(self, print_time, degrees):
if degrees and (degrees < self.min_temp or degrees > self.max_temp):
raise error("Requested temperature (%.1f) out of range (%.1f:%.1f)"
% (degrees, self.min_temp, self.max_temp))
with self.lock:
self.target_temp = degrees
def get_temp(self):
@@ -113,7 +142,7 @@ class PrinterHeater:
class ControlBangBang:
def __init__(self, heater, config):
self.heater = heater
self.max_delta = config.getfloat('max_delta', 2.0)
self.max_delta = config.getfloat('max_delta', 2.0, above=0.)
self.heating = False
def adc_callback(self, read_time, temp):
if self.heating and temp >= self.heater.target_temp+self.max_delta:
@@ -121,9 +150,9 @@ class ControlBangBang:
elif not self.heating and temp <= self.heater.target_temp-self.max_delta:
self.heating = True
if self.heating:
self.heater.set_pwm(read_time, PWM_MAX)
self.heater.set_pwm(read_time, self.heater.max_power)
else:
self.heater.set_pwm(read_time, 0)
self.heater.set_pwm(read_time, 0.)
def check_busy(self, eventtime):
return self.heater.last_temp < self.heater.target_temp-self.max_delta
@@ -135,11 +164,11 @@ class ControlBangBang:
class ControlPID:
def __init__(self, heater, config):
self.heater = heater
self.Kp = config.getfloat('pid_Kp')
self.Ki = config.getfloat('pid_Ki')
self.Kd = config.getfloat('pid_Kd')
self.min_deriv_time = config.getfloat('pid_deriv_time', 2.)
imax = config.getint('pid_integral_max', PWM_MAX)
self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE
self.min_deriv_time = config.getfloat('pid_deriv_time', 2., above=0.)
imax = config.getfloat('pid_integral_max', heater.max_power, minval=0.)
self.temp_integ_max = imax / self.Ki
self.prev_temp = AMBIENT_TEMP
self.prev_temp_time = 0.
@@ -159,10 +188,10 @@ class ControlPID:
temp_integ = self.prev_temp_integ + temp_err * time_diff
temp_integ = max(0., min(self.temp_integ_max, temp_integ))
# Calculate output
co = int(self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv)
co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv
#logging.debug("pid: %f@%.3f -> diff=%f deriv=%f err=%f integ=%f co=%d" % (
# temp, read_time, temp_diff, temp_deriv, temp_err, temp_integ, co))
bounded_co = max(0, min(PWM_MAX, co))
bounded_co = max(0., min(self.heater.max_power, co))
self.heater.set_pwm(read_time, bounded_co)
# Store state for next measurement
self.prev_temp = temp
@@ -198,12 +227,12 @@ class ControlAutoTune:
self.heating = True
self.check_peaks()
if self.heating:
self.heater.set_pwm(read_time, PWM_MAX)
self.heater.set_pwm(read_time, self.heater.max_power)
if temp < self.peak:
self.peak = temp
self.peak_time = read_time
else:
self.heater.set_pwm(read_time, 0)
self.heater.set_pwm(read_time, 0.)
if temp > self.peak:
self.peak = temp
self.peak_time = read_time
@@ -217,8 +246,8 @@ class ControlAutoTune:
return
temp_diff = self.peaks[-1][0] - self.peaks[-2][0]
time_diff = self.peaks[-1][1] - self.peaks[-3][1]
pwm_diff = PWM_MAX - 0
Ku = 4. * (2. * pwm_diff) / (abs(temp_diff) * math.pi)
max_power = self.heater.max_power
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
Tu = time_diff
Kp = 0.6 * Ku
@@ -226,8 +255,9 @@ class ControlAutoTune:
Td = 0.125 * Tu
Ki = Kp / Ti
Kd = Kp * Td
logging.info("Autotune: raw=%f/%d Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % (
temp_diff, pwm_diff, Ku, Tu, Kp, Ki, Kd))
logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % (
temp_diff, max_power, Ku, Tu,
Kp * PID_PARAM_BASE, Ki * PID_PARAM_BASE, Kd * PID_PARAM_BASE))
def check_busy(self, eventtime):
if self.heating or len(self.peaks) < 12:
return True
@@ -253,17 +283,17 @@ class ControlBumpTest:
def adc_callback(self, read_time, temp):
self.temp_samples[read_time] = temp
if not self.state:
self.set_pwm(read_time, 0)
self.set_pwm(read_time, 0.)
if len(self.temp_samples) >= 20:
self.state += 1
elif self.state == 1:
if temp < self.target_temp:
self.set_pwm(read_time, PWM_MAX)
self.set_pwm(read_time, self.heater.max_power)
return
self.set_pwm(read_time, 0)
self.set_pwm(read_time, 0.)
self.state += 1
elif self.state == 2:
self.set_pwm(read_time, 0)
self.set_pwm(read_time, 0.)
if temp <= (self.target_temp + AMBIENT_TEMP) / 2.:
self.dump_stats()
self.state += 1

View File

@@ -6,6 +6,9 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, ConfigParser, logging, time, threading
import gcode, toolhead, util, mcu, fan, heater, extruder, reactor, queuelogger
import msgproto
message_ready = "Printer is ready"
message_startup = """
The klippy host software is attempting to connect. Please
@@ -19,17 +22,26 @@ command to reload the config and restart the host software.
Printer is halted
"""
message_protocol_error = """
This type of error is frequently caused by running an older
version of the firmware on the micro-controller (fix by
recompiling and flashing the firmware).
Once the underlying issue is corrected, use the "RESTART"
command to reload the config and restart the host software.
Protocol error connecting to printer
"""
message_mcu_connect_error = """
This is an unrecoverable error. Please manually restart the
micro-controller and then issue the "RESTART" command to
restart the host software.
Once the underlying issue is corrected, use the
"FIRMWARE_RESTART" command to reset the firmware, reload the
config, and restart the host software.
Error configuring printer
"""
message_shutdown = """
Once the underlying issue is corrected, the "CLEAR_SHUTDOWN"
command can be used to clear the firmware flag and restart
the host software.
Once the underlying issue is corrected, use the
"FIRMWARE_RESTART" command to reset the firmware, reload the
config, and restart the host software.
Printer is shutdown
"""
@@ -40,26 +52,47 @@ class ConfigWrapper:
def __init__(self, printer, section):
self.printer = printer
self.section = section
def get_wrapper(self, parser, option, default):
def get_wrapper(self, parser, option, default
, minval=None, maxval=None, above=None, below=None):
if (default is not self.sentinel
and not self.printer.fileconfig.has_option(self.section, option)):
return default
self.printer.all_config_options[
(self.section.lower(), option.lower())] = 1
try:
return parser(self.section, option)
v = parser(self.section, option)
except self.error, e:
raise
except:
raise self.error("Unable to parse option '%s' in section '%s'" % (
option, self.section))
if minval is not None and v < minval:
raise self.error(
"Option '%s' in section '%s' must have minimum of %s" % (
option, self.section, minval))
if maxval is not None and v > maxval:
raise self.error(
"Option '%s' in section '%s' must have maximum of %s" % (
option, self.section, maxval))
if above is not None and v <= above:
raise self.error(
"Option '%s' in section '%s' must be above %s" % (
option, self.section, above))
if below is not None and v >= below:
raise self.error(
"Option '%s' in section '%s' must be below %s" % (
option, self.section, below))
return v
def get(self, option, default=sentinel):
return self.get_wrapper(self.printer.fileconfig.get, option, default)
def getint(self, option, default=sentinel):
return self.get_wrapper(self.printer.fileconfig.getint, option, default)
def getfloat(self, option, default=sentinel):
def getint(self, option, default=sentinel, minval=None, maxval=None):
return self.get_wrapper(
self.printer.fileconfig.getfloat, option, default)
self.printer.fileconfig.getint, option, default, minval, maxval)
def getfloat(self, option, default=sentinel
, minval=None, maxval=None, above=None, below=None):
return self.get_wrapper(
self.printer.fileconfig.getfloat, option, default
, minval, maxval, above, below)
def getboolean(self, option, default=sentinel):
return self.get_wrapper(
self.printer.fileconfig.getboolean, option, default)
@@ -73,10 +106,28 @@ class ConfigWrapper:
def getsection(self, section):
return ConfigWrapper(self.printer, section)
class ConfigLogger():
def __init__(self, cfg, bglogger):
self.lines = ["===== Config file ====="]
cfg.write(self)
self.lines.append("=======================")
data = "\n".join(self.lines)
logging.info(data)
bglogger.set_rollover_info("config", data)
def write(self, data):
self.lines.append(data.strip())
class Printer:
def __init__(self, conffile, input_fd, is_fileinput=False):
def __init__(self, conffile, input_fd, startup_state
, is_fileinput=False, version="?", bglogger=None):
self.conffile = conffile
self.startup_state = startup_state
self.software_version = version
self.bglogger = bglogger
if bglogger is not None:
bglogger.set_rollover_info("config", None)
self.reactor = reactor.Reactor()
self.objects = {}
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
self.stats_timer = self.reactor.register_timer(self.stats)
self.connect_timer = self.reactor.register_timer(
@@ -88,23 +139,25 @@ class Printer:
self.run_result = None
self.fileconfig = None
self.mcu = None
self.objects = {}
def set_fileoutput(self, debugoutput, dictionary):
self.debugoutput = debugoutput
self.dictionary = dictionary
def stats(self, eventtime):
def stats(self, eventtime, force_output=False):
if self.need_dump_debug:
# Call dump_debug here so it is executed in the main thread
self.gcode.dump_debug()
self.need_dump_debug = False
toolhead = self.objects.get('toolhead')
if toolhead is None or self.mcu is None:
return
is_active, thstats = toolhead.stats(eventtime)
if not is_active and not force_output:
return
out = []
out.append(self.gcode.stats(eventtime))
toolhead = self.objects.get('toolhead')
if toolhead is not None:
out.append(toolhead.stats(eventtime))
if self.mcu is not None:
out.append(thstats)
out.append(self.mcu.stats(eventtime))
logging.info("Stats %.0f: %s" % (eventtime, ' '.join(out)))
logging.info("Stats %.1f: %s" % (eventtime, ' '.join(out)))
return eventtime + 1.
def load_config(self):
self.fileconfig = ConfigParser.RawConfigParser()
@@ -112,24 +165,23 @@ class Printer:
if not res:
raise ConfigParser.Error("Unable to open config file %s" % (
self.conffile,))
if self.bglogger is not None:
ConfigLogger(self.fileconfig, self.bglogger)
self.mcu = mcu.MCU(self, ConfigWrapper(self, 'mcu'))
if self.fileconfig.has_section('fan'):
self.objects['fan'] = fan.PrinterFan(
self, ConfigWrapper(self, 'fan'))
if self.debugoutput is not None:
self.mcu.connect_file(self.debugoutput, self.dictionary)
if self.fileconfig.has_section('extruder'):
self.objects['extruder'] = extruder.PrinterExtruder(
self, ConfigWrapper(self, 'extruder'))
if self.fileconfig.has_section('fan'):
self.objects['fan'] = fan.PrinterFan(
self, ConfigWrapper(self, 'fan'))
if self.fileconfig.has_section('heater_bed'):
self.objects['heater_bed'] = heater.PrinterHeater(
self, ConfigWrapper(self, 'heater_bed'))
self.objects['toolhead'] = toolhead.ToolHead(
self, ConfigWrapper(self, 'printer'))
def build_config(self):
for oname in sorted(self.objects.keys()):
self.objects[oname].build_config()
self.gcode.build_config()
self.mcu.build_config()
def validate_config(self):
# Validate that there are no undefined parameters in the config file
valid_sections = dict([(s, 1) for s, o in self.all_config_options])
for section in self.fileconfig.sections():
section = section.lower()
@@ -147,17 +199,17 @@ class Printer:
self.load_config()
if self.debugoutput is None:
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
else:
self.mcu.connect_file(self.debugoutput, self.dictionary)
self.mcu.connect()
self.build_config()
self.validate_config()
self.gcode.set_printer_ready(True)
self.state_message = "Printer is ready"
self.state_message = message_ready
except ConfigParser.Error, e:
logging.exception("Config error")
self.state_message = "%s%s" % (str(e), message_restart)
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
except msgproto.error, e:
logging.exception("Protocol error")
self.state_message = "%s%s" % (str(e), message_protocol_error)
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
except mcu.error, e:
logging.exception("MCU error during connect")
self.state_message = "%s%s" % (str(e), message_mcu_connect_error)
@@ -170,6 +222,10 @@ class Printer:
self.reactor.unregister_timer(self.connect_timer)
return self.reactor.NEVER
def run(self):
systime = time.time()
monotime = self.reactor.monotonic()
logging.info("Start printer at %s (%.1f %.1f)" % (
time.asctime(time.localtime(systime)), systime, monotime))
try:
self.reactor.run()
except:
@@ -179,7 +235,7 @@ class Printer:
def get_state_message(self):
return self.state_message
def note_shutdown(self, msg):
if self.state_message == 'Running':
if self.state_message == message_ready:
self.need_dump_debug = True
self.state_message = "Firmware shutdown: %s%s" % (
msg, message_shutdown)
@@ -191,15 +247,22 @@ class Printer:
def disconnect(self):
try:
if self.mcu is not None:
self.stats(time.time())
self.stats(self.reactor.monotonic(), force_output=True)
self.mcu.disconnect()
except:
logging.exception("Unhandled exception during disconnect")
def request_restart(self):
self.run_result = "restart"
self.reactor.end()
def request_exit_eof(self):
self.run_result = "exit_eof"
def firmware_restart(self):
try:
if self.mcu is not None:
self.stats(self.reactor.monotonic(), force_output=True)
self.mcu.microcontroller_restart()
self.mcu.disconnect()
except:
logging.exception("Unhandled exception during firmware_restart")
def get_startup_state(self):
return self.startup_state
def request_exit(self, result="exit"):
self.run_result = result
self.reactor.end()
@@ -250,11 +313,22 @@ def main():
else:
logging.basicConfig(level=debuglevel)
logging.info("Starting Klippy...")
software_version = util.get_git_version()
if bglogger is not None:
lines = ["Args: %s" % (sys.argv,),
"Git version: %s" % (repr(software_version),),
"CPU: %s" % (util.get_cpu_info(),),
"Python: %s" % (repr(sys.version),)]
lines = "\n".join(lines)
logging.info(lines)
bglogger.set_rollover_info('versions', lines)
# Start firmware
res = 'startup'
while 1:
is_fileinput = debuginput is not None
printer = Printer(conffile, input_fd, is_fileinput)
printer = Printer(
conffile, input_fd, res, is_fileinput, software_version, bglogger)
if debugoutput:
proto_dict = read_dictionary(options.read_dictionary)
printer.set_fileoutput(debugoutput, proto_dict)
@@ -264,6 +338,11 @@ def main():
time.sleep(1.)
logging.info("Restarting printer")
continue
elif res == 'firmware_restart':
printer.firmware_restart()
time.sleep(1.)
logging.info("Restarting printer")
continue
elif res == 'exit_eof':
printer.disconnect()
break

View File

@@ -1,9 +1,9 @@
# Multi-processor safe interface to micro-controller
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, zlib, logging, time, math
import sys, os, zlib, logging, math
import serialhdl, pins, chelper
class error(Exception):
@@ -19,139 +19,181 @@ def parse_pin_extras(pin, can_pullup=False):
pin = pin[1:].strip()
return pin, pullup, invert
STEPCOMPRESS_ERROR_RET = -989898989
class MCU_stepper:
def __init__(self, mcu, step_pin, dir_pin, min_stop_interval, max_error):
def __init__(self, mcu, step_pin, dir_pin):
self._mcu = mcu
self._oid = mcu.create_oid()
step_pin, pullup, invert_step = parse_pin_extras(step_pin)
dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
self._mcu_freq = mcu.get_mcu_freq()
min_stop_interval = int(min_stop_interval * self._mcu_freq)
max_error = int(max_error * self._mcu_freq)
self.commanded_position = 0
self._oid = mcu.create_oid(self)
self._step_pin, pullup, self._invert_step = parse_pin_extras(step_pin)
self._dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
self._commanded_pos = 0
self._step_dist = self._inv_step_dist = 1.
self._velocity_factor = self._accel_factor = 0.
self._mcu_position_offset = 0
mcu.add_config_cmd(
"config_stepper oid=%d step_pin=%s dir_pin=%s"
" min_stop_interval=%d invert_step=%d" % (
self._oid, step_pin, dir_pin, min_stop_interval, invert_step))
mcu.register_stepper(self)
self._step_cmd = mcu.lookup_command(
"queue_step oid=%c interval=%u count=%hu add=%hi")
self._dir_cmd = mcu.lookup_command(
"set_next_step_dir oid=%c dir=%c")
self._reset_cmd = mcu.lookup_command(
"reset_step_clock oid=%c clock=%u")
ffi_main, self.ffi_lib = chelper.get_ffi()
self._stepqueue = ffi_main.gc(self.ffi_lib.stepcompress_alloc(
max_error, self._step_cmd.msgid
, self._dir_cmd.msgid, self._invert_dir, self._oid),
self.ffi_lib.stepcompress_free)
self._mcu_freq = self._min_stop_interval = 0.
self._reset_cmd = self._get_position_cmd = None
self._ffi_lib = self._stepqueue = None
self.print_to_mcu_time = mcu.print_to_mcu_time
def set_min_stop_interval(self, min_stop_interval):
self._min_stop_interval = min_stop_interval
def set_step_distance(self, step_dist):
self._step_dist = step_dist
self._inv_step_dist = 1. / step_dist
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
self._velocity_factor = 1. / (self._mcu_freq * self._step_dist)
self._accel_factor = 1. / (self._mcu_freq**2 * self._step_dist)
max_error = self._mcu.get_max_stepper_error()
min_stop_interval = max(0., self._min_stop_interval - max_error)
self._mcu.add_config_cmd(
"config_stepper oid=%d step_pin=%s dir_pin=%s"
" min_stop_interval=TICKS(%.9f) invert_step=%d" % (
self._oid, self._step_pin, self._dir_pin,
min_stop_interval, self._invert_step))
self._mcu.register_stepper(self)
step_cmd = self._mcu.lookup_command(
"queue_step oid=%c interval=%u count=%hu add=%hi")
dir_cmd = self._mcu.lookup_command(
"set_next_step_dir oid=%c dir=%c")
self._reset_cmd = self._mcu.lookup_command(
"reset_step_clock oid=%c clock=%u")
self._get_position_cmd = self._mcu.lookup_command(
"stepper_get_position oid=%c")
ffi_main, self._ffi_lib = chelper.get_ffi()
max_error = int(max_error * self._mcu_freq)
self._stepqueue = ffi_main.gc(self._ffi_lib.stepcompress_alloc(
max_error, step_cmd.msgid, dir_cmd.msgid,
self._invert_dir, self._oid),
self._ffi_lib.stepcompress_free)
def get_oid(self):
return self._oid
def get_invert_dir(self):
return self._invert_dir
def set_position(self, pos):
self._mcu_position_offset += self.commanded_position - pos
self.commanded_position = pos
def set_mcu_position(self, pos):
self._mcu_position_offset = pos - self.commanded_position
if pos >= 0.:
steppos = int(pos * self._inv_step_dist + 0.5)
else:
steppos = int(pos * self._inv_step_dist - 0.5)
self._mcu_position_offset += self._commanded_pos - steppos
self._commanded_pos = steppos
def get_commanded_position(self):
return self._commanded_pos * self._step_dist
def get_mcu_position(self):
return self.commanded_position + self._mcu_position_offset
def note_stepper_stop(self):
self.ffi_lib.stepcompress_reset(self._stepqueue, 0)
return self._commanded_pos + self._mcu_position_offset
def note_homing_start(self, homing_clock):
ret = self._ffi_lib.stepcompress_set_homing(
self._stepqueue, homing_clock)
if ret:
raise error("Internal error in stepcompress")
def note_homing_finalized(self):
ret = self._ffi_lib.stepcompress_set_homing(self._stepqueue, 0)
if ret:
raise error("Internal error in stepcompress")
ret = self._ffi_lib.stepcompress_reset(self._stepqueue, 0)
if ret:
raise error("Internal error in stepcompress")
def note_homing_triggered(self):
params = self._mcu.serial.send_with_response(
self._get_position_cmd.encode(self._oid),
'stepper_position', self._oid)
pos = params['pos']
if self._invert_dir:
pos = -pos
self._mcu_position_offset = pos - self._commanded_pos
def reset_step_clock(self, mcu_time):
clock = int(mcu_time * self._mcu_freq)
self.ffi_lib.stepcompress_reset(self._stepqueue, clock)
ret = self._ffi_lib.stepcompress_reset(self._stepqueue, clock)
if ret:
raise error("Internal error in stepcompress")
data = (self._reset_cmd.msgid, self._oid, clock & 0xffffffff)
self.ffi_lib.stepcompress_queue_msg(self._stepqueue, data, len(data))
ret = self._ffi_lib.stepcompress_queue_msg(
self._stepqueue, data, len(data))
if ret:
raise error("Internal error in stepcompress")
def step(self, mcu_time, sdir):
clock = mcu_time * self._mcu_freq
self.ffi_lib.stepcompress_push(self._stepqueue, clock, sdir)
ret = self._ffi_lib.stepcompress_push(self._stepqueue, clock, sdir)
if ret:
raise error("Internal error in stepcompress")
if sdir:
self.commanded_position += 1
self._commanded_pos += 1
else:
self.commanded_position -= 1
def step_sqrt(self, mcu_time, steps, step_offset, sqrt_offset, factor):
clock = mcu_time * self._mcu_freq
mcu_freq2 = self._mcu_freq**2
count = self.ffi_lib.stepcompress_push_sqrt(
self._stepqueue, steps, step_offset, clock
, sqrt_offset * mcu_freq2, factor * mcu_freq2)
self.commanded_position += count
return count
def step_factor(self, mcu_time, steps, step_offset, factor):
clock = mcu_time * self._mcu_freq
count = self.ffi_lib.stepcompress_push_factor(
self._stepqueue, steps, step_offset, clock, factor * self._mcu_freq)
self.commanded_position += count
return count
def step_delta_const(self, mcu_time, dist, start_pos
, inv_velocity, step_dist
, height, closestxy_d, closest_height2, movez_r):
clock = mcu_time * self._mcu_freq
count = self.ffi_lib.stepcompress_push_delta_const(
self._stepqueue, clock, dist, start_pos
, inv_velocity * self._mcu_freq, step_dist
, height, closestxy_d, closest_height2, movez_r)
self.commanded_position += count
return count
def step_delta_accel(self, mcu_time, dist, start_pos
, accel_multiplier, step_dist
, height, closestxy_d, closest_height2, movez_r):
clock = mcu_time * self._mcu_freq
mcu_freq2 = self._mcu_freq**2
count = self.ffi_lib.stepcompress_push_delta_accel(
self._stepqueue, clock, dist, start_pos
, accel_multiplier * mcu_freq2, step_dist
, height, closestxy_d, closest_height2, movez_r)
self.commanded_position += count
return count
def get_errors(self):
return self.ffi_lib.stepcompress_get_errors(self._stepqueue)
self._commanded_pos -= 1
def step_const(self, mcu_time, start_pos, dist, start_v, accel):
inv_step_dist = self._inv_step_dist
step_offset = self._commanded_pos - start_pos * inv_step_dist
count = self._ffi_lib.stepcompress_push_const(
self._stepqueue, mcu_time * self._mcu_freq, step_offset,
dist * inv_step_dist, start_v * self._velocity_factor,
accel * self._accel_factor)
if count == STEPCOMPRESS_ERROR_RET:
raise error("Internal error in stepcompress")
self._commanded_pos += count
def step_delta(self, mcu_time, dist, start_v, accel
, height_base, startxy_d, arm_d, movez_r):
inv_step_dist = self._inv_step_dist
height = self._commanded_pos - height_base * inv_step_dist
count = self._ffi_lib.stepcompress_push_delta(
self._stepqueue, mcu_time * self._mcu_freq, dist * inv_step_dist,
start_v * self._velocity_factor, accel * self._accel_factor,
height, startxy_d * inv_step_dist, arm_d * inv_step_dist, movez_r)
if count == STEPCOMPRESS_ERROR_RET:
raise error("Internal error in stepcompress")
self._commanded_pos += count
class MCU_endstop:
error = error
RETRY_QUERY = 1.000
def __init__(self, mcu, pin, stepper):
def __init__(self, mcu, pin):
self._mcu = mcu
self._oid = mcu.create_oid()
self._stepper = stepper
stepper_oid = stepper.get_oid()
pin, pullup, self._invert = parse_pin_extras(pin, can_pullup=True)
self._oid = mcu.create_oid(self)
self._steppers = []
self._pin, self._pullup, self._invert = parse_pin_extras(
pin, can_pullup=True)
self._cmd_queue = mcu.alloc_command_queue()
mcu.add_config_cmd(
"config_end_stop oid=%d pin=%s pull_up=%d stepper_oid=%d" % (
self._oid, pin, pullup, stepper_oid))
self._home_cmd = mcu.lookup_command(
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c")
mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
, self._oid)
self._query_cmd = mcu.lookup_command("end_stop_query oid=%c")
self._home_cmd = self._query_cmd = None
self._homing = False
self._min_query_time = 0.
self._min_query_time = self._mcu_freq = 0.
self._next_query_clock = self._home_timeout_clock = 0
self._mcu_freq = mcu.get_mcu_freq()
self._retry_query_ticks = int(self._mcu_freq * self.RETRY_QUERY)
self._retry_query_ticks = 0
self._last_state = {}
mcu.add_init_callback(self._init_callback)
self.print_to_mcu_time = mcu.print_to_mcu_time
def add_stepper(self, stepper):
self._steppers.append(stepper)
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
self._mcu.add_config_cmd(
"config_end_stop oid=%d pin=%s pull_up=%d stepper_count=%d" % (
self._oid, self._pin, self._pullup, len(self._steppers)))
self._retry_query_ticks = int(self._mcu_freq * self.RETRY_QUERY)
self._home_cmd = self._mcu.lookup_command(
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c")
self._query_cmd = self._mcu.lookup_command("end_stop_query oid=%c")
self._mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
, self._oid)
def _init_callback(self):
set_cmd = self._mcu.lookup_command(
"end_stop_set_stepper oid=%c pos=%c stepper_oid=%c")
for i, s in enumerate(self._steppers):
msg = set_cmd.encode(self._oid, i, s.get_oid())
self._mcu.send(msg, cq=self._cmd_queue)
def home_start(self, mcu_time, rest_time):
clock = int(mcu_time * self._mcu_freq)
rest_ticks = int(rest_time * self._mcu_freq)
self._homing = True
self._min_query_time = time.time()
self._min_query_time = self._mcu.monotonic()
self._next_query_clock = clock + self._retry_query_ticks
msg = self._home_cmd.encode(
self._oid, clock, rest_ticks, 1 ^ self._invert)
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
for s in self._steppers:
s.note_homing_start(clock)
def home_finalize(self, mcu_time):
# XXX - this flushes the serial port of messages ready to be
# sent, but doesn't flush messages if they had an unmet minclock
self._mcu.serial.send_flush()
self._stepper.note_stepper_stop()
for s in self._steppers:
s.note_homing_finalized()
self._home_timeout_clock = int(mcu_time * self._mcu_freq)
def home_wait(self):
eventtime = time.time()
eventtime = self._mcu.monotonic()
while self._check_busy(eventtime):
eventtime = self._mcu.pause(eventtime + 0.1)
def _handle_end_stop_state(self, params):
@@ -166,10 +208,8 @@ class MCU_endstop:
if not self._homing:
return False
if not self._last_state.get('homing', 0):
pos = self._last_state.get('pos', 0)
if self._stepper.get_invert_dir():
pos = -pos
self._stepper.set_mcu_position(pos)
for s in self._steppers:
s.note_homing_triggered()
self._homing = False
return False
if (self._mcu.serial.get_clock(last_sent_time)
@@ -189,10 +229,10 @@ class MCU_endstop:
def query_endstop(self, mcu_time):
clock = int(mcu_time * self._mcu_freq)
self._homing = False
self._min_query_time = time.time()
self._min_query_time = self._mcu.monotonic()
self._next_query_clock = clock
def query_endstop_wait(self):
eventtime = time.time()
eventtime = self._mcu.monotonic()
while self._check_busy(eventtime):
eventtime = self._mcu.pause(eventtime + 0.1)
return self._last_state.get('pin', self._invert) ^ self._invert
@@ -200,18 +240,22 @@ class MCU_endstop:
class MCU_digital_out:
def __init__(self, mcu, pin, max_duration):
self._mcu = mcu
self._oid = mcu.create_oid()
self._oid = mcu.create_oid(self)
pin, pullup, self._invert = parse_pin_extras(pin)
self._last_clock = 0
self._last_value = None
self._mcu_freq = mcu.get_mcu_freq()
self._mcu_freq = 0.
self._cmd_queue = mcu.alloc_command_queue()
mcu.add_config_cmd(
"config_digital_out oid=%d pin=%s default_value=%d"
" max_duration=%d" % (self._oid, pin, self._invert, max_duration))
self._set_cmd = mcu.lookup_command(
"schedule_digital_out oid=%c clock=%u value=%c")
" max_duration=TICKS(%f)" % (
self._oid, pin, self._invert, max_duration))
self._set_cmd = None
self.print_to_mcu_time = mcu.print_to_mcu_time
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
self._set_cmd = self._mcu.lookup_command(
"schedule_digital_out oid=%c clock=%u value=%c")
def set_digital(self, mcu_time, value):
clock = int(mcu_time * self._mcu_freq)
msg = self._set_cmd.encode(self._oid, clock, value ^ self._invert)
@@ -223,33 +267,46 @@ class MCU_digital_out:
return self._last_value
def set_pwm(self, mcu_time, value):
dval = 0
if value > 127:
if value >= 0.5:
dval = 1
self.set_digital(mcu_time, dval)
class MCU_pwm:
def __init__(self, mcu, pin, cycle_ticks, max_duration, hard_pwm=True):
PWM_MAX = 255.
def __init__(self, mcu, pin, cycle_time, hard_cycle_ticks, max_duration):
self._mcu = mcu
self._oid = mcu.create_oid()
self._hard_cycle_ticks = hard_cycle_ticks
self._oid = mcu.create_oid(self)
pin, pullup, self._invert = parse_pin_extras(pin)
self._last_clock = 0
self._mcu_freq = mcu.get_mcu_freq()
self._mcu_freq = 0.
self._cmd_queue = mcu.alloc_command_queue()
if hard_pwm:
if hard_cycle_ticks:
mcu.add_config_cmd(
"config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=0"
" max_duration=%d" % (self._oid, pin, cycle_ticks, max_duration))
self._set_cmd = mcu.lookup_command(
"schedule_pwm_out oid=%c clock=%u value=%c")
"config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=%d"
" max_duration=TICKS(%f)" % (
self._oid, pin, hard_cycle_ticks, self._invert,
max_duration))
else:
mcu.add_config_cmd(
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=%d"
" default_value=0 max_duration=%d" % (
self._oid, pin, cycle_ticks, max_duration))
self._set_cmd = mcu.lookup_command(
"schedule_soft_pwm_out oid=%c clock=%u value=%c")
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=TICKS(%f)"
" default_value=%d max_duration=TICKS(%f)" % (
self._oid, pin, cycle_time, self._invert, max_duration))
self._set_cmd = None
self.print_to_mcu_time = mcu.print_to_mcu_time
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
if self._hard_cycle_ticks:
self._set_cmd = self._mcu.lookup_command(
"schedule_pwm_out oid=%c clock=%u value=%c")
else:
self._set_cmd = self._mcu.lookup_command(
"schedule_soft_pwm_out oid=%c clock=%u value=%c")
def set_pwm(self, mcu_time, value):
clock = int(mcu_time * self._mcu_freq)
if self._invert:
value = 1. - value
value = int(value * self.PWM_MAX + 0.5)
msg = self._set_cmd.encode(self._oid, clock, value)
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
, cq=self._cmd_queue)
@@ -258,41 +315,46 @@ class MCU_pwm:
class MCU_adc:
def __init__(self, mcu, pin):
self._mcu = mcu
self._oid = mcu.create_oid()
self._min_sample = 0
self._max_sample = 0xffff
self._sample_ticks = 0
self._sample_count = 1
self._oid = mcu.create_oid(self)
self._min_sample = self._max_sample = 0.
self._sample_time = self._report_time = 0.
self._sample_count = 0
self._report_clock = 0
self._callback = None
self._inv_max_adc = 0.
self._mcu_freq = mcu.get_mcu_freq()
self._mcu_freq = 0.
self._cmd_queue = mcu.alloc_command_queue()
mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (self._oid, pin))
self._query_cmd = None
mcu.add_init_callback(self._init_callback)
mcu.register_msg(self._handle_analog_in_state, "analog_in_state"
, self._oid)
self._query_cmd = mcu.lookup_command(
self._query_cmd = None
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
self._query_cmd = self._mcu.lookup_command(
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u min_value=%hu max_value=%hu")
def set_minmax(self, sample_time, sample_count, minval=None, maxval=None):
self._sample_ticks = int(sample_time * self._mcu_freq)
def set_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
self._sample_time = sample_time
self._sample_count = sample_count
if minval is None:
minval = 0
if maxval is None:
maxval = 0xffff
mcu_adc_max = float(self._mcu.serial.msgparser.config["ADC_MAX"])
max_adc = sample_count * mcu_adc_max
self._min_sample = int(minval * max_adc)
self._max_sample = min(0xffff, int(math.ceil(maxval * max_adc)))
self._inv_max_adc = 1.0 / max_adc
self._min_sample = minval
self._max_sample = maxval
def _init_callback(self):
if not self._sample_count:
return
last_clock, last_clock_time = self._mcu.get_last_clock()
clock = last_clock + int(self._mcu_freq * (1.0 + self._oid * 0.01)) # XXX
sample_ticks = int(self._sample_time * self._mcu_freq)
mcu_adc_max = self._mcu.serial.msgparser.get_constant_float("ADC_MAX")
max_adc = self._sample_count * mcu_adc_max
self._inv_max_adc = 1.0 / max_adc
self._report_clock = int(self._report_time * self._mcu_freq)
self._mcu.register_msg(self._handle_analog_in_state, "analog_in_state"
, self._oid)
min_sample = int(self._min_sample * max_adc)
max_sample = min(0xffff, int(math.ceil(self._max_sample * max_adc)))
msg = self._query_cmd.encode(
self._oid, clock, self._sample_ticks, self._sample_count
, self._report_clock, self._min_sample, self._max_sample)
self._oid, clock, sample_ticks, self._sample_count
, self._report_clock, min_sample, max_sample)
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
def _handle_analog_in_state(self, params):
last_value = params['value'] * self._inv_max_adc
@@ -301,7 +363,7 @@ class MCU_adc:
if self._callback is not None:
self._callback(last_read_time, last_value)
def set_adc_callback(self, report_time, callback):
self._report_clock = int(report_time * self._mcu_freq)
self._report_time = report_time
self._callback = callback
class MCU:
@@ -309,60 +371,91 @@ class MCU:
COMM_TIMEOUT = 3.5
def __init__(self, printer, config):
self._printer = printer
self._config = config
# Serial port
baud = config.getint('baud', 115200)
serialport = config.get('serial', '/dev/ttyS0')
self.serial = serialhdl.SerialReader(printer.reactor, serialport, baud)
baud = config.getint('baud', 250000)
self._serialport = config.get('serial', '/dev/ttyS0')
self.serial = serialhdl.SerialReader(
printer.reactor, self._serialport, baud)
self.is_shutdown = False
self._shutdown_msg = ""
self._is_fileoutput = False
self._timeout_timer = printer.reactor.register_timer(
self.timeout_handler)
rmethods = {m: m for m in ['arduino', 'command', 'rpi_usb']}
self._restart_method = config.getchoice(
'restart_method', rmethods, 'arduino')
# Config building
self._emergency_stop_cmd = self._clear_shutdown_cmd = None
self._num_oids = 0
if printer.bglogger is not None:
printer.bglogger.set_rollover_info("mcu", None)
self._config_error = config.error
self._emergency_stop_cmd = self._reset_cmd = None
self._oids = []
self._config_cmds = []
self._config_crc = None
self._init_callbacks = []
self._pin_map = config.get('pin_map', None)
self._custom = config.get('custom', '')
# Move command queuing
ffi_main, self.ffi_lib = chelper.get_ffi()
ffi_main, self._ffi_lib = chelper.get_ffi()
self._max_stepper_error = config.getfloat(
'max_stepper_error', 0.000025, minval=0.)
self._steppers = []
self._steppersync = None
# Print time to clock epoch calculations
self._print_start_time = 0.
self._mcu_freq = 0.
# Stats
self._stats_sumsq_base = 0.
self._mcu_tick_avg = 0.
self._mcu_tick_stddev = 0.
def handle_mcu_stats(self, params):
logging.debug("mcu stats: %s" % (params,))
count = params['count']
tick_sum = params['sum']
c = 1.0 / (count * self._mcu_freq)
self._mcu_tick_avg = tick_sum * c
tick_sumsq = params['sumsq']
tick_sumavgsq = ((tick_sum // (256*count)) * count)**2
self._mcu_tick_stddev = c * 256. * math.sqrt(
count * tick_sumsq - tick_sumavgsq)
tick_sumsq = params['sumsq'] * self._stats_sumsq_base
self._mcu_tick_stddev = c * math.sqrt(count*tick_sumsq - tick_sum**2)
def handle_shutdown(self, params):
if self.is_shutdown:
return
self.is_shutdown = True
logging.info("%s: %s" % (params['#name'], params['#msg']))
self._shutdown_msg = params['#msg']
logging.info("%s: %s" % (params['#name'], self._shutdown_msg))
pst = self._print_start_time
logging.info("Clock last synchronized at %.6f (%d)" % (
pst, int(pst * self._mcu_freq)))
self.serial.dump_debug()
self._printer.note_shutdown(params['#msg'])
self._printer.note_shutdown(self._shutdown_msg)
# Connection phase
def _check_restart(self, reason):
if self._printer.get_startup_state() == 'firmware_restart':
return
logging.info("Attempting automated firmware restart: %s" % (reason,))
self._printer.request_exit('firmware_restart')
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
raise error("Attempt firmware restart failed")
def connect(self):
if not self._is_fileoutput:
if (self._restart_method == 'rpi_usb'
and not os.path.exists(self._serialport)):
# Try toggling usb power
self._check_restart("enable power")
self.serial.connect()
self._printer.reactor.update_timer(
self._timeout_timer, time.time() + self.COMM_TIMEOUT)
self._mcu_freq = float(self.serial.msgparser.config['CLOCK_FREQ'])
self._timeout_timer, self.monotonic() + self.COMM_TIMEOUT)
self._mcu_freq = self.serial.msgparser.get_constant_float('CLOCK_FREQ')
self._stats_sumsq_base = self.serial.msgparser.get_constant_float(
'STATS_SUMSQ_BASE')
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
self._clear_shutdown_cmd = self.lookup_command("clear_shutdown")
try:
self._reset_cmd = self.lookup_command("reset")
except self.serial.msgparser.error, e:
pass
self.register_msg(self.handle_shutdown, 'shutdown')
self.register_msg(self.handle_shutdown, 'is_shutdown')
self.register_msg(self.handle_mcu_stats, 'stats')
self._build_config()
self._send_config()
def connect_file(self, debugoutput, dictionary, pace=False):
self._is_fileoutput = True
self.serial.connect_file(debugoutput, dictionary)
@@ -370,7 +463,7 @@ class MCU:
def dummy_set_print_start_time(eventtime):
pass
def dummy_get_print_buffer_time(eventtime, last_move_end):
return 0.250
return 1.250
self.set_print_start_time = dummy_set_print_start_time
self.get_print_buffer_time = dummy_get_print_buffer_time
def timeout_handler(self, eventtime):
@@ -385,29 +478,45 @@ class MCU:
def disconnect(self):
self.serial.disconnect()
if self._steppersync is not None:
self.ffi_lib.steppersync_free(self._steppersync)
self._ffi_lib.steppersync_free(self._steppersync)
self._steppersync = None
def stats(self, eventtime):
stats = self.serial.stats(eventtime)
stats += " mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
return "%s mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
self.serial.stats(eventtime),
self._mcu_tick_avg, self._mcu_tick_stddev)
err = 0
for s in self._steppers:
err += s.get_errors()
if err:
stats += " step_errors=%d" % (err,)
return stats
def force_shutdown(self):
self.send(self._emergency_stop_cmd.encode())
def clear_shutdown(self):
logging.info("Sending clear_shutdown command")
self.send(self._clear_shutdown_cmd.encode())
def microcontroller_restart(self):
reactor = self._printer.reactor
if self._restart_method == 'rpi_usb':
logging.info("Attempting a microcontroller reset via rpi usb power")
self.disconnect()
chelper.run_hub_ctrl(0)
reactor.pause(reactor.monotonic() + 2.000)
chelper.run_hub_ctrl(1)
return
if self._restart_method == 'command':
last_clock, last_clock_time = self.serial.get_last_clock()
eventtime = reactor.monotonic()
if (self._reset_cmd is None
or eventtime > last_clock_time + self.COMM_TIMEOUT):
logging.info("Unable to issue reset command")
return
# Attempt reset via command
logging.info("Attempting a microcontroller reset command")
self.send(self._reset_cmd.encode())
reactor.pause(reactor.monotonic() + 0.015)
self.disconnect()
return
# Attempt reset via arduino mechanism
logging.info("Attempting a microcontroller reset")
self.disconnect()
serialhdl.arduino_reset(self._serialport, reactor)
def is_fileoutput(self):
return self._is_fileoutput
# Configuration phase
def _add_custom(self):
data = self._config.get('custom', '')
for line in data.split('\n'):
for line in self._custom.split('\n'):
line = line.strip()
cpos = line.find('#')
if cpos >= 0:
@@ -415,33 +524,30 @@ class MCU:
if not line:
continue
self.add_config_cmd(line)
def build_config(self):
def _build_config(self):
# Build config commands
for oid in self._oids:
oid.build_config()
self._add_custom()
self._config_cmds.insert(0, "allocate_oids count=%d" % (
self._num_oids,))
len(self._oids),))
# Resolve pin names
mcu = self.serial.msgparser.config['MCU']
pin_map = self._config.get('pin_map', None)
if pin_map is None:
pnames = pins.mcu_to_pins(mcu)
else:
pnames = pins.map_pins(pin_map, mcu)
mcu = self.serial.msgparser.get_constant('MCU')
pnames = pins.get_pin_map(mcu, self._pin_map)
updated_cmds = []
for cmd in self._config_cmds:
try:
updated_cmds.append(pins.update_command(cmd, pnames))
updated_cmds.append(pins.update_command(
cmd, self._mcu_freq, pnames))
except:
raise self._config.error("Unable to translate pin name: %s" % (
raise self._config_error("Unable to translate pin name: %s" % (
cmd,))
self._config_cmds = updated_cmds
# Calculate config CRC
self._config_crc = zlib.crc32('\n'.join(self._config_cmds)) & 0xffffffff
self.add_config_cmd("finalize_config crc=%d" % (self._config_crc,))
self._send_config()
def _send_config(self):
msg = self.create_command("get_config")
if self._is_fileoutput:
@@ -450,25 +556,45 @@ class MCU:
else:
config_params = self.serial.send_with_response(msg, 'config')
if not config_params['is_config']:
if self._restart_method == 'rpi_usb':
# Only configure mcu after usb power reset
self._check_restart("full reset before config")
# Send config commands
logging.info("Sending printer configuration...")
for c in self._config_cmds:
self.send(self.create_command(c))
if not self._is_fileoutput:
config_params = self.serial.send_with_response(msg, 'config')
if not config_params['is_config']:
if self.is_shutdown:
raise error("Firmware error during config: %s" % (
self._shutdown_msg,))
raise error("Unable to configure printer")
elif self._printer.get_startup_state() == 'firmware_restart':
raise error("Failed automated reset of micro-controller")
if self._config_crc != config_params['crc']:
self._check_restart("CRC mismatch")
raise error("Printer CRC does not match config")
logging.info("Configured")
move_count = config_params['move_count']
logging.info("Configured (%d moves)" % (move_count,))
if self._printer.bglogger is not None:
msgparser = self.serial.msgparser
info = [
"Configured (%d moves)" % (move_count,),
"Loaded %d commands (%s)" % (
len(msgparser.messages_by_id), msgparser.version),
"MCU config: %s" % (" ".join(
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
self._printer.bglogger.set_rollover_info("mcu", "\n".join(info))
stepqueues = tuple(s._stepqueue for s in self._steppers)
self._steppersync = self.ffi_lib.steppersync_alloc(
self.serial.serialqueue, stepqueues, len(stepqueues),
config_params['move_count'])
self._steppersync = self._ffi_lib.steppersync_alloc(
self.serial.serialqueue, stepqueues, len(stepqueues), move_count)
for cb in self._init_callbacks:
cb()
# Config creation helpers
def create_oid(self):
oid = self._num_oids
self._num_oids += 1
return oid
def create_oid(self, oid):
self._oids.append(oid)
return len(self._oids) - 1
def add_config_cmd(self, cmd):
self._config_cmds.append(cmd)
def add_init_callback(self, callback):
@@ -484,26 +610,24 @@ class MCU:
def create_command(self, msg):
return self.serial.msgparser.create_command(msg)
# Wrappers for mcu object creation
def create_stepper(self, step_pin, dir_pin, min_stop_interval, max_error):
return MCU_stepper(self, step_pin, dir_pin, min_stop_interval, max_error)
def create_endstop(self, pin, stepper):
return MCU_endstop(self, pin, stepper)
def create_stepper(self, step_pin, dir_pin):
return MCU_stepper(self, step_pin, dir_pin)
def create_endstop(self, pin):
return MCU_endstop(self, pin)
def create_digital_out(self, pin, max_duration=2.):
max_duration = int(max_duration * self._mcu_freq)
return MCU_digital_out(self, pin, max_duration)
def create_pwm(self, pin, hard_cycle_ticks, max_duration=2.):
max_duration = int(max_duration * self._mcu_freq)
if hard_cycle_ticks:
return MCU_pwm(self, pin, hard_cycle_ticks, max_duration)
def create_pwm(self, pin, cycle_time, hard_cycle_ticks=0, max_duration=2.):
if hard_cycle_ticks < 0:
return MCU_digital_out(self, pin, max_duration)
cycle_ticks = int(self._mcu_freq / 10.)
return MCU_pwm(self, pin, cycle_ticks, max_duration, hard_pwm=False)
return MCU_pwm(self, pin, cycle_time, hard_cycle_ticks, max_duration)
def create_adc(self, pin):
return MCU_adc(self, pin)
# Clock syncing
def set_print_start_time(self, eventtime):
est_mcu_time = self.serial.get_clock(eventtime) / self._mcu_freq
clock = self.serial.get_clock(eventtime)
logging.debug("Synchronizing mcu clock at %.6f to %d" % (
eventtime, clock))
est_mcu_time = clock / self._mcu_freq
self._print_start_time = est_mcu_time
def get_print_buffer_time(self, eventtime, print_time):
if self.is_shutdown:
@@ -517,14 +641,22 @@ class MCU:
return self._mcu_freq
def get_last_clock(self):
return self.serial.get_last_clock()
def get_max_stepper_error(self):
return self._max_stepper_error
# Move command queuing
def send(self, cmd, minclock=0, reqclock=0, cq=None):
self.serial.send(cmd, minclock, reqclock, cq=cq)
def flush_moves(self, print_time):
if self._steppersync is None:
return
mcu_time = print_time + self._print_start_time
clock = int(mcu_time * self._mcu_freq)
self.ffi_lib.steppersync_flush(self._steppersync, clock)
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock)
if ret:
raise error("Internal error in stepcompress")
def pause(self, waketime):
return self._printer.reactor.pause(waketime)
def monotonic(self):
return self._printer.reactor.monotonic()
def __del__(self):
self.disconnect()

View File

@@ -1,6 +1,6 @@
# Protocol definitions for firmware communication
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import json, zlib, logging
@@ -106,8 +106,8 @@ class MessageFormat:
self.name = parts[0]
argparts = [arg.split('=') for arg in parts[1:]]
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
self.param_names = [name for name, fmt in argparts]
self.name_to_type = dict(zip(self.param_names, self.param_types))
self.param_names = [(name, MessageTypes[fmt]) for name, fmt in argparts]
self.name_to_type = dict(self.param_names)
def encode(self, *params):
out = []
out.append(self.msgid)
@@ -117,26 +117,24 @@ class MessageFormat:
def encode_by_name(self, **params):
out = []
out.append(self.msgid)
for name, t in zip(self.param_names, self.param_types):
for name, t in self.param_names:
t.encode(out, params[name])
return out
def parse(self, s, pos):
pos += 1
out = {}
for t, name in zip(self.param_types, self.param_names):
for name, t in self.param_names:
v, pos = t.parse(s, pos)
out[name] = v
return out, pos
def dump(self, s, pos):
pos += 1
def format_params(self, params):
out = []
for t in self.param_types:
v, pos = t.parse(s, pos)
for name, t in self.param_names:
v = params[name]
if not t.is_int:
v = repr(v)
out.append(v)
outmsg = self.debugformat % tuple(out)
return outmsg, pos
return self.debugformat % tuple(out)
class OutputFormat:
name = '#output'
@@ -164,17 +162,13 @@ class OutputFormat:
out = []
for t in self.param_types:
v, pos = t.parse(s, pos)
if not t.is_int:
v = repr(v)
out.append(v)
outmsg = self.debugformat % tuple(out)
return {'#msg': outmsg}, pos
def dump(self, s, pos):
pos += 1
out = []
for t in self.param_types:
v, pos = t.parse(s, pos)
out.append(v)
outmsg = self.debugformat % tuple(out)
return outmsg, pos
def format_params(self, params):
return "#output %s" % (params['#msg'],)
class UnknownFormat:
name = '#unknown'
@@ -182,8 +176,11 @@ class UnknownFormat:
msgid = s[pos]
msg = str(bytearray(s))
return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE
def format_params(self, params):
return "#unknown %s" % (repr(params['#msg']),)
class MessageParser:
error = error
def __init__(self):
self.unknown = UnknownFormat()
self.messages_by_id = {}
@@ -220,11 +217,20 @@ class MessageParser:
while 1:
msgid = s[pos]
mid = self.messages_by_id.get(msgid, self.unknown)
params, pos = mid.dump(s, pos)
out.append("%s" % (params,))
params, pos = mid.parse(s, pos)
out.append(mid.format_params(params))
if pos >= len(s)-MESSAGE_TRAILER_SIZE:
break
return out
def format_params(self, params):
name = params.get('#name')
mid = self.messages_by_name.get(name)
if mid is not None:
return mid.format_params(params)
msg = params.get('#msg')
if msg is not None:
return "%s %s" % (name, msg)
return str(params)
def parse(self, s):
msgid = s[MESSAGE_HEADER_SIZE]
mid = self.messages_by_id.get(msgid, self.unknown)
@@ -308,3 +314,15 @@ class MessageParser:
self.static_strings = data.get('static_strings', [])
self.config.update(data.get('config', {}))
self.version = data.get('version', '')
def get_constant(self, name):
try:
return self.config[name]
except KeyError:
raise error("Firmware constant '%s' not found" % (name,))
def get_constant_float(self, name):
try:
return float(self.config[name])
except ValueError:
raise error("Firmware constant '%s' not a float" % (name,))
except KeyError:
raise error("Firmware constant '%s' not found" % (name,))

View File

@@ -16,25 +16,13 @@ def port_pins(port_count, bit_count=8):
pins['P%c%d' % (portchr, portbit)] = port * bit_count + portbit
return pins
PINS_atmega164 = port_pins(4)
PINS_atmega1280 = port_pins(12)
MCU_PINS = {
"atmega168": PINS_atmega164, "atmega644p": PINS_atmega164,
"atmega168": port_pins(4), "atmega644p": port_pins(4),
"at90usb1286": port_pins(5),
"atmega1280": PINS_atmega1280, "atmega2560": PINS_atmega1280,
"atmega1280": port_pins(12), "atmega2560": port_pins(12),
"sam3x8e": port_pins(4, 32)
}
def mcu_to_pins(mcu):
return MCU_PINS.get(mcu, {})
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
def update_command(cmd, pmap):
def fixup(m):
return m.group('prefix') + str(pmap[m.group('name')])
return re_pin.sub(fixup, cmd)
######################################################################
# Arduino mappings
@@ -96,12 +84,28 @@ Arduino_from_mcu = {
"sam3x8e": (Arduino_Due, Arduino_Due_analog),
}
def map_pins(name, mcu):
######################################################################
# External commands
######################################################################
# Obtains the pin mappings
def get_pin_map(mcu, mapping_name=None):
pins = MCU_PINS.get(mcu, {})
if name == 'arduino':
if mapping_name == 'arduino':
dpins, apins = Arduino_from_mcu.get(mcu, [])
for i in range(len(dpins)):
pins['ar' + str(i)] = pins[dpins[i]]
for i in range(len(apins)):
pins['analog%d' % (i,)] = pins[apins[i]]
return pins
# Translate pin names and tick times in a firmware command
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
re_ticks = re.compile(r'TICKS\((?P<ticks>[^)]*)\)')
def update_command(cmd, mcu_freq, pmap):
def pin_fixup(m):
return m.group('prefix') + str(pmap[m.group('name')])
def ticks_fixup(m):
return str(int(mcu_freq * float(m.group('ticks'))))
return re_ticks.sub(ticks_fixup, re_pin.sub(pin_fixup, cmd))

View File

@@ -9,17 +9,20 @@
#include <stdint.h> // uint8_t
#include <stdio.h> // fprintf
#include <string.h> // strerror
#include <sys/time.h> // gettimeofday
#include <time.h> // struct timespec
#include "pyhelper.h" // get_time
#include "pyhelper.h" // get_monotonic
// Return the current system time as a double
// Return the monotonic system time as a double
double
get_time(void)
get_monotonic(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.;
struct timespec ts;
int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
if (ret) {
report_errno("clock_gettime", ret);
return 0.;
}
return (double)ts.tv_sec + (double)ts.tv_nsec * .000000001;
}
// Fill a 'struct timespec' with a system time stored in a double

View File

@@ -1,7 +1,10 @@
#ifndef PYHELPER_H
#define PYHELPER_H
double get_time(void);
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
double get_monotonic(void);
struct timespec fill_time(double time);
void set_python_logging_callback(void (*func)(const char *));
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

View File

@@ -1,9 +1,9 @@
# Code to implement asynchronous logging from a background thread
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, threading, Queue
import logging, logging.handlers, threading, Queue, time
# Class to forward all messages through a queue to a background thread
class QueueHandler(logging.Handler):
@@ -21,27 +21,39 @@ class QueueHandler(logging.Handler):
self.handleError(record)
# Class to poll a queue in a background thread and log each message
class QueueListener(object):
def __init__(self, handler):
self.handler = handler
self.queue = Queue.Queue()
self.thread = threading.Thread(target=self._bg_thread)
self.thread.start()
class QueueListener(logging.handlers.TimedRotatingFileHandler):
def __init__(self, filename):
logging.handlers.TimedRotatingFileHandler.__init__(
self, filename, when='midnight', backupCount=5)
self.bg_queue = Queue.Queue()
self.bg_thread = threading.Thread(target=self._bg_thread)
self.bg_thread.start()
self.rollover_info = {}
def _bg_thread(self):
while 1:
record = self.queue.get(True)
record = self.bg_queue.get(True)
if record is None:
break
self.handler.handle(record)
self.handle(record)
def stop(self):
self.queue.put_nowait(None)
self.thread.join()
self.bg_queue.put_nowait(None)
self.bg_thread.join()
def set_rollover_info(self, name, info):
self.rollover_info[name] = info
def doRollover(self):
logging.handlers.TimedRotatingFileHandler.doRollover(self)
lines = [self.rollover_info[name]
for name in sorted(self.rollover_info)
if self.rollover_info[name]]
lines.append(
"=============== Log rollover at %s ===============" % (
time.asctime(),))
self.emit(logging.makeLogRecord(
{'msg': "\n".join(lines), 'level': logging.INFO}))
def setup_bg_logging(filename, debuglevel):
logoutput = open(filename, 'wb')
handler = logging.StreamHandler(logoutput)
ql = QueueListener(handler)
qh = QueueHandler(ql.queue)
ql = QueueListener(filename)
qh = QueueHandler(ql.bg_queue)
root = logging.getLogger()
root.addHandler(qh)
root.setLevel(debuglevel)

View File

@@ -1,10 +1,11 @@
# File descriptor and timer event helper
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import select, time, math
import select, math, time
import greenlet
import chelper
class ReactorTimer:
def __init__(self, callback, waketime):
@@ -33,6 +34,7 @@ class SelectReactor:
self._process = False
self._g_dispatch = None
self._greenlets = []
self.monotonic = chelper.get_ffi()[1].get_monotonic
# Timers
def _note_time(self, t):
nexttime = t.waketime
@@ -67,11 +69,19 @@ class SelectReactor:
self._note_time(t)
if eventtime >= self._next_timer:
return 0.
return min(1., max(.001, self._next_timer - time.time()))
return min(1., max(.001, self._next_timer - self.monotonic()))
# Greenlets
def _sys_pause(self, waketime):
# Pause using system sleep for when reactor not running
delay = waketime - self.monotonic()
if delay > 0.:
time.sleep(delay)
return self.monotonic()
def pause(self, waketime):
g = greenlet.getcurrent()
if g is not self._g_dispatch:
if self._g_dispatch is None:
return self._sys_pause(waketime)
return self._g_dispatch.switch(waketime)
if self._greenlets:
g_next = self._greenlets.pop()
@@ -95,20 +105,21 @@ class SelectReactor:
self._fds.pop(self._fds.index(handler))
# Main loop
def _dispatch_loop(self):
self._process = True
self._g_dispatch = g_dispatch = greenlet.getcurrent()
eventtime = time.time()
eventtime = self.monotonic()
while self._process:
timeout = self._check_timers(eventtime)
res = select.select(self._fds, [], [], timeout)
eventtime = time.time()
eventtime = self.monotonic()
for fd in res[0]:
fd.callback(eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
eventtime = self.monotonic()
break
self._g_dispatch = None
def run(self):
self._process = True
g_next = ReactorGreenlet(run=self._dispatch_loop)
g_next.switch()
def end(self):
@@ -134,17 +145,17 @@ class PollReactor(SelectReactor):
self._fds = fds
# Main loop
def _dispatch_loop(self):
self._process = True
self._g_dispatch = g_dispatch = greenlet.getcurrent()
eventtime = time.time()
eventtime = self.monotonic()
while self._process:
timeout = int(math.ceil(self._check_timers(eventtime) * 1000.))
res = self._poll.poll(timeout)
eventtime = time.time()
timeout = self._check_timers(eventtime)
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
eventtime = self.monotonic()
for fd, event in res:
self._fds[fd](eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
eventtime = self.monotonic()
break
self._g_dispatch = None
@@ -168,17 +179,17 @@ class EPollReactor(SelectReactor):
self._fds = fds
# Main loop
def _dispatch_loop(self):
self._process = True
self._g_dispatch = g_dispatch = greenlet.getcurrent()
eventtime = time.time()
eventtime = self.monotonic()
while self._process:
timeout = self._check_timers(eventtime)
res = self._epoll.poll(timeout)
eventtime = time.time()
eventtime = self.monotonic()
for fd, event in res:
self._fds[fd](eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
eventtime = self.monotonic()
break
self._g_dispatch = None

View File

@@ -3,11 +3,14 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import time, logging, threading
import logging, threading
import serial
import msgproto, chelper, util
class error(Exception):
pass
class SerialReader:
BITS_PER_BYTE = 10.
def __init__(self, reactor, serialport, baud):
@@ -30,8 +33,7 @@ class SerialReader:
self.lock = threading.Lock()
self.background_thread = None
# Message handlers
self.status_timer = self.reactor.register_timer(
self._status_event, self.reactor.NOW)
self.status_timer = self.reactor.register_timer(self._status_event)
self.status_cmd = None
handlers = {
'#unknown': self.handle_unknown,
@@ -60,10 +62,10 @@ class SerialReader:
# Initial connection
logging.info("Starting serial connect")
while 1:
starttime = time.time()
starttime = self.reactor.monotonic()
try:
self.ser = serial.Serial(self.serialport, self.baud, timeout=0)
except OSError, e:
except (OSError, serial.SerialException), e:
logging.warn("Unable to open port: %s" % (e,))
self.reactor.pause(starttime + 5.)
continue
@@ -78,8 +80,6 @@ class SerialReader:
if identify_data is None:
logging.warn("Timeout on serial connect")
self.disconnect()
self.ser.close()
self.ser = None
continue
break
msgparser = msgproto.MessageParser()
@@ -88,14 +88,29 @@ class SerialReader:
self.register_callback(self.handle_unknown, '#unknown')
logging.info("Loaded %d commands (%s)" % (
len(msgparser.messages_by_id), msgparser.version))
# Setup for runtime
logging.info("MCU config: %s" % (" ".join(
["%s=%s" % (k, v) for k, v in msgparser.config.items()])))
# Setup baud adjust
mcu_baud = float(msgparser.config.get('SERIAL_BAUD', 0.))
if mcu_baud:
baud_adjust = self.BITS_PER_BYTE / mcu_baud
self.ffi_lib.serialqueue_set_baud_adjust(
self.serialqueue, baud_adjust)
# Enable periodic get_status timer
get_status = msgparser.lookup_command('get_status')
self.status_cmd = get_status.encode()
self.reactor.update_timer(self.status_timer, self.reactor.NOW)
# Load initial last_ack_clock/last_ack_time
uptime_msg = msgparser.create_command('get_uptime')
params = self.send_with_response(uptime_msg, 'uptime')
self.last_ack_clock = (params['high'] << 32) | params['clock']
self.last_ack_time = params['#receive_time']
# Make sure est_clock is calculated
starttime = eventtime = self.reactor.monotonic()
while not self.est_clock:
if eventtime > starttime + 5.:
raise error("timeout on est_clock calculation")
eventtime = self.reactor.pause(eventtime + 0.010)
def connect_file(self, debugoutput, dictionary, pace=False):
self.ser = debugoutput
self.msgparser.process_identify(dictionary, decompress=False)
@@ -104,19 +119,21 @@ class SerialReader:
est_clock = float(self.msgparser.config['CLOCK_FREQ'])
self.serialqueue = self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1)
self.est_clock = est_clock
self.last_ack_time = time.time()
self.last_ack_time = self.reactor.monotonic()
self.last_ack_clock = 0
self.ffi_lib.serialqueue_set_clock_est(
self.serialqueue, self.est_clock, self.last_ack_time
, self.last_ack_clock)
def disconnect(self):
if self.serialqueue is None:
return
if self.serialqueue is not None:
self.ffi_lib.serialqueue_exit(self.serialqueue)
if self.background_thread is not None:
self.background_thread.join()
self.ffi_lib.serialqueue_free(self.serialqueue)
self.background_thread = self.serialqueue = None
if self.ser is not None:
self.ser.close()
self.ser = None
def stats(self, eventtime):
if self.serialqueue is None:
return ""
@@ -127,8 +144,6 @@ class SerialReader:
self.est_clock, self.last_ack_time, self.last_ack_clock)
return sqstats + tstats
def _status_event(self, eventtime):
if self.status_cmd is None:
return eventtime + 0.1
self.send(self.status_cmd)
return eventtime + 1.0
# Serial response callbacks
@@ -162,11 +177,9 @@ class SerialReader:
def encode_and_send(self, data, minclock, reqclock, cq):
self.ffi_lib.serialqueue_encode_and_send(
self.serialqueue, cq, data, len(data), minclock, reqclock)
def send_with_response(self, cmd, name):
src = SerialRetryCommand(self, cmd, name)
def send_with_response(self, cmd, name, oid=None):
src = SerialRetryCommand(self, cmd, name, oid)
return src.get_response()
def send_flush(self):
self.ffi_lib.serialqueue_flush_ready(self.serialqueue)
def alloc_command_queue(self):
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
self.ffi_lib.serialqueue_free_commandqueue)
@@ -226,16 +239,21 @@ class SerialReader:
# Class to retry sending of a query command until a given response is received
class SerialRetryCommand:
TIMEOUT_TIME = 5.0
RETRY_TIME = 0.500
def __init__(self, serial, cmd, name):
def __init__(self, serial, cmd, name, oid=None):
self.serial = serial
self.cmd = cmd
self.name = name
self.oid = oid
self.response = None
self.min_query_time = time.time()
self.serial.register_callback(self.handle_callback, self.name)
self.min_query_time = self.serial.reactor.monotonic()
self.serial.register_callback(self.handle_callback, self.name, self.oid)
self.send_timer = self.serial.reactor.register_timer(
self.send_event, self.serial.reactor.NOW)
def unregister(self):
self.serial.unregister_callback(self.name, self.oid)
self.serial.reactor.unregister_timer(self.send_timer)
def send_event(self, eventtime):
if self.response is not None:
return self.serial.reactor.NEVER
@@ -246,11 +264,13 @@ class SerialRetryCommand:
if last_sent_time >= self.min_query_time:
self.response = params
def get_response(self):
eventtime = time.time()
eventtime = self.serial.reactor.monotonic()
while self.response is None:
eventtime = self.serial.reactor.pause(eventtime + 0.05)
self.serial.unregister_callback(self.name)
self.serial.reactor.unregister_timer(self.send_timer)
if eventtime > self.min_query_time + self.TIMEOUT_TIME:
self.unregister()
raise error("Timeout on wait for '%s' response" % (self.name,))
self.unregister()
return self.response
# Code to start communication and download message type dictionary
@@ -267,7 +287,7 @@ class SerialBootStrap:
self.send_timer = self.serial.reactor.register_timer(
self.send_event, self.serial.reactor.NOW)
def get_identify_data(self, timeout):
eventtime = time.time()
eventtime = self.serial.reactor.monotonic()
while not self.is_done and eventtime <= timeout:
eventtime = self.serial.reactor.pause(eventtime + 0.05)
self.serial.unregister_callback('identify_response')
@@ -305,10 +325,23 @@ def stk500v2_leave(ser, reactor):
ser.read(1)
# Send stk500v2 leave programmer sequence
ser.baudrate = 115200
reactor.pause(time.time() + 0.100)
reactor.pause(reactor.monotonic() + 0.100)
ser.read(4096)
ser.write('\x1b\x01\x00\x01\x0e\x11\x04')
reactor.pause(time.time() + 0.050)
reactor.pause(reactor.monotonic() + 0.050)
res = ser.read(4096)
logging.debug("Got %s from stk500v2" % (repr(res),))
ser.baudrate = origbaud
# Attempt an arduino style reset on a serial port
def arduino_reset(serialport, reactor):
# First try opening the port at 1200 baud
ser = serial.Serial(serialport, 1200, timeout=0)
ser.read(1)
reactor.pause(reactor.monotonic() + 0.100)
# Then try toggling DTR
ser.dtr = True
reactor.pause(reactor.monotonic() + 0.100)
ser.dtr = False
reactor.pause(reactor.monotonic() + 0.100)
ser.close()

View File

@@ -23,7 +23,7 @@
#include <termios.h> // tcflush
#include <unistd.h> // pipe
#include "list.h" // list_add_tail
#include "pyhelper.h" // get_time
#include "pyhelper.h" // get_monotonic
#include "serialqueue.h" // struct queue_message
@@ -149,11 +149,11 @@ static void
pollreactor_run(struct pollreactor *pr)
{
pr->must_exit = 0;
double eventtime = get_time();
double eventtime = get_monotonic();
while (! pr->must_exit) {
int timeout = pollreactor_check_timers(pr, eventtime);
int ret = poll(pr->fds, pr->num_fds, timeout);
eventtime = get_time();
eventtime = get_monotonic();
if (ret > 0) {
int i;
for (i=0; i<pr->num_fds; i++)
@@ -356,7 +356,6 @@ struct serialqueue {
struct list_head pending_queues;
int ready_bytes, stalled_bytes;
uint64_t need_kick_clock;
int can_delay_writes;
// Received messages
struct list_head receive_queue;
// Debugging
@@ -488,7 +487,8 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
if (rseq != sq->receive_seq)
// New sequence number
update_receive_seq(sq, eventtime, rseq);
else if (len == MESSAGE_MIN && rseq > sq->retransmit_seq)
else if (len == MESSAGE_MIN && rseq > sq->retransmit_seq
&& !list_empty(&sq->sent_queue))
// Duplicate sequence number in an empty message is a nak
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NOW);
@@ -496,7 +496,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
// Add message to receive queue
struct queue_message *qm = message_fill(sq->input_buf, len);
qm->sent_time = sq->last_receive_sent_time;
qm->receive_time = eventtime;
qm->receive_time = get_monotonic(); // must be time post read()
list_add_tail(&qm->node, &sq->receive_queue);
check_wake_receive(sq);
}
@@ -695,11 +695,9 @@ check_send_command(struct serialqueue *sq, double eventtime)
// Check for messages to send
if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX)
return PR_NOW;
if (! sq->can_delay_writes) {
if (! sq->est_clock) {
if (sq->ready_bytes)
return PR_NOW;
if (sq->est_clock)
sq->can_delay_writes = 1;
sq->need_kick_clock = MAX_CLOCK;
return PR_NEVER;
}
@@ -986,16 +984,6 @@ serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
pthread_mutex_unlock(&sq->lock);
}
// Flush all messages in a "ready" state
void
serialqueue_flush_ready(struct serialqueue *sq)
{
pthread_mutex_lock(&sq->lock);
sq->can_delay_writes = 0;
pthread_mutex_unlock(&sq->lock);
kick_bg_thread(sq);
}
// Return a string buffer containing statistics for the serial port
void
serialqueue_get_stats(struct serialqueue *sq, char *buf, int len)

View File

@@ -61,7 +61,6 @@ void serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm);
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust);
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
, double last_ack_time, uint64_t last_ack_clock);
void serialqueue_flush_ready(struct serialqueue *sq);
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
int serialqueue_extract_old(struct serialqueue *sq, int sentq
, struct pull_queue_message *q, int max);

View File

@@ -1,6 +1,6 @@
// Stepper pulse schedule compression
//
// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
//
@@ -31,10 +31,8 @@ struct stepcompress {
uint64_t *queue, *queue_end, *queue_pos, *queue_next;
// Internal tracking
uint32_t max_error;
// Error checking
uint32_t errors;
// Message generation
uint64_t last_step_clock;
uint64_t last_step_clock, homing_clock;
struct list_head msg_queue;
uint32_t queue_step_msgid, set_next_step_dir_msgid, oid;
int sdir, invert_sdir;
@@ -174,6 +172,9 @@ compress_bisect_add(struct stepcompress *sc)
zerointerval = interval;
zerocount = count;
}
if (count > 0x200)
// No 'add' will improve sequence; avoid integer overflow
break;
}
// Check if a greater or lesser add could extend the sequence
@@ -221,27 +222,30 @@ compress_bisect_add(struct stepcompress *sc)
* Step compress checking
****************************************************************/
#define ERROR_RET -989898989
// Verify that a given 'step_move' matches the actual step times
static void
static int
check_line(struct stepcompress *sc, struct step_move move)
{
if (!CHECK_LINES)
return;
return 0;
if (move.count == 1) {
if (move.interval != (uint32_t)(*sc->queue_pos - sc->last_step_clock)
|| *sc->queue_pos < sc->last_step_clock) {
errorf("Count 1 point out of range: %d %d %d"
, move.interval, move.count, move.add);
sc->errors++;
errorf("stepcompress o=%d i=%d c=%d a=%d:"
" Count 1 point out of range (%lld)"
, sc->oid, move.interval, move.count, move.add
, (long long)(*sc->queue_pos - sc->last_step_clock));
return ERROR_RET;
}
return;
return 0;
}
int err = 0;
if (!move.count || (!move.interval && !move.add)
|| move.interval >= 0x80000000) {
errorf("Point out of range: %d %d %d"
, move.interval, move.count, move.add);
err++;
errorf("stepcompress o=%d i=%d c=%d a=%d: Invalid sequence"
, sc->oid, move.interval, move.count, move.add);
return ERROR_RET;
}
uint32_t interval = move.interval, p = 0;
uint16_t i;
@@ -249,18 +253,21 @@ check_line(struct stepcompress *sc, struct step_move move)
struct points point = minmax_point(sc, sc->queue_pos + i);
p += interval;
if (p < point.minp || p > point.maxp) {
errorf("Point %d of %d: %d not in %d:%d"
, i+1, move.count, p, point.minp, point.maxp);
err++;
errorf("stepcompress o=%d i=%d c=%d a=%d: Point %d: %d not in %d:%d"
, sc->oid, move.interval, move.count, move.add
, i+1, p, point.minp, point.maxp);
return ERROR_RET;
}
if (interval >= 0x80000000) {
errorf("Point %d of %d: interval overflow %d"
, i+1, move.count, interval);
err++;
errorf("stepcompress o=%d i=%d c=%d a=%d:"
" Point %d: interval overflow %d"
, sc->oid, move.interval, move.count, move.add
, i+1, interval);
return ERROR_RET;
}
interval += move.add;
}
sc->errors += err;
return 0;
}
@@ -268,22 +275,6 @@ check_line(struct stepcompress *sc, struct step_move move)
* Step compress interface
****************************************************************/
#define likely(x) __builtin_expect(!!(x), 1)
// Wrapper around sqrt() to handle small negative numbers
static double
_safe_sqrt(double v)
{
if (v > -0.001)
// Due to floating point truncation, it's possible to get a
// small negative number - treat it as zero.
return 0.;
return sqrt(v);
}
static inline double safe_sqrt(double v) {
return likely(v >= 0.) ? sqrt(v) : _safe_sqrt(v);
}
// Allocate a new 'stepcompress' object
struct stepcompress *
stepcompress_alloc(uint32_t max_error, uint32_t queue_step_msgid
@@ -314,14 +305,16 @@ stepcompress_free(struct stepcompress *sc)
}
// Convert previously scheduled steps into commands for the mcu
static void
static int
stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
{
if (sc->queue_pos >= sc->queue_next)
return;
return 0;
while (move_clock > sc->last_step_clock) {
struct step_move move = compress_bisect_add(sc);
check_line(sc, move);
int ret = check_line(sc, move);
if (ret)
return ret;
uint32_t msg[5] = {
sc->queue_step_msgid, sc->oid, move.interval, move.count, move.add
@@ -332,10 +325,13 @@ stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
// Be careful with 32bit overflow
sc->last_step_clock = qm->req_clock = *sc->queue_pos;
} else {
uint32_t addfactor = move.count*(move.count-1)/2;
int32_t addfactor = move.count*(move.count-1)/2;
uint32_t ticks = move.add*addfactor + move.interval*move.count;
sc->last_step_clock += ticks;
}
if (sc->homing_clock)
// When homing, all steps should be sent prior to homing_clock
qm->min_clock = qm->req_clock = sc->homing_clock;
list_add_tail(&qm->node, &sc->msg_queue);
if (sc->queue_pos + move.count >= sc->queue_next) {
@@ -344,232 +340,325 @@ stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
}
sc->queue_pos += move.count;
}
return 0;
}
// Send the set_next_step_dir command
static void
static int
set_next_step_dir(struct stepcompress *sc, int sdir)
{
if (sc->sdir == sdir)
return 0;
sc->sdir = sdir;
stepcompress_flush(sc, UINT64_MAX);
int ret = stepcompress_flush(sc, UINT64_MAX);
if (ret)
return ret;
uint32_t msg[3] = {
sc->set_next_step_dir_msgid, sc->oid, sdir ^ sc->invert_sdir
};
struct queue_message *qm = message_alloc_and_encode(msg, 3);
qm->req_clock = sc->last_step_clock;
qm->req_clock = sc->homing_clock ?: sc->last_step_clock;
list_add_tail(&qm->node, &sc->msg_queue);
return 0;
}
// Check if the internal queue needs to be expanded, and expand if so
static inline void
check_expand(struct stepcompress *sc, int sdir, int count)
static int
_check_push(struct stepcompress *sc)
{
if (sdir != sc->sdir)
set_next_step_dir(sc, sdir);
if (sc->queue_next + count > sc->queue_end)
expand_queue(sc, count);
if (sc->queue_next - sc->queue_pos > 65535 + 2000) {
// No point in keeping more than 64K steps in memory
int ret = stepcompress_flush(sc, *(sc->queue_next - 65535));
if (ret)
return ret;
}
// Schedule a step event at the specified step_clock time
void
stepcompress_push(struct stepcompress *sc, double step_clock, int32_t sdir)
{
sdir = !!sdir;
check_expand(sc, sdir, 1);
step_clock += 0.5;
*sc->queue_next++ = step_clock;
}
// Schedule 'steps' number of steps with a constant time between steps
// using the formula: step_clock = clock_offset + step_num*factor
int32_t
stepcompress_push_factor(struct stepcompress *sc
, double steps, double step_offset
, double clock_offset, double factor)
{
// Calculate number of steps to take
int sdir = 1;
if (steps < 0) {
sdir = 0;
steps = -steps;
step_offset = -step_offset;
}
int count = steps + .5 - step_offset;
if (count <= 0 || count > 1000000) {
if (count && steps)
errorf("push_factor invalid count %d %f %f %f %f"
, sc->oid, steps, step_offset, clock_offset, factor);
expand_queue(sc, 1);
return 0;
}
check_expand(sc, sdir, count);
// Calculate each step time
uint64_t *qn = sc->queue_next, *end = &qn[count];
clock_offset += 0.5;
double pos = step_offset + .5;
while (qn < end) {
*qn++ = clock_offset + pos*factor;
pos += 1.0;
}
sc->queue_next = qn;
return sdir ? count : -count;
}
// Schedule 'steps' number of steps using the formula:
// step_clock = clock_offset + sqrt(step_num*factor + sqrt_offset)
int32_t
stepcompress_push_sqrt(struct stepcompress *sc, double steps, double step_offset
, double clock_offset, double sqrt_offset, double factor)
static inline int
check_push(struct stepcompress *sc, uint64_t **pqnext, uint64_t **pqend
, uint64_t c)
{
// Calculate number of steps to take
int sdir = 1;
if (steps < 0) {
sdir = 0;
steps = -steps;
step_offset = -step_offset;
if (unlikely(*pqnext >= *pqend)) {
sc->queue_next = *pqnext;
int ret = _check_push(sc);
if (ret)
return ret;
*pqnext = sc->queue_next;
*pqend = sc->queue_end;
}
int count = steps + .5 - step_offset;
if (count <= 0 || count > 1000000) {
if (count && steps)
errorf("push_sqrt invalid count %d %f %f %f %f %f"
, sc->oid, steps, step_offset, clock_offset, sqrt_offset
, factor);
*(*pqnext)++ = c;
return 0;
}
check_expand(sc, sdir, count);
// Calculate each step time
uint64_t *qn = sc->queue_next, *end = &qn[count];
clock_offset += 0.5;
double pos = step_offset + .5 + sqrt_offset/factor;
while (qn < end) {
double v = safe_sqrt(pos*factor);
*qn++ = clock_offset + (factor >= 0. ? v : -v);
pos += 1.0;
}
sc->queue_next = qn;
return sdir ? count : -count;
}
// Schedule 'count' number of steps using the delta kinematic const speed
int32_t
stepcompress_push_delta_const(
struct stepcompress *sc, double clock_offset, double dist, double start_pos
, double inv_velocity, double step_dist
, double height, double closestxy_d, double closest_height2, double movez_r)
{
// Calculate number of steps to take
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
double reldist = closestxy_d - movexy_r*dist;
double end_height = safe_sqrt(closest_height2 - reldist*reldist);
int count = (end_height - height + movez_r*dist) / step_dist + .5;
if (count <= 0 || count > 1000000) {
if (count)
errorf("push_delta_const invalid count %d %d %f %f %f %f %f %f %f %f"
, sc->oid, count, clock_offset, dist, step_dist, start_pos
, closest_height2, height, movez_r, inv_velocity);
return 0;
}
check_expand(sc, step_dist > 0., count);
// Calculate each step time
uint64_t *qn = sc->queue_next, *end = &qn[count];
clock_offset += 0.5;
start_pos += movexy_r*closestxy_d;
height += .5 * step_dist;
if (!movez_r) {
// Optmized case for common XY only moves (no Z movement)
while (qn < end) {
double v = safe_sqrt(closest_height2 - height*height);
double pos = start_pos + (step_dist > 0. ? -v : v);
*qn++ = clock_offset + pos * inv_velocity;
height += step_dist;
}
} else if (!movexy_r) {
// Optmized case for Z only moves
double v = (step_dist > 0. ? -end_height : end_height);
while (qn < end) {
double pos = start_pos + movez_r*height + v;
*qn++ = clock_offset + pos * inv_velocity;
height += step_dist;
}
} else {
// General case (handles XY+Z moves)
while (qn < end) {
double relheight = movexy_r*height - movez_r*closestxy_d;
double v = safe_sqrt(closest_height2 - relheight*relheight);
double pos = start_pos + movez_r*height + (step_dist > 0. ? -v : v);
*qn++ = clock_offset + pos * inv_velocity;
height += step_dist;
}
}
sc->queue_next = qn;
return step_dist > 0. ? count : -count;
}
// Schedule 'count' number of steps using delta kinematic acceleration
int32_t
stepcompress_push_delta_accel(
struct stepcompress *sc, double clock_offset, double dist, double start_pos
, double accel_multiplier, double step_dist
, double height, double closestxy_d, double closest_height2, double movez_r)
{
// Calculate number of steps to take
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
double reldist = closestxy_d - movexy_r*dist;
double end_height = safe_sqrt(closest_height2 - reldist*reldist);
int count = (end_height - height + movez_r*dist) / step_dist + .5;
if (count <= 0 || count > 1000000) {
if (count)
errorf("push_delta_accel invalid count %d %d %f %f %f %f %f %f %f %f"
, sc->oid, count, clock_offset, dist, step_dist, start_pos
, closest_height2, height, movez_r, accel_multiplier);
return 0;
}
check_expand(sc, step_dist > 0., count);
// Calculate each step time
uint64_t *qn = sc->queue_next, *end = &qn[count];
clock_offset += 0.5;
start_pos += movexy_r*closestxy_d;
height += .5 * step_dist;
while (qn < end) {
double relheight = movexy_r*height - movez_r*closestxy_d;
double v = safe_sqrt(closest_height2 - relheight*relheight);
double pos = start_pos + movez_r*height + (step_dist > 0. ? -v : v);
v = safe_sqrt(pos * accel_multiplier);
*qn++ = clock_offset + (accel_multiplier >= 0. ? v : -v);
height += step_dist;
}
sc->queue_next = qn;
return step_dist > 0. ? count : -count;
}
// Reset the internal state of the stepcompress object
void
int
stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock)
{
stepcompress_flush(sc, UINT64_MAX);
int ret = stepcompress_flush(sc, UINT64_MAX);
if (ret)
return ret;
sc->last_step_clock = last_step_clock;
sc->sdir = -1;
return 0;
}
// Indicate the stepper is in homing mode (or done homing if zero)
int
stepcompress_set_homing(struct stepcompress *sc, uint64_t homing_clock)
{
int ret = stepcompress_flush(sc, UINT64_MAX);
if (ret)
return ret;
sc->homing_clock = homing_clock;
return 0;
}
// Queue an mcu command to go out in order with stepper commands
void
int
stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len)
{
stepcompress_flush(sc, UINT64_MAX);
int ret = stepcompress_flush(sc, UINT64_MAX);
if (ret)
return ret;
struct queue_message *qm = message_alloc_and_encode(data, len);
qm->req_clock = sc->last_step_clock;
qm->req_clock = sc->homing_clock ?: sc->last_step_clock;
list_add_tail(&qm->node, &sc->msg_queue);
return 0;
}
// Return the count of internal errors found
uint32_t
stepcompress_get_errors(struct stepcompress *sc)
/****************************************************************
* Motion to step conversions
****************************************************************/
// Wrapper around sqrt() to handle small negative numbers
static double
_safe_sqrt(double v)
{
return sc->errors;
// Due to floating point truncation, it's possible to get a small
// negative number - treat it as zero.
if (v < -0.001)
errorf("safe_sqrt of %.9f", v);
return 0.;
}
static inline double safe_sqrt(double v) {
return likely(v >= 0.) ? sqrt(v) : _safe_sqrt(v);
}
// Schedule a step event at the specified step_clock time
int
stepcompress_push(struct stepcompress *sc, double step_clock, int32_t sdir)
{
int ret = set_next_step_dir(sc, !!sdir);
if (ret)
return ret;
step_clock += 0.5;
uint64_t *qnext = sc->queue_next, *qend = sc->queue_end;
ret = check_push(sc, &qnext, &qend, step_clock);
if (ret)
return ret;
sc->queue_next = qnext;
return 0;
}
// Common suffixes: _sd is step distance (a unit length the same
// distance the stepper moves on each step), _sv is step velocity (in
// units of step distance per clock tick), _sd2 is step distance
// squared, _r is ratio (scalar usually between 0.0 and 1.0). Times
// are represented as clock ticks (a unit of time determined by a
// micro-controller tick) and acceleration is in units of step
// distance per clock ticks squared.
// Schedule 'steps' number of steps at constant acceleration. If
// acceleration is zero (ie, constant velocity) it uses the formula:
// step_clock = clock_offset + step_num/start_sv
// Otherwise it uses the formula:
// step_clock = (clock_offset + sqrt(2*step_num/accel + (start_sv/accel)**2)
// - start_sv/accel)
int32_t
stepcompress_push_const(
struct stepcompress *sc, double clock_offset
, double step_offset, double steps, double start_sv, double accel)
{
// Calculate number of steps to take
int sdir = 1;
if (steps < 0) {
sdir = 0;
steps = -steps;
step_offset = -step_offset;
}
int count = steps + .5 - step_offset;
if (count <= 0 || count > 10000000) {
if (count && steps) {
errorf("push_const invalid count %d %f %f %f %f %f"
, sc->oid, clock_offset, step_offset, steps
, start_sv, accel);
return ERROR_RET;
}
return 0;
}
int ret = set_next_step_dir(sc, sdir);
if (ret)
return ret;
int res = sdir ? count : -count;
// Calculate each step time
clock_offset += 0.5;
double pos = step_offset + .5;
uint64_t *qnext = sc->queue_next, *qend = sc->queue_end;
if (!accel) {
// Move at constant velocity (zero acceleration)
double inv_cruise_sv = 1. / start_sv;
while (count--) {
uint64_t c = clock_offset + pos*inv_cruise_sv;
int ret = check_push(sc, &qnext, &qend, c);
if (ret)
return ret;
pos += 1.0;
}
} else {
// Move with constant acceleration
double inv_accel = 1. / accel;
clock_offset -= start_sv * inv_accel;
pos += .5 * start_sv*start_sv * inv_accel;
double accel_multiplier = 2. * inv_accel;
while (count--) {
double v = safe_sqrt(pos * accel_multiplier);
uint64_t c = clock_offset + (accel_multiplier >= 0. ? v : -v);
int ret = check_push(sc, &qnext, &qend, c);
if (ret)
return ret;
pos += 1.0;
}
}
sc->queue_next = qnext;
return res;
}
// Schedule steps using delta kinematics
static int32_t
_stepcompress_push_delta(
struct stepcompress *sc, int sdir
, double clock_offset, double move_sd, double start_sv, double accel
, double height, double startxy_sd, double arm_sd, double movez_r)
{
// Calculate number of steps to take
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
double arm_sd2 = arm_sd * arm_sd;
double endxy_sd = startxy_sd - movexy_r*move_sd;
double end_height = safe_sqrt(arm_sd2 - endxy_sd*endxy_sd);
int count = (end_height + movez_r*move_sd - height) * (sdir ? 1. : -1.) + .5;
if (count <= 0 || count > 10000000) {
if (count) {
errorf("push_delta invalid count %d %d %f %f %f %f %f %f %f %f"
, sc->oid, count, clock_offset, move_sd, start_sv, accel
, height, startxy_sd, arm_sd, movez_r);
return ERROR_RET;
}
return 0;
}
int ret = set_next_step_dir(sc, sdir);
if (ret)
return ret;
int res = sdir ? count : -count;
// Calculate each step time
clock_offset += 0.5;
height += (sdir ? .5 : -.5);
uint64_t *qnext = sc->queue_next, *qend = sc->queue_end;
if (!accel) {
// Move at constant velocity (zero acceleration)
double inv_cruise_sv = 1. / start_sv;
if (!movez_r) {
// Optimized case for common XY only moves (no Z movement)
while (count--) {
double v = safe_sqrt(arm_sd2 - height*height);
double pos = startxy_sd + (sdir ? -v : v);
uint64_t c = clock_offset + pos * inv_cruise_sv;
int ret = check_push(sc, &qnext, &qend, c);
if (ret)
return ret;
height += (sdir ? 1. : -1.);
}
} else if (!movexy_r) {
// Optimized case for Z only moves
double pos = (sdir ? height-end_height : end_height-height);
while (count--) {
uint64_t c = clock_offset + pos * inv_cruise_sv;
int ret = check_push(sc, &qnext, &qend, c);
if (ret)
return ret;
pos += 1.;
}
} else {
// General case (handles XY+Z moves)
double start_pos = movexy_r*startxy_sd, zoffset = movez_r*startxy_sd;
while (count--) {
double relheight = movexy_r*height - zoffset;
double v = safe_sqrt(arm_sd2 - relheight*relheight);
double pos = start_pos + movez_r*height + (sdir ? -v : v);
uint64_t c = clock_offset + pos * inv_cruise_sv;
int ret = check_push(sc, &qnext, &qend, c);
if (ret)
return ret;
height += (sdir ? 1. : -1.);
}
}
} else {
// Move with constant acceleration
double start_pos = movexy_r*startxy_sd, zoffset = movez_r*startxy_sd;
double inv_accel = 1. / accel;
clock_offset -= start_sv * inv_accel;
start_pos += 0.5 * start_sv*start_sv * inv_accel;
double accel_multiplier = 2. * inv_accel;
while (count--) {
double relheight = movexy_r*height - zoffset;
double v = safe_sqrt(arm_sd2 - relheight*relheight);
double pos = start_pos + movez_r*height + (sdir ? -v : v);
v = safe_sqrt(pos * accel_multiplier);
uint64_t c = clock_offset + (accel_multiplier >= 0. ? v : -v);
int ret = check_push(sc, &qnext, &qend, c);
if (ret)
return ret;
height += (sdir ? 1. : -1.);
}
}
sc->queue_next = qnext;
return res;
}
int32_t
stepcompress_push_delta(
struct stepcompress *sc, double clock_offset, double move_sd
, double start_sv, double accel
, double height, double startxy_sd, double arm_sd, double movez_r)
{
double reversexy_sd = startxy_sd + arm_sd*movez_r;
if (reversexy_sd <= 0.)
// All steps are in down direction
return _stepcompress_push_delta(
sc, 0, clock_offset, move_sd, start_sv, accel
, height, startxy_sd, arm_sd, movez_r);
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
if (reversexy_sd >= move_sd * movexy_r)
// All steps are in up direction
return _stepcompress_push_delta(
sc, 1, clock_offset, move_sd, start_sv, accel
, height, startxy_sd, arm_sd, movez_r);
// Steps in both up and down direction
int res1 = _stepcompress_push_delta(
sc, 1, clock_offset, reversexy_sd / movexy_r, start_sv, accel
, height, startxy_sd, arm_sd, movez_r);
if (res1 == ERROR_RET)
return res1;
int res2 = _stepcompress_push_delta(
sc, 0, clock_offset, move_sd, start_sv, accel
, height + res1, startxy_sd, arm_sd, movez_r);
if (res2 == ERROR_RET)
return res2;
return res1 + res2;
}
@@ -655,13 +744,16 @@ heap_replace(struct steppersync *ss, uint64_t req_clock)
}
// Find and transmit any scheduled steps prior to the given 'move_clock'
void
int
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
{
// Flush each stepcompress to the specified move_clock
int i;
for (i=0; i<ss->sc_num; i++)
stepcompress_flush(ss->sc_list[i], move_clock);
for (i=0; i<ss->sc_num; i++) {
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
if (ret)
return ret;
}
// Order commands by the reqclock of each pending command
struct list_head msgs;
@@ -701,4 +793,5 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock)
// Transmit commands
if (!list_empty(&msgs))
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
return 0;
}

View File

@@ -8,24 +8,26 @@ import homing
class PrinterStepper:
def __init__(self, printer, config, name):
self.printer = printer
self.config = config
self.name = name
self.mcu_stepper = self.mcu_enable = self.mcu_endstop = None
self.step_dist = config.getfloat('step_distance')
self.step_dist = config.getfloat('step_distance', above=0.)
self.inv_step_dist = 1. / self.step_dist
self.min_stop_interval = 0.
self.homing_speed = config.getfloat('homing_speed', 5.0)
self.homing_speed = config.getfloat('homing_speed', 5.0, above=0.)
self.homing_positive_dir = config.getboolean(
'homing_positive_dir', False)
self.homing_retract_dist = config.getfloat('homing_retract_dist', 5.)
self.homing_stepper_phases = config.getint('homing_stepper_phases', None)
self.homing_endstop_phase = config.getint('homing_endstop_phase', None)
endstop_accuracy = config.getfloat('homing_endstop_accuracy', None)
self.homing_endstop_accuracy = None
self.homing_retract_dist = config.getfloat(
'homing_retract_dist', 5., above=0.)
self.homing_stepper_phases = config.getint(
'homing_stepper_phases', None, minval=0)
endstop_accuracy = config.getfloat(
'homing_endstop_accuracy', None, above=0.)
self.homing_endstop_accuracy = self.homing_endstop_phase = None
if self.homing_stepper_phases:
self.homing_endstop_phase = config.getint(
'homing_endstop_phase', None, minval=0
, maxval=self.homing_stepper_phases-1)
if endstop_accuracy is None:
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
elif self.homing_endstop_phase is not None:
@@ -36,34 +38,40 @@ class PrinterStepper:
endstop_accuracy * self.inv_step_dist))
if self.homing_endstop_accuracy >= self.homing_stepper_phases/2:
logging.info("Endstop for %s is not accurate enough for stepper"
" phase adjustment" % (self.config.section,))
" phase adjustment" % (name,))
self.homing_stepper_phases = None
if printer.mcu.is_fileoutput():
self.homing_endstop_accuracy = self.homing_stepper_phases
self.position_min = self.position_endstop = self.position_max = None
if config.get('endstop_pin', None) is not None:
self.position_min = config.getfloat('position_min', 0.)
self.position_endstop = config.getfloat('position_endstop')
self.position_max = config.getfloat('position_max', 0.)
self.need_motor_enable = True
def set_max_jerk(self, max_halt_velocity, max_accel):
jc = max_halt_velocity / max_accel
inv_max_step_accel = self.step_dist / max_accel
self.min_stop_interval = (math.sqrt(3.*inv_max_step_accel + jc**2)
- math.sqrt(inv_max_step_accel + jc**2))
def build_config(self):
max_error = self.config.getfloat('max_error', 0.000050)
step_pin = self.config.get('step_pin')
dir_pin = self.config.get('dir_pin')
min_stop_interval = max(0., self.min_stop_interval - max_error)
mcu = self.printer.mcu
self.mcu_stepper = mcu.create_stepper(
step_pin, dir_pin, min_stop_interval, max_error)
enable_pin = self.config.get('enable_pin', None)
endstop_pin = config.get('endstop_pin', None)
step_pin = config.get('step_pin')
dir_pin = config.get('dir_pin')
mcu = printer.mcu
self.mcu_stepper = mcu.create_stepper(step_pin, dir_pin)
self.mcu_stepper.set_step_distance(self.step_dist)
enable_pin = config.get('enable_pin', None)
if enable_pin is not None:
self.mcu_enable = mcu.create_digital_out(enable_pin, 0)
endstop_pin = self.config.get('endstop_pin', None)
if endstop_pin is not None:
self.mcu_endstop = mcu.create_endstop(endstop_pin, self.mcu_stepper)
self.mcu_endstop = mcu.create_endstop(endstop_pin)
self.mcu_endstop.add_stepper(self.mcu_stepper)
self.position_min = config.getfloat('position_min', 0.)
self.position_max = config.getfloat(
'position_max', 0., above=self.position_min)
self.position_endstop = config.getfloat('position_endstop')
self.need_motor_enable = True
def _dist_to_time(self, dist, start_velocity, accel):
# Calculate the time it takes to travel a distance with constant accel
time_offset = start_velocity / accel
return math.sqrt(2. * dist / accel + time_offset**2) - time_offset
def set_max_jerk(self, max_halt_velocity, max_accel):
# Calculate the firmware's maximum halt interval time
last_step_time = self._dist_to_time(
self.step_dist, max_halt_velocity, max_accel)
second_last_step_time = self._dist_to_time(
2. * self.step_dist, max_halt_velocity, max_accel)
min_stop_interval = second_last_step_time - last_step_time
self.mcu_stepper.set_min_stop_interval(min_stop_interval)
def motor_enable(self, move_time, enable=0):
if enable and self.need_motor_enable:
mcu_time = self.mcu_stepper.print_to_mcu_time(move_time)
@@ -87,8 +95,7 @@ class PrinterStepper:
pos = self.mcu_stepper.get_mcu_position()
pos %= self.homing_stepper_phases
if self.homing_endstop_phase is None:
logging.info("Setting %s endstop phase to %d" % (
self.config.section, pos))
logging.info("Setting %s endstop phase to %d" % (self.name, pos))
self.homing_endstop_phase = pos
return 0
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
@@ -98,4 +105,4 @@ class PrinterStepper:
raise homing.EndstopError(
"Endstop %s incorrect phase (got %d vs %d)" % (
self.name, pos, self.homing_endstop_phase))
return delta
return delta * self.step_dist

View File

@@ -3,148 +3,171 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging, time
import cartesian, delta
EXTRUDE_DIFF_IGNORE = 1.02
import math, logging
import cartesian, corexy, delta, extruder
# Common suffixes: _d is distance (in mm), _v is velocity (in
# mm/second), _t is time (in seconds), _r is ratio (scalar between
# 0.0 and 1.0)
# mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in
# seconds), _r is ratio (scalar between 0.0 and 1.0)
# Class to track each move request
class Move:
def __init__(self, toolhead, start_pos, end_pos, speed, accel):
def __init__(self, toolhead, start_pos, end_pos, speed):
self.toolhead = toolhead
self.start_pos = tuple(start_pos)
self.end_pos = tuple(end_pos)
self.accel = accel
self.do_calc_junction = True
self.accel = toolhead.max_accel
self.is_kinematic_move = True
self.axes_d = axes_d = [end_pos[i] - start_pos[i] for i in (0, 1, 2, 3)]
if axes_d[2]:
# Move with Z
move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
self.do_calc_junction = False
else:
move_d = math.sqrt(axes_d[0]**2 + axes_d[1]**2)
self.move_d = move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
if not move_d:
# Extrude only move
move_d = abs(axes_d[3])
if not move_d:
# No move
self.move_d = 0.
return
self.do_calc_junction = False
self.move_d = move_d
self.extrude_r = axes_d[3] / move_d
# Junction speeds are velocities squared. The junction_delta
# is the maximum amount of this squared-velocity that can
# change in this move.
self.junction_max = speed**2
self.junction_delta = 2.0 * move_d * accel
self.junction_start_max = 0.
self.move_d = move_d = abs(axes_d[3])
self.is_kinematic_move = False
self.min_move_t = move_d / speed
# Junction speeds are tracked in velocity squared. The
# delta_v2 is the maximum amount of this squared-velocity that
# can change in this move.
self.max_start_v2 = 0.
self.max_cruise_v2 = speed**2
self.delta_v2 = 2.0 * move_d * self.accel
self.max_smoothed_v2 = 0.
self.smooth_delta_v2 = 2.0 * move_d * toolhead.max_accel_to_decel
def limit_speed(self, speed, accel):
self.junction_max = min(self.junction_max, speed**2)
speed2 = speed**2
if speed2 < self.max_cruise_v2:
self.max_cruise_v2 = speed2
self.min_move_t = self.move_d / speed
self.accel = min(self.accel, accel)
self.junction_delta = 2.0 * self.move_d * self.accel
self.delta_v2 = 2.0 * self.move_d * self.accel
self.smooth_delta_v2 = min(self.smooth_delta_v2, self.delta_v2)
def calc_junction(self, prev_move):
if not self.do_calc_junction or not prev_move.do_calc_junction:
axes_d = self.axes_d
prev_axes_d = prev_move.axes_d
if (axes_d[2] or prev_axes_d[2] or self.accel != prev_move.accel
or not self.is_kinematic_move or not prev_move.is_kinematic_move):
return
# Find max junction_start_velocity between two moves
if (self.extrude_r > prev_move.extrude_r * EXTRUDE_DIFF_IGNORE
or prev_move.extrude_r > self.extrude_r * EXTRUDE_DIFF_IGNORE):
# Extrude ratio between moves is too different
return
self.extrude_r = prev_move.extrude_r
# Allow extruder to calculate its maximum junction
extruder_v2 = self.toolhead.extruder.calc_junction(prev_move, self)
# Find max velocity using approximated centripetal velocity as
# described at:
# https://onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/
junction_cos_theta = -((self.axes_d[0] * prev_move.axes_d[0]
+ self.axes_d[1] * prev_move.axes_d[1])
junction_cos_theta = -((axes_d[0] * prev_axes_d[0]
+ axes_d[1] * prev_axes_d[1])
/ (self.move_d * prev_move.move_d))
if junction_cos_theta > 0.999999:
return
junction_cos_theta = max(junction_cos_theta, -0.999999)
sin_theta_d2 = math.sqrt(0.5*(1.0-junction_cos_theta))
R = self.toolhead.junction_deviation * sin_theta_d2 / (1. - sin_theta_d2)
self.junction_start_max = min(
R * self.accel, self.junction_max, prev_move.junction_max
, prev_move.junction_start_max + prev_move.junction_delta)
def process(self, junction_start, junction_cruise, junction_end
, cornering_min, cornering_max):
self.max_start_v2 = min(
R * self.accel, self.max_cruise_v2, prev_move.max_cruise_v2
, extruder_v2, prev_move.max_start_v2 + prev_move.delta_v2)
self.max_smoothed_v2 = min(
self.max_start_v2
, prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2)
def set_junction(self, start_v2, cruise_v2, end_v2):
# Determine accel, cruise, and decel portions of the move distance
inv_junction_delta = 1. / self.junction_delta
accel_r = (junction_cruise-junction_start) * inv_junction_delta
decel_r = (junction_cruise-junction_end) * inv_junction_delta
cruise_r = 1. - accel_r - decel_r
self.accel_r, self.cruise_r, self.decel_r = accel_r, cruise_r, decel_r
inv_delta_v2 = 1. / self.delta_v2
self.accel_r = accel_r = (cruise_v2 - start_v2) * inv_delta_v2
self.decel_r = decel_r = (cruise_v2 - end_v2) * inv_delta_v2
self.cruise_r = cruise_r = 1. - accel_r - decel_r
# Determine move velocities
start_v = math.sqrt(junction_start)
cruise_v = math.sqrt(junction_cruise)
end_v = math.sqrt(junction_end)
self.start_v, self.cruise_v, self.end_v = start_v, cruise_v, end_v
self.corner_min = math.sqrt(cornering_min)
self.corner_max = math.sqrt(cornering_max)
self.start_v = start_v = math.sqrt(start_v2)
self.cruise_v = cruise_v = math.sqrt(cruise_v2)
self.end_v = end_v = math.sqrt(end_v2)
# Determine time spent in each portion of move (time is the
# distance divided by average velocity)
accel_t = accel_r * self.move_d / ((start_v + cruise_v) * 0.5)
cruise_t = cruise_r * self.move_d / cruise_v
decel_t = decel_r * self.move_d / ((end_v + cruise_v) * 0.5)
self.accel_t, self.cruise_t, self.decel_t = accel_t, cruise_t, decel_t
self.accel_t = accel_r * self.move_d / ((start_v + cruise_v) * 0.5)
self.cruise_t = cruise_r * self.move_d / cruise_v
self.decel_t = decel_r * self.move_d / ((end_v + cruise_v) * 0.5)
def move(self):
# Generate step times for the move
next_move_time = self.toolhead.get_next_move_time()
if self.is_kinematic_move:
self.toolhead.kin.move(next_move_time, self)
if self.axes_d[3]:
self.toolhead.extruder.move(next_move_time, self)
self.toolhead.update_move_time(accel_t + cruise_t + decel_t)
self.toolhead.update_move_time(
self.accel_t + self.cruise_t + self.decel_t)
LOOKAHEAD_FLUSH_TIME = 0.250
# Class to track a list of pending move requests and to facilitate
# "look-ahead" across moves to reduce acceleration between moves.
class MoveQueue:
def __init__(self):
def __init__(self, extruder_lookahead):
self.extruder_lookahead = extruder_lookahead
self.queue = []
self.junction_flush = 0.
self.leftover = 0
self.junction_flush = LOOKAHEAD_FLUSH_TIME
def reset(self):
del self.queue[:]
self.leftover = 0
self.junction_flush = LOOKAHEAD_FLUSH_TIME
def set_flush_time(self, flush_time):
self.junction_flush = flush_time
def flush(self, lazy=False):
flush_count = len(self.queue)
move_info = [None] * flush_count
self.junction_flush = LOOKAHEAD_FLUSH_TIME
update_flush_count = lazy
queue = self.queue
flush_count = len(queue)
# Traverse queue from last to first move and determine maximum
# junction speed assuming the robot comes to a complete stop
# after the last move.
next_junction_end = cornering_min = cornering_max = 0.
for i in range(flush_count-1, -1, -1):
move = self.queue[i]
reachable_start = next_junction_end + move.junction_delta
junction_start = min(move.junction_start_max, reachable_start)
junction_cruise = min((junction_start + reachable_start) * .5
, move.junction_max)
move_info[i] = (junction_start, junction_cruise, next_junction_end
, cornering_min, cornering_max)
if reachable_start > junction_start:
cornering_min = junction_start
if junction_start + move.junction_delta > next_junction_end:
cornering_max = junction_cruise
if lazy:
delayed = []
next_end_v2 = next_smoothed_v2 = peak_cruise_v2 = 0.
for i in range(flush_count-1, self.leftover-1, -1):
move = queue[i]
reachable_start_v2 = next_end_v2 + move.delta_v2
start_v2 = min(move.max_start_v2, reachable_start_v2)
reachable_smoothed_v2 = next_smoothed_v2 + move.smooth_delta_v2
smoothed_v2 = min(move.max_smoothed_v2, reachable_smoothed_v2)
if smoothed_v2 < reachable_smoothed_v2:
# It's possible for this move to accelerate
if (smoothed_v2 + move.smooth_delta_v2 > next_smoothed_v2
or delayed):
# This move can decelerate or this is a full accel
# move after a full decel move
if update_flush_count and peak_cruise_v2:
flush_count = i
lazy = False
next_junction_end = junction_start
if lazy:
flush_count = 0
update_flush_count = False
peak_cruise_v2 = min(move.max_cruise_v2, (
smoothed_v2 + reachable_smoothed_v2) * .5)
if delayed:
# Propagate peak_cruise_v2 to any delayed moves
if not update_flush_count and i < flush_count:
for m, ms_v2, me_v2 in delayed:
mc_v2 = min(peak_cruise_v2, ms_v2)
m.set_junction(min(ms_v2, mc_v2), mc_v2
, min(me_v2, mc_v2))
del delayed[:]
if not update_flush_count and i < flush_count:
cruise_v2 = min((start_v2 + reachable_start_v2) * .5
, move.max_cruise_v2, peak_cruise_v2)
move.set_junction(min(start_v2, cruise_v2), cruise_v2
, min(next_end_v2, cruise_v2))
else:
# Delay calculating this move until peak_cruise_v2 is known
delayed.append((move, start_v2, next_end_v2))
next_end_v2 = start_v2
next_smoothed_v2 = smoothed_v2
if update_flush_count:
return
# Allow extruder to do its lookahead
move_count = self.extruder_lookahead(queue, flush_count, lazy)
# Generate step times for all moves ready to be flushed
for i in range(flush_count):
self.queue[i].process(*move_info[i])
for move in queue[:move_count]:
move.move()
# Remove processed moves from the queue
del self.queue[:flush_count]
if self.queue:
self.junction_flush = 2. * self.queue[-1].junction_max
self.leftover = flush_count - move_count
del queue[:move_count]
def add_move(self, move):
self.queue.append(move)
if len(self.queue) == 1:
self.junction_flush = 2. * move.junction_max
return
move.calc_junction(self.queue[-2])
self.junction_flush -= move.junction_delta
self.junction_flush -= move.min_move_t
if self.junction_flush <= 0.:
# There are enough queued moves to return to zero velocity
# from the first move's maximum possible velocity, so at
@@ -159,58 +182,94 @@ class ToolHead:
self.printer = printer
self.reactor = printer.reactor
self.extruder = printer.objects.get('extruder')
if self.extruder is None:
self.extruder = extruder.DummyExtruder()
kintypes = {'cartesian': cartesian.CartKinematics,
'corexy': corexy.CoreXYKinematics,
'delta': delta.DeltaKinematics}
self.kin = config.getchoice('kinematics', kintypes)(printer, config)
self.max_speed = config.getfloat('max_velocity')
self.max_accel = config.getfloat('max_accel')
self.junction_deviation = config.getfloat('junction_deviation', 0.02)
self.move_queue = MoveQueue()
self.max_speed = config.getfloat('max_velocity', above=0.)
self.max_accel = config.getfloat('max_accel', above=0.)
self.max_accel_to_decel = config.getfloat(
'max_accel_to_decel', self.max_accel * 0.5
, above=0., maxval=self.max_accel)
self.junction_deviation = config.getfloat(
'junction_deviation', 0.02, above=0.)
self.move_queue = MoveQueue(self.extruder.lookahead)
self.commanded_pos = [0., 0., 0., 0.]
# Print time tracking
self.buffer_time_high = config.getfloat('buffer_time_high', 5.000)
self.buffer_time_low = config.getfloat('buffer_time_low', 0.150)
self.move_flush_time = config.getfloat('move_flush_time', 0.050)
self.motor_off_delay = config.getfloat('motor_off_time', 60.000)
self.buffer_time_low = config.getfloat(
'buffer_time_low', 1.000, above=0.)
self.buffer_time_high = config.getfloat(
'buffer_time_high', 2.000, above=self.buffer_time_low)
self.buffer_time_start = config.getfloat(
'buffer_time_start', 0.250, above=0.)
self.move_flush_time = config.getfloat(
'move_flush_time', 0.050, above=0.)
self.print_time = 0.
self.last_print_end_time = self.reactor.monotonic()
self.need_check_stall = -1.
self.print_time_stall = 0
self.motor_off_time = self.reactor.NEVER
self.print_stall = 0
self.synch_print_time = True
self.forced_synch = False
self.flush_timer = self.reactor.register_timer(self._flush_handler)
def build_config(self):
self.kin.set_max_jerk(0.005 * self.max_accel, self.max_accel) # XXX
self.kin.build_config()
self.move_queue.set_flush_time(self.buffer_time_high)
# Motor off tracking
self.motor_off_time = config.getfloat(
'motor_off_time', 600.000, minval=0.)
self.motor_off_timer = self.reactor.register_timer(
self._motor_off_handler)
# Determine the maximum velocity a cartesian axis could have
# before cornering. The 8. was determined experimentally.
xy_halt = math.sqrt(8. * self.junction_deviation * self.max_accel)
self.kin.set_max_jerk(xy_halt, self.max_speed, self.max_accel)
self.extruder.set_max_jerk(xy_halt, self.max_speed, self.max_accel)
# Print time tracking
def update_move_time(self, movetime):
self.print_time += movetime
flush_to_time = self.print_time - self.move_flush_time
self.printer.mcu.flush_moves(flush_to_time)
def get_next_move_time(self):
if not self.print_time:
self.print_time = self.buffer_time_low + STALL_TIME
curtime = time.time()
if self.synch_print_time:
curtime = self.reactor.monotonic()
if self.print_time:
buffer_time = self.printer.mcu.get_print_buffer_time(
curtime, self.print_time)
self.print_time += max(self.buffer_time_start - buffer_time, 0.)
if self.forced_synch:
self.print_stall += 1
self.forced_synch = False
else:
self.printer.mcu.set_print_start_time(curtime)
self.print_time = self.buffer_time_start
self._reset_motor_off()
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
self.synch_print_time = False
return self.print_time
def get_last_move_time(self):
self.move_queue.flush()
return self.get_next_move_time()
def reset_motor_off_time(self, eventtime):
self.motor_off_time = eventtime + self.motor_off_delay
def reset_print_time(self):
def _flush_lookahead(self, must_synch=False):
synch_print_time = self.synch_print_time
self.move_queue.flush()
if synch_print_time or must_synch:
self.synch_print_time = True
self.move_queue.set_flush_time(self.buffer_time_high)
self.printer.mcu.flush_moves(self.print_time)
def get_last_move_time(self):
self._flush_lookahead()
return self.get_next_move_time()
def reset_print_time(self):
self._flush_lookahead(must_synch=True)
self.print_time = 0.
self.last_print_end_time = self.reactor.monotonic()
self.need_check_stall = -1.
self.reset_motor_off_time(time.time())
self.reactor.update_timer(self.flush_timer, self.motor_off_time)
self.forced_synch = False
self._reset_motor_off()
def _check_stall(self):
eventtime = self.reactor.monotonic()
if not self.print_time:
# XXX - find better way to flush initial move_queue items
if self.move_queue.queue:
self.reactor.update_timer(self.flush_timer, time.time() + 0.100)
# Building initial queue - make sure to flush on idle input
self.reactor.update_timer(self.flush_timer, eventtime + 0.100)
return
eventtime = time.time()
# Check if there are lots of queued moves and stall if so
while 1:
buffer_time = self.printer.mcu.get_print_buffer_time(
eventtime, self.print_time)
@@ -224,47 +283,58 @@ class ToolHead:
def _flush_handler(self, eventtime):
try:
if not self.print_time:
self.move_queue.flush()
# Input idled before filling lookahead queue - flush it
self._flush_lookahead()
if not self.print_time:
if eventtime >= self.motor_off_time:
self.motor_off()
self.reset_print_time()
self.motor_off_time = self.reactor.NEVER
return self.motor_off_time
return self.reactor.NEVER
print_time = self.print_time
buffer_time = self.printer.mcu.get_print_buffer_time(
eventtime, print_time)
if buffer_time > self.buffer_time_low:
# Running normally - reschedule check
return eventtime + buffer_time - self.buffer_time_low
self.move_queue.flush()
# Under ran low buffer mark - flush lookahead queue
self._flush_lookahead(must_synch=True)
if print_time != self.print_time:
self.print_time_stall += 1
self.dwell(self.buffer_time_low + STALL_TIME)
# Flushed something - retry
self.forced_synch = True
return self.reactor.NOW
if buffer_time > 0.:
# Wait for buffer to fully empty
return eventtime + buffer_time
self.reset_print_time()
return self.motor_off_time
except:
logging.exception("Exception in flush_handler")
self.force_shutdown()
def stats(self, eventtime):
buffer_time = 0.
if self.print_time:
buffer_time = self.printer.mcu.get_print_buffer_time(
eventtime, self.print_time)
return "print_time=%.3f buffer_time=%.3f print_time_stall=%d" % (
self.print_time, buffer_time, self.print_time_stall)
return self.reactor.NEVER
# Motor off timer
def _reset_motor_off(self):
if not self.print_time:
waketime = self.reactor.monotonic() + self.motor_off_time
else:
waketime = self.reactor.NEVER
self.reactor.update_timer(self.motor_off_timer, waketime)
def _motor_off_handler(self, eventtime):
try:
self.motor_off()
self.reset_print_time()
except:
logging.exception("Exception in motor_off_handler")
self.force_shutdown()
return self.reactor.NEVER
# Movement commands
def get_position(self):
return list(self.commanded_pos)
def set_position(self, newpos):
self.move_queue.flush()
self._flush_lookahead()
self.commanded_pos[:] = newpos
self.kin.set_position(newpos)
def move(self, newpos, speed):
speed = min(speed, self.max_speed)
move = Move(self, self.commanded_pos, newpos, speed, self.max_accel)
move = Move(self, self.commanded_pos, newpos, speed)
if not move.move_d:
return
if move.is_kinematic_move:
self.kin.check_move(move)
if move.axes_d[3]:
self.extruder.check_move(move)
@@ -285,9 +355,30 @@ class ToolHead:
self.extruder.motor_off(last_move_time)
self.dwell(STALL_TIME)
logging.debug('; Max time of %f' % (last_move_time,))
def wait_moves(self):
self._flush_lookahead()
eventtime = self.reactor.monotonic()
while self.print_time:
eventtime = self.reactor.pause(eventtime + 0.100)
def query_endstops(self):
last_move_time = self.get_last_move_time()
return self.kin.query_endstops(last_move_time)
# Misc commands
def stats(self, eventtime):
buffer_time = 0.
print_time = self.print_time
if print_time:
is_active = True
buffer_time = max(0., self.printer.mcu.get_print_buffer_time(
eventtime, print_time))
else:
is_active = eventtime < self.last_print_end_time + 60.
return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
print_time, buffer_time, self.print_stall)
def force_shutdown(self):
try:
self.printer.mcu.force_shutdown()
self.move_queue.reset()
self.reset_print_time()
except:
logging.exception("Exception in force_shutdown")

View File

@@ -3,8 +3,8 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, pty, fcntl, termios, signal
import sys, os, pty, fcntl, termios, signal, logging
import subprocess, traceback, shlex
# Return the SIGINT interrupt handler back to the OS default
def fix_sigint():
@@ -36,3 +36,34 @@ def create_pty(ptyname):
old[3] = old[3] & ~termios.ECHO
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
return mfd
def get_cpu_info():
try:
f = open('/proc/cpuinfo', 'rb')
data = f.read()
f.close()
except OSError:
logging.debug("Exception on read /proc/cpuinfo: %s" % (
traceback.format_exc(),))
return "?"
lines = [l.split(':', 1) for l in data.split('\n')]
lines = [(l[0].strip(), l[1].strip()) for l in lines if len(l) == 2]
core_count = [k for k, v in lines].count("processor")
model_name = dict(lines).get("model name", "?")
return "%d core %s" % (core_count, model_name)
def get_git_version():
# Obtain version info from "git" program
gitdir = os.path.join(sys.path[0], '..', '.git')
if not os.path.exists(gitdir):
logging.debug("No '.git' file/directory found")
return "?"
prog = "git --git-dir=%s describe --tags --long --dirty" % (gitdir,)
try:
process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
output = process.communicate()[0]
retcode = process.poll()
except OSError:
logging.debug("Exception on run: %s" % (traceback.format_exc(),))
return "?"
return output.strip()

View File

@@ -10,3 +10,7 @@ The cmsis-sam3x8e directory contains code from the Arduino project:
version 1.5.1 (extracted on 20160608). It has been modified to
compile with gcc's LTO feature. See cmsis-sam3x8e.patch for the
modifications.
The hub-ctrl directory contains code from:
https://github.com/codazoda/hub-ctrl.c/
revision 42095e522859059e8a5f4ec05c1e3def01a870a9.

412
lib/hub-ctrl/hub-ctrl.c Normal file
View File

@@ -0,0 +1,412 @@
/*
* Copyright (C) 2006 Free Software Initiative of Japan
*
* Author: NIIBE Yutaka <gniibe at fsij.org>
*
* This file can be distributed under the terms and conditions of the
* GNU General Public License version 2 (or later).
*
*/
#include <errno.h>
#include <usb.h>
#include <stdio.h>
#include <string.h>
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE)
#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER)
#define USB_PORT_FEAT_POWER 8
#define USB_PORT_FEAT_INDICATOR 22
#define USB_DIR_IN 0x80 /* to host */
#define COMMAND_SET_NONE 0
#define COMMAND_SET_LED 1
#define COMMAND_SET_POWER 2
#define HUB_LED_GREEN 2
static void
usage (const char *progname)
{
fprintf (stderr,
"Usage: %s [{-h HUBNUM | -b BUSNUM -d DEVNUM}] \\\n"
" [-P PORT] [{-p [VALUE]|-l [VALUE]}]\n", progname);
}
static void
exit_with_usage (const char *progname)
{
usage (progname);
exit (1);
}
#define HUB_CHAR_LPSM 0x0003
#define HUB_CHAR_PORTIND 0x0080
struct usb_hub_descriptor {
unsigned char bDescLength;
unsigned char bDescriptorType;
unsigned char bNbrPorts;
unsigned char wHubCharacteristics[2];
unsigned char bPwrOn2PwrGood;
unsigned char bHubContrCurrent;
unsigned char data[0];
};
#define CTRL_TIMEOUT 1000
#define USB_STATUS_SIZE 4
#define MAX_HUBS 128
struct hub_info {
int busnum, devnum;
struct usb_device *dev;
int nport;
int indicator_support;
};
static struct hub_info hubs[MAX_HUBS];
static int number_of_hubs_with_feature;
static void
hub_port_status (usb_dev_handle *uh, int nport)
{
int i;
printf(" Hub Port Status:\n");
for (i = 0; i < nport; i++)
{
char buf[USB_STATUS_SIZE];
int ret;
ret = usb_control_msg (uh,
USB_ENDPOINT_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_GET_STATUS,
0, i + 1,
buf, USB_STATUS_SIZE,
CTRL_TIMEOUT);
if (ret < 0)
{
fprintf (stderr,
"cannot read port %d status, %s (%d)\n",
i + 1, strerror(errno), errno);
break;
}
printf(" Port %d: %02x%02x.%02x%02x", i + 1,
buf[3], buf [2],
buf[1], buf [0]);
printf("%s%s%s%s%s",
(buf[2] & 0x10) ? " C_RESET" : "",
(buf[2] & 0x08) ? " C_OC" : "",
(buf[2] & 0x04) ? " C_SUSPEND" : "",
(buf[2] & 0x02) ? " C_ENABLE" : "",
(buf[2] & 0x01) ? " C_CONNECT" : "");
printf("%s%s%s%s%s%s%s%s%s%s\n",
(buf[1] & 0x10) ? " indicator" : "",
(buf[1] & 0x08) ? " test" : "",
(buf[1] & 0x04) ? " highspeed" : "",
(buf[1] & 0x02) ? " lowspeed" : "",
(buf[1] & 0x01) ? " power" : "",
(buf[0] & 0x10) ? " RESET" : "",
(buf[0] & 0x08) ? " oc" : "",
(buf[0] & 0x04) ? " suspend" : "",
(buf[0] & 0x02) ? " enable" : "",
(buf[0] & 0x01) ? " connect" : "");
}
}
static int
usb_find_hubs (int listing, int verbose, int busnum, int devnum, int hub)
{
struct usb_bus *busses;
struct usb_bus *bus;
number_of_hubs_with_feature = 0;
busses = usb_get_busses();
if (busses == NULL)
{
perror ("failed to access USB");
return -1;
}
for (bus = busses; bus; bus = bus->next)
{
struct usb_device *dev;
for (dev = bus->devices; dev; dev = dev->next)
{
usb_dev_handle *uh;
int print = 0;
if (dev->descriptor.bDeviceClass != USB_CLASS_HUB)
continue;
if (listing
|| (verbose
&& ((atoi (bus->dirname) == busnum && dev->devnum == devnum)
|| hub == number_of_hubs_with_feature)))
print = 1;
uh = usb_open (dev);
if (uh != NULL)
{
char buf[1024];
int len;
int nport;
struct usb_hub_descriptor *uhd = (struct usb_hub_descriptor *)buf;
if ((len = usb_control_msg (uh, USB_DIR_IN | USB_RT_HUB,
USB_REQ_GET_DESCRIPTOR,
USB_DT_HUB << 8, 0,
buf, sizeof (buf), CTRL_TIMEOUT))
> sizeof (struct usb_hub_descriptor))
{
if (!(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND)
&& (uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM) >= 2)
continue;
if (print)
printf ("Hub #%d at %s:%03d\n",
number_of_hubs_with_feature,
bus->dirname, dev->devnum);
switch ((uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM))
{
case 0:
if (print)
fprintf (stderr, " INFO: ganged switching.\n");
break;
case 1:
if (print)
fprintf (stderr, " INFO: individual power switching.\n");
break;
case 2:
case 3:
if (print)
fprintf (stderr, " WARN: No power switching.\n");
break;
}
if (print
&& !(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND))
fprintf (stderr, " WARN: Port indicators are NOT supported.\n");
}
else
{
perror ("Can't get hub descriptor");
usb_close (uh);
continue;
}
nport = buf[2];
hubs[number_of_hubs_with_feature].busnum = atoi (bus->dirname);
hubs[number_of_hubs_with_feature].devnum = dev->devnum;
hubs[number_of_hubs_with_feature].dev = dev;
hubs[number_of_hubs_with_feature].indicator_support =
(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND)? 1 : 0;
hubs[number_of_hubs_with_feature].nport = nport;
number_of_hubs_with_feature++;
if (verbose)
hub_port_status (uh, nport);
usb_close (uh);
}
}
}
return number_of_hubs_with_feature;
}
int
get_hub (int busnum, int devnum)
{
int i;
for (i = 0; i < number_of_hubs_with_feature; i++)
if (hubs[i].busnum == busnum && hubs[i].devnum == devnum)
return i;
return -1;
}
/*
* HUB-CTRL - program to control port power/led of USB hub
*
* # hub-ctrl // List hubs available
* # hub-ctrl -P 1 // Power off at port 1
* # hub-ctrl -P 1 -p 1 // Power on at port 1
* # hub-ctrl -P 2 -l // LED on at port 1
*
* Requirement: USB hub which implements port power control / indicator control
*
* Work fine:
* Elecom's U2H-G4S: www.elecom.co.jp (indicator depends on power)
* 04b4:6560
*
* Sanwa Supply's USB-HUB14GPH: www.sanwa.co.jp (indicators don't)
*
* Targus, Inc.'s PAUH212: www.targus.com (indicators don't)
* 04cc:1521
*
* Hawking Technology's UH214: hawkingtech.com (indicators don't)
*
*/
int
main (int argc, const char *argv[])
{
int busnum = 0, devnum = 0;
int cmd = COMMAND_SET_NONE;
int port = 1;
int value = 0;
int request, feature, index;
int result = 0;
int listing = 0;
int verbose = 0;
int hub = -1;
usb_dev_handle *uh = NULL;
int i;
if (argc == 1)
listing = 1;
for (i = 1; i < argc; i++)
if (argv[i][0] == '-')
switch (argv[i][1])
{
case 'h':
if (++i >= argc || busnum > 0 || devnum > 0)
exit_with_usage (argv[0]);
hub = atoi (argv[i]);
break;
case 'b':
if (++i >= argc || hub >= 0)
exit_with_usage (argv[0]);
busnum = atoi (argv[i]);
break;
case 'd':
if (++i >= argc || hub >= 0)
exit_with_usage (argv[0]);
devnum = atoi (argv[i]);
break;
case 'P':
if (++i >= argc)
exit_with_usage (argv[0]);
port = atoi (argv[i]);
break;
case 'l':
if (cmd != COMMAND_SET_NONE)
exit_with_usage (argv[0]);
if (++i < argc)
value = atoi (argv[i]);
else
value = HUB_LED_GREEN;
cmd = COMMAND_SET_LED;
break;
case 'p':
if (cmd != COMMAND_SET_NONE)
exit_with_usage (argv[0]);
if (++i < argc)
value = atoi (argv[i]);
else
value= 0;
cmd = COMMAND_SET_POWER;
break;
case 'v':
verbose = 1;
if (argc == 2)
listing = 1;
break;
default:
exit_with_usage (argv[0]);
}
else
exit_with_usage (argv[0]);
if ((busnum > 0 && devnum <= 0) || (busnum <= 0 && devnum > 0))
/* BUS is specified, but DEV is'nt, or ... */
exit_with_usage (argv[0]);
/* Default is the hub #0 */
if (hub < 0 && busnum == 0)
hub = 0;
/* Default is POWER */
if (cmd == COMMAND_SET_NONE)
cmd = COMMAND_SET_POWER;
usb_init ();
usb_find_busses ();
usb_find_devices ();
if (usb_find_hubs (listing, verbose, busnum, devnum, hub) <= 0)
{
fprintf (stderr, "No hubs found.\n");
exit (1);
}
if (listing)
exit (0);
if (hub < 0)
hub = get_hub (busnum, devnum);
if (hub >= 0 && hub < number_of_hubs_with_feature)
uh = usb_open (hubs[hub].dev);
if (uh == NULL)
{
fprintf (stderr, "Device not found.\n");
result = 1;
}
else
{
if (cmd == COMMAND_SET_POWER)
if (value)
{
request = USB_REQ_SET_FEATURE;
feature = USB_PORT_FEAT_POWER;
index = port;
}
else
{
request = USB_REQ_CLEAR_FEATURE;
feature = USB_PORT_FEAT_POWER;
index = port;
}
else
{
request = USB_REQ_SET_FEATURE;
feature = USB_PORT_FEAT_INDICATOR;
index = (value << 8) | port;
}
if (verbose)
printf ("Send control message (REQUEST=%d, FEATURE=%d, INDEX=%d)\n",
request, feature, index);
if (usb_control_msg (uh, USB_RT_PORT, request, feature, index,
NULL, 0, CTRL_TIMEOUT) < 0)
{
perror ("failed to control.\n");
result = 1;
}
if (verbose)
hub_port_status (uh, hubs[hub].nport);
usb_close (uh);
}
exit (result);
}

View File

@@ -5,21 +5,22 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, os, pty, select, fcntl, termios, traceback, errno
import sys, optparse, time, os, pty, fcntl, termios, errno
import pysimulavr
SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
SIMULAVR_FREQ = 10**9
# Class to read serial data from AVR serial transmit pin.
class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
def __init__(self, baud):
def __init__(self, baud, terminal):
pysimulavr.Pin.__init__(self)
pysimulavr.PySimulationMember.__init__(self)
self.terminal = terminal
self.sc = pysimulavr.SystemClock.Instance()
self.delay = 10**9 / baud
self.delay = SIMULAVR_FREQ / baud
self.current = 0
self.pos = -1
self.queue = ""
def SetInState(self, pin):
pysimulavr.Pin.SetInState(self, pin)
self.state = pin.outState
@@ -33,32 +34,33 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
if self.pos == 1:
return int(self.delay * 1.5)
if self.pos >= SERIALBITS:
self.queue += chr((self.current >> 1) & 0xff)
data = chr((self.current >> 1) & 0xff)
self.terminal.write(data)
self.pos = -1
self.current = 0
return -1
return self.delay
def popChars(self):
d = self.queue
self.queue = ""
return d
# Class to send serial data to AVR serial receive pin.
class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
MAX_QUEUE = 64
def __init__(self, baud):
def __init__(self, baud, terminal):
pysimulavr.Pin.__init__(self)
pysimulavr.PySimulationMember.__init__(self)
self.terminal = terminal
self.SetPin('H')
self.sc = pysimulavr.SystemClock.Instance()
self.delay = 10**9 / baud
self.delay = SIMULAVR_FREQ / baud
self.current = 0
self.pos = 0
self.queue = ""
self.sc.Add(self)
def DoStep(self, trueHwStep):
if not self.pos:
if not self.queue:
return -1
data = self.terminal.read()
if not data:
return self.delay * 100
self.queue += data
self.current = (ord(self.queue[0]) << 1) | 0x200
self.queue = self.queue[1:]
newstate = 'L'
@@ -69,15 +71,6 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
if self.pos >= SERIALBITS:
self.pos = 0
return self.delay
def needChars(self):
if len(self.queue) > self.MAX_QUEUE / 2:
return 0
return self.MAX_QUEUE - len(self.queue)
def pushChars(self, c):
queueEmpty = not self.queue
self.queue += c
if queueEmpty:
self.sc.Add(self)
# Support for creating VCD trace files
class Tracing:
@@ -108,6 +101,47 @@ class Tracing:
if self.dman is not None:
self.dman.stopApplication()
# Pace the simulation scaled to real time
class Pacing(pysimulavr.PySimulationMember):
def __init__(self, rate):
pysimulavr.PySimulationMember.__init__(self)
self.sc = pysimulavr.SystemClock.Instance()
self.pacing_rate = 1. / (rate * SIMULAVR_FREQ)
self.rel_time = self.next_rel_time = time.time()
self.rel_clock = self.next_rel_clock = self.sc.GetCurrentTime()
self.delay = SIMULAVR_FREQ / 10000
self.sc.Add(self)
def DoStep(self, trueHwStep):
curtime = time.time()
clock = self.sc.GetCurrentTime()
clock_diff = clock - self.rel_clock
time_diff = curtime - self.rel_time
offset = clock_diff * self.pacing_rate - time_diff
if offset > 0.000050:
time.sleep(offset)
if clock_diff > self.delay * 20:
self.rel_clock = self.next_rel_clock
self.rel_time = self.next_rel_time
self.next_rel_clock = clock
self.next_rel_time = curtime
return self.delay
# Forward data from a terminal device to the serial port pins
class TerminalIO:
def __init__(self):
self.fd = -1
def run(self, fd):
self.fd = fd
def write(self, data):
os.write(self.fd, data)
def read(self):
try:
return os.read(self.fd, 64)
except os.error, e:
if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK):
pysimulavr.SystemClock.Instance().stop()
return ""
# Support for creating a pseudo-tty for emulating a serial port
def create_pty(ptyname):
mfd, sfd = pty.openpty()
@@ -130,6 +164,8 @@ def main():
default="atmega644", help="type of AVR machine to simulate")
opts.add_option("-s", "--speed", type="int", dest="speed", default=8000000,
help="machine speed")
opts.add_option("-r", "--rate", type="float", dest="pacing_rate", default=0.,
help="real-time pacing rate")
opts.add_option("-b", "--baud", type="int", dest="baud", default=38400,
help="baud rate of the emulated serial port")
opts.add_option("-t", "--trace", type="string", dest="trace",
@@ -154,18 +190,26 @@ def main():
trace = Tracing(options.tracefile, options.trace)
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
dev.Load(elffile)
dev.SetClockFreq(10**9 / speed)
dev.SetClockFreq(SIMULAVR_FREQ / speed)
sc.Add(dev)
pysimulavr.cvar.sysConHandler.SetUseExit(False)
trace.load_options()
# Do optional real-time pacing
if options.pacing_rate:
pacing = Pacing(options.pacing_rate)
# Setup terminal
io = TerminalIO()
# Setup rx pin
rxpin = SerialRxPin(baud)
rxpin = SerialRxPin(baud, io)
net = pysimulavr.Net()
net.Add(rxpin)
net.Add(dev.GetPin("D1"))
# Setup tx pin
txpin = SerialTxPin(baud)
txpin = SerialTxPin(baud, io)
net2 = pysimulavr.Net()
net2.Add(dev.GetPin("D0"))
net2.Add(txpin)
@@ -183,27 +227,9 @@ def main():
# Run loop
try:
io.run(fd)
trace.start()
while 1:
starttime = sc.GetCurrentTime()
r = sc.RunTimeRange(speed/1000)
endtime = sc.GetCurrentTime()
if starttime == endtime:
break
d = rxpin.popChars()
if d:
os.write(fd, d)
txsize = txpin.needChars()
if txsize:
res = select.select([fd], [], [], 0)
if res[0]:
try:
d = os.read(fd, txsize)
except os.error, e:
if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
continue
break
txpin.pushChars(d)
sc.RunTimeRange(0x7fff0000ffff0000)
trace.finish()
finally:
os.unlink(ptyname)

View File

@@ -172,6 +172,9 @@ def main():
if '+' in ref:
# Inter-function jump.
pass
elif insn.startswith('ld') or insn.startswith('st'):
# memory access
pass
elif insn in ('rjmp', 'jmp', 'brne', 'brcs'):
# Tail call
cur.noteCall(insnaddr, calladdr, 0)
@@ -190,7 +193,7 @@ def main():
funcsbyname[funcnameroot] = info
mainfunc = funcsbyname.get('sched_main')
cmdfunc = funcsbyname.get('command_task')
eventfunc = funcsbyname.get('__vector_13')
eventfunc = funcsbyname.get('__vector_13', funcsbyname.get('__vector_17'))
for funcnameroot, info in funcsbyname.items():
if (funcnameroot.startswith('_DECL_taskfuncs_')
or funcnameroot.startswith('_DECL_initfuncs_')
@@ -204,7 +207,7 @@ def main():
numparams = int(datalines[info.funcaddr][2], 16)
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
cmdfunc.noteCall(0, f.funcaddr, stackusage)
if funcnameroot.endswith('_event'):
if funcnameroot.endswith('_event') and eventfunc is not None:
eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage + 2)
# Calculate maxstackusage

View File

@@ -8,7 +8,7 @@ import optparse, datetime
import matplotlib.pyplot as plt, matplotlib.dates as mdates
MAXBANDWIDTH=25000.
MAXBUFFER=5.
MAXBUFFER=2.
def parse_log(logname):
f = open(logname, 'rb')
@@ -27,10 +27,32 @@ def parse_log(logname):
f.close()
return out
def find_print_restarts(data):
last_print_time = 0.
print_resets = []
for d in data:
print_time = float(d.get('print_time', last_print_time))
if print_time < last_print_time:
print_resets.append(d['#sampletime'])
last_print_time = 0.
else:
last_print_time = print_time
sample_resets = {}
for d in data:
st = d['#sampletime']
while print_resets and st > print_resets[0]:
print_resets.pop(0)
if not print_resets:
break
if st + 2. * MAXBUFFER > print_resets[0]:
sample_resets[st] = 1
return sample_resets
def plot_mcu(data, maxbw, outname):
# Generate data for plot
basetime = lasttime = data[0]['#sampletime']
lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
sample_resets = find_print_restarts(data)
times = []
bwdeltas = []
loads = []
@@ -49,7 +71,7 @@ def plot_mcu(data, maxbw, outname):
load = 0.
pt = float(d['print_time'])
hb = float(d['buffer_time'])
if pt <= 2*MAXBUFFER or hb >= MAXBUFFER:
if pt <= 2. * MAXBUFFER or hb >= MAXBUFFER or st in sample_resets:
hb = 0.
else:
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
@@ -63,12 +85,12 @@ def plot_mcu(data, maxbw, outname):
# Build plot
fig, ax1 = plt.subplots()
ax1.set_title("MCU bandwidth and load utilization")
ax1.set_xlabel('Time (UTC)')
ax1.set_xlabel('Time')
ax1.set_ylabel('Usage (%)')
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth')
ax1.plot_date(times, loads, 'r', label='MCU load')
#ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
ax1.legend()
ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
ax1.legend(loc='best')
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
#plt.gcf().autofmt_xdate()
ax1.grid(True)

102
scripts/install-octopi.sh Executable file
View File

@@ -0,0 +1,102 @@
#!/bin/bash
# This script installs Klipper on a Raspberry Pi machine running the
# OctoPi distribution.
PYTHONDIR="${HOME}/klippy-env"
# Step 1: Install system packages
install_packages()
{
# Packages for python cffi
PKGLIST="libffi-dev"
# kconfig requirements
PKGLIST="${PKGLIST} libncurses-dev"
# hub-ctrl
PKGLIST="${PKGLIST} libusb-dev"
# AVR chip installation and building
PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc"
# ARM chip installation and building
PKGLIST="${PKGLIST} bossa-cli libnewlib-arm-none-eabi"
# Update system package info
report_status "Running apt-get update..."
sudo apt-get update
# Install desired packages
report_status "Installing packages..."
sudo apt-get install --yes ${PKGLIST}
}
# Step 2: Create python virtual environment
create_virtualenv()
{
report_status "Updating python virtual environment..."
# Create virtualenv if it doesn't already exist
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
# Install/update dependencies
${PYTHONDIR}/bin/pip install cffi==1.6.0 pyserial==3.2.1 greenlet==0.4.10
}
# Step 3: Install startup script
install_script()
{
report_status "Installing system start script..."
sudo cp "${SRCDIR}/scripts/klipper-start.sh" /etc/init.d/klipper
sudo update-rc.d klipper defaults
}
# Step 4: Install startup script config
install_config()
{
DEFAULTS_FILE=/etc/default/klipper
[ -f $DEFAULTS_FILE ] && return
report_status "Installing system start configuration..."
sudo /bin/sh -c "cat > $DEFAULTS_FILE" <<EOF
# Configuration for /etc/init.d/klipper
KLIPPY_USER=$USER
KLIPPY_EXEC=${PYTHONDIR}/bin/python
KLIPPY_ARGS="${SRCDIR}/klippy/klippy.py ${HOME}/printer.cfg -l /tmp/klippy.log"
EOF
}
# Step 5: Start host software
start_software()
{
report_status "Launching Klipper host software..."
sudo /etc/init.d/klipper restart
}
# Helper functions
report_status()
{
echo -e "\n\n###### $1"
}
verify_ready()
{
if [ "$EUID" -eq 0 ]; then
echo "This script must not run as root"
exit -1
fi
}
# Force script to exit if an error occurs
set -e
# Find SRCDIR from the pathname of this script
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
# Run installation steps defined above
verify_ready
install_packages
create_virtualenv
install_script
install_config
start_software

54
scripts/klipper-start.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/bin/sh
# System startup script for Klipper 3d-printer host code
### BEGIN INIT INFO
# Provides: klipper
# Required-Start: $local_fs
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Klipper daemon
# Description: Starts the Klipper daemon.
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DESC="klipper daemon"
NAME="klipper"
DEFAULTS_FILE=/etc/default/klipper
PIDFILE=/var/run/klipper.pid
. /lib/lsb/init-functions
# Read defaults file
[ -r $DEFAULTS_FILE ] && . $DEFAULTS_FILE
case "$1" in
start) log_daemon_msg "Starting klipper" $NAME
start-stop-daemon --start --quiet --exec $KLIPPY_EXEC \
--background --pidfile $PIDFILE --make-pidfile \
--chuid $KLIPPY_USER --user $KLIPPY_USER \
-- $KLIPPY_ARGS
log_end_msg $?
;;
stop) log_daemon_msg "Stopping klipper" $NAME
killproc -p $PIDFILE $KLIPPY_EXEC
RETVAL=$?
[ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE
log_end_msg $RETVAL
;;
restart) log_daemon_msg "Restarting klipper" $NAME
$0 stop
$0 start
;;
reload|force-reload)
log_daemon_msg "Reloading configuration not supported" $NAME
log_end_msg 1
;;
status)
status_of_proc -p $PIDFILE $KLIPPY_EXEC $NAME && exit 0 || exit $?
;;
*) log_action_msg "Usage: /etc/init.d/klipper {start|stop|status|restart|reload|force-reload}"
exit 2
;;
esac
exit 0

View File

@@ -4,7 +4,7 @@
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "basecmd.h" // alloc_oid
#include "basecmd.h" // oid_alloc
#include "board/gpio.h" // struct gpio_adc
#include "board/irq.h" // irq_disable
#include "command.h" // DECL_COMMAND
@@ -50,7 +50,7 @@ analog_in_event(struct timer *timer)
void
command_config_analog_in(uint32_t *args)
{
struct analog_in *a = alloc_oid(
struct analog_in *a = oid_alloc(
args[0], command_config_analog_in, sizeof(*a));
a->timer.func = analog_in_event;
a->pin = gpio_adc_setup(args[1]);
@@ -61,7 +61,7 @@ DECL_COMMAND(command_config_analog_in, "config_analog_in oid=%c pin=%u");
void
command_query_analog_in(uint32_t *args)
{
struct analog_in *a = lookup_oid(args[0], command_config_analog_in);
struct analog_in *a = oid_lookup(args[0], command_config_analog_in);
sched_del_timer(&a->timer);
gpio_adc_cancel_sample(a->pin);
a->next_begin_time = args[1];
@@ -74,7 +74,7 @@ command_query_analog_in(uint32_t *args)
a->max_value = args[6];
if (! a->sample_count)
return;
sched_timer(&a->timer);
sched_add_timer(&a->timer);
}
DECL_COMMAND(command_query_analog_in,
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"

View File

@@ -5,8 +5,7 @@ CROSS_PREFIX=avr-
dirs-y += src/avr lib/pjrc_usb_serial
CFLAGS-y += -Os -mmcu=$(CONFIG_MCU) -DF_CPU=$(CONFIG_CLOCK_FREQ)
LDFLAGS-y += -Wl,--relax
CFLAGS-y += -mmcu=$(CONFIG_MCU)
# Add avr source files
src-y += avr/main.c avr/timer.c avr/gpio.c avr/misc.c
@@ -14,9 +13,16 @@ src-$(CONFIG_AVR_WATCHDOG) += avr/watchdog.c
src-$(CONFIG_AVR_USBSERIAL) += avr/usbserial.c ../lib/pjrc_usb_serial/usb_serial.c
src-$(CONFIG_AVR_SERIAL) += avr/serial.c
# Suppress broken "misspelled signal handler" warnings on gcc 4.8.1
CFLAGS_klipper.o := $(if $(filter 4.8.1, $(shell $(CC) -dumpversion)), -w)
# Build the additional hex output file
target-y += $(OUT)klipper.elf.hex
$(OUT)klipper.elf.hex: $(OUT)klipper.elf
@echo " Creating hex file $@"
$(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
flash: $(OUT)klipper.elf.hex
@echo " Flashing $(FLASH_DEVICE) via avrdude"
$(Q)avrdude -p$(CONFIG_MCU) -cwiring -P"$(FLASH_DEVICE)" -D -U"flash:w:$(OUT)klipper.elf.hex:i"

View File

@@ -1,6 +1,6 @@
// GPIO functions on AVR.
//
// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@@ -14,7 +14,7 @@
/****************************************************************
* AVR chip definitions
* General Purpose Input Output (GPIO) pins
****************************************************************/
#define GPIO(PORT, NUM) (((PORT)-'A') * 8 + (NUM))
@@ -44,6 +44,67 @@ struct gpio_digital_regs {
#define GPIO2REGS(pin) \
((struct gpio_digital_regs*)READP(digital_regs[GPIO2PORT(pin)]))
struct gpio_out
gpio_out_setup(uint8_t pin, uint8_t val)
{
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
goto fail;
struct gpio_digital_regs *regs = GPIO2REGS(pin);
if (! regs)
goto fail;
uint8_t bit = GPIO2BIT(pin);
irqstatus_t flag = irq_save();
regs->out = val ? (regs->out | bit) : (regs->out & ~bit);
regs->mode |= bit;
irq_restore(flag);
return (struct gpio_out){ .regs=regs, .bit=bit };
fail:
shutdown("Not an output pin");
}
void
gpio_out_toggle(struct gpio_out g)
{
g.regs->in = g.bit;
}
void
gpio_out_write(struct gpio_out g, uint8_t val)
{
irqstatus_t flag = irq_save();
g.regs->out = val ? (g.regs->out | g.bit) : (g.regs->out & ~g.bit);
irq_restore(flag);
}
struct gpio_in
gpio_in_setup(uint8_t pin, int8_t pull_up)
{
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
goto fail;
struct gpio_digital_regs *regs = GPIO2REGS(pin);
if (! regs)
goto fail;
uint8_t bit = GPIO2BIT(pin);
irqstatus_t flag = irq_save();
regs->out = pull_up > 0 ? (regs->out | bit) : (regs->out & ~bit);
regs->mode &= ~bit;
irq_restore(flag);
return (struct gpio_in){ .regs=regs, .bit=bit };
fail:
shutdown("Not an input pin");
}
uint8_t
gpio_in_read(struct gpio_in g)
{
return !!(g.regs->in & g.bit);
}
/****************************************************************
* Hardware Pulse Width Modulation (PWM) pins
****************************************************************/
struct gpio_pwm_info {
volatile void *ocr;
volatile uint8_t *rega, *regb;
@@ -101,116 +162,21 @@ static const uint8_t pwm_pins[ARRAY_SIZE(pwm_regs)] PROGMEM = {
#endif
};
static const uint8_t adc_pins[] PROGMEM = {
#if CONFIG_MACH_atmega168
GPIO('C', 0), GPIO('C', 1), GPIO('C', 2), GPIO('C', 3),
GPIO('C', 4), GPIO('C', 5), GPIO('E', 0), GPIO('E', 1),
#elif CONFIG_MACH_atmega644p
GPIO('A', 0), GPIO('A', 1), GPIO('A', 2), GPIO('A', 3),
GPIO('A', 4), GPIO('A', 5), GPIO('A', 6), GPIO('A', 7),
#elif CONFIG_MACH_at90usb1286
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
GPIO('K', 0), GPIO('K', 1), GPIO('K', 2), GPIO('K', 3),
GPIO('K', 4), GPIO('K', 5), GPIO('K', 6), GPIO('K', 7),
#endif
};
#if CONFIG_MACH_atmega168
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
#elif CONFIG_MACH_atmega644p
static const uint8_t SS = GPIO('B', 4), SCK = GPIO('B', 7), MOSI = GPIO('B', 5);
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
#endif
static const uint8_t ADMUX_DEFAULT = 0x40;
/****************************************************************
* gpio functions
****************************************************************/
struct gpio_out
gpio_out_setup(uint8_t pin, uint8_t val)
{
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
goto fail;
struct gpio_digital_regs *regs = GPIO2REGS(pin);
if (! regs)
goto fail;
uint8_t bit = GPIO2BIT(pin);
irqstatus_t flag = irq_save();
regs->out = val ? (regs->out | bit) : (regs->out & ~bit);
regs->mode |= bit;
irq_restore(flag);
return (struct gpio_out){ .regs=regs, .bit=bit };
fail:
shutdown("Not an output pin");
}
void gpio_out_toggle(struct gpio_out g)
{
g.regs->in = g.bit;
}
void
gpio_out_write(struct gpio_out g, uint8_t val)
{
irqstatus_t flag = irq_save();
g.regs->out = val ? (g.regs->out | g.bit) : (g.regs->out & ~g.bit);
irq_restore(flag);
}
struct gpio_in
gpio_in_setup(uint8_t pin, int8_t pull_up)
{
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
goto fail;
struct gpio_digital_regs *regs = GPIO2REGS(pin);
if (! regs)
goto fail;
uint8_t bit = GPIO2BIT(pin);
irqstatus_t flag = irq_save();
regs->out = pull_up > 0 ? (regs->out | bit) : (regs->out & ~bit);
regs->mode &= ~bit;
irq_restore(flag);
return (struct gpio_in){ .regs=regs, .bit=bit };
fail:
shutdown("Not an input pin");
}
uint8_t
gpio_in_read(struct gpio_in g)
{
return !!(g.regs->in & g.bit);
}
void
gpio_pwm_write(struct gpio_pwm g, uint8_t val)
{
if (g.size8) {
*(volatile uint8_t*)g.reg = val;
} else {
irqstatus_t flag = irq_save();
*(volatile uint16_t*)g.reg = val;
irq_restore(flag);
}
}
struct gpio_pwm
gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
{
// Find pin in pwm_pins table
uint8_t chan;
for (chan=0; chan<ARRAY_SIZE(pwm_regs); chan++) {
if (READP(pwm_pins[chan]) != pin)
continue;
for (chan=0; ; chan++) {
if (chan >= ARRAY_SIZE(pwm_pins))
shutdown("Not a valid PWM pin");
if (READP(pwm_pins[chan]) == pin)
break;
}
// Map cycle_time to pwm clock divisor
const struct gpio_pwm_info *p = &pwm_regs[chan];
irqstatus_t flags = READP(p->flags), cs;
uint8_t flags = READP(p->flags), cs;
if (flags & GP_AFMT) {
switch (cycle_time) {
case 0 ... 8*510L - 1: cs = 1; break;
@@ -232,12 +198,12 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
}
volatile uint8_t *rega = READP(p->rega), *regb = READP(p->regb);
uint8_t en_bit = READP(p->en_bit);
struct gpio_digital_regs *regs = GPIO2REGS(pin);
uint8_t bit = GPIO2BIT(pin);
struct gpio_digital_regs *gpio_regs = GPIO2REGS(pin);
uint8_t gpio_bit = GPIO2BIT(pin);
struct gpio_pwm g = (struct gpio_pwm) {
(void*)READP(p->ocr), flags & GP_8BIT };
if (rega == &TCCR1A)
shutdown("Can not user timer1 for PWM; timer1 is used for timers");
shutdown("Can not use timer1 for PWM; timer1 is used for timers");
// Setup PWM timer
irqstatus_t flag = irq_save();
@@ -249,24 +215,62 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
// Set default value and enable output
gpio_pwm_write(g, val);
*rega |= (1<<WGM00) | en_bit;
regs->mode |= bit;
gpio_regs->mode |= gpio_bit;
irq_restore(flag);
return g;
}
shutdown("Not a valid PWM pin");
void
gpio_pwm_write(struct gpio_pwm g, uint8_t val)
{
if (g.size8) {
*(volatile uint8_t*)g.reg = val;
} else {
irqstatus_t flag = irq_save();
*(volatile uint16_t*)g.reg = val;
irq_restore(flag);
}
}
/****************************************************************
* Analog to Digital Converter (ADC) pins
****************************************************************/
static const uint8_t adc_pins[] PROGMEM = {
#if CONFIG_MACH_atmega168
GPIO('C', 0), GPIO('C', 1), GPIO('C', 2), GPIO('C', 3),
GPIO('C', 4), GPIO('C', 5), GPIO('E', 0), GPIO('E', 1),
#elif CONFIG_MACH_atmega644p
GPIO('A', 0), GPIO('A', 1), GPIO('A', 2), GPIO('A', 3),
GPIO('A', 4), GPIO('A', 5), GPIO('A', 6), GPIO('A', 7),
#elif CONFIG_MACH_at90usb1286
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
GPIO('K', 0), GPIO('K', 1), GPIO('K', 2), GPIO('K', 3),
GPIO('K', 4), GPIO('K', 5), GPIO('K', 6), GPIO('K', 7),
#endif
};
static const uint8_t ADMUX_DEFAULT = 0x40;
DECL_CONSTANT(ADC_MAX, 1024);
struct gpio_adc
gpio_adc_setup(uint8_t pin)
{
// Find pin in adc_pins table
uint8_t chan;
for (chan=0; chan<ARRAY_SIZE(adc_pins); chan++) {
if (READP(adc_pins[chan]) != pin)
continue;
for (chan=0; ; chan++) {
if (chan >= ARRAY_SIZE(adc_pins))
shutdown("Not a valid ADC pin");
if (READP(adc_pins[chan]) == pin)
break;
}
// Enable ADC
ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
@@ -281,8 +285,6 @@ gpio_adc_setup(uint8_t pin)
return (struct gpio_adc){ chan };
}
shutdown("Not a valid ADC pin");
}
enum { ADC_DUMMY=0xff };
static uint8_t last_analog_read = ADC_DUMMY;
@@ -335,6 +337,18 @@ gpio_adc_cancel_sample(struct gpio_adc g)
}
/****************************************************************
* Serial Peripheral Interface (SPI) hardware
****************************************************************/
#if CONFIG_MACH_atmega168
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
#elif CONFIG_MACH_atmega644p
static const uint8_t SS = GPIO('B', 4), SCK = GPIO('B', 7), MOSI = GPIO('B', 5);
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
#endif
void
spi_config(void)
{

View File

@@ -4,9 +4,13 @@
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "autoconf.h" // CONFIG_MCU
#include "command.h" // DECL_CONSTANT
#include "irq.h" // irq_enable
#include "sched.h" // sched_main
DECL_CONSTANT(MCU, CONFIG_MCU);
// Main entry point for avr code.
int
main(void)

View File

@@ -30,10 +30,12 @@ serial_init(void)
{
if (CONFIG_SERIAL_BAUD_U2X) {
UCSR0A = 1<<U2X0;
UBRR0 = DIV_ROUND_CLOSEST(F_CPU, 8UL * CONFIG_SERIAL_BAUD) - 1UL;
UBRR0 = DIV_ROUND_CLOSEST(
CONFIG_CLOCK_FREQ, 8UL * CONFIG_SERIAL_BAUD) - 1UL;
} else {
UCSR0A = 0;
UBRR0 = DIV_ROUND_CLOSEST(F_CPU, 16UL * CONFIG_SERIAL_BAUD) - 1UL;
UBRR0 = DIV_ROUND_CLOSEST(
CONFIG_CLOCK_FREQ, 16UL * CONFIG_SERIAL_BAUD) - 1UL;
}
UCSR0C = (1<<UCSZ01) | (1<<UCSZ00);
@@ -115,7 +117,7 @@ char *
console_get_output(uint8_t len)
{
uint8_t tpos = readb(&transmit_pos), tmax = readb(&transmit_max);
if (tpos == tmax) {
if (tpos >= tmax) {
tpos = tmax = 0;
writeb(&transmit_max, 0);
writeb(&transmit_pos, 0);
@@ -126,12 +128,10 @@ console_get_output(uint8_t len)
return NULL;
// Disable TX irq and move buffer
writeb(&transmit_max, 0);
barrier();
tpos = readb(&transmit_pos);
tmax -= tpos;
memmove(&transmit_buf[0], &transmit_buf[tpos], tmax);
writeb(&transmit_pos, 0);
barrier();
writeb(&transmit_max, tmax);
enable_tx_irq();
return &transmit_buf[tmax];

View File

@@ -16,14 +16,38 @@
* Low level timer code
****************************************************************/
DECL_CONSTANT(CLOCK_FREQ, F_CPU);
DECL_CONSTANT(MCU, CONFIG_MCU);
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
// Return the number of clock ticks for a given number of microseconds
uint32_t
timer_from_us(uint32_t us)
{
return us * (F_CPU / 1000000);
return us * (CONFIG_CLOCK_FREQ / 1000000);
}
union u32_u {
struct { uint8_t b0, b1, b2, b3; };
struct { uint16_t lo, hi; };
uint32_t val;
};
// Return true if time1 is before time2. Always use this function to
// compare times as regular C comparisons can fail if the counter
// rolls over.
uint8_t __always_inline
timer_is_before(uint32_t time1, uint32_t time2)
{
// This asm is equivalent to:
// return (int32_t)(time1 - time2) < 0;
// But gcc doesn't do a good job with the above, so it's hand coded.
union u32_u utime1 = { .val = time1 };
uint8_t f = utime1.b3;
asm(" cp %A1, %A2\n"
" cpc %B1, %B2\n"
" cpc %C1, %C2\n"
" sbc %0, %D2"
: "+r"(f) : "r"(time1), "r"(time2));
return (int8_t)f < 0;
}
static inline uint16_t
@@ -38,13 +62,6 @@ timer_set(uint16_t next)
OCR1A = next;
}
static inline void
timer_set_clear(uint16_t next)
{
OCR1A = next;
TIFR1 = 1<<OCF1A;
}
static inline void
timer_repeat_set(uint16_t next)
{
@@ -53,10 +70,16 @@ timer_repeat_set(uint16_t next)
TIFR1 = 1<<OCF1B;
}
ISR(TIMER1_COMPA_vect)
// Reset the timer - clear settings and dispatch next timer immediately
static void
timer_reset(void)
{
sched_timer_kick();
uint16_t now = timer_get();
timer_set(now + 50);
TIFR1 = 1<<OCF1A;
timer_repeat_set(now + 50);
}
DECL_SHUTDOWN(timer_reset);
static void
timer_init(void)
@@ -73,8 +96,13 @@ timer_init(void)
TCCR1A = 0;
// Normal Mode
TCCR1B = 1<<CS10;
// Setup for first irq
irqstatus_t flag = irq_save();
timer_reset();
TIFR1 = 1<<TOV1;
// enable interrupt
TIMSK1 = 1<<OCIE1A;
irq_restore(flag);
}
DECL_INIT(timer_init);
@@ -83,20 +111,19 @@ DECL_INIT(timer_init);
* 32bit timer wrappers
****************************************************************/
static uint32_t timer_last;
static uint16_t timer_high;
// Return the 32bit current time given the 16bit current time.
static __always_inline uint32_t
calc_time(uint32_t last, uint16_t cur)
// Return the current time (in absolute clock ticks).
uint32_t
timer_read_time(void)
{
union u32_u16_u {
struct { uint16_t lo, hi; };
uint32_t val;
} calc;
calc.val = last;
if (cur < calc.lo)
irqstatus_t flag = irq_save();
union u32_u calc;
calc.val = timer_get();
calc.hi = timer_high;
if (TIFR1 & (1<<TOV1) && calc.lo < 0x8000)
calc.hi++;
calc.lo = cur;
irq_restore(flag);
return calc.val;
}
@@ -104,39 +131,11 @@ calc_time(uint32_t last, uint16_t cur)
void
timer_periodic(void)
{
timer_last = calc_time(timer_last, timer_get());
if (TIFR1 & (1<<TOV1)) {
// Hardware timer has overflowed - update overflow counter
TIFR1 = 1<<TOV1;
timer_high++;
}
// Return the current time (in absolute clock ticks).
uint32_t
timer_read_time(void)
{
irqstatus_t flag = irq_save();
uint16_t cur = timer_get();
uint32_t last = timer_last;
irq_restore(flag);
return calc_time(last, cur);
}
#define TIMER_MIN_TICKS 100
// Set the next timer wake time (in absolute clock ticks). Caller
// must disable irqs. The caller should not schedule a time more than
// a few milliseconds in the future.
uint8_t
timer_set_next(uint32_t next)
{
uint16_t cur = timer_get();
if ((int16_t)(OCR1A - cur) < 0 && !(TIFR1 & (1<<OCF1A)))
// Already processing timer irqs
try_shutdown("timer_set_next called during timer dispatch");
uint32_t mintime = calc_time(timer_last, cur + TIMER_MIN_TICKS);
if (sched_is_before(mintime, next)) {
timer_set_clear(next);
return 0;
}
timer_set_clear(mintime);
return 1;
}
#define TIMER_IDLE_REPEAT_TICKS 8000
@@ -145,45 +144,51 @@ timer_set_next(uint32_t next)
#define TIMER_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress
#define TIMER_DEFER_REPEAT_TICKS 200
// Similar to timer_set_next(), but wait for the given time if it is
// in the near future.
uint8_t
timer_try_set_next(uint32_t target)
// Hardware timer IRQ handler - dispatch software timers
ISR(TIMER1_COMPA_vect)
{
uint16_t next = target, now = timer_get();
int16_t diff = next - now;
if (diff > TIMER_MIN_TRY_TICKS)
// Schedule next timer normally.
uint16_t next;
for (;;) {
// Run the next software timer
next = sched_timer_dispatch();
int16_t diff = timer_get() - next;
if (likely(diff >= 0)) {
// Another timer is pending - briefly allow irqs to fire
irq_enable();
if (unlikely(TIFR1 & (1<<OCF1B)))
// Too many repeat timers - must exit irq handler
goto force_defer;
irq_disable();
continue;
}
if (likely(diff <= -TIMER_MIN_TRY_TICKS))
// Schedule next timer normally
goto done;
// Next timer is in the past or near future - can't reschedule to it
if (!(TIFR1 & (1<<OCF1B))) {
// Can run more timers from this irq; briefly allow irqs
// Next timer in very near future - wait for it to be ready
do {
irq_enable();
asm("nop");
irq_disable();
while (diff >= 0) {
// Next timer is in the near future - wait for time to occur
now = timer_get();
irq_enable();
diff = next - now;
if (unlikely(TIFR1 & (1<<OCF1B)))
goto force_defer;
irq_disable();
diff = timer_get() - next;
} while (diff < 0);
}
return 0;
}
if (diff < (int16_t)(-timer_from_us(1000)))
goto fail;
force_defer:
// Too many repeat timers - force a pause so tasks aren't starved
irq_disable();
uint16_t now = timer_get();
if ((int16_t)(next - now) < (int16_t)(-timer_from_us(1000)))
shutdown("Rescheduled timer in the past");
timer_repeat_set(now + TIMER_REPEAT_TICKS);
next = now + TIMER_DEFER_REPEAT_TICKS;
done:
timer_set(next);
return 1;
fail:
shutdown("Rescheduled timer in the past");
return;
}
// Periodic background task that temporarily boosts priority of

View File

@@ -7,6 +7,7 @@
#include <avr/interrupt.h> // WDT_vect
#include <avr/wdt.h> // wdt_enable
#include "command.h" // shutdown
#include "irq.h" // irq_disable
#include "sched.h" // DECL_TASK
static uint8_t watchdog_shutdown;
@@ -22,7 +23,7 @@ watchdog_reset(void)
{
wdt_reset();
if (watchdog_shutdown) {
WDTCSR |= 1<<WDIE;
WDTCSR = 1<<WDIE;
watchdog_shutdown = 0;
}
}
@@ -33,6 +34,25 @@ watchdog_init(void)
{
// 0.5s timeout, interrupt and system reset
wdt_enable(WDTO_500MS);
WDTCSR |= 1<<WDIE;
WDTCSR = 1<<WDIE;
}
DECL_INIT(watchdog_init);
// Very early reset of the watchdog
void __attribute__((naked)) __visible __section(".init3")
watchdog_early_init(void)
{
MCUSR = 0;
wdt_disable();
}
// Support reset on AVR via the watchdog timer
void
command_reset(uint32_t *args)
{
irq_disable();
wdt_enable(WDTO_15MS);
for (;;)
;
}
DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "reset");

View File

@@ -5,8 +5,8 @@
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stdlib.h> // malloc
#include <string.h> // memcpy
#include "basecmd.h" // lookup_oid
#include <string.h> // memset
#include "basecmd.h" // oid_lookup
#include "board/irq.h" // irq_save
#include "board/misc.h" // alloc_maxsize
#include "command.h" // DECL_COMMAND
@@ -17,26 +17,53 @@
* Move queue
****************************************************************/
static struct move *move_list, *move_free_list;
static uint16_t move_count;
struct move_freed {
struct move_freed *next;
};
void
move_free(struct move *m)
static struct move_freed *move_free_list;
static void *move_list;
static uint16_t move_count;
static uint8_t move_item_size;
// Is the config and move queue finalized?
static int
is_finalized(void)
{
m->next = move_free_list;
move_free_list = m;
return !!move_count;
}
struct move *
// Free previously allocated storage from move_alloc(). Caller must
// disable irqs.
void
move_free(void *m)
{
struct move_freed *mf = m;
mf->next = move_free_list;
move_free_list = mf;
}
// Allocate runtime storage
void *
move_alloc(void)
{
irqstatus_t flag = irq_save();
struct move *m = move_free_list;
if (!m)
struct move_freed *mf = move_free_list;
if (!mf)
shutdown("Move queue empty");
move_free_list = m->next;
move_free_list = mf->next;
irq_restore(flag);
return m;
return mf;
}
// Request minimum size of runtime allocations returned by move_alloc()
void
move_request_size(int size)
{
if (size > UINT8_MAX || is_finalized())
shutdown("Invalid move request size");
if (size > move_item_size)
move_item_size = size;
}
static void
@@ -46,13 +73,28 @@ move_reset(void)
return;
// Add everything in move_list to the free list.
uint32_t i;
for (i=0; i<move_count-1; i++)
move_list[i].next = &move_list[i+1];
move_list[move_count-1].next = NULL;
move_free_list = &move_list[0];
for (i=0; i<move_count-1; i++) {
struct move_freed *mf = move_list + i*move_item_size;
mf->next = move_list + (i + 1)*move_item_size;
}
struct move_freed *mf = move_list + (move_count - 1)*move_item_size;
mf->next = NULL;
move_free_list = move_list;
}
DECL_SHUTDOWN(move_reset);
static void
move_finalize(void)
{
move_request_size(sizeof(*move_free_list));
uint16_t count = alloc_maxsize(move_item_size*1024) / move_item_size;
move_list = malloc(count * move_item_size);
if (!count || !move_list)
shutdown("move queue malloc failed");
move_count = count;
move_reset();
}
/****************************************************************
* Generic object ids (oid)
@@ -63,45 +105,43 @@ struct oid_s {
};
static struct oid_s *oids;
static uint8_t num_oid;
static uint32_t config_crc;
static uint8_t config_finalized;
static uint8_t oid_count;
void *
lookup_oid(uint8_t oid, void *type)
oid_lookup(uint8_t oid, void *type)
{
if (oid >= num_oid || type != oids[oid].type)
if (oid >= oid_count || type != oids[oid].type)
shutdown("Invalid oid type");
return oids[oid].data;
}
static void
assign_oid(uint8_t oid, void *type, void *data)
oid_assign(uint8_t oid, void *type, void *data)
{
if (oid >= num_oid || oids[oid].type || config_finalized)
if (oid >= oid_count || oids[oid].type || is_finalized())
shutdown("Can't assign oid");
oids[oid].type = type;
oids[oid].data = data;
}
void *
alloc_oid(uint8_t oid, void *type, uint16_t size)
oid_alloc(uint8_t oid, void *type, uint16_t size)
{
void *data = malloc(size);
if (!data)
shutdown("malloc failed");
memset(data, 0, size);
assign_oid(oid, type, data);
oid_assign(oid, type, data);
return data;
}
void *
next_oid(uint8_t *i, void *type)
oid_next(uint8_t *i, void *type)
{
uint8_t oid = *i;
for (;;) {
oid++;
if (oid >= num_oid)
if (oid >= oid_count)
return NULL;
if (oids[oid].type == type) {
*i = oid;
@@ -120,31 +160,32 @@ command_allocate_oids(uint32_t *args)
if (!oids)
shutdown("malloc failed");
memset(oids, 0, sizeof(oids[0]) * count);
num_oid = count;
oid_count = count;
}
DECL_COMMAND(command_allocate_oids, "allocate_oids count=%c");
/****************************************************************
* Config CRC
****************************************************************/
static uint32_t config_crc;
void
command_get_config(uint32_t *args)
{
sendf("config is_config=%c crc=%u move_count=%hu"
, config_finalized, config_crc, move_count);
, is_finalized(), config_crc, move_count);
}
DECL_COMMAND_FLAGS(command_get_config, HF_IN_SHUTDOWN, "get_config");
void
command_finalize_config(uint32_t *args)
{
if (!oids || config_finalized)
if (!oids || is_finalized())
shutdown("Can't finalize");
uint16_t count = alloc_maxsize(sizeof(*move_list)*1024) / sizeof(*move_list);
move_list = malloc(count * sizeof(*move_list));
if (!count || !move_list)
shutdown("malloc failed");
move_count = count;
move_reset();
move_finalize();
config_crc = args[0];
config_finalized = 1;
command_get_config(NULL);
}
DECL_COMMAND(command_finalize_config, "finalize_config crc=%u");
@@ -168,7 +209,7 @@ command_start_group(uint32_t *args)
sched_del_timer(&group_timer);
group_timer.func = group_end_event;
group_timer.waketime = args[0];
sched_timer(&group_timer);
sched_add_timer(&group_timer);
}
DECL_COMMAND(command_start_group, "start_group clock=%u");
@@ -187,28 +228,51 @@ DECL_COMMAND(command_end_group, "end_group");
void
command_get_status(uint32_t *args)
{
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
sendf("status clock=%u status=%c", timer_read_time(), sched_is_shutdown());
}
DECL_COMMAND_FLAGS(command_get_status, HF_IN_SHUTDOWN, "get_status");
static uint32_t stats_send_time, stats_send_time_high;
void
command_get_uptime(uint32_t *args)
{
uint32_t cur = timer_read_time();
uint32_t high = stats_send_time_high + (cur < stats_send_time);
sendf("uptime high=%u clock=%u", high, cur);
}
DECL_COMMAND_FLAGS(command_get_uptime, HF_IN_SHUTDOWN, "get_uptime");
#define SUMSQ_BASE 256
DECL_CONSTANT(STATS_SUMSQ_BASE, SUMSQ_BASE);
static void
stats_task(void)
{
static uint32_t last, count, sumsq;
uint32_t cur = sched_read_time();
uint32_t diff = (cur - last) >> 8;
uint32_t cur = timer_read_time();
uint32_t diff = cur - last;
last = cur;
count++;
uint32_t nextsumsq = sumsq + diff*diff;
// Calculate sum of diff^2 - be careful of integer overflow
uint32_t nextsumsq;
if (diff <= 0xffff) {
nextsumsq = sumsq + DIV_ROUND_UP(diff * diff, SUMSQ_BASE);
} else if (diff <= 0xfffff) {
nextsumsq = sumsq + DIV_ROUND_UP(diff, SUMSQ_BASE) * diff;
} else {
nextsumsq = 0xffffffff;
}
if (nextsumsq < sumsq)
nextsumsq = 0xffffffff;
sumsq = nextsumsq;
static uint32_t prev;
if (sched_is_before(cur, prev + sched_from_us(5000000)))
if (timer_is_before(cur, stats_send_time + timer_from_us(5000000)))
return;
sendf("stats count=%u sum=%u sumsq=%u", count, cur - prev, sumsq);
prev = cur;
sendf("stats count=%u sum=%u sumsq=%u", count, cur - stats_send_time, sumsq);
if (cur < stats_send_time)
stats_send_time_high++;
stats_send_time = cur;
count = sumsq = 0;
}
DECL_TASK(stats_task);
@@ -258,18 +322,26 @@ command_debug_write16(uint32_t *args)
DECL_COMMAND_FLAGS(command_debug_write16, HF_IN_SHUTDOWN,
"debug_write16 addr=%u val=%u");
void
command_debug_ping(uint32_t *args)
{
uint8_t len = args[0];
char *data = (void*)(size_t)args[1];
sendf("pong data=%*s", len, data);
}
DECL_COMMAND_FLAGS(command_debug_ping, HF_IN_SHUTDOWN, "debug_ping data=%*s");
void
command_debug_nop(uint32_t *args)
{
}
DECL_COMMAND_FLAGS(command_debug_nop, HF_IN_SHUTDOWN, "debug_nop data=%*s");
/****************************************************************
* Misc commands
****************************************************************/
void
command_reset(uint32_t *args)
{
// XXX - implement reset
}
DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "msg_reset");
void
command_emergency_stop(uint32_t *args)
{

View File

@@ -3,21 +3,14 @@
#include <stdint.h> // uint8_t
struct move {
uint32_t interval;
int16_t add;
uint16_t count;
struct move *next;
uint8_t flags;
};
void move_free(struct move *m);
struct move *move_alloc(void);
void *lookup_oid(uint8_t oid, void *type);
void *alloc_oid(uint8_t oid, void *type, uint16_t size);
void *next_oid(uint8_t *i, void *type);
void move_free(void *m);
void *move_alloc(void);
void move_request_size(int size);
void *oid_lookup(uint8_t oid, void *type);
void *oid_alloc(uint8_t oid, void *type, uint16_t size);
void *oid_next(uint8_t *i, void *type);
#define foreach_oid(pos,data,oidtype) \
for (pos=-1; (data=next_oid(&pos, oidtype)); )
for (pos=-1; (data=oid_next(&pos, oidtype)); )
#endif // basecmd.h

View File

@@ -4,12 +4,9 @@
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <ctype.h> // isspace
#include <stdarg.h> // va_start
#include <stdio.h> // vsnprintf
#include <stdlib.h> // strtod
#include <string.h> // strcasecmp
#include "board/irq.h" // irq_disable
#include <string.h> // memcpy
#include "board/io.h" // readb
#include "board/misc.h" // crc16_ccitt
#include "board/pgm.h" // READP
#include "command.h" // output_P
@@ -110,15 +107,23 @@ error:
shutdown("Command parser error");
}
static uint8_t in_sendf;
// Encode a message and transmit it
void
_sendf(uint8_t parserid, ...)
{
if (readb(&in_sendf))
// This sendf call was made from an irq handler while the main
// code was already in sendf - just drop this sendf request.
return;
writeb(&in_sendf, 1);
const struct command_encoder *cp = &command_encoders[parserid];
uint8_t max_size = READP(cp->max_size);
char *buf = console_get_output(max_size + MESSAGE_MIN);
if (!buf)
return;
goto done;
char *p = &buf[MESSAGE_HEADER_SIZE];
if (max_size) {
char *maxend = &p[max_size];
@@ -140,7 +145,10 @@ _sendf(uint8_t parserid, ...)
case PT_int16:
case PT_byte:
if (t >= PT_uint16)
v = va_arg(args, int) & 0xffff;
if (t == PT_int16)
v = (int32_t)va_arg(args, int);
else
v = va_arg(args, unsigned int);
else
v = va_arg(args, uint32_t);
p = encode_int(p, v);
@@ -182,11 +190,20 @@ _sendf(uint8_t parserid, ...)
*p++ = crc;
*p++ = MESSAGE_SYNC;
console_push_output(msglen);
done:
writeb(&in_sendf, 0);
return;
error:
shutdown("Message encode error");
}
static void
sendf_shutdown(void)
{
writeb(&in_sendf, 0);
}
DECL_SHUTDOWN(sendf_shutdown);
/****************************************************************
* Command routing
@@ -293,6 +310,5 @@ command_task(void)
func(args);
}
console_pop_input(msglen);
return;
}
DECL_TASK(command_task);

View File

@@ -4,8 +4,7 @@
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stddef.h> // offsetof
#include "basecmd.h" // alloc_oid
#include "basecmd.h" // oid_alloc
#include "board/gpio.h" // struct gpio
#include "board/irq.h" // irq_disable
#include "command.h" // DECL_COMMAND
@@ -15,12 +14,22 @@
struct end_stop {
struct timer time;
uint32_t rest_time;
struct stepper *stepper;
struct gpio_in pin;
uint8_t pin_value, flags;
uint8_t flags, stepper_count;
struct stepper *steppers[0];
};
enum { ESF_HOMING=1, ESF_REPORT=2 };
enum { ESF_PIN_HIGH=1<<0, ESF_HOMING=1<<1, ESF_REPORT=1<<2 };
static void noinline
stop_steppers(struct end_stop *e)
{
e->flags = ESF_REPORT;
uint8_t count = e->stepper_count;
while (count--)
if (e->steppers[count])
stepper_stop(e->steppers[count]);
}
// Timer callback for an end stop
static uint_fast8_t
@@ -28,33 +37,46 @@ end_stop_event(struct timer *t)
{
struct end_stop *e = container_of(t, struct end_stop, time);
uint8_t val = gpio_in_read(e->pin);
if (val != e->pin_value) {
if ((val ? ~e->flags : e->flags) & ESF_PIN_HIGH) {
// No match - reschedule for the next attempt
e->time.waketime += e->rest_time;
return SF_RESCHEDULE;
}
// Stop stepper
e->flags = ESF_REPORT;
stepper_stop(e->stepper);
stop_steppers(e);
return SF_DONE;
}
void
command_config_end_stop(uint32_t *args)
{
struct end_stop *e = alloc_oid(args[0], command_config_end_stop, sizeof(*e));
struct stepper *s = lookup_oid(args[3], command_config_stepper);
uint8_t stepper_count = args[3];
struct end_stop *e = oid_alloc(
args[0], command_config_end_stop
, sizeof(*e) + sizeof(e->steppers[0]) * stepper_count);
e->time.func = end_stop_event;
e->stepper = s;
e->pin = gpio_in_setup(args[1], args[2]);
e->stepper_count = stepper_count;
}
DECL_COMMAND(command_config_end_stop,
"config_end_stop oid=%c pin=%c pull_up=%c stepper_oid=%c");
"config_end_stop oid=%c pin=%c pull_up=%c stepper_count=%c");
void
command_end_stop_set_stepper(uint32_t *args)
{
struct end_stop *e = oid_lookup(args[0], command_config_end_stop);
uint8_t pos = args[1];
if (pos >= e->stepper_count)
shutdown("Set stepper past maximum stepper count");
e->steppers[pos] = stepper_oid_lookup(args[2]);
}
DECL_COMMAND(command_end_stop_set_stepper,
"end_stop_set_stepper oid=%c pos=%c stepper_oid=%c");
// Home an axis
void
command_end_stop_home(uint32_t *args)
{
struct end_stop *e = lookup_oid(args[0], command_config_end_stop);
struct end_stop *e = oid_lookup(args[0], command_config_end_stop);
sched_del_timer(&e->time);
e->time.waketime = args[1];
e->rest_time = args[2];
@@ -63,9 +85,8 @@ command_end_stop_home(uint32_t *args)
e->flags = 0;
return;
}
e->pin_value = args[3];
e->flags = ESF_HOMING;
sched_timer(&e->time);
e->flags = ESF_HOMING | (args[3] ? ESF_PIN_HIGH : 0);
sched_add_timer(&e->time);
}
DECL_COMMAND(command_end_stop_home,
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c");
@@ -74,21 +95,19 @@ static void
end_stop_report(uint8_t oid, struct end_stop *e)
{
irq_disable();
uint32_t position = stepper_get_position(e->stepper);
uint8_t eflags = e->flags;
e->flags &= ~ESF_REPORT;
irq_enable();
sendf("end_stop_state oid=%c homing=%c pin=%c pos=%i"
, oid, !!(eflags & ESF_HOMING), gpio_in_read(e->pin)
, position - STEPPER_POSITION_BIAS);
sendf("end_stop_state oid=%c homing=%c pin=%c"
, oid, !!(eflags & ESF_HOMING), gpio_in_read(e->pin));
}
void
command_end_stop_query(uint32_t *args)
{
uint8_t oid = args[0];
struct end_stop *e = lookup_oid(oid, command_config_end_stop);
struct end_stop *e = oid_lookup(oid, command_config_end_stop);
end_stop_report(oid, e);
}
DECL_COMMAND(command_end_stop_query, "end_stop_query oid=%c");

58
src/generic/armcm_irq.c Normal file
View File

@@ -0,0 +1,58 @@
// Definitions for irq enable/disable on ARM Cortex-M processors
//
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "irq.h" // irqstatus_t
#include "sched.h" // DECL_SHUTDOWN
void
irq_disable(void)
{
asm volatile("cpsid i" ::: "memory");
}
void
irq_enable(void)
{
asm volatile("cpsie i" ::: "memory");
}
irqstatus_t
irq_save(void)
{
irqstatus_t flag;
asm volatile("mrs %0, primask" : "=r" (flag) :: "memory");
irq_disable();
return flag;
}
void
irq_restore(irqstatus_t flag)
{
asm volatile("msr primask, %0" :: "r" (flag) : "memory");
}
// Clear the active irq if a shutdown happened in an irq handler
static void
clear_active_irq(void)
{
uint32_t psr;
asm volatile("mrs %0, psr" : "=r" (psr));
if (!(psr & 0x1ff))
// Shutdown did not occur in an irq - nothing to do.
return;
// Clear active irq status
psr = 1<<24; // T-bit
uint32_t temp;
asm volatile(
" push { %1 }\n"
" adr %0, 1f\n"
" push { %0 }\n"
" push { r0, r1, r2, r3, r12, lr }\n"
" bx %2\n"
"1:\n"
: "=&r"(temp) : "r"(psr), "r"(0xfffffff9) : "cc");
}
DECL_SHUTDOWN(clear_active_irq);

View File

@@ -2,24 +2,34 @@
#define __GENERIC_IO_H
#include <stdint.h> // uint32_t
#include "compiler.h" // barrier
static inline void writel(void *addr, uint32_t val) {
barrier();
*(volatile uint32_t *)addr = val;
}
static inline void writew(void *addr, uint16_t val) {
barrier();
*(volatile uint16_t *)addr = val;
}
static inline void writeb(void *addr, uint8_t val) {
barrier();
*(volatile uint8_t *)addr = val;
}
static inline uint32_t readl(const void *addr) {
return *(volatile const uint32_t *)addr;
uint32_t val = *(volatile const uint32_t *)addr;
barrier();
return val;
}
static inline uint16_t readw(const void *addr) {
return *(volatile const uint16_t *)addr;
uint16_t val = *(volatile const uint16_t *)addr;
barrier();
return val;
}
static inline uint8_t readb(const void *addr) {
return *(volatile const uint8_t *)addr;
uint8_t val = *(volatile const uint8_t *)addr;
barrier();
return val;
}
#endif // io.h

View File

@@ -10,10 +10,9 @@ char *console_get_output(uint8_t len);
void console_push_output(uint8_t len);
uint32_t timer_from_us(uint32_t us);
void timer_periodic(void);
uint8_t timer_is_before(uint32_t time1, uint32_t time2);
uint32_t timer_read_time(void);
uint8_t timer_set_next(uint32_t next);
uint8_t timer_try_set_next(uint32_t next);
void timer_periodic(void);
size_t alloc_maxsize(size_t reqsize);

99
src/generic/timer_irq.c Normal file
View File

@@ -0,0 +1,99 @@
// Generic interrupt based timer helper functions
//
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_from_us
#include "board/timer_irq.h" // timer_dispatch_many
#include "command.h" // shutdown
#include "sched.h" // sched_timer_kick
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
// Return the number of clock ticks for a given number of microseconds
uint32_t
timer_from_us(uint32_t us)
{
return us * (CONFIG_CLOCK_FREQ / 1000000);
}
// Return true if time1 is before time2. Always use this function to
// compare times as regular C comparisons can fail if the counter
// rolls over.
uint8_t
timer_is_before(uint32_t time1, uint32_t time2)
{
return (int32_t)(time1 - time2) < 0;
}
// Called by main code once every millisecond. (IRQs disabled.)
void
timer_periodic(void)
{
}
static uint32_t timer_repeat_until;
#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
#define TIMER_REPEAT_TICKS timer_from_us(100)
#define TIMER_MIN_TRY_TICKS timer_from_us(1)
#define TIMER_DEFER_REPEAT_TICKS timer_from_us(5)
// Reschedule timers after a brief pause to prevent task starvation
static uint32_t noinline
force_defer(uint32_t next)
{
uint32_t now = timer_read_time();
if (timer_is_before(next + timer_from_us(1000), now))
shutdown("Rescheduled timer in the past");
timer_repeat_until = now + TIMER_REPEAT_TICKS;
return now + TIMER_DEFER_REPEAT_TICKS;
}
// Invoke timers - called from board irq code.
uint32_t
timer_dispatch_many(void)
{
uint32_t tru = timer_repeat_until;
for (;;) {
// Run the next software timer
uint32_t next = sched_timer_dispatch();
uint32_t now = timer_read_time();
int32_t diff = next - now;
if (diff > (int32_t)TIMER_MIN_TRY_TICKS)
// Schedule next timer normally.
return next;
if (unlikely(timer_is_before(tru, now)))
// Too many repeat timers from a single interrupt - force a pause
return force_defer(next);
// Next timer in the past or near future - wait for it to be ready
irq_enable();
while (unlikely(diff > 0))
diff = next - timer_read_time();
irq_disable();
}
}
// Periodic background task that temporarily boosts priority of
// timers. This helps prioritize timers when tasks are idling.
static void
timer_task(void)
{
irq_disable();
timer_repeat_until = timer_read_time() + TIMER_IDLE_REPEAT_TICKS;
irq_enable();
}
DECL_TASK(timer_task);
static void
timer_irq_shutdown(void)
{
timer_repeat_until = timer_read_time() + TIMER_IDLE_REPEAT_TICKS;
}
DECL_SHUTDOWN(timer_irq_shutdown);

6
src/generic/timer_irq.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef __GENERIC_TIMER_IRQ_H
#define __GENERIC_TIMER_IRQ_H
uint32_t timer_dispatch_many(void);
#endif // timer_irq.h

View File

@@ -4,11 +4,12 @@
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "basecmd.h" // alloc_oid
#include "basecmd.h" // oid_alloc
#include "board/gpio.h" // struct gpio_out
#include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_is_before
#include "command.h" // DECL_COMMAND
#include "sched.h" // sched_timer
#include "sched.h" // sched_add_timer
/****************************************************************
@@ -43,7 +44,7 @@ digital_out_event(struct timer *timer)
void
command_config_digital_out(uint32_t *args)
{
struct digital_out_s *d = alloc_oid(args[0], command_config_digital_out
struct digital_out_s *d = oid_alloc(args[0], command_config_digital_out
, sizeof(*d));
d->default_value = args[2];
d->pin = gpio_out_setup(args[1], d->default_value);
@@ -56,12 +57,12 @@ DECL_COMMAND(command_config_digital_out,
void
command_schedule_digital_out(uint32_t *args)
{
struct digital_out_s *d = lookup_oid(args[0], command_config_digital_out);
struct digital_out_s *d = oid_lookup(args[0], command_config_digital_out);
sched_del_timer(&d->timer);
d->timer.func = digital_out_event;
d->timer.waketime = args[1];
d->value = args[2];
sched_timer(&d->timer);
sched_add_timer(&d->timer);
}
DECL_COMMAND(command_schedule_digital_out,
"schedule_digital_out oid=%c clock=%u value=%c");
@@ -117,7 +118,7 @@ soft_pwm_toggle_event(struct timer *timer)
waketime += s->on_duration;
else
waketime += s->off_duration;
if (s->flags & SPF_CHECK_END && !sched_is_before(waketime, s->end_time)) {
if (s->flags & SPF_CHECK_END && !timer_is_before(waketime, s->end_time)) {
// End of normal pulsing - next event loads new pwm settings
s->timer.func = soft_pwm_load_event;
waketime = s->end_time;
@@ -155,7 +156,7 @@ soft_pwm_load_event(struct timer *timer)
void
command_config_soft_pwm_out(uint32_t *args)
{
struct soft_pwm_s *s = alloc_oid(args[0], command_config_soft_pwm_out
struct soft_pwm_s *s = oid_alloc(args[0], command_config_soft_pwm_out
, sizeof(*s));
s->cycle_time = args[2];
s->pulse_time = s->cycle_time / 255;
@@ -171,7 +172,7 @@ DECL_COMMAND(command_config_soft_pwm_out,
void
command_schedule_soft_pwm_out(uint32_t *args)
{
struct soft_pwm_s *s = lookup_oid(args[0], command_config_soft_pwm_out);
struct soft_pwm_s *s = oid_lookup(args[0], command_config_soft_pwm_out);
uint32_t time = args[1];
uint8_t value = args[2];
uint8_t next_flags = SPF_CHECK_END | SPF_HAVE_NEXT;
@@ -189,20 +190,20 @@ command_schedule_soft_pwm_out(uint32_t *args)
next_flags |= SPF_NEXT_CHECK_END;
}
irq_disable();
if (s->flags & SPF_CHECK_END && sched_is_before(s->end_time, time))
if (s->flags & SPF_CHECK_END && timer_is_before(s->end_time, time))
shutdown("next soft pwm extends existing pwm");
s->end_time = time;
s->next_on_duration = next_on_duration;
s->next_off_duration = next_off_duration;
s->flags |= next_flags;
if (s->flags & SPF_TOGGLING && sched_is_before(s->timer.waketime, time)) {
if (s->flags & SPF_TOGGLING && timer_is_before(s->timer.waketime, time)) {
// soft_pwm_toggle_event() will schedule a load event when ready
} else {
// Schedule the loading of the pwm parameters at the requested time
sched_del_timer(&s->timer);
s->timer.waketime = time;
s->timer.func = soft_pwm_load_event;
sched_timer(&s->timer);
sched_add_timer(&s->timer);
}
irq_enable();
}

Some files were not shown because too many files have changed in this diff Show More